ttt 1.0.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,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