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.
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