checkers_game_engine_sgallagher 0.0.2 → 0.0.3
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.
- data/lib/draughts.rb +10 -0
- data/lib/draughts/board.rb +98 -0
- data/lib/draughts/board_survey.rb +248 -0
- data/lib/draughts/checker.rb +18 -0
- data/lib/draughts/computer_player.rb +0 -0
- data/lib/draughts/evaluation.rb +70 -0
- data/lib/draughts/game.rb +68 -0
- data/lib/draughts/gui.rb +63 -0
- data/lib/draughts/minimax.rb +151 -0
- data/lib/draughts/move_check.rb +159 -0
- data/lib/draughts/user_input.rb +6 -0
- metadata +41 -22
data/lib/draughts.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'draughts/board'
|
2
|
+
require 'draughts/board_survey'
|
3
|
+
require 'draughts/checker'
|
4
|
+
require 'draughts/game'
|
5
|
+
require 'draughts/gui'
|
6
|
+
require 'draughts/move_check'
|
7
|
+
require 'draughts/user_input'
|
8
|
+
require 'draughts/computer_player'
|
9
|
+
require 'draughts/evaluation'
|
10
|
+
require 'draughts/minimax'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class Board
|
2
|
+
|
3
|
+
def create_board
|
4
|
+
board = Array.new(8)
|
5
|
+
8.times { |index| board[index] = Array.new(8) }
|
6
|
+
populate_checkers(board)
|
7
|
+
board
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_test_board
|
11
|
+
board = Array.new(8)
|
12
|
+
8.times { |index| board[index] = Array.new(8) }
|
13
|
+
board
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_checker(board, color, x, y)
|
17
|
+
board[x][y] = Checker.new(x, y, color)
|
18
|
+
end
|
19
|
+
|
20
|
+
def place_checker_on_board(board, checker)
|
21
|
+
board[checker.x_pos][checker.y_pos] = checker
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate_checkers(board)
|
25
|
+
0.upto(2) do |x_coord|
|
26
|
+
populate_checker(board, x_coord, :red)
|
27
|
+
end
|
28
|
+
|
29
|
+
5.upto(7) do |x_coord|
|
30
|
+
populate_checker(board, x_coord, :black)
|
31
|
+
end
|
32
|
+
board
|
33
|
+
end
|
34
|
+
|
35
|
+
def populate_checker(board, x_coord, color)
|
36
|
+
evens = [0, 2, 4, 6]
|
37
|
+
odds = [1, 3, 5, 7]
|
38
|
+
|
39
|
+
apply_checker = lambda do |y_coord|
|
40
|
+
checker = Checker.new(x_coord, y_coord, color)
|
41
|
+
board[x_coord][y_coord] = checker
|
42
|
+
end
|
43
|
+
|
44
|
+
if x_coord.even?
|
45
|
+
evens.each(&apply_checker)
|
46
|
+
elsif x_coord.odd?
|
47
|
+
odds.each(&apply_checker)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def checkers_left(board, color)
|
52
|
+
checker_count = 0
|
53
|
+
|
54
|
+
board.each do |row|
|
55
|
+
row.each do |location|
|
56
|
+
if (location.nil? == false) and (location.color == color)
|
57
|
+
checker_count += 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
checker_count
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.king_checkers_if_necessary(board)
|
65
|
+
board.each do |row|
|
66
|
+
row.each do |loc|
|
67
|
+
if (loc != nil) and (loc.color == :red) and (loc.x_pos == 7)
|
68
|
+
loc.make_king
|
69
|
+
elsif (loc != nil) and (loc.color == :black) and (loc.x_pos == 0)
|
70
|
+
loc.make_king
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.remove_jumped_checker(board, x_origin, y_origin, x_destination, y_destination)
|
77
|
+
x_delta = (x_destination > x_origin) ? 1 : -1
|
78
|
+
y_delta = (y_destination > y_origin) ? 1 : -1
|
79
|
+
|
80
|
+
board[x_origin + x_delta][y_origin + y_delta] = nil
|
81
|
+
board
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.return_jumped_checker(board, x_origin, y_origin, x_destination, y_destination)
|
85
|
+
jumping_checker_color = board[x_destination][y_destination].color
|
86
|
+
returning_checker_color = jumping_checker_color == :red ? :black : :red
|
87
|
+
|
88
|
+
x_delta = (x_destination > x_origin) ? 1 : -1
|
89
|
+
y_delta = (y_destination > y_origin) ? 1 : -1
|
90
|
+
|
91
|
+
board[x_origin + x_delta][y_origin + y_delta] = Checker.new(x_origin + x_delta, y_origin + y_delta, returning_checker_color)
|
92
|
+
board
|
93
|
+
end
|
94
|
+
|
95
|
+
def remove_checker(board, x, y)
|
96
|
+
board[x][y] = nil
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
class BoardSurvey
|
2
|
+
|
3
|
+
attr_accessor :board, :current_player
|
4
|
+
|
5
|
+
QUADRANTS = ["upper_left", "upper_right", "lower_left", "lower_right"]
|
6
|
+
|
7
|
+
def invert_array(array)
|
8
|
+
array.map! { |x| -x }
|
9
|
+
end
|
10
|
+
|
11
|
+
def normal_deltas(current_player)
|
12
|
+
deltas = [1, 1, 1, -1, -1, 1, -1, -1]
|
13
|
+
current_player == :red ? deltas : invert_array(deltas)
|
14
|
+
end
|
15
|
+
|
16
|
+
def edge?(x)
|
17
|
+
x == 7 or x == 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def edge_adjust(x, current_player, hash)
|
21
|
+
if x == 0
|
22
|
+
current_player == :red ? hash.merge({"lower_left" => nil, "lower_right" => nil}) : hash.merge({"upper_left" => nil, "upper_right" => nil})
|
23
|
+
else
|
24
|
+
current_player == :red ? hash.merge({"upper_left" => nil, "upper_right" => nil}) : hash.merge({"lower_left" => nil, "lower_right" => nil})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def deltas_to_board_locations(deltas, x, y)
|
29
|
+
board_coords = []
|
30
|
+
deltas.each_slice(2) do |slice|
|
31
|
+
board_coords << x + slice[0]
|
32
|
+
board_coords << y + slice[1]
|
33
|
+
end
|
34
|
+
board_coords
|
35
|
+
end
|
36
|
+
|
37
|
+
def assign_adjacent_board_coords(current_player, x, y)
|
38
|
+
jump_positions = Hash[QUADRANTS.zip(deltas_to_board_locations(normal_deltas(current_player), x, y).each_slice(2))]
|
39
|
+
if edge?(x)
|
40
|
+
return edge_adjust(x, current_player, jump_positions)
|
41
|
+
end
|
42
|
+
jump_positions
|
43
|
+
end
|
44
|
+
|
45
|
+
def determine_adjacent_positions_content(board, board_coords)
|
46
|
+
adjacent_content = {}
|
47
|
+
board_coords.each_pair { |quad, coords| coords.nil? ? adjacent_content[quad] = nil : adjacent_content[quad] = board[coords[0]][coords[1]] }
|
48
|
+
adjacent_content
|
49
|
+
end
|
50
|
+
|
51
|
+
def opposing_checker_adjacent(current_player, adjacent_content)
|
52
|
+
opposing_checker_adjacent = {}
|
53
|
+
adjacent_content.each_pair do |quad, content|
|
54
|
+
if content != nil
|
55
|
+
content.color != current_player ? opposing_checker_adjacent[quad] = true : opposing_checker_adjacent[quad] = false
|
56
|
+
else
|
57
|
+
opposing_checker_adjacent[quad] = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
opposing_checker_adjacent
|
61
|
+
end
|
62
|
+
|
63
|
+
def not_outside_bounds?(x, y)
|
64
|
+
move_check = MoveCheck.new
|
65
|
+
not move_check.out_of_bounds?(x, y)
|
66
|
+
end
|
67
|
+
|
68
|
+
def jump_possible?(board, deltas)
|
69
|
+
(not_outside_bounds?(deltas[0], deltas[1]) and board[deltas[0]][deltas[1]] == nil) ? true : false
|
70
|
+
end
|
71
|
+
|
72
|
+
def delta_translator(current_player, quad, x, y, mag)
|
73
|
+
deltas = []
|
74
|
+
if current_player == :red
|
75
|
+
case quad
|
76
|
+
when "upper_left"
|
77
|
+
x += mag; y += mag
|
78
|
+
when "upper_right"
|
79
|
+
x += mag; y -= mag
|
80
|
+
when "lower_left"
|
81
|
+
x -= mag; y += mag
|
82
|
+
when "lower_right"
|
83
|
+
x -= mag; y -= mag
|
84
|
+
end
|
85
|
+
else
|
86
|
+
case quad
|
87
|
+
when "upper_left"
|
88
|
+
x -= mag; y -= mag
|
89
|
+
when "upper_right"
|
90
|
+
x -= mag; y += mag
|
91
|
+
when "lower_left"
|
92
|
+
x += mag; y -= mag
|
93
|
+
when "lower_right"
|
94
|
+
x += mag; y += mag
|
95
|
+
end
|
96
|
+
end
|
97
|
+
deltas << x << y
|
98
|
+
deltas
|
99
|
+
end
|
100
|
+
|
101
|
+
def adjust_jump_locations_if_not_king(board, x, y, jump_locations)
|
102
|
+
unless board[x][y].is_king?
|
103
|
+
jump_locations["lower_left"] = false
|
104
|
+
jump_locations["lower_right"] = false
|
105
|
+
end
|
106
|
+
jump_locations
|
107
|
+
end
|
108
|
+
|
109
|
+
def jump_locations(board, current_player, x, y, opposing_checkers)
|
110
|
+
|
111
|
+
jump_locations = {}
|
112
|
+
opposing_checkers.each_pair do |quad, present|
|
113
|
+
if present
|
114
|
+
deltas = delta_translator(current_player, quad, x, y, 2)
|
115
|
+
jump_possible?(board, deltas) ? jump_locations[quad] = true : jump_locations[quad] = false
|
116
|
+
else
|
117
|
+
jump_locations[quad] = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
adjust_jump_locations_if_not_king(board, x, y, jump_locations)
|
121
|
+
jump_locations
|
122
|
+
end
|
123
|
+
|
124
|
+
def coordinates_of_jump_landings(current_player,x, y, jump_locations)
|
125
|
+
jump_coords = []
|
126
|
+
|
127
|
+
jump_locations.each_pair do |quad, jump|
|
128
|
+
if jump
|
129
|
+
jump_coords << delta_translator(current_player, quad, x, y, 2)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
jump_coords
|
133
|
+
end
|
134
|
+
|
135
|
+
def jump_location_finder_stack(board, current_player, x, y)
|
136
|
+
adj_board_coords = assign_adjacent_board_coords(current_player, x, y)
|
137
|
+
adj_content = determine_adjacent_positions_content(board, adj_board_coords)
|
138
|
+
opposing_checkers = opposing_checker_adjacent(current_player, adj_content)
|
139
|
+
jump_locations(board, current_player, x, y, opposing_checkers)
|
140
|
+
end
|
141
|
+
|
142
|
+
def generate_jump_locations_list(board, current_player)
|
143
|
+
coordinates_list = []
|
144
|
+
|
145
|
+
board.each do |row|
|
146
|
+
row.each do |loc|
|
147
|
+
if (loc != nil) and (loc.color == current_player)
|
148
|
+
jump_locations = jump_location_finder_stack(board, current_player, loc.x_pos, loc.y_pos)
|
149
|
+
coordinates_list << coordinates_of_jump_landings(current_player, loc.x_pos, loc.y_pos, jump_locations)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
coordinates_list.flatten
|
154
|
+
end
|
155
|
+
|
156
|
+
def any_jumps_left?(board, current_player, x, y)
|
157
|
+
jumps = jump_location_finder_stack(board, current_player, x, y)
|
158
|
+
jumps.has_value?(true)
|
159
|
+
end
|
160
|
+
|
161
|
+
def surrounding_locations_for_checker(board, current_player, x, y)
|
162
|
+
#p "BOARDSURVEY::SURROUNDING board position = #{x}, #{y} - #{board[x][y]}"
|
163
|
+
deltas = normal_deltas(current_player)
|
164
|
+
possible_moves = deltas_to_board_locations(deltas, x, y)
|
165
|
+
if board[x][y].is_king? == false
|
166
|
+
possible_moves.slice!(4, 4)
|
167
|
+
end
|
168
|
+
possible_moves
|
169
|
+
end
|
170
|
+
|
171
|
+
def remove_out_of_bounds_locations(possible_moves)
|
172
|
+
corrected_possible_moves = []
|
173
|
+
possible_moves.each_slice(2) do |coords|
|
174
|
+
if ((coords[0] <= 7 and coords[0] >= 0) and (coords[1] <= 7 and coords[1] >= 0))
|
175
|
+
corrected_possible_moves << coords[0] << coords[1]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
corrected_possible_moves
|
179
|
+
end
|
180
|
+
|
181
|
+
def normal_move_locations(possible_moves, board, current_player, x, y)
|
182
|
+
corrected_possible_moves = []
|
183
|
+
possible_moves.each_slice(2) do |coords|
|
184
|
+
if board[coords[0]][coords[1]] == nil
|
185
|
+
corrected_possible_moves << x << y << coords[0] << coords[1]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
corrected_possible_moves
|
189
|
+
end
|
190
|
+
|
191
|
+
def normal_move_location_finder_stack(board, current_player, x, y)
|
192
|
+
locations = surrounding_locations_for_checker(board, current_player, x, y)
|
193
|
+
removed_out_of_bounds_locations = remove_out_of_bounds_locations(locations)
|
194
|
+
normal_move_locations = normal_move_locations(removed_out_of_bounds_locations, board, current_player, x, y)
|
195
|
+
end
|
196
|
+
|
197
|
+
def generate_normal_move_locations_list(board, current_player)
|
198
|
+
move_locations = []
|
199
|
+
|
200
|
+
board.each do |row|
|
201
|
+
row.each do |loc|
|
202
|
+
if (loc != nil) and (loc.color == current_player)
|
203
|
+
move_locations << normal_move_location_finder_stack(board, current_player, loc.x_pos, loc.y_pos)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
move_locations.flatten
|
208
|
+
end
|
209
|
+
|
210
|
+
def coordinates_of_computer_jump_landings(current_player,x, y, jump_locations)
|
211
|
+
jump_coords = []
|
212
|
+
|
213
|
+
jump_locations.each_pair do |quad, jump|
|
214
|
+
if jump
|
215
|
+
jump_coords << x << y << delta_translator(current_player, quad, x, y, 2)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
jump_coords
|
219
|
+
end
|
220
|
+
|
221
|
+
def generate_computer_jump_locations_list(board, current_player)
|
222
|
+
coordinates_list = []
|
223
|
+
|
224
|
+
board.each do |row|
|
225
|
+
row.each do |loc|
|
226
|
+
if (loc != nil) and (loc.color == current_player)
|
227
|
+
jump_locations = jump_location_finder_stack(board, current_player, loc.x_pos, loc.y_pos)
|
228
|
+
coordinates_list << coordinates_of_computer_jump_landings(current_player, loc.x_pos, loc.y_pos, jump_locations)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
coordinates_list.flatten
|
233
|
+
end
|
234
|
+
|
235
|
+
def generate_all_possible_moves(board, current_player)
|
236
|
+
all_moves = generate_normal_move_locations_list(board, current_player)
|
237
|
+
all_moves << generate_computer_jump_locations_list(board, current_player)
|
238
|
+
all_moves.flatten
|
239
|
+
end
|
240
|
+
|
241
|
+
def generate_computer_moves(board, current_player)
|
242
|
+
all_moves = generate_computer_jump_locations_list(board, current_player)
|
243
|
+
if all_moves.size == 0
|
244
|
+
all_moves << generate_normal_move_locations_list(board, current_player)
|
245
|
+
end
|
246
|
+
all_moves.flatten
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Checker
|
2
|
+
attr_accessor :x_pos, :y_pos, :color, :king_status
|
3
|
+
|
4
|
+
def initialize (x_location, y_location, color)
|
5
|
+
@x_pos = x_location
|
6
|
+
@y_pos = y_location
|
7
|
+
@color = color
|
8
|
+
@king_status = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_king?
|
12
|
+
return @king_status
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_king
|
16
|
+
@king_status = true
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Evaluation
|
2
|
+
|
3
|
+
BOARD_WEIGHT = [ [ 4, nil, 4, nil, 4, nil, 4, nil],
|
4
|
+
[ nil, 3, nil, 3, nil, 3, nil, 4],
|
5
|
+
[ 4, nil, 2, nil, 2, nil, 3, nil],
|
6
|
+
[ nil, 3, nil, 1, nil, 2, nil, 4],
|
7
|
+
[ 4, nil, 2, nil, 1, nil, 3, nil],
|
8
|
+
[ nil, 3, nil, 2, nil, 2, nil, 4],
|
9
|
+
[ 4, nil, 3, nil, 3, nil, 3, nil],
|
10
|
+
[ nil, 4, nil, 4, nil, 4, nil, 4], ]
|
11
|
+
|
12
|
+
def evaluate_board(board)
|
13
|
+
player_value = 0
|
14
|
+
opponent_value = 0
|
15
|
+
|
16
|
+
#color == :red ? opponent_color = :black : opponent_color = :red
|
17
|
+
|
18
|
+
board.each do |row|
|
19
|
+
row.each do |position|
|
20
|
+
if position.nil? == false
|
21
|
+
position.color == :red ? player_value += calculate_value(position, :red) : opponent_value += calculate_value(position, :black)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
#p "player value - opponent_value = #{player_value} - #{opponent_value}"
|
26
|
+
return player_value - opponent_value
|
27
|
+
end
|
28
|
+
|
29
|
+
def evaluate_board2(board)
|
30
|
+
red_count= 0
|
31
|
+
black_count = 0
|
32
|
+
|
33
|
+
board.each do |row|
|
34
|
+
row.each do |position|
|
35
|
+
if position.nil? == false
|
36
|
+
position.color == :red ? red_count += 1 : black_count += 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
#p "player value - opponent_value = #{player_value} - #{opponent_value}"
|
41
|
+
#p "IN EVALUATE 2: net count = > #{red_count - black_count}"
|
42
|
+
return red_count - black_count
|
43
|
+
end
|
44
|
+
|
45
|
+
def calculate_value(position, color)
|
46
|
+
if color == :red
|
47
|
+
if position.x_pos == 7
|
48
|
+
value = 10
|
49
|
+
elsif position.x_pos == 5 or position.x_pos == 6
|
50
|
+
value = 7
|
51
|
+
else
|
52
|
+
value = 5
|
53
|
+
end
|
54
|
+
elsif color == :black
|
55
|
+
if position.x_pos == 0
|
56
|
+
value = 10
|
57
|
+
elsif position.x_pos == 1 or position.x_pos == 2
|
58
|
+
value = 7
|
59
|
+
else
|
60
|
+
value = 5
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if position.is_king?
|
65
|
+
value = 10
|
66
|
+
end
|
67
|
+
#p "IN CALCULATE VALUE : positon, color, value = (#{position.x_pos}, #{position.y_pos}) , #{color} , #{value}"
|
68
|
+
return value * BOARD_WEIGHT[position.x_pos][position.y_pos]
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Game
|
2
|
+
|
3
|
+
attr_accessor :board, :gui, :single_player_game, :current_player , :x_orig, :y_orig, :x_dest, :y_dest
|
4
|
+
|
5
|
+
def initialize(view)
|
6
|
+
@view = view
|
7
|
+
@current_player = :red
|
8
|
+
@bs = BoardSurvey.new
|
9
|
+
@evaluation = Evaluation.new
|
10
|
+
@input = UserInput.new
|
11
|
+
@move_check = MoveCheck.new
|
12
|
+
@board = Board.new
|
13
|
+
@game_board = @board.create_board
|
14
|
+
@minmax = Minimax.new(@bs, @evaluation)
|
15
|
+
end
|
16
|
+
|
17
|
+
def play_test_game
|
18
|
+
@game_board = @board.create_test_board
|
19
|
+
@board.add_checker(@game_board, :red, 0, 0)
|
20
|
+
@board.add_checker(@game_board, :red, 0, 2)
|
21
|
+
@board.add_checker(@game_board, :black, 7, 5)
|
22
|
+
@board.add_checker(@game_board, :black, 7, 7)
|
23
|
+
play_game
|
24
|
+
end
|
25
|
+
|
26
|
+
def play_game
|
27
|
+
@view.intro
|
28
|
+
player_input = @view.one_or_two_player
|
29
|
+
@single_player_game = true if player_input == "1\n"
|
30
|
+
while(game_over?(@game_board) == false)
|
31
|
+
message = nil
|
32
|
+
@view.render_board(@game_board)
|
33
|
+
if single_player_game and @current_player == :red
|
34
|
+
move = @minmax.best_move(@game_board, :red, 3)
|
35
|
+
coordinates = move
|
36
|
+
message = @move_check.move_validator(self, @game_board, :red, coordinates[0], coordinates[1], coordinates[2], coordinates[3])
|
37
|
+
else
|
38
|
+
coordinates = @view.get_move_coordinates(@current_player)
|
39
|
+
|
40
|
+
message = @move_check.move_validator(self, @game_board, @current_player, coordinates[0], coordinates[1], coordinates[2], coordinates[3])
|
41
|
+
end
|
42
|
+
puts message unless (message.nil? or message == "jumping move")
|
43
|
+
end
|
44
|
+
@view.render_board(@game_board)
|
45
|
+
@view.display_game_ending_message(@game_board)
|
46
|
+
end
|
47
|
+
|
48
|
+
def move(board, x_origin, y_origin, x_destination, y_destination)
|
49
|
+
#moving
|
50
|
+
moving_checker = board[x_origin][y_origin]
|
51
|
+
|
52
|
+
# set new location for checker
|
53
|
+
moving_checker.x_pos = x_destination
|
54
|
+
moving_checker.y_pos = y_destination
|
55
|
+
|
56
|
+
# update board positions of checker
|
57
|
+
board[x_origin][y_origin] = nil
|
58
|
+
board[x_destination][y_destination] = moving_checker
|
59
|
+
end
|
60
|
+
|
61
|
+
def game_over?(board)
|
62
|
+
@board.checkers_left(board, :black) == 0 or @board.checkers_left(board, :red) == 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.switch_player(current_player)
|
66
|
+
current_player == :red ? :black : :red
|
67
|
+
end
|
68
|
+
end
|
data/lib/draughts/gui.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
class Gui
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@board = Board.new
|
5
|
+
@input = UserInput.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def intro
|
9
|
+
puts 'Welcome to Checkers!'
|
10
|
+
end
|
11
|
+
|
12
|
+
def one_or_two_player
|
13
|
+
print "Do you want a one-player or two-player game (1 or 2) : "
|
14
|
+
response = gets
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_move_coordinates(current_player)
|
18
|
+
print "#{current_player.to_s.upcase} make move(x1, y1, x2, y2): "
|
19
|
+
player_input = gets
|
20
|
+
coordinates = @input.translate_move_request_to_coordinates(player_input)
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_board(board)
|
24
|
+
board_display = []
|
25
|
+
board_display << "\n 0 1 2 3 4 5 6 7 \n"
|
26
|
+
board_display << "\n -------------------------------------------------\n"
|
27
|
+
|
28
|
+
0.upto(7) do |x_coord|
|
29
|
+
board_display << " #{x_coord} | "
|
30
|
+
0.upto(7) do |y_coord|
|
31
|
+
if board[x_coord][y_coord].nil?
|
32
|
+
if (x_coord.even? && y_coord.odd?) || (x_coord.odd? && y_coord.even?)
|
33
|
+
board_display << "#" << " | "
|
34
|
+
elsif x_coord.even? && y_coord.even? || (x_coord.odd? && y_coord.odd?)
|
35
|
+
board_display << " " << " | "
|
36
|
+
end
|
37
|
+
elsif board[x_coord][y_coord].color == :red
|
38
|
+
if (board[x_coord][y_coord].is_king?) == true
|
39
|
+
board_display << "RK" << " | "
|
40
|
+
else
|
41
|
+
board_display << "R" << " | "
|
42
|
+
end
|
43
|
+
elsif board[x_coord][y_coord].color == :black
|
44
|
+
if (board[x_coord][y_coord].is_king?) == true
|
45
|
+
board_display << "BK" << " | "
|
46
|
+
else
|
47
|
+
board_display << "B" << " | "
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
board_display << "\n -------------------------------------------------\n"
|
52
|
+
end
|
53
|
+
board_display << "\n"
|
54
|
+
board_display.join
|
55
|
+
board_display.each { |line| print line}
|
56
|
+
end
|
57
|
+
|
58
|
+
def display_game_ending_message(board)
|
59
|
+
winner = @board.checkers_left(board, :black) == 0 ? :red : :black
|
60
|
+
winner = winner.to_s.capitalize
|
61
|
+
return "\n\nCongratulations, #{winner}, You have won!!!"
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
class Minimax
|
2
|
+
|
3
|
+
INFINITY = 100000
|
4
|
+
|
5
|
+
# Best move -> Have a method that generates all the possible moves, then apply the minimax to each one of those board positions, match the highest score
|
6
|
+
# and return the best move.
|
7
|
+
#
|
8
|
+
def initialize(board_survey, evaluation)
|
9
|
+
@bs = board_survey
|
10
|
+
@eval = evaluation
|
11
|
+
@jumped_checker = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def best_move(board, player, depth)
|
15
|
+
#p "IN BEST MOVE : player -> #{player}"
|
16
|
+
move_scores = []
|
17
|
+
moves_list = @bs.generate_computer_moves(board, player)
|
18
|
+
moves_list.each_slice(4) do |move|
|
19
|
+
#p "IN BEST MOVE : before apply move"
|
20
|
+
apply_move(board, move)
|
21
|
+
move_scores << minimax(board, :black, depth)
|
22
|
+
#p "IN BEST MOVE : before unapply move"
|
23
|
+
unapply_move(board, move)
|
24
|
+
end
|
25
|
+
move_scores.flatten
|
26
|
+
#p "IN BEST MOVE : moves_list -> #{moves_list}"
|
27
|
+
#p "IN BEST MOVE : move_scores -> #{move_scores}"
|
28
|
+
best_score_index = move_scores.index(move_scores.max)
|
29
|
+
#p "IN BEST MOVE : best_score_index -> #{best_score_index}"
|
30
|
+
best_move_coords = moves_list.slice(best_score_index * 4, 4)
|
31
|
+
#p "IN BEST MOVE : best_move_coords -> #{best_move_coords}"
|
32
|
+
best_move_coords
|
33
|
+
end
|
34
|
+
|
35
|
+
def minimax(board, player, depth)
|
36
|
+
#p "IN MINIMAX: player -> #{player}"
|
37
|
+
if game_over?(board)
|
38
|
+
return who_won(board)
|
39
|
+
end
|
40
|
+
|
41
|
+
if depth == 0
|
42
|
+
evaluated_score = @eval.evaluate_board(board)
|
43
|
+
#p "IN MINIMAX -> returned value = #{evaluated_score}"
|
44
|
+
return @eval.evaluate_board(board)
|
45
|
+
else
|
46
|
+
player == :red ? best_score = -INFINITY : best_score = INFINITY
|
47
|
+
|
48
|
+
moves_list = @bs.generate_all_possible_moves(board, player)
|
49
|
+
#p "IN MINIMAX -> moves list = #{moves_list}"
|
50
|
+
if moves_list == nil
|
51
|
+
score = @eval.evaluate_board(board)
|
52
|
+
else
|
53
|
+
moves_list.each_slice(4) do |move|
|
54
|
+
#p "IN MINIMAX -> before apply move"
|
55
|
+
apply_move(board, move)
|
56
|
+
score = minimax(board, Game.switch_player(player), depth-1)
|
57
|
+
if player == :red
|
58
|
+
#p "IN MINIMAX -> in best score branch(red) -> #{best_score}"
|
59
|
+
best_score = score > best_score ? score : best_score
|
60
|
+
elsif player == :black
|
61
|
+
#p "IN MINIMAX -> in best score branch(black) -> #{best_score}"
|
62
|
+
best_score = score < best_score ? score : best_score
|
63
|
+
end
|
64
|
+
#p "IN MINIMAX -> before unapply move"
|
65
|
+
unapply_move(board, move)
|
66
|
+
#p "IN MINIMAX -> after unapply move board status = "
|
67
|
+
#puts Gui.render_board(board)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
#p "IN MINIMAX -> best score = #{best_score}"
|
72
|
+
return best_score
|
73
|
+
end
|
74
|
+
|
75
|
+
def game_over?(board)
|
76
|
+
red_checkers = 0
|
77
|
+
black_checkers = 0
|
78
|
+
|
79
|
+
board.each do |row|
|
80
|
+
row.each do |position|
|
81
|
+
if position.nil? == false
|
82
|
+
position.color == :red ? red_checkers += 1 : black_checkers += 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
red_checkers == 0 or black_checkers == 0
|
87
|
+
end
|
88
|
+
|
89
|
+
def who_won(board)
|
90
|
+
red_checkers = 0
|
91
|
+
black_checkers = 0
|
92
|
+
|
93
|
+
board.each do |row|
|
94
|
+
row.each do |position|
|
95
|
+
if position.nil? == false
|
96
|
+
position.color == :red ? red_checkers += 1 : black_checkers += 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
red_checkers == 0 ? -INFINITY : INFINITY
|
101
|
+
end
|
102
|
+
|
103
|
+
def apply_move(board, move)
|
104
|
+
#p "IN APPLY MOVE: move -> #{move}"
|
105
|
+
checker = board[move[0]][move[1]]
|
106
|
+
board[move[2]][move[3]] = checker
|
107
|
+
board[move[0]][move[1]] = nil
|
108
|
+
checker.x_pos = move[2]
|
109
|
+
checker.y_pos = move[3]
|
110
|
+
if (move[2] - move[0]).abs == 2
|
111
|
+
#Board.remove_jumped_checker(board, move[0], move[1], move[2], move[3])
|
112
|
+
x_delta = (move[2] > move[0]) ? 1 : -1
|
113
|
+
y_delta = (move[3] > move[1]) ? 1 : -1
|
114
|
+
#p "MINIMAX::APPLY_MOVE -> move 1 - 4, x_delta, y_delta : #{move}, #{x_delta}, #{y_delta}"
|
115
|
+
#p "MINIMAX::APPLY_MOVE -> before assign, jumping_checker pos = #{@jumped_checker}"
|
116
|
+
@jumped_checker.push(board[move[0] + x_delta][move[1] + y_delta])
|
117
|
+
#p "MINIMAX::APPLY_MOVE -> jumped_checker = #{@jumped_checker}"
|
118
|
+
#p "MINIMAX::APPLY_MOVE -> jumped pos = #{move[0] + x_delta } , #{move[1] + y_delta }"
|
119
|
+
board[move[0] + x_delta][move[1] + y_delta] = nil
|
120
|
+
end
|
121
|
+
board
|
122
|
+
end
|
123
|
+
|
124
|
+
def unapply_move(board, move)
|
125
|
+
#p "IN UNAPPLY MOVE: move -> #{move}"
|
126
|
+
if (move[2] - move[0]).abs == 2
|
127
|
+
#Board.return_jumped_checker(board, move[0], move[1], move[2], move[3])
|
128
|
+
x_delta = (move[2] > move[0]) ? 1 : -1
|
129
|
+
y_delta = (move[3] > move[1]) ? 1 : -1
|
130
|
+
|
131
|
+
#p "MINIMAX::UNAPPLY_MOVE -> move 1 - 4, x_delta, y_delta : #{move}, #{x_delta}, #{y_delta}"
|
132
|
+
#p "MINIMAX::UNAPPLY_MOVE -> jumped_checker = #{@jumped_checker}"
|
133
|
+
#p "MINIMAX::UNAPPLY_MOVE -> jumped pos = #{move[0] + x_delta } , #{move[1] + y_delta }"
|
134
|
+
board[move[0] + x_delta][move[1] + y_delta] = @jumped_checker.pop
|
135
|
+
board
|
136
|
+
end
|
137
|
+
#p "IN UNAPPLY MOVE: before unapplied move, board at x-orig, y-orig -> #{board[move[0]][move[1]]} and x-dest, y-dest -> #{board[move[2]][move[3]]}"
|
138
|
+
checker = board[move[2]][move[3]]
|
139
|
+
board[move[0]][move[1]] = checker
|
140
|
+
board[move[2]][move[3]] = nil
|
141
|
+
checker.x_pos = move[0]
|
142
|
+
checker.y_pos = move[1]
|
143
|
+
#p "IN UNAPPLY MOVE: after unapplied move, board at x-orig, y-orig -> #{board[move[0]][move[1]]} and x-dest, y-dest -> #{board[move[2]][move[3]]}"
|
144
|
+
#p "IN UNAPPLY MOVE: after unapplied move, TOTAL BOARD IS : #{board}}"
|
145
|
+
board
|
146
|
+
end
|
147
|
+
|
148
|
+
def other_player(player)
|
149
|
+
player == :red ? :black : :red
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
class MoveCheck
|
2
|
+
|
3
|
+
attr_accessor :board, :current_player
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@survey = BoardSurvey.new
|
7
|
+
@consecutive_jumps = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def move_validator(game, board, current_player, x_origin, y_origin, x_destination, y_destination)
|
11
|
+
message = nil
|
12
|
+
|
13
|
+
case
|
14
|
+
when (@consecutive_jumps and board[x_origin][y_origin] != @consecutive_jumper)
|
15
|
+
message = "You cannot jump with a different checker"
|
16
|
+
when out_of_bounds?(x_destination, y_destination)
|
17
|
+
message = "You cannot move off the board"
|
18
|
+
|
19
|
+
when no_checker_at_origin?(board, x_origin, y_origin)
|
20
|
+
message = "There is no checker to move in requested location"
|
21
|
+
|
22
|
+
when trying_to_move_opponents_checker?(board,current_player, x_origin, y_origin)
|
23
|
+
message = "You cannot move an opponents checker"
|
24
|
+
|
25
|
+
when trying_to_move_more_than_one_space_and_not_jumping?(x_origin, x_destination)
|
26
|
+
message = "You cannot move more than one space if not jumping"
|
27
|
+
|
28
|
+
when attempted_non_diagonal_move?(x_origin, y_origin, x_destination, y_destination)
|
29
|
+
message = "You can only move a checker diagonally"
|
30
|
+
|
31
|
+
when attempted_move_to_occupied_square?(board, x_destination, y_destination)
|
32
|
+
message = "You cannot move to an occupied square"
|
33
|
+
|
34
|
+
when non_king_moving_backwards?(board, current_player, x_origin, y_origin, x_destination)
|
35
|
+
message = "A non-king checker cannot move backwards"
|
36
|
+
|
37
|
+
when attempted_jump_of_empty_space?(board,current_player, x_origin, y_origin, x_destination, y_destination)
|
38
|
+
message = "You cannot jump an empty space"
|
39
|
+
|
40
|
+
when attempted_jump_of_own_checker?(board, current_player, x_origin, y_origin, x_destination, y_destination)
|
41
|
+
message = "You cannot jump a checker of your own color"
|
42
|
+
|
43
|
+
when jump_available_and_not_taken?(board, current_player, x_destination, y_destination)
|
44
|
+
message = "You must jump if a jump is available"
|
45
|
+
|
46
|
+
else
|
47
|
+
game.move(board, x_origin, y_origin, x_destination, y_destination)
|
48
|
+
|
49
|
+
if jumping_move?(x_origin, x_destination)
|
50
|
+
message = "jumping move"
|
51
|
+
Board.remove_jumped_checker(board, x_origin, y_origin, x_destination, y_destination)
|
52
|
+
@consecutive_jumps = false
|
53
|
+
|
54
|
+
if @survey.any_jumps_left?(board, current_player, x_destination, y_destination)
|
55
|
+
@consecutive_jumps = true
|
56
|
+
@consecutive_jumper = board[x_destination][y_destination]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
Board.king_checkers_if_necessary(board)
|
60
|
+
end
|
61
|
+
|
62
|
+
if message == nil or (message == "jumping move" and @survey.any_jumps_left?(board, current_player, x_destination, y_destination)== false)
|
63
|
+
if @consecutive_jumps == false
|
64
|
+
game.current_player = Game.switch_player(game.current_player)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
message
|
68
|
+
end
|
69
|
+
|
70
|
+
def jump_available?(board, current_player)
|
71
|
+
possible_jumps = @survey.generate_jump_locations_list(board, current_player)
|
72
|
+
|
73
|
+
possible_jumps.size > 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def jump_available_and_not_taken?(board, current_player, x_destination, y_destination)
|
77
|
+
jump_possiblities = @survey.generate_jump_locations_list(board, current_player)
|
78
|
+
|
79
|
+
not_taken_jump = true
|
80
|
+
jump_possiblities.each_slice(2) do |i|
|
81
|
+
if(i[0] == x_destination) and (i[1] == y_destination)
|
82
|
+
not_taken_jump = false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
(jump_available?(board, current_player) == true) and (not_taken_jump)
|
86
|
+
end
|
87
|
+
|
88
|
+
def jump_deltas(current_player, x_orig, y_orig, x_dest, y_dest)
|
89
|
+
deltas = []
|
90
|
+
x_dest > x_orig ? deltas << 1 : deltas << -1
|
91
|
+
y_dest > y_orig ? deltas << 1 : deltas << -1
|
92
|
+
end
|
93
|
+
|
94
|
+
def attempted_jump_of_empty_space?(board, current_player, x_origin, y_origin, x_destination, y_destination)
|
95
|
+
if jumping_move?(x_origin, x_destination)
|
96
|
+
deltas = jump_deltas(current_player, x_origin, y_origin, x_destination, y_destination)
|
97
|
+
board[x_origin + deltas[0]][y_origin + deltas[1]].nil? ? true : false
|
98
|
+
else
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def attempted_jump_of_own_checker?(board, current_player, x_origin, y_origin, x_destination, y_destination)
|
104
|
+
if jumping_move?(x_origin, x_destination)
|
105
|
+
x_delta = (x_destination > x_origin) ? 1 : -1
|
106
|
+
y_delta = (y_destination > y_origin) ? 1 : -1
|
107
|
+
|
108
|
+
if current_player == :black
|
109
|
+
x_delta = (x_destination < x_origin) ? -1 : 1
|
110
|
+
y_delta = (y_destination < y_origin) ? -1 : 1
|
111
|
+
end
|
112
|
+
|
113
|
+
jumped_checker_x_value = x_origin + x_delta
|
114
|
+
jumped_checker_y_value = y_origin + y_delta
|
115
|
+
|
116
|
+
jumped_checker = board[jumped_checker_x_value][jumped_checker_y_value]
|
117
|
+
|
118
|
+
jumping_checker = board[x_origin][y_origin]
|
119
|
+
|
120
|
+
jumped_checker.color == jumping_checker.color
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def jumping_move?(x_origin, x_destination)
|
125
|
+
(x_destination - x_origin).abs == 2
|
126
|
+
end
|
127
|
+
|
128
|
+
def out_of_bounds?(x, y)
|
129
|
+
( x < 0 or y < 0) or (x > 7 or y > 7)
|
130
|
+
end
|
131
|
+
|
132
|
+
def no_checker_at_origin?(board, x_origin, y_origin)
|
133
|
+
board[x_origin][y_origin].nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
def trying_to_move_opponents_checker?(board, current_player, x_origin, y_origin)
|
137
|
+
current_player != board[x_origin][y_origin].color
|
138
|
+
end
|
139
|
+
|
140
|
+
def trying_to_move_more_than_one_space_and_not_jumping?(x_origin, x_destination)
|
141
|
+
(x_destination - x_origin).abs > 2
|
142
|
+
end
|
143
|
+
|
144
|
+
def attempted_non_diagonal_move?(x_origin, y_origin, x_destination, y_destination)
|
145
|
+
(x_origin == x_destination) or (y_origin == y_destination)
|
146
|
+
end
|
147
|
+
|
148
|
+
def attempted_move_to_occupied_square?(board, x_destination, y_destination)
|
149
|
+
not board[x_destination][y_destination].nil?
|
150
|
+
end
|
151
|
+
|
152
|
+
def non_king_moving_backwards?(board, current_player, x_origin, y_origin, x_destination)
|
153
|
+
if current_player == :red
|
154
|
+
(x_destination < x_origin) and (board[x_origin][y_origin].is_king? == false)
|
155
|
+
else
|
156
|
+
(x_destination > x_origin) and (board[x_origin][y_origin].is_king? == false)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
metadata
CHANGED
@@ -1,45 +1,64 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: checkers_game_engine_sgallagher
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.2
|
3
|
+
version: !ruby/object:Gem::Version
|
5
4
|
prerelease:
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
8
|
-
- Steven Gallagher
|
7
|
+
authors:
|
8
|
+
- Steven Gallagher
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
12
|
+
|
13
|
+
date: 2012-01-26 00:00:00 Z
|
13
14
|
dependencies: []
|
14
|
-
|
15
|
-
|
15
|
+
|
16
|
+
description: Contains all the logic necessary to play a one or two player game of checkers
|
16
17
|
email: s.thomas.gallagher@gmail.com
|
17
18
|
executables: []
|
19
|
+
|
18
20
|
extensions: []
|
21
|
+
|
19
22
|
extra_rdoc_files: []
|
20
|
-
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/draughts.rb
|
26
|
+
- lib/draughts/board.rb
|
27
|
+
- lib/draughts/board_survey.rb
|
28
|
+
- lib/draughts/checker.rb
|
29
|
+
- lib/draughts/computer_player.rb
|
30
|
+
- lib/draughts/evaluation.rb
|
31
|
+
- lib/draughts/game.rb
|
32
|
+
- lib/draughts/gui.rb
|
33
|
+
- lib/draughts/minimax.rb
|
34
|
+
- lib/draughts/move_check.rb
|
35
|
+
- lib/draughts/user_input.rb
|
21
36
|
homepage: https://github.com/greenone1975/draughts
|
22
37
|
licenses: []
|
38
|
+
|
23
39
|
post_install_message:
|
24
40
|
rdoc_options: []
|
25
|
-
|
26
|
-
|
27
|
-
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
28
45
|
none: false
|
29
|
-
requirements:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
51
|
none: false
|
35
|
-
requirements:
|
36
|
-
|
37
|
-
|
38
|
-
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
39
56
|
requirements: []
|
57
|
+
|
40
58
|
rubyforge_project:
|
41
|
-
rubygems_version: 1.8.
|
59
|
+
rubygems_version: 1.8.9
|
42
60
|
signing_key:
|
43
61
|
specification_version: 3
|
44
62
|
summary: Checkers game engine for one or two players
|
45
63
|
test_files: []
|
64
|
+
|