2 ## -*-Ruby-*- $RCSfile$ $Revision$ $Name$
4 ## Copyright (C) 2004 773@2ch
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, write to the Free Software
18 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 DEFAULT_TIMEOUT = 10 # for single socket operation
22 Least_Time_Per_Move = 1
23 Watchdog_Time = 30 # time for ping
24 Agree_Time = 300 # time for AGREE
25 Login_Time = 300 # time for LOGIN
27 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
28 Release.concat("-") if (Release == "")
29 Revision = "$Revision$".gsub(/[^\.\d]/, '')
40 TCPSocket.do_not_reverse_lookup = true
43 def gets_timeout(t = DEFAULT_TIMEOUT)
63 return self.write(str)
78 @hash[player.name] = player
81 @hash.delete(player.name)
83 def duplicated?(player)
84 if (@hash[player.name])
96 def initialize(str, socket)
100 @state = "connected" # wait_game -> game
102 @x1 = false # extention protocol
103 @eol = "\m" # favorite eol code
107 @watchdog_thread = nil
112 attr_accessor :name, :password, :socket, :state
113 attr_accessor :x1, :eol, :game, :mytime, :watchdog_thread
117 @socket.write_safe(str + @eol)
124 (login, @name, @password, ext) = str.split
130 log_message(sprintf("user %s run in x1 mode", @name))
131 write_safe("## LOGIN in x1 mode")
133 log_message(sprintf("user %s run in CSA mode", @name))
136 while (str = @socket.gets_safe)
141 when /^%%GAME\s+(\S+)\s+([\+\-])/
143 @state = "game_waiting"
151 rival = LEAGUE.get_player(game_name, rival_sente)
154 LEAGUE.start_game(game_name, self, rival)
156 when /^%%CHAT\s+(\S+)/
158 LEAGUE.hash.each do |name, player|
159 s = player.write_safe(sprintf("## [%s] %s", @name, message))
160 player.status = "zombie" if (! s)
163 LEAGUE.hash.each do |name, player|
164 write_safe(sprintf("## %s %s", name, player.state))
169 write_safe(sprintf("## unknown command %s", str))
179 def initialize(event, sente, gote)
180 @id = sprintf("%s-%s-%s-%s", event, sente.name, gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
181 @logfile = @id + ".csa"
187 @currnet_player = sente
190 printf("%s: new game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
194 @sente.watchdog_start(Watchdog_Time)
195 @gote.watchdog_start(Watchdog_Time)
197 @fh = open(@logfile, "w")
201 @fh.printf("N+%s\n", @sente.name)
202 @fh.printf("N-%s\n", @gote.name)
203 @fh.printf("$EVENT:%s\n", @id)
204 @sente.write(start_message("+"))
205 @gote.write(start_message("-"))
206 @sente.wait_agree(Agree_Time)
207 @gote.wait_agree(Agree_Time)
209 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
211 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
212 P2 * -HI * * * * * -KA *
213 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
217 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
218 P8 * +KA * * * * * +HI *
219 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
223 @sente.write(sprintf("START:%s\n", @id))
224 @gote.write(sprintf("START:%s\n", @id))
226 @currnet_player = @sente
228 handle_one_move(@currnet_player, @next_player)
230 @currnet_player = @gote
231 @next_player = @sente
232 handle_one_move(@currnet_player, @next_player)
234 rescue ShogiWatchdogTimeout
235 sg_flag_of_timeout = $!.message
236 if (sg_flag_of_timeout == "+")
243 printf("watchdog timeout by %s\n", loser.name)
244 loser.write("#TIME_UP\n#LOSE\n")
245 winner.write("#TIME_UP\n#WIN\n")
246 rescue TimeoutError, ShogiTimeout
247 printf("%s: end timeup by %s\n", Time::new.to_s, @currnet_player.name)
248 @currnet_player.write("#TIME_UP\n#LOSE\n")
249 @next_player.write("#TIME_UP\n#WIN\n")
252 printf("%s: reject by %s\n", Time::new.to_s, sender)
253 str = sprintf("REJECT:%s by %s\n", @id, sender)
256 rescue ShogiIllegalMove
257 printf("%s: end illegal move by %s\n", Time::new.to_s, @currnet_player.name)
259 @fh.printf("%%ERROR\n")
260 @currnet_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#LOSE\n", move))
261 @next_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#WIN\n", move))
263 printf("%s: end by %s\n", Time::new.to_s, @currnet_player.name)
267 @currnet_player.write(sprintf("%s\n#RESIGN\n#LOSE\n", move))
268 @next_player.write(sprintf("%s\n#RESIGN\n#WIN\n", move))
270 @currnet_player.write(sprintf("%s\n#JISHOGI\n#WIN\n", move))
271 @next_player.write(sprintf("%s\n#JISHOGI\n#LOSE\n", move))
274 @fh.printf("'END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
277 def handle_one_move(current_player, next_player)
278 start_time = Time::new
279 str = current_player.get_move
280 @fh.printf("%s\n", str)
282 time = (end_time - start_time).truncate
283 time = Least_Time_Per_Move if (time < Least_Time_Per_Move)
284 current_player.sub_time(time)
285 raise ShogiEnd, str if (str =~ /\A%/)
286 @sente.write(sprintf("%s,T%d\n", str, time))
287 @gote.write(sprintf("%s,T%d\n", str, time))
288 @fh.printf("T%s\n", time)
295 printf("%s: end game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
298 def start_message(sg_flag)
310 Total_Time:#{Total_Time}
311 Least_Time_Per_Move:#{Least_Time_Per_Move}
314 Jishogi_Declaration:1.1
315 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
316 P2 * -HI * * * * * -KA *
317 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
321 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
322 P8 * +KA * * * * * +HI *
323 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
335 shogi-server - server for CSA server protocol
338 shogi-server event_name port_number
341 server for CSA server protocol
345 specify filename for logging process ID
348 this file is distributed under GPL version2 and might be compiled by Exerb
361 printf("%s message: %s\n", Time::new.to_s, str)
365 printf("%s message: %s\n", Time::new.to_s, str)
369 printf("%s error: %s\n", Time::new.to_s, str)
373 def parse_command_line
375 parser = GetoptLong.new
376 parser.ordering = GetoptLong::REQUIRE_ORDER
378 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
381 parser.each_option do |name, arg|
382 options[name] = arg.dup
386 raise parser.error_message
394 return false if (str !~ /^LOGIN /)
396 if ((tokens.length == 3) || (tokens.length == 4))
405 $options = parse_command_line
406 if (ARGV.length != 2)
413 Thread.abort_on_exception = true
415 server = TCPserver.open(port)
416 log_message("server started")
419 Thread::start(server.accept) do |client|
421 while (str = client.gets_timeout(Login_Time))
422 Thread::kill(Thread::current) if (! str) # disconnected
425 if (good_login?(str))
426 player = Player::new(str, client)
427 if (LEAGUE.duplicated?(player))
428 client.write_safe(sprintf("username %s is already connected", player.name))
434 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol)
437 log_message(sprintf("user %s login", player.name))
439 LEAGUE.delete(player)
440 log_message(sprintf("user %s logout", player.name))