erics_tic_tac_toe 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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