ttt-core 1.0.0 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 592a2b99d813d6eff7554da5722ef2ce5f960470
4
- data.tar.gz: 65262df5515d534718ed484cab124e32f847fb22
3
+ metadata.gz: 824e96f3c46aab6ab11e4d1fdca9771b30656d8e
4
+ data.tar.gz: 436d80f07cb56f402e20f3d258df8d4315270c75
5
5
  SHA512:
6
- metadata.gz: e1eb6b836b09ba27c3c944a4ae5064242f8595e20955dbb0516405ad22bccebec1247e3a1a32c2999b32a06050cb7bbf3dc1a88bdedb059da668643bf9596f99
7
- data.tar.gz: ce091661e1f82bd04dca7b1fa02a128a63361cba5fdb90bfd35b477aab073d85a475fddf55fb58ff0776de7001cf9ddb7f9771f3a872a455fe0ffa3c46184fca
6
+ metadata.gz: 066027d759aaee23a10b626a2e86c35884aebb5ad1d6f4191479f04726a2d71d562a2b6b0622f1eedb927b731836ff4fb47e3168c5a064ef279dc3b62ab5a6db
7
+ data.tar.gz: 91596679e33a8026f5d1c7e84bc2dfa75869cd7f3b4241c68f19d5908695ae6055091bb17416f523a6000a742df4c7aec3b0b83812619117b1037203ff070b62
data/lib/ai_player.rb CHANGED
@@ -1,117 +1,117 @@
1
- require 'player_symbols'
2
- require 'player'
3
-
4
- class AiPlayer
5
- include Player
6
-
7
- def initialize(symbol)
8
- super(symbol)
9
- end
10
-
11
- def choose_move(board)
12
- minimax(board, true, board.vacant_indices.size, ALPHA, BETA).first[1]
13
- end
14
-
15
- def ready?
16
- true
17
- end
18
-
19
- def minimax(board, is_max_player, depth, alpha, beta)
20
- best_score_so_far = initial_score(is_max_player)
21
-
22
- if round_is_over(board, depth)
23
- return score(board, depth)
24
- end
25
-
26
- board.vacant_indices.each do |i|
27
- new_board = board.make_move(i, current_players_symbol(is_max_player))
28
- result = minimax(new_board, !is_max_player, depth - 1, alpha, beta)
29
- best_score_so_far = update_score(is_max_player, i, best_score_so_far, score_from(result))
30
-
31
- alpha = update_alpha(is_max_player, best_score_so_far, alpha)
32
- beta = update_beta(is_max_player, best_score_so_far, beta)
33
-
34
- if alpha > beta
35
- break
36
- end
37
- end
38
-
39
- best_score_so_far
40
- end
41
-
42
-
43
- private
44
-
45
- ALPHA = -2
46
- BETA = 2
47
-
48
- def update_alpha(is_max_player, best_score_so_far, alpha)
49
- if is_max_player && score_from(best_score_so_far) > alpha
50
- alpha = score_from(best_score_so_far)
51
- end
52
- alpha
53
- end
54
-
55
- def score_from(score)
56
- score.first.first
57
- end
58
-
59
- def update_beta(is_max_player, best_score_so_far, beta)
60
- if !is_max_player && score_from(best_score_so_far) < beta
61
- beta = score_from(best_score_so_far)
62
- end
63
- beta
64
- end
65
-
66
- def round_is_over(board, depth)
67
- @winner = board.winning_symbol
68
- !@winner.nil? || depth == 0
69
- end
70
-
71
- def score(board, depth)
72
- if @winner.nil?
73
- return {0 => nil}
74
- end
75
-
76
- if maximizing_player_won?(@winner)
77
- return {(1 + depth) => nil}
78
- end
79
-
80
- if minimizing_player_won?(@winner)
81
- return {(-1 - depth) => nil}
82
- end
83
- end
84
-
85
- def maximizing_player_won?(winners_symbol)
86
- symbols_are_equal?(winners_symbol, game_symbol)
87
- end
88
-
89
- def minimizing_player_won?(winners_symbol)
90
- symbols_are_equal?(winners_symbol, PlayerSymbols::opponent(game_symbol))
91
- end
92
-
93
- def symbols_are_equal?(symbol1, symbol2)
94
- symbol1 == symbol2
95
- end
96
-
97
- def initial_score(is_max_player)
98
- if is_max_player
99
- best_score_so_far = {ALPHA => nil}
100
- else
101
- best_score_so_far = {BETA => nil}
102
- end
103
- end
104
-
105
- def current_players_symbol(is_max_player)
106
- is_max_player == true ? game_symbol : PlayerSymbols::opponent(game_symbol)
107
- end
108
-
109
- def update_score(is_max_player, move, score, result_score)
110
- if is_max_player && (result_score >= score_from(score))
111
- return {result_score => move}
112
- elsif !is_max_player && (result_score < score_from(score))
113
- return {result_score => move}
114
- end
115
- return score
116
- end
117
- end
1
+ require 'player_symbols'
2
+ require 'player'
3
+
4
+ class AiPlayer
5
+ include Player
6
+
7
+ def initialize(symbol)
8
+ super(symbol)
9
+ end
10
+
11
+ def choose_move(board)
12
+ minimax(board, true, board.vacant_indices.size, ALPHA, BETA).first[1]
13
+ end
14
+
15
+ def ready?
16
+ true
17
+ end
18
+
19
+ def minimax(board, is_max_player, depth, alpha, beta)
20
+ best_score_so_far = initial_score(is_max_player)
21
+
22
+ if round_is_over(board, depth)
23
+ return score(board, depth)
24
+ end
25
+
26
+ board.vacant_indices.each do |i|
27
+ new_board = board.make_move(i, current_players_symbol(is_max_player))
28
+ result = minimax(new_board, !is_max_player, depth - 1, alpha, beta)
29
+ best_score_so_far = update_score(is_max_player, i, best_score_so_far, score_from(result))
30
+
31
+ alpha = update_alpha(is_max_player, best_score_so_far, alpha)
32
+ beta = update_beta(is_max_player, best_score_so_far, beta)
33
+
34
+ if alpha > beta
35
+ break
36
+ end
37
+ end
38
+
39
+ best_score_so_far
40
+ end
41
+
42
+
43
+ private
44
+
45
+ ALPHA = -2
46
+ BETA = 2
47
+
48
+ def update_alpha(is_max_player, best_score_so_far, alpha)
49
+ if is_max_player && score_from(best_score_so_far) > alpha
50
+ alpha = score_from(best_score_so_far)
51
+ end
52
+ alpha
53
+ end
54
+
55
+ def score_from(score)
56
+ score.first.first
57
+ end
58
+
59
+ def update_beta(is_max_player, best_score_so_far, beta)
60
+ if !is_max_player && score_from(best_score_so_far) < beta
61
+ beta = score_from(best_score_so_far)
62
+ end
63
+ beta
64
+ end
65
+
66
+ def round_is_over(board, depth)
67
+ @winner = board.winning_symbol
68
+ !@winner.nil? || depth == 0
69
+ end
70
+
71
+ def score(board, depth)
72
+ if @winner.nil?
73
+ return {0 => nil}
74
+ end
75
+
76
+ if maximizing_player_won?(@winner)
77
+ return {(1 + depth) => nil}
78
+ end
79
+
80
+ if minimizing_player_won?(@winner)
81
+ return {(-1 - depth) => nil}
82
+ end
83
+ end
84
+
85
+ def maximizing_player_won?(winners_symbol)
86
+ symbols_are_equal?(winners_symbol, game_symbol)
87
+ end
88
+
89
+ def minimizing_player_won?(winners_symbol)
90
+ symbols_are_equal?(winners_symbol, PlayerSymbols::opponent(game_symbol))
91
+ end
92
+
93
+ def symbols_are_equal?(symbol1, symbol2)
94
+ symbol1 == symbol2
95
+ end
96
+
97
+ def initial_score(is_max_player)
98
+ if is_max_player
99
+ best_score_so_far = {ALPHA => nil}
100
+ else
101
+ best_score_so_far = {BETA => nil}
102
+ end
103
+ end
104
+
105
+ def current_players_symbol(is_max_player)
106
+ is_max_player == true ? game_symbol : PlayerSymbols::opponent(game_symbol)
107
+ end
108
+
109
+ def update_score(is_max_player, move, score, result_score)
110
+ if is_max_player && (result_score >= score_from(score))
111
+ return {result_score => move}
112
+ elsif !is_max_player && (result_score < score_from(score))
113
+ return {result_score => move}
114
+ end
115
+ return score
116
+ end
117
+ end
data/lib/board.rb CHANGED
@@ -18,7 +18,7 @@ class Board
18
18
  grid.include?(nil)
19
19
  end
20
20
 
21
- def get_symbol_at(position)
21
+ def symbol_at(position)
22
22
  grid.at(position)
23
23
  end
24
24
 
data/lib/game.rb CHANGED
@@ -7,21 +7,29 @@ class Game
7
7
 
8
8
  def play
9
9
  while game_in_progress?
10
- @board = board.make_move(current_player.choose_move(board), current_player.game_symbol)
11
- players.reverse!
10
+ current_player = players[player_symbol]
11
+ @board = board.make_move(current_player.choose_move(board), player_symbol)
12
12
  end
13
13
  board
14
14
  end
15
15
 
16
+ def play_specific(move)
17
+ @board = board.make_move(move, player_symbol)
18
+ play
19
+ end
20
+
16
21
  private
17
22
 
18
23
  attr_reader :board, :players
19
24
 
20
- def game_in_progress?
21
- current_player.ready? && board.free_spaces? && !board.winning_combination?
25
+ def player_symbol
26
+ number_of_x = board.grid_for_display.flatten.count(PlayerSymbols::X)
27
+ number_of_o = board.grid_for_display.flatten.count(PlayerSymbols::O)
28
+ next_players_symbol = number_of_x > number_of_o ? PlayerSymbols::O : PlayerSymbols::X
22
29
  end
23
30
 
24
- def current_player
25
- players.first
31
+ def game_in_progress?
32
+ players[player_symbol].ready? && board.free_spaces? && !board.winning_combination?
26
33
  end
34
+
27
35
  end
@@ -13,7 +13,7 @@ class PlayerOptions
13
13
  ID_TO_PLAYER_TYPE.keys
14
14
  end
15
15
 
16
- def self.get_player_type_for_id(id)
16
+ def self.player_type_for_id(id)
17
17
  game_value_of_player = ID_TO_PLAYER_TYPE.fetch(id)
18
18
  end
19
19
 
@@ -1,3 +1,3 @@
1
1
  module TTTCore
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/spec/board_spec.rb CHANGED
@@ -23,12 +23,12 @@ RSpec.describe Board do
23
23
 
24
24
  it "can get symbol at given position" do
25
25
  board = Board.new([nil, nil, nil, nil, X, O, nil, nil, nil])
26
- expect(board.get_symbol_at(4)).to be :X
26
+ expect(board.symbol_at(4)).to be :X
27
27
  end
28
28
 
29
29
  it "can be updated at a given position" do
30
30
  updated_board = board.make_move(1, X)
31
- expect(updated_board.get_symbol_at(1)).to eq(X)
31
+ expect(updated_board.symbol_at(1)).to eq(X)
32
32
  end
33
33
 
34
34
  it "has free spaces" do
data/spec/game_spec.rb CHANGED
@@ -2,11 +2,34 @@ require 'board'
2
2
  require 'game'
3
3
  require 'player'
4
4
  require 'player_symbols'
5
+ require 'player_options'
5
6
 
6
7
  RSpec.describe Game do
7
8
  let(:player_x_spy) { instance_double(FakePlayer).as_null_object }
8
9
  let(:player_o_spy) { instance_double(FakePlayer).as_null_object }
9
10
 
11
+ it "game is played with specific move" do
12
+ allow(player_o_spy).to receive(:ready?).and_return(false)
13
+
14
+ game = Game.new(Board.new, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy })
15
+ updated_board = game.play_specific(3)
16
+
17
+ expect(updated_board.symbol_at(3)).to be (PlayerSymbols::X)
18
+ end
19
+
20
+ it "game is started with a specific move" do
21
+ allow(player_x_spy).to receive(:ready?).and_return(false)
22
+ allow(player_o_spy).to receive(:choose_move).and_return(8)
23
+ allow(player_o_spy).to receive(:ready?).and_return(true, false)
24
+
25
+ game = Game.new(Board.new, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy })
26
+ updated_board = game.play_specific(3)
27
+
28
+ expect(updated_board.symbol_at(3)).to be (PlayerSymbols::X)
29
+ expect(updated_board.symbol_at(8)).to be (PlayerSymbols::O)
30
+ expect(updated_board.vacant_indices.size).to eq 7
31
+ end
32
+
10
33
  it "continues until player is not ready" do
11
34
  allow(player_x_spy).to receive(:ready?).and_return(true, false)
12
35
  allow(player_o_spy).to receive(:ready?).and_return(true)
@@ -14,31 +37,32 @@ RSpec.describe Game do
14
37
  expect(player_x_spy).to receive(:choose_move).exactly(1).times.and_return(2)
15
38
  expect(player_o_spy).to receive(:choose_move).exactly(1).times.and_return(8)
16
39
 
17
- Game.new(Board.new, [player_x_spy, player_o_spy]).play
40
+ Game.new(Board.new, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy }).play
18
41
  end
19
42
 
20
43
  it "players take turn until there is no space on board" do
21
44
  expect(player_x_spy).to receive(:choose_move).exactly(5).times.and_return(0, 1, 4, 5, 6)
22
45
  expect(player_o_spy).to receive(:choose_move).exactly(4).times.and_return(2, 3, 7, 8)
23
46
 
24
- Game.new(Board.new, [player_x_spy, player_o_spy]).play
47
+ Game.new(Board.new, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy }).play
25
48
 
26
49
  end
27
50
 
28
51
  it "game ends when a winning combination is formed" do
52
+ allow(player_x_spy).to receive(:ready?).and_return(true)
29
53
  allow(player_x_spy).to receive(:choose_move).once.and_return(2)
30
54
  allow(player_x_spy).to receive(:game_symbol).and_return(PlayerSymbols::X)
31
55
 
32
- board = Board.new([PlayerSymbols::X, PlayerSymbols::X, nil, PlayerSymbols::O, nil, nil, nil, nil, nil])
33
- updated_board = Game.new(board, [player_x_spy, player_o_spy]).play
56
+ board = Board.new([PlayerSymbols::X, PlayerSymbols::X, nil, PlayerSymbols::O, nil, PlayerSymbols::O, nil, nil, nil])
57
+ updated_board = Game.new(board, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy }).play
34
58
 
35
- expect(updated_board.get_symbol_at(2)).to be (PlayerSymbols::X)
59
+ expect(updated_board.symbol_at(2)).to be (PlayerSymbols::X)
36
60
  expect(player_o_spy).to_not have_received(:choose_move)
37
61
  end
38
62
 
39
63
  it "game ends when there are no free spaces on the board" do
40
64
  full_board = Board.new([PlayerSymbols::X, PlayerSymbols::X, PlayerSymbols::O, PlayerSymbols::O, PlayerSymbols::O, PlayerSymbols::X, PlayerSymbols::X, PlayerSymbols::O, PlayerSymbols::X])
41
- game = Game.new(full_board, [player_x_spy, player_o_spy]).play
65
+ game = Game.new(full_board, { PlayerSymbols::X => player_x_spy, PlayerSymbols::O => player_o_spy }).play
42
66
 
43
67
  expect(player_o_spy).to_not have_received(:choose_move)
44
68
  expect(player_x_spy).to_not have_received(:choose_move)
@@ -19,7 +19,7 @@ RSpec.describe PlayerOptions do
19
19
  end
20
20
 
21
21
  it "gets player type for id" do
22
- expect(PlayerOptions::get_player_type_for_id(1)).to eq (PlayerOptions::HUMAN_VS_HUMAN)
22
+ expect(PlayerOptions::player_type_for_id(1)).to eq (PlayerOptions::HUMAN_VS_HUMAN)
23
23
  end
24
24
 
25
25
  it "gets player options for display" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ttt-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georgina McFadyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-02 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler