tic_tac_toes 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/Rakefile +2 -2
- data/bin/tic_tac_toes +18 -22
- data/lib/tic_tac_toes/command_line/menu.rb +55 -0
- data/lib/tic_tac_toes/command_line/prompt.rb +29 -0
- data/lib/tic_tac_toes/command_line/runner.rb +38 -0
- data/lib/tic_tac_toes/core/board.rb +64 -0
- data/lib/tic_tac_toes/core/board_factory.rb +11 -0
- data/lib/tic_tac_toes/core/game_state.rb +40 -0
- data/lib/tic_tac_toes/core/game_state_factory.rb +15 -0
- data/lib/tic_tac_toes/core/history.rb +29 -0
- data/lib/tic_tac_toes/core/io.rb +93 -0
- data/lib/tic_tac_toes/core/move_strategies/easy_ai.rb +13 -0
- data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +89 -0
- data/lib/tic_tac_toes/core/move_strategies/human.rb +20 -0
- data/lib/tic_tac_toes/core/move_strategies/medium_ai.rb +15 -0
- data/lib/tic_tac_toes/core/player.rb +22 -0
- data/lib/tic_tac_toes/core/player_factory.rb +30 -0
- data/lib/tic_tac_toes/core/rules.rb +66 -0
- data/lib/tic_tac_toes/core/strings.rb +106 -0
- data/lib/tic_tac_toes/database/pg_wrapper.rb +74 -0
- data/lib/tic_tac_toes/ui/adapter.rb +96 -0
- data/lib/tic_tac_toes/ui/serializer.rb +0 -0
- data/lib/tic_tac_toes/version.rb +1 -1
- data/spec/{command_line → tic_tac_toes/command_line}/menu_spec.rb +14 -14
- data/spec/{command_line → tic_tac_toes/command_line}/runner_spec.rb +7 -10
- data/spec/tic_tac_toes/{board_factory_spec.rb → core/board_factory_spec.rb} +3 -3
- data/spec/tic_tac_toes/{board_spec.rb → core/board_spec.rb} +23 -23
- data/spec/tic_tac_toes/{game_state_factory_spec.rb → core/game_state_factory_spec.rb} +3 -3
- data/spec/tic_tac_toes/core/game_state_spec.rb +129 -0
- data/spec/tic_tac_toes/{history_spec.rb → core/history_spec.rb} +3 -3
- data/spec/tic_tac_toes/{io_spec.rb → core/io_spec.rb} +7 -7
- data/spec/tic_tac_toes/core/move_strategies/easy_ai_spec.rb +19 -0
- data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +121 -0
- data/spec/tic_tac_toes/{move_strategies → core/move_strategies}/human_spec.rb +3 -3
- data/spec/tic_tac_toes/core/move_strategies/medium_ai_spec.rb +21 -0
- data/spec/tic_tac_toes/{player_factory_spec.rb → core/player_factory_spec.rb} +7 -7
- data/spec/tic_tac_toes/{player_spec.rb → core/player_spec.rb} +5 -5
- data/spec/tic_tac_toes/{rules_spec.rb → core/rules_spec.rb} +34 -34
- data/spec/tic_tac_toes/{strings_spec.rb → core/strings_spec.rb} +8 -8
- data/spec/{database → tic_tac_toes/database}/pg_wrapper_spec.rb +3 -3
- data/spec/tic_tac_toes/test_board_generator.rb +17 -0
- data/spec/{ui → tic_tac_toes/ui}/adapter_spec.rb +13 -13
- data/spec/tic_tac_toes/ui/serializer_spec.rb +0 -0
- metadata +64 -61
- data/lib/command_line/menu.rb +0 -53
- data/lib/command_line/prompt.rb +0 -27
- data/lib/command_line/runner.rb +0 -37
- data/lib/database/pg_wrapper.rb +0 -72
- data/lib/tic_tac_toes/board.rb +0 -62
- data/lib/tic_tac_toes/board_factory.rb +0 -9
- data/lib/tic_tac_toes/game_state.rb +0 -27
- data/lib/tic_tac_toes/game_state_factory.rb +0 -14
- data/lib/tic_tac_toes/history.rb +0 -27
- data/lib/tic_tac_toes/io.rb +0 -91
- data/lib/tic_tac_toes/move_strategies/easy_ai.rb +0 -11
- data/lib/tic_tac_toes/move_strategies/hard_ai.rb +0 -87
- data/lib/tic_tac_toes/move_strategies/human.rb +0 -18
- data/lib/tic_tac_toes/move_strategies/medium_ai.rb +0 -13
- data/lib/tic_tac_toes/player.rb +0 -20
- data/lib/tic_tac_toes/player_factory.rb +0 -28
- data/lib/tic_tac_toes/rules.rb +0 -64
- data/lib/tic_tac_toes/strings.rb +0 -104
- data/lib/ui/adapter.rb +0 -94
- data/spec/test_board_generator.rb +0 -15
- data/spec/tic_tac_toes/game_state_spec.rb +0 -66
- data/spec/tic_tac_toes/move_strategies/easy_ai_spec.rb +0 -19
- data/spec/tic_tac_toes/move_strategies/hard_ai_spec.rb +0 -121
- data/spec/tic_tac_toes/move_strategies/medium_ai_spec.rb +0 -21
- /data/lib/{tasks → tic_tac_toes/tasks}/destroy_databases.rake +0 -0
- /data/lib/{tasks → tic_tac_toes/tasks}/set_up_databases.rake +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'tic_tac_toes/core/player_factory'
|
2
|
+
|
3
|
+
module TicTacToes
|
4
|
+
module Core
|
5
|
+
module Rules
|
6
|
+
ROW_SIZE_RANGE = (2..10)
|
7
|
+
|
8
|
+
def self.row_size_valid?(row_size)
|
9
|
+
row_size.between?(ROW_SIZE_RANGE.min, ROW_SIZE_RANGE.max)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.token_valid?(token, taken_tokens)
|
13
|
+
correct_length = token.length == 1
|
14
|
+
untaken = !taken_tokens.include?(token)
|
15
|
+
|
16
|
+
correct_length && untaken
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.difficulty_valid?(difficulty)
|
20
|
+
PlayerFactory::AIS.include? difficulty
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.game_over?(board, players)
|
24
|
+
winner = !determine_winner(board, players).nil?
|
25
|
+
tie = board.full?
|
26
|
+
|
27
|
+
winner || tie
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.determine_winner(board, players)
|
31
|
+
winner = nil
|
32
|
+
|
33
|
+
players.each do |player|
|
34
|
+
player_has_won = win?(board, player)
|
35
|
+
winner = player.token if player_has_won
|
36
|
+
end
|
37
|
+
|
38
|
+
winner
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.win?(board, player)
|
42
|
+
diagonal_win?(board, player) ||
|
43
|
+
horizontal_win?(board, player) ||
|
44
|
+
vertical_win?(board, player)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def self.diagonal_win?(board, player)
|
50
|
+
set_win?(board.diagonals, player)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.horizontal_win?(board, player)
|
54
|
+
set_win?(board.rows, player)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.vertical_win?(board, player)
|
58
|
+
set_win?(board.columns, player)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.set_win?(sets, player)
|
62
|
+
sets.any? { |set| set.all? { |space| space.token == player.token unless space.nil? } }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'tic_tac_toes/command_line/prompt'
|
2
|
+
require 'tic_tac_toes/core/rules'
|
3
|
+
|
4
|
+
module TicTacToes
|
5
|
+
module Core
|
6
|
+
module Strings
|
7
|
+
smallest_row_size = Rules::ROW_SIZE_RANGE.min
|
8
|
+
largest_row_size = Rules::ROW_SIZE_RANGE.max
|
9
|
+
|
10
|
+
NOT_AN_INTEGER = "Input must be an integer"
|
11
|
+
INVALID_ROW_SIZE = "Input must be between #{smallest_row_size} and #{largest_row_size}"
|
12
|
+
INVALID_TOKEN = "Input must be a single, untaken character"
|
13
|
+
INVALID_DIFFICULTY = "Input must be a valid difficulty"
|
14
|
+
INVALID_MOVE = "Input must be a space that is on the board and untaken"
|
15
|
+
ROW_SIZE_SOLICITATION = "Pick row size of board:"
|
16
|
+
DIFFICULTY_SOLICITATION = "Pick difficulty (easy, medium, hard):"
|
17
|
+
MOVE_SOLICITATION = "Pick a space:"
|
18
|
+
THINKING = "Thinking..."
|
19
|
+
|
20
|
+
def self.token_solicitation(player)
|
21
|
+
"Pick #{player} token:"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.game_over_notification(winner)
|
25
|
+
"#{winner} wins!"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.board(board)
|
29
|
+
board_string = ""
|
30
|
+
board.rows.each_with_index do |row, index|
|
31
|
+
row_start_index = (index * board.row_size).to_i
|
32
|
+
at_last_row = index == board.row_size - 1
|
33
|
+
board_string << "\n"
|
34
|
+
board_string << row(row, row_start_index, board.size)
|
35
|
+
board_string << "\n"
|
36
|
+
board_string << horizontal_divider(board.row_size, board.size) unless at_last_row
|
37
|
+
end
|
38
|
+
board_string << "\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.row(row, row_start_index, board_size)
|
44
|
+
row_array = []
|
45
|
+
row.each_with_index do |space, index|
|
46
|
+
if space.nil?
|
47
|
+
board_index = index + row_start_index
|
48
|
+
row_array << empty_space(board_index, board_size)
|
49
|
+
else
|
50
|
+
row_array << token(space, board_size)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
row_array.join("|")
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.empty_space(board_index, board_size)
|
57
|
+
if space_needs_buffer?(board_index, board_size)
|
58
|
+
"[ #{board_index}]"
|
59
|
+
else
|
60
|
+
"[#{board_index}]"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.token(space, board_size)
|
65
|
+
token = get_colored_token(space)
|
66
|
+
|
67
|
+
if double_digit_board?(board_size)
|
68
|
+
" #{token} "
|
69
|
+
else
|
70
|
+
" #{token} "
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.get_colored_token(player)
|
75
|
+
if player.needs_to_think
|
76
|
+
return CommandLine::Prompt.red(player.token)
|
77
|
+
else
|
78
|
+
return CommandLine::Prompt.blue(player.token)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.space_needs_buffer?(board_index, board_size)
|
83
|
+
is_double_digit_board = double_digit_board?(board_size)
|
84
|
+
is_single_digit_space = board_index < 10
|
85
|
+
|
86
|
+
is_double_digit_board && is_single_digit_space
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.double_digit_board?(board_size)
|
90
|
+
board_size > 10
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.horizontal_divider(row_size, board_size)
|
94
|
+
horizontal_divider = ""
|
95
|
+
divider_unit = "-"
|
96
|
+
divider_units_per_space = double_digit_board?(board_size) ? 5 : 4
|
97
|
+
extra_units_per_row = 1
|
98
|
+
|
99
|
+
raw_length = (row_size * divider_units_per_space).to_i
|
100
|
+
truncated_length = raw_length - extra_units_per_row
|
101
|
+
truncated_length.times { horizontal_divider << divider_unit }
|
102
|
+
horizontal_divider
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'pg'
|
2
|
+
require 'tic_tac_toes/core/history'
|
3
|
+
|
4
|
+
module TicTacToes
|
5
|
+
module Database
|
6
|
+
class PGWrapper
|
7
|
+
def initialize(database)
|
8
|
+
@database = database
|
9
|
+
end
|
10
|
+
|
11
|
+
def record_game_history(history)
|
12
|
+
connection = establish_connection
|
13
|
+
record_board_size_and_winner(history, connection)
|
14
|
+
record_moves(history, connection)
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_game_histories
|
18
|
+
connection = establish_connection
|
19
|
+
games_result = connection.exec("SELECT * FROM games")
|
20
|
+
games = []
|
21
|
+
|
22
|
+
games_result.each_row do |row|
|
23
|
+
game_id = row[0]
|
24
|
+
board_size = row[1].to_i
|
25
|
+
winner = row[2]
|
26
|
+
moves_result = connection.exec("SELECT * FROM moves WHERE game = #{game_id}")
|
27
|
+
|
28
|
+
history = Core::History.new(self)
|
29
|
+
|
30
|
+
moves_result.each_row do |row|
|
31
|
+
token, space = row[2], row[3].to_i
|
32
|
+
history.record_move([token, space])
|
33
|
+
end
|
34
|
+
history.record_board_size(board_size)
|
35
|
+
history.record_winner(winner)
|
36
|
+
|
37
|
+
games << history
|
38
|
+
end
|
39
|
+
|
40
|
+
games
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def establish_connection
|
46
|
+
PG.connect(dbname: @database)
|
47
|
+
end
|
48
|
+
|
49
|
+
def record_board_size_and_winner(history, connection)
|
50
|
+
connection.exec("INSERT INTO games (board_size, winner) VALUES (
|
51
|
+
#{history.board_size},
|
52
|
+
'#{history.winner}')")
|
53
|
+
end
|
54
|
+
|
55
|
+
def record_moves(history, connection)
|
56
|
+
game_id = read_game_id(connection)
|
57
|
+
|
58
|
+
history.moves.each_with_index do |move, index|
|
59
|
+
move_number = index + 1
|
60
|
+
connection.exec("INSERT INTO moves (game, number, token, space) VALUES (
|
61
|
+
#{game_id},
|
62
|
+
#{move_number},
|
63
|
+
'#{move.first}',
|
64
|
+
#{move.last})")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_game_id(connection)
|
69
|
+
result = connection.exec("SELECT currval(pg_get_serial_sequence('games','id'))")
|
70
|
+
result.getvalue(0,0)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'tic_tac_toes/core/board'
|
2
|
+
require 'tic_tac_toes/core/game_state'
|
3
|
+
require 'tic_tac_toes/core/player_factory'
|
4
|
+
require 'tic_tac_toes/core/rules'
|
5
|
+
|
6
|
+
module TicTacToes
|
7
|
+
module UI
|
8
|
+
module Adapter
|
9
|
+
def self.new_board_structure
|
10
|
+
board = Core::Board.new
|
11
|
+
board.spaces
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make_move(board_structure, move, listener)
|
15
|
+
move = move.to_i
|
16
|
+
game_state = game_state_from_board_structure(board_structure)
|
17
|
+
|
18
|
+
human_player = game_state.players.first
|
19
|
+
game_state.board.place(human_player, move)
|
20
|
+
|
21
|
+
if Core::Rules.game_over?(game_state.board, game_state.players)
|
22
|
+
return listener.game_is_over(game_state_to_board_structure(game_state), "Game over")
|
23
|
+
end
|
24
|
+
|
25
|
+
game_state.turn_over(move)
|
26
|
+
|
27
|
+
computer_player = game_state.players.first
|
28
|
+
computer_player.place_and_return_move(game_state.board, game_state.players)
|
29
|
+
|
30
|
+
if Core::Rules.game_over?(game_state.board, game_state.players)
|
31
|
+
return listener.game_is_over(game_state_to_board_structure(game_state), "Game over")
|
32
|
+
end
|
33
|
+
|
34
|
+
listener.move_was_valid(game_state_to_board_structure(game_state))
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.game_state_from_board_structure(board_structure)
|
38
|
+
player_factory = Core::PlayerFactory.new('unused_io')
|
39
|
+
human_player = player_factory.generate_human_player('X')
|
40
|
+
computer_player = player_factory.generate_computer_player('O', :hard)
|
41
|
+
players = [human_player, computer_player]
|
42
|
+
|
43
|
+
board_structure_with_players = replace_tokens_with_players(board_structure, human_player, computer_player)
|
44
|
+
board = board_from_structure(board_structure_with_players)
|
45
|
+
|
46
|
+
Core::GameState.new(board, players, NullHistory.new)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.game_state_to_board_structure(game_state)
|
50
|
+
structure_with_players = game_state.board.spaces
|
51
|
+
|
52
|
+
replace_players_with_tokens(structure_with_players)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def self.replace_tokens_with_players(board_structure, human_player, computer_player)
|
58
|
+
board_structure.map do |space|
|
59
|
+
case space
|
60
|
+
when 'X'
|
61
|
+
human_player
|
62
|
+
when 'O'
|
63
|
+
computer_player
|
64
|
+
else
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.replace_players_with_tokens(board_structure)
|
71
|
+
board_structure.map { |space| space.token unless space.nil? }
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.board_from_structure(board_structure)
|
75
|
+
row_size = Math.sqrt(board_structure.count).to_i
|
76
|
+
board = Core::Board.new(row_size: row_size)
|
77
|
+
|
78
|
+
board_structure.each_with_index do |player, index|
|
79
|
+
board.place(player, index)
|
80
|
+
end
|
81
|
+
|
82
|
+
board
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class NullHistory
|
87
|
+
def record_board_size(size)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def record_move(move)
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
File without changes
|
data/lib/tic_tac_toes/version.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require 'command_line/menu'
|
2
|
-
require 'tic_tac_toes/board_factory'
|
3
|
-
require 'tic_tac_toes/player_factory'
|
4
|
-
require 'tic_tac_toes/io'
|
5
|
-
require 'tic_tac_toes/move_strategies/human'
|
6
|
-
require 'tic_tac_toes/move_strategies/medium_ai'
|
7
|
-
|
8
|
-
describe CommandLine::Menu do
|
1
|
+
require 'tic_tac_toes/command_line/menu'
|
2
|
+
require 'tic_tac_toes/core/board_factory'
|
3
|
+
require 'tic_tac_toes/core/player_factory'
|
4
|
+
require 'tic_tac_toes/core/io'
|
5
|
+
require 'tic_tac_toes/core/move_strategies/human'
|
6
|
+
require 'tic_tac_toes/core/move_strategies/medium_ai'
|
7
|
+
|
8
|
+
describe TicTacToes::CommandLine::Menu do
|
9
9
|
let(:io) { double }
|
10
|
-
let(:board_factory) { TicTacToes::BoardFactory.new }
|
11
|
-
let(:player_factory) { TicTacToes::PlayerFactory.new(io) }
|
12
|
-
let(:menu) { CommandLine::Menu.new(io, board_factory, player_factory) }
|
10
|
+
let(:board_factory) { TicTacToes::Core::BoardFactory.new }
|
11
|
+
let(:player_factory) { TicTacToes::Core::PlayerFactory.new(io) }
|
12
|
+
let(:menu) { TicTacToes::CommandLine::Menu.new(io, board_factory, player_factory) }
|
13
13
|
|
14
14
|
describe '#get_board' do
|
15
15
|
let(:invalid_row_size) { 11 }
|
@@ -40,8 +40,8 @@ describe CommandLine::Menu do
|
|
40
40
|
allow(io).to receive(:get_difficulty).and_return(difficulty)
|
41
41
|
|
42
42
|
human_player, computer_player = menu.get_players
|
43
|
-
expect(human_player.move_strategy).to be_a TicTacToes::MoveStrategies::Human
|
44
|
-
expect(computer_player.move_strategy).to eq(TicTacToes::MoveStrategies::MediumAI)
|
43
|
+
expect(human_player.move_strategy).to be_a TicTacToes::Core::MoveStrategies::Human
|
44
|
+
expect(computer_player.move_strategy).to eq(TicTacToes::Core::MoveStrategies::MediumAI)
|
45
45
|
end
|
46
46
|
|
47
47
|
context 'when given an invalid (non-single-character) token' do
|
@@ -92,7 +92,7 @@ describe CommandLine::Menu do
|
|
92
92
|
allow(io).to receive(:get_difficulty).and_return(invalid_difficulty, valid_difficulty)
|
93
93
|
|
94
94
|
computer_player = menu.get_players.last
|
95
|
-
expect(computer_player.move_strategy).to eq(TicTacToes::MoveStrategies::MediumAI)
|
95
|
+
expect(computer_player.move_strategy).to eq(TicTacToes::Core::MoveStrategies::MediumAI)
|
96
96
|
end
|
97
97
|
end
|
98
98
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'command_line/runner'
|
1
|
+
require 'tic_tac_toes/command_line/runner'
|
2
2
|
|
3
|
-
describe CommandLine::Runner do
|
3
|
+
describe TicTacToes::CommandLine::Runner do
|
4
4
|
describe '#run' do
|
5
5
|
let(:game_state) { double(board: 'board', players: 'players', game_over: true) }
|
6
6
|
|
@@ -11,15 +11,12 @@ describe CommandLine::Runner do
|
|
11
11
|
get_players: true) }
|
12
12
|
let(:game_state) { double(board: 'board',
|
13
13
|
players: 'players',
|
14
|
-
game_over: true
|
15
|
-
|
16
|
-
let(:rules) { double(game_over?: true,
|
14
|
+
game_over: true,
|
15
|
+
game_over?: true,
|
17
16
|
determine_winner: true) }
|
17
|
+
let(:game_state_factory) { double(generate_game_state: game_state) }
|
18
18
|
|
19
|
-
let(:runner)
|
20
|
-
menu,
|
21
|
-
game_state_factory,
|
22
|
-
rules) }
|
19
|
+
let(:runner) { TicTacToes::CommandLine::Runner.new(io, menu, game_state_factory) }
|
23
20
|
|
24
21
|
it 'gets an initial game state' do
|
25
22
|
expect(game_state_factory).to receive(:generate_game_state)
|
@@ -27,7 +24,7 @@ describe CommandLine::Runner do
|
|
27
24
|
end
|
28
25
|
|
29
26
|
it 'takes turns until the game is over' do
|
30
|
-
allow(
|
27
|
+
allow(game_state).to receive(:game_over?).and_return(false, true)
|
31
28
|
|
32
29
|
expect(runner).to receive(:take_turn).once
|
33
30
|
runner.run
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'tic_tac_toes/board_factory'
|
1
|
+
require 'tic_tac_toes/core/board_factory'
|
2
2
|
|
3
|
-
describe TicTacToes::BoardFactory do
|
3
|
+
describe TicTacToes::Core::BoardFactory do
|
4
4
|
describe '#generate_board' do
|
5
5
|
it 'returns a board object generated from a row size' do
|
6
|
-
board_factory = TicTacToes::BoardFactory.new
|
6
|
+
board_factory = TicTacToes::Core::BoardFactory.new
|
7
7
|
row_size = 5
|
8
8
|
|
9
9
|
board = board_factory.generate_board(row_size)
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'test_board_generator'
|
2
|
-
require 'tic_tac_toes/board'
|
1
|
+
require 'tic_tac_toes/test_board_generator'
|
2
|
+
require 'tic_tac_toes/core/board'
|
3
3
|
|
4
|
-
describe TicTacToes::Board do
|
4
|
+
describe TicTacToes::Core::Board do
|
5
5
|
describe '#place (and #space)' do
|
6
|
-
let(:board) { TicTacToes::Board.new(row_size: 3) }
|
6
|
+
let(:board) { TicTacToes::Core::Board.new(row_size: 3) }
|
7
7
|
|
8
8
|
it "returns nil if the space is not nil" do
|
9
9
|
first_token, second_token = :X, :O
|
@@ -28,9 +28,9 @@ describe TicTacToes::Board do
|
|
28
28
|
|
29
29
|
describe '#open_spaces' do
|
30
30
|
it "returns an array of the board's nil spaces" do
|
31
|
-
board = TestBoardGenerator.generate([:X, :O, nil,
|
32
|
-
|
33
|
-
|
31
|
+
board = TicTacToes::TestBoardGenerator.generate([:X, :O, nil,
|
32
|
+
:O, :O, :X,
|
33
|
+
:X, :X, nil])
|
34
34
|
open_spaces = [2, 8]
|
35
35
|
|
36
36
|
expect(board.open_spaces).to eql(open_spaces)
|
@@ -40,10 +40,10 @@ describe TicTacToes::Board do
|
|
40
40
|
|
41
41
|
describe '#rows' do
|
42
42
|
it "returns an array of row arrays based on the board's spaces" do
|
43
|
-
board = TestBoardGenerator.generate([ :X, :X, :X, :X,
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
board = TicTacToes::TestBoardGenerator.generate([ :X, :X, :X, :X,
|
44
|
+
nil, nil, nil, nil,
|
45
|
+
nil, nil, nil, nil,
|
46
|
+
nil, nil, nil, nil])
|
47
47
|
first_row = [:X, :X, :X, :X]
|
48
48
|
|
49
49
|
expect(board.rows.size).to eq(4)
|
@@ -54,10 +54,10 @@ describe TicTacToes::Board do
|
|
54
54
|
|
55
55
|
describe '#columns' do
|
56
56
|
it "returns an array of column arrays based on the board's spaces" do
|
57
|
-
board = TestBoardGenerator.generate([:X, nil, nil, nil,
|
58
|
-
|
59
|
-
|
60
|
-
|
57
|
+
board = TicTacToes::TestBoardGenerator.generate([:X, nil, nil, nil,
|
58
|
+
:X, nil, nil, nil,
|
59
|
+
:X, nil, nil, nil,
|
60
|
+
:X, nil, nil, nil])
|
61
61
|
first_column = [:X, :X, :X, :X]
|
62
62
|
|
63
63
|
expect(board.columns.size).to eq(4)
|
@@ -68,10 +68,10 @@ describe TicTacToes::Board do
|
|
68
68
|
|
69
69
|
describe '#diagonals' do
|
70
70
|
it "returns an array of diagonal arrays based on the board's spaces" do
|
71
|
-
board = TestBoardGenerator.generate([ :X, nil, nil, :O,
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
board = TicTacToes::TestBoardGenerator.generate([ :X, nil, nil, :O,
|
72
|
+
nil, :X, :O, nil,
|
73
|
+
nil, :O, :X, nil,
|
74
|
+
:O, nil, nil, :X])
|
75
75
|
back_diagonal = [:X, :X, :X, :X]
|
76
76
|
front_diagonal = [:O, :O, :O, :O]
|
77
77
|
|
@@ -84,14 +84,14 @@ describe TicTacToes::Board do
|
|
84
84
|
|
85
85
|
describe '#full?' do
|
86
86
|
it "returns false if any spaces are still nil" do
|
87
|
-
board = TicTacToes::Board.new
|
87
|
+
board = TicTacToes::Core::Board.new
|
88
88
|
expect(board.full?).to be false
|
89
89
|
end
|
90
90
|
|
91
91
|
it "returns true if all spaces are non-nil" do
|
92
|
-
board = TestBoardGenerator.generate([:X, :O, :X,
|
93
|
-
|
94
|
-
|
92
|
+
board = TicTacToes::TestBoardGenerator.generate([:X, :O, :X,
|
93
|
+
:O, :X, :O,
|
94
|
+
:X, :O, :X])
|
95
95
|
|
96
96
|
expect(board.full?).to be true
|
97
97
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'tic_tac_toes/game_state_factory'
|
1
|
+
require 'tic_tac_toes/core/game_state_factory'
|
2
2
|
|
3
|
-
describe TicTacToes::GameStateFactory do
|
3
|
+
describe TicTacToes::Core::GameStateFactory do
|
4
4
|
describe '#generate_game_state' do
|
5
5
|
it 'returns a game state object generated from a board object and player array' do
|
6
6
|
history = double(record_board_size: true)
|
7
|
-
game_state_factory = TicTacToes::GameStateFactory.new(history)
|
7
|
+
game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
|
8
8
|
board, players = double(size: 3), double
|
9
9
|
|
10
10
|
game_state = game_state_factory.generate_game_state(board, players)
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'tic_tac_toes/core/game_state'
|
2
|
+
require 'tic_tac_toes/test_board_generator'
|
3
|
+
|
4
|
+
describe TicTacToes::Core::GameState do
|
5
|
+
describe '@initialize' do
|
6
|
+
it "records its board's size" do
|
7
|
+
size = 5
|
8
|
+
board = double(size: size)
|
9
|
+
history = double
|
10
|
+
|
11
|
+
expect(history).to receive(:record_board_size).with(size)
|
12
|
+
TicTacToes::Core::GameState.new(board, 'players', history)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#current_player' do
|
17
|
+
it 'returns the first item of its players array' do
|
18
|
+
history = double(record_board_size: true)
|
19
|
+
players = ['first_player', 'second_player']
|
20
|
+
game_state = TicTacToes::Core::GameState.new('board', players, history)
|
21
|
+
|
22
|
+
current_player = game_state.current_player
|
23
|
+
expect(current_player).to eq('first_player')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#turn_over' do
|
28
|
+
it 'records the last move' do
|
29
|
+
move = double
|
30
|
+
players = double(rotate!: true)
|
31
|
+
history = double(record_board_size: true)
|
32
|
+
game_state = TicTacToes::Core::GameState.new('board', players, history)
|
33
|
+
|
34
|
+
expect(history).to receive(:record_move).with(move)
|
35
|
+
game_state.turn_over(move)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'rotates its player array' do
|
39
|
+
players = ['first_player', 'second_player']
|
40
|
+
history = double(record_board_size: true, record_move: true)
|
41
|
+
game_state = TicTacToes::Core::GameState.new('board', players, history)
|
42
|
+
|
43
|
+
game_state.turn_over('move')
|
44
|
+
expect(game_state.current_player).to eq('second_player')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#game_over' do
|
49
|
+
it 'records the winner' do
|
50
|
+
winner = double
|
51
|
+
history = double(record_board_size: true, persist: true)
|
52
|
+
game_state = TicTacToes::Core::GameState.new('board', 'players', history)
|
53
|
+
|
54
|
+
expect(history).to receive(:record_winner).with(winner)
|
55
|
+
game_state.game_over(winner)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'persists its history' do
|
59
|
+
winner = double
|
60
|
+
history = double(record_board_size: true, record_winner: true)
|
61
|
+
game_state = TicTacToes::Core::GameState.new('board', 'players', history)
|
62
|
+
|
63
|
+
expect(history).to receive(:persist)
|
64
|
+
game_state.game_over(winner)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#game_over?' do
|
69
|
+
let(:x) { double("human player", token: "x") }
|
70
|
+
let(:o) { double("computer player", token: "o") }
|
71
|
+
let(:players) { [x, o] }
|
72
|
+
|
73
|
+
let(:history) { double(record_board_size: true) }
|
74
|
+
|
75
|
+
it "returns false if there is not yet a winner and the board is not full" do
|
76
|
+
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
77
|
+
o, o, nil,
|
78
|
+
x, o, x])
|
79
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
80
|
+
expect(game_state.game_over?).to be false
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns true if any player has won" do
|
84
|
+
board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
|
85
|
+
nil, x, nil,
|
86
|
+
nil, nil, x])
|
87
|
+
|
88
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
89
|
+
|
90
|
+
expect(game_state.game_over?).to be true
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns true if the board is full" do
|
94
|
+
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
95
|
+
o, o, x,
|
96
|
+
x, o, x])
|
97
|
+
|
98
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
99
|
+
expect(game_state.game_over?).to be true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe '#determine_winner' do
|
104
|
+
let(:x) { double("human player", token: "x") }
|
105
|
+
let(:o) { double("computer player", token: "o") }
|
106
|
+
let(:players) { [x, o] }
|
107
|
+
|
108
|
+
let(:history) { double(record_board_size: true) }
|
109
|
+
it "returns the winning token when there is a winner" do
|
110
|
+
board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
|
111
|
+
nil, o, nil,
|
112
|
+
nil, nil, o])
|
113
|
+
winning_token = "o"
|
114
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
115
|
+
|
116
|
+
expect(game_state.determine_winner).to eql(winning_token)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns nil if there is not a winner" do
|
120
|
+
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
121
|
+
o, o, nil,
|
122
|
+
x, o, x])
|
123
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
124
|
+
|
125
|
+
expect(game_state.determine_winner).to be_nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|