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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/Rakefile +2 -2
  4. data/bin/tic_tac_toes +18 -22
  5. data/lib/tic_tac_toes/command_line/menu.rb +55 -0
  6. data/lib/tic_tac_toes/command_line/prompt.rb +29 -0
  7. data/lib/tic_tac_toes/command_line/runner.rb +38 -0
  8. data/lib/tic_tac_toes/core/board.rb +64 -0
  9. data/lib/tic_tac_toes/core/board_factory.rb +11 -0
  10. data/lib/tic_tac_toes/core/game_state.rb +40 -0
  11. data/lib/tic_tac_toes/core/game_state_factory.rb +15 -0
  12. data/lib/tic_tac_toes/core/history.rb +29 -0
  13. data/lib/tic_tac_toes/core/io.rb +93 -0
  14. data/lib/tic_tac_toes/core/move_strategies/easy_ai.rb +13 -0
  15. data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +89 -0
  16. data/lib/tic_tac_toes/core/move_strategies/human.rb +20 -0
  17. data/lib/tic_tac_toes/core/move_strategies/medium_ai.rb +15 -0
  18. data/lib/tic_tac_toes/core/player.rb +22 -0
  19. data/lib/tic_tac_toes/core/player_factory.rb +30 -0
  20. data/lib/tic_tac_toes/core/rules.rb +66 -0
  21. data/lib/tic_tac_toes/core/strings.rb +106 -0
  22. data/lib/tic_tac_toes/database/pg_wrapper.rb +74 -0
  23. data/lib/tic_tac_toes/ui/adapter.rb +96 -0
  24. data/lib/tic_tac_toes/ui/serializer.rb +0 -0
  25. data/lib/tic_tac_toes/version.rb +1 -1
  26. data/spec/{command_line → tic_tac_toes/command_line}/menu_spec.rb +14 -14
  27. data/spec/{command_line → tic_tac_toes/command_line}/runner_spec.rb +7 -10
  28. data/spec/tic_tac_toes/{board_factory_spec.rb → core/board_factory_spec.rb} +3 -3
  29. data/spec/tic_tac_toes/{board_spec.rb → core/board_spec.rb} +23 -23
  30. data/spec/tic_tac_toes/{game_state_factory_spec.rb → core/game_state_factory_spec.rb} +3 -3
  31. data/spec/tic_tac_toes/core/game_state_spec.rb +129 -0
  32. data/spec/tic_tac_toes/{history_spec.rb → core/history_spec.rb} +3 -3
  33. data/spec/tic_tac_toes/{io_spec.rb → core/io_spec.rb} +7 -7
  34. data/spec/tic_tac_toes/core/move_strategies/easy_ai_spec.rb +19 -0
  35. data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +121 -0
  36. data/spec/tic_tac_toes/{move_strategies → core/move_strategies}/human_spec.rb +3 -3
  37. data/spec/tic_tac_toes/core/move_strategies/medium_ai_spec.rb +21 -0
  38. data/spec/tic_tac_toes/{player_factory_spec.rb → core/player_factory_spec.rb} +7 -7
  39. data/spec/tic_tac_toes/{player_spec.rb → core/player_spec.rb} +5 -5
  40. data/spec/tic_tac_toes/{rules_spec.rb → core/rules_spec.rb} +34 -34
  41. data/spec/tic_tac_toes/{strings_spec.rb → core/strings_spec.rb} +8 -8
  42. data/spec/{database → tic_tac_toes/database}/pg_wrapper_spec.rb +3 -3
  43. data/spec/tic_tac_toes/test_board_generator.rb +17 -0
  44. data/spec/{ui → tic_tac_toes/ui}/adapter_spec.rb +13 -13
  45. data/spec/tic_tac_toes/ui/serializer_spec.rb +0 -0
  46. metadata +64 -61
  47. data/lib/command_line/menu.rb +0 -53
  48. data/lib/command_line/prompt.rb +0 -27
  49. data/lib/command_line/runner.rb +0 -37
  50. data/lib/database/pg_wrapper.rb +0 -72
  51. data/lib/tic_tac_toes/board.rb +0 -62
  52. data/lib/tic_tac_toes/board_factory.rb +0 -9
  53. data/lib/tic_tac_toes/game_state.rb +0 -27
  54. data/lib/tic_tac_toes/game_state_factory.rb +0 -14
  55. data/lib/tic_tac_toes/history.rb +0 -27
  56. data/lib/tic_tac_toes/io.rb +0 -91
  57. data/lib/tic_tac_toes/move_strategies/easy_ai.rb +0 -11
  58. data/lib/tic_tac_toes/move_strategies/hard_ai.rb +0 -87
  59. data/lib/tic_tac_toes/move_strategies/human.rb +0 -18
  60. data/lib/tic_tac_toes/move_strategies/medium_ai.rb +0 -13
  61. data/lib/tic_tac_toes/player.rb +0 -20
  62. data/lib/tic_tac_toes/player_factory.rb +0 -28
  63. data/lib/tic_tac_toes/rules.rb +0 -64
  64. data/lib/tic_tac_toes/strings.rb +0 -104
  65. data/lib/ui/adapter.rb +0 -94
  66. data/spec/test_board_generator.rb +0 -15
  67. data/spec/tic_tac_toes/game_state_spec.rb +0 -66
  68. data/spec/tic_tac_toes/move_strategies/easy_ai_spec.rb +0 -19
  69. data/spec/tic_tac_toes/move_strategies/hard_ai_spec.rb +0 -121
  70. data/spec/tic_tac_toes/move_strategies/medium_ai_spec.rb +0 -21
  71. /data/lib/{tasks → tic_tac_toes/tasks}/destroy_databases.rake +0 -0
  72. /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
@@ -1,3 +1,3 @@
1
1
  module TicTacToes
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -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
- let(:game_state_factory) { double(generate_game_state: game_state) }
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) { CommandLine::Runner.new(io,
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(rules).to receive(:game_over?).and_return(false, true)
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
- :O, :O, :X,
33
- :X, :X, nil])
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
- nil, nil, nil, nil,
45
- nil, nil, nil, nil,
46
- nil, nil, nil, nil])
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
- :X, nil, nil, nil,
59
- :X, nil, nil, nil,
60
- :X, nil, nil, nil])
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
- nil, :X, :O, nil,
73
- nil, :O, :X, nil,
74
- :O, nil, nil, :X])
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
- :O, :X, :O,
94
- :X, :O, :X])
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