Last active
December 16, 2015 16:08
-
-
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/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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