tic_tac_toes 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/bin/tic_tac_toes +2 -6
  4. data/lib/tic_tac_toes/command_line/menu.rb +3 -3
  5. data/lib/tic_tac_toes/command_line/runner.rb +6 -4
  6. data/lib/tic_tac_toes/core/game_state.rb +8 -0
  7. data/lib/tic_tac_toes/core/history.rb +1 -0
  8. data/lib/tic_tac_toes/core/move_strategies/easy_ai.rb +2 -4
  9. data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +27 -27
  10. data/lib/tic_tac_toes/core/move_strategies/human.rb +2 -2
  11. data/lib/tic_tac_toes/core/move_strategies/medium_ai.rb +2 -2
  12. data/lib/tic_tac_toes/core/player.rb +3 -3
  13. data/lib/tic_tac_toes/core/rules.rb +1 -1
  14. data/lib/tic_tac_toes/ui/adapter.rb +5 -5
  15. data/lib/tic_tac_toes/version.rb +1 -1
  16. data/spec/tic_tac_toes/command_line/menu_spec.rb +1 -3
  17. data/spec/tic_tac_toes/command_line/runner_spec.rb +0 -34
  18. data/spec/tic_tac_toes/core/game_state_spec.rb +54 -7
  19. data/spec/tic_tac_toes/core/history_spec.rb +4 -4
  20. data/spec/tic_tac_toes/core/move_strategies/easy_ai_spec.rb +14 -7
  21. data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +43 -28
  22. data/spec/tic_tac_toes/core/move_strategies/human_spec.rb +9 -9
  23. data/spec/tic_tac_toes/core/move_strategies/medium_ai_spec.rb +14 -9
  24. data/spec/tic_tac_toes/core/player_spec.rb +17 -14
  25. data/spec/tic_tac_toes/core/rules_spec.rb +3 -2
  26. data/spec/tic_tac_toes/ui/adapter_spec.rb +9 -9
  27. metadata +2 -8
  28. data/lib/tic_tac_toes/core/board_factory.rb +0 -11
  29. data/lib/tic_tac_toes/core/game_state_factory.rb +0 -15
  30. data/spec/tic_tac_toes/core/board_factory_spec.rb +0 -13
  31. data/spec/tic_tac_toes/core/game_state_factory_spec.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 919ce75f7f8ad28d261cef9d2a9c7dc1cb0fdad7
4
- data.tar.gz: 676c42fa2f8acb3c7dc03d989ab40febdd9958ee
3
+ metadata.gz: 5b7b828672ca3ca2908fc62b6f7cf25622a16322
4
+ data.tar.gz: 704c24b5374e84549277e659401621ee7044ee75
5
5
  SHA512:
6
- metadata.gz: 8e94d802c318a795d6272a5341a2183248cf355109abf6022ce3fd725c2080987028bc1037fb3134fc1e31f321468b93c21e66e4ea3d663c98cd45a69164efef
7
- data.tar.gz: 7bd562a193cde805efb2d9ee5c12f4dc10b4e9fa78b6f1ccc622d1e4cc1c7b1438f5bf3adf523ee41ddab88362fdee97301c2f6c114ac40096fbbad4b7600342
6
+ metadata.gz: a9a5b96876e221e2b43c7e5996be97ea4e14af904017d6f7e5f64e3ae978ab08b71ef3f194ad25ffe690a785ca8a8168943c290b396c6c414a957dd042be566d
7
+ data.tar.gz: 14ff8a5d98dd83cadd2821a3321a758a716eb81783d617539777093c4eae479a5b7057fb5bb364a169169d089b58bd902a03352feb6f96c8bd5ef62e7133009f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tic_tac_toes (0.0.9)
4
+ tic_tac_toes (0.1.0)
5
5
  pg
6
6
 
7
7
  GEM
data/bin/tic_tac_toes CHANGED
@@ -5,13 +5,11 @@ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../lib')
5
5
  require 'tic_tac_toes/command_line/prompt'
6
6
  require 'tic_tac_toes/core/io'
7
7
 
8
- require 'tic_tac_toes/core/board_factory'
9
8
  require 'tic_tac_toes/core/player_factory'
10
9
  require 'tic_tac_toes/command_line/menu'
11
10
 
12
11
  require 'tic_tac_toes/database/pg_wrapper'
13
12
  require 'tic_tac_toes/core/history'
14
- require 'tic_tac_toes/core/game_state_factory'
15
13
 
16
14
  require 'tic_tac_toes/command_line/runner'
17
15
 
@@ -20,12 +18,10 @@ database = "tic_tac_toes"
20
18
  prompt = TicTacToes::CommandLine::Prompt
21
19
  io = TicTacToes::Core::IO.new(prompt)
22
20
 
23
- board_factory = TicTacToes::Core::BoardFactory.new
24
21
  player_factory = TicTacToes::Core::PlayerFactory.new(io)
25
- menu = TicTacToes::CommandLine::Menu.new(io, board_factory, player_factory)
22
+ menu = TicTacToes::CommandLine::Menu.new(io, player_factory)
26
23
 
27
24
  database_wrapper = TicTacToes::Database::PGWrapper.new(database)
28
25
  history = TicTacToes::Core::History.new(database_wrapper)
29
- game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
30
26
 
31
- TicTacToes::CommandLine::Runner.new(io, menu, game_state_factory).run
27
+ TicTacToes::CommandLine::Runner.new(io, menu, history).run
@@ -1,16 +1,16 @@
1
+ require 'tic_tac_toes/core/board'
1
2
  require 'tic_tac_toes/core/rules'
2
3
 
3
4
  module TicTacToes
4
5
  module CommandLine
5
6
  class Menu
6
- def initialize(io, board_factory, player_factory)
7
+ def initialize(io, player_factory)
7
8
  @io = io
8
- @board_factory = board_factory
9
9
  @player_factory = player_factory
10
10
  end
11
11
 
12
12
  def get_board
13
- @board_factory.generate_board(get_row_size)
13
+ TicTacToes::Core::Board.new(row_size: get_row_size)
14
14
  end
15
15
 
16
16
  def get_players
@@ -1,15 +1,17 @@
1
+ require 'tic_tac_toes/core/game_state'
2
+
1
3
  module TicTacToes
2
4
  module CommandLine
3
5
  class Runner
4
- def initialize(io, menu, game_state_factory)
6
+ def initialize(io, menu, history)
5
7
  @io = io
6
8
  @menu = menu
7
- @game_state_factory = game_state_factory
9
+ @history = history
8
10
  end
9
11
 
10
12
  def run
11
13
  board, players = @menu.get_board, @menu.get_players
12
- game_state = @game_state_factory.generate_game_state(board, players)
14
+ game_state = TicTacToes::Core::GameState.new(board, players, @history)
13
15
 
14
16
  take_turn(game_state) until game_state.game_over?
15
17
  end_game(game_state)
@@ -21,7 +23,7 @@ module TicTacToes
21
23
  @io.draw_board(game_state.board)
22
24
  @io.thinking_notification if game_state.current_player.needs_to_think
23
25
 
24
- move = game_state.current_player.place_and_return_move(game_state.board, game_state.players)
26
+ move = game_state.current_player.place_and_return_move(game_state)
25
27
  game_state.turn_over(move)
26
28
  end
27
29
 
@@ -14,10 +14,18 @@ module TicTacToes
14
14
  @history.record_board_size(@board.size)
15
15
  end
16
16
 
17
+ def place_move(space)
18
+ @board.place(current_player, space)
19
+ end
20
+
17
21
  def current_player
18
22
  @players.first
19
23
  end
20
24
 
25
+ def next_player
26
+ @players[1]
27
+ end
28
+
21
29
  def turn_over(move)
22
30
  @history.record_move(move)
23
31
  @players.rotate!
@@ -18,6 +18,7 @@ module TicTacToes
18
18
 
19
19
  def record_winner(winner)
20
20
  winner = "Draw" if winner.nil?
21
+ winner = winner.token unless winner.is_a? String
21
22
  @winner = winner
22
23
  end
23
24
 
@@ -1,11 +1,9 @@
1
- require 'tic_tac_toes/core/board'
2
-
3
1
  module TicTacToes
4
2
  module Core
5
3
  module MoveStrategies
6
4
  module EasyAI
7
- def self.move(board, players)
8
- board.open_spaces.sample
5
+ def self.move(game_state)
6
+ game_state.board.open_spaces.sample
9
7
  end
10
8
  end
11
9
  end
@@ -4,14 +4,18 @@ module TicTacToes
4
4
  module Core
5
5
  module MoveStrategies
6
6
  module HardAI
7
- def self.move(board, players)
7
+ ALPHA, BETA = -1, 1
8
+
9
+ def self.move(game_state)
10
+ board = game_state.board
11
+
8
12
  return 0 if nine_board_first_move?(board)
9
13
  return second_move(board) if nine_board_second_move?(board)
10
14
 
11
15
  open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
12
16
 
13
17
  open_spaces.each do |space, score|
14
- score = minimax(generate_board(players.first, space, board), :min, players)
18
+ score = minimax(generate_game_state(space, game_state), :min, ALPHA, BETA)
15
19
  open_spaces[space] = score
16
20
  end
17
21
 
@@ -21,43 +25,39 @@ module TicTacToes
21
25
 
22
26
  private
23
27
 
24
- def self.minimax(board, current_player, players)
25
- return score(board, players) if Rules.game_over?(board, players)
28
+ def self.minimax(game_state, current_player, alpha, beta)
29
+ return score(game_state) if game_state.game_over?
26
30
 
27
31
  if current_player == :max
28
- best_score = -1
29
- board.open_spaces.each do |space|
30
- score = minimax(generate_board(players.first, space, board), :min, players)
31
- best_score = [best_score, score].max
32
+ game_state.board.open_spaces.each do |space|
33
+ alpha = [alpha, minimax(generate_game_state(space, game_state), :min, alpha, beta)].max
34
+ break if beta <= alpha
32
35
  end
33
- best_score
34
-
36
+ alpha
35
37
  elsif current_player == :min
36
- best_score = 1
37
- board.open_spaces.each do |space|
38
- score = minimax(generate_board(players.last, space, board), :max, players)
39
- best_score = [best_score, score].min
38
+ game_state.board.open_spaces.each do |space|
39
+ beta = [beta, minimax(generate_game_state(space, game_state), :max, alpha, beta)].min
40
+ break if beta <= alpha
40
41
  end
41
- best_score
42
+ beta
42
43
  end
43
44
  end
44
45
 
45
- def self.generate_board(player, space, board)
46
- new_board = Marshal.load(Marshal.dump(board))
47
- new_board.place(player, space)
48
- new_board
46
+ def self.generate_game_state(space, game_state)
47
+ new_game_state = Marshal.load(Marshal.dump(game_state))
48
+ new_game_state.place_move(space)
49
+ new_game_state.turn_over([])
50
+ new_game_state
49
51
  end
50
52
 
51
- def self.score(board, players)
52
- own_token, opponent_token = players.first.token, players.last.token
53
-
54
- case Rules.determine_winner(board, players)
55
- when own_token
53
+ def self.score(game_state)
54
+ winner = game_state.determine_winner
55
+ if winner.nil?
56
+ 0
57
+ elsif winner.move_strategy == self
56
58
  1
57
- when opponent_token
58
- -1
59
59
  else
60
- 0
60
+ -1
61
61
  end
62
62
  end
63
63
 
@@ -6,13 +6,13 @@ module TicTacToes
6
6
  @io = io
7
7
  end
8
8
 
9
- def move(board, players)
9
+ def move(game_state)
10
10
  @io.move_solicitation
11
11
 
12
12
  Integer(@io.solicit_input)
13
13
  rescue ArgumentError
14
14
  @io.not_an_integer_error
15
- move(board, players)
15
+ move(game_state)
16
16
  end
17
17
  end
18
18
  end
@@ -5,9 +5,9 @@ module TicTacToes
5
5
  module Core
6
6
  module MoveStrategies
7
7
  module MediumAI
8
- def self.move(board, players)
8
+ def self.move(game_state)
9
9
  move_strategy = [EasyAI, HardAI, HardAI].sample
10
- move_strategy.move(board, players)
10
+ move_strategy.move(game_state)
11
11
  end
12
12
  end
13
13
  end
@@ -10,10 +10,10 @@ module TicTacToes
10
10
  @io = io
11
11
  end
12
12
 
13
- def place_and_return_move(board, players)
13
+ def place_and_return_move(game_state)
14
14
  loop do
15
- space = @move_strategy.move(board, players)
16
- break [@token, space] if board.place(self, space)
15
+ space = @move_strategy.move(game_state)
16
+ break [@token, space] if game_state.place_move(space)
17
17
  @io.invalid_move_error
18
18
  end
19
19
  end
@@ -32,7 +32,7 @@ module TicTacToes
32
32
 
33
33
  players.each do |player|
34
34
  player_has_won = win?(board, player)
35
- winner = player.token if player_has_won
35
+ winner = player if player_has_won
36
36
  end
37
37
 
38
38
  winner
@@ -4,7 +4,7 @@ module TicTacToes
4
4
  def self.make_move(game_state, move, listener)
5
5
  unless move.nil?
6
6
  move = move.to_i
7
- game_state.board.place(game_state.current_player, move)
7
+ game_state.place_move(move)
8
8
  end
9
9
 
10
10
  if game_state.game_over?
@@ -13,7 +13,7 @@ module TicTacToes
13
13
  end
14
14
 
15
15
  game_state.turn_over(move)
16
- game_state.current_player.place_and_return_move(game_state.board, game_state.players)
16
+ game_state.current_player.place_and_return_move(game_state)
17
17
 
18
18
  if game_state.game_over?
19
19
  tell_listener_game_is_over(game_state, listener)
@@ -26,13 +26,13 @@ module TicTacToes
26
26
  private
27
27
 
28
28
  def self.tell_listener_game_is_over(game_state, listener)
29
- winning_token = game_state.determine_winner
29
+ winning_player = game_state.determine_winner
30
30
 
31
- case winning_token
31
+ case winning_player
32
32
  when nil
33
33
  listener.game_ended_in_draw(game_state)
34
34
  else
35
- listener.game_ended_in_winner(game_state, winning_token)
35
+ listener.game_ended_in_winner(game_state, winning_player.token)
36
36
  end
37
37
  end
38
38
  end
@@ -1,3 +1,3 @@
1
1
  module TicTacToes
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,5 +1,4 @@
1
1
  require 'tic_tac_toes/command_line/menu'
2
- require 'tic_tac_toes/core/board_factory'
3
2
  require 'tic_tac_toes/core/player_factory'
4
3
  require 'tic_tac_toes/core/io'
5
4
  require 'tic_tac_toes/core/move_strategies/human'
@@ -7,9 +6,8 @@ require 'tic_tac_toes/core/move_strategies/medium_ai'
7
6
 
8
7
  describe TicTacToes::CommandLine::Menu do
9
8
  let(:io) { double }
10
- let(:board_factory) { TicTacToes::Core::BoardFactory.new }
11
9
  let(:player_factory) { TicTacToes::Core::PlayerFactory.new(io) }
12
- let(:menu) { TicTacToes::CommandLine::Menu.new(io, board_factory, player_factory) }
10
+ let(:menu) { TicTacToes::CommandLine::Menu.new(io, player_factory) }
13
11
 
14
12
  describe '#get_board' do
15
13
  let(:invalid_row_size) { 11 }
@@ -1,38 +1,4 @@
1
1
  require 'tic_tac_toes/command_line/runner'
2
2
 
3
3
  describe TicTacToes::CommandLine::Runner do
4
- describe '#run' do
5
- let(:game_state) { double(board: 'board', players: 'players', game_over: true) }
6
-
7
- let(:io) { double(draw_board: true,
8
- thinking_notification: true,
9
- game_over_notification: true) }
10
- let(:menu) { double(get_board: true,
11
- get_players: true) }
12
- let(:game_state) { double(board: 'board',
13
- players: 'players',
14
- game_over: true,
15
- game_over?: true,
16
- determine_winner: true) }
17
- let(:game_state_factory) { double(generate_game_state: game_state) }
18
-
19
- let(:runner) { TicTacToes::CommandLine::Runner.new(io, menu, game_state_factory) }
20
-
21
- it 'gets an initial game state' do
22
- expect(game_state_factory).to receive(:generate_game_state)
23
- runner.run
24
- end
25
-
26
- it 'takes turns until the game is over' do
27
- allow(game_state).to receive(:game_over?).and_return(false, true)
28
-
29
- expect(runner).to receive(:take_turn).once
30
- runner.run
31
- end
32
-
33
- it 'ends the game when the game is over' do
34
- expect(runner).to receive(:end_game)
35
- runner.run
36
- end
37
- end
38
4
  end
@@ -13,6 +13,41 @@ describe TicTacToes::Core::GameState do
13
13
  end
14
14
  end
15
15
 
16
+ describe '#place_move' do
17
+ let(:x) { double("human player", token: "x") }
18
+ let(:o) { double("computer player", token: "o") }
19
+ let(:players) { [x, o] }
20
+
21
+ let(:history) { double(record_board_size: true) }
22
+
23
+ it 'returns nil if the board’s space is not nil' do
24
+ board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
25
+ nil, nil, nil,
26
+ nil, nil, nil])
27
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
28
+
29
+ expect(game_state.place_move(0)).to eq(nil)
30
+ end
31
+
32
+ it 'returns nil if it isn’t in its board’s spaces range' do
33
+ board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
34
+ nil, nil, nil,
35
+ nil, nil, nil])
36
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
37
+ expect(game_state.place_move(9)).to eq(nil)
38
+ end
39
+
40
+ it 'places the current player at the space if the space is nil and in the board’s spaces range' do
41
+ board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
42
+ nil, nil, nil,
43
+ nil, nil, nil])
44
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
45
+
46
+ game_state.place_move(0)
47
+ expect(game_state.board.space(0)).to eq(game_state.current_player)
48
+ end
49
+ end
50
+
16
51
  describe '#current_player' do
17
52
  it 'returns the first item of its players array' do
18
53
  history = double(record_board_size: true)
@@ -24,6 +59,17 @@ describe TicTacToes::Core::GameState do
24
59
  end
25
60
  end
26
61
 
62
+ describe '#next_player' do
63
+ it 'returns the second item of its players array' do
64
+ history = double(record_board_size: true)
65
+ players = ['first_player', 'second_player']
66
+ game_state = TicTacToes::Core::GameState.new('board', players, history)
67
+
68
+ next_player = game_state.next_player
69
+ expect(next_player).to eq('second_player')
70
+ end
71
+ end
72
+
27
73
  describe '#turn_over' do
28
74
  it 'records the last move' do
29
75
  move = double
@@ -47,21 +93,21 @@ describe TicTacToes::Core::GameState do
47
93
 
48
94
  describe '#game_over' do
49
95
  it 'records the winner' do
50
- winner = double
96
+ winning_player = double
51
97
  history = double(record_board_size: true, persist: true)
52
98
  game_state = TicTacToes::Core::GameState.new('board', 'players', history)
53
99
 
54
- expect(history).to receive(:record_winner).with(winner)
55
- game_state.game_over(winner)
100
+ expect(history).to receive(:record_winner).with(winning_player)
101
+ game_state.game_over(winning_player)
56
102
  end
57
103
 
58
104
  it 'persists its history' do
59
- winner = double
105
+ winning_player = double(token: 'x')
60
106
  history = double(record_board_size: true, record_winner: true)
61
107
  game_state = TicTacToes::Core::GameState.new('board', 'players', history)
62
108
 
63
109
  expect(history).to receive(:persist)
64
- game_state.game_over(winner)
110
+ game_state.game_over(winning_player)
65
111
  end
66
112
  end
67
113
 
@@ -76,6 +122,7 @@ describe TicTacToes::Core::GameState do
76
122
  board = TicTacToes::TestBoardGenerator.generate([x, x, o,
77
123
  o, o, nil,
78
124
  x, o, x])
125
+
79
126
  game_state = TicTacToes::Core::GameState.new(board, players, history)
80
127
  expect(game_state.game_over?).to be false
81
128
  end
@@ -113,7 +160,8 @@ describe TicTacToes::Core::GameState do
113
160
  winning_token = "o"
114
161
  game_state = TicTacToes::Core::GameState.new(board, players, history)
115
162
 
116
- expect(game_state.determine_winner).to eql(winning_token)
163
+ winning_player = game_state.determine_winner
164
+ expect(winning_player.token).to eq(winning_token)
117
165
  end
118
166
 
119
167
  it "returns nil if there is not a winner" do
@@ -125,5 +173,4 @@ describe TicTacToes::Core::GameState do
125
173
  expect(game_state.determine_winner).to be_nil
126
174
  end
127
175
  end
128
-
129
176
  end
@@ -23,11 +23,11 @@ describe TicTacToes::Core::History do
23
23
  end
24
24
 
25
25
  describe '#record_winner' do
26
- it "records the passed winner" do
27
- token = "O"
26
+ it "records the token of the passed winner" do
27
+ winner = double(token: 'O')
28
28
 
29
- history.record_winner(token)
30
- expect(history.winner).to eq(token)
29
+ history.record_winner(winner)
30
+ expect(history.winner).to eq(winner.token)
31
31
  end
32
32
  end
33
33
 
@@ -1,18 +1,25 @@
1
1
  require 'tic_tac_toes/test_board_generator'
2
+ require 'tic_tac_toes/core/game_state'
2
3
  require 'tic_tac_toes/core/move_strategies/easy_ai'
4
+ require 'tic_tac_toes/core/player_factory'
3
5
 
4
6
  describe TicTacToes::Core::MoveStrategies::EasyAI do
5
7
  describe '#move' do
6
- let(:players) { double("players") }
7
- let(:easy_ai) { TicTacToes::Core::MoveStrategies::EasyAI }
8
-
9
8
  it "returns a randomly-selected valid move" do
10
- board = TicTacToes::TestBoardGenerator.generate([ :O, nil, nil,
11
- nil, :X, nil,
12
- nil, :X, nil])
9
+ player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
10
+ x = player_factory.generate_human_player('x')
11
+ o = player_factory.generate_computer_player('o', :hard)
12
+ players = [x, o]
13
+
14
+ board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
15
+ nil, x, nil,
16
+ nil, x, nil])
17
+
18
+ history = double(record_board_size: true)
19
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
13
20
  valid_moves = [1, 2, 3, 5, 6, 8]
14
21
 
15
- move = easy_ai.move(board, players)
22
+ move = TicTacToes::Core::MoveStrategies::EasyAI.move(game_state)
16
23
  expect(valid_moves).to include(move)
17
24
  end
18
25
  end
@@ -1,129 +1,144 @@
1
1
  require 'tic_tac_toes/test_board_generator'
2
2
  require 'tic_tac_toes/core/move_strategies/hard_ai'
3
+ require 'tic_tac_toes/core/game_state'
3
4
  require 'tic_tac_toes/core/player'
4
5
 
5
6
  describe TicTacToes::Core::MoveStrategies::HardAI do
7
+ let(:alpha) { TicTacToes::Core::MoveStrategies::HardAI::ALPHA }
8
+ let(:beta) { TicTacToes::Core::MoveStrategies::HardAI::BETA }
6
9
  let(:hard_ai) { TicTacToes::Core::MoveStrategies::HardAI }
7
10
  let(:x) { TicTacToes::Core::Player.new("human", "x", false, "io") }
8
11
  let(:o) { TicTacToes::Core::Player.new(hard_ai, "o", true, "io") }
9
12
  let(:players) { [o, x] }
10
-
13
+ let(:history) { TicTacToes::UI::NullHistory.new }
11
14
 
12
15
  describe '#move' do
13
- it "returns the best move" do
16
+ it 'returns the best move' do
14
17
  board = TicTacToes::TestBoardGenerator.generate([x, nil, nil,
15
18
  o, o, nil,
16
19
  x, nil, x])
20
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
17
21
  best_move = 5
18
22
 
19
- expect(hard_ai.move(board, players)).to eql(best_move)
23
+ expect(hard_ai.move(game_state)).to eql(best_move)
20
24
  end
21
25
 
22
- context "when playing on a 3x3 board" do
26
+ context 'when playing on a 3x3 board' do
23
27
  it 'returns 0 when making the first move' do
24
28
  board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
25
29
  nil, nil, nil,
26
30
  nil, nil, nil])
31
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
27
32
 
28
- expect(hard_ai.move(board, players)).to eq(0)
33
+ expect(hard_ai.move(game_state)).to eq(0)
29
34
  end
30
35
 
31
- it "returns 4 when the opponent’s first move was a corner" do
36
+ it 'returns 4 when the opponent’s first move was a corner' do
32
37
  board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
33
38
  nil, nil, nil,
34
39
  nil, nil, x])
40
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
35
41
 
36
- expect(hard_ai.move(board, players)).to eq(4)
42
+ expect(hard_ai.move(game_state)).to eq(4)
37
43
  end
38
44
 
39
- it "returns 4 when the opponent’s first move was an edge" do
45
+ it 'returns 4 when the opponent’s first move was an edge' do
40
46
  board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
41
47
  nil, nil, x,
42
48
  nil, nil, nil])
49
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
43
50
 
44
- expect(hard_ai.move(board, players)).to eq(4)
51
+ expect(hard_ai.move(game_state)).to eq(4)
45
52
  end
46
53
 
47
- it "returns 0 when the opponent’s first move was the center" do
54
+ it 'returns 0 when the opponent’s first move was the center' do
48
55
  board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
49
56
  nil, x, nil,
50
57
  nil, nil, nil])
58
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
51
59
 
52
- expect(hard_ai.move(board, players)).to eq(0)
60
+ expect(hard_ai.move(game_state)).to eq(0)
53
61
  end
54
62
  end
55
63
  end
56
64
 
57
65
 
58
66
  describe '#minimax' do
59
- it "returns the correct score for a pre-win board" do
67
+ it 'returns the correct score for a pre-win board' do
60
68
  board = TicTacToes::TestBoardGenerator.generate([x, nil, nil,
61
69
  o, o, nil,
62
70
  x, nil, x])
71
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
63
72
  win_score = 1
64
73
 
65
- expect(hard_ai.minimax(board, :max, players)).to eql(win_score)
74
+ expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(win_score)
66
75
  end
67
76
 
68
- it "returns the correct score for a pre-loss board" do
77
+ it 'returns the correct score for a pre-loss board' do
69
78
  board = TicTacToes::TestBoardGenerator.generate([ o, o, x,
70
79
  nil, nil, nil,
71
80
  x, nil, x])
81
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
72
82
  loss_score = -1
73
83
 
74
- expect(hard_ai.minimax(board, :max, players)).to eql(loss_score)
84
+ expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(loss_score)
75
85
  end
76
86
 
77
- it "returns the correct score for a pre-draw board" do
87
+ it 'returns the correct score for a pre-draw board' do
78
88
  board = TicTacToes::TestBoardGenerator.generate([x, x, o,
79
89
  o, nil, x,
80
90
  x, o, x])
91
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
81
92
  draw_score = 0
82
93
 
83
- expect(hard_ai.minimax(board, :max, players)).to eql(draw_score)
94
+ expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(draw_score)
84
95
  end
85
96
  end
86
97
 
87
98
 
88
- describe '#generate_board' do
89
- it "returns a board based on a token, a space, and an existing board" do
90
- token, space = o, 3
99
+ describe '#generate_game_state' do
100
+ it 'returns a game state based on a space and an existing game state' do
101
+ space, token = 3, 'o'
91
102
  board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
92
103
  nil, o, nil,
93
104
  x, nil, nil])
105
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
94
106
 
95
- new_board = hard_ai.generate_board(token, space, board)
96
- expect(new_board.space(space)).to eql(token)
107
+ new_game_state = hard_ai.generate_game_state(space, game_state)
108
+ expect(new_game_state.board.space(space).token).to eq(token)
97
109
  end
98
110
  end
99
111
 
100
112
 
101
113
  describe '#score' do
102
- it "returns the correct score when HardAI has won" do
114
+ it 'returns the correct score when HardAI has won' do
103
115
  board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
104
116
  nil, o, nil,
105
117
  nil, nil, o])
118
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
106
119
  win_score = 1
107
120
 
108
- expect(hard_ai.score(board, players)).to eql(win_score)
121
+ expect(hard_ai.score(game_state)).to eq(win_score)
109
122
  end
110
123
 
111
- it "returns the correct score when no one has won" do
124
+ it 'returns the correct score when no one has won' do
112
125
  board = TicTacToes::TestBoardGenerator.generate([o, o, x,
113
126
  x, x, o,
114
127
  o, x, o])
128
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
115
129
  draw_score = 0
116
130
 
117
- expect(hard_ai.score(board, players)).to eql(draw_score)
131
+ expect(hard_ai.score(game_state)).to eql(draw_score)
118
132
  end
119
133
 
120
- it "returns the correct score when the opponent has won" do
134
+ it 'returns the correct score when the opponent has won' do
121
135
  board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
122
136
  nil, x, nil,
123
137
  nil, nil, x])
138
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
124
139
  loss_score = -1
125
140
 
126
- expect(hard_ai.score(board, players)).to eql(loss_score)
141
+ expect(hard_ai.score(game_state)).to eql(loss_score)
127
142
  end
128
143
  end
129
144
  end
@@ -2,29 +2,29 @@ require 'tic_tac_toes/core/move_strategies/human'
2
2
 
3
3
  describe TicTacToes::Core::MoveStrategies::Human do
4
4
  describe '#move' do
5
- let(:io) { double("io", solicit_input: 0, move_solicitation: true, not_an_integer_error: true) }
5
+ let(:io) { double('io', solicit_input: 0, move_solicitation: true, not_an_integer_error: true) }
6
6
  let(:human) { TicTacToes::Core::MoveStrategies::Human.new(io) }
7
7
 
8
- it "displays a move solicitation" do
8
+ it 'displays a move solicitation' do
9
9
  expect(io).to receive(:move_solicitation)
10
- human.move("board", "players")
10
+ human.move('game_state')
11
11
  end
12
12
 
13
13
  context 'when given not-integer-like input' do
14
- let(:not_integer_like) { "string" }
15
- let(:integer_like) { "100" }
14
+ let(:not_integer_like) { 'string' }
15
+ let(:integer_like) { '100' }
16
16
 
17
- it "displays a not an integer error" do
17
+ it 'displays a not an integer error' do
18
18
  allow(io).to receive(:solicit_input).and_return(not_integer_like, integer_like)
19
19
 
20
20
  expect(io).to receive(:not_an_integer_error).once
21
- human.move("board", "players")
21
+ human.move('game_state')
22
22
  end
23
23
 
24
- it "only returns a move (converted to integer) once it gets integer-like input" do
24
+ it 'only returns a move (converted to integer) once it gets integer-like input' do
25
25
  allow(io).to receive(:solicit_input).and_return(not_integer_like, integer_like)
26
26
 
27
- expect(human.move("board", "players")).to eq(100)
27
+ expect(human.move('game_state')).to eq(100)
28
28
  end
29
29
  end
30
30
  end
@@ -1,20 +1,25 @@
1
1
  require 'tic_tac_toes/test_board_generator'
2
+ require 'tic_tac_toes/core/game_state'
2
3
  require 'tic_tac_toes/core/move_strategies/medium_ai'
4
+ require 'tic_tac_toes/core/player_factory'
3
5
 
4
6
  describe TicTacToes::Core::MoveStrategies::MediumAI do
5
- let(:medium_ai) { TicTacToes::Core::MoveStrategies::MediumAI }
6
- let(:x) { TicTacToes::Core::Player.new("human", "x", false, "io") }
7
- let(:o) { TicTacToes::Core::Player.new(medium_ai, "o", true, "io") }
8
- let(:players) { [x, o] }
9
-
10
7
  describe '#move' do
11
8
  it "returns a valid move (based on either EasyAI or HardAI)" do
12
- board = TicTacToes::TestBoardGenerator.generate([ o, o, x,
13
- nil, x, nil,
14
- nil, x, nil])
9
+ player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
10
+ x = player_factory.generate_human_player('x')
11
+ o = player_factory.generate_computer_player('o', :hard)
12
+ players = [x, o]
13
+
14
+ board = TicTacToes::TestBoardGenerator.generate([ o, o, x,
15
+ nil, x, nil,
16
+ nil, x, nil])
17
+
18
+ history = TicTacToes::UI::NullHistory.new
19
+ game_state = TicTacToes::Core::GameState.new(board, players, history)
15
20
  valid_moves = [3, 5, 6, 8]
16
21
 
17
- move = medium_ai.move(board, players)
22
+ move = TicTacToes::Core::MoveStrategies::MediumAI.move(game_state)
18
23
  expect(valid_moves).to include(move)
19
24
  end
20
25
  end
@@ -2,31 +2,34 @@ require 'tic_tac_toes/core/board'
2
2
  require 'tic_tac_toes/core/player'
3
3
 
4
4
  describe TicTacToes::Core::Player do
5
- let(:board) { TicTacToes::Core::Board.new(row_size: 3) }
6
- let(:io) { double("io", invalid_move_error: true) }
7
- let(:players) { double("players") }
5
+ let(:board) { TicTacToes::Core::Board.new(row_size: 3) }
8
6
 
9
- describe '#place_and_return_move' do
10
- let(:move_strategy) { double("move_strategy") }
11
- let(:token) { "X" }
12
- let(:needs_to_think) { false }
7
+ let(:io) { double(invalid_move_error: true) }
8
+
9
+ let(:move_strategy) { double }
10
+ let(:x) { TicTacToes::Core::Player.new(move_strategy, 'x', false, io) }
11
+ let(:o) { double }
12
+ let(:players) { [x, o] }
13
+
14
+ let(:history) { double(record_board_size: true) }
13
15
 
14
- let(:player) { TicTacToes::Core::Player.new(move_strategy, token, needs_to_think, io) }
15
-
16
- it "only places/returns a move once it receives a valid move" do
16
+ let(:game_state) { TicTacToes::Core::GameState.new(board, players, history) }
17
+
18
+ describe '#place_and_return_move' do
19
+ it 'only places/returns a move once it receives a valid move' do
17
20
  invalid_space, valid_space = 9, 0
18
21
  allow(move_strategy).to receive(:move).and_return(invalid_space, valid_space)
19
22
 
20
- player.place_and_return_move(board, players)
21
- expect(board.space(valid_space)).to eq(player)
23
+ x.place_and_return_move(game_state)
24
+ expect(board.space(valid_space)).to eq(x)
22
25
  end
23
26
 
24
- it "displays an invalid move error when given an invalid move" do
27
+ it 'displays an invalid move error when given an invalid move' do
25
28
  invalid_space, valid_space = 9, 0
26
29
  allow(move_strategy).to receive(:move).and_return(invalid_space, valid_space)
27
30
 
28
31
  expect(io).to receive(:invalid_move_error)
29
- player.place_and_return_move(board, players)
32
+ x.place_and_return_move(game_state)
30
33
  end
31
34
  end
32
35
  end
@@ -86,13 +86,14 @@ describe TicTacToes::Core::Rules do
86
86
 
87
87
 
88
88
  describe '#determine_winner' do
89
- it "returns the winning token when there is a winner" do
89
+ it "returns the winning player when there is a winner" do
90
90
  board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
91
91
  nil, o, nil,
92
92
  nil, nil, o])
93
93
  winning_token = "o"
94
94
 
95
- expect(rules.determine_winner(board, players)).to eql(winning_token)
95
+ winning_player = rules.determine_winner(board, players)
96
+ expect(winning_player.token).to eql(winning_token)
96
97
  end
97
98
 
98
99
  it "returns nil if there is not a winner" do
@@ -5,12 +5,12 @@ require 'tic_tac_toes/ui/adapter'
5
5
 
6
6
  describe TicTacToes::UI::Adapter do
7
7
  describe '#make_move' do
8
- let(:history) { double(record_board_size: true,
9
- record_move: true) }
8
+ let(:history) { TicTacToes::UI::NullHistory.new }
9
+
10
10
  player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
11
- let(:x) { player_factory.generate_human_player('x') }
12
- let(:o) { player_factory.generate_computer_player('o', :hard) }
13
- let(:players) { [x, o] }
11
+ let(:x) { player_factory.generate_human_player('x') }
12
+ let(:o) { player_factory.generate_computer_player('o', :hard) }
13
+ let(:players) { [x, o] }
14
14
 
15
15
  context 'when the game is still in progress' do
16
16
  it 'sends its listener #moves_were_made with the updated game state' do
@@ -25,8 +25,8 @@ describe TicTacToes::UI::Adapter do
25
25
  TicTacToes::UI::Adapter.make_move(game_state, move, listener)
26
26
 
27
27
  first_move, second_move = 2, 1
28
- game_state.board.place(x, first_move)
29
- game_state.board.place(o, second_move)
28
+ board.place(x, first_move)
29
+ board.place(o, second_move)
30
30
  expect(listener).to have_received(:moves_were_made).with(game_state)
31
31
  end
32
32
  end
@@ -44,7 +44,7 @@ describe TicTacToes::UI::Adapter do
44
44
  allow(listener).to receive(:game_ended_in_winner)
45
45
  TicTacToes::UI::Adapter.make_move(game_state, move, listener)
46
46
 
47
- game_state.board.place(x, 2)
47
+ board.place(x, 2)
48
48
  expect(listener).to have_received(:game_ended_in_winner).with(game_state, winning_token)
49
49
  end
50
50
  end
@@ -61,7 +61,7 @@ describe TicTacToes::UI::Adapter do
61
61
  allow(listener).to receive(:game_ended_in_draw)
62
62
  TicTacToes::UI::Adapter.make_move(game_state, move, listener)
63
63
 
64
- game_state.board.place(x, 8)
64
+ board.place(x, 8)
65
65
  expect(listener).to have_received(:game_ended_in_draw).with(game_state)
66
66
  end
67
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tic_tac_toes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Spatafora
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-29 00:00:00.000000000 Z
11
+ date: 2014-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -90,9 +90,7 @@ files:
90
90
  - lib/tic_tac_toes/command_line/prompt.rb
91
91
  - lib/tic_tac_toes/command_line/runner.rb
92
92
  - lib/tic_tac_toes/core/board.rb
93
- - lib/tic_tac_toes/core/board_factory.rb
94
93
  - lib/tic_tac_toes/core/game_state.rb
95
- - lib/tic_tac_toes/core/game_state_factory.rb
96
94
  - lib/tic_tac_toes/core/history.rb
97
95
  - lib/tic_tac_toes/core/io.rb
98
96
  - lib/tic_tac_toes/core/move_strategies/easy_ai.rb
@@ -111,9 +109,7 @@ files:
111
109
  - lib/tic_tac_toes/version.rb
112
110
  - spec/tic_tac_toes/command_line/menu_spec.rb
113
111
  - spec/tic_tac_toes/command_line/runner_spec.rb
114
- - spec/tic_tac_toes/core/board_factory_spec.rb
115
112
  - spec/tic_tac_toes/core/board_spec.rb
116
- - spec/tic_tac_toes/core/game_state_factory_spec.rb
117
113
  - spec/tic_tac_toes/core/game_state_spec.rb
118
114
  - spec/tic_tac_toes/core/history_spec.rb
119
115
  - spec/tic_tac_toes/core/io_spec.rb
@@ -157,9 +153,7 @@ summary: The game Tic-tac-toe, featuring an unbeatable AI
157
153
  test_files:
158
154
  - spec/tic_tac_toes/command_line/menu_spec.rb
159
155
  - spec/tic_tac_toes/command_line/runner_spec.rb
160
- - spec/tic_tac_toes/core/board_factory_spec.rb
161
156
  - spec/tic_tac_toes/core/board_spec.rb
162
- - spec/tic_tac_toes/core/game_state_factory_spec.rb
163
157
  - spec/tic_tac_toes/core/game_state_spec.rb
164
158
  - spec/tic_tac_toes/core/history_spec.rb
165
159
  - spec/tic_tac_toes/core/io_spec.rb
@@ -1,11 +0,0 @@
1
- require 'tic_tac_toes/core/board'
2
-
3
- module TicTacToes
4
- module Core
5
- class BoardFactory
6
- def generate_board(row_size)
7
- Board.new(row_size: row_size)
8
- end
9
- end
10
- end
11
- end
@@ -1,15 +0,0 @@
1
- require 'tic_tac_toes/core/game_state'
2
-
3
- module TicTacToes
4
- module Core
5
- class GameStateFactory
6
- def initialize(history)
7
- @history = history
8
- end
9
-
10
- def generate_game_state(board, players)
11
- GameState.new(board, players, @history)
12
- end
13
- end
14
- end
15
- end
@@ -1,13 +0,0 @@
1
- require 'tic_tac_toes/core/board_factory'
2
-
3
- describe TicTacToes::Core::BoardFactory do
4
- describe '#generate_board' do
5
- it 'returns a board object generated from a row size' do
6
- board_factory = TicTacToes::Core::BoardFactory.new
7
- row_size = 5
8
-
9
- board = board_factory.generate_board(row_size)
10
- expect(board.row_size).to eq(row_size)
11
- end
12
- end
13
- end
@@ -1,15 +0,0 @@
1
- require 'tic_tac_toes/core/game_state_factory'
2
-
3
- describe TicTacToes::Core::GameStateFactory do
4
- describe '#generate_game_state' do
5
- it 'returns a game state object generated from a board object and player array' do
6
- history = double(record_board_size: true)
7
- game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
8
- board, players = double(size: 3), double
9
-
10
- game_state = game_state_factory.generate_game_state(board, players)
11
- expect(game_state.board).to eq(board)
12
- expect(game_state.players).to eq(players)
13
- end
14
- end
15
- end