erics_tic_tac_toe 0.1.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ require 'test_helper'
2
+ require 'stringio'
3
+
4
+ class TerminalGameTest < MiniTest::Unit::TestCase
5
+
6
+ class IoMock
7
+ attr_reader :output
8
+
9
+ def initialize(output=nil)
10
+ @input = Array(output)
11
+ end
12
+
13
+ def gets
14
+ @input.pop
15
+ end
16
+
17
+ def puts(text); @output = text end
18
+ def print(text); @output = text end
19
+ end
20
+
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?
24
+ end
25
+
26
+ def test_get_move_from_user
27
+ game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new("1"))
28
+ assert_equal [0, 0], game.get_move_from_user
29
+
30
+ game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new("9"))
31
+ assert_equal [2, 2], game.get_move_from_user
32
+ end
33
+
34
+ def test_handle_bad_input
35
+ game = TicTacToe::TerminalGame.new(TicTacToe::Board.new, IoMock.new(["1", "a"]))
36
+ assert_equal [0, 0], game.get_move_from_user
37
+ end
38
+
39
+ def test_play_again
40
+ game = TicTacToe::TerminalGame.new(nil, IoMock.new("y"))
41
+ assert game.play_again?
42
+
43
+ game = TicTacToe::TerminalGame.new(nil, IoMock.new("n"))
44
+ refute game.play_again?
45
+ end
46
+
47
+ def test_update_board
48
+ mock = IoMock.new
49
+ game = TicTacToe::TerminalGame.new("FooBar", mock)
50
+ game.update_board
51
+
52
+ assert_equal "FooBar", mock.output
53
+ end
54
+
55
+ def test_display_text
56
+ mock = IoMock.new
57
+ game = TicTacToe::TerminalGame.new(nil, mock)
58
+ game.display_text("FooBar")
59
+
60
+ assert_equal "FooBar", mock.output
61
+ end
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
+ end
data/test/test_helper.rb CHANGED
@@ -1,3 +1,7 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
1
4
  require 'tic_tac_toe'
5
+ require 'minitest/pride'
2
6
  require 'minitest/unit'
3
7
  require 'minitest/autorun'
data/tic-tac-toe.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require "tic-tac-toe/version"
3
+ require "tic_tac_toe/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "erics_tic_tac_toe"
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.1.0
5
+ version: 0.5.0
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-04-09 00:00:00 -04:00
13
+ date: 2012-08-15 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -50,17 +50,26 @@ files:
50
50
  - README.markdown
51
51
  - Rakefile
52
52
  - bin/tic_tac_toe
53
- - lib/tic-tac-toe/board.rb
54
- - lib/tic-tac-toe/game.rb
55
- - lib/tic-tac-toe/game_types/terminal_game.rb
56
- - lib/tic-tac-toe/solver.rb
57
- - lib/tic-tac-toe/strategies/threebythree_implementations/brute_force_implementation.rb
58
- - lib/tic-tac-toe/strategies/threebythree_stategy.rb
59
- - lib/tic-tac-toe/version.rb
60
53
  - lib/tic_tac_toe.rb
54
+ - lib/tic_tac_toe/board.rb
55
+ - lib/tic_tac_toe/game.rb
56
+ - lib/tic_tac_toe/game_types/terminal_game.rb
57
+ - lib/tic_tac_toe/player.rb
58
+ - lib/tic_tac_toe/players/computer_player.rb
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
62
+ - lib/tic_tac_toe/strategies/minimax_strategy.rb
63
+ - lib/tic_tac_toe/strategies/three_by_three_strategy.rb
64
+ - lib/tic_tac_toe/version.rb
61
65
  - test/board_test.rb
62
- - test/brute_force_implementation_test.rb
66
+ - test/game_presentor_test.rb
67
+ - test/game_test.rb
68
+ - test/player_presenter_test.rb
69
+ - test/player_test.rb
70
+ - test/potential_state_test.rb
63
71
  - test/solver_test.rb
72
+ - test/terminal_game_test.rb
64
73
  - test/test_helper.rb
65
74
  - tic-tac-toe.gemspec
66
75
  has_rdoc: true
@@ -93,6 +102,11 @@ specification_version: 3
93
102
  summary: A game of Tic Tac Toe
94
103
  test_files:
95
104
  - test/board_test.rb
96
- - test/brute_force_implementation_test.rb
105
+ - test/game_presentor_test.rb
106
+ - test/game_test.rb
107
+ - test/player_presenter_test.rb
108
+ - test/player_test.rb
109
+ - test/potential_state_test.rb
97
110
  - test/solver_test.rb
111
+ - test/terminal_game_test.rb
98
112
  - test/test_helper.rb
@@ -1,148 +0,0 @@
1
- module TicTacToe
2
- class Board
3
-
4
- SIZE = 3.freeze
5
-
6
- def initialize(grid=nil)
7
- #[[ nil, nil, nil],
8
- # [ nil, nil, nil],
9
- # [ nil, nil, nil]]
10
- @grid = grid || [ [nil] * SIZE ] * SIZE
11
- end
12
-
13
- def get_cell(x, y)
14
- @grid[y][x]
15
- end
16
-
17
- # Returns the closest middle cell
18
- def center_cell
19
- mid = @grid.size/2
20
- get_cell(mid, mid)
21
- end
22
-
23
- # Sets the closet middle cell if its not already set
24
- def center_cell=(letter)
25
- mid = @grid.size/2
26
- play_at(mid, mid, letter)
27
- end
28
-
29
- # Returns the corners of the grid
30
- def corners
31
- [@grid[0][0],
32
- @grid[0][SIZE-1],
33
- @grid[SIZE-1][SIZE-1],
34
- @grid[SIZE-1][0]]
35
- end
36
-
37
- # Returns the corners of the empty cells
38
- def any_empty_position(&block)
39
- SIZE.times do |y|
40
- SIZE.times do |x|
41
- next if get_cell(x, y)
42
- yield(x, y)
43
- end
44
- end
45
- end
46
-
47
- # Plays a letter at a position, unless that position has already been taken
48
- def play_at(x, y, letter)
49
- # Weird bug I found in ruby 1.9.3-p0
50
- # Given a @grid of [ [nil,nil,nil], [nil,nil,nil], [nil,nil,nil] ]
51
- # If you call @grid[0][0] = 'x'
52
- # I aspect @grid to be [ ['x',nil,nil], [nil,nil,nil], [nil,nil,nil] ]
53
- # What happens is that @grid is [ ['x',nil,nil], ['x',nil,nil], ['x',nil,nil] ]
54
- # This is a workaround for that
55
- inner = @grid[y].clone
56
- inner[x] ||= letter
57
- @grid[y] = inner
58
- end
59
-
60
- # Returns true if the grid is empty
61
- def empty?
62
- each_cell do |cell|
63
- return false if cell
64
- end
65
- true
66
- end
67
-
68
- # Returns true if the grid only has one element
69
- def only_one?
70
- counter = 0
71
- each_cell do |cell|
72
- counter += 1 if cell
73
- return false if counter >= 2
74
- end
75
- counter == 1
76
- end
77
-
78
- # Returns true if every cell is set to a value
79
- def full?
80
- each_cell do |cell|
81
- return false unless cell
82
- end
83
- true
84
- end
85
-
86
- # Returns true if the board has a wining pattern
87
- def solved?
88
- return true if won_across?
89
- return true if won_up_and_down?
90
- return true if won_diagonally?
91
- false
92
- end
93
-
94
- def to_s
95
- output = ["-----------\n"]
96
- @grid.each_with_index do |row, i|
97
- row.each_with_index do |cell, j|
98
- output << "#{cell || ((i*3)+j+1)} | #{"\n" if j==2}"
99
- end
100
- end
101
- output << ["-----------\n\n"]
102
- output.join
103
- end
104
-
105
- # Preform a deep clone of the board
106
- # FIXME: This only works for SIZE=3
107
- def clone
108
- Board.new([[@grid[0][0], @grid[0][1], @grid[0][2] ],
109
- [@grid[1][0], @grid[1][1], @grid[1][2] ],
110
- [@grid[2][0], @grid[2][1], @grid[2][2] ]])
111
- end
112
-
113
- private
114
-
115
- def each_cell(&block)
116
- @grid.each do |row|
117
- row.each do |cell|
118
- yield(cell)
119
- end
120
- end
121
- end
122
-
123
- def won_across?(grid = @grid)
124
- grid.any? do |row|
125
- winning_group?(row)
126
- end
127
- end
128
-
129
- def won_up_and_down?
130
- won_across?(@grid.transpose)
131
- end
132
-
133
- def won_diagonally?
134
- base = (0..@grid.size-1)
135
- left_to_right = base.collect { |i| @grid[i][i]}
136
- right_to_left = base.collect { |i| @grid[i][@grid.size-1-i]}
137
- return true if winning_group?(left_to_right)
138
- return true if winning_group?(right_to_left)
139
- false
140
- end
141
-
142
- def winning_group?(group)
143
- # Won when none of the cells are nil and they are all the same value
144
- !group.any? { |cell| cell.nil? } && group.uniq.size == 1
145
- end
146
-
147
- end
148
- end
@@ -1,62 +0,0 @@
1
- #This is a computer that will play a perfect game of tic-tac-toe
2
- #Author: Eric Koslow
3
-
4
- require_relative 'game_types/terminal_game'
5
-
6
- module TicTacToe
7
- class Game
8
- def initialize(type=TerminalGame)
9
- @board = Board.new
10
- @game_type = type.new(@board)
11
- end
12
-
13
- def run
14
- if @game_type.computer_goes_first?
15
- @computer_letter = O
16
- get_move_from_computer!
17
- else
18
- @computer_letter = X
19
- end
20
-
21
- @game_type.update_board
22
-
23
- loop do
24
- @game_type.get_move_from_user!
25
- @game_type.update_board
26
- break if game_over?
27
-
28
- get_move_from_computer!
29
- @game_type.update_board
30
- break if game_over?
31
- end
32
-
33
- if @game_type.play_again?
34
- new_game
35
- end
36
- end
37
-
38
- private
39
-
40
- def new_game
41
- Game.new.run
42
- end
43
-
44
- def game_over?
45
- if @board.solved?
46
- @game_type.display_text("You lost!")
47
- true
48
- elsif @board.full?
49
- @game_type.display_text("Cats game!")
50
- true
51
- else
52
- false
53
- end
54
- end
55
-
56
- def get_move_from_computer!
57
- Solver.new(@board, @computer_letter).next_move!
58
- @game_type.display_text("Computer's move (#{@computer_letter}):")
59
- end
60
-
61
- end
62
- end
@@ -1,63 +0,0 @@
1
- module TicTacToe
2
- class TerminalGame
3
- class IllegalMove < RuntimeError
4
- end
5
-
6
- def initialize(board)
7
- @board = board
8
- end
9
-
10
- def computer_goes_first?
11
- print "Would you like to play first or second? (f/s) "
12
- input = gets.chomp
13
- unless input =~ /^(f|first)$/i
14
- @human_letter = X
15
- true
16
- else
17
- @human_letter = O
18
- false
19
- end
20
- end
21
-
22
- def get_move_from_user!
23
- print "Your move (#{@human_letter}) (1-9): "
24
- input = gets.chomp
25
-
26
- if input =~ /^\d$/
27
- cords = cord_from_num(input.to_i)
28
- if !@board.get_cell(*cords)
29
- @board.play_at(*(cords+[@human_letter]))
30
- else
31
- raise IllegalMove.new("That cell is already taken")
32
- end
33
- else
34
- raise IllegalMove.new("Must be a single number")
35
- end
36
- rescue IllegalMove => e
37
- display_text "Illegal Move: #{e.message}. Please try again"
38
- get_move_from_user!
39
- end
40
-
41
- def play_again?
42
- print "Play again? (y/n) "
43
- input = gets.chomp
44
- input =~ /^(y|yes)$/i
45
- end
46
-
47
- def update_board
48
- puts @board
49
- end
50
-
51
- def display_text(text)
52
- puts text
53
- end
54
-
55
- private
56
-
57
- def cord_from_num(num)
58
- num -= 1
59
- [num % Board::SIZE, num/Board::SIZE]
60
- end
61
- end
62
-
63
- end
@@ -1,16 +0,0 @@
1
- require_relative 'strategies/threebythree_stategy'
2
-
3
- module TicTacToe
4
- class Solver
5
-
6
- def initialize(board, letter, strategy=ThreebythreeStrategy)
7
- @strategy = strategy.new(board, letter)
8
- end
9
-
10
- # Performs the next perfect move and updates the board as a side effect
11
- def next_move!
12
- @strategy.solve!
13
- end
14
-
15
- end
16
- end