OSDN Git Service

65f991f97cd050aed726b0627d10d687113ef25f
[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 <= 0) && (@total_time > 0))
1260         status = :timeout
1261       elsif (str == :timeout)
1262         return false            # time isn't expired. players aren't swapped. continue game
1263       else
1264         if (@current_player.mytime - t < @byoyomi)
1265           @current_player.mytime = @byoyomi
1266         else
1267           @current_player.mytime = @current_player.mytime - t
1268         end
1269
1270 #        begin
1271           move_status = @board.handle_one_move(str)
1272 #        rescue
1273 #          log_error("handle_one_move raise exception for #{str}")
1274 #          move_status = :illegal
1275 #        end
1276
1277         if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1278           @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1279         else
1280           if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1281             @sente.write_safe(sprintf("%s,T%d\n", str, t))
1282             @gote.write_safe(sprintf("%s,T%d\n", str, t))
1283             @fh.printf("%s\nT%d\n", str, t)
1284             @last_move = sprintf("%s,T%d", str, t)
1285             @current_turn = @current_turn + 1
1286           end
1287
1288           @monitors.each do |monitor|
1289             monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1290             monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1291           end
1292         end
1293       end
1294
1295       if (@next_player.status != "game") # rival is logout or disconnected
1296         abnormal_win()
1297       elsif (status == :timeout)
1298         timeout_lose()
1299       elsif (move_status == :illegal)
1300         illegal_lose()
1301       elsif (move_status == :kachi_win)
1302         kachi_win()
1303       elsif (move_status == :kachi_lose)
1304         kachi_lose()
1305       elsif (move_status == :toryo)
1306         toryo_lose()
1307       elsif (move_status == :outori)
1308         outori_win()
1309       elsif (move_status == :sennichite)
1310         sennichite_draw()
1311       elsif (move_status == :oute_sennichite)
1312         oute_sennichite_lose()
1313       elsif (move_status == :uchifuzume)
1314         uchifuzume_lose()
1315       elsif (move_status == :oute_kaihimore)
1316         oute_kaihimore_lose()
1317       else
1318         finish_flag = false
1319       end
1320       finish() if finish_flag
1321       (@current_player, @next_player) = [@next_player, @current_player]
1322       @start_time = Time::new
1323       return finish_flag
1324     end
1325   end
1326
1327   def abnormal_win
1328     @current_player.status = "connected"
1329     @next_player.status = "connected"
1330     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1331     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1332     @fh.printf("%%TORYO\n")
1333     @fh.print(@board.to_s.gsub(/^/, "\'"))
1334     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1335     @monitors.each do |monitor|
1336       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1337     end
1338   end
1339
1340   def abnormal_lose
1341     @current_player.status = "connected"
1342     @next_player.status = "connected"
1343     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1344     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1345     @fh.printf("%%TORYO\n")
1346     @fh.print(@board.to_s.gsub(/^/, "\'"))
1347     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1348     @monitors.each do |monitor|
1349       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1350     end
1351   end
1352
1353   def sennichite_draw
1354     @current_player.status = "connected"
1355     @next_player.status = "connected"
1356     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1357     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1358     @fh.print(@board.to_s.gsub(/^/, "\'"))
1359     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1360     @monitors.each do |monitor|
1361       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1362     end
1363   end
1364
1365   def oute_sennichite_lose
1366     @current_player.status = "connected"
1367     @next_player.status = "connected"
1368     @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1369     @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1370     @fh.print(@board.to_s.gsub(/^/, "\'"))
1371     @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1372     @monitors.each do |monitor|
1373       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1374     end
1375   end
1376
1377   def illegal_lose
1378     @current_player.status = "connected"
1379     @next_player.status = "connected"
1380     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1381     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1382     @fh.print(@board.to_s.gsub(/^/, "\'"))
1383     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1384     @monitors.each do |monitor|
1385       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1386     end
1387   end
1388
1389   def uchifuzume_lose
1390     @current_player.status = "connected"
1391     @next_player.status = "connected"
1392     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1393     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1394     @fh.print(@board.to_s.gsub(/^/, "\'"))
1395     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1396     @monitors.each do |monitor|
1397       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1398     end
1399   end
1400
1401   def oute_kaihimore_lose
1402     @current_player.status = "connected"
1403     @next_player.status = "connected"
1404     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1405     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1406     @fh.print(@board.to_s.gsub(/^/, "\'"))
1407     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1408     @monitors.each do |monitor|
1409       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1410     end
1411   end
1412
1413   def timeout_lose
1414     @current_player.status = "connected"
1415     @next_player.status = "connected"
1416     @current_player.write_safe("#TIME_UP\n#LOSE\n")
1417     @next_player.write_safe("#TIME_UP\n#WIN\n")
1418     @fh.print(@board.to_s.gsub(/^/, "\'"))
1419     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1420     @monitors.each do |monitor|
1421       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1422     end
1423   end
1424
1425   def kachi_win
1426     @current_player.status = "connected"
1427     @next_player.status = "connected"
1428     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1429     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1430     @fh.printf("%%KACHI\n")
1431     @fh.print(@board.to_s.gsub(/^/, "\'"))
1432     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1433     @monitors.each do |monitor|
1434       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1435     end
1436   end
1437
1438   def kachi_lose
1439     @current_player.status = "connected"
1440     @next_player.status = "connected"
1441     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1442     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1443     @fh.printf("%%KACHI\n")
1444     @fh.print(@board.to_s.gsub(/^/, "\'"))
1445     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1446     @monitors.each do |monitor|
1447       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1448     end
1449   end
1450
1451   def toryo_lose
1452     @current_player.status = "connected"
1453     @next_player.status = "connected"
1454     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1455     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1456     @fh.printf("%%TORYO\n")
1457     @fh.print(@board.to_s.gsub(/^/, "\'"))
1458     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1459     @monitors.each do |monitor|
1460       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1461     end
1462   end
1463
1464   def outori_win
1465     @current_player.status = "connected"
1466     @next_player.status = "connected"
1467     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1468     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1469     @fh.print(@board.to_s.gsub(/^/, "\'"))
1470     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1471     @monitors.each do |monitor|
1472       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1473     end
1474   end
1475
1476   def start
1477     log_message(sprintf("game started %s", @id))
1478     @sente.write_safe(sprintf("START:%s\n", @id))
1479     @gote.write_safe(sprintf("START:%s\n", @id))
1480     @sente.mytime = @total_time
1481     @gote.mytime = @total_time
1482     @start_time = Time::new
1483   end
1484
1485   def propose
1486     begin
1487       @fh = open(@logfile, "w")
1488       @fh.sync = true
1489
1490       @fh.printf("V2\n")
1491       @fh.printf("N+%s\n", @sente.name)
1492       @fh.printf("N-%s\n", @gote.name)
1493       @fh.printf("$EVENT:%s\n", @id)
1494
1495       @sente.write_safe(propose_message("+"))
1496       @gote.write_safe(propose_message("-"))
1497
1498       @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1499       @fh.print <<EOM
1500 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1501 P2 * -HI *  *  *  *  * -KA * 
1502 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1503 P4 *  *  *  *  *  *  *  *  * 
1504 P5 *  *  *  *  *  *  *  *  * 
1505 P6 *  *  *  *  *  *  *  *  * 
1506 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1507 P8 * +KA *  *  *  *  * +HI * 
1508 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1509 +
1510 EOM
1511     end
1512   end
1513
1514   def show()
1515     str0 = <<EOM
1516 BEGIN Game_Summary
1517 Protocol_Version:1.0
1518 Protocol_Mode:Server
1519 Format:Shogi 1.0
1520 Game_ID:#{@id}
1521 Name+:#{@sente.name}
1522 Name-:#{@gote.name}
1523 Rematch_On_Draw:NO
1524 To_Move:+
1525 BEGIN Time
1526 Time_Unit:1sec
1527 Total_Time:#{@total_time}
1528 Byoyomi:#{@byoyomi}
1529 Least_Time_Per_Move:#{Least_Time_Per_Move}
1530 Remaining_Time+:#{@sente.mytime}
1531 Remaining_Time-:#{@gote.mytime}
1532 Last_Move:#{@last_move}
1533 Current_Turn:#{@current_turn}
1534 END Time
1535 BEGIN Position
1536 Jishogi_Declaration:1.1
1537 EOM
1538
1539     str1 = <<EOM
1540 END Position
1541 END Game_Summary
1542 EOM
1543
1544     return str0 + @board.to_s + str1
1545   end
1546
1547   def propose_message(sg_flag)
1548     str = <<EOM
1549 BEGIN Game_Summary
1550 Protocol_Version:1.0
1551 Protocol_Mode:Server
1552 Format:Shogi 1.0
1553 Game_ID:#{@id}
1554 Name+:#{@sente.name}
1555 Name-:#{@gote.name}
1556 Your_Turn:#{sg_flag}
1557 Rematch_On_Draw:NO
1558 To_Move:+
1559 BEGIN Time
1560 Time_Unit:1sec
1561 Total_Time:#{@total_time}
1562 Byoyomi:#{@byoyomi}
1563 Least_Time_Per_Move:#{Least_Time_Per_Move}
1564 END Time
1565 BEGIN Position
1566 Jishogi_Declaration:1.1
1567 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1568 P2 * -HI *  *  *  *  * -KA * 
1569 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1570 P4 *  *  *  *  *  *  *  *  * 
1571 P5 *  *  *  *  *  *  *  *  * 
1572 P6 *  *  *  *  *  *  *  *  * 
1573 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1574 P8 * +KA *  *  *  *  * +HI * 
1575 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1576 P+
1577 P-
1578 +
1579 END Position
1580 END Game_Summary
1581 EOM
1582     return str
1583   end
1584 end
1585
1586 def usage
1587     print <<EOM
1588 NAME
1589         shogi-server - server for CSA server protocol
1590
1591 SYNOPSIS
1592         shogi-server event_name port_number
1593
1594 DESCRIPTION
1595         server for CSA server protocol
1596
1597 OPTIONS
1598         --pid-file file
1599                 specify filename for logging process ID
1600
1601 LICENSE
1602         this file is distributed under GPL version2 and might be compiled by Exerb
1603
1604 SEE ALSO
1605
1606 RELEASE
1607         #{Release}
1608
1609 REVISION
1610         #{Revision}
1611 EOM
1612 end
1613
1614 def log_message(str)
1615   printf("%s message: %s\n", Time::new.to_s, str)
1616 end
1617
1618 def log_warning(str)
1619   printf("%s warning: %s\n", Time::new.to_s, str)
1620 end
1621
1622 def log_error(str)
1623   printf("%s error: %s\n", Time::new.to_s, str)
1624 end
1625
1626
1627 def parse_command_line
1628   options = Hash::new
1629   parser = GetoptLong.new
1630   parser.ordering = GetoptLong::REQUIRE_ORDER
1631   parser.set_options(
1632                      ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1633
1634   parser.quiet = true
1635   begin
1636     parser.each_option do |name, arg|
1637       name.sub!(/^--/, '')
1638       options[name] = arg.dup
1639     end
1640   rescue
1641     usage
1642     raise parser.error_message
1643   end
1644   return options
1645 end
1646
1647 def good_game_name?(str)
1648   if ((str =~ /^(.+)-\d+-\d+$/) &&
1649       (good_identifier?($1)))
1650     return true
1651   else
1652     return false
1653   end
1654 end
1655
1656 def good_identifier?(str)
1657   if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1658       (str.length < Max_Identifier_Length))
1659     return true
1660   else
1661     return false
1662   end
1663 end
1664
1665 def good_login?(str)
1666   tokens = str.split
1667   if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1668       (tokens[0] == "LOGIN") &&
1669       (good_identifier?(tokens[1])))
1670     return true
1671   else
1672     return false
1673   end
1674 end
1675
1676 def  write_pid_file(file)
1677   open(file, "w") do |fh|
1678     fh.print Process::pid, "\n"
1679   end
1680 end
1681
1682 def mutex_watchdog(mutex, sec)
1683   while true
1684     begin
1685       timeout(sec) do
1686         mutex.lock
1687         mutex.unlock
1688       end
1689       sleep(sec)
1690     rescue TimeoutError
1691       log_error("mutex watchdog timeout")
1692       exit(1)
1693     end
1694   end
1695 end
1696
1697 def main
1698   $mutex = Mutex::new
1699   Thread::start do
1700     mutex_watchdog($mutex, 10)
1701   end
1702
1703   $options = parse_command_line
1704   if (ARGV.length != 2)
1705     usage
1706     exit 2
1707   end
1708
1709   LEAGUE.event = ARGV.shift
1710   port = ARGV.shift
1711
1712   write_pid_file($options["pid-file"]) if ($options["pid-file"])
1713
1714
1715   Thread.abort_on_exception = true
1716
1717   server = TCPserver.open(port)
1718   log_message("server started")
1719
1720   while true
1721     Thread::start(server.accept) do |client|
1722       client.sync = true
1723       player = nil
1724       while (str = client.gets_timeout(Login_Time))
1725         begin
1726           $mutex.lock
1727           str =~ /([\r\n]*)$/
1728           eol = $1
1729           if (good_login?(str))
1730             player = Player::new(str, client)
1731             if (LEAGUE.players[player.name])
1732               if ((LEAGUE.players[player.name].password == player.password) &&
1733                   (LEAGUE.players[player.name].status != "game"))
1734                 log_message(sprintf("user %s login forcely", player.name))
1735                 LEAGUE.players[player.name].kill
1736               else
1737                 client.write_safe("LOGIN:incorrect" + eol)
1738                 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1739                 client.close
1740                 Thread::exit
1741               end
1742             end
1743             LEAGUE.add(player)
1744             break
1745           else
1746             client.write_safe("LOGIN:incorrect" + eol)
1747             client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1748           end
1749         ensure
1750           $mutex.unlock
1751         end
1752       end                       # login loop
1753       if (! player)
1754         client.close
1755         Thread::exit
1756       end
1757       log_message(sprintf("user %s login", player.name))
1758       player.run
1759       begin
1760         $mutex.lock
1761         if (player.game)
1762           player.game.kill(player)
1763         end
1764         player.finish
1765         LEAGUE.delete(player)
1766         log_message(sprintf("user %s logout", player.name))
1767       ensure
1768         $mutex.unlock
1769       end
1770     end
1771   end
1772 end
1773
1774 if ($0 == __FILE__)
1775   LEAGUE = League::new
1776   main
1777 end