tic_tac_toe_mchliakh 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +7 -0
- data/lib/tic_tac_toe_mchliakh/board/board.rb +77 -0
- data/lib/tic_tac_toe_mchliakh/board/line.rb +51 -0
- data/lib/tic_tac_toe_mchliakh/board/lines.rb +31 -0
- data/lib/tic_tac_toe_mchliakh/board/square.rb +55 -0
- data/lib/tic_tac_toe_mchliakh/board/squares.rb +23 -0
- data/lib/tic_tac_toe_mchliakh/players/computer.rb +44 -0
- data/lib/tic_tac_toe_mchliakh/players/player.rb +11 -0
- data/lib/tic_tac_toe_mchliakh/version.rb +3 -0
- data/lib/tic_tac_toe_mchliakh.rb +29 -0
- data/spec/board/board_spec.rb +31 -0
- data/spec/board/line_spec.rb +55 -0
- data/spec/board/lines_spec.rb +49 -0
- data/spec/board/square_spec.rb +62 -0
- data/spec/board/squares_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/tic_tac_toe_spec.rb +15 -0
- data/tic_tac_toe_mchliakh.gemspec +24 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f0630392041b9d59eb4b0774816016ea84e30272
|
|
4
|
+
data.tar.gz: b4b13cc7ee428545521ae95dff0e3388d39bd0db
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1e7b9a00a99008344992afb5fc554ba34dd90c15217997e9449602490484e723a428aa0f71e189927434353933547095b73c5585e37fc8f8fcb9cea3ae2694c0
|
|
7
|
+
data.tar.gz: 92c7ef80064c5d886d8580adbbe87fb064dc24caec63e666bf1cd9f68aa5c5e24370681339a58bfb7bd2ee8f03b55cc03173a804f60613e244438845e0561bd0
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015 Mikhail Chliakhovski
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Tic-tac-toe
|
|
2
|
+
|
|
3
|
+
A Tic-tac-toe game that never loses.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'tic_tac_toe_mchliakh'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install tic_tac_toe_mchliakh
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
class MovesController < ActionController::Base
|
|
25
|
+
def make
|
|
26
|
+
game = if params[:game_id]
|
|
27
|
+
Game.new
|
|
28
|
+
else
|
|
29
|
+
Game.find(params[:game_id])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
render json: TicTacToeMchliakh.move(params[:square], game.board)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
1. Fork it ( https://github.com/[my-github-username]/tic_tac_toe_mchliakh/fork )
|
|
38
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
39
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
40
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
41
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class Board
|
|
3
|
+
attr_reader :squares, :lines, :winner
|
|
4
|
+
|
|
5
|
+
def initialize(saved=nil)
|
|
6
|
+
setup do
|
|
7
|
+
if saved
|
|
8
|
+
saved.map.with_index do |s, n|
|
|
9
|
+
squares << Square.new(self, n + 1, s)
|
|
10
|
+
end
|
|
11
|
+
else
|
|
12
|
+
9.times {|n| squares << Square.new(self, n + 1) }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def square(number)
|
|
18
|
+
unless (1..9).include?(number)
|
|
19
|
+
raise RangeError, 'Choose a number between 1 and 9'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
squares[number - 1]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def game_over?
|
|
26
|
+
!!@game_over
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def square_taken
|
|
30
|
+
game_over if squares.all_taken?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def three_in_a_row(player)
|
|
34
|
+
@winner = player
|
|
35
|
+
game_over
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def serialize
|
|
39
|
+
squares.map(&:serialize)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def setup
|
|
45
|
+
@squares = Squares.new
|
|
46
|
+
yield
|
|
47
|
+
build_lines
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_lines
|
|
51
|
+
@lines = Lines.new
|
|
52
|
+
|
|
53
|
+
lines_of_three.each do |lot|
|
|
54
|
+
lines << Line.new(self,
|
|
55
|
+
Squares.new(lot.map {|s| squares[s] })
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def lines_of_three
|
|
61
|
+
[
|
|
62
|
+
[0, 1, 2],
|
|
63
|
+
[2, 5, 8],
|
|
64
|
+
[6, 7, 8],
|
|
65
|
+
[0, 3, 6],
|
|
66
|
+
[1, 4, 7],
|
|
67
|
+
[3, 4, 5],
|
|
68
|
+
[0, 4, 8],
|
|
69
|
+
[2, 4, 6]
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def game_over
|
|
74
|
+
@game_over = true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class Line
|
|
3
|
+
attr_reader :squares
|
|
4
|
+
|
|
5
|
+
def initialize(board, squares)
|
|
6
|
+
@board = board
|
|
7
|
+
@squares = squares
|
|
8
|
+
listen
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def &(other)
|
|
12
|
+
squares & other.squares
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def can_win?(player)
|
|
16
|
+
squares.taken_by(player).count == 2 &&
|
|
17
|
+
squares.empty.one?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def can_lose?(player)
|
|
21
|
+
squares.taken_by_opponent(player).count == 2 &&
|
|
22
|
+
squares.empty.one?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def could_win?(player)
|
|
26
|
+
squares.taken_by(player).one? &&
|
|
27
|
+
squares.empty.count == 2
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def could_lose?(player)
|
|
31
|
+
squares.taken_by_opponent(player).one? &&
|
|
32
|
+
squares.empty.count == 2
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def square_taken
|
|
36
|
+
if three_in_a_row?
|
|
37
|
+
@board.three_in_a_row(squares.first.player)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def three_in_a_row?
|
|
44
|
+
squares.all_taken? && squares.all_equal?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def listen
|
|
48
|
+
squares.each {|s| s << self }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class Lines < Array
|
|
3
|
+
def can_win(player)
|
|
4
|
+
select {|l| l.can_win?(player) }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def can_lose(player)
|
|
8
|
+
select {|l| l.can_lose?(player) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def could_win(player)
|
|
12
|
+
select {|l| l.could_win?(player) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def could_lose(player)
|
|
16
|
+
select {|l| l.could_lose?(player) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def squares_that_can_fork(player)
|
|
20
|
+
Squares.new(could_win(player).combination(2).map do |ll|
|
|
21
|
+
(ll.first & ll.last).first
|
|
22
|
+
end.compact)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def squares_that_can_be_forked(player)
|
|
26
|
+
Squares.new(could_lose(player).combination(2).map do |ll|
|
|
27
|
+
(ll.first & ll.last).first
|
|
28
|
+
end.compact)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class IllegalMoveError < StandardError; end
|
|
3
|
+
|
|
4
|
+
class Square
|
|
5
|
+
attr_reader :player, :number
|
|
6
|
+
|
|
7
|
+
def initialize(board, number, saved=nil)
|
|
8
|
+
@board = board
|
|
9
|
+
@number = number
|
|
10
|
+
@player = saved
|
|
11
|
+
@lines = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def ==(other)
|
|
15
|
+
other.player == player
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <<(line)
|
|
19
|
+
@lines << line
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def line_count
|
|
23
|
+
@lines.size
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def take(player)
|
|
27
|
+
raise IllegalMoveError, 'Square already taken' unless empty?
|
|
28
|
+
|
|
29
|
+
@player = player
|
|
30
|
+
notify
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def empty?
|
|
34
|
+
player.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def taken_by?(player)
|
|
38
|
+
self.player == player
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def taken_by_opponent?(player)
|
|
42
|
+
!empty? && !taken_by?(player)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def serialize
|
|
46
|
+
player
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def notify
|
|
52
|
+
([@board] + @lines).map(&:square_taken)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class Squares < Array
|
|
3
|
+
def empty
|
|
4
|
+
select(&:empty?)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def taken_by(player)
|
|
8
|
+
select {|s| s.taken_by?(player) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def taken_by_opponent(player)
|
|
12
|
+
select {|s| s.taken_by_opponent?(player) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def all_taken?
|
|
16
|
+
none?(&:empty?)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def all_equal?
|
|
20
|
+
all? {|s| s == first }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module TicTacToeMchliakh
|
|
2
|
+
class Computer < Player
|
|
3
|
+
def next_move
|
|
4
|
+
line = any_winning_line || any_losing_line
|
|
5
|
+
|
|
6
|
+
if line
|
|
7
|
+
line.squares.empty.sample
|
|
8
|
+
else
|
|
9
|
+
forks.sample || sticky_situation || @board.squares.empty.max_by(&:line_count)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def any_winning_line
|
|
16
|
+
@board.lines.can_win(@number).sample
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def any_losing_line
|
|
20
|
+
@board.lines.can_lose(@number).sample
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def forks
|
|
24
|
+
@board.lines.squares_that_can_fork(@number).empty
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def opponent_forks
|
|
28
|
+
@board.lines.squares_that_can_be_forked(@number).empty
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sticky_situation
|
|
32
|
+
_opponent_forks = opponent_forks
|
|
33
|
+
|
|
34
|
+
if opponent_forks.size > 1
|
|
35
|
+
@board.lines.could_win(@number).each do |l|
|
|
36
|
+
non_fork = (l.squares.empty - (l.squares & _opponent_forks)).first
|
|
37
|
+
return (l.squares.empty - [non_fork]).first
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
opponent_forks.first
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'tic_tac_toe_mchliakh/board/square'
|
|
2
|
+
require 'tic_tac_toe_mchliakh/board/squares'
|
|
3
|
+
require 'tic_tac_toe_mchliakh/board/line'
|
|
4
|
+
require 'tic_tac_toe_mchliakh/board/lines'
|
|
5
|
+
require 'tic_tac_toe_mchliakh/board/board'
|
|
6
|
+
require 'tic_tac_toe_mchliakh/players/player'
|
|
7
|
+
require 'tic_tac_toe_mchliakh/players/computer'
|
|
8
|
+
|
|
9
|
+
module TicTacToeMchliakh
|
|
10
|
+
def self.move(square, saved_board=nil)
|
|
11
|
+
board = Board.new(saved_board)
|
|
12
|
+
board.square(square).take(1)
|
|
13
|
+
|
|
14
|
+
if board.game_over?
|
|
15
|
+
return { board: board.serialize, winner: board.winner }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
computer = Computer.new(board, 0)
|
|
19
|
+
computer.next_move.take(0)
|
|
20
|
+
|
|
21
|
+
if board.game_over?
|
|
22
|
+
{ board: board.serialize, winner: board.winner }
|
|
23
|
+
else
|
|
24
|
+
{ board: board.serialize }
|
|
25
|
+
end
|
|
26
|
+
rescue IllegalMoveError => e
|
|
27
|
+
{ error: e.message }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe Board do
|
|
5
|
+
before do
|
|
6
|
+
@board = Board.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'gets a square' do
|
|
10
|
+
expect(@board.square(1).number).to eq(1)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'loads itself' do
|
|
14
|
+
saved = Board.new(Array.new(9, 123))
|
|
15
|
+
expect(saved.squares).to be_all_taken
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'raises an error when square is not 1 to 9' do
|
|
19
|
+
expect {
|
|
20
|
+
@board.square(0).to raise_error(RangeError)
|
|
21
|
+
}
|
|
22
|
+
expect {
|
|
23
|
+
@board.square(10).to raise_error(RangeError)
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'serializes itself' do
|
|
28
|
+
expect(@board.serialize).to eq(Array.new(9))
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe Line do
|
|
5
|
+
before do
|
|
6
|
+
@square = Square.new(nil, 0, 123)
|
|
7
|
+
@line = Line.new(nil, Squares.new([@square]))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'takes an intresection' do
|
|
11
|
+
other = Line.new(nil, Squares.new([@square]))
|
|
12
|
+
expect((@line & other).size).to be(1)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'knows when a player can win' do
|
|
16
|
+
winning_line = Line.new(
|
|
17
|
+
nil, Squares.new([@square, @square, Square.new(nil, 0)])
|
|
18
|
+
)
|
|
19
|
+
expect(winning_line.can_win?(123)).to be(true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'knows when an player can lose' do
|
|
23
|
+
winning_line = Line.new(
|
|
24
|
+
nil, Squares.new([@square, @square, Square.new(nil, 0)])
|
|
25
|
+
)
|
|
26
|
+
expect(winning_line.can_lose?(321)).to be(true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'knows when a player could win' do
|
|
30
|
+
empty_square = Square.new(nil, 0)
|
|
31
|
+
maybe_line = Line.new(
|
|
32
|
+
nil, Squares.new([@square, empty_square, empty_square])
|
|
33
|
+
)
|
|
34
|
+
expect(maybe_line.could_win?(123)).to be(true)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'knows when a player could lose' do
|
|
38
|
+
empty_square = Square.new(nil, 0)
|
|
39
|
+
maybe_line = Line.new(
|
|
40
|
+
nil, Squares.new([@square, empty_square, empty_square])
|
|
41
|
+
)
|
|
42
|
+
expect(maybe_line.could_lose?(321)).to be(true)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'notifies board when three in a row' do
|
|
46
|
+
board = double()
|
|
47
|
+
expect(board).to receive(:three_in_a_row).with(123)
|
|
48
|
+
|
|
49
|
+
three_in_a_row = Line.new(
|
|
50
|
+
board, Squares.new([@square, @square, @square])
|
|
51
|
+
)
|
|
52
|
+
three_in_a_row.square_taken
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe Lines do
|
|
5
|
+
before do
|
|
6
|
+
@board = Board.new([
|
|
7
|
+
0, nil, 0,
|
|
8
|
+
nil, nil, nil,
|
|
9
|
+
nil, nil, nil
|
|
10
|
+
])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'finds winning lines' do
|
|
14
|
+
expect(@board.lines.can_win(0).count).to be(1)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'finds losing lines' do
|
|
18
|
+
expect(@board.lines.can_lose(1).count).to be(1)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'finds potentially winning lines' do
|
|
22
|
+
expect(@board.lines.could_win(0).count).to be(4)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'finds potentially losing lines' do
|
|
26
|
+
expect(@board.lines.could_lose(1).count).to be(4)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'opponent can fork' do
|
|
30
|
+
before do
|
|
31
|
+
@board = Board.new([
|
|
32
|
+
0, nil, nil,
|
|
33
|
+
nil, 1, 0,
|
|
34
|
+
nil, nil, nil
|
|
35
|
+
])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'finds squares that can fork' do
|
|
39
|
+
squares_that_can_fork = @board.lines.squares_that_can_fork(0)
|
|
40
|
+
expect(squares_that_can_fork.empty.count).to be(1)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'finds squares that can be forked' do
|
|
44
|
+
squares_that_can_be_forked = @board.lines.squares_that_can_be_forked(1)
|
|
45
|
+
expect(squares_that_can_be_forked.empty.count).to be(1)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe Square do
|
|
5
|
+
before do
|
|
6
|
+
@board = double()
|
|
7
|
+
allow(@board).to receive(:square_taken)
|
|
8
|
+
|
|
9
|
+
@square = Square.new(@board, 0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'is empty when initialized' do
|
|
13
|
+
expect(@square).to be_empty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'loads itself' do
|
|
17
|
+
saved = Square.new(@board, 0, 123)
|
|
18
|
+
expect(saved.player).to be(123)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'compares itself' do
|
|
22
|
+
other = Square.new(@board, 0)
|
|
23
|
+
expect(@square == other).to be(true)
|
|
24
|
+
|
|
25
|
+
@square.take(123)
|
|
26
|
+
expect(@square == other).to be(false)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'adds lines' do
|
|
30
|
+
expect(@square.line_count).to be(0)
|
|
31
|
+
@square << nil
|
|
32
|
+
expect(@square.line_count).to be(1)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'notifies board when taken' do
|
|
36
|
+
expect(@board).to receive(:square_taken)
|
|
37
|
+
@square.take(123)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'can only be taken once' do
|
|
41
|
+
@square.take(123)
|
|
42
|
+
expect {
|
|
43
|
+
@square.take(321)
|
|
44
|
+
}.to raise_error(IllegalMoveError)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'knows when taken by player' do
|
|
48
|
+
@square.take(123)
|
|
49
|
+
expect(@square.taken_by?(123)).to be(true)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'knows when taken by opponent' do
|
|
53
|
+
@square.take(321)
|
|
54
|
+
expect(@square.taken_by_opponent?(123)).to be(true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'serializes itself' do
|
|
58
|
+
@square.take(123)
|
|
59
|
+
expect(@square.serialize).to be(123)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe Squares do
|
|
5
|
+
before do
|
|
6
|
+
@squares = Squares.new
|
|
7
|
+
[nil, 123, 321].each do |p|
|
|
8
|
+
@squares << Square.new(nil, 0, p)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'finds empty squares' do
|
|
13
|
+
expect(@squares.empty.count).to be(1)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'finds squares taken by player' do
|
|
17
|
+
expect(@squares.taken_by(123).count).to be(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'finds squares taken by opponent' do
|
|
21
|
+
expect(@squares.taken_by_opponent(123).count).to be(1)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'knows when all squares are taken' do
|
|
25
|
+
@squares[0] = Square.new(nil, 0, 123)
|
|
26
|
+
expect(@squares).to be_all_taken
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'knows when all squares are equal' do
|
|
30
|
+
@squares[0] = @squares[2] = Square.new(nil, 0, 123)
|
|
31
|
+
expect(@squares).to be_all_equal
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'tic_tac_toe_mchliakh'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module TicTacToeMchliakh
|
|
4
|
+
describe TicTacToeMchliakh do
|
|
5
|
+
it 'makes a move' do
|
|
6
|
+
expect(TicTacToeMchliakh.move(1)[:board].compact.sort)
|
|
7
|
+
.to eq([0, 1])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'returns an error when desired square is taken' do
|
|
11
|
+
expect(TicTacToeMchliakh.move(1, Array.new(9, 0)))
|
|
12
|
+
.to have_key(:error)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'tic_tac_toe_mchliakh/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'tic_tac_toe_mchliakh'
|
|
8
|
+
s.version = TicTacToeMchliakh::VERSION
|
|
9
|
+
s.authors = ['Mikhail Chliakhovski']
|
|
10
|
+
s.email = ['mchliakh.dev@gmail.com']
|
|
11
|
+
s.summary = 'Tic-tac-toe'
|
|
12
|
+
s.description = 'A Tic-tac-toe game that never loses'
|
|
13
|
+
s.homepage = 'http://github.com/mchliakh/tic_tac_toe'
|
|
14
|
+
s.license = 'MIT'
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files -z`.split("\x0")
|
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
s.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
s.add_development_dependency 'bundler', '~> 1.7'
|
|
22
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
|
23
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tic_tac_toe_mchliakh
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mikhail Chliakhovski
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-02-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
description: A Tic-tac-toe game that never loses
|
|
56
|
+
email:
|
|
57
|
+
- mchliakh.dev@gmail.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".gitignore"
|
|
63
|
+
- Gemfile
|
|
64
|
+
- LICENSE.txt
|
|
65
|
+
- README.md
|
|
66
|
+
- Rakefile
|
|
67
|
+
- lib/tic_tac_toe_mchliakh.rb
|
|
68
|
+
- lib/tic_tac_toe_mchliakh/board/board.rb
|
|
69
|
+
- lib/tic_tac_toe_mchliakh/board/line.rb
|
|
70
|
+
- lib/tic_tac_toe_mchliakh/board/lines.rb
|
|
71
|
+
- lib/tic_tac_toe_mchliakh/board/square.rb
|
|
72
|
+
- lib/tic_tac_toe_mchliakh/board/squares.rb
|
|
73
|
+
- lib/tic_tac_toe_mchliakh/players/computer.rb
|
|
74
|
+
- lib/tic_tac_toe_mchliakh/players/player.rb
|
|
75
|
+
- lib/tic_tac_toe_mchliakh/version.rb
|
|
76
|
+
- spec/board/board_spec.rb
|
|
77
|
+
- spec/board/line_spec.rb
|
|
78
|
+
- spec/board/lines_spec.rb
|
|
79
|
+
- spec/board/square_spec.rb
|
|
80
|
+
- spec/board/squares_spec.rb
|
|
81
|
+
- spec/spec_helper.rb
|
|
82
|
+
- spec/tic_tac_toe_spec.rb
|
|
83
|
+
- tic_tac_toe_mchliakh.gemspec
|
|
84
|
+
homepage: http://github.com/mchliakh/tic_tac_toe
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata: {}
|
|
88
|
+
post_install_message:
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubyforge_project:
|
|
104
|
+
rubygems_version: 2.4.5
|
|
105
|
+
signing_key:
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: Tic-tac-toe
|
|
108
|
+
test_files:
|
|
109
|
+
- spec/board/board_spec.rb
|
|
110
|
+
- spec/board/line_spec.rb
|
|
111
|
+
- spec/board/lines_spec.rb
|
|
112
|
+
- spec/board/square_spec.rb
|
|
113
|
+
- spec/board/squares_spec.rb
|
|
114
|
+
- spec/spec_helper.rb
|
|
115
|
+
- spec/tic_tac_toe_spec.rb
|