OSDN Git Service

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