OSDN Git Service

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