go_gtp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rvmrc +1 -0
- data/README.markdown +61 -0
- data/Rakefile +14 -0
- data/TODO +4 -0
- data/example/create_game.rb +10 -0
- data/example/generate_move.rb +10 -0
- data/example/play_all.rb +23 -0
- data/example/play_go.rb +40 -0
- data/example/play_move.rb +12 -0
- data/example/show_board.rb +7 -0
- data/go_gtp.gemspec +31 -0
- data/lib/go/gtp.rb +485 -0
- data/lib/go/gtp/board.rb +44 -0
- data/lib/go/gtp/point.rb +58 -0
- data/spec/board_spec.rb +68 -0
- data/spec/gtp_spec.rb +166 -0
- data/spec/point_spec.rb +75 -0
- data/spec/run_gnugo_spec.rb +75 -0
- metadata +101 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2-p0
|
data/README.markdown
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
The Go Text Protocol
|
2
|
+
====================
|
3
|
+
|
4
|
+
This library wraps [GNU Go](http://www.gnu.org/software/gnugo/)'s version of the Go Text Protocol or GTP. It runs GNU Go in a separate process and communicates with the program over a pipe using the GTP protocol. This makes it easy to:
|
5
|
+
|
6
|
+
* Manage full games of Go
|
7
|
+
* Work with SGF files
|
8
|
+
* Analyze Go positions
|
9
|
+
|
10
|
+
Installing
|
11
|
+
----------
|
12
|
+
|
13
|
+
This library is available as a gem, so you can install it with a command like:
|
14
|
+
|
15
|
+
gem install go_gtp
|
16
|
+
|
17
|
+
The above command may need super user privileges.
|
18
|
+
|
19
|
+
This library requires an install of GNU Go to communicate with. You will need to install that separately.
|
20
|
+
|
21
|
+
Examples
|
22
|
+
--------
|
23
|
+
|
24
|
+
This code would load an SGF file and show the current state of the game in that file:
|
25
|
+
|
26
|
+
require "go/gtp"
|
27
|
+
|
28
|
+
go = Go::GTP.run_gnugo
|
29
|
+
go.loadsgf("game.sgf") or abort "Failed to load file"
|
30
|
+
puts go.showboard
|
31
|
+
go.quit
|
32
|
+
|
33
|
+
This shows the two main types of GTP methods. Methods like `showboard()` return the indicated content. In this case, you actually get back a `Go::GTP::Board` object which can indexed into, or just converted into a `String` for display as it is used here.
|
34
|
+
|
35
|
+
Other methods, like `loadsgf()`, are just called for their side effects and they don't return anything. For these, you get a boolean result telling you if the call succeeded (`true`) or triggered an error (`false`). You can always check the `success?()` of either type of call after the fact and retrieve the `last_error()` when there is one, so these return values are just a convenience. As another convenience, these boolean methods can be called with Ruby's query syntax as well: `loadsgf?()`.
|
36
|
+
|
37
|
+
When working with a GNU Go process, it's a good idea to remember to call `quit()` so the pipe can be closed. One way to ensure that happens is to use the block form of `run_gnugo()` to have it done for you. Given that, the following example is another way to handle loading and displaying a game:
|
38
|
+
|
39
|
+
require "go/gtp"
|
40
|
+
|
41
|
+
Go::GTP.run_gnugo do |go|
|
42
|
+
go.loadsgf?("game.sgf") or abort "Failed to load file"
|
43
|
+
puts go.showboard
|
44
|
+
end # quit called automatically after this block
|
45
|
+
|
46
|
+
You can customize how GNU Go is invoked, by passing parameters to `run_gnugo()`. Probably the two most useful are the `:directory` where the executable lives and any `:arguments` you would like to pass it. For example:
|
47
|
+
|
48
|
+
require "go/gtp"
|
49
|
+
|
50
|
+
go = Go::GTP.run_gnugo( directory: "/usr/local/bin",
|
51
|
+
arguments: "--boardsize 9" )
|
52
|
+
# ...
|
53
|
+
|
54
|
+
Of course, you could also set the board size after the connection is open with `go.boardsize(9)`.
|
55
|
+
|
56
|
+
See the [example directory](http://github.com/JEG2/go_gtp/tree/master/example/) for more ideas about how to use this library.
|
57
|
+
|
58
|
+
GTP Commands
|
59
|
+
------------
|
60
|
+
|
61
|
+
The method names are literally the command names right out of [the GTP documentation](http://www.gnu.org/software/gnugo/gnugo_19.html#SEC200). That's intended to make it easy to figure out what you can do with this library. Return values are Rubified into nice objects, when it makes sense to do so.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "spec/rake/spectask"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
Spec::Rake::SpecTask.new do |rspec|
|
8
|
+
rspec.warning = true
|
9
|
+
end
|
10
|
+
|
11
|
+
load(File.join(File.dirname(__FILE__), "go_gtp.gemspec"))
|
12
|
+
Rake::GemPackageTask.new(SPEC) do |package|
|
13
|
+
# do nothing: I just need a gem but this block is required
|
14
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
|
2
|
+
require "go/gtp"
|
3
|
+
|
4
|
+
abort "USAGE: #{$PROGRAM_NAME} BOARD_SIZE_INT" unless ARGV.first =~ /\A\d+\z/
|
5
|
+
|
6
|
+
Go::GTP.run_gnugo do |go|
|
7
|
+
go.boardsize(ARGV.first) or abort "Invalid board size"
|
8
|
+
go.clear_board
|
9
|
+
go.printsgf(File.join(File.dirname(__FILE__), "game.sgf"))
|
10
|
+
end
|
data/example/play_all.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
|
2
|
+
require "go/gtp"
|
3
|
+
|
4
|
+
abort "USAGE: #{$PROGRAM_NAME} BOARD_SIZE_INT" unless ARGV.first =~ /\A\d+\z/
|
5
|
+
|
6
|
+
# this script is handy for measuring performance
|
7
|
+
|
8
|
+
start = Time.now
|
9
|
+
colors = %w[black white].cycle
|
10
|
+
Go::GTP.run_gnugo do |go|
|
11
|
+
go.boardsize(ARGV.first) or abort "Invalid board size"
|
12
|
+
go.clear_board
|
13
|
+
1.upto(19) do |row|
|
14
|
+
("A".."T").each do |column|
|
15
|
+
next if column == "I"
|
16
|
+
move = "#{column}#{row}"
|
17
|
+
next if move == "T19" # illegal move
|
18
|
+
go.play(colors.next, move) or abort "Invalid move #{move}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
puts go.showboard
|
22
|
+
end
|
23
|
+
puts "Total time: #{Time.now - start} seconds"
|
data/example/play_go.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
|
2
|
+
require "go/gtp"
|
3
|
+
|
4
|
+
class IllegalMoveError < RuntimeError; end
|
5
|
+
|
6
|
+
COLORS = %w[black white]
|
7
|
+
PLAYERS = Hash[COLORS.zip(%w[computer player].sample(2))]
|
8
|
+
|
9
|
+
begin
|
10
|
+
Go::GTP.run_gnugo(arguments: ARGV.empty? ? nil : ARGV) do |go|
|
11
|
+
COLORS.cycle do |color|
|
12
|
+
puts go.showboard
|
13
|
+
abort "Error: failed to show board" unless go.success?
|
14
|
+
move = nil
|
15
|
+
case PLAYERS[color]
|
16
|
+
when "player"
|
17
|
+
begin
|
18
|
+
print "Move for #{color}? "
|
19
|
+
move = $stdin.gets.to_s.strip
|
20
|
+
unless move =~ /\S/ and go.is_legal?(color, move)
|
21
|
+
raise IllegalMoveError, "Illegal move"
|
22
|
+
end
|
23
|
+
go.play?(color, move) or abort "Error: move failed"
|
24
|
+
rescue IllegalMoveError => error
|
25
|
+
puts error.message
|
26
|
+
retry
|
27
|
+
end
|
28
|
+
when "computer"
|
29
|
+
move = go.genmove(color)
|
30
|
+
abort "Error: failed to generate move" unless go.success?
|
31
|
+
puts "Move for #{color}: #{move}"
|
32
|
+
end
|
33
|
+
puts
|
34
|
+
break if go.over?
|
35
|
+
end
|
36
|
+
puts "Final score: #{go.final_score}"
|
37
|
+
end
|
38
|
+
rescue Errno::EPIPE
|
39
|
+
abort "Error: bad arguments"
|
40
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
|
2
|
+
require "go/gtp"
|
3
|
+
|
4
|
+
abort "USAGE: #{$PROGRAM_NAME} MOVE" unless ARGV.first =~ /\A[A-Z]\d+\z/i
|
5
|
+
|
6
|
+
GAME_FILE = File.join(File.dirname(__FILE__), "game.sgf")
|
7
|
+
|
8
|
+
Go::GTP.run_gnugo do |go|
|
9
|
+
color = go.loadsgf(GAME_FILE)
|
10
|
+
go.play(color, ARGV.first) or abort "Invalid move for #{color}"
|
11
|
+
go.printsgf(GAME_FILE)
|
12
|
+
end
|
data/go_gtp.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
DIR = File.dirname(__FILE__)
|
2
|
+
LIB = File.join(DIR, *%w[lib go gtp.rb])
|
3
|
+
VERSION = open(LIB) { |lib|
|
4
|
+
lib.each { |line|
|
5
|
+
if v = line[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]; break v end
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
SPEC = Gem::Specification.new do |s|
|
10
|
+
s.name = "go_gtp"
|
11
|
+
s.version = VERSION
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.authors = ["James Edward Gray II", "Ryan Bates"]
|
14
|
+
s.email = ["james@graysoftinc.com"]
|
15
|
+
s.homepage = "http://github.com/JEG2/go_gtp"
|
16
|
+
s.summary = "A wrapper for GNU Go's Go Text Protocol (GTP)."
|
17
|
+
s.description = <<-END_DESCRIPTION.gsub(/\s+/, " ").strip
|
18
|
+
This library runs GNU Go in a separate process and allows you to communicate
|
19
|
+
with it using the Go Text Protocol (GTP). This makes it easy to manage full
|
20
|
+
games of Go, work with SGF files, analyze Go positions, and more.
|
21
|
+
END_DESCRIPTION
|
22
|
+
|
23
|
+
s.required_rubygems_version = "~> 1.9.2"
|
24
|
+
s.required_rubygems_version = "~> 1.3.6"
|
25
|
+
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
|
28
|
+
s.files = `git ls-files`.split("\n")
|
29
|
+
s.test_files = `git ls-files -- spec/*_spec.rb`.split("\n")
|
30
|
+
s.require_paths = %w[lib]
|
31
|
+
end
|
data/lib/go/gtp.rb
ADDED
@@ -0,0 +1,485 @@
|
|
1
|
+
require "go/gtp/board"
|
2
|
+
|
3
|
+
module Go
|
4
|
+
class GTP
|
5
|
+
VERSION = "0.0.1"
|
6
|
+
|
7
|
+
def self.run_gnugo(options = { }, &commands)
|
8
|
+
directory = options.fetch(:directory, nil)
|
9
|
+
command = options.fetch(:command, "gnugo --mode gtp")
|
10
|
+
arguments = options.fetch(:arguments, nil)
|
11
|
+
redirections = options.fetch(:redirections, "2>&1")
|
12
|
+
path = [ File.join(*[directory, command].compact),
|
13
|
+
arguments,
|
14
|
+
redirections ].compact.join(" ")
|
15
|
+
|
16
|
+
new(IO.popen(path, "r+"), &commands)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(io)
|
20
|
+
@io = io
|
21
|
+
@id = 0
|
22
|
+
@last_error = nil
|
23
|
+
|
24
|
+
if block_given?
|
25
|
+
begin
|
26
|
+
yield self
|
27
|
+
ensure
|
28
|
+
quit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :last_error
|
34
|
+
|
35
|
+
def success?
|
36
|
+
@last_error.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def quit
|
40
|
+
send_command(:quit)
|
41
|
+
@io.close
|
42
|
+
success?
|
43
|
+
end
|
44
|
+
alias_method :quit?, :quit
|
45
|
+
|
46
|
+
def protocol_version
|
47
|
+
send_command(:protocol_version)
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
send_command(:name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def version
|
55
|
+
send_command(:version)
|
56
|
+
end
|
57
|
+
|
58
|
+
def boardsize(boardsize)
|
59
|
+
send_command(:boardsize, boardsize)
|
60
|
+
success?
|
61
|
+
end
|
62
|
+
alias_method :boardsize?, :boardsize
|
63
|
+
|
64
|
+
def query_boardsize
|
65
|
+
send_command(:query_boardsize)
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear_board
|
69
|
+
send_command(:clear_board)
|
70
|
+
success?
|
71
|
+
end
|
72
|
+
alias_method :clear_board?, :clear_board
|
73
|
+
|
74
|
+
def orientation(orientation)
|
75
|
+
send_command(:orientation, orientation)
|
76
|
+
success?
|
77
|
+
end
|
78
|
+
alias_method :orientation?, :orientation
|
79
|
+
|
80
|
+
def query_orientation
|
81
|
+
send_command(:query_orientation)
|
82
|
+
end
|
83
|
+
|
84
|
+
def komi(komi)
|
85
|
+
send_command(:komi, komi)
|
86
|
+
success?
|
87
|
+
end
|
88
|
+
alias_method :komi?, :komi
|
89
|
+
|
90
|
+
def get_komi
|
91
|
+
send_command(:get_komi)
|
92
|
+
end
|
93
|
+
|
94
|
+
def play(color, vertex)
|
95
|
+
send_command(:play, color, vertex)
|
96
|
+
success?
|
97
|
+
end
|
98
|
+
alias_method :play?, :play
|
99
|
+
|
100
|
+
def replay(vertices)
|
101
|
+
colors = %w[black white].cycle
|
102
|
+
vertices.each do |vertex|
|
103
|
+
play(colors.next, vertex)
|
104
|
+
return success? unless success?
|
105
|
+
end
|
106
|
+
success?
|
107
|
+
end
|
108
|
+
alias_method :replay?, :replay
|
109
|
+
|
110
|
+
def fixed_handicap(number_of_stones)
|
111
|
+
extract_vertices(send_command(:fixed_handicap, number_of_stones))
|
112
|
+
end
|
113
|
+
|
114
|
+
def place_free_handicap(number_of_stones)
|
115
|
+
extract_vertices(send_command(:place_free_handicap, number_of_stones))
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_free_handicap(*vertices)
|
119
|
+
send_command(:set_free_handicap, *vertices)
|
120
|
+
success?
|
121
|
+
end
|
122
|
+
alias_method :set_free_handicap?, :set_free_handicap
|
123
|
+
|
124
|
+
def get_handicap
|
125
|
+
send_command(:get_handicap)
|
126
|
+
end
|
127
|
+
|
128
|
+
def loadsgf(path, move_number_or_vertex = nil)
|
129
|
+
send_command(:loadsgf, *[path, move_number_or_vertex].compact)
|
130
|
+
end
|
131
|
+
|
132
|
+
def color(vertex)
|
133
|
+
extract_color(send_command(:color, vertex))
|
134
|
+
end
|
135
|
+
|
136
|
+
def list_stones(color)
|
137
|
+
extract_vertices(send_command(:list_stones, color))
|
138
|
+
end
|
139
|
+
|
140
|
+
def countlib(vertex)
|
141
|
+
extract_integer(send_command(:countlib, vertex))
|
142
|
+
end
|
143
|
+
|
144
|
+
def findlib(vertex)
|
145
|
+
extract_vertices(send_command(:findlib, vertex))
|
146
|
+
end
|
147
|
+
|
148
|
+
def accuratelib(color, vertex)
|
149
|
+
extract_vertices(send_command(:accuratelib, color, vertex))
|
150
|
+
end
|
151
|
+
|
152
|
+
def is_legal(color, vertex)
|
153
|
+
extract_boolean(send_command(:is_legal, color, vertex))
|
154
|
+
end
|
155
|
+
alias_method :is_legal?, :is_legal
|
156
|
+
|
157
|
+
def all_legal(color)
|
158
|
+
extract_vertices(send_command(:all_legal, color))
|
159
|
+
end
|
160
|
+
|
161
|
+
def captures(color)
|
162
|
+
extract_integer(send_command(:captures, color))
|
163
|
+
end
|
164
|
+
|
165
|
+
def last_move
|
166
|
+
extract_move(send_command(:last_move))
|
167
|
+
end
|
168
|
+
|
169
|
+
def move_history
|
170
|
+
extract_moves(send_command(:move_history))
|
171
|
+
end
|
172
|
+
|
173
|
+
def over?
|
174
|
+
last_two_moves = move_history.first(2)
|
175
|
+
Array(last_two_moves.first).last.to_s.upcase == "RESIGN" or
|
176
|
+
last_two_moves.map { |m| Array(m).last.to_s.upcase } == %w[PASS PASS]
|
177
|
+
end
|
178
|
+
|
179
|
+
def invariant_hash
|
180
|
+
send_command(:invariant_hash)
|
181
|
+
end
|
182
|
+
|
183
|
+
def invariant_hash_for_moves(color)
|
184
|
+
extract_moves(send_command(:invariant_hash_for_moves, color))
|
185
|
+
end
|
186
|
+
|
187
|
+
def trymove(color, vertex)
|
188
|
+
send_command(:trymove, color, vertex)
|
189
|
+
success?
|
190
|
+
end
|
191
|
+
alias_method :trymove?, :trymove
|
192
|
+
|
193
|
+
def tryko(color, vertex)
|
194
|
+
send_command(:tryko, color, vertex)
|
195
|
+
success?
|
196
|
+
end
|
197
|
+
alias_method :tryko?, :tryko
|
198
|
+
|
199
|
+
def popgo
|
200
|
+
send_command(:popgo)
|
201
|
+
success?
|
202
|
+
end
|
203
|
+
alias_method :popgo?, :popgo
|
204
|
+
|
205
|
+
def clear_cache
|
206
|
+
send_command(:clear_cache)
|
207
|
+
success?
|
208
|
+
end
|
209
|
+
alias_method :clear_cache?, :clear_cache
|
210
|
+
|
211
|
+
# ...
|
212
|
+
|
213
|
+
def increase_depths
|
214
|
+
send_command(:increase_depths)
|
215
|
+
success?
|
216
|
+
end
|
217
|
+
alias_method :increase_depths?, :increase_depths
|
218
|
+
|
219
|
+
def decrease_depths
|
220
|
+
send_command(:decrease_depths)
|
221
|
+
success?
|
222
|
+
end
|
223
|
+
alias_method :decrease_depths?, :decrease_depths
|
224
|
+
|
225
|
+
# ...
|
226
|
+
|
227
|
+
def unconditional_status(vertex)
|
228
|
+
send_command(:unconditional_status, vertex)
|
229
|
+
end
|
230
|
+
|
231
|
+
# ...
|
232
|
+
|
233
|
+
def genmove(color)
|
234
|
+
send_command(:genmove, color)
|
235
|
+
end
|
236
|
+
|
237
|
+
def reg_genmove(color)
|
238
|
+
send_command(:reg_genmove, color)
|
239
|
+
end
|
240
|
+
|
241
|
+
def gg_genmove(color, random_seed = nil)
|
242
|
+
send_command(:gg_genmove, *[color, random_seed].compact)
|
243
|
+
end
|
244
|
+
|
245
|
+
def restricted_genmove(color, *vertices)
|
246
|
+
send_command(:restricted_genmove, color, *vertices)
|
247
|
+
end
|
248
|
+
|
249
|
+
def kgs_genmove_cleanup(color)
|
250
|
+
send_command(:kgs_genmove_cleanup, color)
|
251
|
+
end
|
252
|
+
|
253
|
+
def level(level)
|
254
|
+
send_command(:level, level)
|
255
|
+
success?
|
256
|
+
end
|
257
|
+
alias_method :level?, :level
|
258
|
+
|
259
|
+
def undo
|
260
|
+
send_command(:undo)
|
261
|
+
success?
|
262
|
+
end
|
263
|
+
alias_method :undo?, :undo
|
264
|
+
|
265
|
+
def gg_undo(moves = nil)
|
266
|
+
send_command(:gg_undo, *[moves].compact)
|
267
|
+
success?
|
268
|
+
end
|
269
|
+
alias_method :gg_undo?, :gg_undo
|
270
|
+
|
271
|
+
def time_settings(main_time, byo_yomi_time, byo_yomi_stones)
|
272
|
+
send_command(:time_settings, main_time, byo_yomi_time, byo_yomi_stones)
|
273
|
+
success?
|
274
|
+
end
|
275
|
+
alias_method :time_settings?, :time_settings
|
276
|
+
|
277
|
+
def time_left(color, time, stones)
|
278
|
+
send_command(:time_left, color, time, stones)
|
279
|
+
success?
|
280
|
+
end
|
281
|
+
alias_method :time_left?, :time_left
|
282
|
+
|
283
|
+
def final_score(random_seed = nil)
|
284
|
+
send_command(:final_score, *[random_seed].compact)
|
285
|
+
end
|
286
|
+
|
287
|
+
def final_status(vertex, random_seed = nil)
|
288
|
+
send_command(:final_status, *[vertex, random_seed].compact)
|
289
|
+
end
|
290
|
+
|
291
|
+
def final_status_list(status, random_seed = nil)
|
292
|
+
extract_vertices( send_command( :final_status_list,
|
293
|
+
*[status, random_seed].compact ) )
|
294
|
+
end
|
295
|
+
|
296
|
+
def estimate_score
|
297
|
+
send_command(:estimate_score)
|
298
|
+
end
|
299
|
+
|
300
|
+
def experimental_score(color)
|
301
|
+
send_command(:experimental_score, color)
|
302
|
+
end
|
303
|
+
|
304
|
+
def reset_owl_node_counter
|
305
|
+
send_command(:reset_owl_node_counter)
|
306
|
+
success?
|
307
|
+
end
|
308
|
+
alias_method :reset_owl_node_counter?, :reset_owl_node_counter
|
309
|
+
|
310
|
+
def get_owl_node_counter
|
311
|
+
send_command(:reset_owl_node_counter)
|
312
|
+
end
|
313
|
+
|
314
|
+
def reset_reading_node_counter
|
315
|
+
send_command(:reset_reading_node_counter)
|
316
|
+
success?
|
317
|
+
end
|
318
|
+
alias_method :reset_reading_node_counter?, :reset_reading_node_counter
|
319
|
+
|
320
|
+
def get_reading_node_counter
|
321
|
+
send_command(:reset_reading_node_counter)
|
322
|
+
end
|
323
|
+
|
324
|
+
def reset_trymove_node_counter
|
325
|
+
send_command(:reset_trymove_node_counter)
|
326
|
+
success?
|
327
|
+
end
|
328
|
+
alias_method :reset_trymove_node_counter?, :reset_trymove_node_counter
|
329
|
+
|
330
|
+
def get_trymove_node_counter
|
331
|
+
send_command(:reset_trymove_node_counter)
|
332
|
+
end
|
333
|
+
|
334
|
+
def reset_connection_node_counter
|
335
|
+
send_command(:reset_connection_node_counter)
|
336
|
+
success?
|
337
|
+
end
|
338
|
+
alias_method :reset_connection_node_counter?, :reset_connection_node_counter
|
339
|
+
|
340
|
+
def get_connection_node_counter
|
341
|
+
send_command(:reset_connection_node_counter)
|
342
|
+
end
|
343
|
+
|
344
|
+
# ...
|
345
|
+
|
346
|
+
def cputime
|
347
|
+
send_command(:cputime)
|
348
|
+
end
|
349
|
+
|
350
|
+
def showboard
|
351
|
+
Board.new(send_command(:showboard))
|
352
|
+
end
|
353
|
+
|
354
|
+
# ...
|
355
|
+
|
356
|
+
def printsgf(path = nil)
|
357
|
+
if path
|
358
|
+
send_command(:printsgf, path)
|
359
|
+
success?
|
360
|
+
else
|
361
|
+
send_command(:printsgf)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
alias_method :printsgf?, :printsgf
|
365
|
+
|
366
|
+
def tune_move_ordering(*move_ordering_parameters)
|
367
|
+
send_command(:tune_move_ordering, *move_ordering_parameters)
|
368
|
+
end
|
369
|
+
|
370
|
+
def echo(string)
|
371
|
+
send_command(:echo, string)
|
372
|
+
end
|
373
|
+
|
374
|
+
def echo_err(string)
|
375
|
+
send_command(:echo_err, string)
|
376
|
+
end
|
377
|
+
|
378
|
+
def help
|
379
|
+
extract_lines(send_command(:help))
|
380
|
+
end
|
381
|
+
|
382
|
+
def known_command(command)
|
383
|
+
extract_boolean(send_command(:known_command, command))
|
384
|
+
end
|
385
|
+
alias_method :known_command?, :known_command
|
386
|
+
|
387
|
+
def report_uncertainty(on_or_off)
|
388
|
+
send_command(:report_uncertainty, on_or_off)
|
389
|
+
end
|
390
|
+
alias_method :report_uncertainty?, :report_uncertainty
|
391
|
+
|
392
|
+
def get_random_seed
|
393
|
+
send_command(:get_random_seed)
|
394
|
+
end
|
395
|
+
|
396
|
+
def set_random_seed(random_seed)
|
397
|
+
send_command(:set_random_seed, random_seed)
|
398
|
+
success?
|
399
|
+
end
|
400
|
+
alias_method :set_random_seed?, :set_random_seed
|
401
|
+
|
402
|
+
def advance_random_seed(games)
|
403
|
+
send_command(:advance_random_seed, games)
|
404
|
+
success?
|
405
|
+
end
|
406
|
+
alias_method :advance_random_seed?, :advance_random_seed
|
407
|
+
|
408
|
+
# ...
|
409
|
+
|
410
|
+
def set_search_diamond(position)
|
411
|
+
send_command(:set_search_diamond, position)
|
412
|
+
success?
|
413
|
+
end
|
414
|
+
alias_method :set_search_diamond?, :set_search_diamond
|
415
|
+
|
416
|
+
def reset_search_mask
|
417
|
+
send_command(:reset_search_mask)
|
418
|
+
success?
|
419
|
+
end
|
420
|
+
alias_method :reset_search_mask?, :reset_search_mask
|
421
|
+
|
422
|
+
def limit_search(value)
|
423
|
+
send_command(:limit_search, value)
|
424
|
+
success?
|
425
|
+
end
|
426
|
+
alias_method :limit_search?, :limit_search
|
427
|
+
|
428
|
+
def set_search_limit(position)
|
429
|
+
send_command(:set_search_limit, position)
|
430
|
+
success?
|
431
|
+
end
|
432
|
+
alias_method :set_search_limit?, :set_search_limit
|
433
|
+
|
434
|
+
def draw_search_area
|
435
|
+
send_command(:draw_search_area)
|
436
|
+
end
|
437
|
+
|
438
|
+
private
|
439
|
+
|
440
|
+
def next_id
|
441
|
+
@id += 1
|
442
|
+
end
|
443
|
+
|
444
|
+
def send_command(command, *arguments)
|
445
|
+
@io.puts [next_id, command, *arguments].join(" ")
|
446
|
+
result = @io.take_while { |line| line != "\n" }.join
|
447
|
+
if result.sub!(/^=#{@id} */, "")
|
448
|
+
@last_error = nil
|
449
|
+
elsif result.sub!(/^\?#{@id} *(\S.*\S?).*/, "")
|
450
|
+
@last_error = $1
|
451
|
+
else
|
452
|
+
raise "Unexpected response format"
|
453
|
+
end
|
454
|
+
result.sub(/\A(?: *\n)+/, "").sub(/(?:\n *)+\z/, "")
|
455
|
+
end
|
456
|
+
|
457
|
+
def extract_vertices(response)
|
458
|
+
success? ? response.scan(/[A-Z]\d+/) : [ ]
|
459
|
+
end
|
460
|
+
|
461
|
+
def extract_color(response)
|
462
|
+
!success? || response == "empty" ? nil : response
|
463
|
+
end
|
464
|
+
|
465
|
+
def extract_move(response)
|
466
|
+
success? ? response.split : nil
|
467
|
+
end
|
468
|
+
|
469
|
+
def extract_moves(response)
|
470
|
+
success? ? response.lines.map { |line| line.strip.split } : nil
|
471
|
+
end
|
472
|
+
|
473
|
+
def extract_boolean(response)
|
474
|
+
success? ? response == "1" || response == "true" : nil
|
475
|
+
end
|
476
|
+
|
477
|
+
def extract_integer(response)
|
478
|
+
success? ? response.to_i : nil
|
479
|
+
end
|
480
|
+
|
481
|
+
def extract_lines(response)
|
482
|
+
success? ? response.lines.map { |line| line.strip } : nil
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
data/lib/go/gtp/board.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Go
|
2
|
+
class GTP
|
3
|
+
class Board
|
4
|
+
STONES = {"X" => "black", "O" => "white"}
|
5
|
+
|
6
|
+
def initialize(board_string)
|
7
|
+
@string = board_string
|
8
|
+
@array = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](*args)
|
12
|
+
point = if args.size == 1 and args.first.is_a? Point
|
13
|
+
args.first
|
14
|
+
elsif args.size == 1 and
|
15
|
+
args.first =~ /\A([A-HJ-T])(\d{1,2})\z/i
|
16
|
+
Point.new(*args, board_size: size)
|
17
|
+
else
|
18
|
+
Point.new(*args)
|
19
|
+
end
|
20
|
+
to_a[point.y][point.x]
|
21
|
+
end
|
22
|
+
|
23
|
+
def captures(color)
|
24
|
+
@string[/#{Regexp.escape(color)}(?: \([XO])?\) has captured (\d+)/i, 1]
|
25
|
+
.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@string
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_a
|
33
|
+
@array ||= @string.scan(/^\s*(\d+)((?:.[.+XO])+).\1\b/)
|
34
|
+
.map { |_, row| row.chars
|
35
|
+
.each_slice(2)
|
36
|
+
.map { |_, stone| STONES[stone] } }
|
37
|
+
end
|
38
|
+
|
39
|
+
def size
|
40
|
+
to_a.size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/go/gtp/point.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Go
|
2
|
+
class GTP
|
3
|
+
#
|
4
|
+
# This utility class manages points on a Go board using any one of three
|
5
|
+
# formats:
|
6
|
+
#
|
7
|
+
# * X and Y Array indices counting from the upper left hand corner
|
8
|
+
# * SGF letter pairs ("ac") counting from the upper left hand corner
|
9
|
+
# * GNU Go letter and number pairs ("A13") counting from the lower left hand
|
10
|
+
# corner
|
11
|
+
#
|
12
|
+
# There are two gotchas to stay aware of with these systems. First, the GNU
|
13
|
+
# Go format skips over I in columns, but the SGF format does not. Second,
|
14
|
+
# the GNU Go format relies on knowing the board size. A 19x19 size is
|
15
|
+
# assumed, but you can override this when creating from or converting to
|
16
|
+
# this format.
|
17
|
+
#
|
18
|
+
# Point instances can be initialized from any format and converted to any
|
19
|
+
# format.
|
20
|
+
#
|
21
|
+
class Point
|
22
|
+
BIG_A = "A".getbyte(0)
|
23
|
+
LITTLE_A = "a".getbyte(0)
|
24
|
+
|
25
|
+
def initialize(*args)
|
26
|
+
if args.size == 2 and args.all? { |n| n.is_a? Integer }
|
27
|
+
@x, @y = args
|
28
|
+
elsif (args.size == 1 or (args.size == 2 and args.last.is_a?(Hash))) and
|
29
|
+
args.first =~ /\A([A-HJ-T])(\d{1,2})\z/i
|
30
|
+
options = args.last.is_a?(Hash) ? args.pop : { }
|
31
|
+
letter = $1.upcase
|
32
|
+
@x = letter.getbyte(0) - BIG_A - (letter > "I" ? 1 : 0)
|
33
|
+
@y = options.fetch(:board_size, 19) - $2.to_i
|
34
|
+
elsif args.size == 1 and args.first =~ /\A([a-s])([a-s])\z/i
|
35
|
+
@x, @y = $~.captures.map { |l| l.downcase.getbyte(0) - LITTLE_A }
|
36
|
+
else
|
37
|
+
fail ArgumentError, "unrecognized point format"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :x, :y
|
42
|
+
|
43
|
+
def to_indexes
|
44
|
+
[@x, @y]
|
45
|
+
end
|
46
|
+
alias_method :to_indices, :to_indexes
|
47
|
+
|
48
|
+
def to_sgf
|
49
|
+
[@x, @y].map { |n| (LITTLE_A + n).chr }.join
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_gnugo(board_size = 19)
|
53
|
+
"#{(BIG_A + @x).chr}#{board_size - @y}"
|
54
|
+
end
|
55
|
+
alias_method :to_s, :to_gnugo
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/board_spec.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "go/gtp/board"
|
2
|
+
|
3
|
+
describe Go::GTP::Board do
|
4
|
+
before :all do
|
5
|
+
@string = <<-END_BOARD.gsub(/^ {4}/, "")
|
6
|
+
A B C D E F G H J
|
7
|
+
9 . . . . . . . . . 9
|
8
|
+
8 . . . . . . . . . 8
|
9
|
+
7 . . X . . . + . . 7
|
10
|
+
6 . . . . . . . . . 6
|
11
|
+
5 . . . . + . . . . 5
|
12
|
+
4 . . . . . . . . . 4
|
13
|
+
3 . . + . . O + . . 3
|
14
|
+
2 . . . . . . . . . 2 WHITE (O) has captured 10 stones
|
15
|
+
1 . . . . . . . . . 1 BLACK (X) has captured 11 stones
|
16
|
+
A B C D E F G H J
|
17
|
+
END_BOARD
|
18
|
+
@board = Go::GTP::Board.new(@string)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should stringify to what it was created from" do
|
22
|
+
@board.to_s.should == @string
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be possible to get the board as an array of arrays" do
|
26
|
+
@board.to_a.should == [
|
27
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
28
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
29
|
+
[nil, nil, "black", nil, nil, nil, nil, nil, nil],
|
30
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
31
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
32
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
33
|
+
[nil, nil, nil, nil, nil, "white", nil, nil, nil],
|
34
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil],
|
35
|
+
[nil, nil, nil, nil, nil, nil, nil, nil, nil]
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should support indexing by coordinates" do
|
40
|
+
@board[0, 0].should be_nil
|
41
|
+
@board[2, 2].should satisfy { |color| color == "black" }
|
42
|
+
@board[5, 6].should satisfy { |color| color == "white" }
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should support indexing by vertices" do
|
46
|
+
@board["A1"].should be_nil
|
47
|
+
@board["C7"].should satisfy { |color| color == "black" }
|
48
|
+
@board["F3"].should satisfy { |color| color == "white" }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fail to index for any other combination" do
|
52
|
+
lambda { @board["A", "1"] }.should raise_error(ArgumentError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should be able to count captures by color name" do
|
56
|
+
@board.captures("white").should satisfy { |stones| stones == 10 }
|
57
|
+
@board.captures("WHITE").should satisfy { |stones| stones == 10 }
|
58
|
+
@board.captures("black").should satisfy { |stones| stones == 11 }
|
59
|
+
@board.captures("BLACK").should satisfy { |stones| stones == 11 }
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should be able to count captures by stone type" do
|
63
|
+
@board.captures("o").should satisfy { |stones| stones == 10 }
|
64
|
+
@board.captures("O").should satisfy { |stones| stones == 10 }
|
65
|
+
@board.captures("x").should satisfy { |stones| stones == 11 }
|
66
|
+
@board.captures("X").should satisfy { |stones| stones == 11 }
|
67
|
+
end
|
68
|
+
end
|
data/spec/gtp_spec.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require "stringio"
|
2
|
+
|
3
|
+
require "go/gtp"
|
4
|
+
|
5
|
+
class MockPipe
|
6
|
+
def initialize(input)
|
7
|
+
@input = StringIO.new(input)
|
8
|
+
@output = StringIO.new
|
9
|
+
@closed = false
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :input, :output
|
13
|
+
|
14
|
+
def puts(*args)
|
15
|
+
@output.puts(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
@closed = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def closed?
|
23
|
+
@closed
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(meth, *args, &blk)
|
27
|
+
@input.send(meth, *args, &blk)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Go::GTP do
|
32
|
+
def gtp(input = "=1", &commands)
|
33
|
+
@pipe = MockPipe.new(input)
|
34
|
+
return [Go::GTP.new(@pipe, &commands), @pipe.input, @pipe.output]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should send quit after running a provided block" do
|
38
|
+
ran = false
|
39
|
+
_, _, output = gtp do
|
40
|
+
ran = true
|
41
|
+
end
|
42
|
+
ran.should be(true)
|
43
|
+
output.string.should match(/\A\d+\s+quit\Z/)
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when connected" do
|
47
|
+
before :each do
|
48
|
+
@go, @input, @output = gtp
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_input(input)
|
52
|
+
@input << input
|
53
|
+
@input.rewind
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return an instance without sending commands without a block" do
|
57
|
+
@output.string.should be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should report the success of the last command" do
|
61
|
+
@go.clear_board
|
62
|
+
@go.should be_success
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not report success if the last command failed" do
|
66
|
+
add_input("?1 error message")
|
67
|
+
@go.clear_board
|
68
|
+
@go.should_not be_success
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should not have an error after a successful command" do
|
72
|
+
@go.clear_board.should be(true)
|
73
|
+
@go.last_error.should be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should have an error after an unsuccessful command" do
|
77
|
+
add_input("?1 error message")
|
78
|
+
@go.clear_board.should be(false)
|
79
|
+
@go.last_error.should == "error message"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should close the IO on quit" do
|
83
|
+
@pipe.should_not be_closed
|
84
|
+
@go.quit
|
85
|
+
@pipe.should be_closed
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return output from commands that return data" do
|
89
|
+
add_input("=1 2.0")
|
90
|
+
@go.protocol_version.should == "2.0"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return the success or failure of boolean operations" do
|
94
|
+
@go.boardsize(9).should == @go.success?
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should support the query interface for boolean operations" do
|
98
|
+
@go.boardsize?(9).should == @go.success?
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return lists of vertices in an array" do
|
102
|
+
add_input("=1 A1 B2")
|
103
|
+
@go.fixed_handicap(2) == %w[A1 B1]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should return colors as black, white, or nil" do
|
107
|
+
add_input("=1 black\n\n=2 white\n\n=3 empty")
|
108
|
+
@go.color("A1").should satisfy { |stone| stone == "black" }
|
109
|
+
@go.color("B1").should satisfy { |stone| stone == "white" }
|
110
|
+
@go.color("C1").should be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return boolean results as true and false" do
|
114
|
+
add_input("=1 1\n\n=2 0")
|
115
|
+
@go.is_legal?("black", "A1").should be(true)
|
116
|
+
@go.is_legal?("black", "B1").should be(false)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should return a move in an array" do
|
120
|
+
add_input("=1 white A1")
|
121
|
+
@go.last_move.should == %w[white A1]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should return moves in an array of arrays" do
|
125
|
+
add_input("=1\nwhite A1\nblack B1")
|
126
|
+
@go.move_history.should == [%w[white A1], %w[black B1]]
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should return board diagrams wrapped in an appropriate object" do
|
130
|
+
add_input("=1 board")
|
131
|
+
@go.showboard.should be_an_instance_of(Go::GTP::Board)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should support a dual interface for methods that can return data" do
|
135
|
+
add_input("=1 board\n\n=2")
|
136
|
+
@go.printsgf.should satisfy { |sgf| sgf == "board" }
|
137
|
+
@go.printsgf?("/path/to/file").should be(true)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should return line results in an array" do
|
141
|
+
add_input("=1 help\nknown_command")
|
142
|
+
@go.help.should == %w[help known_command]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should detect a resignation as a game over condition" do
|
146
|
+
add_input("=1 black RESIGN")
|
147
|
+
@go.should be_over
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should detect two passes as a game over condition" do
|
151
|
+
add_input("=1 black PASS\nwhite PASS")
|
152
|
+
@go.should be_over
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should not any other game over conditions" do
|
156
|
+
add_input("=1 black E4\nwhite PASS")
|
157
|
+
@go.should_not be_over
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should allow you to replay a series of moves" do
|
161
|
+
add_input("=1\n\n=2\n\n=3\n\n=4 black E6")
|
162
|
+
@go.replay(%w[E4 E5 E6]).should be(true)
|
163
|
+
@go.last_move.should == %w[black E6]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/spec/point_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require "go/gtp/point"
|
2
|
+
|
3
|
+
describe Go::GTP::Point do
|
4
|
+
it "should initialize from an x, y integer index pair" do
|
5
|
+
lambda { Go::GTP::Point.new(0, 2) }.should_not raise_error(ArgumentError)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should initialize from a GNU Go letter and integer pair" do
|
9
|
+
lambda { Go::GTP::Point.new("A12") }.should_not raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize from an SGF letter pair" do
|
13
|
+
lambda { Go::GTP::Point.new("ad") }.should_not raise_error(ArgumentError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should fail with an argmument error with any other arguments" do
|
17
|
+
lambda { Go::GTP::Point.new("junk") }.should raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "initialized from any format" do
|
21
|
+
before :all do
|
22
|
+
@points = [[0, 2], "A17", "ac"].map { |args| Go::GTP::Point.new(*args) }
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should track its indices" do
|
26
|
+
@points.each do |point|
|
27
|
+
point.x.should satisfy { |x| x == 0 }
|
28
|
+
point.y.should satisfy { |y| y == 2 }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should convert to indices" do
|
33
|
+
@points.each do |point|
|
34
|
+
point.to_indexes.should satisfy { |xy| xy == [0, 2] }
|
35
|
+
point.to_indices.should satisfy { |xy| xy == [0, 2] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should convert to SGF letters" do
|
40
|
+
@points.each do |point|
|
41
|
+
point.to_sgf.should == "ac"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should convert to GNU Go letters and numbers" do
|
46
|
+
@points.each do |point|
|
47
|
+
point.to_gnugo.should satisfy { |ln| ln == "A17" }
|
48
|
+
point.to_s.should satisfy { |ln| ln == "A17" }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "in the GNU Go format" do
|
54
|
+
it "should default to assuming a 19x19 board on creation" do
|
55
|
+
Go::GTP::Point.new("A13").to_indices.should == [0, 6]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should allow you to override board size on creation" do
|
59
|
+
Go::GTP::Point.new("A13", board_size: 13).to_indices.should == [0, 0]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should default to assuming a 19x19 board on conversion" do
|
63
|
+
Go::GTP::Point.new(0, 6).to_gnugo.should == "A13"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow you to override board size on conversion" do
|
67
|
+
Go::GTP::Point.new(0, 0).to_gnugo(13).should == "A13"
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should skip over the unused I column" do
|
71
|
+
Go::GTP::Point.new("H19").to_indices.should satisfy { |ln| ln == [7, 0] }
|
72
|
+
Go::GTP::Point.new("J19").to_indices.should satisfy { |ln| ln == [8, 0] }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "go/gtp"
|
2
|
+
|
3
|
+
describe Go::GTP, "when connecting to GNU Go" do
|
4
|
+
before :all do
|
5
|
+
Go::GTP::IO = mock
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should return a connected instance" do
|
9
|
+
Go::GTP::IO.should_receive(:popen)
|
10
|
+
Go::GTP.run_gnugo.should be_an_instance_of(Go::GTP)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should open the connection in reading and writing mode" do
|
14
|
+
Go::GTP::IO.should_receive(:popen) do |_, mode|
|
15
|
+
mode.should match(/\A[rw]\+\z/)
|
16
|
+
end
|
17
|
+
Go::GTP.run_gnugo
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should default to using no directory" do
|
21
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
22
|
+
path.should match(/\Agnugo\b/)
|
23
|
+
end
|
24
|
+
Go::GTP.run_gnugo
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow the directory to be overriden" do
|
28
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
29
|
+
path.should match(%r{\A/usr/local/bin/gnugo\b})
|
30
|
+
end
|
31
|
+
Go::GTP.run_gnugo(directory: "/usr/local/bin")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should default to using gnugo in GTP mode" do
|
35
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
36
|
+
path.should match(/\Agnugo\b.*--mode\s+gtp\b/)
|
37
|
+
end
|
38
|
+
Go::GTP.run_gnugo
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow the command to be overriden" do
|
42
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
43
|
+
path.should match(/\Amy_go\b/)
|
44
|
+
end
|
45
|
+
Go::GTP.run_gnugo(command: "my_go")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should default to using no arguments" do
|
49
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
50
|
+
path.should_not match(/--(?!mode\b)\w+/)
|
51
|
+
end
|
52
|
+
Go::GTP.run_gnugo
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should allow the arguments to be overriden" do
|
56
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
57
|
+
path.should match(/--boardsize\s+9\b/)
|
58
|
+
end
|
59
|
+
Go::GTP.run_gnugo(arguments: "--boardsize 9")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should default to redirecting STDERR to STDOUT" do
|
63
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
64
|
+
path.should match(/2>&1\z/)
|
65
|
+
end
|
66
|
+
Go::GTP.run_gnugo
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should allow the redirections to be overriden" do
|
70
|
+
Go::GTP::IO.should_receive(:popen) do |path, _|
|
71
|
+
path.should match(%r{2>/dev/null\z})
|
72
|
+
end
|
73
|
+
Go::GTP.run_gnugo(redirections: "2>/dev/null")
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: go_gtp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- James Edward Gray II
|
13
|
+
- Ryan Bates
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-02 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
description: This library runs GNU Go in a separate process and allows you to communicate with it using the Go Text Protocol (GTP). This makes it easy to manage full games of Go, work with SGF files, analyze Go positions, and more.
|
35
|
+
email:
|
36
|
+
- james@graysoftinc.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- .rvmrc
|
46
|
+
- README.markdown
|
47
|
+
- Rakefile
|
48
|
+
- TODO
|
49
|
+
- example/create_game.rb
|
50
|
+
- example/generate_move.rb
|
51
|
+
- example/play_all.rb
|
52
|
+
- example/play_go.rb
|
53
|
+
- example/play_move.rb
|
54
|
+
- example/show_board.rb
|
55
|
+
- go_gtp.gemspec
|
56
|
+
- lib/go/gtp.rb
|
57
|
+
- lib/go/gtp/board.rb
|
58
|
+
- lib/go/gtp/point.rb
|
59
|
+
- spec/board_spec.rb
|
60
|
+
- spec/gtp_spec.rb
|
61
|
+
- spec/point_spec.rb
|
62
|
+
- spec/run_gnugo_spec.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/JEG2/go_gtp
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 1
|
87
|
+
- 3
|
88
|
+
- 6
|
89
|
+
version: 1.3.6
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.7
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: A wrapper for GNU Go's Go Text Protocol (GTP).
|
97
|
+
test_files:
|
98
|
+
- spec/board_spec.rb
|
99
|
+
- spec/gtp_spec.rb
|
100
|
+
- spec/point_spec.rb
|
101
|
+
- spec/run_gnugo_spec.rb
|