OSDN Git Service

Kifu logs are always written in separate files. Add a test case. Some refactorings.
[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 ##
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.
10 ##
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.
15 ##
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
19
20 Max_Write_Queue_Size = 1000
21 Max_Identifier_Length = 32
22 Default_Timeout = 60            # for single socket operation
23
24 Default_Game_Name = "default-1500-0"
25
26 One_Time = 10
27 Least_Time_Per_Move = 1
28 Login_Time = 300                # time for LOGIN
29
30 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
31 Release.concat("-") if (Release == "")
32 Revision = "$Revision$".gsub(/[^\.\d]/, '')
33
34 STDOUT.sync = true
35 STDERR.sync = true
36
37 require 'getoptlong'
38 require 'thread'
39 require 'timeout'
40 require 'socket'
41
42 TCPSocket.do_not_reverse_lookup = true
43 Thread.abort_on_exception = true
44
45
46 class TCPSocket
47   def gets_timeout(t = Default_Timeout)
48     begin
49       timeout(t) do
50         return self.gets
51       end
52     rescue TimeoutError
53       return nil
54     rescue
55       return nil
56     end
57   end
58   def gets_safe(t = nil)
59     if (t && t > 0)
60       begin
61         timeout(t) do
62           return self.gets
63         end
64       rescue TimeoutError
65         return :timeout
66       rescue
67         return nil
68       end
69     else
70       begin
71         return self.gets
72       rescue
73         return nil
74       end
75     end
76   end
77   def write_safe(str)
78     begin
79       return self.write(str)
80     rescue
81       return nil
82     end
83   end
84 end
85
86
87 class League
88   def initialize
89     @games = Hash::new
90     @players = Hash::new
91     @event = nil
92   end
93   attr_accessor :players, :games, :event
94
95   def add(player)
96     @players[player.name] = player
97   end
98   def delete(player)
99     @players.delete(player.name)
100   end
101   def get_player(status, game_name, sente, searcher=nil)
102     @players.each do |name, player|
103       if ((player.status == status) &&
104           (player.game_name == game_name) &&
105           ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
106           ((searcher == nil) || (player != searcher)))
107         return player
108       end
109     end
110     return nil
111   end
112 end
113
114 class Player
115   def initialize(str, socket)
116     @name = nil
117     @password = nil
118     @socket = socket
119     @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
120
121     @protocol = nil             # CSA or x1
122     @eol = "\m"                 # favorite eol code
123     @game = nil
124     @game_name = ""
125     @mytime = 0                 # set in start method also
126     @sente = nil
127     @writer_thread = nil
128     @main_thread = nil
129     @write_queue = Queue::new
130     login(str)
131   end
132
133   attr_accessor :name, :password, :socket, :status
134   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
135   attr_accessor :main_thread, :writer_thread, :write_queue
136   def kill
137     log_message(sprintf("user %s killed", @name))
138     if (@game)
139       @game.kill(self)
140     end
141     finish
142     Thread::kill(@main_thread) if @main_thread
143   end
144
145   def finish
146     if (@status != "finished")
147       @status = "finished"
148       log_message(sprintf("user %s finish", @name))    
149       Thread::kill(@writer_thread) if @writer_thread
150       begin
151         @socket.close if (! @socket.closed?)
152       rescue
153         log_message(sprintf("user %s finish failed", @name))    
154       end
155     end
156   end
157
158   def write_safe(str)
159     @write_queue.push(str.gsub(/[\r\n]+/, @eol))
160   end
161
162   def writer
163     while (str = @write_queue.pop)
164       @socket.write_safe(str)
165     end
166   end
167
168   def to_s
169     if ((status == "game_waiting") ||
170         (status == "start_waiting") ||
171         (status == "agree_waiting") ||
172         (status == "game"))
173       if (@sente)
174         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
175       elsif (@sente == false)
176         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
177       elsif (@sente == nil)
178         return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
179       end
180     else
181       return sprintf("%s %s %s", @name, @protocol, @status)
182     end
183   end
184
185   def write_help
186     @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
187   end
188
189   def login(str)
190     str =~ /([\r\n]*)$/
191     @eol = $1
192     str.chomp!
193     (login, @name, @password, ext) = str.split
194     if (ext)
195       @protocol = "x1"
196     else
197       @protocol = "CSA"
198     end
199     @main_thread = Thread::current
200     @writer_thread = Thread::start do
201       writer()
202     end
203   end
204     
205   def run
206     write_safe(sprintf("LOGIN:%s OK\n", @name))
207     if (@protocol != "CSA")
208       log_message(sprintf("user %s run in %s mode", @name, @protocol))
209       write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
210     else
211       log_message(sprintf("user %s run in CSA mode", @name))
212       if (good_game_name?(@password))
213         csa_1st_str = "%%GAME #{@password} *"
214       else
215         csa_1st_str = "%%GAME #{Default_Game_Name} *"
216       end
217     end
218     
219     while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
220       begin
221         $mutex.lock
222         if (csa_1st_str)
223           str = csa_1st_str
224           csa_1st_str = nil
225         end
226         if (@write_queue.size > Max_Write_Queue_Size)
227           log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
228                 return
229         end
230
231         if (@status == "finished")
232           return
233         end
234         str.chomp! if (str.class == String)
235         case str
236         when /^[\+\-][^%]/
237           if (@status == "game")
238             array_str = str.split(",")
239             move = array_str.shift
240             additional = array_str.shift
241             if /^'(.*)/ =~ additional
242               comment = array_str.unshift("'*#{$1}")
243             end
244             s = @game.handle_one_move(move, self)
245             @game.fh.print("#{comment}\n") if (comment && !s)
246             return if (s && @protocol == "CSA")
247           end
248         when /^%[^%]/, :timeout
249           if (@status == "game")
250             s = @game.handle_one_move(str, self)
251             return if (s && @protocol == "CSA")
252           end
253         when /^REJECT/
254           if (@status == "agree_waiting")
255             @game.reject(@name)
256             return if (@protocol == "CSA")
257           else
258             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
259           end
260         when /^AGREE/
261           if (@status == "agree_waiting")
262             @status = "start_waiting"
263             if ((@game.sente.status == "start_waiting") &&
264                 (@game.gote.status == "start_waiting"))
265               @game.start
266               @game.sente.status = "game"
267               @game.gote.status = "game"
268             end
269           else
270             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
271           end
272         when /^%%SHOW\s+(\S+)/
273           game_id = $1
274           if (LEAGUE.games[game_id])
275             write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
276           end
277           write_safe("##[SHOW] +OK\n")
278         when /^%%MONITORON\s+(\S+)/
279           game_id = $1
280           if (LEAGUE.games[game_id])
281             LEAGUE.games[game_id].monitoron(self)
282             write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
283             write_safe("##[MONITOR][#{game_id}] +OK\n")
284           end
285         when /^%%MONITOROFF\s+(\S+)/
286           game_id = $1
287           if (LEAGUE.games[game_id])
288             LEAGUE.games[game_id].monitoroff(self)
289           end
290         when /^%%HELP/
291           write_help
292         when /^%%GAME\s*$/
293           if ((@status == "connected") || (@status == "game_waiting"))
294             @status = "connected"
295             @game_name = ""
296           else
297             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
298           end
299         when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
300           command_name = $1
301           game_name = $2
302           my_sente_str = $3
303           if (! good_game_name?(game_name))
304             write_safe(sprintf("##[ERROR] bad game name\n"))
305             next
306           elsif ((@status == "connected") || (@status == "game_waiting"))
307             ## continue
308           else
309             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
310             next
311           end
312           if ((my_sente_str == "*") ||
313               (my_sente_str == "+") ||
314               (my_sente_str == "-"))
315             ## ok
316           else
317             write_safe(sprintf("##[ERROR] bad game option\n"))
318             next
319           end
320
321           if (my_sente_str == "*")
322             rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
323           elsif (my_sente_str == "+")
324             rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
325           elsif (my_sente_str == "-")
326             rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
327           else
328             ## never reached
329           end
330           if (rival)
331             @game_name = game_name
332             if ((my_sente_str == "*") && (rival.sente == nil))
333               if (rand(2) == 0)
334                 @sente = true
335                 rival.sente = false
336               else
337                 @sente = false
338                 rival.sente = true
339               end
340             elsif (rival.sente == true) # rival has higher priority
341               @sente = false
342             elsif (rival.sente == false)
343               @sente = true
344             elsif (my_sente_str == "+")
345               @sente = true
346               rival.sente = false
347             elsif (my_sente_str == "-")
348               @sente = false
349               rival.sente = true
350             else
351               ## never reached
352             end
353             Game::new(@game_name, self, rival)
354             self.status = "agree_waiting"
355             rival.status = "agree_waiting"
356           else # rival not found
357             if (command_name == "GAME")
358               @status = "game_waiting"
359               @game_name = game_name
360               if (my_sente_str == "+")
361                 @sente = true
362               elsif (my_sente_str == "-")
363                 @sente = false
364               else
365                 @sente = nil
366               end
367             else                # challenge
368               write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
369               @status = "connected"
370               @game_name = ""
371               @sente = nil
372             end
373           end
374         when /^%%CHAT\s+(.+)/
375           message = $1
376           LEAGUE.players.each do |name, player|
377             if (player.protocol != "CSA")
378               player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
379             end
380           end
381         when /^%%LIST/
382           buf = Array::new
383           LEAGUE.games.each do |id, game|
384             buf.push(sprintf("##[LIST] %s\n", id))
385           end
386           buf.push("##[LIST] +OK\n")
387           write_safe(buf.join)
388         when /^%%WHO/
389           buf = Array::new
390           LEAGUE.players.each do |name, player|
391             buf.push(sprintf("##[WHO] %s\n", player.to_s))
392           end
393           buf.push("##[WHO] +OK\n")
394           write_safe(buf.join)
395         when /^LOGOUT/
396           @status = "connected"
397           write_safe("LOGOUT:completed\n")
398           return
399         when /^\s*$/
400           ## ignore null string
401         else
402           write_safe(sprintf("##[ERROR] unknown command %s\n", str))
403         end
404       ensure
405         $mutex.unlock
406       end
407     end                         # enf of while
408   end
409 end
410
411 class Piece
412   PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
413   def initialize(board, x, y, sente, promoted=false)
414     @board = board
415     @x = x
416     @y = y
417     @sente = sente
418     @promoted = promoted
419
420     if ((x == 0) || (y == 0))
421       if (sente)
422         hands = board.sente_hands
423       else
424         hands = board.gote_hands
425       end
426       hands.push(self)
427       hands.sort! {|a, b|
428         a.name <=> b.name
429       }
430     else
431       @board.array[x][y] = self
432     end
433   end
434   attr_accessor :promoted, :sente, :x, :y, :board
435
436   def room_of_head?(x, y, name)
437     true
438   end
439
440   def movable_grids
441     return adjacent_movable_grids + far_movable_grids
442   end
443
444   def far_movable_grids
445     return []
446   end
447
448   def jump_to?(x, y)
449     if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
450       if ((@board.array[x][y] == nil) || # dst is empty
451           (@board.array[x][y].sente != @sente)) # dst is enemy
452         return true
453       end
454     end
455     return false
456   end
457
458   def put_to?(x, y)
459     if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
460       if (@board.array[x][y] == nil) # dst is empty?
461         return true
462       end
463     end
464     return false
465   end
466
467   def adjacent_movable_grids
468     grids = Array::new
469     if (@promoted)
470       moves = @promoted_moves
471     else
472       moves = @normal_moves
473     end
474     moves.each do |(dx, dy)|
475       if (@sente)
476         cand_y = @y - dy
477       else
478         cand_y = @y + dy
479       end
480       cand_x = @x + dx
481       if (jump_to?(cand_x, cand_y))
482         grids.push([cand_x, cand_y])
483       end
484     end
485     return grids
486   end
487
488   def move_to?(x, y, name)
489     return false if (! room_of_head?(x, y, name))
490     return false if ((name != @name) && (name != @promoted_name))
491     return false if (@promoted && (name != @promoted_name)) # can't un-promote
492
493     if (! @promoted)
494       return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
495       if (@sente)
496         return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
497       else
498         return false if ((6 >= @y) && (6 >= y) && (name != @name))
499       end
500     end
501
502     if ((@x == 0) || (@y == 0))
503       return jump_to?(x, y)
504     else
505       return movable_grids.include?([x, y])
506     end
507   end
508
509   def move_to(x, y)
510     if ((@x == 0) || (@y == 0))
511       if (@sente)
512         @board.sente_hands.delete(self)
513       else
514         @board.gote_hands.delete(self)
515       end
516       @board.array[x][y] = self
517     elsif ((x == 0) || (y == 0))
518       @promoted = false         # clear promoted flag before moving to hands
519       if (@sente)
520         @board.sente_hands.push(self)
521       else
522         @board.gote_hands.push(self)
523       end
524       @board.array[@x][@y] = nil
525     else
526       @board.array[@x][@y] = nil
527       @board.array[x][y] = self
528     end
529     @x = x
530     @y = y
531   end
532
533   def point
534     @point
535   end
536
537   def name
538     @name
539   end
540
541   def promoted_name
542     @promoted_name
543   end
544
545   def to_s
546     if (@sente)
547       sg = "+"
548     else
549       sg = "-"
550     end
551     if (@promoted)
552       n = @promoted_name
553     else
554       n = @name
555     end
556     return sg + n
557   end
558 end
559
560 class PieceFU < Piece
561   def initialize(*arg)
562     @point = 1
563     @normal_moves = [[0, +1]]
564     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
565     @name = "FU"
566     @promoted_name = "TO"
567     super
568   end
569   def room_of_head?(x, y, name)
570     if (name == "FU")
571       if (@sente)
572         return false if (y == 1)
573       else
574         return false if (y == 9)
575       end
576       ## 2fu check
577       c = 0
578       iy = 1
579       while (iy <= 9)
580         if ((iy  != @y) &&      # not source position
581             @board.array[x][iy] &&
582             (@board.array[x][iy].sente == @sente) && # mine
583             (@board.array[x][iy].name == "FU") &&
584             (@board.array[x][iy].promoted == false))
585           return false
586         end
587         iy = iy + 1
588       end
589     end
590     return true
591   end
592 end
593
594 class PieceKY  < Piece
595   def initialize(*arg)
596     @point = 1
597     @normal_moves = []
598     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
599     @name = "KY"
600     @promoted_name = "NY"
601     super
602   end
603   def room_of_head?(x, y, name)
604     if (name == "KY")
605       if (@sente)
606         return false if (y == 1)
607       else
608         return false if (y == 9)
609       end
610     end
611     return true
612   end
613   def far_movable_grids
614     grids = Array::new
615     if (@promoted)
616       return []
617     else
618       if (@sente)                 # up
619         cand_x = @x
620         cand_y = @y - 1
621         while (jump_to?(cand_x, cand_y))
622           grids.push([cand_x, cand_y])
623           break if (! put_to?(cand_x, cand_y))
624           cand_y = cand_y - 1
625         end
626       else                        # down
627         cand_x = @x
628         cand_y = @y + 1
629         while (jump_to?(cand_x, cand_y))
630           grids.push([cand_x, cand_y])
631           break if (! put_to?(cand_x, cand_y))
632           cand_y = cand_y + 1
633         end
634       end
635       return grids
636     end
637   end
638 end
639 class PieceKE  < Piece
640   def initialize(*arg)
641     @point = 1
642     @normal_moves = [[+1, +2], [-1, +2]]
643     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
644     @name = "KE"
645     @promoted_name = "NK"
646     super
647   end
648   def room_of_head?(x, y, name)
649     if (name == "KE")
650       if (@sente)
651         return false if ((y == 1) || (y == 2))
652       else
653         return false if ((y == 9) || (y == 8))
654       end
655     end
656     return true
657   end
658 end
659 class PieceGI  < Piece
660   def initialize(*arg)
661     @point = 1
662     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
663     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
664     @name = "GI"
665     @promoted_name = "NG"
666     super
667   end
668 end
669 class PieceKI  < Piece
670   def initialize(*arg)
671     @point = 1
672     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
673     @promoted_moves = []
674     @name = "KI"
675     @promoted_name = nil
676     super
677   end
678 end
679 class PieceKA  < Piece
680   def initialize(*arg)
681     @point = 5
682     @normal_moves = []
683     @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
684     @name = "KA"
685     @promoted_name = "UM"
686     super
687   end
688   def far_movable_grids
689     grids = Array::new
690     ## up right
691     cand_x = @x - 1
692     cand_y = @y - 1
693     while (jump_to?(cand_x, cand_y))
694       grids.push([cand_x, cand_y])
695       break if (! put_to?(cand_x, cand_y))
696       cand_x = cand_x - 1
697       cand_y = cand_y - 1
698     end
699     ## down right
700     cand_x = @x - 1
701     cand_y = @y + 1
702     while (jump_to?(cand_x, cand_y))
703       grids.push([cand_x, cand_y])
704       break if (! put_to?(cand_x, cand_y))
705       cand_x = cand_x - 1
706       cand_y = cand_y + 1
707     end
708     ## up left
709     cand_x = @x + 1
710     cand_y = @y - 1
711     while (jump_to?(cand_x, cand_y))
712       grids.push([cand_x, cand_y])
713       break if (! put_to?(cand_x, cand_y))
714       cand_x = cand_x + 1
715       cand_y = cand_y - 1
716     end
717     ## down left
718     cand_x = @x + 1
719     cand_y = @y + 1
720     while (jump_to?(cand_x, cand_y))
721       grids.push([cand_x, cand_y])
722       break if (! put_to?(cand_x, cand_y))
723       cand_x = cand_x + 1
724       cand_y = cand_y + 1
725     end
726     return grids
727   end
728 end
729 class PieceHI  < Piece
730   def initialize(*arg)
731     @point = 5
732     @normal_moves = []
733     @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
734     @name = "HI"
735     @promoted_name = "RY"
736     super
737   end
738   def far_movable_grids
739     grids = Array::new
740     ## up
741     cand_x = @x
742     cand_y = @y - 1
743     while (jump_to?(cand_x, cand_y))
744       grids.push([cand_x, cand_y])
745       break if (! put_to?(cand_x, cand_y))
746       cand_y = cand_y - 1
747     end
748     ## down
749     cand_x = @x
750     cand_y = @y + 1
751     while (jump_to?(cand_x, cand_y))
752       grids.push([cand_x, cand_y])
753       break if (! put_to?(cand_x, cand_y))
754       cand_y = cand_y + 1
755     end
756     ## right
757     cand_x = @x - 1
758     cand_y = @y
759     while (jump_to?(cand_x, cand_y))
760       grids.push([cand_x, cand_y])
761       break if (! put_to?(cand_x, cand_y))
762       cand_x = cand_x - 1
763     end
764     ## down
765     cand_x = @x + 1
766     cand_y = @y
767     while (jump_to?(cand_x, cand_y))
768       grids.push([cand_x, cand_y])
769       break if (! put_to?(cand_x, cand_y))
770       cand_x = cand_x + 1
771     end
772     return grids
773   end
774 end
775 class PieceOU < Piece
776   def initialize(*arg)
777     @point = 0
778     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
779     @promoted_moves = []
780     @name = "OU"
781     @promoted_name = nil
782     super
783   end
784 end
785
786 class Board
787   def initialize
788     @sente_hands = Array::new
789     @gote_hands = Array::new
790     @history = Hash::new
791     @sente_history = Hash::new
792     @gote_history = Hash::new
793     @array = [[], [], [], [], [], [], [], [], [], []]
794   end
795   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
796
797   def initial
798     PieceKY::new(self, 1, 1, false)
799     PieceKE::new(self, 2, 1, false)
800     PieceGI::new(self, 3, 1, false)
801     PieceKI::new(self, 4, 1, false)
802     PieceOU::new(self, 5, 1, false)
803     PieceKI::new(self, 6, 1, false)
804     PieceGI::new(self, 7, 1, false)
805     PieceKE::new(self, 8, 1, false)
806     PieceKY::new(self, 9, 1, false)
807     PieceKA::new(self, 2, 2, false)
808     PieceHI::new(self, 8, 2, false)
809     PieceFU::new(self, 1, 3, false)
810     PieceFU::new(self, 2, 3, false)
811     PieceFU::new(self, 3, 3, false)
812     PieceFU::new(self, 4, 3, false)
813     PieceFU::new(self, 5, 3, false)
814     PieceFU::new(self, 6, 3, false)
815     PieceFU::new(self, 7, 3, false)
816     PieceFU::new(self, 8, 3, false)
817     PieceFU::new(self, 9, 3, false)
818
819     PieceKY::new(self, 1, 9, true)
820     PieceKE::new(self, 2, 9, true)
821     PieceGI::new(self, 3, 9, true)
822     PieceKI::new(self, 4, 9, true)
823     PieceOU::new(self, 5, 9, true)
824     PieceKI::new(self, 6, 9, true)
825     PieceGI::new(self, 7, 9, true)
826     PieceKE::new(self, 8, 9, true)
827     PieceKY::new(self, 9, 9, true)
828     PieceKA::new(self, 8, 8, true)
829     PieceHI::new(self, 2, 8, true)
830     PieceFU::new(self, 1, 7, true)
831     PieceFU::new(self, 2, 7, true)
832     PieceFU::new(self, 3, 7, true)
833     PieceFU::new(self, 4, 7, true)
834     PieceFU::new(self, 5, 7, true)
835     PieceFU::new(self, 6, 7, true)
836     PieceFU::new(self, 7, 7, true)
837     PieceFU::new(self, 8, 7, true)
838     PieceFU::new(self, 9, 7, true)
839   end
840
841   def have_piece?(hands, name)
842     piece = hands.find { |i|
843       i.name == name
844     }
845     return piece
846   end
847
848   def move_to(x0, y0, x1, y1, name, sente)
849     if (sente)
850       hands = @sente_hands
851     else
852       hands = @gote_hands
853     end
854
855     if ((x0 == 0) || (y0 == 0))
856       piece = have_piece?(hands, name)
857       return :illegal if (! piece.move_to?(x1, y1, name))
858       piece.move_to(x1, y1)
859     else
860       return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
861       if (@array[x0][y0].name != name) # promoted ?
862         @array[x0][y0].promoted = true
863       end
864       if (@array[x1][y1])
865         if (@array[x1][y1].name == "OU")
866           return :outori        # return board update
867         end
868         @array[x1][y1].sente = @array[x0][y0].sente
869         @array[x1][y1].move_to(0, 0)
870         hands.sort! {|a, b|
871           a.name <=> b.name
872         }
873       end
874       @array[x0][y0].move_to(x1, y1)
875     end
876     return true
877   end
878
879   def look_for_ou(sente)
880     x = 1
881     while (x <= 9)
882       y = 1
883       while (y <= 9)
884         if (@array[x][y] &&
885             (@array[x][y].name == "OU") &&
886             (@array[x][y].sente == sente))
887           return @array[x][y]
888         end
889         y = y + 1
890       end
891       x = x + 1
892     end
893     raise "can't find ou"
894   end
895
896   def checkmated?(sente)        # sente is loosing
897     ou = look_for_ou(sente)
898     x = 1
899     while (x <= 9)
900       y = 1
901       while (y <= 9)
902         if (@array[x][y] &&
903             (@array[x][y].sente != sente))
904           if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
905             return true
906           end
907         end
908         y = y + 1
909       end
910       x = x + 1
911     end
912     return false
913   end
914
915   def uchifuzume?(sente)
916     rival_ou = look_for_ou(! sente)   # rival's ou
917     if (sente)                  # rival is gote
918       if ((rival_ou.y != 9) &&
919           (@array[rival_ou.x][rival_ou.y + 1]) &&
920           (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
921           (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
922         fu_x = rival_ou.x
923         fu_y = rival_ou.y + 1
924       else
925         return false
926       end
927     else                        # gote
928       if ((rival_ou.y != 0) &&
929           (@array[rival_ou.x][rival_ou.y - 1]) &&
930           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
931           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
932         fu_x = rival_ou.x
933         fu_y = rival_ou.y - 1
934       else
935         return false
936       end
937     end
938     
939     ## case: rival_ou is moving
940     escaped = false
941     rival_ou.movable_grids.each do |(cand_x, cand_y)|
942       tmp_board = Marshal.load(Marshal.dump(self))
943       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
944       raise "internal error" if (s != true)
945       if (! tmp_board.checkmated?(! sente)) # good move
946         return false
947       end
948     end
949
950     ## case: rival is capturing fu
951     x = 1
952     while (x <= 9)
953       y = 1
954       while (y <= 9)
955         if (@array[x][y] &&
956             (@array[x][y].sente != sente) &&
957             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
958           if (@array[x][y].promoted)
959             name = @array[x][y].promoted_name
960           else
961             name = @array[x][y].name
962           end
963           tmp_board = Marshal.load(Marshal.dump(self))
964           s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
965           raise "internal error" if (s != true)
966           if (! tmp_board.checkmated?(! sente)) # good move
967             return false
968           end
969         end
970         y = y + 1
971       end
972       x = x + 1
973     end
974     return true
975   end
976
977   def oute_sennichite?(sente)
978     if (checkmated?(! sente))
979       str = to_s
980       if (sente)
981         if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
982           return true
983         end
984       else
985         if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
986           return true
987         end
988       end
989     end
990     return false
991   end
992
993   def sennichite?(sente)
994     str = to_s
995     if (@history[str] && (@history[str] >= 3)) # already 3 times
996       return true
997     end
998     return false
999   end
1000
1001   def good_kachi?(sente)
1002     return false if (checkmated?(sente))
1003     ou = look_for_ou(sente)
1004     return false if (sente && (ou.y >= 4))
1005     return false if (! sente && (ou.y <= 6))
1006
1007     number = 0
1008     point = 0
1009
1010     if (sente)
1011       hands = @sente_hands
1012       r = [1, 2, 3]
1013     else
1014       hands = @gote_hands
1015       r = [7, 8, 9]
1016     end
1017     r.each do |y|
1018       x = 1
1019       while (x <= 9)
1020         if (@array[x][y] &&
1021             (@array[x][y].sente == sente) &&
1022             (@array[x][y].point > 0))
1023           point = point + @array[x][y].point
1024           number = number + 1
1025         end
1026         x = x + 1
1027       end
1028     end
1029     hands.each do |piece|
1030       point = point + piece.point
1031     end
1032
1033     return false if (number < 10)
1034     if (sente)
1035       return false if (point < 28)
1036     else
1037       return false if (point < 27)
1038     end
1039     return true
1040   end
1041
1042   def handle_one_move(str)
1043     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1044       sg = $1
1045       x0 = $2.to_i
1046       y0 = $3.to_i
1047       x1 = $4.to_i
1048       y1 = $5.to_i
1049       name = $6
1050     elsif (str =~ /^%KACHI/)
1051       if (@sente == @current_player)
1052         sente = true
1053       else
1054         sente = false
1055       end
1056       if (good_kachi?(sente))
1057         return :kachi_win
1058       else
1059         return :kachi_lose
1060       end
1061     elsif (str =~ /^%TORYO/)
1062       return :toryo
1063     else
1064       return :illegal
1065     end
1066     
1067     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1068         ((x0 != 0) || (y0 != 0)))
1069       return :illegal
1070     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1071       return :illegal
1072     end
1073     
1074     if (sg == "+")
1075       sente = true
1076       hands = @sente_hands
1077     else
1078       sente = false
1079       hands = @gote_hands
1080     end
1081     
1082     ## source check
1083     if ((x0 == 0) && (y0 == 0))
1084       return :illegal if (! have_piece?(hands, name))
1085     elsif (! @array[x0][y0])
1086       return :illegal           # no piece
1087     elsif (@array[x0][y0].sente != sente)
1088       return :illegal           # this is not mine
1089     elsif (@array[x0][y0].name != name)
1090       return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1091     end
1092
1093     ## destination check
1094     if (@array[x1][y1] &&
1095         (@array[x1][y1].sente == sente)) # can't capture mine
1096       return :illegal
1097     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1098       return :illegal           # can't put on existing piece
1099     end
1100
1101     tmp_board = Marshal.load(Marshal.dump(self))
1102     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1103     return :oute_kaihimore if (tmp_board.checkmated?(sente))
1104     return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1105     return :sennichite if tmp_board.sennichite?(sente)
1106
1107     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1108       return :uchifuzume
1109     end
1110
1111     move_to(x0, y0, x1, y1, name, sente)
1112     str = to_s
1113
1114     if (checkmated?(! sente))
1115       if (sente)
1116         @sente_history[str] = (@sente_history[str] || 0) + 1
1117       else
1118         @gote_history[str] = (@gote_history[str] || 0) + 1
1119       end
1120     else
1121       if (sente)
1122         @sente_history.clear
1123       else
1124         @gote_history.clear
1125       end
1126     end
1127     @history[str] = (@history[str] || 0) + 1
1128     return :normal
1129   end
1130
1131   def to_s
1132     a = Array::new
1133     y = 1
1134     while (y <= 9)
1135       a.push(sprintf("P%d", y))
1136       x = 9
1137       while (x >= 1)
1138         piece = @array[x][y]
1139         if (piece)
1140           s = piece.to_s
1141         else
1142           s = " * "
1143         end
1144         a.push(s)
1145         x = x - 1
1146       end
1147       a.push(sprintf("\n"))
1148       y = y + 1
1149     end
1150     if (! sente_hands.empty?)
1151       a.push("P+")
1152       sente_hands.each do |p|
1153         a.push("00" + p.name)
1154       end
1155       a.push("\n")
1156     end
1157     if (! gote_hands.empty?)
1158       a.push("P-")
1159       gote_hands.each do |p|
1160         a.push("00" + p.name)
1161       end
1162       a.push("\n")
1163     end
1164     a.push("+\n")
1165     return a.join
1166   end
1167 end
1168
1169 class Game
1170   @@mutex = Mutex.new
1171   @@time  = 0
1172
1173   def initialize(game_name, player0, player1)
1174     @monitors = Array::new
1175     @game_name = game_name
1176     if (@game_name =~ /-(\d+)-(\d+)$/)
1177       @total_time = $1.to_i
1178       @byoyomi = $2.to_i
1179     end
1180
1181     if (player0.sente)
1182       @sente = player0
1183       @gote = player1
1184     else
1185       @sente = player1
1186       @gote = player0
1187     end
1188     @current_player = @sente
1189     @next_player = @gote
1190
1191     @sente.game = self
1192     @gote.game = self
1193
1194     @last_move = ""
1195     @current_turn = 0
1196
1197     @sente.status = "agree_waiting"
1198     @gote.status = "agree_waiting"
1199     
1200     @id = sprintf("%s+%s+%s+%s+%s", 
1201                   LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1202     @logfile = @id + ".csa"
1203
1204     LEAGUE.games[@id] = self
1205
1206     log_message(sprintf("game created %s", @id))
1207
1208     @board = Board::new
1209     @board.initial
1210     @start_time = nil
1211     @fh = nil
1212
1213     propose
1214   end
1215   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1216   attr_accessor :last_move, :current_turn
1217
1218   def monitoron(monitor)
1219     @monitors.delete(monitor)
1220     @monitors.push(monitor)
1221   end
1222
1223   def monitoroff(monitor)
1224     @monitors.delete(monitor)
1225   end
1226
1227   def reject(rejector)
1228     @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1229     @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1230     finish
1231   end
1232
1233   def kill(killer)
1234     if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1235       reject(killer.name)
1236     elsif (@current_player == killer)
1237       abnormal_lose()
1238       finish
1239     end
1240   end
1241
1242   def finish
1243     log_message(sprintf("game finished %s", @id))
1244     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
1245     @fh.close
1246
1247     @sente.game = nil
1248     @gote.game = nil
1249     @sente.status = "connected"
1250     @gote.status = "connected"
1251
1252     if (@current_player.protocol == "CSA")
1253       @current_player.finish
1254     end
1255     if (@next_player.protocol == "CSA")
1256       @next_player.finish
1257     end
1258     @monitors = Array::new
1259     @sente = nil
1260     @gote = nil
1261     @current_player = nil
1262     @next_player = nil
1263     LEAGUE.games.delete(@id)
1264   end
1265
1266   def handle_one_move(str, player)
1267     finish_flag = true
1268     if (@current_player == player)
1269       @end_time = Time::new
1270       t = (@end_time - @start_time).ceil
1271       t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1272       
1273       move_status = nil
1274       if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1275         status = :timeout
1276       elsif (str == :timeout)
1277         return false            # time isn't expired. players aren't swapped. continue game
1278       else
1279         @current_player.mytime = @current_player.mytime - t
1280         if (@current_player.mytime < 0)
1281           @current_player.mytime = 0
1282         end
1283
1284 #        begin
1285           move_status = @board.handle_one_move(str)
1286 #        rescue
1287 #          log_error("handle_one_move raise exception for #{str}")
1288 #          move_status = :illegal
1289 #        end
1290
1291         if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1292           @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1293         else
1294           if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1295             @sente.write_safe(sprintf("%s,T%d\n", str, t))
1296             @gote.write_safe(sprintf("%s,T%d\n", str, t))
1297             @fh.printf("%s\nT%d\n", str, t)
1298             @last_move = sprintf("%s,T%d", str, t)
1299             @current_turn = @current_turn + 1
1300           end
1301
1302           @monitors.each do |monitor|
1303             monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1304             monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1305           end
1306         end
1307       end
1308
1309       if (@next_player.status != "game") # rival is logout or disconnected
1310         abnormal_win()
1311       elsif (status == :timeout)
1312         timeout_lose()
1313       elsif (move_status == :illegal)
1314         illegal_lose()
1315       elsif (move_status == :kachi_win)
1316         kachi_win()
1317       elsif (move_status == :kachi_lose)
1318         kachi_lose()
1319       elsif (move_status == :toryo)
1320         toryo_lose()
1321       elsif (move_status == :outori)
1322         outori_win()
1323       elsif (move_status == :sennichite)
1324         sennichite_draw()
1325       elsif (move_status == :oute_sennichite)
1326         oute_sennichite_lose()
1327       elsif (move_status == :uchifuzume)
1328         uchifuzume_lose()
1329       elsif (move_status == :oute_kaihimore)
1330         oute_kaihimore_lose()
1331       else
1332         finish_flag = false
1333       end
1334       finish() if finish_flag
1335       (@current_player, @next_player) = [@next_player, @current_player]
1336       @start_time = Time::new
1337       return finish_flag
1338     end
1339   end
1340
1341   def abnormal_win
1342     @current_player.status = "connected"
1343     @next_player.status = "connected"
1344     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1345     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1346     @fh.printf("%%TORYO\n")
1347     @fh.print(@board.to_s.gsub(/^/, "\'"))
1348     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1349     @monitors.each do |monitor|
1350       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1351     end
1352   end
1353
1354   def abnormal_lose
1355     @current_player.status = "connected"
1356     @next_player.status = "connected"
1357     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1358     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1359     @fh.printf("%%TORYO\n")
1360     @fh.print(@board.to_s.gsub(/^/, "\'"))
1361     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1362     @monitors.each do |monitor|
1363       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1364     end
1365   end
1366
1367   def sennichite_draw
1368     @current_player.status = "connected"
1369     @next_player.status = "connected"
1370     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1371     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1372     @fh.print(@board.to_s.gsub(/^/, "\'"))
1373     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1374     @monitors.each do |monitor|
1375       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1376     end
1377   end
1378
1379   def oute_sennichite_lose
1380     @current_player.status = "connected"
1381     @next_player.status = "connected"
1382     @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1383     @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1384     @fh.print(@board.to_s.gsub(/^/, "\'"))
1385     @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1386     @monitors.each do |monitor|
1387       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1388     end
1389   end
1390
1391   def illegal_lose
1392     @current_player.status = "connected"
1393     @next_player.status = "connected"
1394     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1395     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1396     @fh.print(@board.to_s.gsub(/^/, "\'"))
1397     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1398     @monitors.each do |monitor|
1399       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1400     end
1401   end
1402
1403   def uchifuzume_lose
1404     @current_player.status = "connected"
1405     @next_player.status = "connected"
1406     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1407     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1408     @fh.print(@board.to_s.gsub(/^/, "\'"))
1409     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1410     @monitors.each do |monitor|
1411       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1412     end
1413   end
1414
1415   def oute_kaihimore_lose
1416     @current_player.status = "connected"
1417     @next_player.status = "connected"
1418     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1419     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1420     @fh.print(@board.to_s.gsub(/^/, "\'"))
1421     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1422     @monitors.each do |monitor|
1423       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1424     end
1425   end
1426
1427   def timeout_lose
1428     @current_player.status = "connected"
1429     @next_player.status = "connected"
1430     @current_player.write_safe("#TIME_UP\n#LOSE\n")
1431     @next_player.write_safe("#TIME_UP\n#WIN\n")
1432     @fh.print(@board.to_s.gsub(/^/, "\'"))
1433     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1434     @monitors.each do |monitor|
1435       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1436     end
1437   end
1438
1439   def kachi_win
1440     @current_player.status = "connected"
1441     @next_player.status = "connected"
1442     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1443     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1444     @fh.printf("%%KACHI\n")
1445     @fh.print(@board.to_s.gsub(/^/, "\'"))
1446     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1447     @monitors.each do |monitor|
1448       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1449     end
1450   end
1451
1452   def kachi_lose
1453     @current_player.status = "connected"
1454     @next_player.status = "connected"
1455     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1456     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1457     @fh.printf("%%KACHI\n")
1458     @fh.print(@board.to_s.gsub(/^/, "\'"))
1459     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1460     @monitors.each do |monitor|
1461       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1462     end
1463   end
1464
1465   def toryo_lose
1466     @current_player.status = "connected"
1467     @next_player.status = "connected"
1468     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1469     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1470     @fh.printf("%%TORYO\n")
1471     @fh.print(@board.to_s.gsub(/^/, "\'"))
1472     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1473     @monitors.each do |monitor|
1474       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1475     end
1476   end
1477
1478   def outori_win
1479     @current_player.status = "connected"
1480     @next_player.status = "connected"
1481     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1482     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1483     @fh.print(@board.to_s.gsub(/^/, "\'"))
1484     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1485     @monitors.each do |monitor|
1486       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1487     end
1488   end
1489
1490   def start
1491     log_message(sprintf("game started %s", @id))
1492     @sente.write_safe(sprintf("START:%s\n", @id))
1493     @gote.write_safe(sprintf("START:%s\n", @id))
1494     @sente.mytime = @total_time
1495     @gote.mytime = @total_time
1496     @start_time = Time::new
1497   end
1498
1499   def propose
1500     begin
1501       @fh = open(@logfile, "w")
1502       @fh.sync = true
1503
1504       @fh.printf("V2\n")
1505       @fh.printf("N+%s\n", @sente.name)
1506       @fh.printf("N-%s\n", @gote.name)
1507       @fh.printf("$EVENT:%s\n", @id)
1508
1509       @sente.write_safe(propose_message("+"))
1510       @gote.write_safe(propose_message("-"))
1511
1512       @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1513       @fh.print <<EOM
1514 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1515 P2 * -HI *  *  *  *  * -KA * 
1516 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1517 P4 *  *  *  *  *  *  *  *  * 
1518 P5 *  *  *  *  *  *  *  *  * 
1519 P6 *  *  *  *  *  *  *  *  * 
1520 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1521 P8 * +KA *  *  *  *  * +HI * 
1522 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1523 +
1524 EOM
1525     end
1526   end
1527
1528   def show()
1529     str0 = <<EOM
1530 BEGIN Game_Summary
1531 Protocol_Version:1.0
1532 Protocol_Mode:Server
1533 Format:Shogi 1.0
1534 Game_ID:#{@id}
1535 Name+:#{@sente.name}
1536 Name-:#{@gote.name}
1537 Rematch_On_Draw:NO
1538 To_Move:+
1539 BEGIN Time
1540 Time_Unit:1sec
1541 Total_Time:#{@total_time}
1542 Byoyomi:#{@byoyomi}
1543 Least_Time_Per_Move:#{Least_Time_Per_Move}
1544 Remaining_Time+:#{@sente.mytime}
1545 Remaining_Time-:#{@gote.mytime}
1546 Last_Move:#{@last_move}
1547 Current_Turn:#{@current_turn}
1548 END Time
1549 BEGIN Position
1550 Jishogi_Declaration:1.1
1551 EOM
1552
1553     str1 = <<EOM
1554 END Position
1555 END Game_Summary
1556 EOM
1557
1558     return str0 + @board.to_s + str1
1559   end
1560
1561   def propose_message(sg_flag)
1562     str = <<EOM
1563 BEGIN Game_Summary
1564 Protocol_Version:1.0
1565 Protocol_Mode:Server
1566 Format:Shogi 1.0
1567 Game_ID:#{@id}
1568 Name+:#{@sente.name}
1569 Name-:#{@gote.name}
1570 Your_Turn:#{sg_flag}
1571 Rematch_On_Draw:NO
1572 To_Move:+
1573 BEGIN Time
1574 Time_Unit:1sec
1575 Total_Time:#{@total_time}
1576 Byoyomi:#{@byoyomi}
1577 Least_Time_Per_Move:#{Least_Time_Per_Move}
1578 END Time
1579 BEGIN Position
1580 Jishogi_Declaration:1.1
1581 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1582 P2 * -HI *  *  *  *  * -KA * 
1583 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1584 P4 *  *  *  *  *  *  *  *  * 
1585 P5 *  *  *  *  *  *  *  *  * 
1586 P6 *  *  *  *  *  *  *  *  * 
1587 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1588 P8 * +KA *  *  *  *  * +HI * 
1589 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1590 P+
1591 P-
1592 +
1593 END Position
1594 END Game_Summary
1595 EOM
1596     return str
1597   end
1598   
1599   private
1600   
1601   def issue_current_time
1602     time = Time::new.strftime("%Y%m%d%H%M%S").to_i
1603     @@mutex.synchronize do
1604       while time <= @@time do
1605         time += 1
1606       end
1607       @@time = time
1608     end
1609   end
1610 end
1611
1612 def usage
1613     print <<EOM
1614 NAME
1615         shogi-server - server for CSA server protocol
1616
1617 SYNOPSIS
1618         shogi-server event_name port_number
1619
1620 DESCRIPTION
1621         server for CSA server protocol
1622
1623 OPTIONS
1624         --pid-file file
1625                 specify filename for logging process ID
1626
1627 LICENSE
1628         this file is distributed under GPL version2 and might be compiled by Exerb
1629
1630 SEE ALSO
1631
1632 RELEASE
1633         #{Release}
1634
1635 REVISION
1636         #{Revision}
1637 EOM
1638 end
1639
1640 def log_message(str)
1641   printf("%s message: %s\n", Time::new.to_s, str)
1642 end
1643
1644 def log_warning(str)
1645   printf("%s warning: %s\n", Time::new.to_s, str)
1646 end
1647
1648 def log_error(str)
1649   printf("%s error: %s\n", Time::new.to_s, str)
1650 end
1651
1652
1653 def parse_command_line
1654   options = Hash::new
1655   parser = GetoptLong.new
1656   parser.ordering = GetoptLong::REQUIRE_ORDER
1657   parser.set_options(
1658                      ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1659
1660   parser.quiet = true
1661   begin
1662     parser.each_option do |name, arg|
1663       name.sub!(/^--/, '')
1664       options[name] = arg.dup
1665     end
1666   rescue
1667     usage
1668     raise parser.error_message
1669   end
1670   return options
1671 end
1672
1673 def good_game_name?(str)
1674   if ((str =~ /^(.+)-\d+-\d+$/) &&
1675       (good_identifier?($1)))
1676     return true
1677   else
1678     return false
1679   end
1680 end
1681
1682 def good_identifier?(str)
1683   if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1684       (str.length < Max_Identifier_Length))
1685     return true
1686   else
1687     return false
1688   end
1689 end
1690
1691 def good_login?(str)
1692   tokens = str.split
1693   if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1694       (tokens[0] == "LOGIN") &&
1695       (good_identifier?(tokens[1])))
1696     return true
1697   else
1698     return false
1699   end
1700 end
1701
1702 def  write_pid_file(file)
1703   open(file, "w") do |fh|
1704     fh.print Process::pid, "\n"
1705   end
1706 end
1707
1708 def mutex_watchdog(mutex, sec)
1709   while true
1710     begin
1711       timeout(sec) do
1712         begin
1713           mutex.lock
1714         ensure
1715           mutex.unlock
1716         end
1717       end
1718       sleep(sec)
1719     rescue TimeoutError
1720       log_error("mutex watchdog timeout")
1721       exit(1)
1722     end
1723   end
1724 end
1725
1726 def main
1727   $mutex = Mutex::new
1728   Thread::start do
1729     Thread.pass
1730     mutex_watchdog($mutex, 10)
1731   end
1732
1733   $options = parse_command_line
1734   if (ARGV.length != 2)
1735     usage
1736     exit 2
1737   end
1738
1739   LEAGUE.event = ARGV.shift
1740   port = ARGV.shift
1741
1742   write_pid_file($options["pid-file"]) if ($options["pid-file"])
1743
1744
1745
1746   server = TCPserver.open(port)
1747   log_message("server started")
1748
1749   while true
1750     Thread::start(server.accept) do |client|
1751       Thread.pass
1752       client.sync = true
1753       player = nil
1754       while (str = client.gets_timeout(Login_Time))
1755         begin
1756           $mutex.lock
1757           str =~ /([\r\n]*)$/
1758           eol = $1
1759           if (good_login?(str))
1760             player = Player::new(str, client)
1761             if (LEAGUE.players[player.name])
1762               if ((LEAGUE.players[player.name].password == player.password) &&
1763                   (LEAGUE.players[player.name].status != "game"))
1764                 log_message(sprintf("user %s login forcely", player.name))
1765                 LEAGUE.players[player.name].kill
1766               else
1767                 client.write_safe("LOGIN:incorrect" + eol)
1768                 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1769                 client.close
1770                 Thread::exit
1771                 return
1772               end
1773             end
1774             LEAGUE.add(player)
1775             break
1776           else
1777             client.write_safe("LOGIN:incorrect" + eol)
1778             client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1779           end
1780         ensure
1781           $mutex.unlock
1782         end
1783       end                       # login loop
1784       if (! player)
1785         client.close
1786         Thread::exit
1787         return
1788       end
1789       log_message(sprintf("user %s login", player.name))
1790       player.run
1791       begin
1792         $mutex.lock
1793         if (player.game)
1794           player.game.kill(player)
1795         end
1796         player.finish # socket has been closed
1797         LEAGUE.delete(player)
1798         log_message(sprintf("user %s logout", player.name))
1799       ensure
1800         $mutex.unlock
1801       end
1802     end
1803   end
1804 end
1805
1806 if ($0 == __FILE__)
1807   LEAGUE = League::new
1808   main
1809 end