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]/, '')
42 TCPSocket.do_not_reverse_lookup = true
43 Thread.abort_on_exception = true
47 def gets_timeout(t = Default_Timeout)
58 def gets_safe(t = nil)
79 return self.write(str)
93 attr_accessor :players, :games, :event
96 @players[player.name] = player
99 @players.delete(player.name)
101 def get_player(status, game_name, sente, searcher=nil)
102 @players.each do |name, player|
103 if ((player.status == status) &&
104 (player.game_name == game_name) &&
105 ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
106 ((searcher == nil) || (player != searcher)))
115 def initialize(str, socket)
119 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
121 @protocol = nil # CSA or x1
122 @eol = "\m" # favorite eol code
125 @mytime = 0 # set in start method also
129 @write_queue = Queue::new
133 attr_accessor :name, :password, :socket, :status
134 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
135 attr_accessor :main_thread, :writer_thread, :write_queue
137 log_message(sprintf("user %s killed", @name))
142 Thread::kill(@main_thread) if @main_thread
146 if (@status != "finished")
148 log_message(sprintf("user %s finish", @name))
149 Thread::kill(@writer_thread) if @writer_thread
151 @socket.close if (! @socket.closed?)
153 log_message(sprintf("user %s finish failed", @name))
159 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
163 while (str = @write_queue.pop)
164 @socket.write_safe(str)
169 if ((status == "game_waiting") ||
170 (status == "start_waiting") ||
171 (status == "agree_waiting") ||
174 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
175 elsif (@sente == false)
176 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
177 elsif (@sente == nil)
178 return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
181 return sprintf("%s %s %s", @name, @protocol, @status)
186 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
193 (login, @name, @password, ext) = str.split
199 @main_thread = Thread::current
200 @writer_thread = Thread::start do
206 write_safe(sprintf("LOGIN:%s OK\n", @name))
207 if (@protocol != "CSA")
208 log_message(sprintf("user %s run in %s mode", @name, @protocol))
209 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
211 log_message(sprintf("user %s run in CSA mode", @name))
212 if (good_game_name?(@password))
213 csa_1st_str = "%%GAME #{@password} *"
215 csa_1st_str = "%%GAME #{Default_Game_Name} *"
219 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
226 if (@write_queue.size > Max_Write_Queue_Size)
227 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
231 if (@status == "finished")
234 str.chomp! if (str.class == String)
237 if (@status == "game")
238 array_str = str.split(",")
239 move = array_str.shift
240 additional = array_str.shift
241 if /^'(.*)/ =~ additional
242 comment = array_str.unshift("'*#{$1}")
244 s = @game.handle_one_move(move, self)
245 @game.fh.print("#{comment}\n") if (comment && !s)
246 return if (s && @protocol == "CSA")
248 when /^%[^%]/, :timeout
249 if (@status == "game")
250 s = @game.handle_one_move(str, self)
251 return if (s && @protocol == "CSA")
254 if (@status == "agree_waiting")
256 return if (@protocol == "CSA")
258 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
261 if (@status == "agree_waiting")
262 @status = "start_waiting"
263 if ((@game.sente.status == "start_waiting") &&
264 (@game.gote.status == "start_waiting"))
266 @game.sente.status = "game"
267 @game.gote.status = "game"
270 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
272 when /^%%SHOW\s+(\S+)/
274 if (LEAGUE.games[game_id])
275 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
277 write_safe("##[SHOW] +OK\n")
278 when /^%%MONITORON\s+(\S+)/
280 if (LEAGUE.games[game_id])
281 LEAGUE.games[game_id].monitoron(self)
282 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
283 write_safe("##[MONITOR][#{game_id}] +OK\n")
285 when /^%%MONITOROFF\s+(\S+)/
287 if (LEAGUE.games[game_id])
288 LEAGUE.games[game_id].monitoroff(self)
293 if ((@status == "connected") || (@status == "game_waiting"))
294 @status = "connected"
297 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
299 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
303 if (! good_game_name?(game_name))
304 write_safe(sprintf("##[ERROR] bad game name\n"))
306 elsif ((@status == "connected") || (@status == "game_waiting"))
309 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
312 if ((my_sente_str == "*") ||
313 (my_sente_str == "+") ||
314 (my_sente_str == "-"))
317 write_safe(sprintf("##[ERROR] bad game option\n"))
321 if (my_sente_str == "*")
322 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
323 elsif (my_sente_str == "+")
324 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
325 elsif (my_sente_str == "-")
326 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
331 @game_name = game_name
332 if ((my_sente_str == "*") && (rival.sente == nil))
340 elsif (rival.sente == true) # rival has higher priority
342 elsif (rival.sente == false)
344 elsif (my_sente_str == "+")
347 elsif (my_sente_str == "-")
353 Game::new(@game_name, self, rival)
354 self.status = "agree_waiting"
355 rival.status = "agree_waiting"
356 else # rival not found
357 if (command_name == "GAME")
358 @status = "game_waiting"
359 @game_name = game_name
360 if (my_sente_str == "+")
362 elsif (my_sente_str == "-")
368 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
369 @status = "connected"
374 when /^%%CHAT\s+(.+)/
376 LEAGUE.players.each do |name, player|
377 if (player.protocol != "CSA")
378 player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
383 LEAGUE.games.each do |id, game|
384 buf.push(sprintf("##[LIST] %s\n", id))
386 buf.push("##[LIST] +OK\n")
390 LEAGUE.players.each do |name, player|
391 buf.push(sprintf("##[WHO] %s\n", player.to_s))
393 buf.push("##[WHO] +OK\n")
396 @status = "connected"
397 write_safe("LOGOUT:completed\n")
400 ## ignore null string
402 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
412 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
413 def initialize(board, x, y, sente, promoted=false)
420 if ((x == 0) || (y == 0))
422 hands = board.sente_hands
424 hands = board.gote_hands
431 @board.array[x][y] = self
434 attr_accessor :promoted, :sente, :x, :y, :board
436 def room_of_head?(x, y, name)
441 return adjacent_movable_grids + far_movable_grids
444 def far_movable_grids
449 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
450 if ((@board.array[x][y] == nil) || # dst is empty
451 (@board.array[x][y].sente != @sente)) # dst is enemy
459 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
460 if (@board.array[x][y] == nil) # dst is empty?
467 def adjacent_movable_grids
470 moves = @promoted_moves
472 moves = @normal_moves
474 moves.each do |(dx, dy)|
481 if (jump_to?(cand_x, cand_y))
482 grids.push([cand_x, cand_y])
488 def move_to?(x, y, name)
489 return false if (! room_of_head?(x, y, name))
490 return false if ((name != @name) && (name != @promoted_name))
491 return false if (@promoted && (name != @promoted_name)) # can't un-promote
494 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
496 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
498 return false if ((6 >= @y) && (6 >= y) && (name != @name))
502 if ((@x == 0) || (@y == 0))
503 return jump_to?(x, y)
505 return movable_grids.include?([x, y])
510 if ((@x == 0) || (@y == 0))
512 @board.sente_hands.delete(self)
514 @board.gote_hands.delete(self)
516 @board.array[x][y] = self
517 elsif ((x == 0) || (y == 0))
518 @promoted = false # clear promoted flag before moving to hands
520 @board.sente_hands.push(self)
522 @board.gote_hands.push(self)
524 @board.array[@x][@y] = nil
526 @board.array[@x][@y] = nil
527 @board.array[x][y] = self
560 class PieceFU < Piece
563 @normal_moves = [[0, +1]]
564 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
566 @promoted_name = "TO"
569 def room_of_head?(x, y, name)
572 return false if (y == 1)
574 return false if (y == 9)
580 if ((iy != @y) && # not source position
581 @board.array[x][iy] &&
582 (@board.array[x][iy].sente == @sente) && # mine
583 (@board.array[x][iy].name == "FU") &&
584 (@board.array[x][iy].promoted == false))
594 class PieceKY < Piece
598 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
600 @promoted_name = "NY"
603 def room_of_head?(x, y, name)
606 return false if (y == 1)
608 return false if (y == 9)
613 def far_movable_grids
621 while (jump_to?(cand_x, cand_y))
622 grids.push([cand_x, cand_y])
623 break if (! put_to?(cand_x, cand_y))
629 while (jump_to?(cand_x, cand_y))
630 grids.push([cand_x, cand_y])
631 break if (! put_to?(cand_x, cand_y))
639 class PieceKE < Piece
642 @normal_moves = [[+1, +2], [-1, +2]]
643 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
645 @promoted_name = "NK"
648 def room_of_head?(x, y, name)
651 return false if ((y == 1) || (y == 2))
653 return false if ((y == 9) || (y == 8))
659 class PieceGI < Piece
662 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
663 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
665 @promoted_name = "NG"
669 class PieceKI < Piece
672 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
679 class PieceKA < Piece
683 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
685 @promoted_name = "UM"
688 def far_movable_grids
693 while (jump_to?(cand_x, cand_y))
694 grids.push([cand_x, cand_y])
695 break if (! put_to?(cand_x, cand_y))
702 while (jump_to?(cand_x, cand_y))
703 grids.push([cand_x, cand_y])
704 break if (! put_to?(cand_x, cand_y))
711 while (jump_to?(cand_x, cand_y))
712 grids.push([cand_x, cand_y])
713 break if (! put_to?(cand_x, cand_y))
720 while (jump_to?(cand_x, cand_y))
721 grids.push([cand_x, cand_y])
722 break if (! put_to?(cand_x, cand_y))
729 class PieceHI < Piece
733 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
735 @promoted_name = "RY"
738 def far_movable_grids
743 while (jump_to?(cand_x, cand_y))
744 grids.push([cand_x, cand_y])
745 break if (! put_to?(cand_x, cand_y))
751 while (jump_to?(cand_x, cand_y))
752 grids.push([cand_x, cand_y])
753 break if (! put_to?(cand_x, cand_y))
759 while (jump_to?(cand_x, cand_y))
760 grids.push([cand_x, cand_y])
761 break if (! put_to?(cand_x, cand_y))
767 while (jump_to?(cand_x, cand_y))
768 grids.push([cand_x, cand_y])
769 break if (! put_to?(cand_x, cand_y))
775 class PieceOU < Piece
778 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
788 @sente_hands = Array::new
789 @gote_hands = Array::new
791 @sente_history = Hash::new
792 @gote_history = Hash::new
793 @array = [[], [], [], [], [], [], [], [], [], []]
796 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
797 attr_reader :move_count
800 PieceKY::new(self, 1, 1, false)
801 PieceKE::new(self, 2, 1, false)
802 PieceGI::new(self, 3, 1, false)
803 PieceKI::new(self, 4, 1, false)
804 PieceOU::new(self, 5, 1, false)
805 PieceKI::new(self, 6, 1, false)
806 PieceGI::new(self, 7, 1, false)
807 PieceKE::new(self, 8, 1, false)
808 PieceKY::new(self, 9, 1, false)
809 PieceKA::new(self, 2, 2, false)
810 PieceHI::new(self, 8, 2, false)
811 PieceFU::new(self, 1, 3, false)
812 PieceFU::new(self, 2, 3, false)
813 PieceFU::new(self, 3, 3, false)
814 PieceFU::new(self, 4, 3, false)
815 PieceFU::new(self, 5, 3, false)
816 PieceFU::new(self, 6, 3, false)
817 PieceFU::new(self, 7, 3, false)
818 PieceFU::new(self, 8, 3, false)
819 PieceFU::new(self, 9, 3, false)
821 PieceKY::new(self, 1, 9, true)
822 PieceKE::new(self, 2, 9, true)
823 PieceGI::new(self, 3, 9, true)
824 PieceKI::new(self, 4, 9, true)
825 PieceOU::new(self, 5, 9, true)
826 PieceKI::new(self, 6, 9, true)
827 PieceGI::new(self, 7, 9, true)
828 PieceKE::new(self, 8, 9, true)
829 PieceKY::new(self, 9, 9, true)
830 PieceKA::new(self, 8, 8, true)
831 PieceHI::new(self, 2, 8, true)
832 PieceFU::new(self, 1, 7, true)
833 PieceFU::new(self, 2, 7, true)
834 PieceFU::new(self, 3, 7, true)
835 PieceFU::new(self, 4, 7, true)
836 PieceFU::new(self, 5, 7, true)
837 PieceFU::new(self, 6, 7, true)
838 PieceFU::new(self, 7, 7, true)
839 PieceFU::new(self, 8, 7, true)
840 PieceFU::new(self, 9, 7, true)
843 def have_piece?(hands, name)
844 piece = hands.find { |i|
850 def move_to(x0, y0, x1, y1, name, sente)
857 if ((x0 == 0) || (y0 == 0))
858 piece = have_piece?(hands, name)
859 return :illegal if (! piece.move_to?(x1, y1, name))
860 piece.move_to(x1, y1)
862 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
863 if (@array[x0][y0].name != name) # promoted ?
864 @array[x0][y0].promoted = true
867 if (@array[x1][y1].name == "OU")
868 return :outori # return board update
870 @array[x1][y1].sente = @array[x0][y0].sente
871 @array[x1][y1].move_to(0, 0)
876 @array[x0][y0].move_to(x1, y1)
882 def look_for_ou(sente)
888 (@array[x][y].name == "OU") &&
889 (@array[x][y].sente == sente))
896 raise "can't find ou"
899 def checkmated?(sente) # sente is loosing
900 ou = look_for_ou(sente)
906 (@array[x][y].sente != sente))
907 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
918 def uchifuzume?(sente)
919 rival_ou = look_for_ou(! sente) # rival's ou
920 if (sente) # rival is gote
921 if ((rival_ou.y != 9) &&
922 (@array[rival_ou.x][rival_ou.y + 1]) &&
923 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
924 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
926 fu_y = rival_ou.y + 1
931 if ((rival_ou.y != 0) &&
932 (@array[rival_ou.x][rival_ou.y - 1]) &&
933 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
934 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
936 fu_y = rival_ou.y - 1
942 ## case: rival_ou is moving
944 rival_ou.movable_grids.each do |(cand_x, cand_y)|
945 tmp_board = Marshal.load(Marshal.dump(self))
946 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
947 raise "internal error" if (s != true)
948 if (! tmp_board.checkmated?(! sente)) # good move
953 ## case: rival is capturing fu
959 (@array[x][y].sente != sente) &&
960 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
961 if (@array[x][y].promoted)
962 name = @array[x][y].promoted_name
964 name = @array[x][y].name
966 tmp_board = Marshal.load(Marshal.dump(self))
967 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
968 raise "internal error" if (s != true)
969 if (! tmp_board.checkmated?(! sente)) # good move
980 def oute_sennichite?(sente)
981 if (checkmated?(! sente))
984 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
988 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
996 def sennichite?(sente)
998 if (@history[str] && (@history[str] >= 3)) # already 3 times
1004 def good_kachi?(sente)
1005 if (checkmated?(sente))
1006 puts "'NG: Checkmating." if $DEBUG
1010 ou = look_for_ou(sente)
1011 if (sente && (ou.y >= 4))
1012 puts "'NG: Black's OU does not enter yet." if $DEBUG
1015 if (! sente && (ou.y <= 6))
1016 puts "'NG: White's OU does not enter yet." if $DEBUG
1024 hands = @sente_hands
1034 (@array[x][y].sente == sente) &&
1035 (@array[x][y].point > 0))
1036 point = point + @array[x][y].point
1042 hands.each do |piece|
1043 point = point + piece.point
1047 puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
1052 puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
1057 puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
1062 puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
1066 def handle_one_move(str, sente)
1067 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1074 elsif (str =~ /^%KACHI/)
1075 if (good_kachi?(sente))
1080 elsif (str =~ /^%TORYO/)
1086 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1087 ((x0 != 0) || (y0 != 0)))
1089 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1095 hands = @sente_hands
1102 if ((x0 == 0) && (y0 == 0))
1103 return :illegal if (! have_piece?(hands, name))
1104 elsif (! @array[x0][y0])
1105 return :illegal # no piece
1106 elsif (@array[x0][y0].sente != sente)
1107 return :illegal # this is not mine
1108 elsif (@array[x0][y0].name != name)
1109 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1112 ## destination check
1113 if (@array[x1][y1] &&
1114 (@array[x1][y1].sente == sente)) # can't capture mine
1116 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1117 return :illegal # can't put on existing piece
1120 tmp_board = Marshal.load(Marshal.dump(self))
1121 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1122 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1123 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1124 return :sennichite if tmp_board.sennichite?(sente)
1126 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1130 move_to(x0, y0, x1, y1, name, sente)
1133 if (checkmated?(! sente))
1135 @sente_history[str] = (@sente_history[str] || 0) + 1
1137 @gote_history[str] = (@gote_history[str] || 0) + 1
1141 @sente_history.clear
1146 @history[str] = (@history[str] || 0) + 1
1154 a.push(sprintf("P%d", y))
1157 piece = @array[x][y]
1166 a.push(sprintf("\n"))
1169 if (! sente_hands.empty?)
1171 sente_hands.each do |p|
1172 a.push("00" + p.name)
1176 if (! gote_hands.empty?)
1178 gote_hands.each do |p|
1179 a.push("00" + p.name)
1192 def initialize(game_name, player0, player1)
1193 @monitors = Array::new
1194 @game_name = game_name
1195 if (@game_name =~ /-(\d+)-(\d+)$/)
1196 @total_time = $1.to_i
1207 @current_player = @sente
1208 @next_player = @gote
1216 @sente.status = "agree_waiting"
1217 @gote.status = "agree_waiting"
1219 @id = sprintf("%s+%s+%s+%s+%s",
1220 LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1221 @logfile = @id + ".csa"
1223 LEAGUE.games[@id] = self
1225 log_message(sprintf("game created %s", @id))
1234 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1235 attr_accessor :last_move, :current_turn
1237 def monitoron(monitor)
1238 @monitors.delete(monitor)
1239 @monitors.push(monitor)
1242 def monitoroff(monitor)
1243 @monitors.delete(monitor)
1246 def reject(rejector)
1247 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1248 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1253 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1255 elsif (@current_player == killer)
1262 log_message(sprintf("game finished %s", @id))
1263 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1268 @sente.status = "connected"
1269 @gote.status = "connected"
1271 if (@current_player.protocol == "CSA")
1272 @current_player.finish
1274 if (@next_player.protocol == "CSA")
1277 @monitors = Array::new
1280 @current_player = nil
1282 LEAGUE.games.delete(@id)
1285 def handle_one_move(str, player)
1287 if (@current_player == player)
1288 @end_time = Time::new
1289 t = (@end_time - @start_time).floor
1290 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1293 if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1295 elsif (str == :timeout)
1296 return false # time isn't expired. players aren't swapped. continue game
1298 @current_player.mytime = @current_player.mytime - t
1299 if (@current_player.mytime < 0)
1300 @current_player.mytime = 0
1304 move_status = @board.handle_one_move(str, @sente == @current_player)
1306 # log_error("handle_one_move raise exception for #{str}")
1307 # move_status = :illegal
1310 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1311 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1313 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1314 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1315 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1316 @fh.printf("%s\nT%d\n", str, t)
1317 @last_move = sprintf("%s,T%d", str, t)
1318 @current_turn = @current_turn + 1
1321 @monitors.each do |monitor|
1322 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1323 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1328 if (@next_player.status != "game") # rival is logout or disconnected
1330 elsif (status == :timeout)
1332 elsif (move_status == :illegal)
1334 elsif (move_status == :kachi_win)
1336 elsif (move_status == :kachi_lose)
1338 elsif (move_status == :toryo)
1340 elsif (move_status == :outori)
1342 elsif (move_status == :sennichite)
1344 elsif (move_status == :oute_sennichite)
1345 oute_sennichite_lose()
1346 elsif (move_status == :uchifuzume)
1348 elsif (move_status == :oute_kaihimore)
1349 oute_kaihimore_lose()
1353 finish() if finish_flag
1354 (@current_player, @next_player) = [@next_player, @current_player]
1355 @start_time = Time::new
1361 @current_player.status = "connected"
1362 @next_player.status = "connected"
1363 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1364 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1365 @fh.printf("%%TORYO\n")
1366 @fh.print(@board.to_s.gsub(/^/, "\'"))
1367 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1368 @monitors.each do |monitor|
1369 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1374 @current_player.status = "connected"
1375 @next_player.status = "connected"
1376 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1377 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1378 @fh.printf("%%TORYO\n")
1379 @fh.print(@board.to_s.gsub(/^/, "\'"))
1380 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1381 @monitors.each do |monitor|
1382 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1387 @current_player.status = "connected"
1388 @next_player.status = "connected"
1389 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1390 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1391 @fh.print(@board.to_s.gsub(/^/, "\'"))
1392 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1393 @monitors.each do |monitor|
1394 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1398 def oute_sennichite_lose
1399 @current_player.status = "connected"
1400 @next_player.status = "connected"
1401 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1402 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1403 @fh.print(@board.to_s.gsub(/^/, "\'"))
1404 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1405 @monitors.each do |monitor|
1406 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1411 @current_player.status = "connected"
1412 @next_player.status = "connected"
1413 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1414 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1415 @fh.print(@board.to_s.gsub(/^/, "\'"))
1416 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1417 @monitors.each do |monitor|
1418 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1423 @current_player.status = "connected"
1424 @next_player.status = "connected"
1425 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1426 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1427 @fh.print(@board.to_s.gsub(/^/, "\'"))
1428 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1429 @monitors.each do |monitor|
1430 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1434 def oute_kaihimore_lose
1435 @current_player.status = "connected"
1436 @next_player.status = "connected"
1437 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1438 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1439 @fh.print(@board.to_s.gsub(/^/, "\'"))
1440 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1441 @monitors.each do |monitor|
1442 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1447 @current_player.status = "connected"
1448 @next_player.status = "connected"
1449 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1450 @next_player.write_safe("#TIME_UP\n#WIN\n")
1451 @fh.print(@board.to_s.gsub(/^/, "\'"))
1452 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1453 @monitors.each do |monitor|
1454 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1459 @current_player.status = "connected"
1460 @next_player.status = "connected"
1461 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1462 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1463 @fh.printf("%%KACHI\n")
1464 @fh.print(@board.to_s.gsub(/^/, "\'"))
1465 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1466 @monitors.each do |monitor|
1467 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1472 @current_player.status = "connected"
1473 @next_player.status = "connected"
1474 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1475 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1476 @fh.printf("%%KACHI\n")
1477 @fh.print(@board.to_s.gsub(/^/, "\'"))
1478 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1479 @monitors.each do |monitor|
1480 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1485 @current_player.status = "connected"
1486 @next_player.status = "connected"
1487 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1488 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1489 @fh.printf("%%TORYO\n")
1490 @fh.print(@board.to_s.gsub(/^/, "\'"))
1491 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1492 @monitors.each do |monitor|
1493 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1498 @current_player.status = "connected"
1499 @next_player.status = "connected"
1500 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1501 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1502 @fh.print(@board.to_s.gsub(/^/, "\'"))
1503 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1504 @monitors.each do |monitor|
1505 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1510 log_message(sprintf("game started %s", @id))
1511 @sente.write_safe(sprintf("START:%s\n", @id))
1512 @gote.write_safe(sprintf("START:%s\n", @id))
1513 @sente.mytime = @total_time
1514 @gote.mytime = @total_time
1515 @start_time = Time::new
1520 @fh = open(@logfile, "w")
1524 @fh.printf("N+%s\n", @sente.name)
1525 @fh.printf("N-%s\n", @gote.name)
1526 @fh.printf("$EVENT:%s\n", @id)
1528 @sente.write_safe(propose_message("+"))
1529 @gote.write_safe(propose_message("-"))
1531 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1533 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1534 P2 * -HI * * * * * -KA *
1535 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1536 P4 * * * * * * * * *
1537 P5 * * * * * * * * *
1538 P6 * * * * * * * * *
1539 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1540 P8 * +KA * * * * * +HI *
1541 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1550 Protocol_Version:1.1
1551 Protocol_Mode:Server
1553 Declaration:Jishogi 1.1
1555 Name+:#{@sente.name}
1561 Total_Time:#{@total_time}
1563 Least_Time_Per_Move:#{Least_Time_Per_Move}
1564 Remaining_Time+:#{@sente.mytime}
1565 Remaining_Time-:#{@gote.mytime}
1566 Last_Move:#{@last_move}
1567 Current_Turn:#{@current_turn}
1577 return str0 + @board.to_s + str1
1580 def propose_message(sg_flag)
1583 Protocol_Version:1.1
1584 Protocol_Mode:Server
1586 Declaration:Jishogi 1.1
1588 Name+:#{@sente.name}
1590 Your_Turn:#{sg_flag}
1595 Total_Time:#{@total_time}
1597 Least_Time_Per_Move:#{Least_Time_Per_Move}
1600 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1601 P2 * -HI * * * * * -KA *
1602 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1603 P4 * * * * * * * * *
1604 P5 * * * * * * * * *
1605 P6 * * * * * * * * *
1606 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1607 P8 * +KA * * * * * +HI *
1608 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1620 def issue_current_time
1621 time = Time::new.strftime("%Y%m%d%H%M%S").to_i
1622 @@mutex.synchronize do
1623 while time <= @@time do
1634 shogi-server - server for CSA server protocol
1637 shogi-server event_name port_number
1640 server for CSA server protocol
1644 specify filename for logging process ID
1647 this file is distributed under GPL version2 and might be compiled by Exerb
1659 def log_message(str)
1660 printf("%s message: %s\n", Time::new.to_s, str)
1663 def log_warning(str)
1664 printf("%s warning: %s\n", Time::new.to_s, str)
1668 printf("%s error: %s\n", Time::new.to_s, str)
1672 def parse_command_line
1674 parser = GetoptLong.new
1675 parser.ordering = GetoptLong::REQUIRE_ORDER
1677 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1681 parser.each_option do |name, arg|
1682 name.sub!(/^--/, '')
1683 options[name] = arg.dup
1687 raise parser.error_message
1692 def good_game_name?(str)
1693 if ((str =~ /^(.+)-\d+-\d+$/) &&
1694 (good_identifier?($1)))
1701 def good_identifier?(str)
1702 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1703 (str.length < Max_Identifier_Length))
1710 def good_login?(str)
1712 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1713 (tokens[0] == "LOGIN") &&
1714 (good_identifier?(tokens[1])))
1721 def write_pid_file(file)
1722 open(file, "w") do |fh|
1723 fh.print Process::pid, "\n"
1727 def mutex_watchdog(mutex, sec)
1739 log_error("mutex watchdog timeout")
1749 mutex_watchdog($mutex, 10)
1752 $options = parse_command_line
1753 if (ARGV.length != 2)
1758 LEAGUE.event = ARGV.shift
1761 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1765 server = TCPserver.open(port)
1766 log_message("server started")
1769 Thread::start(server.accept) do |client|
1773 while (str = client.gets_timeout(Login_Time))
1778 if (good_login?(str))
1779 player = Player::new(str, client)
1780 if (LEAGUE.players[player.name])
1781 if ((LEAGUE.players[player.name].password == player.password) &&
1782 (LEAGUE.players[player.name].status != "game"))
1783 log_message(sprintf("user %s login forcely", player.name))
1784 LEAGUE.players[player.name].kill
1786 client.write_safe("LOGIN:incorrect" + eol)
1787 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1796 client.write_safe("LOGIN:incorrect" + eol)
1797 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1808 log_message(sprintf("user %s login", player.name))
1813 player.game.kill(player)
1815 player.finish # socket has been closed
1816 LEAGUE.delete(player)
1817 log_message(sprintf("user %s logout", player.name))
1826 LEAGUE = League::new