erics_tic_tac_toe 0.1.0 → 0.5.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.
@@ -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