kayabot 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.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Gabriel Benmergui
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,28 @@
1
+ This is the official Gem to run a bot in Kaya.
2
+
3
+ Right now only gnubot is default and has to be installed.
4
+
5
+ Feel free to expand the gem to allow other bots with GTP interface, should work plug-play easily.
6
+
7
+
8
+ On Ubuntu:
9
+ sudo apt-get install gnugo
10
+
11
+ To install :
12
+ gem install kayabot
13
+ To run:
14
+ kayabot my_configuration.yaml
15
+
16
+ The configuration yaml must look like this:
17
+
18
+ url : "http://alpha.kaya.gs:9292"
19
+ user : "MyBot"
20
+ pass : "MyBot's password"
21
+
22
+ Ask for a bot account at info@kaya.gs to run it!
23
+
24
+ You can get a personalized bot account and if you run many games you can get a collaborator account.
25
+
26
+
27
+
28
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ #nothing yet
data/bin/kayabot ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("../lib/sgf", File.dirname(__FILE__))
4
+ require 'rubygems'
5
+ require 'mechanize'
6
+ require 'json'
7
+ require 'yaml'
8
+ require File.expand_path("../lib/kayabot", File.dirname(__FILE__))
9
+ require File.expand_path("../lib/gg", File.dirname(__FILE__))
10
+ $0='kayabot'
11
+
12
+
13
+ @url = ""
14
+
15
+ if ARGV.length > 0
16
+ if ARGV.length == 1
17
+ config_file = ARGV.first
18
+ if File.exists?(config_file)
19
+ config = YAML.load_file(config_file)
20
+ if config["url"] && config["user"] && config["pass"]
21
+ k = KayaBot.new(config["url"], config["user"], config["pass"])
22
+ k.listener_loop
23
+ else
24
+ puts "Invalid config file."
25
+ end
26
+ else
27
+ puts "File does not exist."
28
+ end
29
+ elsif ARGV.length >= 3
30
+ k = KayaBot.new(ARGV[0], ARGV[1], ARGV[2])
31
+ k.listener_loop
32
+ end
33
+ else
34
+ puts "Usage:\n - kayabot <yaml_config_file>\n - kayabot <url> <bot_nickname> <password>"
35
+ end
data/kayabot.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "kayabot"
3
+ s.version = "0.1.0"
4
+ s.summary = "Interface to run Go Bots on Kaya"
5
+ s.description = "This is an interface to run GTP compliant Go Bots on Kaya."
6
+ s.authors = ["Gabriel Benmergui"]
7
+ s.email = ["gabriel.benmergui@kaya.gs"]
8
+ s.homepage = "http://github.com/conanbatt/OpenKaya"
9
+
10
+ s.executables.push("kayabot")
11
+
12
+ s.add_dependency("mechanize")
13
+ s.add_dependency("json")
14
+
15
+ s.files = Dir[
16
+ "README.markdown",
17
+ "LICENSE",
18
+ "Rakefile",
19
+ "lib/**/*.rb",
20
+ "*.gemspec",
21
+ ]
22
+ end
data/lib/gg.rb ADDED
@@ -0,0 +1,118 @@
1
+ class GTP
2
+
3
+ def self.run(bot_type, &command)
4
+ if bot_type == 'gnugo'
5
+ new(IO.popen("gnugo --mode gtp", "r+"), &command)
6
+ elsif bot_type == 'fuego'
7
+ raise "not supported yet"
8
+ # new(IO.popen("fuego --config #{RAILS_ROOT}/public/fuego.gtp", "r+"), &command)
9
+ end
10
+ end
11
+
12
+ def initialize(io)
13
+ @io = io
14
+
15
+ if block_given?
16
+ begin
17
+ yield self
18
+ ensure
19
+ quit
20
+ end
21
+ end
22
+ end
23
+
24
+ def quit
25
+ send_command(:quit)
26
+ @io.close
27
+ end
28
+
29
+ def protocol_version
30
+ send_command(:protocol_version)
31
+ end
32
+
33
+ def genmove(color)
34
+ send_command(:genmove, color)
35
+ end
36
+
37
+ def loadsgf(path)
38
+ send_command(:loadsgf, path)
39
+ end
40
+
41
+ def list_stones(color)
42
+ @io.puts [:list_stones, color].join(" ")
43
+ result = @io.take_while { |line| line != "\n" }.join
44
+ return result.sub(/^=\s/, "").sub(/\n/, "")
45
+ end
46
+
47
+ def send_command(command, *arguments)
48
+ @io.puts [command, *arguments].join(" ")
49
+ result = @io.take_while { |line| line != "\n" }.join
50
+
51
+ rc = result.scan(/^=\s[a-zA-Z]*[0-9]*$/)
52
+ if rc.first.nil?
53
+ return rc
54
+ else
55
+ return rc.first.sub(/^=\s/, "").sub(/\n/, "")
56
+ end
57
+ end
58
+ end
59
+
60
+ SGF_FILE_PATH = "gnugo_games/"
61
+
62
+ def score_game(game_id, game_sgf)
63
+ re = nil
64
+ if not Dir.exists?(SGF_FILE_PATH)
65
+ Dir.mkdir(SGF_FILE_PATH)
66
+ end
67
+ filepath = SGF_FILE_PATH + "#{game_id}.sgf"
68
+ File.open(filepath, "w") do |f|
69
+ f.write game_sgf
70
+ end
71
+
72
+ IO.popen("gnugo --score aftermath #{filepath}") do |f|
73
+ re = f.read
74
+ end
75
+
76
+ File.delete(filepath)
77
+ return re
78
+ end
79
+
80
+ # return a move coordinates, such as "c17", or "PASS", or "resign"
81
+ def ai_move(game_id, game_sgf, color)
82
+
83
+ re = nil
84
+ if not Dir.exists?(SGF_FILE_PATH)
85
+ Dir.mkdir(SGF_FILE_PATH)
86
+ end
87
+ filepath = SGF_FILE_PATH + "#{game_id}.sgf"
88
+ File.open(filepath, "w") do |f|
89
+ f.write game_sgf
90
+ end
91
+
92
+ #why this?
93
+ if color == 'black' || color == 'white'
94
+ game_bot = 'gnugo'
95
+ end
96
+ GTP.run(game_bot) do |gtp|
97
+ gtp.loadsgf filepath
98
+ re = gtp.genmove color
99
+ end
100
+ p re
101
+ move = convert_move(re)
102
+
103
+ File.delete(filepath)
104
+ return move
105
+ end
106
+
107
+ #switches GTP move into sgf-like coordinate
108
+ def convert_move(move)
109
+ if move == "PASS"
110
+ return 'pass'
111
+ elsif move == "resign"
112
+ return 'resign'
113
+ else
114
+ alphabet = "ABCDEFGHIJKLMNOPQRS"
115
+ return alphabet["ABCDEFGHJKLMNOPQRST".index(move[0])].downcase + alphabet.reverse[(move[1].to_s + move[2].to_s).to_i - 1].downcase
116
+ end
117
+ end
118
+
data/lib/kayabot.rb ADDED
@@ -0,0 +1,98 @@
1
+ require File.expand_path("sgf", File.dirname(__FILE__))
2
+ require 'rubygems'
3
+ require 'mechanize'
4
+ require 'json'
5
+ require 'yaml'
6
+ $0='kayabot'
7
+
8
+ class KayaBot
9
+
10
+ PLAY_URL = "/bot/play"
11
+ OPEN_GAME_URL = "/bot/open_game"
12
+ RESIGN_URL = "/bot/resign"
13
+ SCORE_URL = "/bot/score"
14
+
15
+ attr_accessor :challenger, :status, :move
16
+
17
+ def initialize(server_url, user, pass)
18
+ @server_url = server_url
19
+ @user = user
20
+ @pass = pass
21
+ @agent = Mechanize.new
22
+ @status
23
+ @sgf
24
+ end
25
+
26
+ def game_configuration
27
+ {:title => "Come at me bro"}
28
+ end
29
+
30
+ def connect
31
+ page = @agent.post(@server_url+ "/session/create", {:id => @user, :password => @pass})
32
+ end
33
+
34
+ TIME_LAPSE = 4
35
+
36
+ def listener_loop
37
+ connect
38
+ while (true) do
39
+ fetch_and_parse_data
40
+ open_game if @status=="connected" || @status=="finished"
41
+ post_score if @status=="scoring"
42
+ post_move if @bots_turn && @status=="playing"
43
+ sleep TIME_LAPSE #lets not explode in requests
44
+ end
45
+ end
46
+
47
+ def fetch_and_parse_data
48
+ page = @agent.get(@server_url + "/bot/status")
49
+ json = JSON.parse(page.body)
50
+ p json
51
+ @status = json["status"]
52
+ @move = json["moves"]
53
+ @bots_turn = json["bot_play?"]
54
+ @color = json["next"]
55
+ @master_node = json["sgf_master_node"]
56
+ end
57
+
58
+ def open_game
59
+ @agent.post(@server_url+ OPEN_GAME_URL, game_configuration)
60
+ end
61
+ def post_move
62
+ #TODO should insert master node . Need handi/komi
63
+ bot_move = ai_move("temp",sgf_content, @color)
64
+ if (bot_move == "resign")
65
+ resign
66
+ else
67
+ color_short = (@color=="black" ? "B" : "W")
68
+ bot_move = "" if bot_move == "pass"
69
+ bot_move = ";#{color_short}[#{bot_move}]#{color_short}L[#{(25*60) - (@move ? @move.count(";") : 1)}]"
70
+ @agent.post(@server_url+ PLAY_URL,
71
+ :move => bot_move)
72
+ #@sgf.add_move(bot_move)
73
+ @move = nil
74
+ end
75
+ end
76
+ def resign
77
+ @agent.post(@server_url+ RESIGN_URL, {:result => "resign"})
78
+ end
79
+ def post_score
80
+ result = score_game("temp", sgf_content)
81
+ p result
82
+ @agent.post(@server_url+ SCORE_URL, {:score => parse_result_from_bot(result)})
83
+ end
84
+
85
+ #Black wins by 61.5 points
86
+ def parse_result_from_bot(result)
87
+ color = result[0]
88
+ points = result.match(/\d{0,3}\.\d/)[0]
89
+ return "#{color}+#{points}"
90
+ end
91
+
92
+ def sgf_content
93
+ @sgf = SGF.new
94
+ @sgf.add_move(@move) if @move
95
+ return "(#{@master_node}#{@sgf.move_list})"
96
+ end
97
+
98
+ end
data/lib/node.rb ADDED
@@ -0,0 +1,150 @@
1
+ class Node
2
+
3
+ attr_reader :node_text, :children, :parent
4
+
5
+ def initialize(parent,node_text= "")
6
+ validate_node_format(node_text)
7
+ @node_text = node_text
8
+ @comments = []
9
+ @children = []
10
+ @parent = parent
11
+ @parent.add_child(self) if parent
12
+ end
13
+
14
+ def add_child(node)
15
+ @children.push(node)
16
+ end
17
+
18
+ def to_move_list
19
+ children_to_s
20
+ end
21
+
22
+ def children_to_s(with_comments=false)
23
+ branches = ""
24
+ if @children.size > 1
25
+ @children[1..-1].each {|node| branches += "(#{node.children_to_s(with_comments)})"}
26
+ end
27
+ children_text =""
28
+ if @children.first
29
+ children_text = @children.first.children_to_s(with_comments)
30
+ end
31
+ comment_node = with_comments ? comments : ""
32
+ node_text + comment_node + branches + children_text
33
+ end
34
+
35
+ def to_s
36
+ children_to_s(true)
37
+ end
38
+
39
+ def add_comment(comment)
40
+ @comments << (comment + "\n")
41
+ end
42
+ def comments
43
+ @comments.empty? ? "" : "C[#{@comments.join.gsub("]","\\]").gsub(")","\\)")}]"
44
+ end
45
+
46
+ def color
47
+ @node_text[1]
48
+ end
49
+
50
+ def x
51
+ @node_text[3] unless pass_node?
52
+ end
53
+ def y
54
+ @node_text[4] unless pass_node?
55
+ end
56
+ def coordinate
57
+ x+y
58
+ end
59
+ def pass_node?
60
+ @node_text.match(/[BW]\[\]/)
61
+ end
62
+
63
+ def validate_node_format(node)
64
+ valid = node.match(/;[BW]\[(|[a-z][a-z])\]/)
65
+ if node.include?("BL") || node.include?("WL")
66
+ valid = valid && node.match(/[BW]L\[\d{0,6}.\d{3}\]/)
67
+ end
68
+ raise "#{node} is invalid node format" unless valid
69
+ end
70
+
71
+ def time_left
72
+ @node_text.match(/[#{color}]L\[\d{0,6}.\d{3}\]/).to_s[3..-2].to_f
73
+ end
74
+ def time_left=(time_left)
75
+ @node_text.gsub!(/[#{color}]L\[\d{0,6}.\d{3}\]/, "#{color}L[%.3f]" % [time_left])
76
+ end
77
+
78
+
79
+ end
80
+
81
+ class ConfigNode
82
+
83
+ attr_accessor :node_text, :children
84
+
85
+ def initialize(property="")
86
+ @node_text = property.dup
87
+ write_property(:file_format,4)
88
+ handicap = property(:handicap)
89
+ @comments = []
90
+ @children = []
91
+ end
92
+
93
+ def parent
94
+ #stub
95
+ end
96
+
97
+ def add_child(node)
98
+ @children.push(node)
99
+ end
100
+
101
+ def validate_node_format
102
+ return true
103
+ end
104
+
105
+ def add_comment(comment)
106
+ @comments << (comment + "\n")
107
+ end
108
+ def comments
109
+ @comments.empty? ? "" : "C[#{@comments.join.gsub("]","\\]").gsub(")","\\)")}]"
110
+ end
111
+
112
+ def to_s
113
+ children_text = @children.first.to_s
114
+ ";"+node_text + comments + children_text
115
+ end
116
+
117
+ def to_move_list
118
+ children_text = @children.first.to_move_list
119
+ ";"+node_text + children_text
120
+ end
121
+
122
+ METALABELS= {:black_rank => "BR", :white_rank => "WR",:white_player => "PW", :black_player => "PB",
123
+ :komi => "KM", :date => "DT", :result => "RE",
124
+ :file_format => "FF", :black_country => "BC",
125
+ :white_country => "WC", :event => "EV", :source => "SO",
126
+ :encoding => "CA", :size => "SZ", :rules => "RU", :time_set => "OT",:handicap => "HA"}
127
+
128
+ def property(symbol)
129
+ return node_text if symbol == :all
130
+ dup = node_text.dup
131
+ dup.slice!(/.*#{METALABELS[symbol]}\[/)
132
+ return nil if dup.length == node_text.length #means it wasnt found
133
+ dup.slice!(/\].*/)
134
+ return dup
135
+ end
136
+
137
+ def write_property(symbol, value)
138
+ return unless value
139
+ raise "Invalid property #{symbol}" unless METALABELS[symbol]
140
+ node = "#{METALABELS[symbol]}[#{value}]"
141
+ @node_text.gsub!(/#{METALABELS[symbol]}\[\w*\]/, "") #in case it already had it
142
+ @node_text = node + @node_text
143
+ size = property(:size)
144
+ #a little hackish to insert the AB node only
145
+ @node_text += SGF.handi_node(property(:size),value)[5..-1] if(size &&
146
+ symbol == :handicap)
147
+ end
148
+
149
+ end
150
+
data/lib/sgf.rb ADDED
@@ -0,0 +1,233 @@
1
+ require File.expand_path("node", File.dirname(__FILE__))
2
+
3
+ class SGF
4
+
5
+ BLACK = "B"
6
+ WHITE = "W"
7
+ attr_accessor :move_list, :comment_buffer,:property, :focus
8
+
9
+ def initialize(moves="", properties={})
10
+ moves ||= ""
11
+ @config = ConfigNode.new
12
+ @focus = @config
13
+ nodify_move_list(moves) unless moves.empty?
14
+ @comment_buffer = ""
15
+ @size = properties[:size]
16
+ properties.keys.each {|k| @config.write_property(k,properties[k]) }
17
+ @config.write_property(:handicap, properties[:handicap])
18
+ end
19
+
20
+ def last_two_moves_are_pass?
21
+ if @focus && @focus.parent
22
+ return @focus.pass_node? && @focus.parent.pass_node?
23
+ end
24
+ false
25
+ end
26
+
27
+ def nodify_move_list(moves)
28
+ moves.split(";").each do |txt|
29
+ add_move(";"+txt) unless txt.empty?
30
+ end
31
+ end
32
+
33
+ def add_move(node) #TODO objetify node
34
+ @focus = Node.new(@focus,node)
35
+ move_list
36
+ end
37
+
38
+ def last_play_color
39
+ @focus != @config && @focus.color
40
+ end
41
+
42
+ def add_comment(comment)
43
+ if @focus
44
+ @focus.add_comment(comment)
45
+ else
46
+ @config.add_comment(comment)
47
+ end
48
+ move_list
49
+ end
50
+ #takes a hash and inputs the contents into the nodes
51
+ def parse_comments!(comments)
52
+ comments.each do |key, value|
53
+ if (key.to_i == 0)
54
+ value.each {|v| @config.add_comment(hash_to_comment(v))}
55
+ next
56
+ end
57
+ value.each{ |v| move_by_number(key.to_i-1) && move_by_number(key.to_i-1).add_comment(hash_to_comment(v))}
58
+ end
59
+ end
60
+
61
+ def hash_to_comment(hash)
62
+ raise "invalid hash" unless hash["user"] && hash["rank"] && hash["message"]
63
+ "#{hash["user"]}#{hash["rank"]}: #{hash["message"]}"
64
+ end
65
+
66
+ def move_list
67
+ @config.children.first.to_move_list unless @config.children.empty?
68
+ end
69
+
70
+ def move_list_with_comments
71
+ @config.children.first.to_s
72
+ end
73
+
74
+ def move_by_number(index)
75
+ index = index.to_i
76
+ return if (index < 0)
77
+ node = @config.children.first
78
+ while(index >0)
79
+ node = node.children.first
80
+ index -= 1
81
+ end
82
+ node
83
+ end
84
+
85
+ #light validation to make sure the input is not totally bs. only makes sure the coordinate is in the board
86
+ def validate_coordinate(x, y)
87
+ lower_boundary = 97
88
+ upper_boundary = 97+ @size.to_i
89
+ valid_y_axis = y.bytes.first <= upper_boundary && y.bytes.first >= lower_boundary
90
+ valid_x_axis = x.bytes.first <= upper_boundary && x.bytes.first >= lower_boundary
91
+ throw "Invalid coordinate #{x},#{y}" unless valid_y_axis && valid_x_axis
92
+ end
93
+
94
+ def validate_node_format(node)
95
+ valid = node.match(/;[BW]\[(|[a-z][a-z])\]/)
96
+ if node.include?("BL") || node.include?("WL")
97
+ valid = valid && node.match(/[BW]L\[\d{0,6}.\d{3}\]/)
98
+ end
99
+ raise "#{node} is invalid node format" unless valid
100
+ end
101
+
102
+ def load_file(filename)
103
+ sgf =""
104
+ File.open(filename, 'r') do |file|
105
+ while (line = file.gets)
106
+ sgf += line
107
+ end
108
+ load_from_string(sgf)
109
+ end
110
+ end
111
+ def load_from_string(input)
112
+ properties= input.split(";")[1]
113
+ @focus = @config = ConfigNode.new(properties) #will process this later
114
+ nodify_move_list(input.gsub(properties, "").chomp[2..-2])
115
+ end
116
+
117
+ def properties
118
+ @config.to_s
119
+ end
120
+
121
+ def properties=(arg)
122
+ @config.node_text = arg
123
+ end
124
+
125
+ def property(symbol)
126
+ @config.property(symbol)
127
+ end
128
+
129
+ def write_property(symbol, value)
130
+ @config.write_property(symbol, value)
131
+ end
132
+
133
+ def to_s
134
+ "(#{@config.to_s})"
135
+ end
136
+
137
+ def time_left(player)
138
+ raise "Invalid input #{player}. W or B expected" unless player == "B" || player == "W"
139
+ ln = last_node_by_player(player)
140
+ ln && ln.time_left
141
+ end
142
+
143
+ def add_time(player,time)
144
+ ln = last_node_by_player(player)
145
+ ln.time_left= (ln.time_left + time)
146
+ end
147
+
148
+ def last_node_by_player(player)
149
+ return if @focus == @config
150
+ if @focus.color == player
151
+ return @focus
152
+ elsif @focus.parent
153
+ @focus.parent
154
+ end
155
+ end
156
+
157
+ def undo
158
+ to_del = @focus
159
+ @focus = @focus.parent
160
+ @focus.children.delete(to_del)
161
+ end
162
+
163
+ def self.handi_node(size,handicap)
164
+ case size.to_i
165
+
166
+ when 19
167
+ case handicap
168
+ when 2
169
+ return "HA[2]AB[dd][pp]"
170
+ when 3
171
+ return "HA[3]AB[dd][dp][pd]"
172
+ when 4
173
+ return "HA[4]AB[dd][pd][dp][pp]"
174
+ when 5
175
+ return "HA[5]AB[dd][pd][dp][pp][jj]"
176
+ when 6
177
+ return "HA[6]AB[dd][pd][dp][pp][dj][pj]"
178
+ when 7
179
+ return "HA[7]AB[dd][pd][dp][pp][dj][pj][jj]"
180
+ when 8
181
+ return "HA[8]AB[dd][jd][pd][dj][pj][dp][jp][pp]"
182
+ when 9
183
+ return "HA[9]AB[dd][jd][pd][dj][jj][pj][dp][jp][pp]"
184
+ else
185
+ raise "Invalid handicap setting #{handicap}"
186
+ end
187
+ when 13
188
+ case handicap
189
+ when 2
190
+ return "HA[2]AB[dd][jj]"
191
+ when 3
192
+ return "HA[3]AB[dd][dj][jd]"
193
+ when 4
194
+ return "HA[4]AB[dd][jd][dj][jj]"
195
+ when 5
196
+ return "HA[5]AB[dd][jd][dj][gg][jj]"
197
+ when 6
198
+ return "HA[6]AB[dd][jd][dj][jj][dg][jg]"
199
+ when 7
200
+ return "HA[7]AB[dd][jd][dj][jj][dg][jg][gg]"
201
+ when 8
202
+ return "HA[8]AB[dd][jd][dj][gj][jj][jg][gd][dg]"
203
+ when 9
204
+ return "HA[9]AB[dd][jd][dj][gj][jj][jg][gg][gd][dg]"
205
+ else
206
+ raise "Invalid handicap setting #{handicap}"
207
+ end
208
+ when 9
209
+ case handicap
210
+ when 2
211
+ return "HA[2]AB[cc][gg]"
212
+ when 3
213
+ return "HA[3]AB[cc][cg][gg]"
214
+ when 4
215
+ return "HA[4]AB[cc][gg][cg][gc]"
216
+ when 5
217
+ return "HA[5]AB[cc][gg][cg][gc][ee]"
218
+ when 6
219
+ return "HA[6]AB[cc][gg][cg][gc][ee][ge]"
220
+ when 7
221
+ return "HA[7]AB[cc][gg][cg][gc][ee][ge][ee]"
222
+ when 8
223
+ return "HA[8]AB[cc][gc][cg][gg][ce][ge][ec][eg]"
224
+ when 9
225
+ return "HA[9]AB[cc][gc][cg][gg][ce][ge][ec][ee][eg]"
226
+ else
227
+ raise "Invalid handicap setting #{handicap}"
228
+ end
229
+ end
230
+ raise "Invalid handicap setting Size: #{size} and handicap #{handicap}"
231
+ end
232
+ end
233
+
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kayabot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gabriel Benmergui
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mechanize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: This is an interface to run GTP compliant Go Bots on Kaya.
47
+ email:
48
+ - gabriel.benmergui@kaya.gs
49
+ executables:
50
+ - kayabot
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - README.markdown
55
+ - LICENSE
56
+ - Rakefile
57
+ - lib/kayabot.rb
58
+ - lib/node.rb
59
+ - lib/sgf.rb
60
+ - lib/gg.rb
61
+ - kayabot.gemspec
62
+ - bin/kayabot
63
+ homepage: http://github.com/conanbatt/OpenKaya
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.23
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Interface to run Go Bots on Kaya
87
+ test_files: []