tic_tac_toes 0.0.7 → 0.0.8
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.
- 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
|