ruby_ttt 0.3.1 → 0.3.2
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/ai.rb +57 -55
- data/lib/alpha_beta_player.rb +39 -0
- data/lib/board.rb +117 -187
- data/lib/game.rb +49 -0
- data/lib/game_setup.rb +10 -109
- data/lib/player.rb +22 -59
- data/lib/player_factory.rb +30 -24
- data/lib/tictactoe_constants.rb +11 -0
- data/lib/ui.rb +18 -103
- metadata +5 -3
- data/lib/ruby_ttt.rb +0 -125
data/lib/ai.rb
CHANGED
@@ -1,70 +1,72 @@
|
|
1
|
-
|
2
|
-
NEG_INF = -999
|
3
|
-
WIN = 1
|
4
|
-
LOSE = -1
|
5
|
-
TIE = 0
|
1
|
+
module RubyTictactoe
|
6
2
|
|
7
|
-
class AI
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
class AI
|
4
|
+
POS_INF = 999
|
5
|
+
NEG_INF = -999
|
6
|
+
WIN = 1
|
7
|
+
LOSE = -1
|
8
|
+
TIE = 0
|
9
|
+
attr_reader :current_player
|
10
|
+
def initialize(player)
|
11
|
+
@current_player = player
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
def computer_move(board, player)
|
15
|
+
test_board = board.dup
|
16
|
+
test_board.all_cells = board.all_cells.dup
|
17
|
+
get_best_move(test_board, player)
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def get_best_move(board, player)
|
21
|
+
ranked_moves = rank_possible_moves(board, player)
|
22
|
+
move = ranked_moves.max_by {|cell, score| score}
|
23
|
+
move.first
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
+
private
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def rank_possible_moves(board, player)
|
29
|
+
possible_moves = board.open_cells
|
30
|
+
possible_moves.each_key do |cell|
|
31
|
+
possible_moves[cell] = get_move_score(board, player, cell)
|
32
|
+
end
|
31
33
|
end
|
32
|
-
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
def get_move_score(board, player, cell)
|
36
|
+
player.add_marker(board, cell)
|
37
|
+
best_score = apply_minimax(board, player, cell, depth=0, NEG_INF, POS_INF)
|
38
|
+
board.remove_marker(cell)
|
39
|
+
best_score
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
def get_score(board, player)
|
43
|
+
return WIN if board.winner?(player.marker) && (player == current_player)
|
44
|
+
return LOSE if board.winner?(player.marker)
|
45
|
+
TIE
|
46
|
+
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
def apply_minimax(board, player, cell, depth, alpha, beta)
|
49
|
+
return get_score(board, player) if board.game_over?
|
50
|
+
if (player == current_player)
|
51
|
+
maximizing_player = MaximizingPlayer.new(player)
|
52
|
+
alphabeta(board, maximizing_player, depth, alpha, beta)
|
53
|
+
else
|
54
|
+
minimizing_player = MinimizingPlayer.new(player)
|
55
|
+
alphabeta(board, minimizing_player, depth, alpha, beta)
|
56
|
+
end
|
55
57
|
end
|
56
|
-
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
def alphabeta(board, player, depth, alpha, beta)
|
60
|
+
board.open_cells.each_key do |cell|
|
61
|
+
player.opponent.add_marker(board, cell)
|
62
|
+
score = (apply_minimax(board, player.opponent, cell, depth += 1, alpha, beta) / depth.to_f)
|
63
|
+
alpha = player.get_alpha(alpha, score)
|
64
|
+
beta = player.get_beta(beta, score)
|
65
|
+
board.remove_marker(cell)
|
66
|
+
break if alpha >= beta
|
67
|
+
end
|
68
|
+
player.return_best_score(alpha, beta)
|
66
69
|
end
|
67
|
-
player.return_best_score(alpha, beta)
|
68
70
|
end
|
69
71
|
|
70
72
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RubyTictactoe
|
2
|
+
|
3
|
+
class AlphaBetaPlayer
|
4
|
+
attr_accessor :marker, :opponent
|
5
|
+
def initialize(player)
|
6
|
+
@marker = player.marker
|
7
|
+
@opponent = player.opponent
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_alpha(alpha, score)
|
11
|
+
alpha
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_beta(beta, score)
|
15
|
+
beta
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class MinimizingPlayer < AlphaBetaPlayer
|
20
|
+
def get_alpha(alpha, score)
|
21
|
+
score > alpha ? score : alpha
|
22
|
+
end
|
23
|
+
|
24
|
+
def return_best_score(alpha, beta)
|
25
|
+
alpha
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class MaximizingPlayer < AlphaBetaPlayer
|
30
|
+
def get_beta(beta, score)
|
31
|
+
score < beta ? score : beta
|
32
|
+
end
|
33
|
+
|
34
|
+
def return_best_score(alpha, beta)
|
35
|
+
beta
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/board.rb
CHANGED
@@ -1,220 +1,150 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
class Board
|
5
|
-
attr_accessor :all_cells, :num_of_rows, :winning_lines
|
6
|
-
def initialize(num_of_rows)
|
7
|
-
@num_of_rows = num_of_rows
|
8
|
-
@all_cells = create_board_hash
|
9
|
-
@winning_lines = get_winning_lines
|
10
|
-
end
|
1
|
+
require 'tictactoe_constants'
|
2
|
+
|
3
|
+
module RubyTictactoe
|
11
4
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
class Board
|
6
|
+
include TictactoeConstants
|
7
|
+
attr_accessor :all_cells, :num_of_rows, :winning_lines
|
8
|
+
def initialize(num_of_rows)
|
9
|
+
@num_of_rows = num_of_rows
|
10
|
+
@all_cells = create_board_hash
|
11
|
+
@winning_lines = get_winning_lines
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_board_hash
|
15
|
+
new_board = Hash.new
|
16
|
+
alpha = 'A'
|
17
|
+
numeric = 1
|
17
18
|
num_of_rows.times do
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
num_of_rows.times do
|
20
|
+
cellID = numeric.to_s + alpha
|
21
|
+
numeric += 1
|
22
|
+
new_board[cellID] = nil
|
23
|
+
end
|
24
|
+
alpha = alpha.next
|
25
|
+
numeric = 1
|
21
26
|
end
|
22
|
-
|
23
|
-
numeric = 1
|
27
|
+
new_board
|
24
28
|
end
|
25
|
-
new_board
|
26
|
-
end
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
def all_rows
|
37
|
-
rows = []
|
38
|
-
cellIDs = all_cells.keys
|
39
|
-
beg = 0
|
40
|
-
ending = num_of_rows - 1
|
41
|
-
until rows.length == num_of_rows
|
42
|
-
rows << cellIDs[beg..ending]
|
43
|
-
beg += num_of_rows
|
44
|
-
ending += num_of_rows
|
45
|
-
end
|
46
|
-
rows
|
47
|
-
end
|
48
|
-
|
49
|
-
def add_test_marker(marker, cell)
|
50
|
-
all_cells[cell] = marker
|
51
|
-
end
|
52
|
-
|
53
|
-
def available_cell?(cell)
|
54
|
-
valid_cell?(cell) && all_cells[cell].nil?
|
55
|
-
end
|
56
|
-
|
57
|
-
def valid_cell?(cell)
|
58
|
-
all_cells.has_key?(cell)
|
59
|
-
end
|
60
|
-
|
61
|
-
def remove_marker(cell)
|
62
|
-
all_cells[cell] = nil
|
63
|
-
end
|
64
|
-
|
65
|
-
def moves_remaining?
|
66
|
-
all_cells.has_value?(nil)
|
67
|
-
end
|
68
|
-
|
69
|
-
def winner?(marker)
|
70
|
-
board_markers = all_cells.select { |cell, value| value == marker }.keys
|
71
|
-
winning_lines.each do |line|
|
72
|
-
return true if (line & board_markers).length == num_of_rows
|
30
|
+
def get_winning_lines
|
31
|
+
lines = []
|
32
|
+
all_rows.each { |row| lines << row }
|
33
|
+
all_cols.each { |col| lines << col }
|
34
|
+
diagonals.each { |diagonal| lines << diagonal }
|
35
|
+
lines
|
73
36
|
end
|
74
|
-
false
|
75
|
-
end
|
76
|
-
|
77
|
-
def game_over?
|
78
|
-
!moves_remaining? || winner?(MARKER_X) || winner?(MARKER_O)
|
79
|
-
end
|
80
37
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
cells[rand(cells_count)]
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
def all_cols
|
97
|
-
cols = []
|
98
|
-
index = 0
|
99
|
-
num_of_rows.times do
|
100
|
-
cols << get_column(index)
|
101
|
-
index += 1
|
38
|
+
def all_rows
|
39
|
+
rows = []
|
40
|
+
cellIDs = all_cells.keys
|
41
|
+
beg = 0
|
42
|
+
ending = num_of_rows - 1
|
43
|
+
until rows.length == num_of_rows
|
44
|
+
rows << cellIDs[beg..ending]
|
45
|
+
beg += num_of_rows
|
46
|
+
ending += num_of_rows
|
47
|
+
end
|
48
|
+
rows
|
102
49
|
end
|
103
|
-
cols
|
104
|
-
end
|
105
50
|
|
106
|
-
|
107
|
-
|
108
|
-
cellIDs = all_cells.keys
|
109
|
-
num_of_rows.times do
|
110
|
-
column << cellIDs[index]
|
111
|
-
index += num_of_rows
|
51
|
+
def add_test_marker(marker, cell)
|
52
|
+
all_cells[cell] = marker
|
112
53
|
end
|
113
|
-
column
|
114
|
-
end
|
115
|
-
|
116
|
-
def diagonals
|
117
|
-
diagonals = []
|
118
|
-
diagonals << diagonal_one
|
119
|
-
diagonals << diagonal_two
|
120
|
-
end
|
121
54
|
|
122
|
-
|
123
|
-
|
124
|
-
alpha = 'A'
|
125
|
-
numeric = 1
|
126
|
-
num_of_rows.times do
|
127
|
-
diagonal << numeric.to_s + alpha
|
128
|
-
alpha = alpha.next
|
129
|
-
numeric += 1
|
55
|
+
def available_cell?(cell)
|
56
|
+
valid_cell?(cell) && all_cells[cell].nil?
|
130
57
|
end
|
131
|
-
diagonal
|
132
|
-
end
|
133
58
|
|
134
|
-
|
135
|
-
|
136
|
-
alpha = 'A'
|
137
|
-
numeric = num_of_rows
|
138
|
-
num_of_rows.times do
|
139
|
-
diagonal << numeric.to_s + alpha
|
140
|
-
alpha = alpha.next
|
141
|
-
numeric -= 1
|
59
|
+
def valid_cell?(cell)
|
60
|
+
all_cells.has_key?(cell)
|
142
61
|
end
|
143
|
-
diagonal
|
144
|
-
end
|
145
62
|
|
146
|
-
|
63
|
+
def remove_marker(cell)
|
64
|
+
all_cells[cell] = nil
|
65
|
+
end
|
147
66
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
super
|
152
|
-
@io = Kernel
|
153
|
-
end
|
67
|
+
def moves_remaining?
|
68
|
+
all_cells.has_value?(nil)
|
69
|
+
end
|
154
70
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
71
|
+
def winner?(marker)
|
72
|
+
board_markers = all_cells.select { |cell, value| value == marker }.keys
|
73
|
+
winning_lines.each do |line|
|
74
|
+
return true if (line & board_markers).length == num_of_rows
|
75
|
+
end
|
76
|
+
false
|
161
77
|
end
|
162
|
-
io.print "\n"
|
163
|
-
end
|
164
78
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
io.print "\n"
|
169
|
-
end
|
79
|
+
def game_over?
|
80
|
+
!moves_remaining? || winner?(RubyTictactoe::TictactoeConstants::MARKER_X) || winner?(RubyTictactoe::TictactoeConstants::MARKER_O)
|
81
|
+
end
|
170
82
|
|
171
|
-
|
172
|
-
|
173
|
-
all_rows.each do |row|
|
174
|
-
show_row(alpha, row)
|
175
|
-
alpha = alpha.next
|
83
|
+
def open_cells
|
84
|
+
all_cells.select { |k,v| v.nil? }
|
176
85
|
end
|
177
|
-
end
|
178
86
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
io.print " | #{letter}\n"
|
183
|
-
print_divider
|
184
|
-
end
|
87
|
+
def empty?
|
88
|
+
open_cells.length == (num_of_rows * num_of_rows)
|
89
|
+
end
|
185
90
|
|
186
|
-
|
187
|
-
|
188
|
-
|
91
|
+
def random_cell
|
92
|
+
cells = open_cells.keys
|
93
|
+
cells_count = cells.length - 1
|
94
|
+
cells[rand(cells_count)]
|
95
|
+
end
|
189
96
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
97
|
+
private
|
98
|
+
def all_cols
|
99
|
+
cols = []
|
100
|
+
index = 0
|
101
|
+
num_of_rows.times do
|
102
|
+
cols << get_column(index)
|
103
|
+
index += 1
|
104
|
+
end
|
105
|
+
cols
|
106
|
+
end
|
195
107
|
|
196
|
-
|
108
|
+
def get_column(index)
|
109
|
+
column = []
|
110
|
+
cellIDs = all_cells.keys
|
111
|
+
num_of_rows.times do
|
112
|
+
column << cellIDs[index]
|
113
|
+
index += num_of_rows
|
114
|
+
end
|
115
|
+
column
|
116
|
+
end
|
197
117
|
|
198
|
-
|
118
|
+
def diagonals
|
119
|
+
diagonals = []
|
120
|
+
diagonals << diagonal_one
|
121
|
+
diagonals << diagonal_two
|
122
|
+
end
|
199
123
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
124
|
+
def diagonal_one
|
125
|
+
diagonal = []
|
126
|
+
alpha = 'A'
|
127
|
+
numeric = 1
|
128
|
+
num_of_rows.times do
|
129
|
+
diagonal << numeric.to_s + alpha
|
130
|
+
alpha = alpha.next
|
131
|
+
numeric += 1
|
132
|
+
end
|
133
|
+
diagonal
|
206
134
|
end
|
207
|
-
board_string
|
208
|
-
end
|
209
135
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
136
|
+
def diagonal_two
|
137
|
+
diagonal = []
|
138
|
+
alpha = 'A'
|
139
|
+
numeric = num_of_rows
|
140
|
+
num_of_rows.times do
|
141
|
+
diagonal << numeric.to_s + alpha
|
142
|
+
alpha = alpha.next
|
143
|
+
numeric -= 1
|
144
|
+
end
|
145
|
+
diagonal
|
216
146
|
end
|
217
|
-
|
147
|
+
|
218
148
|
end
|
219
149
|
|
220
150
|
end
|
data/lib/game.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module RubyTictactoe
|
2
|
+
|
3
|
+
class Game
|
4
|
+
include TictactoeConstants
|
5
|
+
attr_accessor :board, :player_one, :player_two, :ui, :player_first_move
|
6
|
+
def initialize(settings)
|
7
|
+
@board = settings[:board]
|
8
|
+
@player_one = settings[:player_one]
|
9
|
+
@player_two = settings[:player_two]
|
10
|
+
@player_first_move = settings[:player_first_move]
|
11
|
+
@ui = UI.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def advance_game
|
15
|
+
game_status_check(current_player.opponent.marker)
|
16
|
+
ui.next_move_message(current_player.marker) unless board.game_over?
|
17
|
+
end
|
18
|
+
|
19
|
+
def game_status_check(marker)
|
20
|
+
if board.winner?(marker)
|
21
|
+
ui.winning_game_message(marker)
|
22
|
+
elsif !board.moves_remaining?
|
23
|
+
ui.tie_game_message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def verify_move(cell)
|
28
|
+
return false if !board.available_cell?(cell)
|
29
|
+
current_player.add_marker(board, cell)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_player
|
34
|
+
if total_markers(MARKER_X) > total_markers(MARKER_O)
|
35
|
+
player_two
|
36
|
+
elsif total_markers(MARKER_O) > total_markers(MARKER_X)
|
37
|
+
player_one
|
38
|
+
else
|
39
|
+
player_first_move
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def total_markers(marker)
|
44
|
+
board.all_cells.select { |cell, value| value == marker }.count
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/lib/game_setup.rb
CHANGED
@@ -1,116 +1,17 @@
|
|
1
|
-
|
2
|
-
HUMAN_PLAYER = 'human'
|
3
|
-
AI_PLAYER = 'hard computer'
|
4
|
-
HARD_LEVEL = 'hard'
|
5
|
-
EASY_LEVEL = 'easy'
|
1
|
+
require 'tictactoe_constants'
|
6
2
|
|
7
|
-
|
8
|
-
attr_accessor :ui
|
9
|
-
def initialize
|
10
|
-
@ui = UI.new
|
11
|
-
end
|
12
|
-
|
13
|
-
def get_settings
|
14
|
-
{}
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class CLIGameSetup < GameSetup
|
19
|
-
attr_accessor :ui
|
20
|
-
def initialize
|
21
|
-
@ui = CLIUI.new
|
22
|
-
end
|
3
|
+
module RubyTictactoe
|
23
4
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
settings[:player_one] = players.player_one
|
30
|
-
settings[:player_two] = players.player_two
|
31
|
-
settings[:player_first_move] = players.player_goes_first
|
32
|
-
settings
|
33
|
-
rescue Interrupt
|
34
|
-
ui.early_exit_message
|
35
|
-
exit
|
5
|
+
class GameSetup
|
6
|
+
include TictactoeConstants
|
7
|
+
attr_accessor :ui
|
8
|
+
def initialize
|
9
|
+
@ui = UI.new
|
36
10
|
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def set_up_players
|
40
|
-
player_one_type = get_player_type(MARKER_X)
|
41
|
-
player_two_type = get_player_type(MARKER_O)
|
42
|
-
PlayerFactory.new(player_one_type, player_two_type)
|
43
|
-
end
|
44
|
-
|
45
|
-
def get_player_type(marker)
|
46
|
-
type = ui.request_player_type(marker)
|
47
|
-
validated_type = valid_type?(type) ? get_difficulty_level(type) : invalid_type(type, marker)
|
48
|
-
ui.type_assigned_message(validated_type, marker)
|
49
|
-
validated_type
|
50
|
-
end
|
51
|
-
|
52
|
-
def get_difficulty_level(type)
|
53
|
-
return type if type == HUMAN_PLAYER
|
54
|
-
level = ui.request_difficulty_level
|
55
|
-
valid_level?(level) ? ui.level_assigned_message(level) : invalid_level(level)
|
56
|
-
player_type_by_level(level)
|
57
|
-
end
|
58
|
-
|
59
|
-
def valid_type?(type)
|
60
|
-
(type == HUMAN_PLAYER) || (type == COMPUTER_PLAYER)
|
61
|
-
end
|
62
|
-
|
63
|
-
def invalid_type(type, marker)
|
64
|
-
ui.invalid_input_message(type)
|
65
|
-
get_player_type(marker)
|
66
|
-
end
|
67
|
-
|
68
|
-
def valid_level?(level)
|
69
|
-
(level == HARD_LEVEL) || (level == EASY_LEVEL)
|
70
|
-
end
|
71
11
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def player_type_by_level(level)
|
78
|
-
level == HARD_LEVEL ? AI_PLAYER : COMPUTER_PLAYER
|
79
|
-
end
|
80
|
-
|
81
|
-
def get_board
|
82
|
-
rows = ui.request_board_size
|
83
|
-
valid_board_size?(rows) ? ui.board_assigned_message(rows) : invalid_board_size(rows)
|
84
|
-
CLIBoard.new(rows.to_i)
|
85
|
-
end
|
86
|
-
|
87
|
-
def valid_board_size?(input)
|
88
|
-
rows = input.to_i
|
89
|
-
rows.is_a?(Integer) && (rows > 2 && rows < 6)
|
90
|
-
end
|
91
|
-
|
92
|
-
def invalid_board_size(rows)
|
93
|
-
ui.invalid_input_message(rows)
|
94
|
-
get_board
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
class WebGameSetup < GameSetup
|
100
|
-
|
101
|
-
def set_up_players(player_one_type, player_two_type)
|
102
|
-
PlayerFactory.new(player_one_type, player_two_type)
|
103
|
-
end
|
104
|
-
|
105
|
-
def get_first_move_player(player_one_type, player_two_type)
|
106
|
-
players = PlayerFactory.new(player_one_type, player_two_type)
|
107
|
-
players.player_goes_first
|
108
|
-
end
|
109
|
-
|
110
|
-
def get_board(board_size, current_board)
|
111
|
-
board = WebBoard.new(board_size)
|
112
|
-
board.all_cells = current_board
|
113
|
-
board
|
12
|
+
def get_settings
|
13
|
+
{}
|
14
|
+
end
|
114
15
|
end
|
115
16
|
|
116
17
|
end
|
data/lib/player.rb
CHANGED
@@ -1,73 +1,36 @@
|
|
1
|
-
|
2
|
-
attr_accessor :marker, :opponent
|
3
|
-
def initialize(marker)
|
4
|
-
@marker = marker
|
5
|
-
@opponent = nil
|
6
|
-
end
|
7
|
-
|
8
|
-
def add_marker(board, cell)
|
9
|
-
board.all_cells[cell] = self.marker
|
10
|
-
end
|
11
|
-
|
12
|
-
def get_alpha(alpha, score)
|
13
|
-
alpha
|
14
|
-
end
|
15
|
-
|
16
|
-
def get_beta(beta, score)
|
17
|
-
beta
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class AIPlayer < Player
|
22
|
-
|
23
|
-
def make_move(board)
|
24
|
-
ai = AI.new(self)
|
25
|
-
cell = ai.computer_move(board, self)
|
26
|
-
add_marker(board, cell)
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
1
|
+
module RubyTictactoe
|
30
2
|
|
31
|
-
class
|
3
|
+
class Player
|
4
|
+
attr_accessor :marker, :opponent
|
5
|
+
def initialize(marker)
|
6
|
+
@marker = marker
|
7
|
+
@opponent = nil
|
8
|
+
end
|
32
9
|
|
33
|
-
|
34
|
-
|
35
|
-
|
10
|
+
def add_marker(board, cell)
|
11
|
+
board.all_cells[cell] = self.marker
|
12
|
+
end
|
36
13
|
end
|
37
14
|
|
38
|
-
|
15
|
+
class AIPlayer < Player
|
39
16
|
|
40
|
-
|
17
|
+
def make_move(board)
|
18
|
+
ai = AI.new(self)
|
19
|
+
cell = ai.computer_move(board, self)
|
20
|
+
add_marker(board, cell)
|
21
|
+
end
|
41
22
|
|
42
|
-
class MinimizingPlayer < Player
|
43
|
-
attr_accessor :marker, :opponent
|
44
|
-
def initialize(player)
|
45
|
-
@marker = player.marker
|
46
|
-
@opponent = player.opponent
|
47
23
|
end
|
48
24
|
|
49
|
-
|
50
|
-
score > alpha ? score : alpha
|
51
|
-
end
|
25
|
+
class ComputerPlayer < Player
|
52
26
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
27
|
+
def make_move(board)
|
28
|
+
cell = board.random_cell
|
29
|
+
add_marker(board, cell)
|
30
|
+
end
|
57
31
|
|
58
|
-
class MaximizingPlayer < Player
|
59
|
-
attr_accessor :marker, :opponent
|
60
|
-
def initialize(player)
|
61
|
-
@marker = player.marker
|
62
|
-
@opponent = player.opponent
|
63
32
|
end
|
64
33
|
|
65
|
-
|
66
|
-
score < beta ? score : beta
|
67
|
-
end
|
34
|
+
class HumanPlayer < Player; end
|
68
35
|
|
69
|
-
def return_best_score(alpha, beta)
|
70
|
-
beta
|
71
|
-
end
|
72
36
|
end
|
73
|
-
|
data/lib/player_factory.rb
CHANGED
@@ -1,31 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
@player_one = create_player(type_one, MARKER_X)
|
5
|
-
@player_two = create_player(type_two, MARKER_O)
|
6
|
-
set_opponents
|
7
|
-
end
|
1
|
+
require 'tictactoe_constants'
|
2
|
+
|
3
|
+
module RubyTictactoe
|
8
4
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
AIPlayer.new(marker)
|
17
|
-
else
|
18
|
-
raise "Invalid player type"
|
5
|
+
class PlayerFactory
|
6
|
+
include TictactoeConstants
|
7
|
+
attr_accessor :player_one, :player_two
|
8
|
+
def initialize(type_one, type_two)
|
9
|
+
@player_one = create_player(type_one, MARKER_X)
|
10
|
+
@player_two = create_player(type_two, MARKER_O)
|
11
|
+
set_opponents
|
19
12
|
end
|
20
|
-
end
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
|
14
|
+
def create_player(type, marker)
|
15
|
+
case type
|
16
|
+
when COMPUTER_PLAYER
|
17
|
+
ComputerPlayer.new(marker)
|
18
|
+
when HUMAN_PLAYER
|
19
|
+
HumanPlayer.new(marker)
|
20
|
+
when AI_PLAYER
|
21
|
+
AIPlayer.new(marker)
|
22
|
+
else
|
23
|
+
raise "Invalid player type"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def player_goes_first
|
28
|
+
rand(0..1) == 1 ? player_one : player_two
|
29
|
+
end
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
|
31
|
+
def set_opponents
|
32
|
+
player_one.opponent = player_two
|
33
|
+
player_two.opponent = player_one
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
end
|
data/lib/ui.rb
CHANGED
@@ -1,112 +1,27 @@
|
|
1
|
-
|
2
|
-
attr_accessor :io
|
3
|
-
def initialize
|
4
|
-
@io = Kernel
|
5
|
-
end
|
6
|
-
|
7
|
-
def first_move_message(marker)
|
8
|
-
"Player '#{marker}' goes first."
|
9
|
-
end
|
10
|
-
|
11
|
-
def next_move_message(marker)
|
12
|
-
"Player '#{marker}': Make your move."
|
13
|
-
end
|
14
|
-
|
15
|
-
def winning_game_message(marker)
|
16
|
-
"GAME OVER! Player '#{marker}' wins!"
|
17
|
-
end
|
18
|
-
|
19
|
-
def tie_game_message
|
20
|
-
"GAME OVER! It's a tie!"
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
class CLIUI < UI
|
26
|
-
|
27
|
-
def request_player_type(marker)
|
28
|
-
player_type_message(marker)
|
29
|
-
io.gets.chomp.downcase
|
30
|
-
end
|
31
|
-
|
32
|
-
def request_difficulty_level
|
33
|
-
difficulty_level_message
|
34
|
-
io.gets.chomp.downcase
|
35
|
-
end
|
36
|
-
|
37
|
-
def request_board_size
|
38
|
-
board_size_message
|
39
|
-
io.gets.chomp.downcase
|
40
|
-
end
|
41
|
-
|
42
|
-
def request_human_move
|
43
|
-
standardize(io.gets.chomp)
|
44
|
-
end
|
45
|
-
|
46
|
-
def standardize(input)
|
47
|
-
input.split('').sort.join('').upcase
|
48
|
-
end
|
1
|
+
module RubyTictactoe
|
49
2
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
io.print "Enter the number of rows you want your board to have (3-5).\n"
|
56
|
-
end
|
3
|
+
class UI
|
4
|
+
attr_accessor :io
|
5
|
+
def initialize
|
6
|
+
@io = Kernel
|
7
|
+
end
|
57
8
|
|
58
|
-
|
59
|
-
|
60
|
-
|
9
|
+
def first_move_message(marker)
|
10
|
+
"Player '#{marker}' goes first."
|
11
|
+
end
|
61
12
|
|
62
|
-
|
63
|
-
|
64
|
-
|
13
|
+
def next_move_message(marker)
|
14
|
+
"Player '#{marker}': Make your move."
|
15
|
+
end
|
65
16
|
|
66
|
-
|
67
|
-
|
68
|
-
|
17
|
+
def winning_game_message(marker)
|
18
|
+
"GAME OVER! Player '#{marker}' wins!"
|
19
|
+
end
|
69
20
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
def type_assigned_message(type, marker)
|
75
|
-
io.print "Player " + "'#{marker}' " + "is #{type}.\n"
|
76
|
-
end
|
77
|
-
|
78
|
-
def first_move_message(marker)
|
79
|
-
output = super
|
80
|
-
io.print "\n\n************ New Game ************\n"
|
81
|
-
io.print output + "\n"
|
82
|
-
end
|
83
|
-
|
84
|
-
def next_move_message(marker)
|
85
|
-
io.print "Player '#{marker}': Enter open cell ID.\n"
|
86
|
-
end
|
87
|
-
|
88
|
-
def winning_game_message(marker)
|
89
|
-
output = super
|
90
|
-
io.print output + "\n"
|
91
|
-
end
|
92
|
-
|
93
|
-
def tie_game_message
|
94
|
-
output = super
|
95
|
-
io.print output + "\n"
|
96
|
-
end
|
97
|
-
|
98
|
-
def taken_cell_message(cell)
|
99
|
-
io.print "#{cell} has already been taken!\n"
|
100
|
-
end
|
101
|
-
|
102
|
-
def bad_cell_message(cell)
|
103
|
-
io.print "#{cell} is not a valid cell ID!\n"
|
104
|
-
end
|
21
|
+
def tie_game_message
|
22
|
+
"GAME OVER! It's a tie!"
|
23
|
+
end
|
105
24
|
|
106
|
-
def early_exit_message
|
107
|
-
io.print "\nExiting Tic-Tac-Toe..."
|
108
|
-
io.print "...\n"
|
109
|
-
io.print "Goodbye!\n\n"
|
110
25
|
end
|
111
26
|
|
112
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_ttt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-12-
|
12
|
+
date: 2013-12-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -36,11 +36,13 @@ extensions: []
|
|
36
36
|
extra_rdoc_files: []
|
37
37
|
files:
|
38
38
|
- lib/ai.rb
|
39
|
+
- lib/alpha_beta_player.rb
|
39
40
|
- lib/board.rb
|
41
|
+
- lib/game.rb
|
40
42
|
- lib/game_setup.rb
|
41
43
|
- lib/player.rb
|
42
44
|
- lib/player_factory.rb
|
43
|
-
- lib/
|
45
|
+
- lib/tictactoe_constants.rb
|
44
46
|
- lib/ui.rb
|
45
47
|
homepage: http://rubygems.org/gems/ruby_ttt
|
46
48
|
licenses: []
|
data/lib/ruby_ttt.rb
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
require 'ai'
|
2
|
-
require 'board'
|
3
|
-
require 'player_factory'
|
4
|
-
require 'game_setup'
|
5
|
-
require 'player'
|
6
|
-
require 'ui'
|
7
|
-
|
8
|
-
class Game
|
9
|
-
attr_accessor :board, :player_one, :player_two, :ui, :player_first_move
|
10
|
-
def initialize(settings)
|
11
|
-
@board = settings[:board]
|
12
|
-
@player_one = settings[:player_one]
|
13
|
-
@player_two = settings[:player_two]
|
14
|
-
@player_first_move = settings[:player_first_move]
|
15
|
-
@ui = UI.new
|
16
|
-
end
|
17
|
-
|
18
|
-
def advance_game
|
19
|
-
game_status_check(current_player.opponent.marker)
|
20
|
-
ui.next_move_message(current_player.marker) unless board.game_over?
|
21
|
-
end
|
22
|
-
|
23
|
-
def game_status_check(marker)
|
24
|
-
if board.winner?(marker)
|
25
|
-
ui.winning_game_message(marker)
|
26
|
-
elsif !board.moves_remaining?
|
27
|
-
ui.tie_game_message
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def verify_move(cell)
|
32
|
-
return false if !board.available_cell?(cell)
|
33
|
-
current_player.add_marker(board, cell)
|
34
|
-
true
|
35
|
-
end
|
36
|
-
|
37
|
-
def current_player
|
38
|
-
if total_markers(MARKER_X) > total_markers(MARKER_O)
|
39
|
-
player_two
|
40
|
-
elsif total_markers(MARKER_O) > total_markers(MARKER_X)
|
41
|
-
player_one
|
42
|
-
else
|
43
|
-
player_first_move
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def total_markers(marker)
|
48
|
-
board.all_cells.select { |cell, value| value == marker }.count
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
class CLIGame < Game
|
54
|
-
attr_accessor :board, :player_one, :player_two, :ui, :player_first_move
|
55
|
-
def initialize(settings)
|
56
|
-
super
|
57
|
-
@ui = CLIUI.new
|
58
|
-
end
|
59
|
-
|
60
|
-
def start_game!
|
61
|
-
begin
|
62
|
-
play!
|
63
|
-
rescue Interrupt
|
64
|
-
ui.early_exit_message
|
65
|
-
exit
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def play!
|
70
|
-
ui.first_move_message(current_player.marker)
|
71
|
-
until board.game_over?
|
72
|
-
board.display_board
|
73
|
-
get_next_move
|
74
|
-
end
|
75
|
-
exit_game
|
76
|
-
end
|
77
|
-
|
78
|
-
def get_next_move
|
79
|
-
if current_player.is_a?(HumanPlayer)
|
80
|
-
move = ui.request_human_move
|
81
|
-
verify_move(move) ? advance_game : invalid_move(move)
|
82
|
-
else
|
83
|
-
current_player.make_move(board)
|
84
|
-
advance_game
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def invalid_move(cell)
|
89
|
-
board.valid_cell?(cell) ? ui.taken_cell_message(cell) : ui.bad_cell_message(cell)
|
90
|
-
end
|
91
|
-
|
92
|
-
def exit_game
|
93
|
-
board.display_board
|
94
|
-
ui.io.exit
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
class WebGame < Game
|
100
|
-
attr_accessor :board, :player_one, :player_two, :ui, :player_first_move
|
101
|
-
def initialize(settings)
|
102
|
-
super
|
103
|
-
@ui = UI.new
|
104
|
-
end
|
105
|
-
|
106
|
-
def get_message(player)
|
107
|
-
return ui.first_move_message(player.marker) if board.empty?
|
108
|
-
if board.winner?(current_player.opponent.marker)
|
109
|
-
ui.winning_game_message(current_player.opponent.marker)
|
110
|
-
elsif !board.moves_remaining?
|
111
|
-
ui.tie_game_message
|
112
|
-
else
|
113
|
-
ui.next_move_message(current_player.marker)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def get_first_move_player(type_one, type_two)
|
118
|
-
PlayerFactory.new(type_one, type_two).player_goes_first
|
119
|
-
end
|
120
|
-
|
121
|
-
def computer_player?(type_one, type_two)
|
122
|
-
((type_one.downcase) == COMPUTER_PLAYER) || ((type_two.downcase) == COMPUTER_PLAYER)
|
123
|
-
end
|
124
|
-
|
125
|
-
end
|