OSDN Git Service

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