OSDN Git Service

Floodgate's thread 10 sec. Do not show old players.
[shogi-server/shogi-server.git] / shogi-server
1 #! /usr/bin/env ruby
2 ## $Id$
3
4 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
5 ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
6 ##
7 ## This program is free software; you can redistribute it and/or modify
8 ## it under the terms of the GNU General Public License as published by
9 ## the Free Software Foundation; either version 2 of the License, or
10 ## (at your option) any later version.
11 ##
12 ## This program is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ## GNU General Public License for more details.
16 ##
17 ## You should have received a copy of the GNU General Public License
18 ## along with this program; if not, write to the Free Software
19 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 require 'getoptlong'
22 require 'thread'
23 require 'timeout'
24 require 'socket'
25 require 'yaml'
26 require 'yaml/store'
27 require 'digest/md5'
28 require 'webrick'
29 require 'fileutils'
30
31
32 class TCPSocket
33   def gets_timeout(t = Default_Timeout)
34     begin
35       timeout(t) do
36         return self.gets
37       end
38     rescue TimeoutError
39       return nil
40     rescue
41       return nil
42     end
43   end
44   def gets_safe(t = nil)
45     if (t && t > 0)
46       begin
47         timeout(t) do
48           return self.gets
49         end
50       rescue TimeoutError
51         return :timeout
52       rescue Exception => ex
53         log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
54         return :exception
55       end
56     else
57       begin
58         return self.gets
59       rescue
60         return nil
61       end
62     end
63   end
64   def write_safe(str)
65     begin
66       return self.write(str)
67     rescue
68       return nil
69     end
70   end
71 end
72
73
74 module ShogiServer # for a namespace
75
76 Max_Write_Queue_Size = 1000
77 Max_Identifier_Length = 32
78 Default_Timeout = 60            # for single socket operation
79
80 Default_Game_Name = "default-1500-0"
81
82 One_Time = 10
83 Least_Time_Per_Move = 1
84 Login_Time = 300                # time for LOGIN
85
86 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
87 Release.concat("-") if (Release == "")
88 Revision = "$Revision$".gsub(/[^\.\d]/, '')
89
90
91
92 class League
93
94   class Floodgate
95     class << self
96       def game_name?(str)
97         return /^floodgate-\d+-\d+$/.match(str) ? true : false
98       end
99     end
100
101     def initialize(league)
102       @league = league
103       @next_time = nil
104       charge
105     end
106
107     def run
108       @thread = Thread.new do
109         Thread.pass
110         while (true)
111           begin
112             sleep(10)
113             next if Time.now < @next_time
114             match_game
115             charge
116           rescue Exception => ex 
117             # ignore errors
118             log_error("[in Floodgate's thread] #{ex}")
119           end
120         end
121       end
122     end
123
124     def shutdown
125       @thread.kill if @thread
126     end
127
128     # private
129
130     def charge
131       now = Time.now
132       if now.min < 30
133         @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30)
134       else
135         @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600
136       end
137       # for test
138       # if now.sec < 30
139       #   @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
140       # else
141       #   @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
142       # end
143     end
144
145     def match_game
146       players = @league.find_all_players do |pl|
147         pl.status == "game_waiting" &&
148         Floodgate.game_name?(pl.game_name) &&
149         pl.sente == nil
150       end
151       random_match(players)
152     end
153
154     def random_match(players)
155       if players.size < 2
156         log_message("Floodgate: too few players [%d]" % [players.size])
157         return
158       end
159       log_message("Floodgate: found %d players. Making games..." % [players.size])
160       random_players = players.sort{ rand < 0.5 ? 1 : -1 }
161       pairs = [[random_players.shift]]
162       while !random_players.empty? do
163         if pairs.last.size < 2
164           pairs.last << random_players.shift
165         else
166           pairs << [random_players.shift]
167         end 
168       end
169       if pairs.last.size < 2
170         pairs.pop
171       end
172       pairs.each do |pair|
173         start_game(pair.first, pair.last)
174       end
175     end
176
177     def start_game(p1, p2)
178       p1.sente = true
179       p2.sente = false
180       Game.new(p1.game_name, p1, p2)
181     end
182   end # class Floodgate
183
184   def initialize
185     @mutex = Mutex.new # guard @players
186     @games = Hash::new
187     @players = Hash::new
188     @event = nil
189     @dir = File.dirname(__FILE__)
190     @floodgate = Floodgate.new(self)
191     @floodgate.run
192   end
193   attr_accessor :players, :games, :event, :dir
194
195   def shutdown
196     @floodgate.shutdown
197   end
198
199   # this should be called just after instanciating a League object.
200   def setup_players_database
201     @db = YAML::Store.new(File.join(@dir, "players.yaml"))
202   end
203
204   def add(player)
205     self.load(player) if player.id
206     @mutex.synchronize do
207       @players[player.name] = player
208     end
209   end
210   
211   def delete(player)
212     @mutex.synchronize do
213       @players.delete(player.name)
214     end
215   end
216
217   def find_all_players
218     found = nil
219     @mutex.synchronize do
220       found = @players.find_all do |name, player|
221         yield player
222       end
223     end
224     return found.map {|a| a.last}
225   end
226   
227   def get_player(status, game_name, sente, searcher)
228     found = nil
229     @mutex.synchronize do
230       found = @players.find do |name, player|
231         (player.status == status) &&
232         (player.game_name == game_name) &&
233         ( (sente == nil) || 
234           (player.sente == nil) || 
235           (player.sente == sente) ) &&
236         (player.name != searcher.name)
237       end
238     end
239     return found ? found.last : nil
240   end
241   
242   def load(player)
243     hash = search(player.id)
244     if hash
245       # a current user
246       player.name         = hash['name']
247       player.rate         = hash['rate']
248       player.modified_at  = hash['last_modified']
249       player.rating_group = hash['rating_group']
250     end
251   end
252
253   def search(id)
254     hash = nil
255     @db.transaction do
256       break unless  @db["players"]
257       @db["players"].each do |group, players|
258         hash = players[id]
259         break if hash
260       end
261     end
262     hash
263   end
264
265   def rated_players
266     players = []
267     @db.transaction(true) do
268       break unless  @db["players"]
269       @db["players"].each do |group, players_hash|
270         players << players_hash.keys
271       end
272     end
273     return players.flatten.collect do |id|
274       p = BasicPlayer.new
275       p.id = id
276       self.load(p)
277       p
278     end
279   end
280 end
281
282
283 ######################################################
284 # Processes the LOGIN command.
285 #
286 class Login
287   def Login.good_login?(str)
288     tokens = str.split
289     if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
290         (tokens[0] == "LOGIN") &&
291         (good_identifier?(tokens[1])))
292       return true
293     else
294       return false
295     end
296   end
297
298   def Login.good_game_name?(str)
299     if ((str =~ /^(.+)-\d+-\d+$/) && (good_identifier?($1)))
300       return true
301     else
302       return false
303     end
304   end
305
306   def Login.good_identifier?(str)
307     if str =~ /\A[\w\d_@\-\.]{1,#{Max_Identifier_Length}}\z/
308       return true
309     else
310       return false
311     end
312   end
313
314   def Login.factory(str, player)
315     (login, player.name, password, ext) = str.chomp.split
316     if (ext)
317       return Loginx1.new(player, password)
318     else
319       return LoginCSA.new(player, password)
320     end
321   end
322
323   attr_reader :player
324   
325   # the first command that will be executed just after LOGIN.
326   # If it is nil, the default process will be started.
327   attr_reader :csa_1st_str
328
329   def initialize(player, password)
330     @player = player
331     @csa_1st_str = nil
332     parse_password(password)
333   end
334
335   def process
336     @player.write_safe(sprintf("LOGIN:%s OK\n", @player.name))
337     log_message(sprintf("user %s run in %s mode", @player.name, @player.protocol))
338   end
339
340   def incorrect_duplicated_player(str)
341     @player.write_safe("LOGIN:incorrect\n")
342     @player.write_safe(sprintf("username %s is already connected\n", @player.name)) if (str.split.length >= 4)
343     sleep 3 # wait for sending the above messages.
344     @player.name = "%s [duplicated]" % [@player.name]
345     @player.finish
346   end
347 end
348
349 ######################################################
350 # Processes LOGIN for the CSA standard mode.
351 #
352 class LoginCSA < Login
353   PROTOCOL = "CSA"
354
355   def initialize(player, password)
356     @gamename = nil
357     super
358     @player.protocol = PROTOCOL
359   end
360
361   def parse_password(password)
362     if Login.good_game_name?(password)
363       @gamename = password
364       @player.set_password(nil)
365     elsif password.split(",").size > 1
366       @gamename, *trip = password.split(",")
367       @player.set_password(trip.join(","))
368     else
369       @player.set_password(password)
370       @gamename = Default_Game_Name
371     end
372     @gamename = self.class.good_game_name?(@gamename) ? @gamename : Default_Game_Name
373   end
374
375   def process
376     super
377     @csa_1st_str = "%%GAME #{@gamename} *"
378   end
379 end
380
381 ######################################################
382 # Processes LOGIN for the extented mode.
383 #
384 class Loginx1 < Login
385   PROTOCOL = "x1"
386
387   def initialize(player, password)
388     super
389     @player.protocol = PROTOCOL
390   end
391   
392   def parse_password(password)
393     @player.set_password(password)
394   end
395
396   def process
397     super
398     @player.write_safe(sprintf("##[LOGIN] +OK %s\n", PROTOCOL))
399   end
400 end
401
402
403 class BasicPlayer
404   # Idetifier of the player in the rating system
405   attr_accessor :id
406
407   # Name of the player
408   attr_accessor :name
409   
410   # Password of the player, which does not include a trip
411   attr_accessor :password
412
413   # Score in the rating sysem
414   attr_accessor :rate
415   
416   # Group in the rating system
417   attr_accessor :rating_group
418
419   # Last timestamp when the rate was modified
420   attr_accessor :modified_at
421
422   def initialize
423     @name = nil
424     @password = nil
425   end
426
427   def modified_at
428     @modified_at || Time.now
429   end
430
431   def rate=(new_rate)
432     if @rate != new_rate
433       @rate = new_rate
434       @modified_at = Time.now
435     end
436   end
437
438   def rated?
439     @id != nil
440   end
441
442   def simple_id
443     if @trip
444       simple_name = @name.gsub(/@.*?$/, '')
445       "%s+%s" % [simple_name, @trip[0..8]]
446     else
447       @name
448     end
449   end
450
451   ##
452   # Parses str in the LOGIN command, sets up @id and @trip
453   #
454   def set_password(str)
455     if str && !str.empty?
456       @password = str.strip
457       @id   = "%s+%s" % [@name, Digest::MD5.hexdigest(@password)]
458     else
459       @id = @password = nil
460     end
461   end
462 end
463
464
465 class Player < BasicPlayer
466   def initialize(str, socket)
467     super()
468     @socket = socket
469     @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
470
471     @protocol = nil             # CSA or x1
472     @eol = "\m"                 # favorite eol code
473     @game = nil
474     @game_name = ""
475     @mytime = 0                 # set in start method also
476     @sente = nil
477     @write_queue = Queue::new
478     @main_thread = Thread::current
479     @writer_thread = Thread::start do
480       Thread.pass
481       writer()
482     end
483   end
484
485   attr_accessor :socket, :status
486   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
487   attr_accessor :main_thread, :writer_thread, :write_queue
488   
489   def kill
490     log_message(sprintf("user %s killed", @name))
491     if (@game)
492       @game.kill(self)
493     end
494     finish
495     Thread::kill(@main_thread) if @main_thread
496   end
497
498   def finish
499     if (@status != "finished")
500       @status = "finished"
501       log_message(sprintf("user %s finish", @name))    
502       # TODO you should confirm that there is no message in the queue.
503       Thread::kill(@writer_thread) if @writer_thread
504       begin
505 #        @socket.close if (! @socket.closed?)
506       rescue
507         log_message(sprintf("user %s finish failed", @name))    
508       end
509     end
510   end
511
512   def write_safe(str)
513     @write_queue.push(str.gsub(/[\r\n]+/, @eol))
514   end
515
516   def writer
517     while (str = @write_queue.pop)
518       begin
519         @socket.write(str)
520       rescue Exception => ex
521         log_error("Failed to send a message to #{@name}.")
522         log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
523         return
524       end
525     end
526   end
527
528   def to_s
529     if ((status == "game_waiting") ||
530         (status == "start_waiting") ||
531         (status == "agree_waiting") ||
532         (status == "game"))
533       if (@sente)
534         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
535       elsif (@sente == false)
536         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
537       elsif (@sente == nil)
538         return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
539       end
540     else
541       return sprintf("%s %s %s", @name, @protocol, @status)
542     end
543   end
544
545   def run(csa_1st_str=nil)
546     while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
547       begin
548         $mutex.lock
549
550         if (@writer_thread == nil || @writer_thread.status == false)
551           # The writer_thread has been killed because of socket errors.
552           return
553         end
554
555         if (csa_1st_str)
556           str = csa_1st_str
557           csa_1st_str = nil
558         end
559         if (@write_queue.size > Max_Write_Queue_Size)
560           log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
561                 return
562         end
563
564         if (@status == "finished")
565           return
566         end
567         str.chomp! if (str.class == String) # may be strip! ?
568         log_message(str) if $DEBUG
569         case str 
570         when "" 
571           # Application-level protocol for Keep-Alive
572           # If the server gets LF, it sends back LF.
573           # 30 sec rule (client may not send LF again within 30 sec) is not implemented yet.
574           write_safe("\n")
575         when /^[\+\-][^%]/
576           if (@status == "game")
577             array_str = str.split(",")
578             move = array_str.shift
579             additional = array_str.shift
580             if /^'(.*)/ =~ additional
581               comment = array_str.unshift("'*#{$1}")
582             end
583             s = @game.handle_one_move(move, self)
584             @game.fh.print("#{comment}\n") if (comment && !s)
585             return if (s && @protocol == LoginCSA::PROTOCOL)
586           end
587         when /^%[^%]/, :timeout
588           if (@status == "game")
589             s = @game.handle_one_move(str, self)
590             return if (s && @protocol == LoginCSA::PROTOCOL)
591           # else
592           #   begin
593           #     @socket.write("##[KEEPALIVE] #{Time.now}\n")
594           #   rescue Exception => ex
595           #     log_error("Failed to send a keepalive to #{@name}.")
596           #     log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
597           #     return
598           #   end
599           end
600         when :exception
601           log_error("Failed to receive a message from #{@name}.")
602           return
603         when /^REJECT/
604           if (@status == "agree_waiting")
605             @game.reject(@name)
606             return if (@protocol == LoginCSA::PROTOCOL)
607           else
608             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
609           end
610         when /^AGREE/
611           if (@status == "agree_waiting")
612             @status = "start_waiting"
613             if ((@game.sente.status == "start_waiting") &&
614                 (@game.gote.status == "start_waiting"))
615               @game.start
616               @game.sente.status = "game"
617               @game.gote.status = "game"
618             end
619           else
620             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
621           end
622         when /^%%SHOW\s+(\S+)/
623           game_id = $1
624           if (LEAGUE.games[game_id])
625             write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
626           end
627           write_safe("##[SHOW] +OK\n")
628         when /^%%MONITORON\s+(\S+)/
629           game_id = $1
630           if (LEAGUE.games[game_id])
631             LEAGUE.games[game_id].monitoron(self)
632             write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
633             write_safe("##[MONITOR][#{game_id}] +OK\n")
634           end
635         when /^%%MONITOROFF\s+(\S+)/
636           game_id = $1
637           if (LEAGUE.games[game_id])
638             LEAGUE.games[game_id].monitoroff(self)
639           end
640         when /^%%HELP/
641           write_safe(
642             %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
643         when /^%%RATING/
644           players = LEAGUE.rated_players
645           players.sort {|a,b| b.rate <=> a.rate}.each do |p|
646             write_safe("##[RATING] %s \t %4d @%s\n" % 
647                        [p.simple_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
648           end
649           write_safe("##[RATING] +OK\n")
650         when /^%%VERSION/
651           write_safe "##[VERSION] Shogi Server revision #{Revision}\n"
652           write_safe("##[VERSION] +OK\n")
653         when /^%%GAME\s*$/
654           if ((@status == "connected") || (@status == "game_waiting"))
655             @status = "connected"
656             @game_name = ""
657           else
658             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
659           end
660         when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
661           command_name = $1
662           game_name = $2
663           my_sente_str = $3
664           if (! Login::good_game_name?(game_name))
665             write_safe(sprintf("##[ERROR] bad game name\n"))
666             next
667           elsif ((@status == "connected") || (@status == "game_waiting"))
668             ## continue
669           else
670             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
671             next
672           end
673
674           rival = nil
675           if (League::Floodgate.game_name?(game_name))
676             if (my_sente_str != "*")
677               write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n"), my_sente_str, game_name)
678               next
679             end
680             @sente = nil
681           else
682             if (my_sente_str == "*")
683               rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
684             elsif (my_sente_str == "+")
685               rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
686             elsif (my_sente_str == "-")
687               rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
688             else
689               ## never reached
690               write_safe(sprintf("##[ERROR] bad game option\n"))
691               next
692             end
693           end
694
695           if (rival)
696             @game_name = game_name
697             if ((my_sente_str == "*") && (rival.sente == nil))
698               if (rand(2) == 0)
699                 @sente = true
700                 rival.sente = false
701               else
702                 @sente = false
703                 rival.sente = true
704               end
705             elsif (rival.sente == true) # rival has higher priority
706               @sente = false
707             elsif (rival.sente == false)
708               @sente = true
709             elsif (my_sente_str == "+")
710               @sente = true
711               rival.sente = false
712             elsif (my_sente_str == "-")
713               @sente = false
714               rival.sente = true
715             else
716               ## never reached
717             end
718             Game::new(@game_name, self, rival)
719           else # rival not found
720             if (command_name == "GAME")
721               @status = "game_waiting"
722               @game_name = game_name
723               if (my_sente_str == "+")
724                 @sente = true
725               elsif (my_sente_str == "-")
726                 @sente = false
727               else
728                 @sente = nil
729               end
730             else                # challenge
731               write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
732               @status = "connected"
733               @game_name = ""
734               @sente = nil
735             end
736           end
737         when /^%%CHAT\s+(.+)/
738           message = $1
739           LEAGUE.players.each do |name, player|
740             if (player.protocol != LoginCSA::PROTOCOL)
741               player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
742             end
743           end
744         when /^%%LIST/
745           buf = Array::new
746           LEAGUE.games.each do |id, game|
747             buf.push(sprintf("##[LIST] %s\n", id))
748           end
749           buf.push("##[LIST] +OK\n")
750           write_safe(buf.join)
751         when /^%%WHO/
752           buf = Array::new
753           LEAGUE.players.each do |name, player|
754             buf.push(sprintf("##[WHO] %s\n", player.to_s))
755           end
756           buf.push("##[WHO] +OK\n")
757           write_safe(buf.join)
758         when /^LOGOUT/
759           @status = "connected"
760           write_safe("LOGOUT:completed\n")
761           return
762         when /^CHALLENGE/
763           # This command is only available for CSA's official testing server.
764           # So, this means nothing for this program.
765           write_safe("CHALLENGE ACCEPTED\n")
766         when /^\s*$/
767           ## ignore null string
768         else
769           msg = "##[ERROR] unknown command %s\n" % [str]
770           write_safe(msg)
771           log_error(msg)
772         end
773       ensure
774         $mutex.unlock
775       end
776     end # enf of while
777   end # def run
778 end # class
779
780 class Piece
781   PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
782   def initialize(board, x, y, sente, promoted=false)
783     @board = board
784     @x = x
785     @y = y
786     @sente = sente
787     @promoted = promoted
788
789     if ((x == 0) || (y == 0))
790       if (sente)
791         hands = board.sente_hands
792       else
793         hands = board.gote_hands
794       end
795       hands.push(self)
796       hands.sort! {|a, b|
797         a.name <=> b.name
798       }
799     else
800       @board.array[x][y] = self
801     end
802   end
803   attr_accessor :promoted, :sente, :x, :y, :board
804
805   def room_of_head?(x, y, name)
806     true
807   end
808
809   def movable_grids
810     return adjacent_movable_grids + far_movable_grids
811   end
812
813   def far_movable_grids
814     return []
815   end
816
817   def jump_to?(x, y)
818     if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
819       if ((@board.array[x][y] == nil) || # dst is empty
820           (@board.array[x][y].sente != @sente)) # dst is enemy
821         return true
822       end
823     end
824     return false
825   end
826
827   def put_to?(x, y)
828     if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
829       if (@board.array[x][y] == nil) # dst is empty?
830         return true
831       end
832     end
833     return false
834   end
835
836   def adjacent_movable_grids
837     grids = Array::new
838     if (@promoted)
839       moves = @promoted_moves
840     else
841       moves = @normal_moves
842     end
843     moves.each do |(dx, dy)|
844       if (@sente)
845         cand_y = @y - dy
846       else
847         cand_y = @y + dy
848       end
849       cand_x = @x + dx
850       if (jump_to?(cand_x, cand_y))
851         grids.push([cand_x, cand_y])
852       end
853     end
854     return grids
855   end
856
857   def move_to?(x, y, name)
858     return false if (! room_of_head?(x, y, name))
859     return false if ((name != @name) && (name != @promoted_name))
860     return false if (@promoted && (name != @promoted_name)) # can't un-promote
861
862     if (! @promoted)
863       return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
864       if (@sente)
865         return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
866       else
867         return false if ((6 >= @y) && (6 >= y) && (name != @name))
868       end
869     end
870
871     if ((@x == 0) || (@y == 0))
872       return jump_to?(x, y)
873     else
874       return movable_grids.include?([x, y])
875     end
876   end
877
878   def move_to(x, y)
879     if ((@x == 0) || (@y == 0))
880       if (@sente)
881         @board.sente_hands.delete(self)
882       else
883         @board.gote_hands.delete(self)
884       end
885       @board.array[x][y] = self
886     elsif ((x == 0) || (y == 0))
887       @promoted = false         # clear promoted flag before moving to hands
888       if (@sente)
889         @board.sente_hands.push(self)
890       else
891         @board.gote_hands.push(self)
892       end
893       @board.array[@x][@y] = nil
894     else
895       @board.array[@x][@y] = nil
896       @board.array[x][y] = self
897     end
898     @x = x
899     @y = y
900   end
901
902   def point
903     @point
904   end
905
906   def name
907     @name
908   end
909
910   def promoted_name
911     @promoted_name
912   end
913
914   def to_s
915     if (@sente)
916       sg = "+"
917     else
918       sg = "-"
919     end
920     if (@promoted)
921       n = @promoted_name
922     else
923       n = @name
924     end
925     return sg + n
926   end
927 end
928
929 class PieceFU < Piece
930   def initialize(*arg)
931     @point = 1
932     @normal_moves = [[0, +1]]
933     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
934     @name = "FU"
935     @promoted_name = "TO"
936     super
937   end
938   def room_of_head?(x, y, name)
939     if (name == "FU")
940       if (@sente)
941         return false if (y == 1)
942       else
943         return false if (y == 9)
944       end
945       ## 2fu check
946       c = 0
947       iy = 1
948       while (iy <= 9)
949         if ((iy  != @y) &&      # not source position
950             @board.array[x][iy] &&
951             (@board.array[x][iy].sente == @sente) && # mine
952             (@board.array[x][iy].name == "FU") &&
953             (@board.array[x][iy].promoted == false))
954           return false
955         end
956         iy = iy + 1
957       end
958     end
959     return true
960   end
961 end
962
963 class PieceKY  < Piece
964   def initialize(*arg)
965     @point = 1
966     @normal_moves = []
967     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
968     @name = "KY"
969     @promoted_name = "NY"
970     super
971   end
972   def room_of_head?(x, y, name)
973     if (name == "KY")
974       if (@sente)
975         return false if (y == 1)
976       else
977         return false if (y == 9)
978       end
979     end
980     return true
981   end
982   def far_movable_grids
983     grids = Array::new
984     if (@promoted)
985       return []
986     else
987       if (@sente)                 # up
988         cand_x = @x
989         cand_y = @y - 1
990         while (jump_to?(cand_x, cand_y))
991           grids.push([cand_x, cand_y])
992           break if (! put_to?(cand_x, cand_y))
993           cand_y = cand_y - 1
994         end
995       else                        # down
996         cand_x = @x
997         cand_y = @y + 1
998         while (jump_to?(cand_x, cand_y))
999           grids.push([cand_x, cand_y])
1000           break if (! put_to?(cand_x, cand_y))
1001           cand_y = cand_y + 1
1002         end
1003       end
1004       return grids
1005     end
1006   end
1007 end
1008 class PieceKE  < Piece
1009   def initialize(*arg)
1010     @point = 1
1011     @normal_moves = [[+1, +2], [-1, +2]]
1012     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
1013     @name = "KE"
1014     @promoted_name = "NK"
1015     super
1016   end
1017   def room_of_head?(x, y, name)
1018     if (name == "KE")
1019       if (@sente)
1020         return false if ((y == 1) || (y == 2))
1021       else
1022         return false if ((y == 9) || (y == 8))
1023       end
1024     end
1025     return true
1026   end
1027 end
1028 class PieceGI  < Piece
1029   def initialize(*arg)
1030     @point = 1
1031     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
1032     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
1033     @name = "GI"
1034     @promoted_name = "NG"
1035     super
1036   end
1037 end
1038 class PieceKI  < Piece
1039   def initialize(*arg)
1040     @point = 1
1041     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
1042     @promoted_moves = []
1043     @name = "KI"
1044     @promoted_name = nil
1045     super
1046   end
1047 end
1048 class PieceKA  < Piece
1049   def initialize(*arg)
1050     @point = 5
1051     @normal_moves = []
1052     @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
1053     @name = "KA"
1054     @promoted_name = "UM"
1055     super
1056   end
1057   def far_movable_grids
1058     grids = Array::new
1059     ## up right
1060     cand_x = @x - 1
1061     cand_y = @y - 1
1062     while (jump_to?(cand_x, cand_y))
1063       grids.push([cand_x, cand_y])
1064       break if (! put_to?(cand_x, cand_y))
1065       cand_x = cand_x - 1
1066       cand_y = cand_y - 1
1067     end
1068     ## down right
1069     cand_x = @x - 1
1070     cand_y = @y + 1
1071     while (jump_to?(cand_x, cand_y))
1072       grids.push([cand_x, cand_y])
1073       break if (! put_to?(cand_x, cand_y))
1074       cand_x = cand_x - 1
1075       cand_y = cand_y + 1
1076     end
1077     ## up left
1078     cand_x = @x + 1
1079     cand_y = @y - 1
1080     while (jump_to?(cand_x, cand_y))
1081       grids.push([cand_x, cand_y])
1082       break if (! put_to?(cand_x, cand_y))
1083       cand_x = cand_x + 1
1084       cand_y = cand_y - 1
1085     end
1086     ## down left
1087     cand_x = @x + 1
1088     cand_y = @y + 1
1089     while (jump_to?(cand_x, cand_y))
1090       grids.push([cand_x, cand_y])
1091       break if (! put_to?(cand_x, cand_y))
1092       cand_x = cand_x + 1
1093       cand_y = cand_y + 1
1094     end
1095     return grids
1096   end
1097 end
1098 class PieceHI  < Piece
1099   def initialize(*arg)
1100     @point = 5
1101     @normal_moves = []
1102     @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
1103     @name = "HI"
1104     @promoted_name = "RY"
1105     super
1106   end
1107   def far_movable_grids
1108     grids = Array::new
1109     ## up
1110     cand_x = @x
1111     cand_y = @y - 1
1112     while (jump_to?(cand_x, cand_y))
1113       grids.push([cand_x, cand_y])
1114       break if (! put_to?(cand_x, cand_y))
1115       cand_y = cand_y - 1
1116     end
1117     ## down
1118     cand_x = @x
1119     cand_y = @y + 1
1120     while (jump_to?(cand_x, cand_y))
1121       grids.push([cand_x, cand_y])
1122       break if (! put_to?(cand_x, cand_y))
1123       cand_y = cand_y + 1
1124     end
1125     ## right
1126     cand_x = @x - 1
1127     cand_y = @y
1128     while (jump_to?(cand_x, cand_y))
1129       grids.push([cand_x, cand_y])
1130       break if (! put_to?(cand_x, cand_y))
1131       cand_x = cand_x - 1
1132     end
1133     ## down
1134     cand_x = @x + 1
1135     cand_y = @y
1136     while (jump_to?(cand_x, cand_y))
1137       grids.push([cand_x, cand_y])
1138       break if (! put_to?(cand_x, cand_y))
1139       cand_x = cand_x + 1
1140     end
1141     return grids
1142   end
1143 end
1144 class PieceOU < Piece
1145   def initialize(*arg)
1146     @point = 0
1147     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
1148     @promoted_moves = []
1149     @name = "OU"
1150     @promoted_name = nil
1151     super
1152   end
1153 end
1154
1155 class Board
1156   def initialize
1157     @sente_hands = Array::new
1158     @gote_hands  = Array::new
1159     @history       = Hash::new(0)
1160     @sente_history = Hash::new(0)
1161     @gote_history  = Hash::new(0)
1162     @array = [[], [], [], [], [], [], [], [], [], []]
1163     @move_count = 0
1164   end
1165   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
1166   attr_reader :move_count
1167
1168   def initial
1169     PieceKY::new(self, 1, 1, false)
1170     PieceKE::new(self, 2, 1, false)
1171     PieceGI::new(self, 3, 1, false)
1172     PieceKI::new(self, 4, 1, false)
1173     PieceOU::new(self, 5, 1, false)
1174     PieceKI::new(self, 6, 1, false)
1175     PieceGI::new(self, 7, 1, false)
1176     PieceKE::new(self, 8, 1, false)
1177     PieceKY::new(self, 9, 1, false)
1178     PieceKA::new(self, 2, 2, false)
1179     PieceHI::new(self, 8, 2, false)
1180     (1..9).each do |i|
1181       PieceFU::new(self, i, 3, false)
1182     end
1183
1184     PieceKY::new(self, 1, 9, true)
1185     PieceKE::new(self, 2, 9, true)
1186     PieceGI::new(self, 3, 9, true)
1187     PieceKI::new(self, 4, 9, true)
1188     PieceOU::new(self, 5, 9, true)
1189     PieceKI::new(self, 6, 9, true)
1190     PieceGI::new(self, 7, 9, true)
1191     PieceKE::new(self, 8, 9, true)
1192     PieceKY::new(self, 9, 9, true)
1193     PieceKA::new(self, 8, 8, true)
1194     PieceHI::new(self, 2, 8, true)
1195     (1..9).each do |i|
1196       PieceFU::new(self, i, 7, true)
1197     end
1198   end
1199
1200   def have_piece?(hands, name)
1201     piece = hands.find { |i|
1202       i.name == name
1203     }
1204     return piece
1205   end
1206
1207   def move_to(x0, y0, x1, y1, name, sente)
1208     if (sente)
1209       hands = @sente_hands
1210     else
1211       hands = @gote_hands
1212     end
1213
1214     if ((x0 == 0) || (y0 == 0))
1215       piece = have_piece?(hands, name)
1216       return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece?
1217       piece.move_to(x1, y1)
1218     else
1219       return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))  # TODO null check?
1220       if (@array[x0][y0].name != name) # promoted ?
1221         @array[x0][y0].promoted = true
1222       end
1223       if (@array[x1][y1]) # capture
1224         if (@array[x1][y1].name == "OU")
1225           return :outori        # return board update
1226         end
1227         @array[x1][y1].sente = @array[x0][y0].sente
1228         @array[x1][y1].move_to(0, 0)
1229         hands.sort! {|a, b| # TODO refactor. Move to Piece class
1230           a.name <=> b.name
1231         }
1232       end
1233       @array[x0][y0].move_to(x1, y1)
1234     end
1235     @move_count += 1
1236     return true
1237   end
1238
1239   def look_for_ou(sente)
1240     x = 1
1241     while (x <= 9)
1242       y = 1
1243       while (y <= 9)
1244         if (@array[x][y] &&
1245             (@array[x][y].name == "OU") &&
1246             (@array[x][y].sente == sente))
1247           return @array[x][y]
1248         end
1249         y = y + 1
1250       end
1251       x = x + 1
1252     end
1253     raise "can't find ou"
1254   end
1255
1256   # note checkmate, but check. sente is checked.
1257   def checkmated?(sente)        # sente is loosing
1258     ou = look_for_ou(sente)
1259     x = 1
1260     while (x <= 9)
1261       y = 1
1262       while (y <= 9)
1263         if (@array[x][y] &&
1264             (@array[x][y].sente != sente))
1265           if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
1266             return true
1267           end
1268         end
1269         y = y + 1
1270       end
1271       x = x + 1
1272     end
1273     return false
1274   end
1275
1276   def uchifuzume?(sente)
1277     rival_ou = look_for_ou(! sente)   # rival's ou
1278     if (sente)                  # rival is gote
1279       if ((rival_ou.y != 9) &&
1280           (@array[rival_ou.x][rival_ou.y + 1]) &&
1281           (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
1282           (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
1283         fu_x = rival_ou.x
1284         fu_y = rival_ou.y + 1
1285       else
1286         return false
1287       end
1288     else                        # gote
1289       if ((rival_ou.y != 0) &&
1290           (@array[rival_ou.x][rival_ou.y - 1]) &&
1291           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
1292           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
1293         fu_x = rival_ou.x
1294         fu_y = rival_ou.y - 1
1295       else
1296         return false
1297       end
1298     end
1299     
1300     ## case: rival_ou is moving
1301     escaped = false
1302     rival_ou.movable_grids.each do |(cand_x, cand_y)|
1303       tmp_board = Marshal.load(Marshal.dump(self))
1304       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
1305       raise "internal error" if (s != true)
1306       if (! tmp_board.checkmated?(! sente)) # good move
1307         return false
1308       end
1309     end
1310
1311     ## case: rival is capturing fu
1312     x = 1
1313     while (x <= 9)
1314       y = 1
1315       while (y <= 9)
1316         if (@array[x][y] &&
1317             (@array[x][y].sente != sente) &&
1318             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
1319           if (@array[x][y].promoted)
1320             name = @array[x][y].promoted_name
1321           else
1322             name = @array[x][y].name
1323           end
1324           tmp_board = Marshal.load(Marshal.dump(self))
1325           s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
1326           raise "internal error" if (s != true)
1327           if (! tmp_board.checkmated?(! sente)) # good move
1328             return false
1329           end
1330         end
1331         y = y + 1
1332       end
1333       x = x + 1
1334     end
1335     return true
1336   end
1337
1338   # @[sente|gote]_history has at least one item while the player is checking the other or 
1339   # the other escapes.
1340   def update_sennichite(player)
1341     str = to_s
1342     @history[str] += 1
1343     if checkmated?(!player)
1344       if (player)
1345         @sente_history["dummy"] = 1  # flag to see Sente player is checking Gote player
1346       else
1347         @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
1348       end
1349     else
1350       if (player)
1351         @sente_history.clear # no more continuous check
1352       else
1353         @gote_history.clear  # no more continuous check
1354       end
1355     end
1356     if @sente_history.size > 0  # possible for Sente's or Gote's turn
1357       @sente_history[str] += 1
1358     end
1359     if @gote_history.size > 0   # possible for Sente's or Gote's turn
1360       @gote_history[str] += 1
1361     end
1362   end
1363
1364   def oute_sennichite?(player)
1365     if (@sente_history[to_s] >= 4)
1366       return :oute_sennichite_sente_lose
1367     elsif (@gote_history[to_s] >= 4)
1368       return :oute_sennichite_gote_lose
1369     else
1370       return nil
1371     end
1372   end
1373
1374   def sennichite?(sente)
1375     if (@history[to_s] >= 4) # already 3 times
1376       return true
1377     end
1378     return false
1379   end
1380
1381   def good_kachi?(sente)
1382     if (checkmated?(sente))
1383       puts "'NG: Checkmating." if $DEBUG
1384       return false 
1385     end
1386     
1387     ou = look_for_ou(sente)
1388     if (sente && (ou.y >= 4))
1389       puts "'NG: Black's OU does not enter yet." if $DEBUG
1390       return false     
1391     end  
1392     if (! sente && (ou.y <= 6))
1393       puts "'NG: White's OU does not enter yet." if $DEBUG
1394       return false 
1395     end
1396       
1397     number = 0
1398     point = 0
1399
1400     if (sente)
1401       hands = @sente_hands
1402       r = [1, 2, 3]
1403     else
1404       hands = @gote_hands
1405       r = [7, 8, 9]
1406     end
1407     r.each do |y|
1408       x = 1
1409       while (x <= 9)
1410         if (@array[x][y] &&
1411             (@array[x][y].sente == sente) &&
1412             (@array[x][y].point > 0))
1413           point = point + @array[x][y].point
1414           number = number + 1
1415         end
1416         x = x + 1
1417       end
1418     end
1419     hands.each do |piece|
1420       point = point + piece.point
1421     end
1422
1423     if (number < 10)
1424       puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
1425       return false     
1426     end  
1427     if (sente)
1428       if (point < 28)
1429         puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
1430         return false 
1431       end  
1432     else
1433       if (point < 27)
1434         puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
1435         return false 
1436       end
1437     end
1438
1439     puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
1440     return true
1441   end
1442
1443   # sente is nil only if tests in test_board run
1444   def handle_one_move(str, sente=nil)
1445     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1446       sg = $1
1447       x0 = $2.to_i
1448       y0 = $3.to_i
1449       x1 = $4.to_i
1450       y1 = $5.to_i
1451       name = $6
1452     elsif (str =~ /^%KACHI/)
1453       raise ArgumentError, "sente is null", caller if sente == nil
1454       if (good_kachi?(sente))
1455         return :kachi_win
1456       else
1457         return :kachi_lose
1458       end
1459     elsif (str =~ /^%TORYO/)
1460       return :toryo
1461     else
1462       return :illegal
1463     end
1464     
1465     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1466         ((x0 != 0) || (y0 != 0)))
1467       return :illegal
1468     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1469       return :illegal
1470     end
1471     
1472
1473     if (sg == "+")
1474       sente = true if sente == nil           # deprecated
1475       return :illegal unless sente == true   # black player's move must be black
1476       hands = @sente_hands
1477     else
1478       sente = false if sente == nil          # deprecated
1479       return :illegal unless sente == false  # white player's move must be white
1480       hands = @gote_hands
1481     end
1482     
1483     ## source check
1484     if ((x0 == 0) && (y0 == 0))
1485       return :illegal if (! have_piece?(hands, name))
1486     elsif (! @array[x0][y0])
1487       return :illegal           # no piece
1488     elsif (@array[x0][y0].sente != sente)
1489       return :illegal           # this is not mine
1490     elsif (@array[x0][y0].name != name)
1491       return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1492     end
1493
1494     ## destination check
1495     if (@array[x1][y1] &&
1496         (@array[x1][y1].sente == sente)) # can't capture mine
1497       return :illegal
1498     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1499       return :illegal           # can't put on existing piece
1500     end
1501
1502     tmp_board = Marshal.load(Marshal.dump(self))
1503     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1504     return :oute_kaihimore if (tmp_board.checkmated?(sente))
1505     tmp_board.update_sennichite(sente)
1506     os_result = tmp_board.oute_sennichite?(sente)
1507     return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
1508     return :sennichite if tmp_board.sennichite?(sente)
1509
1510     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1511       return :uchifuzume
1512     end
1513
1514     move_to(x0, y0, x1, y1, name, sente)
1515     str = to_s
1516
1517     update_sennichite(sente)
1518     return :normal
1519   end
1520
1521   def to_s
1522     a = Array::new
1523     y = 1
1524     while (y <= 9)
1525       a.push(sprintf("P%d", y))
1526       x = 9
1527       while (x >= 1)
1528         piece = @array[x][y]
1529         if (piece)
1530           s = piece.to_s
1531         else
1532           s = " * "
1533         end
1534         a.push(s)
1535         x = x - 1
1536       end
1537       a.push(sprintf("\n"))
1538       y = y + 1
1539     end
1540     if (! sente_hands.empty?)
1541       a.push("P+")
1542       sente_hands.each do |p|
1543         a.push("00" + p.name)
1544       end
1545       a.push("\n")
1546     end
1547     if (! gote_hands.empty?)
1548       a.push("P-")
1549       gote_hands.each do |p|
1550         a.push("00" + p.name)
1551       end
1552       a.push("\n")
1553     end
1554     a.push("+\n")
1555     return a.join
1556   end
1557 end
1558
1559 class GameResult
1560   attr_reader :players, :black, :white
1561
1562   def initialize(p1, p2)
1563     @players = [p1, p2]
1564     if p1.sente && !p2.sente
1565       @black, @white = p1, p2
1566     elsif !p1.sente && p2.sente
1567       @black, @white = p2, p1
1568     else
1569       raise "Never reached!"
1570     end
1571   end
1572 end
1573
1574 class GameResultWin < GameResult
1575   attr_reader :winner, :loser
1576
1577   def initialize(winner, loser)
1578     super
1579     @winner, @loser = winner, loser
1580   end
1581
1582   def to_s
1583     black_name = @black.id || @black.name
1584     white_name = @white.id || @white.name
1585     "%s:%s" % [black_name, white_name]
1586   end
1587 end
1588
1589 class GameResultDraw < GameResult
1590
1591 end
1592
1593 class Game
1594   @@mutex = Mutex.new
1595   @@time  = 0
1596
1597   def initialize(game_name, player0, player1)
1598     @monitors = Array::new
1599     @game_name = game_name
1600     if (@game_name =~ /-(\d+)-(\d+)$/)
1601       @total_time = $1.to_i
1602       @byoyomi = $2.to_i
1603     end
1604
1605     if (player0.sente)
1606       @sente = player0
1607       @gote = player1
1608     else
1609       @sente = player1
1610       @gote = player0
1611     end
1612     @current_player = @sente
1613     @next_player = @gote
1614
1615     @sente.game = self
1616     @gote.game = self
1617
1618     @last_move = ""
1619     @current_turn = 0
1620
1621     @sente.status = "agree_waiting"
1622     @gote.status = "agree_waiting"
1623     
1624     @id = sprintf("%s+%s+%s+%s+%s", 
1625                   LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1626     @logfile = File.join(LEAGUE.dir, @id + ".csa")
1627
1628     LEAGUE.games[@id] = self
1629
1630     log_message(sprintf("game created %s", @id))
1631
1632     @board = Board::new
1633     @board.initial
1634     @start_time = nil
1635     @fh = nil
1636     @result = nil
1637
1638     propose
1639   end
1640   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1641   attr_accessor :last_move, :current_turn
1642   attr_reader   :result
1643
1644   def rated?
1645     @sente.rated? && @gote.rated?
1646   end
1647
1648   def monitoron(monitor)
1649     @monitors.delete(monitor)
1650     @monitors.push(monitor)
1651   end
1652
1653   def monitoroff(monitor)
1654     @monitors.delete(monitor)
1655   end
1656
1657   def reject(rejector)
1658     @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1659     @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1660     finish
1661   end
1662
1663   def kill(killer)
1664     if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1665       reject(killer.name)
1666     elsif (@current_player == killer)
1667       abnormal_lose()
1668       finish
1669     end
1670   end
1671
1672   def finish
1673     log_message(sprintf("game finished %s", @id))
1674     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
1675     @fh.close
1676
1677     @sente.game = nil
1678     @gote.game = nil
1679     @sente.status = "connected"
1680     @gote.status = "connected"
1681
1682     if (@current_player.protocol == LoginCSA::PROTOCOL)
1683       @current_player.finish
1684     end
1685     if (@next_player.protocol == LoginCSA::PROTOCOL)
1686       @next_player.finish
1687     end
1688     @monitors = Array::new
1689     @sente = nil
1690     @gote = nil
1691     @current_player = nil
1692     @next_player = nil
1693     LEAGUE.games.delete(@id)
1694   end
1695
1696   def handle_one_move(str, player)
1697     finish_flag = true
1698     if (@current_player == player)
1699       @end_time = Time::new
1700       t = (@end_time - @start_time).floor
1701       t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1702       
1703       move_status = nil
1704       if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1705         status = :timeout
1706       elsif (str == :timeout)
1707         return false            # time isn't expired. players aren't swapped. continue game
1708       else
1709         @current_player.mytime = @current_player.mytime - t
1710         if (@current_player.mytime < 0)
1711           @current_player.mytime = 0
1712         end
1713
1714 #        begin
1715           move_status = @board.handle_one_move(str, @sente == @current_player)
1716 #        rescue
1717 #          log_error("handle_one_move raise exception for #{str}")
1718 #          move_status = :illegal
1719 #        end
1720
1721         if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1722           @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1723         else
1724           if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite_sente_lose) || (move_status == :oute_sennichite_gote_lose))
1725             @sente.write_safe(sprintf("%s,T%d\n", str, t))
1726             @gote.write_safe(sprintf("%s,T%d\n", str, t))
1727             @fh.printf("%s\nT%d\n", str, t)
1728             @last_move = sprintf("%s,T%d", str, t)
1729             @current_turn = @current_turn + 1
1730           end
1731
1732           @monitors.each do |monitor|
1733             monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1734             monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1735           end
1736         end
1737       end
1738
1739       if (@next_player.status != "game") # rival is logout or disconnected
1740         abnormal_win()
1741       elsif (status == :timeout)
1742         timeout_lose()
1743       elsif (move_status == :illegal)
1744         illegal_lose()
1745       elsif (move_status == :kachi_win)
1746         kachi_win()
1747       elsif (move_status == :kachi_lose)
1748         kachi_lose()
1749       elsif (move_status == :toryo)
1750         toryo_lose()
1751       elsif (move_status == :outori)
1752         outori_win()
1753       elsif (move_status == :oute_sennichite_sente_lose)
1754         oute_sennichite_win_lose(@gote, @sente) # Sente is checking
1755       elsif (move_status == :oute_sennichite_gote_lose)
1756         oute_sennichite_win_lose(@sente, @gote) # Gote is checking
1757       elsif (move_status == :sennichite)
1758         sennichite_draw()
1759       elsif (move_status == :uchifuzume)
1760         uchifuzume_lose()
1761       elsif (move_status == :oute_kaihimore)
1762         oute_kaihimore_lose()
1763       else
1764         finish_flag = false
1765       end
1766       finish() if finish_flag
1767       (@current_player, @next_player) = [@next_player, @current_player]
1768       @start_time = Time::new
1769       return finish_flag
1770     end
1771   end
1772
1773   def abnormal_win
1774     @current_player.status = "connected"
1775     @next_player.status = "connected"
1776     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1777     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1778     @fh.printf("%%TORYO\n")
1779     @fh.print(@board.to_s.gsub(/^/, "\'"))
1780     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1781     @result = GameResultWin.new(@current_player, @next_player)
1782     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1783     @monitors.each do |monitor|
1784       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1785     end
1786   end
1787
1788   def abnormal_lose
1789     @current_player.status = "connected"
1790     @next_player.status = "connected"
1791     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1792     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1793     @fh.printf("%%TORYO\n")
1794     @fh.print(@board.to_s.gsub(/^/, "\'"))
1795     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1796     @result = GameResultWin.new(@next_player, @current_player)
1797     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1798     @monitors.each do |monitor|
1799       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1800     end
1801   end
1802
1803   def sennichite_draw
1804     @current_player.status = "connected"
1805     @next_player.status = "connected"
1806     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1807     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1808     @fh.print(@board.to_s.gsub(/^/, "\'"))
1809     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1810     @result = GameResultDraw.new(@current_player, @next_player)
1811     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1812     @monitors.each do |monitor|
1813       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1814     end
1815   end
1816
1817   def oute_sennichite_win_lose(winner, loser)
1818     @current_player.status = "connected"
1819     @next_player.status = "connected"
1820     loser.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1821     winner.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1822     @fh.print(@board.to_s.gsub(/^/, "\'"))
1823     if loser == @current_player
1824       @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1825     else
1826       @fh.printf("'summary:oute_sennichite:%s win:%s lose\n", @current_player.name, @next_player.name)
1827     end
1828     @result = GameResultWin.new(winner, loser)
1829     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1830     @monitors.each do |monitor|
1831       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1832     end
1833   end
1834
1835   def illegal_lose
1836     @current_player.status = "connected"
1837     @next_player.status = "connected"
1838     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1839     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1840     @fh.print(@board.to_s.gsub(/^/, "\'"))
1841     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1842     @result = GameResultWin.new(@next_player, @current_player)
1843     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1844     @monitors.each do |monitor|
1845       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1846     end
1847   end
1848
1849   def uchifuzume_lose
1850     @current_player.status = "connected"
1851     @next_player.status = "connected"
1852     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1853     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1854     @fh.print(@board.to_s.gsub(/^/, "\'"))
1855     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1856     @result = GameResultWin.new(@next_player, @current_player)
1857     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1858     @monitors.each do |monitor|
1859       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1860     end
1861   end
1862
1863   def oute_kaihimore_lose
1864     @current_player.status = "connected"
1865     @next_player.status = "connected"
1866     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1867     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1868     @fh.print(@board.to_s.gsub(/^/, "\'"))
1869     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1870     @result = GameResultWin.new(@next_player, @current_player)
1871     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1872     @monitors.each do |monitor|
1873       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1874     end
1875   end
1876
1877   def timeout_lose
1878     @current_player.status = "connected"
1879     @next_player.status = "connected"
1880     @current_player.write_safe("#TIME_UP\n#LOSE\n")
1881     @next_player.write_safe("#TIME_UP\n#WIN\n")
1882     @fh.print(@board.to_s.gsub(/^/, "\'"))
1883     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1884     @result = GameResultWin.new(@next_player, @current_player)
1885     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1886     @monitors.each do |monitor|
1887       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1888     end
1889   end
1890
1891   def kachi_win
1892     @current_player.status = "connected"
1893     @next_player.status = "connected"
1894     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1895     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1896     @fh.printf("%%KACHI\n")
1897     @fh.print(@board.to_s.gsub(/^/, "\'"))
1898     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1899     @result = GameResultWin.new(@current_player, @next_player)
1900     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1901     @monitors.each do |monitor|
1902       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1903     end
1904   end
1905
1906   def kachi_lose
1907     @current_player.status = "connected"
1908     @next_player.status = "connected"
1909     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1910     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1911     @fh.printf("%%KACHI\n")
1912     @fh.print(@board.to_s.gsub(/^/, "\'"))
1913     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1914     @result = GameResultWin.new(@next_player, @current_player)
1915     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1916     @monitors.each do |monitor|
1917       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1918     end
1919   end
1920
1921   def toryo_lose
1922     @current_player.status = "connected"
1923     @next_player.status = "connected"
1924     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1925     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1926     @fh.printf("%%TORYO\n")
1927     @fh.print(@board.to_s.gsub(/^/, "\'"))
1928     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1929     @result = GameResultWin.new(@next_player, @current_player)
1930     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1931     @monitors.each do |monitor|
1932       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1933     end
1934   end
1935
1936   def outori_win
1937     @current_player.status = "connected"
1938     @next_player.status = "connected"
1939     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1940     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1941     @fh.print(@board.to_s.gsub(/^/, "\'"))
1942     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1943     @result = GameResultWin.new(@current_player, @next_player)
1944     @fh.printf("'rating:#{@result.to_s}\n") if rated?
1945     @monitors.each do |monitor|
1946       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1947     end
1948   end
1949
1950   def start
1951     log_message(sprintf("game started %s", @id))
1952     @sente.write_safe(sprintf("START:%s\n", @id))
1953     @gote.write_safe(sprintf("START:%s\n", @id))
1954     @sente.mytime = @total_time
1955     @gote.mytime = @total_time
1956     @start_time = Time::new
1957   end
1958
1959   def propose
1960     @fh = open(@logfile, "w")
1961     @fh.sync = true
1962
1963     @fh.puts("V2")
1964     @fh.puts("N+#{@sente.name}")
1965     @fh.puts("N-#{@gote.name}")
1966     @fh.puts("$EVENT:#{@id}")
1967
1968     @sente.write_safe(propose_message("+"))
1969     @gote.write_safe(propose_message("-"))
1970
1971     now = Time::new.strftime("%Y/%m/%d %H:%M:%S")
1972     @fh.puts("$START_TIME:#{now}")
1973     @fh.print <<EOM
1974 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1975 P2 * -HI *  *  *  *  * -KA * 
1976 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1977 P4 *  *  *  *  *  *  *  *  * 
1978 P5 *  *  *  *  *  *  *  *  * 
1979 P6 *  *  *  *  *  *  *  *  * 
1980 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1981 P8 * +KA *  *  *  *  * +HI * 
1982 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1983 +
1984 EOM
1985   end
1986
1987   def show()
1988     str0 = <<EOM
1989 BEGIN Game_Summary
1990 Protocol_Version:1.1
1991 Protocol_Mode:Server
1992 Format:Shogi 1.0
1993 Declaration:Jishogi 1.1
1994 Game_ID:#{@id}
1995 Name+:#{@sente.name}
1996 Name-:#{@gote.name}
1997 Rematch_On_Draw:NO
1998 To_Move:+
1999 BEGIN Time
2000 Time_Unit:1sec
2001 Total_Time:#{@total_time}
2002 Byoyomi:#{@byoyomi}
2003 Least_Time_Per_Move:#{Least_Time_Per_Move}
2004 Remaining_Time+:#{@sente.mytime}
2005 Remaining_Time-:#{@gote.mytime}
2006 Last_Move:#{@last_move}
2007 Current_Turn:#{@current_turn}
2008 END Time
2009 BEGIN Position
2010 EOM
2011
2012     str1 = <<EOM
2013 END Position
2014 END Game_Summary
2015 EOM
2016
2017     return str0 + @board.to_s + str1
2018   end
2019
2020   def propose_message(sg_flag)
2021     str = <<EOM
2022 BEGIN Game_Summary
2023 Protocol_Version:1.1
2024 Protocol_Mode:Server
2025 Format:Shogi 1.0
2026 Declaration:Jishogi 1.1
2027 Game_ID:#{@id}
2028 Name+:#{@sente.name}
2029 Name-:#{@gote.name}
2030 Your_Turn:#{sg_flag}
2031 Rematch_On_Draw:NO
2032 To_Move:+
2033 BEGIN Time
2034 Time_Unit:1sec
2035 Total_Time:#{@total_time}
2036 Byoyomi:#{@byoyomi}
2037 Least_Time_Per_Move:#{Least_Time_Per_Move}
2038 END Time
2039 BEGIN Position
2040 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
2041 P2 * -HI *  *  *  *  * -KA * 
2042 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
2043 P4 *  *  *  *  *  *  *  *  * 
2044 P5 *  *  *  *  *  *  *  *  * 
2045 P6 *  *  *  *  *  *  *  *  * 
2046 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
2047 P8 * +KA *  *  *  *  * +HI * 
2048 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
2049 P+
2050 P-
2051 +
2052 END Position
2053 END Game_Summary
2054 EOM
2055     return str
2056   end
2057   
2058   private
2059   
2060   def issue_current_time
2061     time = Time::new.strftime("%Y%m%d%H%M%S").to_i
2062     @@mutex.synchronize do
2063       while time <= @@time do
2064         time += 1
2065       end
2066       @@time = time
2067     end
2068   end
2069 end
2070 end # module ShogiServer
2071
2072 #################################################
2073 # MAIN
2074 #
2075
2076 def usage
2077     print <<EOM
2078 NAME
2079         shogi-server - server for CSA server protocol
2080
2081 SYNOPSIS
2082         shogi-server [OPTIONS] event_name port_number
2083
2084 DESCRIPTION
2085         server for CSA server protocol
2086
2087 OPTIONS
2088         --pid-file file
2089                 specify filename for logging process ID
2090     --daemon dir
2091         run as a daemon. Log files will be put in dir.
2092
2093 LICENSE
2094         this file is distributed under GPL version2 and might be compiled by Exerb
2095
2096 SEE ALSO
2097
2098 RELEASE
2099         #{ShogiServer::Release}
2100
2101 REVISION
2102         #{ShogiServer::Revision}
2103 EOM
2104 end
2105
2106 def log_debug(str)
2107   $logger.debug(str)
2108 end
2109
2110 def log_message(str)
2111   $logger.info(str)
2112 end
2113
2114 def log_warning(str)
2115   $logger.warn(str)
2116 end
2117
2118 def log_error(str)
2119   $logger.error(str)
2120 end
2121
2122
2123 def parse_command_line
2124   options = Hash::new
2125   parser = GetoptLong.new( ["--daemon",         GetoptLong::REQUIRED_ARGUMENT],
2126                            ["--pid-file",       GetoptLong::REQUIRED_ARGUMENT]
2127                          )
2128   parser.quiet = true
2129   begin
2130     parser.each_option do |name, arg|
2131       name.sub!(/^--/, '')
2132       options[name] = arg.dup
2133     end
2134   rescue
2135     usage
2136     raise parser.error_message
2137   end
2138   return options
2139 end
2140
2141 def write_pid_file(file)
2142   open(file, "w") do |fh|
2143     fh.puts "#{$$}"
2144   end
2145 end
2146
2147 def mutex_watchdog(mutex, sec)
2148   while true
2149     begin
2150       timeout(sec) do
2151         begin
2152           mutex.lock
2153         ensure
2154           mutex.unlock
2155         end
2156       end
2157       sleep(sec)
2158     rescue TimeoutError
2159       log_error("mutex watchdog timeout")
2160       exit(1)
2161     end
2162   end
2163 end
2164
2165 def main
2166
2167   $mutex = Mutex::new
2168   Thread::start do
2169     Thread.pass
2170     mutex_watchdog($mutex, 10)
2171   end
2172
2173   $options = parse_command_line
2174   if (ARGV.length != 2)
2175     usage
2176     exit 2
2177   end
2178
2179   LEAGUE.event = ARGV.shift
2180   port = ARGV.shift
2181
2182   dir = $options["daemon"]
2183   dir = File.expand_path(dir) if dir
2184   if dir && ! File.exist?(dir)
2185     FileUtils.mkdir(dir)
2186   end
2187   log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT
2188   $logger = WEBrick::Log.new(log_file)
2189
2190   LEAGUE.dir = dir || File.dirname(__FILE__)
2191   LEAGUE.setup_players_database
2192
2193   config = {}
2194   config[:Port]       = port
2195   config[:ServerType] = WEBrick::Daemon if $options["daemon"]
2196   config[:Logger]     = $logger
2197   if $options["pid-file"]
2198     pid_file = File.expand_path($options["pid-file"])
2199     config[:StartCallback] = Proc.new do
2200       write_pid_file(pid_file)
2201     end
2202     config[:StopCallback] = Proc.new do
2203       FileUtils.rm(pid_file, :force => true)
2204     end
2205   end
2206
2207   server = WEBrick::GenericServer.new(config)
2208   ["INT", "TERM"].each do |signal| 
2209     trap(signal) do
2210       LEAGUE.shutdown
2211       server.shutdown
2212     end
2213   end
2214   $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"] 
2215   log_message("server started [Revision: #{ShogiServer::Revision}]")
2216
2217   server.start do |client|
2218       # client.sync = true # this is already set in WEBrick 
2219       client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
2220         # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time
2221       player = nil
2222       login  = nil
2223       while (str = client.gets_timeout(ShogiServer::Login_Time))
2224         begin
2225           $mutex.lock
2226           str =~ /([\r\n]*)$/
2227           eol = $1
2228           if (ShogiServer::Login::good_login?(str))
2229             player = ShogiServer::Player::new(str, client)
2230             player.eol = eol
2231             login  = ShogiServer::Login::factory(str, player)
2232             if (LEAGUE.players[player.name])
2233               if ((LEAGUE.players[player.name].password == player.password) &&
2234                   (LEAGUE.players[player.name].status != "game"))
2235                 log_message(sprintf("user %s login forcely", player.name))
2236                 LEAGUE.players[player.name].kill
2237               else
2238                 login.incorrect_duplicated_player(str)
2239                 #Thread::exit
2240                 #return
2241                 # TODO
2242                 player = nil
2243                 break
2244               end
2245             end
2246             LEAGUE.add(player)
2247             break
2248           else
2249             client.write_safe("LOGIN:incorrect" + eol)
2250             client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
2251           end
2252         ensure
2253           $mutex.unlock
2254         end
2255       end                       # login loop
2256       if (! player)
2257         #client.close
2258         #Thread::exit
2259         #return
2260         next
2261       end
2262       log_message(sprintf("user %s login", player.name))
2263       login.process
2264       player.run(login.csa_1st_str)
2265       begin
2266         $mutex.lock
2267         if (player.game)
2268           player.game.kill(player)
2269         end
2270         player.finish # socket has been closed
2271         LEAGUE.delete(player)
2272         log_message(sprintf("user %s logout", player.name))
2273       ensure
2274         $mutex.unlock
2275       end
2276   end
2277 end
2278
2279
2280 if ($0 == __FILE__)
2281   STDOUT.sync = true
2282   STDERR.sync = true
2283   TCPSocket.do_not_reverse_lookup = true
2284   Thread.abort_on_exception = true
2285
2286   LEAGUE = ShogiServer::League::new
2287   main
2288 end