OSDN Git Service

Fix a jishogi bug.
[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     if (checkmated?(sente))
1003       puts "'NG: Checkmating." if $DEBUG
1004       return false 
1005     end
1006     
1007     ou = look_for_ou(sente)
1008     if (sente && (ou.y >= 4))
1009       puts "'NG: Black's OU does not enter yet." if $DEBUG
1010       return false     
1011     end  
1012     if (! sente && (ou.y <= 6))
1013       puts "'NG: White's OU does not enter yet." if $DEBUG
1014       return false 
1015     end
1016       
1017     number = 0
1018     point = 0
1019
1020     if (sente)
1021       hands = @sente_hands
1022       r = [1, 2, 3]
1023     else
1024       hands = @gote_hands
1025       r = [7, 8, 9]
1026     end
1027     r.each do |y|
1028       x = 1
1029       while (x <= 9)
1030         if (@array[x][y] &&
1031             (@array[x][y].sente == sente) &&
1032             (@array[x][y].point > 0))
1033           point = point + @array[x][y].point
1034           number = number + 1
1035         end
1036         x = x + 1
1037       end
1038     end
1039     hands.each do |piece|
1040       point = point + piece.point
1041     end
1042
1043     if (number < 10)
1044       puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
1045       return false     
1046     end  
1047     if (sente)
1048       if (point < 28)
1049         puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
1050         return false 
1051       end  
1052     else
1053       if (point < 27)
1054         puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
1055         return false 
1056       end
1057     end
1058
1059     puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
1060     return true
1061   end
1062
1063   def handle_one_move(str, sente)
1064     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1065       sg = $1
1066       x0 = $2.to_i
1067       y0 = $3.to_i
1068       x1 = $4.to_i
1069       y1 = $5.to_i
1070       name = $6
1071     elsif (str =~ /^%KACHI/)
1072       if (good_kachi?(sente))
1073         return :kachi_win
1074       else
1075         return :kachi_lose
1076       end
1077     elsif (str =~ /^%TORYO/)
1078       return :toryo
1079     else
1080       return :illegal
1081     end
1082     
1083     if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1084         ((x0 != 0) || (y0 != 0)))
1085       return :illegal
1086     elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1087       return :illegal
1088     end
1089     
1090     if (sg == "+")
1091       sente = true
1092       hands = @sente_hands
1093     else
1094       sente = false
1095       hands = @gote_hands
1096     end
1097     
1098     ## source check
1099     if ((x0 == 0) && (y0 == 0))
1100       return :illegal if (! have_piece?(hands, name))
1101     elsif (! @array[x0][y0])
1102       return :illegal           # no piece
1103     elsif (@array[x0][y0].sente != sente)
1104       return :illegal           # this is not mine
1105     elsif (@array[x0][y0].name != name)
1106       return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1107     end
1108
1109     ## destination check
1110     if (@array[x1][y1] &&
1111         (@array[x1][y1].sente == sente)) # can't capture mine
1112       return :illegal
1113     elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1114       return :illegal           # can't put on existing piece
1115     end
1116
1117     tmp_board = Marshal.load(Marshal.dump(self))
1118     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1119     return :oute_kaihimore if (tmp_board.checkmated?(sente))
1120     return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1121     return :sennichite if tmp_board.sennichite?(sente)
1122
1123     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1124       return :uchifuzume
1125     end
1126
1127     move_to(x0, y0, x1, y1, name, sente)
1128     str = to_s
1129
1130     if (checkmated?(! sente))
1131       if (sente)
1132         @sente_history[str] = (@sente_history[str] || 0) + 1
1133       else
1134         @gote_history[str] = (@gote_history[str] || 0) + 1
1135       end
1136     else
1137       if (sente)
1138         @sente_history.clear
1139       else
1140         @gote_history.clear
1141       end
1142     end
1143     @history[str] = (@history[str] || 0) + 1
1144     return :normal
1145   end
1146
1147   def to_s
1148     a = Array::new
1149     y = 1
1150     while (y <= 9)
1151       a.push(sprintf("P%d", y))
1152       x = 9
1153       while (x >= 1)
1154         piece = @array[x][y]
1155         if (piece)
1156           s = piece.to_s
1157         else
1158           s = " * "
1159         end
1160         a.push(s)
1161         x = x - 1
1162       end
1163       a.push(sprintf("\n"))
1164       y = y + 1
1165     end
1166     if (! sente_hands.empty?)
1167       a.push("P+")
1168       sente_hands.each do |p|
1169         a.push("00" + p.name)
1170       end
1171       a.push("\n")
1172     end
1173     if (! gote_hands.empty?)
1174       a.push("P-")
1175       gote_hands.each do |p|
1176         a.push("00" + p.name)
1177       end
1178       a.push("\n")
1179     end
1180     a.push("+\n")
1181     return a.join
1182   end
1183 end
1184
1185 class Game
1186   @@mutex = Mutex.new
1187   @@time  = 0
1188
1189   def initialize(game_name, player0, player1)
1190     @monitors = Array::new
1191     @game_name = game_name
1192     if (@game_name =~ /-(\d+)-(\d+)$/)
1193       @total_time = $1.to_i
1194       @byoyomi = $2.to_i
1195     end
1196
1197     if (player0.sente)
1198       @sente = player0
1199       @gote = player1
1200     else
1201       @sente = player1
1202       @gote = player0
1203     end
1204     @current_player = @sente
1205     @next_player = @gote
1206
1207     @sente.game = self
1208     @gote.game = self
1209
1210     @last_move = ""
1211     @current_turn = 0
1212
1213     @sente.status = "agree_waiting"
1214     @gote.status = "agree_waiting"
1215     
1216     @id = sprintf("%s+%s+%s+%s+%s", 
1217                   LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1218     @logfile = @id + ".csa"
1219
1220     LEAGUE.games[@id] = self
1221
1222     log_message(sprintf("game created %s", @id))
1223
1224     @board = Board::new
1225     @board.initial
1226     @start_time = nil
1227     @fh = nil
1228
1229     propose
1230   end
1231   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1232   attr_accessor :last_move, :current_turn
1233
1234   def monitoron(monitor)
1235     @monitors.delete(monitor)
1236     @monitors.push(monitor)
1237   end
1238
1239   def monitoroff(monitor)
1240     @monitors.delete(monitor)
1241   end
1242
1243   def reject(rejector)
1244     @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1245     @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1246     finish
1247   end
1248
1249   def kill(killer)
1250     if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1251       reject(killer.name)
1252     elsif (@current_player == killer)
1253       abnormal_lose()
1254       finish
1255     end
1256   end
1257
1258   def finish
1259     log_message(sprintf("game finished %s", @id))
1260     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
1261     @fh.close
1262
1263     @sente.game = nil
1264     @gote.game = nil
1265     @sente.status = "connected"
1266     @gote.status = "connected"
1267
1268     if (@current_player.protocol == "CSA")
1269       @current_player.finish
1270     end
1271     if (@next_player.protocol == "CSA")
1272       @next_player.finish
1273     end
1274     @monitors = Array::new
1275     @sente = nil
1276     @gote = nil
1277     @current_player = nil
1278     @next_player = nil
1279     LEAGUE.games.delete(@id)
1280   end
1281
1282   def handle_one_move(str, player)
1283     finish_flag = true
1284     if (@current_player == player)
1285       @end_time = Time::new
1286       t = (@end_time - @start_time).floor
1287       t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1288       
1289       move_status = nil
1290       if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1291         status = :timeout
1292       elsif (str == :timeout)
1293         return false            # time isn't expired. players aren't swapped. continue game
1294       else
1295         @current_player.mytime = @current_player.mytime - t
1296         if (@current_player.mytime < 0)
1297           @current_player.mytime = 0
1298         end
1299
1300 #        begin
1301           move_status = @board.handle_one_move(str, @sente == @current_player)
1302 #        rescue
1303 #          log_error("handle_one_move raise exception for #{str}")
1304 #          move_status = :illegal
1305 #        end
1306
1307         if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1308           @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1309         else
1310           if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1311             @sente.write_safe(sprintf("%s,T%d\n", str, t))
1312             @gote.write_safe(sprintf("%s,T%d\n", str, t))
1313             @fh.printf("%s\nT%d\n", str, t)
1314             @last_move = sprintf("%s,T%d", str, t)
1315             @current_turn = @current_turn + 1
1316           end
1317
1318           @monitors.each do |monitor|
1319             monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1320             monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1321           end
1322         end
1323       end
1324
1325       if (@next_player.status != "game") # rival is logout or disconnected
1326         abnormal_win()
1327       elsif (status == :timeout)
1328         timeout_lose()
1329       elsif (move_status == :illegal)
1330         illegal_lose()
1331       elsif (move_status == :kachi_win)
1332         kachi_win()
1333       elsif (move_status == :kachi_lose)
1334         kachi_lose()
1335       elsif (move_status == :toryo)
1336         toryo_lose()
1337       elsif (move_status == :outori)
1338         outori_win()
1339       elsif (move_status == :sennichite)
1340         sennichite_draw()
1341       elsif (move_status == :oute_sennichite)
1342         oute_sennichite_lose()
1343       elsif (move_status == :uchifuzume)
1344         uchifuzume_lose()
1345       elsif (move_status == :oute_kaihimore)
1346         oute_kaihimore_lose()
1347       else
1348         finish_flag = false
1349       end
1350       finish() if finish_flag
1351       (@current_player, @next_player) = [@next_player, @current_player]
1352       @start_time = Time::new
1353       return finish_flag
1354     end
1355   end
1356
1357   def abnormal_win
1358     @current_player.status = "connected"
1359     @next_player.status = "connected"
1360     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1361     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1362     @fh.printf("%%TORYO\n")
1363     @fh.print(@board.to_s.gsub(/^/, "\'"))
1364     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1365     @monitors.each do |monitor|
1366       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1367     end
1368   end
1369
1370   def abnormal_lose
1371     @current_player.status = "connected"
1372     @next_player.status = "connected"
1373     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1374     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1375     @fh.printf("%%TORYO\n")
1376     @fh.print(@board.to_s.gsub(/^/, "\'"))
1377     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1378     @monitors.each do |monitor|
1379       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1380     end
1381   end
1382
1383   def sennichite_draw
1384     @current_player.status = "connected"
1385     @next_player.status = "connected"
1386     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1387     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1388     @fh.print(@board.to_s.gsub(/^/, "\'"))
1389     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1390     @monitors.each do |monitor|
1391       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1392     end
1393   end
1394
1395   def oute_sennichite_lose
1396     @current_player.status = "connected"
1397     @next_player.status = "connected"
1398     @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1399     @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1400     @fh.print(@board.to_s.gsub(/^/, "\'"))
1401     @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1402     @monitors.each do |monitor|
1403       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1404     end
1405   end
1406
1407   def illegal_lose
1408     @current_player.status = "connected"
1409     @next_player.status = "connected"
1410     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1411     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1412     @fh.print(@board.to_s.gsub(/^/, "\'"))
1413     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1414     @monitors.each do |monitor|
1415       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1416     end
1417   end
1418
1419   def uchifuzume_lose
1420     @current_player.status = "connected"
1421     @next_player.status = "connected"
1422     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1423     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1424     @fh.print(@board.to_s.gsub(/^/, "\'"))
1425     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1426     @monitors.each do |monitor|
1427       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1428     end
1429   end
1430
1431   def oute_kaihimore_lose
1432     @current_player.status = "connected"
1433     @next_player.status = "connected"
1434     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1435     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1436     @fh.print(@board.to_s.gsub(/^/, "\'"))
1437     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1438     @monitors.each do |monitor|
1439       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1440     end
1441   end
1442
1443   def timeout_lose
1444     @current_player.status = "connected"
1445     @next_player.status = "connected"
1446     @current_player.write_safe("#TIME_UP\n#LOSE\n")
1447     @next_player.write_safe("#TIME_UP\n#WIN\n")
1448     @fh.print(@board.to_s.gsub(/^/, "\'"))
1449     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1450     @monitors.each do |monitor|
1451       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1452     end
1453   end
1454
1455   def kachi_win
1456     @current_player.status = "connected"
1457     @next_player.status = "connected"
1458     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1459     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1460     @fh.printf("%%KACHI\n")
1461     @fh.print(@board.to_s.gsub(/^/, "\'"))
1462     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1463     @monitors.each do |monitor|
1464       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1465     end
1466   end
1467
1468   def kachi_lose
1469     @current_player.status = "connected"
1470     @next_player.status = "connected"
1471     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1472     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1473     @fh.printf("%%KACHI\n")
1474     @fh.print(@board.to_s.gsub(/^/, "\'"))
1475     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1476     @monitors.each do |monitor|
1477       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1478     end
1479   end
1480
1481   def toryo_lose
1482     @current_player.status = "connected"
1483     @next_player.status = "connected"
1484     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1485     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1486     @fh.printf("%%TORYO\n")
1487     @fh.print(@board.to_s.gsub(/^/, "\'"))
1488     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1489     @monitors.each do |monitor|
1490       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1491     end
1492   end
1493
1494   def outori_win
1495     @current_player.status = "connected"
1496     @next_player.status = "connected"
1497     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1498     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1499     @fh.print(@board.to_s.gsub(/^/, "\'"))
1500     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1501     @monitors.each do |monitor|
1502       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1503     end
1504   end
1505
1506   def start
1507     log_message(sprintf("game started %s", @id))
1508     @sente.write_safe(sprintf("START:%s\n", @id))
1509     @gote.write_safe(sprintf("START:%s\n", @id))
1510     @sente.mytime = @total_time
1511     @gote.mytime = @total_time
1512     @start_time = Time::new
1513   end
1514
1515   def propose
1516     begin
1517       @fh = open(@logfile, "w")
1518       @fh.sync = true
1519
1520       @fh.printf("V2\n")
1521       @fh.printf("N+%s\n", @sente.name)
1522       @fh.printf("N-%s\n", @gote.name)
1523       @fh.printf("$EVENT:%s\n", @id)
1524
1525       @sente.write_safe(propose_message("+"))
1526       @gote.write_safe(propose_message("-"))
1527
1528       @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1529       @fh.print <<EOM
1530 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1531 P2 * -HI *  *  *  *  * -KA * 
1532 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1533 P4 *  *  *  *  *  *  *  *  * 
1534 P5 *  *  *  *  *  *  *  *  * 
1535 P6 *  *  *  *  *  *  *  *  * 
1536 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1537 P8 * +KA *  *  *  *  * +HI * 
1538 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1539 +
1540 EOM
1541     end
1542   end
1543
1544   def show()
1545     str0 = <<EOM
1546 BEGIN Game_Summary
1547 Protocol_Version:1.1
1548 Protocol_Mode:Server
1549 Format:Shogi 1.0
1550 Declaration:Jishogi 1.1
1551 Game_ID:#{@id}
1552 Name+:#{@sente.name}
1553 Name-:#{@gote.name}
1554 Rematch_On_Draw:NO
1555 To_Move:+
1556 BEGIN Time
1557 Time_Unit:1sec
1558 Total_Time:#{@total_time}
1559 Byoyomi:#{@byoyomi}
1560 Least_Time_Per_Move:#{Least_Time_Per_Move}
1561 Remaining_Time+:#{@sente.mytime}
1562 Remaining_Time-:#{@gote.mytime}
1563 Last_Move:#{@last_move}
1564 Current_Turn:#{@current_turn}
1565 END Time
1566 BEGIN Position
1567 EOM
1568
1569     str1 = <<EOM
1570 END Position
1571 END Game_Summary
1572 EOM
1573
1574     return str0 + @board.to_s + str1
1575   end
1576
1577   def propose_message(sg_flag)
1578     str = <<EOM
1579 BEGIN Game_Summary
1580 Protocol_Version:1.1
1581 Protocol_Mode:Server
1582 Format:Shogi 1.0
1583 Declaration:Jishogi 1.1
1584 Game_ID:#{@id}
1585 Name+:#{@sente.name}
1586 Name-:#{@gote.name}
1587 Your_Turn:#{sg_flag}
1588 Rematch_On_Draw:NO
1589 To_Move:+
1590 BEGIN Time
1591 Time_Unit:1sec
1592 Total_Time:#{@total_time}
1593 Byoyomi:#{@byoyomi}
1594 Least_Time_Per_Move:#{Least_Time_Per_Move}
1595 END Time
1596 BEGIN Position
1597 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1598 P2 * -HI *  *  *  *  * -KA * 
1599 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1600 P4 *  *  *  *  *  *  *  *  * 
1601 P5 *  *  *  *  *  *  *  *  * 
1602 P6 *  *  *  *  *  *  *  *  * 
1603 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1604 P8 * +KA *  *  *  *  * +HI * 
1605 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1606 P+
1607 P-
1608 +
1609 END Position
1610 END Game_Summary
1611 EOM
1612     return str
1613   end
1614   
1615   private
1616   
1617   def issue_current_time
1618     time = Time::new.strftime("%Y%m%d%H%M%S").to_i
1619     @@mutex.synchronize do
1620       while time <= @@time do
1621         time += 1
1622       end
1623       @@time = time
1624     end
1625   end
1626 end
1627
1628 def usage
1629     print <<EOM
1630 NAME
1631         shogi-server - server for CSA server protocol
1632
1633 SYNOPSIS
1634         shogi-server event_name port_number
1635
1636 DESCRIPTION
1637         server for CSA server protocol
1638
1639 OPTIONS
1640         --pid-file file
1641                 specify filename for logging process ID
1642
1643 LICENSE
1644         this file is distributed under GPL version2 and might be compiled by Exerb
1645
1646 SEE ALSO
1647
1648 RELEASE
1649         #{Release}
1650
1651 REVISION
1652         #{Revision}
1653 EOM
1654 end
1655
1656 def log_message(str)
1657   printf("%s message: %s\n", Time::new.to_s, str)
1658 end
1659
1660 def log_warning(str)
1661   printf("%s warning: %s\n", Time::new.to_s, str)
1662 end
1663
1664 def log_error(str)
1665   printf("%s error: %s\n", Time::new.to_s, str)
1666 end
1667
1668
1669 def parse_command_line
1670   options = Hash::new
1671   parser = GetoptLong.new
1672   parser.ordering = GetoptLong::REQUIRE_ORDER
1673   parser.set_options(
1674                      ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1675
1676   parser.quiet = true
1677   begin
1678     parser.each_option do |name, arg|
1679       name.sub!(/^--/, '')
1680       options[name] = arg.dup
1681     end
1682   rescue
1683     usage
1684     raise parser.error_message
1685   end
1686   return options
1687 end
1688
1689 def good_game_name?(str)
1690   if ((str =~ /^(.+)-\d+-\d+$/) &&
1691       (good_identifier?($1)))
1692     return true
1693   else
1694     return false
1695   end
1696 end
1697
1698 def good_identifier?(str)
1699   if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1700       (str.length < Max_Identifier_Length))
1701     return true
1702   else
1703     return false
1704   end
1705 end
1706
1707 def good_login?(str)
1708   tokens = str.split
1709   if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1710       (tokens[0] == "LOGIN") &&
1711       (good_identifier?(tokens[1])))
1712     return true
1713   else
1714     return false
1715   end
1716 end
1717
1718 def  write_pid_file(file)
1719   open(file, "w") do |fh|
1720     fh.print Process::pid, "\n"
1721   end
1722 end
1723
1724 def mutex_watchdog(mutex, sec)
1725   while true
1726     begin
1727       timeout(sec) do
1728         begin
1729           mutex.lock
1730         ensure
1731           mutex.unlock
1732         end
1733       end
1734       sleep(sec)
1735     rescue TimeoutError
1736       log_error("mutex watchdog timeout")
1737       exit(1)
1738     end
1739   end
1740 end
1741
1742 def main
1743   $mutex = Mutex::new
1744   Thread::start do
1745     Thread.pass
1746     mutex_watchdog($mutex, 10)
1747   end
1748
1749   $options = parse_command_line
1750   if (ARGV.length != 2)
1751     usage
1752     exit 2
1753   end
1754
1755   LEAGUE.event = ARGV.shift
1756   port = ARGV.shift
1757
1758   write_pid_file($options["pid-file"]) if ($options["pid-file"])
1759
1760
1761
1762   server = TCPserver.open(port)
1763   log_message("server started")
1764
1765   while true
1766     Thread::start(server.accept) do |client|
1767       Thread.pass
1768       client.sync = true
1769       player = nil
1770       while (str = client.gets_timeout(Login_Time))
1771         begin
1772           $mutex.lock
1773           str =~ /([\r\n]*)$/
1774           eol = $1
1775           if (good_login?(str))
1776             player = Player::new(str, client)
1777             if (LEAGUE.players[player.name])
1778               if ((LEAGUE.players[player.name].password == player.password) &&
1779                   (LEAGUE.players[player.name].status != "game"))
1780                 log_message(sprintf("user %s login forcely", player.name))
1781                 LEAGUE.players[player.name].kill
1782               else
1783                 client.write_safe("LOGIN:incorrect" + eol)
1784                 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1785                 client.close
1786                 Thread::exit
1787                 return
1788               end
1789             end
1790             LEAGUE.add(player)
1791             break
1792           else
1793             client.write_safe("LOGIN:incorrect" + eol)
1794             client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1795           end
1796         ensure
1797           $mutex.unlock
1798         end
1799       end                       # login loop
1800       if (! player)
1801         client.close
1802         Thread::exit
1803         return
1804       end
1805       log_message(sprintf("user %s login", player.name))
1806       player.run
1807       begin
1808         $mutex.lock
1809         if (player.game)
1810           player.game.kill(player)
1811         end
1812         player.finish # socket has been closed
1813         LEAGUE.delete(player)
1814         log_message(sprintf("user %s logout", player.name))
1815       ensure
1816         $mutex.unlock
1817       end
1818     end
1819   end
1820 end
1821
1822 if ($0 == __FILE__)
1823   LEAGUE = League::new
1824   main
1825 end