checkers-game 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +42 -0
- data/.tool-versions +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/checkers-game.gemspec +30 -0
- data/exe/checkers +37 -0
- data/gems.locked +59 -0
- data/gems.rb +12 -0
- data/lib/checkers.rb +16 -0
- data/lib/checkers/ai/engine/alphabeta.rb +35 -0
- data/lib/checkers/ai/engine/base.rb +37 -0
- data/lib/checkers/ai/engine/minmax.rb +37 -0
- data/lib/checkers/ai/node.rb +31 -0
- data/lib/checkers/ai/tree.rb +29 -0
- data/lib/checkers/board.rb +146 -0
- data/lib/checkers/board/moves.rb +33 -0
- data/lib/checkers/board/score.rb +72 -0
- data/lib/checkers/game/engine.rb +28 -0
- data/lib/checkers/game/state.rb +25 -0
- data/lib/checkers/gui.rb +9 -0
- data/lib/checkers/gui/scene.rb +58 -0
- data/lib/checkers/gui/scene/board.rb +108 -0
- data/lib/checkers/gui/scene/piece_animation.rb +61 -0
- data/lib/checkers/jump_move.rb +18 -0
- data/lib/checkers/move.rb +16 -0
- data/lib/checkers/ruby2d/piece.rb +29 -0
- data/lib/checkers/ruby2d/square_with_piece.rb +28 -0
- data/lib/checkers/version.rb +5 -0
- metadata +124 -0
data/lib/checkers/gui.rb
ADDED
@@ -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
|
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: []
|