OSDN Git Service

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