Skip to content

Instantly share code, notes, and snippets.

@marksim
Last active December 16, 2015 16:08
Show Gist options
  • Save marksim/5460578 to your computer and use it in GitHub Desktop.
Save marksim/5460578 to your computer and use it in GitHub Desktop.
Tic Tac Toe implementation through TDD As If you Mean It - (http://cumulative-hypotheses.org/2011/08/30/tdd-as-if-you-meant-it/)
require 'rspec'
# Tic Tac Toe is
# a game
# with a 3x3 board
# and two players
# one is 'X'
# one is 'O'
# X begins
# Players take turns
# placing an indicator on the board
# until
# 3 of the same indicators are in a horizontal, vertical or diagonal row
# and thus that players wins
# or all the squares are taken
# and the cat wins
class Player
attr_accessor :opponent
def opponent
@opponent ||= self.class.new
end
def coords
@coords ||= []
end
def empty?(x, y)
!coords.include?([x, y])
end
def play(x, y)
raise "Off Board" if y > 2 || y < 0 || x > 2 || x < 0
(opponent.empty?(x, y) && !coords.include?([x, y])).tap do |can|
coords << [x, y] if can
end
end
def column_win?
(0..2).map{|c| coords.map(&:last).count(c)}.any?{|c| c > 2}
end
def row_win?
(0..2).map{|r| coords.map(&:first).count(r)}.any?{|c| c > 2}
end
def diagonal_win?
(coords.include?([0,0]) &&
coords.include?([1, 1]) &&
coords.include?([2, 2])) ||
(coords.include?([2,0]) &&
coords.include?([1,1]) &&
coords.include?([0,2]))
end
def win?
row_win? || column_win? || diagonal_win?
end
def full?
(coords.length + opponent.coords.length == 9)
end
def finished?
full? || win? || opponent.win?
end
def basic
[
[1, 1], # Center
[0, 0], [0, 2], [2, 2], [2, 0], # Corners
[0, 1], [1, 2], [2, 1], [1, 0] # Sides
].each do |x, y|
if empty?(x, y) && opponent.empty?(x, y)
return [x, y]
end
end
end
def available_moves
[].tap do |moves|
(0..2).each do |x|
(0..2).each do |y|
moves << [x, y] if empty?(x, y) && opponent.empty?(x, y)
end
end
end
end
def winning_move
available_moves.each do |x, y|
play(x, y)
if win?
coords.delete([x, y])
return [x, y]
else
coords.delete([x, y])
end
end
nil
end
def opponent_winning_move
opponent.winning_move
end
def move
winning_move || opponent_winning_move || basic
end
def play_next_move
play(*move)
end
end
describe "TicTacToe" do
describe "a player" do
let(:player) { Player.new }
describe "playing" do
it 'can play on the board' do
player.play(1, 1).should be_true
end
it "cannot play in a column less than 0" do
expect { player.play(-1, 1) }.to raise_error
end
it "cannot play in a column greater than 2" do
expect { player.play(3, 1) }.to raise_error
end
it "cannot play in a row less than 0" do
expect { player.play(1, -1) }.to raise_error
end
it "cannot play in a row greater than 2" do
expect { player.play(1, 3) }.to raise_error
end
it "cannot play in a taken space" do
player.play(1, 1).should be_true
player.play(1, 1).should be_false
end
it "knows who it is playing opponent" do
player2 = Player.new
player.opponent= player2
player.opponent.should == player2
end
it "cannot play in a space taken by another player" do
player2 = Player.new
player.opponent = player2
player2.play(1, 1).should be_true
player.play(1, 1).should be_false
end
it "can 'play' alone" do
player.opponent = nil
player.play(1,1).should be_true
end
end
describe "winning" do
it "wins if 3 coords with the same col are taken" do
player.play(0, 0)
player.play(0, 1)
player.play(0, 2)
player.should be_win
end
it "can't win without 3 coords" do
player.play(1, 1)
player.should_not be_win
end
it "can't win without 3 coords in a row" do
player.play 1, 1
player.play 2, 1
player.play 0, 0
player.should_not be_win
end
it "wins if 3 coords with the same row are taken" do
player.play(0, 0)
player.play(1, 0)
player.play(2, 0)
player.should be_win
end
it "wins if 3 coords from 0,0 to 2,2 are taken" do
player.play 0,0
player.play 1,1
player.play 2,2
player.should be_win
end
it "wins if 3 coords from 0,2 to 2,0 are taken" do
player.play 2,0
player.play 1,1
player.play 0,2
player.should be_win
end
end
describe "finished" do
it "is finished if all the coords are taken" do
(0..2).each do |x|
(0..2).each do |y|
player.play x, y
end
end
player2 = Player.new
player.opponent = player2
player.should be_finished
end
it "is finished if the player has won" do
player.play 0, 0
player.play 1, 1
player.play 2, 2
player.should be_finished
end
it "is finished if the opponent has one" do
player2 = Player.new
player2.opponent = player
player.play 0, 0
player.play 1, 1
player.play 2, 2
player2.should be_finished
end
end
end
end
describe "AI" do
describe "cat game" do
let(:cat_man) { Player.new }
let(:dog_man) { Player.new }
before(:each) do
cat_man.opponent = dog_man
dog_man.opponent = cat_man
end
it "plays in the middle if available" do
cat_man = Player.new
cat_man.move.should == [1, 1]
end
it "plays on the corner if the middle isn't available" do
dog_man.play(1, 1)
cat_man.move.should == [0, 0]
end
it "plays on the empty spot if an opponent has 2 in a row followed by empty" do
cat_man.play(0, 0)
cat_man.play(0, 1)
dog_man.move.should == [0, 2]
end
it "plays the winning move if it has it" do
cat_man.play(0, 0)
cat_man.play(0, 1)
cat_man.move.should == [0, 2]
end
it "plays a random place if middle and corner are not available" do
cat_man.play(1, 1)
dog_man.play 0, 0
cat_man.move.should == [0, 2]
end
end
end
describe "A Game" do
it "should never have a winner" do
cat_man = Player.new
dog_man = Player.new
cat_man.opponent = dog_man
dog_man.opponent = cat_man
while !cat_man.finished?
cat_man.play_next_move
dog_man.play_next_move unless dog_man.finished?
end
cat_man.should_not be_win
dog_man.should_not be_win
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment