checkers-game 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []