OSDN Git Service

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