checkers-game 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module GUI
5
+ SQUARE_SIZE = 50
6
+ CIRCLE_TRANSLATION = SQUARE_SIZE * Integer.sqrt(2) / 2
7
+ RADIUS = 20
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module GUI
5
+ class Scene
6
+ extend Forwardable
7
+
8
+ attr_reader :board
9
+
10
+ def_delegators :board, :animation_queue
11
+
12
+ def initialize(state, game_enigne)
13
+ @state = state
14
+ @board = Board.new(state, game_enigne)
15
+ @allowed_squares = []
16
+ @allowed_moves = []
17
+ end
18
+
19
+ def handle_click(x, y)
20
+ row, col = click_board_indices(x, y)
21
+
22
+ if piece_clicked?(x, y)
23
+ @allowed_moves = @state.board.find_available_moves(row: row, col: col, player: :human)
24
+ @allowed_squares = @allowed_moves.map { |move| @board.square_at(*move.end_square) }
25
+ else
26
+ return if @allowed_squares.empty? && @allowed_moves.empty?
27
+
28
+ move_made = @allowed_moves.find { |move| move.end_square == [row, col] }
29
+ if move_made
30
+ @allowed_moves = []
31
+ @allowed_squares = []
32
+ new_board = Checkers::Board.make_move(@state.board, move_made)
33
+ turn = if new_board.jumped
34
+ new_board.any_jump_moves?(player: :human) ? :human : :ai
35
+ else
36
+ :ai
37
+ end
38
+ @state.set_state(board: new_board, turn: turn)
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def piece_clicked?(x, y)
46
+ @board.any? do |object|
47
+ object.contains?(x, y) && object.is_a?(Ruby2D::SquareWithPiece) && object.player == :human
48
+ end
49
+ end
50
+
51
+ def click_board_indices(x, y)
52
+ @board.find_index do |object|
53
+ object.contains?(x, y)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module GUI
5
+ class Scene
6
+ class Board
7
+ include Enumerable
8
+
9
+ attr_reader :animation_queue
10
+
11
+ def initialize(state, game_engine)
12
+ @state = state
13
+ @state.add_observer(self)
14
+ @game_engine = game_engine
15
+ @animation_queue = []
16
+ render_board
17
+ end
18
+
19
+ def update
20
+ return if @state.winner || @state.tie
21
+
22
+ check_win
23
+ check_tie
24
+
25
+ @animation_queue.unshift(
26
+ PieceAnimation.animate(self, @state.board.last_move) do
27
+ render_board
28
+ @game_engine.play
29
+ end
30
+ )
31
+ end
32
+
33
+ def each(&block)
34
+ @board_objects.each(&block)
35
+ end
36
+
37
+ def find_index(&block)
38
+ @board_objects.find_index(&block)
39
+ end
40
+
41
+ def clear
42
+ @board_objects.each(&:remove) if @board_objects&.any?
43
+ end
44
+
45
+ def square_at(row, col)
46
+ return @board_objects[row, col] if @board_objects[row, col].is_a?(Square)
47
+ end
48
+
49
+ def piece_at(row, col)
50
+ return @board_objects[row, col] if @board_objects[row, col].is_a?(Ruby2D::SquareWithPiece)
51
+ end
52
+
53
+ private
54
+
55
+ def check_win
56
+ human_pieces = @state.board.count_pieces(player: :human)
57
+ ai_pieces = @state.board.count_pieces(player: :ai)
58
+
59
+ @state.set_state(winner: :human) if ai_pieces.zero?
60
+ @state.set_state(winner: :ai) if human_pieces.zero?
61
+ end
62
+
63
+ def check_tie
64
+ @state.set_state(tie: true) if tie?
65
+ end
66
+
67
+ def tie?
68
+ return false unless @state.winner.nil?
69
+
70
+ if @state.board.find_moves_for_player(player: @state.turn).length.zero?
71
+ turn = @state.turn == :human ? :ai : :human
72
+
73
+ return true if @state.board.find_moves_for_player(player: turn).length.zero?
74
+ end
75
+
76
+ false
77
+ end
78
+
79
+ def render_board
80
+ clear
81
+ @board_objects = Matrix.zero(8)
82
+
83
+ x = y = 0
84
+ square_color = 'white'
85
+
86
+ @state.board.each_with_index do |piece, row, col|
87
+ x = col * SQUARE_SIZE
88
+ y = row * SQUARE_SIZE
89
+ @board_objects[row, col] = if piece.zero?
90
+ Square.new(x: x, y: y, size: SQUARE_SIZE, color: square_color)
91
+ else
92
+ Ruby2D::SquareWithPiece.new(
93
+ x: x,
94
+ y: y,
95
+ size: SQUARE_SIZE,
96
+ color: square_color,
97
+ piece: piece
98
+ )
99
+ end
100
+
101
+ square_color = square_color == 'white' ? 'black' : 'white'
102
+ square_color = square_color == 'white' ? 'black' : 'white' if col == @state.board.row_count - 1
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module GUI
5
+ class Scene
6
+ class PieceAnimation
7
+ attr_writer :animation_proc
8
+ attr_accessor :finished
9
+
10
+ def initialize
11
+ @finished = false
12
+ end
13
+
14
+ def call
15
+ @animation_proc.call
16
+ end
17
+
18
+ class << self
19
+ def animate(board, move)
20
+ object = board.piece_at(*move.start_square)&.piece
21
+ square = board.square_at(*move.end_square)
22
+ x = square.x + Checkers::GUI::CIRCLE_TRANSLATION - object.x
23
+ y = square.y + Checkers::GUI::CIRCLE_TRANSLATION - object.y
24
+ object.z = 10
25
+
26
+ animation = PieceAnimation.new
27
+
28
+ animation.animation_proc = proc do
29
+ unless x.zero?
30
+ if x.negative?
31
+ x += 1
32
+ object.x -= 1
33
+ else
34
+ x -= 1
35
+ object.x += 1
36
+ end
37
+ end
38
+
39
+ unless y.zero?
40
+ if y.negative?
41
+ y += 1
42
+ object.y -= 1
43
+ else
44
+ y -= 1
45
+ object.y += 1
46
+ end
47
+ end
48
+
49
+ if x.zero? && y.zero?
50
+ yield if block_given?
51
+ animation.finished = true
52
+ end
53
+ end
54
+
55
+ animation
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ class JumpMove < Move
5
+ attr_reader :jump_over_square
6
+
7
+ def initialize(start_square, end_square)
8
+ super
9
+ @jump_over_square = calculate_jump_over_square
10
+ end
11
+
12
+ private
13
+
14
+ def calculate_jump_over_square
15
+ [(end_square[0] + start_square[0]) / 2, (end_square[1] + start_square[1]) / 2]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ class Move
5
+ attr_reader :start_square, :end_square
6
+
7
+ def initialize(start_square, end_square)
8
+ @start_square = start_square
9
+ @end_square = end_square
10
+ end
11
+
12
+ def ==(other)
13
+ start_square == other.start_square && end_square == other.end_square
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module Ruby2D
5
+ class Piece < Circle
6
+ HUMAN_PIECE_COLOR = 'red'
7
+ HUMAN_KING_COLOR = 'maroon'
8
+ AI_PIECE_COLOR = 'yellow'
9
+ AI_KING_COLOR = 'orange'
10
+
11
+ attr_accessor :player
12
+
13
+ def initialize(opts = {})
14
+ @player = HUMAN_PIECES.include?(opts[:piece]) ? :human : :ai
15
+ super(opts.merge({ color: piece_color(opts[:piece]) }))
16
+ end
17
+
18
+ private
19
+
20
+ def piece_color(piece)
21
+ if HUMAN_PIECES.include?(piece)
22
+ piece == HUMAN_PIECE ? HUMAN_PIECE_COLOR : HUMAN_KING_COLOR
23
+ else
24
+ piece == AI_PIECE ? AI_PIECE_COLOR : AI_KING_COLOR
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ module Ruby2D
5
+ class SquareWithPiece < Square
6
+ extend Forwardable
7
+
8
+ def_delegators :@piece, :player
9
+ attr_reader :piece
10
+
11
+ def initialize(opts = {})
12
+ @piece = Piece.new(
13
+ x: opts[:x] + Checkers::GUI::CIRCLE_TRANSLATION,
14
+ y: opts[:y] + Checkers::GUI::CIRCLE_TRANSLATION,
15
+ z: 1,
16
+ radius: Checkers::GUI::RADIUS,
17
+ piece: opts[:piece]
18
+ )
19
+ super(opts.slice(:x, :y, :size, :color))
20
+ end
21
+
22
+ def remove
23
+ @piece.remove
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkers
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: checkers-game
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Grzegorz Jakubiak
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: zeitwerk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby2d
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.4
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.4
55
+ description: Game of checkers
56
+ email:
57
+ - grzegorz.jakubiak@outlook.com
58
+ executables:
59
+ - checkers
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rubocop.yml"
66
+ - ".tool-versions"
67
+ - ".travis.yml"
68
+ - CHANGELOG.md
69
+ - CODE_OF_CONDUCT.md
70
+ - LICENSE.txt
71
+ - README.md
72
+ - Rakefile
73
+ - bin/console
74
+ - bin/setup
75
+ - checkers-game.gemspec
76
+ - exe/checkers
77
+ - gems.locked
78
+ - gems.rb
79
+ - lib/checkers.rb
80
+ - lib/checkers/ai/engine/alphabeta.rb
81
+ - lib/checkers/ai/engine/base.rb
82
+ - lib/checkers/ai/engine/minmax.rb
83
+ - lib/checkers/ai/node.rb
84
+ - lib/checkers/ai/tree.rb
85
+ - lib/checkers/board.rb
86
+ - lib/checkers/board/moves.rb
87
+ - lib/checkers/board/score.rb
88
+ - lib/checkers/game/engine.rb
89
+ - lib/checkers/game/state.rb
90
+ - lib/checkers/gui.rb
91
+ - lib/checkers/gui/scene.rb
92
+ - lib/checkers/gui/scene/board.rb
93
+ - lib/checkers/gui/scene/piece_animation.rb
94
+ - lib/checkers/jump_move.rb
95
+ - lib/checkers/move.rb
96
+ - lib/checkers/ruby2d/piece.rb
97
+ - lib/checkers/ruby2d/square_with_piece.rb
98
+ - lib/checkers/version.rb
99
+ homepage: https://github.com/grzegorz-jakubiak/checkers-game
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ homepage_uri: https://github.com/grzegorz-jakubiak/checkers-game
104
+ source_code_uri: https://github.com/grzegorz-jakubiak/checkers-game
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.7.0
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.1.4
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Game of checkers
124
+ test_files: []