erics_tic_tac_toe 0.5.0 → 0.5.1

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.
@@ -20,8 +20,8 @@ def setup
20
20
  terminal = TicTacToe::TerminalGame.new(game.board)
21
21
 
22
22
  if terminal.computer_goes_first?
23
- @player_1 = TicTacToe::ComputerPlayer.new(letter: 'o')
24
- @player_2 = TicTacToe::HumanPlayer.new(letter: 'x')
23
+ @player_1 = TicTacToe::HumanPlayer.new(letter: 'x')
24
+ @player_2 = TicTacToe::ComputerPlayer.new(letter: 'o')
25
25
  else
26
26
  @player_1 = TicTacToe::HumanPlayer.new(letter: 'o')
27
27
  @player_2 = TicTacToe::HumanPlayer.new(letter: 'x')
@@ -35,6 +35,7 @@ end
35
35
  game, terminal = setup
36
36
 
37
37
  loop do
38
+
38
39
  game.start
39
40
  terminal = TicTacToe::TerminalGame.new(game.board)
40
41
 
@@ -42,6 +43,7 @@ loop do
42
43
  terminal.update_board
43
44
  break unless terminal.play_again?
44
45
  game, terminal = setup
46
+ next
45
47
  end
46
48
 
47
49
  terminal.update_board
@@ -4,11 +4,18 @@
4
4
  module TicTacToe
5
5
  X = 'x'.freeze
6
6
  O = 'o'.freeze
7
+
8
+ def self.number_to_cords(num, size)
9
+ num = num.to_i
10
+ num -= 1
11
+ [num % size, num/size]
12
+ end
13
+
7
14
  end
8
15
 
9
16
  # Internal Project Requires
10
17
  require 'tic_tac_toe/board'
11
18
  require 'tic_tac_toe/game'
12
19
  require 'tic_tac_toe/player'
13
- require 'tic_tac_toe/presentors/game_presenter'
14
- require 'tic_tac_toe/presentors/player_presenter'
20
+ require 'tic_tac_toe/presenters/game_presenter'
21
+ require 'tic_tac_toe/presenters/player_presenter'
@@ -25,7 +25,7 @@ module TicTacToe
25
25
 
26
26
  def start
27
27
  while @current_player && (move = @current_player.get_move(@board))
28
- move = number_to_cords(move) unless move.is_a?(Array)
28
+ move = TicTacToe::number_to_cords(move, @board.size) unless move.is_a?(Array)
29
29
 
30
30
  @board.play_at(*move, @current_player.letter)
31
31
  break if over?
@@ -56,12 +56,6 @@ module TicTacToe
56
56
  solved? || cats?
57
57
  end
58
58
 
59
- def number_to_cords(num)
60
- num = num.to_i
61
- num -= 1
62
- [num % @board.size, num/@board.size]
63
- end
64
-
65
59
  def switch_player
66
60
  @current_player = @current_player == @player_1 ? @player_2 : @player_1
67
61
  end
@@ -1,86 +1,68 @@
1
1
  module TicTacToe
2
- # Terminal GameType
3
- # Interacts with the user through the terminal
4
- # Uses puts for output, and gets for input
5
- class TerminalGame
6
- # Internal Error used when a user tries pulling an illegal move
7
- class IllegalMove < RuntimeError
8
- end
9
-
10
- def initialize(board, io=Kernel)
11
- @board = board
12
- @io = io
13
- end
14
-
15
- def computer_goes_first?
16
- input = get_input("Would you like to play first or second? (f/s)")
17
-
18
- return true unless input =~ /^(f|first)$/i
19
- false
20
- end
21
-
22
- def get_move_from_user
23
- cords = get_cords_from_user
2
+ module GameType
3
+ # Terminal GameType
4
+ # Interacts with the user through the terminal
5
+ # Uses puts for output, and gets for input
6
+ class Terminal
7
+ # Internal Error used when a user tries pulling an illegal move
8
+ class IllegalMove < RuntimeError
9
+ end
24
10
 
25
- raise IllegalMove.new("That cell is already taken") unless empty_cell?(cords)
11
+ def initialize(board, io=Kernel)
12
+ @board = board
13
+ @io = io
14
+ end
26
15
 
27
- cords
28
- rescue IllegalMove => error
29
- display_text "Illegal Move: #{error.message}. Please try again"
30
- retry
31
- end
16
+ def computer_goes_first?
17
+ input = get_input("Would you like to play first or second? (f/s)")
32
18
 
33
- def play_again?
34
- input = get_input "Play again? (y/n)"
35
- input =~ /^(y|yes)$/i
36
- end
19
+ return true unless input =~ /^(f|first)$/i
20
+ false
21
+ end
37
22
 
38
- def select(choices)
39
- loop do
40
- output = "Select the solver:\n"
41
- choices.each_with_index { |choice, i| output += "#{i+1}: #{choice}\n" }
23
+ def get_move_from_user
24
+ cords = get_cords_from_user
42
25
 
43
- input = get_input(output)
26
+ raise IllegalMove.new("That cell is already taken") unless empty_cell?(cords)
44
27
 
45
- if input =~ /^\d+$/ and input.to_i <= choices.size
46
- return choices[input.to_i-1]
47
- end
28
+ cords
29
+ rescue IllegalMove => error
30
+ display_text "Illegal Move: #{error.message}. Please try again"
31
+ retry
32
+ end
48
33
 
49
- display_text("Not a valid choice")
34
+ def play_again?
35
+ input = get_input "Play again? (y/n)"
36
+ input =~ /^(y|yes)$/i
50
37
  end
51
- end
52
38
 
53
- def update_board
54
- @io.puts @board
55
- end
39
+ def update_board
40
+ @io.puts @board
41
+ end
56
42
 
57
- def display_text(text)
58
- @io.puts text
59
- end
43
+ def display_text(text)
44
+ @io.puts text
45
+ end
60
46
 
61
- private
47
+ private
62
48
 
63
- def get_input(text)
64
- @io.print text + ' '
65
- @io.gets.chomp
66
- end
49
+ def get_input(text)
50
+ @io.print text + ' '
51
+ @io.gets.chomp
52
+ end
67
53
 
68
- def empty_cell?(cords)
69
- !@board.get_cell(*cords)
70
- end
54
+ def empty_cell?(cords)
55
+ !@board.get_cell(*cords)
56
+ end
71
57
 
72
- def get_cords_from_user
73
- input = get_input("Your move (1-#{@board.size**2}):")
58
+ def get_cords_from_user
59
+ input = get_input("Your move (1-#{@board.size**2}):")
74
60
 
75
- raise IllegalMove.new("That is not a real location") unless input =~ /^\d+$/
61
+ raise IllegalMove.new("That is not a real location") unless input =~ /^\d+$/
76
62
 
77
- cord_from_num(input.to_i)
78
- end
63
+ TicTacToe::number_to_cords(input, @board.size)
64
+ end
79
65
 
80
- def cord_from_num(num)
81
- num -= 1
82
- [num % @board.size, num/@board.size]
83
66
  end
84
67
  end
85
-
86
68
  end
@@ -1,12 +1,12 @@
1
1
  module TicTacToe
2
- class Player
2
+ module Player
3
3
  HUMAN = 'human'
4
4
  COMPUTER = 'computer'
5
5
 
6
6
  def self.build(params)
7
- return HumanPlayer.new(params) if params['type'] == HUMAN
7
+ return Player::Human.new(params) if params['type'] == HUMAN
8
8
 
9
- ComputerPlayer.new(params)
9
+ Player::Computer.new(params)
10
10
  end
11
11
 
12
12
  end
@@ -1,23 +1,27 @@
1
1
  module TicTacToe
2
2
 
3
- class ComputerPlayer
4
- attr_reader :letter
3
+ module Player
5
4
 
6
- def initialize(params, solver=MinimaxStrategy)
7
- @letter = params['letter'] || params[:letter]
8
- @solver = solver
9
- end
5
+ class Computer
6
+ attr_reader :letter
10
7
 
11
- def get_move(board)
12
- @solver.new(board, @letter).solve
13
- end
8
+ def initialize(params, solver=Strategy::MinimaxStrategy)
9
+ @letter = params['letter'] || params[:letter]
10
+ @solver = solver
11
+ end
12
+
13
+ def get_move(board)
14
+ @solver.new(board, @letter).solve
15
+ end
16
+
17
+ def has_next_move?
18
+ true
19
+ end
20
+
21
+ def type
22
+ Player::COMPUTER
23
+ end
14
24
 
15
- def has_next_move?
16
- true
17
- end
18
-
19
- def type
20
- Player::COMPUTER
21
25
  end
22
26
 
23
27
  end
@@ -1,26 +1,29 @@
1
1
  module TicTacToe
2
2
 
3
- class HumanPlayer
4
- attr_reader :letter
5
- attr_writer :move
3
+ module Player
4
+ class Human
5
+ attr_reader :letter
6
+ attr_writer :move
6
7
 
7
- def initialize(params)
8
- @letter, @move = (params['letter'] || params[:letter]),
9
- (params['move'] || params[:move])
10
- end
8
+ def initialize(params)
9
+ @letter, @move = (params['letter'] || params[:letter]),
10
+ (params['move'] || params[:move])
11
+ end
11
12
 
12
- def get_move(_board)
13
- move = @move
14
- @move = nil
15
- move
16
- end
13
+ def get_move(_board)
14
+ move = @move
15
+ @move = nil
16
+ move
17
+ end
17
18
 
18
- def has_next_move?
19
- !!@move
20
- end
19
+ def has_next_move?
20
+ !!@move
21
+ end
22
+
23
+ def type
24
+ Player::HUMAN
25
+ end
21
26
 
22
- def type
23
- Player::HUMAN
24
27
  end
25
28
 
26
29
  end
@@ -0,0 +1,21 @@
1
+ module TicTacToe
2
+
3
+ module Presenter
4
+
5
+ class Game
6
+ def initialize(game)
7
+ @game = game
8
+ end
9
+
10
+ def grid
11
+ @game.grid.each_with_index.map do |row, i|
12
+ row.each_with_index.map do |cell, j|
13
+ cell || ((i*3)+(j+1)).to_s
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module TicTacToe
4
+
5
+ module Presenter
6
+
7
+ class Player
8
+ def initialize(player)
9
+ @player = player
10
+ end
11
+
12
+ def move_json(move=nil)
13
+ {letter: @player.letter, type: @player.type, move: move}.to_json
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -1,89 +1,87 @@
1
1
  module TicTacToe
2
- # This implements the Minimax algorithum with AlphaBeta Prunning
3
- # http://en.wikipedia.org/wiki/Minimax
4
- # http://en.wikipedia.org/wiki/Alpha-beta_pruning
5
- class MinimaxStrategy
6
2
 
7
- attr_reader :board # Avoid getters, setters and properties
3
+ module Strategy
4
+ # This implements the Minimax algorithum with AlphaBeta Prunning
5
+ # http://en.wikipedia.org/wiki/Minimax
6
+ # http://en.wikipedia.org/wiki/Alpha-beta_pruning
7
+ class MinimaxStrategy
8
8
 
9
- def initialize(board, letter)
10
- @board, @letter = board, letter
11
- end
9
+ attr_reader :board
12
10
 
13
- def solve
14
- raise "Can not solve a full board" if @board.full?
15
- Minimax.new(@board, @letter).best_move
16
- end
17
- end
18
-
19
- # The game tree needs a evaluator for generating rankings,
20
- # an initial game state,
21
- # and a player
22
- #
23
- # This class uses 5 instance variables: @evaluator, @state,
24
- # @depth, @alpha, @beta. Should be < 3
25
- #
26
- # This class has 63 lines. Should be <= 50
27
- class Minimax
28
-
29
- MAXDEPTH = 6
30
- PositiveInfinity = +1.0/0.0
31
- NegativeInfinity = -1.0/0.0
32
-
33
- def initialize(board, player)
34
- @start_board = board
35
- @player = player
36
- end
11
+ def initialize(board, letter)
12
+ @board, @letter = board, letter
13
+ end
37
14
 
38
- def best_move
39
- @start_board.empty_positions.max_by do |column, row|
40
- score(@start_board.clone.play_at(column, row, @player), next_turn(@player))
15
+ def solve
16
+ raise "Can not solve a full board" if @board.full?
17
+ Minimax.new(@board, @letter).best_move
41
18
  end
42
19
  end
43
20
 
44
- private
21
+ # The game tree needs a evaluator for generating rankings,
22
+ # an initial game state,
23
+ # and a player
24
+ class Minimax
45
25
 
46
- def score(board, whos_turn, depth=1,
47
- alpha=NegativeInfinity, beta=PositiveInfinity)
26
+ MAXDEPTH = 6
27
+ PositiveInfinity = +1.0/0.0
28
+ NegativeInfinity = -1.0/0.0
48
29
 
49
- if board.full? || board.solved?
50
- return 1.0 / depth if board.winner == @player
51
- return -1.0 if board.solved?
52
- return 0
30
+ def initialize(board, player)
31
+ @start_board = board
32
+ @player = player
53
33
  end
54
34
 
55
- if whos_turn == @player
35
+ def best_move
36
+ @start_board.empty_positions.max_by do |column, row|
37
+ score(@start_board.clone.play_at(column, row, @player), next_turn(@player))
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def score(board, whos_turn, depth=1,
44
+ alpha=NegativeInfinity, beta=PositiveInfinity)
45
+
46
+ if board.full? || board.solved?
47
+ return 1.0 / depth if board.winner == @player
48
+ return -1.0 if board.solved?
49
+ return 0
50
+ end
51
+
52
+ if whos_turn == @player
53
+ board.empty_positions.each do |column, row|
54
+ alpha = [
55
+ alpha,
56
+ next_score(board, column, row, whos_turn, depth, alpha, beta)
57
+ ].max
58
+ break if beta <= alpha || depth >= MAXDEPTH
59
+ end
60
+ return alpha
61
+ end
62
+
56
63
  board.empty_positions.each do |column, row|
57
- alpha = [
58
- alpha,
64
+ beta = [
65
+ beta,
59
66
  next_score(board, column, row, whos_turn, depth, alpha, beta)
60
- ].max
61
- break if beta <= alpha || depth >= MAXDEPTH
67
+ ].min
68
+ break if alpha >= beta || depth >= MAXDEPTH
62
69
  end
63
- return alpha
70
+ beta
64
71
  end
65
72
 
66
- board.empty_positions.each do |column, row|
67
- beta = [
68
- beta,
69
- next_score(board, column, row, whos_turn, depth, alpha, beta)
70
- ].min
71
- break if alpha >= beta || depth >= MAXDEPTH
73
+ def next_score(board, column, row, whos_turn, depth, alpha, beta)
74
+ score( board.clone.play_at(column, row, whos_turn),
75
+ next_turn(whos_turn),
76
+ depth+1, alpha, beta
77
+ )
72
78
  end
73
- beta
74
- end
75
-
76
- def next_score(board, column, row, whos_turn, depth, alpha, beta)
77
- score( board.clone.play_at(column, row, whos_turn),
78
- next_turn(whos_turn),
79
- depth+1, alpha, beta
80
- )
81
- end
82
79
 
83
- def next_turn(player)
84
- case player
85
- when X then O
86
- when O then X
80
+ def next_turn(player)
81
+ case player
82
+ when X then O
83
+ when O then X
84
+ end
87
85
  end
88
86
  end
89
87
  end
@@ -1,257 +1,259 @@
1
1
  module TicTacToe
2
- # Strategy used when playing on a 3x3 board
3
- class ThreeByThreeStrategy
4
- def initialize(board, letter)
5
- @heuristic = ThreeByThree::Heuristic.new(board, letter)
6
- end
2
+ module Strategy
3
+ # Strategy used when playing on a 3x3 board
4
+ class ThreeByThreeStrategy
5
+ def initialize(board, letter)
6
+ @heuristic = ThreeByThree::Heuristic.new(board, letter)
7
+ end
7
8
 
8
- # The strategy is from the Wikipedia article on Tic-Tac-Toe
9
- # 1) Try to win
10
- # 2) Try to block if they're about to win
11
- # 3) Try to fork so you'll win next turn
12
- # 4) Try to block their fork so they will not win next turn
13
- # 5) Take the center if its not already taken
14
- # 6) Play the opposite corner of your opponent
15
- # 7) Play in an empty corner
16
- # 8) Play in an empty side
17
- def solve
18
- [:win, :block, :fork, :block_fork,
19
- :center, :opposite_corner, :empty_corner, :empty_side].each do |step|
20
- move = @heuristic.send(step)
21
- return move if move
22
- end
9
+ # The strategy is from the Wikipedia article on Tic-Tac-Toe
10
+ # 1) Try to win
11
+ # 2) Try to block if they're about to win
12
+ # 3) Try to fork so you'll win next turn
13
+ # 4) Try to block their fork so they will not win next turn
14
+ # 5) Take the center if its not already taken
15
+ # 6) Play the opposite corner of your opponent
16
+ # 7) Play in an empty corner
17
+ # 8) Play in an empty side
18
+ def solve
19
+ [:win, :block, :fork, :block_fork,
20
+ :center, :opposite_corner, :empty_corner, :empty_side].each do |step|
21
+ move = @heuristic.send(step)
22
+ return move if move
23
+ end
23
24
 
24
- raise "No possible moves to play!"
25
+ raise "No possible moves to play!"
26
+ end
25
27
  end
26
- end
27
28
 
28
- module ThreeByThree
29
- # Brute Force Implementation for the Three by Three Strategy
30
- # This implementation uses loops to try a change all of the board values and
31
- # checking the result
32
- #
33
- # For example, win! is implemented by trying every cell, and checking if
34
- # it was a winning solution
35
- class Heuristic
29
+ module ThreeByThree
30
+ # Brute Force Implementation for the Three by Three Strategy
31
+ # This implementation uses loops to try a change all of the board values and
32
+ # checking the result
33
+ #
34
+ # For example, win! is implemented by trying every cell, and checking if
35
+ # it was a winning solution
36
+ class Heuristic
36
37
 
37
- attr_reader :board
38
+ attr_reader :board
38
39
 
39
- def initialize(board, letter)
40
- @board, @letter, @state = board, letter, PotentialState.new(board, letter)
41
- @other_player = other_player
42
- end
40
+ def initialize(board, letter)
41
+ @board, @letter, @state = board, letter, PotentialState.new(board, letter)
42
+ @other_player = other_player
43
+ end
43
44
 
44
- # Try placing letter at every available position
45
- # If the board is solved, do that
46
- def win
47
- each_position do |row, column|
48
- if @state.at(row, column).solved?
49
- return [row, column]
45
+ # Try placing letter at every available position
46
+ # If the board is solved, do that
47
+ def win
48
+ each_position do |row, column|
49
+ if @state.at(row, column).solved?
50
+ return [row, column]
51
+ end
50
52
  end
53
+ false
51
54
  end
52
- false
53
- end
54
55
 
55
- # Try placing the opponent's letter at every available position
56
- # If the board is solved, block them at that position
57
- def block(board = @board, letter = @letter)
58
- state = PotentialState.new(board, other_player(letter))
59
- each_position do |row, column|
60
- if state.at(row, column).solved?
61
- return [row, column]
56
+ # Try placing the opponent's letter at every available position
57
+ # If the board is solved, block them at that position
58
+ def block(board = @board, letter = @letter)
59
+ state = PotentialState.new(board, other_player(letter))
60
+ each_position do |row, column|
61
+ if state.at(row, column).solved?
62
+ return [row, column]
63
+ end
62
64
  end
65
+ false
63
66
  end
64
- false
65
- end
66
67
 
67
- # Try placing the letter at every position.
68
- # If there are now two winning solutions for next turn, go there
69
- def fork
70
- @state.each_forking_position do |row, column|
71
- return [row, column]
68
+ # Try placing the letter at every position.
69
+ # If there are now two winning solutions for next turn, go there
70
+ def fork
71
+ @state.each_forking_position do |row, column|
72
+ return [row, column]
73
+ end
74
+ false
72
75
  end
73
- false
74
- end
75
76
 
76
- # Try placing the opponent's letter at every position.
77
- # If there are now two winning solutions for next turn, block them there
78
- def block_fork
79
- PotentialState.new(@board, @other_player).each_forking_position do |row, column|
77
+ # Try placing the opponent's letter at every position.
78
+ # If there are now two winning solutions for next turn, block them there
79
+ def block_fork
80
+ PotentialState.new(@board, @other_player).each_forking_position do |row, column|
81
+
82
+ # Simulate blocking the fork
83
+ temp_board = @board.clone
84
+ temp_board.play_at(row, column, @letter)
80
85
 
81
- # Simulate blocking the fork
82
- temp_board = @board.clone
83
- temp_board.play_at(row, column, @letter)
86
+ # Search for the elusive double fork
87
+ if PotentialState.new(temp_board, @other_player).forking_positions.any?
88
+ return force_a_block
89
+ end
84
90
 
85
- # Search for the elusive double fork
86
- if PotentialState.new(temp_board, @other_player).forking_positions.any?
87
- return force_a_block
91
+ return [row, column]
88
92
  end
89
-
90
- return [row, column]
93
+ false
91
94
  end
92
- false
93
- end
94
95
 
95
- def center
96
- return false if @board.get_cell(@board.size/2, @board.size/2)
97
- [1,1]
98
- end
96
+ def center
97
+ return false if @board.get_cell(@board.size/2, @board.size/2)
98
+ [@board.size/2, @board.size/2]
99
+ end
99
100
 
100
- # Cycle through all of the corners looking for the opponent's letter
101
- # If one is found, place letter at the opposite corner
102
- def opposite_corner
103
- first = 0
104
- last = @board.size - 1
105
- corners.each_with_index do |corner, index|
106
- if corner == @other_player
107
- next if @board.get_cell(*opposite_corner_from_index(index).compact)
108
- return opposite_corner_from_index(index)
101
+ # Cycle through all of the corners looking for the opponent's letter
102
+ # If one is found, place letter at the opposite corner
103
+ def opposite_corner
104
+ first = 0
105
+ last = @board.size - 1
106
+ corners.each_with_index do |corner, index|
107
+ if corner == @other_player
108
+ next if @board.get_cell(*opposite_corner_from_index(index).compact)
109
+ return opposite_corner_from_index(index)
110
+ end
109
111
  end
112
+ false
110
113
  end
111
- false
112
- end
113
114
 
114
- # Cycle though all of the corners, until one is found that is empty
115
- def empty_corner
116
- corners.each_with_index do |corner, index|
117
- next if corner
118
- return corner_from_index(index)
115
+ # Cycle though all of the corners, until one is found that is empty
116
+ def empty_corner
117
+ corners.each_with_index do |corner, index|
118
+ next if corner
119
+ return corner_from_index(index)
120
+ end
121
+ false
119
122
  end
120
- false
121
- end
122
123
 
123
- # Place letter at a random empty cell, at this point it should only be sides left
124
- def empty_side
125
- @board.empty_positions do |row, column|
126
- return [row, column]
124
+ # Place letter at a random empty cell, at this point it should only be sides left
125
+ def empty_side
126
+ @board.empty_positions do |row, column|
127
+ return [row, column]
128
+ end
129
+ false
127
130
  end
128
- false
129
- end
130
131
 
131
- private
132
+ private
132
133
 
133
- def corners
134
- [@board.get_cell(0, 0), # Top Left
135
- @board.get_cell(@board.size-1, 0), # Top Right
136
- @board.get_cell(@board.size-1, @board.size-1), # Bottom Right
137
- @board.get_cell(0, @board.size-1)] # Bottom Left
138
- end
134
+ def corners
135
+ [@board.get_cell(0, 0), # Top Left
136
+ @board.get_cell(@board.size-1, 0), # Top Right
137
+ @board.get_cell(@board.size-1, @board.size-1), # Bottom Right
138
+ @board.get_cell(0, @board.size-1)] # Bottom Left
139
+ end
139
140
 
140
- def corner_from_index(index)
141
- first = 0
142
- last = @board.size - 1
143
- case index
144
- when 0 # Top Left
145
- [first, first]
146
- when 1 # Top Right
147
- [last, first]
148
- when 2 # Bottom Right
149
- [last, last]
150
- when 3 # Bottom Left
151
- [first, last]
152
- end
153
- end
141
+ def corner_from_index(index)
142
+ first = 0
143
+ last = @board.size - 1
144
+ case index
145
+ when 0 # Top Left
146
+ [first, first]
147
+ when 1 # Top Right
148
+ [last, first]
149
+ when 2 # Bottom Right
150
+ [last, last]
151
+ when 3 # Bottom Left
152
+ [first, last]
153
+ end
154
+ end
154
155
 
155
- def opposite_corner_from_index(index)
156
- first = 0
157
- last = @board.size - 1
158
- case index
159
- when 0 # Top Left
160
- [last, last]
161
- when 1 # Top Right
162
- [first, last]
163
- when 2 # Bottom Right
164
- [first, first]
165
- when 3 # Bottom Left
166
- [last, first]
156
+ def opposite_corner_from_index(index)
157
+ first = 0
158
+ last = @board.size - 1
159
+ case index
160
+ when 0 # Top Left
161
+ [last, last]
162
+ when 1 # Top Right
163
+ [first, last]
164
+ when 2 # Bottom Right
165
+ [first, first]
166
+ when 3 # Bottom Left
167
+ [last, first]
168
+ end
167
169
  end
168
- end
169
170
 
170
- def other_player(letter = @letter)
171
- letter == X ? O : X
172
- end
171
+ def other_player(letter = @letter)
172
+ letter == X ? O : X
173
+ end
173
174
 
174
- def each_position(&block)
175
- @board.size.times do |column|
176
- @board.size.times do |row|
177
- yield(row, column)
175
+ def each_position(&block)
176
+ @board.size.times do |column|
177
+ @board.size.times do |row|
178
+ yield(row, column)
179
+ end
178
180
  end
179
181
  end
180
- end
181
182
 
182
- def force_a_block
183
- # Force them to block without creating another fork
184
- each_position do |row, column|
185
- if @state.at(row, column).can_win_next_turn?
183
+ def force_a_block
184
+ # Force them to block without creating another fork
185
+ each_position do |row, column|
186
+ if @state.at(row, column).can_win_next_turn?
186
187
 
187
- # Simulate forcing them to block
188
- temp_board = @board.clone
189
- temp_board.play_at(row, column, @letter)
190
- temp_board.play_at(*block(temp_board, @other_player), @other_player)
188
+ # Simulate forcing them to block
189
+ temp_board = @board.clone
190
+ temp_board.play_at(row, column, @letter)
191
+ temp_board.play_at(*block(temp_board, @other_player), @other_player)
191
192
 
192
- # Did I just create another fork with that block?
193
- next if PotentialState.new(temp_board, @other_player).fork_exists?
193
+ # Did I just create another fork with that block?
194
+ next if PotentialState.new(temp_board, @other_player).fork_exists?
194
195
 
195
- return [row, column]
196
+ return [row, column]
196
197
 
198
+ end
197
199
  end
198
200
  end
199
201
  end
200
- end
201
- # Represents a state of players move
202
- # This is comprised of a board and letter (player)
203
- class PotentialState
204
- def initialize(board, letter)
205
- @board, @letter = board, letter
206
- end
202
+ # Represents a state of players move
203
+ # This is comprised of a board and letter (player)
204
+ class PotentialState
205
+ def initialize(board, letter)
206
+ @board, @letter = board, letter
207
+ end
207
208
 
208
- def at(row, column)
209
- new_board = @board.clone
210
- new_board.play_at(row, column, @letter)
211
- PotentialState.new(new_board, @letter)
212
- end
209
+ def at(row, column)
210
+ new_board = @board.clone
211
+ new_board.play_at(row, column, @letter)
212
+ PotentialState.new(new_board, @letter)
213
+ end
213
214
 
214
- def solved?
215
- @board.solved?
216
- end
215
+ def solved?
216
+ @board.solved?
217
+ end
217
218
 
218
- def each_forking_position(&block)
219
- forking_positions.each { |position| yield(*position) }
220
- end
219
+ def each_forking_position(&block)
220
+ forking_positions.each { |position| yield(*position) }
221
+ end
221
222
 
222
- def fork_exists?
223
- winning_positions_count >= 2
224
- end
223
+ def fork_exists?
224
+ winning_positions_count >= 2
225
+ end
225
226
 
226
- def can_win_next_turn?
227
- each_position do |row, column|
228
- return true if at(row, column).solved?
227
+ def can_win_next_turn?
228
+ each_position do |row, column|
229
+ return true if at(row, column).solved?
230
+ end
231
+ false
229
232
  end
230
- false
231
- end
232
233
 
233
- def forking_positions
234
- positions = []
235
- each_position do |row, column|
236
- positions << [row, column] if at(row, column).fork_exists?
234
+ def forking_positions
235
+ positions = []
236
+ each_position do |row, column|
237
+ positions << [row, column] if at(row, column).fork_exists?
238
+ end
239
+ positions
237
240
  end
238
- positions
239
- end
240
241
 
241
- private
242
+ private
242
243
 
243
- def winning_positions_count
244
- count = 0
245
- each_position do |row, column|
246
- count += 1 if at(row, column).solved?
244
+ def winning_positions_count
245
+ count = 0
246
+ each_position do |row, column|
247
+ count += 1 if at(row, column).solved?
248
+ end
249
+ count
247
250
  end
248
- count
249
- end
250
251
 
251
- def each_position(&block)
252
- @board.size.times do |column|
253
- @board.size.times do |row|
254
- yield(row, column)
252
+ def each_position(&block)
253
+ @board.size.times do |column|
254
+ @board.size.times do |row|
255
+ yield(row, column)
256
+ end
255
257
  end
256
258
  end
257
259
  end
@@ -1,3 +1,3 @@
1
1
  module TicTacToe
2
- VERSION = '0.5.0'
2
+ VERSION = '0.5.1'
3
3
  end
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  class GamePresentorTest < MiniTest::Unit::TestCase
4
4
  def setup
5
5
  @game = TicTacToe::Game.new
6
- @presentor = TicTacToe::GamePresentor.new(@game)
6
+ @presentor = TicTacToe::Presenter::Game.new(@game)
7
7
  end
8
8
 
9
9
  def test_grid
@@ -12,7 +12,7 @@ class GamePresentorTest < MiniTest::Unit::TestCase
12
12
 
13
13
  def test_grid_with_moves
14
14
  @game = TicTacToe::Game.new([['x', nil, nil], [nil, nil, nil], [nil, nil, nil]])
15
- @presentor = TicTacToe::GamePresentor.new(@game)
15
+ @presentor = TicTacToe::Presenter::Game.new(@game)
16
16
 
17
17
  assert_equal [%w(x 2 3), %w(4 5 6), %w(7 8 9)], @presentor.grid
18
18
  end
@@ -7,6 +7,6 @@ class PlayerPresenterTest < MiniTest::Unit::TestCase
7
7
 
8
8
  def test_move_json
9
9
  assert_equal({letter: 'x', type: 'mock', move: '1'}.to_json,
10
- TicTacToe::PlayerPresenter.new(@player_mock).move_json('1'))
10
+ TicTacToe::Presenter::Player.new(@player_mock).move_json('1'))
11
11
  end
12
12
  end
@@ -5,12 +5,12 @@ class BoardMock; end
5
5
  class PlayerTest < MiniTest::Unit::TestCase
6
6
 
7
7
  def test_build_human_player
8
- assert_equal TicTacToe::HumanPlayer,
8
+ assert_equal TicTacToe::Player::Human,
9
9
  TicTacToe::Player.build('type' => 'human').class
10
10
  end
11
11
 
12
12
  def test_build_computer_player
13
- assert_equal TicTacToe::ComputerPlayer,
13
+ assert_equal TicTacToe::Player::Computer,
14
14
  TicTacToe::Player.build('type' => 'computer').class
15
15
  end
16
16
  end
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  class HumanPlayerTest < MiniTest::Unit::TestCase
38
38
  def setup
39
- @player = TicTacToe::HumanPlayer.new('letter' => "x", 'move' => [0,0])
39
+ @player = TicTacToe::Player::Human.new('letter' => "x", 'move' => [0,0])
40
40
  end
41
41
 
42
42
  include SharedPlayerTests
@@ -50,7 +50,7 @@ class ComputerPlayerTest < MiniTest::Unit::TestCase
50
50
  end
51
51
  end
52
52
  def setup
53
- @player = TicTacToe::ComputerPlayer.new({'letter' => "x"}, SolverMock)
53
+ @player = TicTacToe::Player::Computer.new({'letter' => "x"}, SolverMock)
54
54
  end
55
55
 
56
56
  include SharedPlayerTests
@@ -48,6 +48,6 @@ private
48
48
  def set_grid(grid)
49
49
  board = TicTacToe::Board.new
50
50
  board.grid = grid
51
- @state = TicTacToe::ThreeByThree::PotentialState.new(board, 'x')
51
+ @state = TicTacToe::Strategy::ThreeByThree::PotentialState.new(board, 'x')
52
52
  end
53
53
  end
@@ -165,7 +165,7 @@ end
165
165
  class MinimaxSolverTest < MiniTest::Unit::TestCase
166
166
  def setup
167
167
  @board = TicTacToe::Board.new
168
- @solver = TicTacToe::MinimaxStrategy.new(@board, 'x')
168
+ @solver = TicTacToe::Strategy::MinimaxStrategy.new(@board, 'x')
169
169
  end
170
170
 
171
171
  include SharedSolverTests
@@ -174,7 +174,7 @@ end
174
174
  class ThreeByThreeSolverTest < MiniTest::Unit::TestCase
175
175
  def setup
176
176
  @board = TicTacToe::Board.new
177
- @solver = TicTacToe::ThreeByThreeStrategy.new(@board, 'x')
177
+ @solver = TicTacToe::Strategy::ThreeByThreeStrategy.new(@board, 'x')
178
178
  end
179
179
 
180
180
  include SharedSolverTests
@@ -19,34 +19,34 @@ class TerminalGameTest < MiniTest::Unit::TestCase
19
19
  end
20
20
 
21
21
  def test_computer_goes_first
22
- assert TicTacToe::TerminalGame.new(nil, IoMock.new("s")).computer_goes_first?
23
- refute TicTacToe::TerminalGame.new(nil, IoMock.new("f")).computer_goes_first?
22
+ assert TicTacToe::GameType::Terminal.new(nil, IoMock.new("s")).computer_goes_first?
23
+ refute TicTacToe::GameType::Terminal.new(nil, IoMock.new("f")).computer_goes_first?
24
24
  end
25
25
 
26
26
  def test_get_move_from_user
27
- game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new("1"))
27
+ game = TicTacToe::GameType::Terminal.new(TicTacToe::Board.new, IoMock.new("1"))
28
28
  assert_equal [0, 0], game.get_move_from_user
29
29
 
30
- game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new("9"))
30
+ game = TicTacToe::GameType::Terminal.new(TicTacToe::Board.new, IoMock.new("9"))
31
31
  assert_equal [2, 2], game.get_move_from_user
32
32
  end
33
33
 
34
34
  def test_handle_bad_input
35
- game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new(["1", "a"]))
35
+ game = TicTacToe::GameType::Terminal.new(TicTacToe::Board.new, IoMock.new(["1", "a"]))
36
36
  assert_equal [0, 0], game.get_move_from_user
37
37
  end
38
38
 
39
39
  def test_play_again
40
- game = TicTacToe::TerminalGame.new(nil, IoMock.new("y"))
40
+ game = TicTacToe::GameType::Terminal.new(nil, IoMock.new("y"))
41
41
  assert game.play_again?
42
42
 
43
- game = TicTacToe::TerminalGame.new(nil, IoMock.new("n"))
43
+ game = TicTacToe::GameType::Terminal.new(nil, IoMock.new("n"))
44
44
  refute game.play_again?
45
45
  end
46
46
 
47
47
  def test_update_board
48
48
  mock = IoMock.new
49
- game = TicTacToe::TerminalGame.new("FooBar", mock)
49
+ game = TicTacToe::GameType::Terminal.new("FooBar", mock)
50
50
  game.update_board
51
51
 
52
52
  assert_equal "FooBar", mock.output
@@ -54,25 +54,10 @@ class TerminalGameTest < MiniTest::Unit::TestCase
54
54
 
55
55
  def test_display_text
56
56
  mock = IoMock.new
57
- game = TicTacToe::TerminalGame.new(nil, mock)
57
+ game = TicTacToe::GameType::Terminal.new(nil, mock)
58
58
  game.display_text("FooBar")
59
59
 
60
60
  assert_equal "FooBar", mock.output
61
61
  end
62
62
 
63
- def test_select_solver
64
- mock = IoMock.new("1")
65
- game = TicTacToe::TerminalGame.new(nil, mock)
66
- assert_equal "FooBar", game.select(["FooBar", "BarFoo"])
67
-
68
- mock = IoMock.new("2")
69
- game = TicTacToe::TerminalGame.new(nil, mock)
70
- assert_equal "BarFoo", game.select(["FooBar", "BarFoo"])
71
- end
72
-
73
- def test_select_bad_choice
74
- mock = IoMock.new(["1", 'x'])
75
- game = TicTacToe::TerminalGame.new(nil, mock)
76
- assert_equal "FooBar", game.select(["FooBar", "BarFoo"])
77
- end
78
63
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: erics_tic_tac_toe
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.5.0
5
+ version: 0.5.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Eric Koslow
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-08-15 00:00:00 -05:00
13
+ date: 2012-08-27 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -57,8 +57,8 @@ files:
57
57
  - lib/tic_tac_toe/player.rb
58
58
  - lib/tic_tac_toe/players/computer_player.rb
59
59
  - lib/tic_tac_toe/players/human_player.rb
60
- - lib/tic_tac_toe/presentors/game_presenter.rb
61
- - lib/tic_tac_toe/presentors/player_presenter.rb
60
+ - lib/tic_tac_toe/presenters/game_presenter.rb
61
+ - lib/tic_tac_toe/presenters/player_presenter.rb
62
62
  - lib/tic_tac_toe/strategies/minimax_strategy.rb
63
63
  - lib/tic_tac_toe/strategies/three_by_three_strategy.rb
64
64
  - lib/tic_tac_toe/version.rb
@@ -1,16 +0,0 @@
1
- module TicTacToe
2
- class GamePresentor
3
-
4
- def initialize(game)
5
- @game = game
6
- end
7
-
8
- def grid
9
- @game.grid.each_with_index.map do |row, i|
10
- row.each_with_index.map do |cell, j|
11
- cell || ((i*3)+(j+1)).to_s
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,13 +0,0 @@
1
- require 'json'
2
-
3
- module TicTacToe
4
- class PlayerPresenter
5
- def initialize(player)
6
- @player = player
7
- end
8
-
9
- def move_json(move=nil)
10
- {letter: @player.letter, type: @player.type, move: move}.to_json
11
- end
12
- end
13
- end