ruby-go 0.1.1 → 0.2.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 +4 -4
- data/lib/ruby-go/board.rb +83 -0
- data/lib/ruby-go/game.rb +126 -0
- data/lib/ruby-go/stone.rb +104 -0
- data/lib/ruby-go/version.rb +3 -0
- data/lib/ruby-go.rb +7 -4
- data/test/go_test.rb +138 -134
- data/test/sgf_test.rb +43 -42
- metadata +5 -4
- data/lib/board.rb +0 -82
- data/lib/game.rb +0 -116
- data/lib/stone.rb +0 -101
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99c7a4ed50ed990d7029979e84ff6c18338577f2
|
4
|
+
data.tar.gz: 12c6da43266d2267faae29bfe7b121589e5c85d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7453628a08d3b1f1cf25e39fa2494eaeb085af2e1f986974467f2e51040c58ddd5a351e93fc363a4011cb0e3c9001aa0c48d918afc3a70ae52f299bcfb02dc0b
|
7
|
+
data.tar.gz: 79eaf22114887997f545b5877ab3dcd19bc801b9fc8dd4b9e96d039d83e81400959a86b10ba1fec87b259f4a1b48d02d71f2bd21a4713114520addf51223993f
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module RubyGo
|
2
|
+
class Board
|
3
|
+
Colors = {black: 'x', white: 'o', empty: '_'}
|
4
|
+
|
5
|
+
attr_accessor :board
|
6
|
+
def initialize(size)
|
7
|
+
@board = []
|
8
|
+
size -= 1
|
9
|
+
|
10
|
+
0.upto(size) do |y|
|
11
|
+
row = []
|
12
|
+
0.upto(size) do |x|
|
13
|
+
row << Liberty.new(x, y)
|
14
|
+
end
|
15
|
+
@board << row
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def size
|
20
|
+
@board.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
!@board.flatten.any? do |s|
|
25
|
+
!s.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def at(x, y)
|
30
|
+
@board[y][x]
|
31
|
+
end
|
32
|
+
|
33
|
+
def around(x, y = :y_not_given)
|
34
|
+
x, y = x.to_coord if x.kind_of?(Stone)
|
35
|
+
stones = []
|
36
|
+
stones << at(x-1, y) unless x == 0
|
37
|
+
stones << at(x+1, y) unless x == (@board.length - 1)
|
38
|
+
stones << at(x, y-1) unless y == 0
|
39
|
+
stones << at(x, y+1) unless y == (@board.length - 1)
|
40
|
+
stones
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove(stone)
|
44
|
+
stone.remove_from_board(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def place(stone)
|
48
|
+
stone.place_on_board(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def liberties(stone)
|
52
|
+
group_of(stone).inject(0) do |libs, stn|
|
53
|
+
libs + stn.liberties(self).count
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def group_of(stone)
|
58
|
+
stone.group(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_str
|
62
|
+
out = ""
|
63
|
+
if @board.length < 11
|
64
|
+
out << "\s\s\s#{(0..@board.length - 1).to_a.join(' ')}\n"
|
65
|
+
else
|
66
|
+
out <<
|
67
|
+
"\s\s\s#{(0..10).to_a.join(' ')}" <<
|
68
|
+
"#{(11..@board.length - 1).to_a.join('')}\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
@board.each_with_index do |row, i|
|
72
|
+
i = "\s#{i}" if i < 10
|
73
|
+
out << "#{i}\s#{(row.collect {|stn| stn.to_s}).join(' ')}\n"
|
74
|
+
end
|
75
|
+
|
76
|
+
out
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
to_str
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/ruby-go/game.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
module RubyGo
|
2
|
+
class Game
|
3
|
+
|
4
|
+
LETTERS = ('a'..'z').to_a
|
5
|
+
|
6
|
+
attr_reader :board
|
7
|
+
def initialize(board: 19)
|
8
|
+
@board = Board.new(board)
|
9
|
+
@moves = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(name="my_go_game")
|
13
|
+
tree = SGF::Parser.new.parse(to_sgf)
|
14
|
+
tree.save(name + '.sgf')
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_sgf
|
18
|
+
sgf = "(;GM[1]FF[4]CA[UTF-8]AP[jphager2]SZ[#{board.size}]PW[White]PB[Black]"
|
19
|
+
|
20
|
+
@moves.each do |move|
|
21
|
+
sgf << move[:stone].to_sgf
|
22
|
+
end
|
23
|
+
|
24
|
+
sgf << ')'
|
25
|
+
end
|
26
|
+
|
27
|
+
def view
|
28
|
+
puts @board.to_s
|
29
|
+
puts " " + "_"*(@board.size * 2)
|
30
|
+
print " Prisoners || White: #{captures[:black]} |"
|
31
|
+
puts " Black: #{captures[:white]}"
|
32
|
+
puts " " + "-"*(@board.size * 2)
|
33
|
+
end
|
34
|
+
|
35
|
+
def black(x, y)
|
36
|
+
play(BlackStone.new(x,y))
|
37
|
+
end
|
38
|
+
|
39
|
+
def white(x, y)
|
40
|
+
play(WhiteStone.new(x,y))
|
41
|
+
end
|
42
|
+
|
43
|
+
def black_pass
|
44
|
+
pass(:black)
|
45
|
+
end
|
46
|
+
|
47
|
+
def white_pass
|
48
|
+
pass(:white)
|
49
|
+
end
|
50
|
+
|
51
|
+
def pass(color)
|
52
|
+
@moves << {stone: NullStone.new(color), captures: [], pass: true}
|
53
|
+
end
|
54
|
+
|
55
|
+
def undo
|
56
|
+
move = @moves.pop
|
57
|
+
@board.remove(move[:stone])
|
58
|
+
move[:captures].each {|stone| @board.place(stone)}
|
59
|
+
end
|
60
|
+
|
61
|
+
def passes
|
62
|
+
@moves.inject(0) {|total, move| move[:pass] ? total + 1 : 0}
|
63
|
+
end
|
64
|
+
|
65
|
+
def captures
|
66
|
+
@moves.each_with_object({black: 0, white: 0}) do |move, total|
|
67
|
+
move[:captures].each do |capture|
|
68
|
+
total[capture.color] += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def play(stone)
|
76
|
+
@board.place(stone)
|
77
|
+
@moves << {stone: stone, captures: [], pass: false}
|
78
|
+
|
79
|
+
capture; suicide; ko
|
80
|
+
end
|
81
|
+
|
82
|
+
def ko
|
83
|
+
return if @moves.length < 2 or !@moves[-2][:captures]
|
84
|
+
|
85
|
+
captures = @moves[-2][:captures]
|
86
|
+
stone = @moves.last[:stone]
|
87
|
+
|
88
|
+
if captures == [stone]
|
89
|
+
undo
|
90
|
+
raise IllegalMove,
|
91
|
+
"You cannot capture the ko, play a ko threat first"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def suicide
|
96
|
+
stone = @moves.last[:stone]
|
97
|
+
unless @board.liberties(stone) > 0
|
98
|
+
undo
|
99
|
+
raise IllegalMove, "You cannot play a suicide."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def capture
|
104
|
+
stone = @moves.last[:stone]
|
105
|
+
stones_around = @board.around(stone)
|
106
|
+
|
107
|
+
captures = stones_around.select {|stn| @board.liberties(stn) == 0}
|
108
|
+
|
109
|
+
captures.each {|stone| capture_group(stone)}
|
110
|
+
end
|
111
|
+
|
112
|
+
def capture_group(stone)
|
113
|
+
@board.group_of(stone).each {|stone| capture_stone(stone)}
|
114
|
+
end
|
115
|
+
|
116
|
+
def capture_stone(stone)
|
117
|
+
@moves.last[:captures] << stone
|
118
|
+
@board.remove(stone)
|
119
|
+
end
|
120
|
+
|
121
|
+
public
|
122
|
+
|
123
|
+
class IllegalMove < Exception
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module RubyGo
|
2
|
+
class Stone
|
3
|
+
|
4
|
+
attr_reader :color
|
5
|
+
def initialize(x, y)
|
6
|
+
@x, @y = x, y
|
7
|
+
@color = :none
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_sgf
|
11
|
+
x = Game::LETTERS[@x]
|
12
|
+
y = Game::LETTERS[@y]
|
13
|
+
";#{color.to_s[0].upcase}[#{x+y}]"
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
@color == :empty
|
18
|
+
end
|
19
|
+
|
20
|
+
def place_on_board(board)
|
21
|
+
unless board.at(@x, @y).empty?
|
22
|
+
raise Game::IllegalMove,
|
23
|
+
"You cannot place a stone on top of another stone."
|
24
|
+
end
|
25
|
+
board.board[@y][@x] = self
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_from_board(board)
|
29
|
+
board.board[@y][@x] = Liberty.new(*self.to_coord)
|
30
|
+
end
|
31
|
+
|
32
|
+
def liberties(board)
|
33
|
+
board.around(self).select {|stone| stone.empty?}
|
34
|
+
end
|
35
|
+
|
36
|
+
def group(board, stones = [])
|
37
|
+
return stones if stones.any? {|stone| stone.eql?(self)}
|
38
|
+
stones << self
|
39
|
+
|
40
|
+
board.around(self).each do |stone|
|
41
|
+
if stone.color == @color
|
42
|
+
stone.group(board, stones)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
stones
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_coord
|
50
|
+
[@x, @y]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_str
|
54
|
+
Board::Colors[@color]
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
to_str
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
(self.color == other.color) and (self.to_coord == other.to_coord)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Liberty < Stone
|
67
|
+
def initialize(x, y)
|
68
|
+
super
|
69
|
+
@color = :empty
|
70
|
+
end
|
71
|
+
|
72
|
+
def liberties(board)
|
73
|
+
[self]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class BlackStone < Stone
|
78
|
+
def initialize(x, y)
|
79
|
+
super
|
80
|
+
@color = :black
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class WhiteStone < Stone
|
85
|
+
def initialize(x, y)
|
86
|
+
super
|
87
|
+
@color = :white
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class NullStone < Stone
|
92
|
+
def initialize(color = :empty)
|
93
|
+
@x, @y = nil, nil
|
94
|
+
@color = color
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove_from_board(board)
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_sgf
|
101
|
+
";#{color.to_s[0].upcase}[]"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/ruby-go.rb
CHANGED
data/test/go_test.rb
CHANGED
@@ -1,161 +1,165 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
require 'stringio'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module RubyGo
|
5
|
+
class GoTest < Minitest::Test
|
6
|
+
def setup
|
7
|
+
b,w = Board::Colors[:black], Board::Colors[:white]
|
8
|
+
e = Board::Colors[:empty]
|
8
9
|
|
9
|
-
|
10
|
+
@row_9_str = "_ _ _ _ _ _ _ _ _\n"
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
@game = Game.new(board: 9)
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def test_game_has_blank_board_when_initialized
|
16
|
+
assert @game.board.empty?
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
def test_empty_board_looks_like_empty_board
|
20
|
+
assert_includes @game.board.to_s, @row_9_str
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
def test_game_can_print_the_board
|
24
|
+
out = StringIO.new
|
25
|
+
temp = $stdout
|
26
|
+
$stdout = out
|
27
|
+
@game.view
|
28
|
+
$stdout = temp
|
29
|
+
out.rewind
|
30
|
+
assert_includes out.read, @row_9_str
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
def test_game_can_place_a_stone
|
34
|
+
game = Game.new(board: 9)
|
35
|
+
game.black(2,2)
|
36
|
+
assert_equal BlackStone.new(2,2), game.board.at(2,2)
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def test_cannot_place_a_stone_on_top_of_another_stone
|
40
|
+
game = Game.new(board: 9)
|
41
|
+
game.black(2,2)
|
42
|
+
assert_raises(Game::IllegalMove) do
|
43
|
+
game.white(2,2)
|
44
|
+
end
|
43
45
|
end
|
44
|
-
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
47
|
+
def test_can_capture_one_stone
|
48
|
+
game = Game.new(board: 9)
|
49
|
+
game.black(2,2)
|
50
|
+
game.white(2,1)
|
51
|
+
game.white(2,3)
|
52
|
+
game.white(1,2)
|
53
|
+
game.white(3,2)
|
54
|
+
assert_equal 1, game.captures[:black]
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
57
|
+
def test_capture_removes_stone
|
58
|
+
game = Game.new(board: 9)
|
59
|
+
game.black(2,2)
|
60
|
+
game.white(2,1)
|
61
|
+
game.white(2,3)
|
62
|
+
game.white(1,2)
|
63
|
+
game.white(3,2)
|
64
|
+
assert_equal Liberty.new(2,2), game.board.at(2,2)
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
67
|
+
def test_capture_three_stones
|
68
|
+
game = Game.new(board: 9)
|
69
|
+
game.black(2,1)
|
70
|
+
game.black(2,2)
|
71
|
+
game.black(2,3)
|
72
|
+
game.white(2,0)
|
73
|
+
game.white(2,4)
|
74
|
+
game.white(1,1)
|
75
|
+
game.white(1,2)
|
76
|
+
game.white(1,3)
|
77
|
+
game.white(3,1)
|
78
|
+
game.white(3,2)
|
79
|
+
game.white(3,3)
|
80
|
+
assert_equal 3, game.captures[:black]
|
81
|
+
end
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
def test_can_undo
|
84
|
+
game = Game.new()
|
85
|
+
game.black(2,2)
|
86
|
+
game.white(2,3)
|
87
|
+
game.undo
|
88
|
+
assert_equal Liberty.new(2,3), game.board.at(2,3)
|
89
|
+
end
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
91
|
+
def test_can_undo_until_beginning
|
92
|
+
game = Game.new()
|
93
|
+
game.black(2,2)
|
94
|
+
game.white(3,2)
|
95
|
+
game.black(3,3)
|
96
|
+
3.times {game.undo}
|
97
|
+
assert_equal Liberty.new(2,2), game.board.at(2,2)
|
98
|
+
end
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
100
|
+
def test_can_pass
|
101
|
+
game = Game.new
|
102
|
+
game.pass(:black)
|
103
|
+
game.pass(:white)
|
104
|
+
game.black_pass
|
105
|
+
game.white_pass
|
106
|
+
assert_equal 4, game.passes
|
107
|
+
end
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
game.black(3,3)
|
109
|
-
game.white(2,3)
|
110
|
-
game.white(4,3)
|
111
|
-
game.white(3,4)
|
112
|
-
game.white(3,2)
|
113
|
-
assert_raises(Game::IllegalMove) do
|
109
|
+
def test_cannot_play_a_suicide
|
110
|
+
game = Game.new
|
114
111
|
game.black(3,3)
|
112
|
+
game.white(2,3)
|
113
|
+
game.white(4,3)
|
114
|
+
game.white(3,4)
|
115
|
+
game.white(3,2)
|
116
|
+
assert_raises(Game::IllegalMove) do
|
117
|
+
game.black(3,3)
|
118
|
+
end
|
115
119
|
end
|
116
|
-
end
|
117
120
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
game.white(4,3)
|
123
|
-
game.white(3,4)
|
124
|
-
game.white(3,2)
|
125
|
-
game.black(4,2)
|
126
|
-
game.black(4,4)
|
127
|
-
game.black(5,3)
|
128
|
-
game.black(3,3)
|
129
|
-
assert_raises(Game::IllegalMove) do
|
121
|
+
def test_cannot_play_a_ko
|
122
|
+
game = Game.new
|
123
|
+
game.black(3,3)
|
124
|
+
game.white(2,3)
|
130
125
|
game.white(4,3)
|
126
|
+
game.white(3,4)
|
127
|
+
game.white(3,2)
|
128
|
+
game.black(4,2)
|
129
|
+
game.black(4,4)
|
130
|
+
game.black(5,3)
|
131
|
+
game.black(3,3)
|
132
|
+
assert_raises(Game::IllegalMove) do
|
133
|
+
game.white(4,3)
|
134
|
+
end
|
131
135
|
end
|
132
|
-
end
|
133
136
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
137
|
+
def big_capture_area_game
|
138
|
+
game = Game.new
|
139
|
+
game.black(0,0)
|
140
|
+
game.white(1,1)
|
141
|
+
game.black(0,1)
|
142
|
+
game.white(0,2)
|
143
|
+
game.black(1,0)
|
144
|
+
game.white(2,0)
|
145
|
+
game
|
146
|
+
end
|
144
147
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
148
|
+
def test_can_play_in_previous_capture_area_that_is_not_a_ko
|
149
|
+
game = big_capture_area_game
|
150
|
+
assert_equal Liberty.new(0,0), game.board.at(0,0)
|
151
|
+
game.black(0,0)
|
152
|
+
assert_equal BlackStone.new(0,0), game.board.at(0,0)
|
153
|
+
|
154
|
+
game = big_capture_area_game
|
155
|
+
assert_equal Liberty.new(0,1), game.board.at(0,1)
|
156
|
+
game.black(0,1)
|
157
|
+
assert_equal BlackStone.new(0,1), game.board.at(0,1)
|
158
|
+
|
159
|
+
game = big_capture_area_game
|
160
|
+
assert_equal Liberty.new(1,0), game.board.at(1,0)
|
161
|
+
game.black(1,0)
|
162
|
+
assert_equal BlackStone.new(1,0), game.board.at(1,0)
|
163
|
+
end
|
160
164
|
end
|
161
165
|
end
|
data/test/sgf_test.rb
CHANGED
@@ -1,51 +1,52 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
3
|
+
module RubyGo
|
4
|
+
class SGFTest < Minitest::Test
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def test_game_converts_to_sgf
|
7
|
+
game = Game.new
|
8
|
+
assert_equal "(;GM[1]FF[4]CA[UTF-8]AP[jphager2]SZ[19]PW[White]PB[Black])",
|
9
|
+
game.to_sgf
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
def test_game_converts_to_sgf_with_moves
|
13
|
+
game = Game.new
|
14
|
+
game.black(15,3)
|
15
|
+
game.white(3,3)
|
16
|
+
game.black(3,15)
|
17
|
+
game.white(15,15)
|
18
|
+
assert_equal "(;GM[1]FF[4]CA[UTF-8]AP[jphager2]SZ[19]PW[White]PB[Black];B[pd];W[dd];B[dp];W[pp])",
|
19
|
+
game.to_sgf
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
def test_game_converts_correctly_with_capture
|
23
|
+
game = Game.new
|
24
|
+
game.black(15,3)
|
25
|
+
game.white(16,3)
|
26
|
+
game.black(16,2)
|
27
|
+
game.white(15,2)
|
28
|
+
game.black(14,2)
|
29
|
+
game.white(14,3)
|
30
|
+
game.black(17,3)
|
31
|
+
game.white(15,4)
|
32
|
+
game.black(16,4)
|
33
|
+
game.white(15,3)
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
assert_equal "(;GM[1]FF[4]CA[UTF-8]AP[jphager2]SZ[19]PW[White]PB[Black];B[pd];W[qd];B[qc];W[pc];B[oc];W[od];B[rd];W[pe];B[qe];W[pd])",
|
36
|
+
game.to_sgf
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
def test_saves_sgf
|
40
|
+
file = File.expand_path('../../my_go_game.sgf', __FILE__)
|
41
|
+
path = File.expand_path('../../*.sgf', __FILE__)
|
42
|
+
File.delete(file) if File.exist?(file)
|
43
|
+
game = Game.new
|
44
|
+
game.black(15,3)
|
45
|
+
game.white(16,3)
|
46
|
+
game.black(16,2)
|
47
|
+
game.white(15,2)
|
48
|
+
game.save
|
49
|
+
assert_includes Dir.glob(path), file
|
50
|
+
end
|
49
51
|
end
|
50
|
-
|
51
52
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-go
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jphager2
|
@@ -35,10 +35,11 @@ files:
|
|
35
35
|
- README.md
|
36
36
|
- Rakefile
|
37
37
|
- bin/ruby-go.rb
|
38
|
-
- lib/board.rb
|
39
|
-
- lib/game.rb
|
40
38
|
- lib/ruby-go.rb
|
41
|
-
- lib/
|
39
|
+
- lib/ruby-go/board.rb
|
40
|
+
- lib/ruby-go/game.rb
|
41
|
+
- lib/ruby-go/stone.rb
|
42
|
+
- lib/ruby-go/version.rb
|
42
43
|
- test/go_test.rb
|
43
44
|
- test/sgf_test.rb
|
44
45
|
- test/test_helper.rb
|
data/lib/board.rb
DELETED
@@ -1,82 +0,0 @@
|
|
1
|
-
class Board
|
2
|
-
Colors = {black: 'x', white: 'o', empty: '_'}
|
3
|
-
|
4
|
-
attr_accessor :board
|
5
|
-
def initialize(size)
|
6
|
-
@board = []
|
7
|
-
size -= 1
|
8
|
-
|
9
|
-
0.upto(size) do |y|
|
10
|
-
row = []
|
11
|
-
0.upto(size) do |x|
|
12
|
-
row << Liberty.new(x, y)
|
13
|
-
end
|
14
|
-
@board << row
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def size
|
19
|
-
@board.length
|
20
|
-
end
|
21
|
-
|
22
|
-
def empty?
|
23
|
-
!@board.flatten.any? do |s|
|
24
|
-
!s.empty?
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def at(x, y)
|
29
|
-
@board[y][x]
|
30
|
-
end
|
31
|
-
|
32
|
-
def around(x, y = :y_not_given)
|
33
|
-
x, y = x.to_coord if x.kind_of?(Stone)
|
34
|
-
stones = []
|
35
|
-
stones << at(x-1, y) unless x == 0
|
36
|
-
stones << at(x+1, y) unless x == (@board.length - 1)
|
37
|
-
stones << at(x, y-1) unless y == 0
|
38
|
-
stones << at(x, y+1) unless y == (@board.length - 1)
|
39
|
-
stones
|
40
|
-
end
|
41
|
-
|
42
|
-
def remove(stone)
|
43
|
-
stone.remove_from_board(self)
|
44
|
-
end
|
45
|
-
|
46
|
-
def place(stone)
|
47
|
-
stone.place_on_board(self)
|
48
|
-
end
|
49
|
-
|
50
|
-
def liberties(stone)
|
51
|
-
group_of(stone).inject(0) do |libs, stn|
|
52
|
-
libs + stn.liberties(self).count
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def group_of(stone)
|
57
|
-
stone.group(self)
|
58
|
-
end
|
59
|
-
|
60
|
-
def to_str
|
61
|
-
out = ""
|
62
|
-
if @board.length < 11
|
63
|
-
out << "\s\s\s#{(0..@board.length - 1).to_a.join(' ')}\n"
|
64
|
-
else
|
65
|
-
out <<
|
66
|
-
"\s\s\s#{(0..10).to_a.join(' ')}" <<
|
67
|
-
"#{(11..@board.length - 1).to_a.join('')}\n"
|
68
|
-
end
|
69
|
-
|
70
|
-
@board.each_with_index do |row, i|
|
71
|
-
i = "\s#{i}" if i < 10
|
72
|
-
out << "#{i}\s#{(row.collect {|stn| stn.to_s}).join(' ')}\n"
|
73
|
-
end
|
74
|
-
|
75
|
-
out
|
76
|
-
end
|
77
|
-
|
78
|
-
def to_s
|
79
|
-
to_str
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
data/lib/game.rb
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
class Game
|
2
|
-
|
3
|
-
LETTERS = ('a'..'z').to_a
|
4
|
-
|
5
|
-
attr_reader :board
|
6
|
-
def initialize(board: 19)
|
7
|
-
@board = Board.new(board)
|
8
|
-
@moves = []
|
9
|
-
end
|
10
|
-
|
11
|
-
def save(name="my_go_game")
|
12
|
-
tree = SGF::Parser.new.parse(to_sgf)
|
13
|
-
tree.save(name + '.sgf')
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_sgf
|
17
|
-
sgf = "(;GM[1]FF[4]CA[UTF-8]AP[jphager2]SZ[19]PW[White]PB[Black]"
|
18
|
-
|
19
|
-
@moves.each do |move|
|
20
|
-
sgf << move[:stone].to_sgf
|
21
|
-
end
|
22
|
-
|
23
|
-
sgf << ')'
|
24
|
-
end
|
25
|
-
|
26
|
-
def view
|
27
|
-
puts @board.to_s
|
28
|
-
puts " " + "_"*(@board.size * 2)
|
29
|
-
print " Prisoners || White: #{captures[:black]} |"
|
30
|
-
puts " Black: #{captures[:white]}"
|
31
|
-
puts " " + "-"*(@board.size * 2)
|
32
|
-
end
|
33
|
-
|
34
|
-
def black(x, y)
|
35
|
-
play(BlackStone.new(x,y))
|
36
|
-
end
|
37
|
-
|
38
|
-
def white(x, y)
|
39
|
-
play(WhiteStone.new(x,y))
|
40
|
-
end
|
41
|
-
|
42
|
-
def pass
|
43
|
-
@moves << {stone: NullStone.new(), captures: [], pass: true}
|
44
|
-
end
|
45
|
-
|
46
|
-
def undo
|
47
|
-
move = @moves.pop
|
48
|
-
@board.remove(move[:stone])
|
49
|
-
move[:captures].each {|stone| @board.place(stone)}
|
50
|
-
end
|
51
|
-
|
52
|
-
def passes
|
53
|
-
@moves.inject(0) {|total, move| move[:pass] ? total + 1 : 0}
|
54
|
-
end
|
55
|
-
|
56
|
-
def captures
|
57
|
-
@moves.each_with_object({black: 0, white: 0}) do |move, total|
|
58
|
-
move[:captures].each do |capture|
|
59
|
-
total[capture.color] += 1
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
def play(stone)
|
66
|
-
@board.place(stone)
|
67
|
-
@moves << {stone: stone, captures: [], pass: false}
|
68
|
-
|
69
|
-
capture; suicide; ko
|
70
|
-
end
|
71
|
-
|
72
|
-
def ko
|
73
|
-
return if @moves.length < 2 or !@moves[-2][:captures]
|
74
|
-
|
75
|
-
captures = @moves[-2][:captures]
|
76
|
-
stone = @moves.last[:stone]
|
77
|
-
|
78
|
-
if captures == [stone]
|
79
|
-
undo
|
80
|
-
raise IllegalMove,
|
81
|
-
"You cannot capture the ko, play a ko threat first"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def suicide
|
86
|
-
stone = @moves.last[:stone]
|
87
|
-
unless @board.liberties(stone) > 0
|
88
|
-
undo
|
89
|
-
raise IllegalMove, "You cannot play a suicide."
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def capture
|
94
|
-
stone = @moves.last[:stone]
|
95
|
-
stones_around = @board.around(stone)
|
96
|
-
|
97
|
-
captures = stones_around.select {|stn| @board.liberties(stn) == 0}
|
98
|
-
|
99
|
-
captures.each {|stone| capture_group(stone)}
|
100
|
-
end
|
101
|
-
|
102
|
-
def capture_group(stone)
|
103
|
-
@board.group_of(stone).each {|stone| capture_stone(stone)}
|
104
|
-
end
|
105
|
-
|
106
|
-
def capture_stone(stone)
|
107
|
-
@moves.last[:captures] << stone
|
108
|
-
@board.remove(stone)
|
109
|
-
end
|
110
|
-
|
111
|
-
public
|
112
|
-
|
113
|
-
class IllegalMove < Exception
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
data/lib/stone.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
class Stone
|
2
|
-
|
3
|
-
attr_reader :color
|
4
|
-
def initialize(x, y)
|
5
|
-
@x, @y = x, y
|
6
|
-
@color = :none
|
7
|
-
end
|
8
|
-
|
9
|
-
def to_sgf
|
10
|
-
x = Game::LETTERS[@x]
|
11
|
-
y = Game::LETTERS[@y]
|
12
|
-
";#{color.to_s[0].upcase}[#{x+y}]"
|
13
|
-
end
|
14
|
-
|
15
|
-
def empty?
|
16
|
-
@color == :empty
|
17
|
-
end
|
18
|
-
|
19
|
-
def place_on_board(board)
|
20
|
-
unless board.at(@x, @y).empty?
|
21
|
-
raise Game::IllegalMove,
|
22
|
-
"You cannot place a stone on top of another stone."
|
23
|
-
end
|
24
|
-
board.board[@y][@x] = self
|
25
|
-
end
|
26
|
-
|
27
|
-
def remove_from_board(board)
|
28
|
-
board.board[@y][@x] = Liberty.new(*self.to_coord)
|
29
|
-
end
|
30
|
-
|
31
|
-
def liberties(board)
|
32
|
-
board.around(self).select {|stone| stone.empty?}
|
33
|
-
end
|
34
|
-
|
35
|
-
def group(board, stones = [])
|
36
|
-
return stones if stones.any? {|stone| stone.eql?(self)}
|
37
|
-
stones << self
|
38
|
-
|
39
|
-
board.around(self).each do |stone|
|
40
|
-
if stone.color == @color
|
41
|
-
stone.group(board, stones)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
stones
|
46
|
-
end
|
47
|
-
|
48
|
-
def to_coord
|
49
|
-
[@x, @y]
|
50
|
-
end
|
51
|
-
|
52
|
-
def to_str
|
53
|
-
Board::Colors[@color]
|
54
|
-
end
|
55
|
-
|
56
|
-
def to_s
|
57
|
-
to_str
|
58
|
-
end
|
59
|
-
|
60
|
-
def ==(other)
|
61
|
-
(self.color == other.color) and (self.to_coord == other.to_coord)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class Liberty < Stone
|
66
|
-
def initialize(x, y)
|
67
|
-
super
|
68
|
-
@color = :empty
|
69
|
-
end
|
70
|
-
|
71
|
-
def liberties(board)
|
72
|
-
[self]
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class BlackStone < Stone
|
77
|
-
def initialize(x, y)
|
78
|
-
super
|
79
|
-
@color = :black
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class WhiteStone < Stone
|
84
|
-
def initialize(x, y)
|
85
|
-
super
|
86
|
-
@color = :white
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class NullStone < Stone
|
91
|
-
def initialize(*args)
|
92
|
-
@x, @y = nil, nil
|
93
|
-
end
|
94
|
-
|
95
|
-
def remove_from_board(board)
|
96
|
-
end
|
97
|
-
|
98
|
-
def to_sfg
|
99
|
-
""
|
100
|
-
end
|
101
|
-
end
|