4 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
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.
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.
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
20 Max_Write_Queue_Size = 1000
21 Max_Identifier_Length = 32
22 Default_Timeout = 60 # for single socket operation
24 Default_Game_Name = "default-1500-0"
27 Least_Time_Per_Move = 1
28 Login_Time = 300 # time for LOGIN
30 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
31 Release.concat("-") if (Release == "")
32 Revision = "$Revision$".gsub(/[^\.\d]/, '')
45 TCPSocket.do_not_reverse_lookup = true
46 Thread.abort_on_exception = true
50 def gets_timeout(t = Default_Timeout)
61 def gets_safe(t = nil)
82 return self.write(str)
95 @db = YAML::Store.new( File.join(File.dirname(__FILE__), "players.yaml") )
97 attr_accessor :players, :games, :event
100 self.load(player) if player.id
101 @players[player.name] = player
105 @players.delete(player.name)
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)))
122 @players.each_value do |p|
124 @db[p.id] = {'name' => p.name, 'rate' => p.rate}
130 hash = search(player.id)
133 player.rate = hash['rate']
147 @db.transaction(true) do
148 @db.roots.each do |id|
152 return players.collect do |id|
153 p = RatedPlayer.new(id, nil, nil)
160 class RatedPlayer < Struct.new(:id, :name, :rate); end
163 def initialize(str, socket)
166 @id = nil, @rate = nil # used by rating
168 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
170 @protocol = nil # CSA or x1
171 @eol = "\m" # favorite eol code
174 @mytime = 0 # set in start method also
178 @write_queue = Queue::new
182 attr_accessor :name, :password, :socket, :status, :rate
183 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
184 attr_accessor :main_thread, :writer_thread, :write_queue
188 log_message(sprintf("user %s killed", @name))
193 Thread::kill(@main_thread) if @main_thread
197 if (@status != "finished")
199 log_message(sprintf("user %s finish", @name))
200 Thread::kill(@writer_thread) if @writer_thread
202 @socket.close if (! @socket.closed?)
204 log_message(sprintf("user %s finish failed", @name))
210 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
214 while (str = @write_queue.pop)
215 @socket.write_safe(str)
220 if ((status == "game_waiting") ||
221 (status == "start_waiting") ||
222 (status == "agree_waiting") ||
225 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
226 elsif (@sente == false)
227 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
228 elsif (@sente == nil)
229 return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
232 return sprintf("%s %s %s", @name, @protocol, @status)
237 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
244 (login, @name, @password, ext) = str.split
245 @name, trip = @name.split(",") # used by rating
246 @id = trip ? Digest::MD5.hexdigest("#{@name},#{@trip}") : nil
252 @main_thread = Thread::current
253 @writer_thread = Thread::start do
259 write_safe(sprintf("LOGIN:%s OK\n", @name))
260 if (@protocol != "CSA")
261 log_message(sprintf("user %s run in %s mode", @name, @protocol))
262 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
264 log_message(sprintf("user %s run in CSA mode", @name))
265 if (good_game_name?(@password))
266 csa_1st_str = "%%GAME #{@password} *"
268 csa_1st_str = "%%GAME #{Default_Game_Name} *"
272 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
279 if (@write_queue.size > Max_Write_Queue_Size)
280 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
284 if (@status == "finished")
287 str.chomp! if (str.class == String)
290 if (@status == "game")
291 array_str = str.split(",")
292 move = array_str.shift
293 additional = array_str.shift
294 if /^'(.*)/ =~ additional
295 comment = array_str.unshift("'*#{$1}")
297 s = @game.handle_one_move(move, self)
298 @game.fh.print("#{comment}\n") if (comment && !s)
299 return if (s && @protocol == "CSA")
301 when /^%[^%]/, :timeout
302 if (@status == "game")
303 s = @game.handle_one_move(str, self)
304 return if (s && @protocol == "CSA")
307 if (@status == "agree_waiting")
309 return if (@protocol == "CSA")
311 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
314 if (@status == "agree_waiting")
315 @status = "start_waiting"
316 if ((@game.sente.status == "start_waiting") &&
317 (@game.gote.status == "start_waiting"))
319 @game.sente.status = "game"
320 @game.gote.status = "game"
323 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
325 when /^%%SHOW\s+(\S+)/
327 if (LEAGUE.games[game_id])
328 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
330 write_safe("##[SHOW] +OK\n")
331 when /^%%MONITORON\s+(\S+)/
333 if (LEAGUE.games[game_id])
334 LEAGUE.games[game_id].monitoron(self)
335 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
336 write_safe("##[MONITOR][#{game_id}] +OK\n")
338 when /^%%MONITOROFF\s+(\S+)/
340 if (LEAGUE.games[game_id])
341 LEAGUE.games[game_id].monitoroff(self)
346 players = LEAGUE.rated_players
347 players.sort {|a,b| b.rate <=> a.rate}.each do |p|
348 write_safe("%s (%s) \t %4d\n" % [p.name, p.id, p.rate])
351 if ((@status == "connected") || (@status == "game_waiting"))
352 @status = "connected"
355 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
357 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
361 if (! good_game_name?(game_name))
362 write_safe(sprintf("##[ERROR] bad game name\n"))
364 elsif ((@status == "connected") || (@status == "game_waiting"))
367 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
370 if ((my_sente_str == "*") ||
371 (my_sente_str == "+") ||
372 (my_sente_str == "-"))
375 write_safe(sprintf("##[ERROR] bad game option\n"))
379 if (my_sente_str == "*")
380 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
381 elsif (my_sente_str == "+")
382 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
383 elsif (my_sente_str == "-")
384 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
389 @game_name = game_name
390 if ((my_sente_str == "*") && (rival.sente == nil))
398 elsif (rival.sente == true) # rival has higher priority
400 elsif (rival.sente == false)
402 elsif (my_sente_str == "+")
405 elsif (my_sente_str == "-")
411 Game::new(@game_name, self, rival)
412 self.status = "agree_waiting"
413 rival.status = "agree_waiting"
414 else # rival not found
415 if (command_name == "GAME")
416 @status = "game_waiting"
417 @game_name = game_name
418 if (my_sente_str == "+")
420 elsif (my_sente_str == "-")
426 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
427 @status = "connected"
432 when /^%%CHAT\s+(.+)/
434 LEAGUE.players.each do |name, player|
435 if (player.protocol != "CSA")
436 player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
441 LEAGUE.games.each do |id, game|
442 buf.push(sprintf("##[LIST] %s\n", id))
444 buf.push("##[LIST] +OK\n")
448 LEAGUE.players.each do |name, player|
449 buf.push(sprintf("##[WHO] %s\n", player.to_s))
451 buf.push("##[WHO] +OK\n")
454 @status = "connected"
455 write_safe("LOGOUT:completed\n")
458 ## ignore null string
460 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
470 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
471 def initialize(board, x, y, sente, promoted=false)
478 if ((x == 0) || (y == 0))
480 hands = board.sente_hands
482 hands = board.gote_hands
489 @board.array[x][y] = self
492 attr_accessor :promoted, :sente, :x, :y, :board
494 def room_of_head?(x, y, name)
499 return adjacent_movable_grids + far_movable_grids
502 def far_movable_grids
507 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
508 if ((@board.array[x][y] == nil) || # dst is empty
509 (@board.array[x][y].sente != @sente)) # dst is enemy
517 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
518 if (@board.array[x][y] == nil) # dst is empty?
525 def adjacent_movable_grids
528 moves = @promoted_moves
530 moves = @normal_moves
532 moves.each do |(dx, dy)|
539 if (jump_to?(cand_x, cand_y))
540 grids.push([cand_x, cand_y])
546 def move_to?(x, y, name)
547 return false if (! room_of_head?(x, y, name))
548 return false if ((name != @name) && (name != @promoted_name))
549 return false if (@promoted && (name != @promoted_name)) # can't un-promote
552 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
554 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
556 return false if ((6 >= @y) && (6 >= y) && (name != @name))
560 if ((@x == 0) || (@y == 0))
561 return jump_to?(x, y)
563 return movable_grids.include?([x, y])
568 if ((@x == 0) || (@y == 0))
570 @board.sente_hands.delete(self)
572 @board.gote_hands.delete(self)
574 @board.array[x][y] = self
575 elsif ((x == 0) || (y == 0))
576 @promoted = false # clear promoted flag before moving to hands
578 @board.sente_hands.push(self)
580 @board.gote_hands.push(self)
582 @board.array[@x][@y] = nil
584 @board.array[@x][@y] = nil
585 @board.array[x][y] = self
618 class PieceFU < Piece
621 @normal_moves = [[0, +1]]
622 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
624 @promoted_name = "TO"
627 def room_of_head?(x, y, name)
630 return false if (y == 1)
632 return false if (y == 9)
638 if ((iy != @y) && # not source position
639 @board.array[x][iy] &&
640 (@board.array[x][iy].sente == @sente) && # mine
641 (@board.array[x][iy].name == "FU") &&
642 (@board.array[x][iy].promoted == false))
652 class PieceKY < Piece
656 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
658 @promoted_name = "NY"
661 def room_of_head?(x, y, name)
664 return false if (y == 1)
666 return false if (y == 9)
671 def far_movable_grids
679 while (jump_to?(cand_x, cand_y))
680 grids.push([cand_x, cand_y])
681 break if (! put_to?(cand_x, cand_y))
687 while (jump_to?(cand_x, cand_y))
688 grids.push([cand_x, cand_y])
689 break if (! put_to?(cand_x, cand_y))
697 class PieceKE < Piece
700 @normal_moves = [[+1, +2], [-1, +2]]
701 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
703 @promoted_name = "NK"
706 def room_of_head?(x, y, name)
709 return false if ((y == 1) || (y == 2))
711 return false if ((y == 9) || (y == 8))
717 class PieceGI < Piece
720 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
721 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
723 @promoted_name = "NG"
727 class PieceKI < Piece
730 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
737 class PieceKA < Piece
741 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
743 @promoted_name = "UM"
746 def far_movable_grids
751 while (jump_to?(cand_x, cand_y))
752 grids.push([cand_x, cand_y])
753 break if (! put_to?(cand_x, cand_y))
760 while (jump_to?(cand_x, cand_y))
761 grids.push([cand_x, cand_y])
762 break if (! put_to?(cand_x, cand_y))
769 while (jump_to?(cand_x, cand_y))
770 grids.push([cand_x, cand_y])
771 break if (! put_to?(cand_x, cand_y))
778 while (jump_to?(cand_x, cand_y))
779 grids.push([cand_x, cand_y])
780 break if (! put_to?(cand_x, cand_y))
787 class PieceHI < Piece
791 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
793 @promoted_name = "RY"
796 def far_movable_grids
801 while (jump_to?(cand_x, cand_y))
802 grids.push([cand_x, cand_y])
803 break if (! put_to?(cand_x, cand_y))
809 while (jump_to?(cand_x, cand_y))
810 grids.push([cand_x, cand_y])
811 break if (! put_to?(cand_x, cand_y))
817 while (jump_to?(cand_x, cand_y))
818 grids.push([cand_x, cand_y])
819 break if (! put_to?(cand_x, cand_y))
825 while (jump_to?(cand_x, cand_y))
826 grids.push([cand_x, cand_y])
827 break if (! put_to?(cand_x, cand_y))
833 class PieceOU < Piece
836 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
846 @sente_hands = Array::new
847 @gote_hands = Array::new
849 @sente_history = Hash::new
850 @gote_history = Hash::new
851 @array = [[], [], [], [], [], [], [], [], [], []]
854 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
855 attr_reader :move_count
858 PieceKY::new(self, 1, 1, false)
859 PieceKE::new(self, 2, 1, false)
860 PieceGI::new(self, 3, 1, false)
861 PieceKI::new(self, 4, 1, false)
862 PieceOU::new(self, 5, 1, false)
863 PieceKI::new(self, 6, 1, false)
864 PieceGI::new(self, 7, 1, false)
865 PieceKE::new(self, 8, 1, false)
866 PieceKY::new(self, 9, 1, false)
867 PieceKA::new(self, 2, 2, false)
868 PieceHI::new(self, 8, 2, false)
869 PieceFU::new(self, 1, 3, false)
870 PieceFU::new(self, 2, 3, false)
871 PieceFU::new(self, 3, 3, false)
872 PieceFU::new(self, 4, 3, false)
873 PieceFU::new(self, 5, 3, false)
874 PieceFU::new(self, 6, 3, false)
875 PieceFU::new(self, 7, 3, false)
876 PieceFU::new(self, 8, 3, false)
877 PieceFU::new(self, 9, 3, false)
879 PieceKY::new(self, 1, 9, true)
880 PieceKE::new(self, 2, 9, true)
881 PieceGI::new(self, 3, 9, true)
882 PieceKI::new(self, 4, 9, true)
883 PieceOU::new(self, 5, 9, true)
884 PieceKI::new(self, 6, 9, true)
885 PieceGI::new(self, 7, 9, true)
886 PieceKE::new(self, 8, 9, true)
887 PieceKY::new(self, 9, 9, true)
888 PieceKA::new(self, 8, 8, true)
889 PieceHI::new(self, 2, 8, true)
890 PieceFU::new(self, 1, 7, true)
891 PieceFU::new(self, 2, 7, true)
892 PieceFU::new(self, 3, 7, true)
893 PieceFU::new(self, 4, 7, true)
894 PieceFU::new(self, 5, 7, true)
895 PieceFU::new(self, 6, 7, true)
896 PieceFU::new(self, 7, 7, true)
897 PieceFU::new(self, 8, 7, true)
898 PieceFU::new(self, 9, 7, true)
901 def have_piece?(hands, name)
902 piece = hands.find { |i|
908 def move_to(x0, y0, x1, y1, name, sente)
915 if ((x0 == 0) || (y0 == 0))
916 piece = have_piece?(hands, name)
917 return :illegal if (! piece.move_to?(x1, y1, name))
918 piece.move_to(x1, y1)
920 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
921 if (@array[x0][y0].name != name) # promoted ?
922 @array[x0][y0].promoted = true
925 if (@array[x1][y1].name == "OU")
926 return :outori # return board update
928 @array[x1][y1].sente = @array[x0][y0].sente
929 @array[x1][y1].move_to(0, 0)
934 @array[x0][y0].move_to(x1, y1)
940 def look_for_ou(sente)
946 (@array[x][y].name == "OU") &&
947 (@array[x][y].sente == sente))
954 raise "can't find ou"
957 def checkmated?(sente) # sente is loosing
958 ou = look_for_ou(sente)
964 (@array[x][y].sente != sente))
965 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
976 def uchifuzume?(sente)
977 rival_ou = look_for_ou(! sente) # rival's ou
978 if (sente) # rival is gote
979 if ((rival_ou.y != 9) &&
980 (@array[rival_ou.x][rival_ou.y + 1]) &&
981 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
982 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
984 fu_y = rival_ou.y + 1
989 if ((rival_ou.y != 0) &&
990 (@array[rival_ou.x][rival_ou.y - 1]) &&
991 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
992 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
994 fu_y = rival_ou.y - 1
1000 ## case: rival_ou is moving
1002 rival_ou.movable_grids.each do |(cand_x, cand_y)|
1003 tmp_board = Marshal.load(Marshal.dump(self))
1004 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
1005 raise "internal error" if (s != true)
1006 if (! tmp_board.checkmated?(! sente)) # good move
1011 ## case: rival is capturing fu
1017 (@array[x][y].sente != sente) &&
1018 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
1019 if (@array[x][y].promoted)
1020 name = @array[x][y].promoted_name
1022 name = @array[x][y].name
1024 tmp_board = Marshal.load(Marshal.dump(self))
1025 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
1026 raise "internal error" if (s != true)
1027 if (! tmp_board.checkmated?(! sente)) # good move
1038 def oute_sennichite?(sente)
1039 if (checkmated?(! sente))
1042 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
1046 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
1054 def sennichite?(sente)
1056 if (@history[str] && (@history[str] >= 3)) # already 3 times
1062 def good_kachi?(sente)
1063 if (checkmated?(sente))
1064 puts "'NG: Checkmating." if $DEBUG
1068 ou = look_for_ou(sente)
1069 if (sente && (ou.y >= 4))
1070 puts "'NG: Black's OU does not enter yet." if $DEBUG
1073 if (! sente && (ou.y <= 6))
1074 puts "'NG: White's OU does not enter yet." if $DEBUG
1082 hands = @sente_hands
1092 (@array[x][y].sente == sente) &&
1093 (@array[x][y].point > 0))
1094 point = point + @array[x][y].point
1100 hands.each do |piece|
1101 point = point + piece.point
1105 puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
1110 puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
1115 puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
1120 puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
1124 def handle_one_move(str, sente)
1125 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1132 elsif (str =~ /^%KACHI/)
1133 if (good_kachi?(sente))
1138 elsif (str =~ /^%TORYO/)
1144 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1145 ((x0 != 0) || (y0 != 0)))
1147 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1153 hands = @sente_hands
1160 if ((x0 == 0) && (y0 == 0))
1161 return :illegal if (! have_piece?(hands, name))
1162 elsif (! @array[x0][y0])
1163 return :illegal # no piece
1164 elsif (@array[x0][y0].sente != sente)
1165 return :illegal # this is not mine
1166 elsif (@array[x0][y0].name != name)
1167 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1170 ## destination check
1171 if (@array[x1][y1] &&
1172 (@array[x1][y1].sente == sente)) # can't capture mine
1174 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1175 return :illegal # can't put on existing piece
1178 tmp_board = Marshal.load(Marshal.dump(self))
1179 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1180 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1181 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1182 return :sennichite if tmp_board.sennichite?(sente)
1184 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1188 move_to(x0, y0, x1, y1, name, sente)
1191 if (checkmated?(! sente))
1193 @sente_history[str] = (@sente_history[str] || 0) + 1
1195 @gote_history[str] = (@gote_history[str] || 0) + 1
1199 @sente_history.clear
1204 @history[str] = (@history[str] || 0) + 1
1212 a.push(sprintf("P%d", y))
1215 piece = @array[x][y]
1224 a.push(sprintf("\n"))
1227 if (! sente_hands.empty?)
1229 sente_hands.each do |p|
1230 a.push("00" + p.name)
1234 if (! gote_hands.empty?)
1236 gote_hands.each do |p|
1237 a.push("00" + p.name)
1246 #################################################
1249 # http://www2.saganet.ne.jp/a-sim/mhp0726.html
1250 # http://www10.plala.or.jp/greenstone/content1_3.html
1254 def new_rate(me, you, win)
1257 if me == nil && you != nil
1258 return (you + w*400).to_i
1259 elsif me == nil && you == nil
1260 return (1100 + w*400).to_i
1264 score = me + K*(w - we(me, you))
1273 1.0 / ( 10**(-1.0*dr/400) + 1 )
1280 def initialize(p1, p2)
1287 class GameResultWin < GameResult
1288 attr_reader :winner, :loser
1290 def initialize(winner, loser)
1292 @winner, @loser = winner, loser
1293 rate if @winner.id && @loser.id
1302 new_winner = rating.new_rate(@winner.rate, @loser.rate, true)
1303 new_loser = rating.new_rate(@loser.rate, @winner.rate, false)
1304 @winner.rate = new_winner if new_winner
1305 @loser.rate = new_loser if new_loser
1309 class GameResultDraw < GameResult
1317 def initialize(game_name, player0, player1)
1318 @monitors = Array::new
1319 @game_name = game_name
1320 if (@game_name =~ /-(\d+)-(\d+)$/)
1321 @total_time = $1.to_i
1332 @current_player = @sente
1333 @next_player = @gote
1341 @sente.status = "agree_waiting"
1342 @gote.status = "agree_waiting"
1344 @id = sprintf("%s+%s+%s+%s+%s",
1345 LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1346 @logfile = @id + ".csa"
1348 LEAGUE.games[@id] = self
1350 log_message(sprintf("game created %s", @id))
1360 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1361 attr_accessor :last_move, :current_turn
1364 def monitoron(monitor)
1365 @monitors.delete(monitor)
1366 @monitors.push(monitor)
1369 def monitoroff(monitor)
1370 @monitors.delete(monitor)
1373 def reject(rejector)
1374 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1375 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1380 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1382 elsif (@current_player == killer)
1389 log_message(sprintf("game finished %s", @id))
1390 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1395 @sente.status = "connected"
1396 @gote.status = "connected"
1398 if (@current_player.protocol == "CSA")
1399 @current_player.finish
1401 if (@next_player.protocol == "CSA")
1404 @monitors = Array::new
1407 @current_player = nil
1409 LEAGUE.games.delete(@id)
1413 def handle_one_move(str, player)
1415 if (@current_player == player)
1416 @end_time = Time::new
1417 t = (@end_time - @start_time).floor
1418 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1421 if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1423 elsif (str == :timeout)
1424 return false # time isn't expired. players aren't swapped. continue game
1426 @current_player.mytime = @current_player.mytime - t
1427 if (@current_player.mytime < 0)
1428 @current_player.mytime = 0
1432 move_status = @board.handle_one_move(str, @sente == @current_player)
1434 # log_error("handle_one_move raise exception for #{str}")
1435 # move_status = :illegal
1438 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1439 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1441 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1442 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1443 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1444 @fh.printf("%s\nT%d\n", str, t)
1445 @last_move = sprintf("%s,T%d", str, t)
1446 @current_turn = @current_turn + 1
1449 @monitors.each do |monitor|
1450 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1451 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1456 if (@next_player.status != "game") # rival is logout or disconnected
1458 elsif (status == :timeout)
1460 elsif (move_status == :illegal)
1462 elsif (move_status == :kachi_win)
1464 elsif (move_status == :kachi_lose)
1466 elsif (move_status == :toryo)
1468 elsif (move_status == :outori)
1470 elsif (move_status == :sennichite)
1472 elsif (move_status == :oute_sennichite)
1473 oute_sennichite_lose()
1474 elsif (move_status == :uchifuzume)
1476 elsif (move_status == :oute_kaihimore)
1477 oute_kaihimore_lose()
1481 finish() if finish_flag
1482 (@current_player, @next_player) = [@next_player, @current_player]
1483 @start_time = Time::new
1489 @current_player.status = "connected"
1490 @next_player.status = "connected"
1491 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1492 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1493 @result = GameResultWin.new(@current_player, @next_player)
1494 @fh.printf("%%TORYO\n")
1495 @fh.print(@board.to_s.gsub(/^/, "\'"))
1496 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1497 @monitors.each do |monitor|
1498 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1503 @current_player.status = "connected"
1504 @next_player.status = "connected"
1505 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1506 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1507 @result = GameResultWin.new(@next_player, @current_player)
1508 @fh.printf("%%TORYO\n")
1509 @fh.print(@board.to_s.gsub(/^/, "\'"))
1510 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1511 @monitors.each do |monitor|
1512 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1517 @current_player.status = "connected"
1518 @next_player.status = "connected"
1519 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1520 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1521 @result = GameResultDraw.new(@current_player, @next_player)
1522 @fh.print(@board.to_s.gsub(/^/, "\'"))
1523 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1524 @monitors.each do |monitor|
1525 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1529 def oute_sennichite_lose
1530 @current_player.status = "connected"
1531 @next_player.status = "connected"
1532 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1533 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1534 @result = GameResultWin.new(@next_player, @current_player)
1535 @fh.print(@board.to_s.gsub(/^/, "\'"))
1536 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1537 @monitors.each do |monitor|
1538 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1543 @current_player.status = "connected"
1544 @next_player.status = "connected"
1545 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1546 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1547 @result = GameResultWin.new(@next_player, @current_player)
1548 @fh.print(@board.to_s.gsub(/^/, "\'"))
1549 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1550 @monitors.each do |monitor|
1551 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1556 @current_player.status = "connected"
1557 @next_player.status = "connected"
1558 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1559 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1560 @result = GameResultWin.new(@next_player, @current_player)
1561 @fh.print(@board.to_s.gsub(/^/, "\'"))
1562 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1563 @monitors.each do |monitor|
1564 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1568 def oute_kaihimore_lose
1569 @current_player.status = "connected"
1570 @next_player.status = "connected"
1571 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1572 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1573 @result = GameResultWin.new(@next_player, @current_player)
1574 @fh.print(@board.to_s.gsub(/^/, "\'"))
1575 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1576 @monitors.each do |monitor|
1577 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1582 @current_player.status = "connected"
1583 @next_player.status = "connected"
1584 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1585 @next_player.write_safe("#TIME_UP\n#WIN\n")
1586 @result = GameResultWin.new(@next_player, @current_player)
1587 @fh.print(@board.to_s.gsub(/^/, "\'"))
1588 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1589 @monitors.each do |monitor|
1590 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1595 @current_player.status = "connected"
1596 @next_player.status = "connected"
1597 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1598 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1599 @result = GameResultWin.new(@current_player, @next_player)
1600 @fh.printf("%%KACHI\n")
1601 @fh.print(@board.to_s.gsub(/^/, "\'"))
1602 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1603 @monitors.each do |monitor|
1604 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1609 @current_player.status = "connected"
1610 @next_player.status = "connected"
1611 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1612 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1613 @result = GameResultWin(@next_player, @current_player)
1614 @fh.printf("%%KACHI\n")
1615 @fh.print(@board.to_s.gsub(/^/, "\'"))
1616 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1617 @monitors.each do |monitor|
1618 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1623 @current_player.status = "connected"
1624 @next_player.status = "connected"
1625 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1626 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1627 @result = GameResultWin.new(@next_player, @current_player)
1628 @fh.printf("%%TORYO\n")
1629 @fh.print(@board.to_s.gsub(/^/, "\'"))
1630 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1631 @monitors.each do |monitor|
1632 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1637 @current_player.status = "connected"
1638 @next_player.status = "connected"
1639 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1640 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1641 @result = GameResultWin.new(@current_player, @next_player)
1642 @fh.print(@board.to_s.gsub(/^/, "\'"))
1643 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1644 @monitors.each do |monitor|
1645 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1650 log_message(sprintf("game started %s", @id))
1651 @sente.write_safe(sprintf("START:%s\n", @id))
1652 @gote.write_safe(sprintf("START:%s\n", @id))
1653 @sente.mytime = @total_time
1654 @gote.mytime = @total_time
1655 @start_time = Time::new
1660 @fh = open(@logfile, "w")
1664 @fh.printf("N+%s\n", @sente.name)
1665 @fh.printf("N-%s\n", @gote.name)
1666 @fh.printf("$EVENT:%s\n", @id)
1668 @sente.write_safe(propose_message("+"))
1669 @gote.write_safe(propose_message("-"))
1671 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1673 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1674 P2 * -HI * * * * * -KA *
1675 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1676 P4 * * * * * * * * *
1677 P5 * * * * * * * * *
1678 P6 * * * * * * * * *
1679 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1680 P8 * +KA * * * * * +HI *
1681 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1690 Protocol_Version:1.1
1691 Protocol_Mode:Server
1693 Declaration:Jishogi 1.1
1695 Name+:#{@sente.name}
1701 Total_Time:#{@total_time}
1703 Least_Time_Per_Move:#{Least_Time_Per_Move}
1704 Remaining_Time+:#{@sente.mytime}
1705 Remaining_Time-:#{@gote.mytime}
1706 Last_Move:#{@last_move}
1707 Current_Turn:#{@current_turn}
1717 return str0 + @board.to_s + str1
1720 def propose_message(sg_flag)
1723 Protocol_Version:1.1
1724 Protocol_Mode:Server
1726 Declaration:Jishogi 1.1
1728 Name+:#{@sente.name}
1730 Your_Turn:#{sg_flag}
1735 Total_Time:#{@total_time}
1737 Least_Time_Per_Move:#{Least_Time_Per_Move}
1740 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1741 P2 * -HI * * * * * -KA *
1742 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1743 P4 * * * * * * * * *
1744 P5 * * * * * * * * *
1745 P6 * * * * * * * * *
1746 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1747 P8 * +KA * * * * * +HI *
1748 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1760 def issue_current_time
1761 time = Time::new.strftime("%Y%m%d%H%M%S").to_i
1762 @@mutex.synchronize do
1763 while time <= @@time do
1771 #################################################
1778 shogi-server - server for CSA server protocol
1781 shogi-server event_name port_number
1784 server for CSA server protocol
1788 specify filename for logging process ID
1791 this file is distributed under GPL version2 and might be compiled by Exerb
1803 def log_message(str)
1804 printf("%s message: %s\n", Time::new.to_s, str)
1807 def log_warning(str)
1808 printf("%s warning: %s\n", Time::new.to_s, str)
1812 printf("%s error: %s\n", Time::new.to_s, str)
1816 def parse_command_line
1818 parser = GetoptLong.new
1819 parser.ordering = GetoptLong::REQUIRE_ORDER
1821 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1825 parser.each_option do |name, arg|
1826 name.sub!(/^--/, '')
1827 options[name] = arg.dup
1831 raise parser.error_message
1836 def good_game_name?(str)
1837 if ((str =~ /^(.+)-\d+-\d+$/) &&
1838 (good_identifier?($1)))
1845 def good_identifier?(str)
1846 if ( ((str =~ /\A[\w\d_@\-\.]+\z/) ||
1847 (str =~ /\A[\w\d_@\-\.]+,[\w\d_@\-\.]+\z/)) &&
1848 (str.length < Max_Identifier_Length))
1855 def good_login?(str)
1857 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1858 (tokens[0] == "LOGIN") &&
1859 (good_identifier?(tokens[1])))
1866 def write_pid_file(file)
1867 open(file, "w") do |fh|
1868 fh.print Process::pid, "\n"
1872 def mutex_watchdog(mutex, sec)
1884 log_error("mutex watchdog timeout")
1894 mutex_watchdog($mutex, 10)
1897 $options = parse_command_line
1898 if (ARGV.length != 2)
1903 LEAGUE.event = ARGV.shift
1906 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1908 server = TCPserver.open(port)
1909 log_message("server started")
1912 Thread::start(server.accept) do |client|
1916 while (str = client.gets_timeout(Login_Time))
1921 if (good_login?(str))
1922 player = Player::new(str, client)
1923 if (LEAGUE.players[player.name])
1924 if ((LEAGUE.players[player.name].password == player.password) &&
1925 (LEAGUE.players[player.name].status != "game"))
1926 log_message(sprintf("user %s login forcely", player.name))
1927 LEAGUE.players[player.name].kill
1929 client.write_safe("LOGIN:incorrect" + eol)
1930 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1939 client.write_safe("LOGIN:incorrect" + eol)
1940 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1951 log_message(sprintf("user %s login", player.name))
1956 player.game.kill(player)
1958 player.finish # socket has been closed
1959 LEAGUE.delete(player)
1960 log_message(sprintf("user %s logout", player.name))
1969 LEAGUE = League::new