ttt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ Feature: Computer player
2
+
3
+ In order to hone my tic-tac-toe skills
4
+ As a player
5
+ I want an unbeatable computer opponent
6
+
7
+ The following scenarios should cover a sampling of possible boards. As there are
8
+ 765 possible game possible congruence classes of boards, I won't attempt to cover
9
+ them all, but will verify that the computer plays optimally in a number of common
10
+ situations (two game boards are in a congruence class, such as the two below,
11
+ if one can be turned into the other by rotating or mirroring).
12
+
13
+ For a list of all congruent states, there are some rake tasks which compute them
14
+ and explore some of their properties.
15
+
16
+ Example of congruent boards (rotate the left board 90 degrees to get the right board):
17
+
18
+ 1 | | | | 1
19
+ ---|---|--- ---|---|---
20
+ | | | |
21
+ ---|---|--- ---|---|---
22
+ | | | |
23
+
24
+
25
+ Scenario Outline: Computer's moves
26
+ When I create a game with "<configuration>"
27
+ And a computer player as player <player>
28
+ Then it is player<player>s turn
29
+ And the computer moves to one of the following: <possible boards>
30
+
31
+ Scenarios: Computer always takes a win when it is available
32
+ | configuration | player | possible boards | description |
33
+ | 110200200 | 1 | 111200200 | can win across top |
34
+ | 220000110 | 1 | 220000111 | can win across bottom and opponent can win across top |
35
+ | 201201000 | 1 | 201201001 | can win vertically on RHS opponent can win too |
36
+ | 120120000 | 1 | 120120100 | can win vertically on RHS opponent can win too |
37
+ | 102210000 | 1 | 102210001 | can win diagonally |
38
+ | 120112020 | 1 | 120112120 120112021 | can win in two positions |
39
+ | 120021001 | 2 | 120021021 | takes win when 2nd player and 1st can also win |
40
+
41
+ Scenarios: Computer blocks opponent's win
42
+ | configuration | player | possible boards | description |
43
+ | 120100000 | 2 | 120100200 | blocks lhs |
44
+ | 122110000 | 2 | 122110200 122110002 | blocks either of opponent's possible wins |
45
+ | 211200000 | 1 | 211200100 | blocks when first player |
46
+
47
+ Scenarios: Finds best moves for likely game states
48
+ | configuration | player | possible boards | description |
49
+ | 000000000 | 1 | 100000000 001000000 000000100 000000001 | makes best 1st move |
50
+ | 120000000 | 1 | 120000100 120010000 120100000 | makes move that will guarantee win in future |
51
+ | 100000002 | 1 | 101000002 100000102 | makes move that will guarantee win in future |
52
+ | 100000020 | 1 | 100000120 101000020 100010020 | makes move that will guarantee win in future |
53
+ | 102000000 | 1 | 102100000 102000100 102000001 | makes move that will guarantee win in future |
54
+ | 102100200 | 1 | 102110200 | makes move that will guarantee win next turn |
55
+ | 100020000 | 1 | 110020000 100120000 | makes move with highest probability of win in future |
56
+ | 100000000 | 2 | 100020000 | makes move with lowest probability of losing in future |
57
+
58
+ Scenario: Plays correctly for current player
59
+ Given I create a game with "000000000"
60
+ And I have a computer player
61
+ Then the computer will play for 1
62
+ Then the computer will play for 2
@@ -0,0 +1,63 @@
1
+ Feature: Create game
2
+
3
+ TTT is the core lib that underlies an interface, where the interface allows users to play the game.
4
+
5
+ In order to allow users to play the game
6
+ An interface must be able to create a game in various states.
7
+
8
+ A board can be represented as a string of 9 digits, where a 0 indicates that neither player has moved
9
+ into that position, a 1 indicates that player1 has moved there, and a 2 indicates that player2 has moved there.
10
+ The digits correspond to the board in the following manner:
11
+
12
+ abcdefghi
13
+
14
+ a | b | c
15
+ ---|---|---
16
+ d | e | f
17
+ ---|---|---
18
+ g | h | i
19
+
20
+ So the game board "001002100" would look like this (let "x" represent player 1 and "o" represent player2):
21
+
22
+ | | x
23
+ ---|---|---
24
+ | | o
25
+ ---|---|---
26
+ x | |
27
+
28
+
29
+ Scenario: Create a new game
30
+ When I create a new game
31
+ Then the game board is "000000000"
32
+ And it is player1s turn
33
+
34
+ Scenario Outline: Create an existing game
35
+ When I create a game with "<configuration>"
36
+ Then the game board is "<configuration>"
37
+ And it is player<player>s turn
38
+
39
+ Scenarios: Player1s turn
40
+ | configuration | player |
41
+ | 000000000 | 1 |
42
+ | 120000000 | 1 |
43
+ | 120120000 | 1 |
44
+ | 120120210 | 1 |
45
+ | 000010002 | 1 |
46
+ | 120221112 | 1 |
47
+
48
+ Scenarios: Player2s turn
49
+ | configuration | player |
50
+ | 100000000 | 2 |
51
+ | 012100000 | 2 |
52
+ | 001112200 | 2 |
53
+ | 100200100 | 2 |
54
+ | 120221110 | 2 |
55
+
56
+ Scenario: Know who is where
57
+ Given I create a game with "000120000"
58
+ When I ask who is at position 4, it returns 1
59
+ When I ask who is at position 5, it returns 2
60
+ When I ask who is at position 6, it returns nil
61
+
62
+
63
+
@@ -0,0 +1,53 @@
1
+ Feature: Finish game
2
+
3
+ In order to allow for actual gameplay
4
+ As a player
5
+ I can win, lose, and tie
6
+
7
+ Scenario: Player1 wins
8
+ Given I create a game with "120120000"
9
+ Then the game is not over
10
+ When I mark position 7
11
+ Then the game board is "120120100"
12
+ And the game is over
13
+ And player1 wins
14
+ And player2 loses
15
+ And it is no one's turn
16
+
17
+ Scenario: Player2 wins
18
+ Given I create a game with "121120000"
19
+ Then the game is not over
20
+ When I mark position 8
21
+ Then the game board is "121120020"
22
+ And the game is over
23
+ And player2 wins
24
+ And player1 loses
25
+ And it is no one's turn
26
+
27
+ Scenario: The players tie
28
+ Given I create a game with "121221012"
29
+ Then the game is not over
30
+ When I mark position 7
31
+ Then the game board is "121221112"
32
+ And the game is over
33
+ And player1 ties
34
+ And player2 ties
35
+ And it is no one's turn
36
+
37
+ Scenario Outline: I can ask which positions won
38
+ Given I create a game with "<configuration>"
39
+ When I ask what positions won
40
+ Then it tells me [<p1>, <p2>, <p3>]
41
+
42
+ Scenarios:
43
+ | configuration | p1 | p2 | p3 |
44
+ | 111220000 | 1 | 2 | 3 |
45
+ | 220111000 | 4 | 5 | 6 |
46
+ | 220000111 | 7 | 8 | 9 |
47
+ | 120120100 | 1 | 4 | 7 |
48
+ | 210210010 | 2 | 5 | 8 |
49
+ | 201201001 | 3 | 6 | 9 |
50
+ | 120210001 | 1 | 5 | 9 |
51
+ | 021210100 | 3 | 5 | 7 |
52
+ | 021210100 | 3 | 5 | 7 |
53
+ | 211210200 | 1 | 4 | 7 |
@@ -0,0 +1,56 @@
1
+ Feature: Finished states
2
+
3
+ In order to finish the game, the library must know what a finished state is.
4
+ A finished state is a state that either has no available moves left, or where
5
+ a player has won the game. A player has won the game if they have marked
6
+ three consecutive spaces in a horizontal, vertical, or diagonal direction.
7
+
8
+ Scenario: No winner, no available moves left
9
+ Given a tie game
10
+ Then the game is over
11
+
12
+ Scenario Outline: Three marks in a row
13
+ When I create a game with "<configuration>"
14
+ Then the game is over
15
+ And player<winner> wins
16
+
17
+ Scenarios: Horizontal win
18
+ | configuration | winner |
19
+ | 111000000 | 1 |
20
+ | 000111000 | 1 |
21
+ | 000000111 | 1 |
22
+ | 222000000 | 2 |
23
+ | 000222000 | 2 |
24
+ | 000000222 | 2 |
25
+
26
+ Scenarios: Vertical win
27
+ | configuration | winner |
28
+ | 100100100 | 1 |
29
+ | 010010010 | 1 |
30
+ | 001001001 | 1 |
31
+ | 200200200 | 2 |
32
+ | 020020020 | 2 |
33
+ | 002002002 | 2 |
34
+
35
+ Scenarios: Diagonal win
36
+ | configuration | winner |
37
+ | 100010001 | 1 |
38
+ | 001010100 | 1 |
39
+ | 200020002 | 2 |
40
+ | 002020200 | 2 |
41
+
42
+ Scenario Outline: Unfinished states
43
+ When I create a game with "<configuration>"
44
+ Then the game is not over
45
+
46
+ Scenarios: Playing to a tie game
47
+ | configuration |
48
+ | 000000000 |
49
+ | 100000000 |
50
+ | 100020000 |
51
+ | 101020000 |
52
+ | 121020000 |
53
+ | 121020010 |
54
+ | 121220010 |
55
+ | 121221010 |
56
+ | 121221012 |
@@ -0,0 +1,24 @@
1
+ Feature: Mark board
2
+
3
+ In order to play the game
4
+ As a player (or, more probably, an interface translating a player's desire)
5
+ I want to mark the board
6
+
7
+ Scenario: Marking an empty board
8
+ Given I create a new game
9
+ When I mark position 1
10
+ Then the game board is "100000000"
11
+
12
+ Scenario: Marking a board when it is player1s turn
13
+ Given I create a game with "120000000"
14
+ Then it is player1s turn
15
+ When I mark position 4
16
+ Then the game board is "120100000"
17
+ And it is player2s turn
18
+
19
+ Scenario: Marking a board when it is player2s turn
20
+ Given I create a game with "120100000"
21
+ Then it is player2s turn
22
+ When I mark position 7
23
+ Then the game board is "120100200"
24
+ And it is player1s turn
@@ -0,0 +1,42 @@
1
+ Given /^I look at (.*)$/ do |location|
2
+ @location = location
3
+ end
4
+
5
+ When /^I see it is executable$/ do
6
+ File.executable?(@location).should be
7
+ end
8
+
9
+ Given /^I pass the it "([^"]*)" on the command line$/ do |args|
10
+ pending "Yet again don't know how to test, it hangs b/c it expects input."\
11
+ "I can put it in a thread and kill it later, but inconsistent results."
12
+ require 'open3' # from stdlib
13
+ binary = Class.new Struct.new(:exitstatus, :stdout, :stderr) do
14
+ def initialize(args)
15
+ Open3.popen3 "bin/ttt #{args}" do |stdin, stdout, stderr, wait_thr|
16
+ super(wait_thr.value.exitstatus, stdout.read.strip, stderr.read.strip)
17
+ end
18
+ end
19
+ end
20
+ @binary = binary.new args
21
+ end
22
+
23
+ Then %r{^it should display /([^/]*)/$} do |message|
24
+ Then "it should print /#{message}/ to stdout"
25
+ end
26
+
27
+ Then %r{^it should print /([^/]*)/ to (\w+)$} do |message, output_name|
28
+ output = @binary.send output_name
29
+ output.should match Regexp.new(message)
30
+ end
31
+
32
+ Then /^it should exit with code of (\d+)$/ do |code|
33
+ @binary.exitstatus.should be code.to_i
34
+ end
35
+
36
+
37
+ # a simple greeting we'll have the CLI write
38
+ # just to make sure everything is hooked up correctly
39
+ Then /^it should welcome me to Tic Tac Toe$/ do
40
+ Then "it should display /Welcome to Tic Tac Toe/"
41
+ end
42
+
@@ -0,0 +1,82 @@
1
+ When /^I create a new game$/ do
2
+ @game = TTT::Game.new
3
+ end
4
+
5
+ Then /^the game board is "([^"]*)"$/ do |board|
6
+ @game.board.should == board
7
+ end
8
+
9
+ Then /^it is (player(\d+)s|no one's) turn$/ do |turn, player_number|
10
+ if player_number
11
+ @game.turn.should == player_number.to_i
12
+ else
13
+ @game.turn.should be_nil
14
+ end
15
+ end
16
+
17
+ When /^I ask what positions won$/ do
18
+ @winning_positions = @game.winning_positions
19
+ end
20
+
21
+ Then /^it tells me \[(\d+), (\d+), (\d+)\]$/ do |p1, p2, p3|
22
+ @winning_positions.sort.should == [p1, p2, p3].map(&:to_i).sort
23
+ end
24
+
25
+
26
+ When /^I create a game with "([^"]*)"$/ do |configuration|
27
+ @game = TTT::Game.new configuration
28
+ end
29
+
30
+ When /^I mark position (\d+)$/ do |position|
31
+ @game.mark position.to_i
32
+ end
33
+
34
+ Then /^board\(:ttt\) will be "([^"]*)"$/ do |board|
35
+ @game.board(:ttt).should == board.gsub('\\n', "\n")
36
+ end
37
+
38
+ Then /^the game is (not )?over$/ do |not_over|
39
+ if not_over
40
+ @game.should_not be_over
41
+ else
42
+ @game.should be_over
43
+ end
44
+ end
45
+
46
+ When /^I ask who is at position (\d+), it returns (\w+)$/ do |position, player|
47
+ player = player.to_i
48
+ player = nil if player.zero?
49
+ @game[position.to_i].should == player
50
+ end
51
+
52
+ Then /^player(\d+) (wins|loses|ties)$/ do |player, status|
53
+ @game.status(player.to_i).should == status.to_sym
54
+ end
55
+
56
+ Given /^a tie game$/ do
57
+ @game = TTT::Game.new '121221112'
58
+ end
59
+
60
+ When /^a computer player as player (\d+)$/ do |current_turn|
61
+ @game.turn.should == current_turn.to_i
62
+ end
63
+
64
+ Then /^the computer moves to one of the following: ((?:\d+ ?)+)$/ do |possible_boards|
65
+ @computer = TTT::ComputerPlayer.new @game
66
+ @computer.take_turn
67
+ possible_boards.split.should include @game.board
68
+ end
69
+
70
+ Given /^I have a computer player$/ do
71
+ @computer = TTT::ComputerPlayer.new @game
72
+ end
73
+
74
+ Then /^the computer will play for (\d+)$/ do |player_number|
75
+ player_number = player_number.to_i
76
+ @game.turn.should == player_number
77
+ @computer.player_number.should == player_number
78
+ @computer.take_turn
79
+ @game.turn.should_not == player_number
80
+ @computer.player_number.should_not == player_number.to_i
81
+ end
82
+
@@ -0,0 +1,8 @@
1
+ require 'bundler/bouncer'
2
+
3
+ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
4
+
5
+ # lib files
6
+ require 'ttt'
7
+ require 'ttt/computer_player'
8
+ require 'ttt/binary'
@@ -0,0 +1,13 @@
1
+ Feature: View board as developer
2
+
3
+ In order to be able to do exploratory testing
4
+ As a developer
5
+ I want to be able to view the board in standard tic-tac-toe format
6
+
7
+ Scenario: View board in tic-tac-toe format
8
+ Given I create a game with "abcdefghi"
9
+ Then board(:ttt) will be " a | b | c \n----|---|----\n d | e | f \n----|---|----\n g | h | i "
10
+
11
+ Scenario: Viewing a board with spaces no one has moved into
12
+ Given I create a game with "120000000"
13
+ Then board(:ttt) will be " 1 | 2 | \n----|---|----\n | | \n----|---|----\n | | "
@@ -0,0 +1,9 @@
1
+ Feature: View board as tester
2
+
3
+ In order to verify game state
4
+ As a tester
5
+ I want to view the board as a simple string
6
+
7
+ Scenario: View board in string format
8
+ Given I create a game with "abcdefghi"
9
+ Then the game board is "abcdefghi"
@@ -0,0 +1 @@
1
+ require 'ttt/game'
@@ -0,0 +1,96 @@
1
+ require 'optparse'
2
+
3
+ require 'ttt/interface'
4
+
5
+ module TTT
6
+
7
+ # The code for ttt/bin
8
+ class Binary
9
+
10
+ attr_accessor :filein, :fileout, :fileerr
11
+
12
+ def initialize(argv, io={})
13
+ self.fileout = io.fetch :fileout, $stdout
14
+ self.fileerr = io.fetch :fileerr, $stderr
15
+ self.filein = io.fetch :filein, $stdin
16
+ parse argv
17
+ end
18
+
19
+ def parse(argv)
20
+ argv = ['-h'] if argv.empty?
21
+ Parser.new(self).parse argv
22
+ rescue OptionParser::MissingArgument => e
23
+ fileerr.puts e.message
24
+ Kernel.exit 1
25
+ end
26
+
27
+ def has_interface?(interface_name)
28
+ TTT::Interface.registered? interface_name
29
+ end
30
+
31
+ def list_of_registered
32
+ TTT::Interface.registered_names.map(&:inspect).join(', ')
33
+ end
34
+
35
+ def interface(interface_name)
36
+ TTT::Interface.registered[interface_name]
37
+ end
38
+
39
+
40
+
41
+ class Parser
42
+
43
+ attr_accessor :binary
44
+
45
+ def initialize(binary)
46
+ self.binary = binary
47
+ end
48
+
49
+ def method_missing(meth, *args, &block)
50
+ super unless binary.respond_to? meth
51
+ binary.send meth, *args, &block
52
+ end
53
+
54
+ def parse(argv)
55
+ options.parse argv
56
+ end
57
+
58
+ def options
59
+ OptionParser.new do |options|
60
+ define_banner options
61
+ define_interface options
62
+ define_help options
63
+ end
64
+ end
65
+
66
+ def options_for_interface
67
+ return :filein => filein, :fileout => fileout, :fileerr => fileerr
68
+ end
69
+
70
+ def define_banner(options)
71
+ options.banner = "Usage: ttt --interface interface_name\n" \
72
+ "ttt is an implementation of Tic Tac Toe by Josh Cheek\n\n"
73
+ end
74
+
75
+ def define_interface(options)
76
+ options.on '-i', '--interface TYPE', "Specify which interface to play on. Select from: #{list_of_registered}" do |interface_name|
77
+ if interface_name.equal? true
78
+ fileerr.puts "Please supply interface type"
79
+ Kernel.exit 1
80
+ elsif has_interface? interface_name
81
+ interface(interface_name).new(options_for_interface).play
82
+ else
83
+ fileerr.puts "#{interface_name.inspect} is not a valid interface, select from: #{list_of_registered}"
84
+ end
85
+ end
86
+ end
87
+
88
+ def define_help(options)
89
+ options.on '-h', '--help', 'Display this screen' do
90
+ fileout.puts options
91
+ end
92
+ end
93
+ end
94
+
95
+ end
96
+ end