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
46 def gets_timeout(t = Default_Timeout)
57 def gets_safe(t = nil)
78 return self.write(str)
92 attr_accessor :players, :games, :event
95 @players[player.name] = player
98 @players.delete(player.name)
100 def get_player(status, game_name, sente, searcher=nil)
101 @players.each do |name, player|
102 if ((player.status == status) &&
103 (player.game_name == game_name) &&
104 ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
105 ((searcher == nil) || (player != searcher)))
114 def initialize(str, socket)
118 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
120 @protocol = nil # CSA or x1
121 @eol = "\m" # favorite eol code
124 @mytime = 0 # set in start method also
128 @write_queue = Queue::new
132 attr_accessor :name, :password, :socket, :status
133 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
134 attr_accessor :main_thread, :writer_thread, :write_queue
136 log_message(sprintf("user %s killed", @name))
141 Thread::kill(@main_thread) if @main_thread
145 if (@status != "finished")
147 log_message(sprintf("user %s finish", @name))
148 Thread::kill(@writer_thread) if @writer_thread
150 @socket.close if (! @socket.closed?)
152 log_message(sprintf("user %s finish failed", @name))
158 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
162 while (str = @write_queue.pop)
163 @socket.write_safe(str)
168 if ((status == "game_waiting") ||
169 (status == "start_waiting") ||
170 (status == "agree_waiting") ||
173 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
174 elsif (@sente == false)
175 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
176 elsif (@sente == nil)
177 return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
180 return sprintf("%s %s %s", @name, @protocol, @status)
185 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
192 (login, @name, @password, ext) = str.split
198 @main_thread = Thread::current
199 @writer_thread = Thread::start do
205 write_safe(sprintf("LOGIN:%s OK\n", @name))
206 if (@protocol != "CSA")
207 log_message(sprintf("user %s run in %s mode", @name, @protocol))
208 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
210 log_message(sprintf("user %s run in CSA mode", @name))
211 csa_1st_str = "%%GAME #{Default_Game_Name} *"
214 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
221 if (@write_queue.size > Max_Write_Queue_Size)
222 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
226 if (@status == "finished")
229 str.chomp! if (str.class == String)
231 when /^[\+\-%][^%]/, :timeout
232 if (@status == "game")
233 s = @game.handle_one_move(str, self)
234 return if (s && @protocol == "CSA")
237 if (@status == "agree_waiting")
239 return if (@protocol == "CSA")
241 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
244 if (@status == "agree_waiting")
245 @status = "start_waiting"
246 if ((@game.sente.status == "start_waiting") &&
247 (@game.gote.status == "start_waiting"))
249 @game.sente.status = "game"
250 @game.gote.status = "game"
253 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
255 when /^%%SHOW\s+(\S+)/
257 if (LEAGUE.games[game_id])
258 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
260 write_safe("##[SHOW] +OK\n")
261 when /^%%MONITORON\s+(\S+)/
263 if (LEAGUE.games[game_id])
264 LEAGUE.games[game_id].monitoron(self)
265 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
266 write_safe("##[MONITOR][#{game_id}] +OK\n")
268 when /^%%MONITOROFF\s+(\S+)/
270 if (LEAGUE.games[game_id])
271 LEAGUE.games[game_id].monitoroff(self)
276 @status = "connected"
278 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
282 if (! good_game_name?(game_name))
283 write_safe(sprintf("##[ERROR] bad game name\n"))
285 elsif ((@status == "connected") || (@status == "game_waiting"))
288 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
291 if ((my_sente_str == "*") ||
292 (my_sente_str == "+") ||
293 (my_sente_str == "-"))
296 write_safe(sprintf("##[ERROR] bad game option\n"))
300 if (my_sente_str == "*")
301 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
302 elsif (my_sente_str == "+")
303 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
304 elsif (my_sente_str == "-")
305 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
310 @game_name = game_name
311 if ((my_sente_str == "*") && (rival.sente == nil))
319 elsif (rival.sente == true) # rival has higher priority
321 elsif (rival.sente == false)
323 elsif (my_sente_str == "+")
326 elsif (my_sente_str == "-")
332 Game::new(@game_name, self, rival)
333 self.status = "agree_waiting"
334 rival.status = "agree_waiting"
335 else # rival not found
336 if (command_name == "GAME")
337 @status = "game_waiting"
338 @game_name = game_name
339 if (my_sente_str == "+")
341 elsif (my_sente_str == "-")
347 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
348 @status = "connected"
353 when /^%%CHAT\s+(.+)/
355 LEAGUE.players.each do |name, player|
356 if (player.protocol != "CSA")
357 s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
358 player.status = "zombie" if (! s)
363 LEAGUE.games.each do |id, game|
364 buf.push(sprintf("##[LIST] %s\n", id))
366 buf.push("##[LIST] +OK\n")
370 LEAGUE.players.each do |name, player|
371 buf.push(sprintf("##[WHO] %s\n", player.to_s))
373 buf.push("##[WHO] +OK\n")
376 @status = "connected"
377 write_safe("LOGOUT:completed\n")
380 ## ignore null string
382 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
392 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
393 def initialize(board, x, y, sente, promoted=false)
400 if ((x == 0) || (y == 0))
402 hands = board.sente_hands
404 hands = board.gote_hands
411 @board.array[x][y] = self
414 attr_accessor :promoted, :sente, :x, :y, :board
416 def room_of_head?(x, y, name)
421 return adjacent_movable_grids + far_movable_grids
424 def far_movable_grids
429 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
430 if ((@board.array[x][y] == nil) || # dst is empty
431 (@board.array[x][y].sente != @sente)) # dst is enemy
439 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
440 if (@board.array[x][y] == nil) # dst is empty?
447 def adjacent_movable_grids
450 moves = @promoted_moves
452 moves = @normal_moves
454 moves.each do |(dx, dy)|
461 if (jump_to?(cand_x, cand_y))
462 grids.push([cand_x, cand_y])
468 def move_to?(x, y, name)
469 return false if (! room_of_head?(x, y, name))
470 return false if ((name != @name) && (name != @promoted_name))
471 return false if (@promoted && (name != @promoted_name)) # can't un-promote
474 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
476 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
478 return false if ((6 >= @y) && (6 >= y) && (name != @name))
482 if ((@x == 0) || (@y == 0))
483 return jump_to?(x, y)
485 return movable_grids.include?([x, y])
490 if ((@x == 0) || (@y == 0))
492 @board.sente_hands.delete(self)
494 @board.gote_hands.delete(self)
496 @board.array[x][y] = self
497 elsif ((x == 0) || (y == 0))
498 @promoted = false # clear promoted flag before moving to hands
500 @board.sente_hands.push(self)
502 @board.gote_hands.push(self)
504 @board.array[@x][@y] = nil
506 @board.array[@x][@y] = nil
507 @board.array[x][y] = self
540 class PieceFU < Piece
543 @normal_moves = [[0, +1]]
544 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
546 @promoted_name = "TO"
549 def room_of_head?(x, y, name)
552 return false if (y == 1)
554 return false if (y == 9)
560 if ((iy != @y) && # not source position
561 @board.array[x][iy] &&
562 (@board.array[x][iy].sente == @sente) && # mine
563 (@board.array[x][iy].name == "FU") &&
564 (@board.array[x][iy].promoted == false))
574 class PieceKY < Piece
578 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
580 @promoted_name = "NY"
583 def room_of_head?(x, y, name)
586 return false if (y == 1)
588 return false if (y == 9)
593 def far_movable_grids
601 while (jump_to?(cand_x, cand_y))
602 grids.push([cand_x, cand_y])
603 break if (! put_to?(cand_x, cand_y))
609 while (jump_to?(cand_x, cand_y))
610 grids.push([cand_x, cand_y])
611 break if (! put_to?(cand_x, cand_y))
619 class PieceKE < Piece
622 @normal_moves = [[+1, +2], [-1, +2]]
623 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
625 @promoted_name = "NK"
628 def room_of_head?(x, y, name)
631 return false if ((y == 1) || (y == 2))
633 return false if ((y == 9) || (y == 8))
639 class PieceGI < Piece
642 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
643 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
645 @promoted_name = "NG"
649 class PieceKI < Piece
652 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
659 class PieceKA < Piece
663 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
665 @promoted_name = "UM"
668 def far_movable_grids
673 while (jump_to?(cand_x, cand_y))
674 grids.push([cand_x, cand_y])
675 break if (! put_to?(cand_x, cand_y))
682 while (jump_to?(cand_x, cand_y))
683 grids.push([cand_x, cand_y])
684 break if (! put_to?(cand_x, cand_y))
691 while (jump_to?(cand_x, cand_y))
692 grids.push([cand_x, cand_y])
693 break if (! put_to?(cand_x, cand_y))
700 while (jump_to?(cand_x, cand_y))
701 grids.push([cand_x, cand_y])
702 break if (! put_to?(cand_x, cand_y))
709 class PieceHI < Piece
713 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
715 @promoted_name = "RY"
718 def far_movable_grids
723 while (jump_to?(cand_x, cand_y))
724 grids.push([cand_x, cand_y])
725 break if (! put_to?(cand_x, cand_y))
731 while (jump_to?(cand_x, cand_y))
732 grids.push([cand_x, cand_y])
733 break if (! put_to?(cand_x, cand_y))
739 while (jump_to?(cand_x, cand_y))
740 grids.push([cand_x, cand_y])
741 break if (! put_to?(cand_x, cand_y))
747 while (jump_to?(cand_x, cand_y))
748 grids.push([cand_x, cand_y])
749 break if (! put_to?(cand_x, cand_y))
755 class PieceOU < Piece
758 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
768 @sente_hands = Array::new
769 @gote_hands = Array::new
771 @sente_history = Hash::new
772 @gote_history = Hash::new
773 @array = [[], [], [], [], [], [], [], [], [], []]
775 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
778 PieceKY::new(self, 1, 1, false)
779 PieceKE::new(self, 2, 1, false)
780 PieceGI::new(self, 3, 1, false)
781 PieceKI::new(self, 4, 1, false)
782 PieceOU::new(self, 5, 1, false)
783 PieceKI::new(self, 6, 1, false)
784 PieceGI::new(self, 7, 1, false)
785 PieceKE::new(self, 8, 1, false)
786 PieceKY::new(self, 9, 1, false)
787 PieceKA::new(self, 2, 2, false)
788 PieceHI::new(self, 8, 2, false)
789 PieceFU::new(self, 1, 3, false)
790 PieceFU::new(self, 2, 3, false)
791 PieceFU::new(self, 3, 3, false)
792 PieceFU::new(self, 4, 3, false)
793 PieceFU::new(self, 5, 3, false)
794 PieceFU::new(self, 6, 3, false)
795 PieceFU::new(self, 7, 3, false)
796 PieceFU::new(self, 8, 3, false)
797 PieceFU::new(self, 9, 3, false)
799 PieceKY::new(self, 1, 9, true)
800 PieceKE::new(self, 2, 9, true)
801 PieceGI::new(self, 3, 9, true)
802 PieceKI::new(self, 4, 9, true)
803 PieceOU::new(self, 5, 9, true)
804 PieceKI::new(self, 6, 9, true)
805 PieceGI::new(self, 7, 9, true)
806 PieceKE::new(self, 8, 9, true)
807 PieceKY::new(self, 9, 9, true)
808 PieceKA::new(self, 8, 8, true)
809 PieceHI::new(self, 2, 8, true)
810 PieceFU::new(self, 1, 7, true)
811 PieceFU::new(self, 2, 7, true)
812 PieceFU::new(self, 3, 7, true)
813 PieceFU::new(self, 4, 7, true)
814 PieceFU::new(self, 5, 7, true)
815 PieceFU::new(self, 6, 7, true)
816 PieceFU::new(self, 7, 7, true)
817 PieceFU::new(self, 8, 7, true)
818 PieceFU::new(self, 9, 7, true)
821 def have_piece?(hands, name)
822 piece = hands.find { |i|
828 def move_to(x0, y0, x1, y1, name, sente)
835 if ((x0 == 0) || (y0 == 0))
836 piece = have_piece?(hands, name)
837 return :illegal if (! piece.move_to?(x1, y1, name))
838 piece.move_to(x1, y1)
840 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
841 if (@array[x0][y0].name != name) # promoted ?
842 @array[x0][y0].promoted = true
845 if (@array[x1][y1].name == "OU")
846 return :outori # return board update
848 @array[x1][y1].sente = @array[x0][y0].sente
849 @array[x1][y1].move_to(0, 0)
854 @array[x0][y0].move_to(x1, y1)
859 def look_for_ou(sente)
865 (@array[x][y].name == "OU") &&
866 (@array[x][y].sente == sente))
873 raise "can't find ou"
876 def checkmated?(sente) # sente is loosing
877 ou = look_for_ou(sente)
883 (@array[x][y].sente != sente))
884 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
895 def uchifuzume?(sente)
896 rival_ou = look_for_ou(! sente) # rival's ou
897 if (sente) # rival is gote
898 if ((rival_ou.y != 9) &&
899 (@array[rival_ou.x][rival_ou.y + 1]) &&
900 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
901 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
903 fu_y = rival_ou.y + 1
908 if ((rival_ou.y != 0) &&
909 (@array[rival_ou.x][rival_ou.y - 1]) &&
910 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
911 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
913 fu_y = rival_ou.y - 1
919 ## case: rival_ou is moving
921 rival_ou.movable_grids.each do |(cand_x, cand_y)|
922 tmp_board = Marshal.load(Marshal.dump(self))
923 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
924 raise "internal error" if (s != true)
925 if (! tmp_board.checkmated?(! sente)) # good move
930 ## case: rival is capturing fu
936 (@array[x][y].sente != sente) &&
937 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
938 if (@array[x][y].promoted)
939 name = @array[x][y].promoted_name
941 name = @array[x][y].name
943 tmp_board = Marshal.load(Marshal.dump(self))
944 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
945 raise "internal error" if (s != true)
946 if (! tmp_board.checkmated?(! sente)) # good move
957 def oute_sennichite?(sente)
958 if (checkmated?(! sente))
961 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
965 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
973 def sennichite?(sente)
975 if (@history[str] && (@history[str] >= 3)) # already 3 times
981 def good_kachi?(sente)
982 return false if (checkmated?(sente))
983 ou = look_for_ou(sente)
984 return false if (sente && (ou.y >= 4))
985 return false if (! sente && (ou.y <= 6))
1001 (@array[x][y].sente == sente) &&
1002 (@array[x][y].point > 0))
1003 point = point + @array[x][y].point
1009 hands.each do |piece|
1010 point = point + piece.point
1013 return false if (number < 10)
1015 return false if (point < 28)
1017 return false if (point < 27)
1022 def handle_one_move(str)
1023 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1030 elsif (str =~ /^%KACHI/)
1031 if (@sente == @current_player)
1036 if (good_kachi?(sente))
1041 elsif (str =~ /^%TORYO/)
1047 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1048 ((x0 != 0) || (y0 != 0)))
1050 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1056 hands = @sente_hands
1063 if ((x0 == 0) && (y0 == 0))
1064 return :illegal if (! have_piece?(hands, name))
1065 elsif (! @array[x0][y0])
1066 return :illegal # no piece
1067 elsif (@array[x0][y0].sente != sente)
1068 return :illegal # this is not mine
1069 elsif (@array[x0][y0].name != name)
1070 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1073 ## destination check
1074 if (@array[x1][y1] &&
1075 (@array[x1][y1].sente == sente)) # can't capture mine
1077 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1078 return :illegal # can't put on existing piece
1081 tmp_board = Marshal.load(Marshal.dump(self))
1082 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1083 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1084 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1085 return :sennichite if tmp_board.sennichite?(sente)
1087 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1091 move_to(x0, y0, x1, y1, name, sente)
1094 if (checkmated?(! sente))
1096 @sente_history[str] = (@sente_history[str] || 0) + 1
1098 @gote_history[str] = (@gote_history[str] || 0) + 1
1102 @sente_history.clear
1107 @history[str] = (@history[str] || 0) + 1
1115 a.push(sprintf("P%d", y))
1118 piece = @array[x][y]
1127 a.push(sprintf("\n"))
1130 if (! sente_hands.empty?)
1132 sente_hands.each do |p|
1133 a.push("00" + p.name)
1137 if (! gote_hands.empty?)
1139 gote_hands.each do |p|
1140 a.push("00" + p.name)
1150 def initialize(game_name, player0, player1)
1151 @monitors = Array::new
1152 @game_name = game_name
1153 if (@game_name =~ /-(\d+)-(\d+)$/)
1154 @total_time = $1.to_i
1165 @current_player = @sente
1166 @next_player = @gote
1174 @sente.status = "agree_waiting"
1175 @gote.status = "agree_waiting"
1176 @id = sprintf("%s+%s+%s+%s+%s",
1177 LEAGUE.event, @game_name, @sente.name, @gote.name,
1178 Time::new.strftime("%Y%m%d%H%M%S"))
1180 LEAGUE.games[@id] = self
1183 log_message(sprintf("game created %s", @id))
1185 @logfile = @id + ".csa"
1193 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1194 attr_accessor :last_move, :current_turn
1196 def monitoron(monitor)
1197 @monitors.delete(monitor)
1198 @monitors.push(monitor)
1201 def monitoroff(monitor)
1202 @monitors.delete(monitor)
1205 def reject(rejector)
1206 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1207 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1212 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1214 elsif (@current_player == killer)
1221 log_message(sprintf("game finished %s", @id))
1222 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1227 @sente.status = "connected"
1228 @gote.status = "connected"
1230 if (@current_player.protocol == "CSA")
1231 @current_player.finish
1233 if (@next_player.protocol == "CSA")
1236 @monitors = Array::new
1239 @current_player = nil
1241 LEAGUE.games.delete(@id)
1244 def handle_one_move(str, player)
1246 if (@current_player == player)
1247 @end_time = Time::new
1248 t = (@end_time - @start_time).ceil
1249 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1252 if ((@current_player.mytime - t <= 0) && (@total_time > 0))
1254 elsif (str == :timeout)
1255 return false # time isn't expired. players aren't swapped. continue game
1258 move_status = @board.handle_one_move(str)
1260 # log_error("handle_one_move raise exception for #{str}")
1261 # move_status = :illegal
1263 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1264 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1266 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1267 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1268 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1269 @fh.printf("%s\nT%d\n", str, t)
1270 @last_move = sprintf("%s,T%d", str, t)
1271 @current_turn = @current_turn + 1
1273 @monitors.each do |monitor|
1274 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1275 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1280 if (@current_player.mytime - t < @byoyomi)
1281 @current_player.mytime = @byoyomi
1283 @current_player.mytime = @current_player.mytime - t
1286 if (@next_player.status != "game") # rival is logout or disconnected
1288 elsif (status == :timeout)
1290 elsif (move_status == :illegal)
1292 elsif (move_status == :kachi_win)
1294 elsif (move_status == :kachi_lose)
1296 elsif (move_status == :toryo)
1298 elsif (move_status == :outori)
1300 elsif (move_status == :sennichite)
1302 elsif (move_status == :oute_sennichite)
1303 oute_sennichite_lose()
1304 elsif (move_status == :uchifuzume)
1306 elsif (move_status == :oute_kaihimore)
1307 oute_kaihimore_lose()
1311 finish() if finish_flag
1312 (@current_player, @next_player) = [@next_player, @current_player]
1313 @start_time = Time::new
1319 @current_player.status = "connected"
1320 @next_player.status = "connected"
1321 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1322 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1323 @fh.printf("%%TORYO\n")
1324 @fh.print(@board.to_s.gsub(/^/, "\'"))
1325 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1326 @monitors.each do |monitor|
1327 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1332 @current_player.status = "connected"
1333 @next_player.status = "connected"
1334 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1335 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1336 @fh.printf("%%TORYO\n")
1337 @fh.print(@board.to_s.gsub(/^/, "\'"))
1338 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1339 @monitors.each do |monitor|
1340 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1345 @current_player.status = "connected"
1346 @next_player.status = "connected"
1347 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1348 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1349 @fh.print(@board.to_s.gsub(/^/, "\'"))
1350 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1351 @monitors.each do |monitor|
1352 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1356 def oute_sennichite_lose
1357 @current_player.status = "connected"
1358 @next_player.status = "connected"
1359 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1360 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1361 @fh.print(@board.to_s.gsub(/^/, "\'"))
1362 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1363 @monitors.each do |monitor|
1364 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1369 @current_player.status = "connected"
1370 @next_player.status = "connected"
1371 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1372 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1373 @fh.print(@board.to_s.gsub(/^/, "\'"))
1374 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1375 @monitors.each do |monitor|
1376 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1381 @current_player.status = "connected"
1382 @next_player.status = "connected"
1383 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1384 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1385 @fh.print(@board.to_s.gsub(/^/, "\'"))
1386 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1387 @monitors.each do |monitor|
1388 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1392 def oute_kaihimore_lose
1393 @current_player.status = "connected"
1394 @next_player.status = "connected"
1395 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1396 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1397 @fh.print(@board.to_s.gsub(/^/, "\'"))
1398 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1399 @monitors.each do |monitor|
1400 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1405 @current_player.status = "connected"
1406 @next_player.status = "connected"
1407 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1408 @next_player.write_safe("#TIME_UP\n#WIN\n")
1409 @fh.print(@board.to_s.gsub(/^/, "\'"))
1410 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1411 @monitors.each do |monitor|
1412 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1417 @current_player.status = "connected"
1418 @next_player.status = "connected"
1419 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1420 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1421 @fh.printf("%%KACHI\n")
1422 @fh.print(@board.to_s.gsub(/^/, "\'"))
1423 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1424 @monitors.each do |monitor|
1425 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1430 @current_player.status = "connected"
1431 @next_player.status = "connected"
1432 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1433 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1434 @fh.printf("%%KACHI\n")
1435 @fh.print(@board.to_s.gsub(/^/, "\'"))
1436 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1437 @monitors.each do |monitor|
1438 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1443 @current_player.status = "connected"
1444 @next_player.status = "connected"
1445 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1446 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1447 @fh.printf("%%TORYO\n")
1448 @fh.print(@board.to_s.gsub(/^/, "\'"))
1449 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1450 @monitors.each do |monitor|
1451 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1456 @current_player.status = "connected"
1457 @next_player.status = "connected"
1458 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1459 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1460 @fh.print(@board.to_s.gsub(/^/, "\'"))
1461 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1462 @monitors.each do |monitor|
1463 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1468 log_message(sprintf("game started %s", @id))
1469 @sente.write_safe(sprintf("START:%s\n", @id))
1470 @gote.write_safe(sprintf("START:%s\n", @id))
1471 @sente.mytime = @total_time
1472 @gote.mytime = @total_time
1473 @start_time = Time::new
1478 @fh = open(@logfile, "w")
1482 @fh.printf("N+%s\n", @sente.name)
1483 @fh.printf("N-%s\n", @gote.name)
1484 @fh.printf("$EVENT:%s\n", @id)
1486 @sente.write_safe(propose_message("+"))
1487 @gote.write_safe(propose_message("-"))
1489 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1491 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1492 P2 * -HI * * * * * -KA *
1493 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1494 P4 * * * * * * * * *
1495 P5 * * * * * * * * *
1496 P6 * * * * * * * * *
1497 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1498 P8 * +KA * * * * * +HI *
1499 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1508 Protocol_Version:1.0
1509 Protocol_Mode:Server
1512 Name+:#{@sente.name}
1518 Total_Time:#{@total_time}
1520 Least_Time_Per_Move:#{Least_Time_Per_Move}
1521 Remaining_Time+:#{@sente.mytime}
1522 Remaining_Time-:#{@gote.mytime}
1523 Last_Move:#{@last_move}
1524 Current_Turn:#{@current_turn}
1527 Jishogi_Declaration:1.1
1535 return str0 + @board.to_s + str1
1538 def propose_message(sg_flag)
1541 Protocol_Version:1.0
1542 Protocol_Mode:Server
1545 Name+:#{@sente.name}
1547 Your_Turn:#{sg_flag}
1552 Total_Time:#{@total_time}
1554 Least_Time_Per_Move:#{Least_Time_Per_Move}
1557 Jishogi_Declaration:1.1
1558 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1559 P2 * -HI * * * * * -KA *
1560 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1561 P4 * * * * * * * * *
1562 P5 * * * * * * * * *
1563 P6 * * * * * * * * *
1564 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1565 P8 * +KA * * * * * +HI *
1566 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1580 shogi-server - server for CSA server protocol
1583 shogi-server event_name port_number
1586 server for CSA server protocol
1590 specify filename for logging process ID
1593 this file is distributed under GPL version2 and might be compiled by Exerb
1605 def log_message(str)
1606 printf("%s message: %s\n", Time::new.to_s, str)
1609 def log_warning(str)
1610 printf("%s warning: %s\n", Time::new.to_s, str)
1614 printf("%s error: %s\n", Time::new.to_s, str)
1618 def parse_command_line
1620 parser = GetoptLong.new
1621 parser.ordering = GetoptLong::REQUIRE_ORDER
1623 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1627 parser.each_option do |name, arg|
1628 name.sub!(/^--/, '')
1629 options[name] = arg.dup
1633 raise parser.error_message
1638 def good_game_name?(str)
1639 if ((str =~ /^(.+)-\d+-\d+$/) &&
1640 (good_identifier?($1)))
1647 def good_identifier?(str)
1648 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1649 (str.length < Max_Identifier_Length))
1656 def good_login?(str)
1658 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1659 (tokens[0] == "LOGIN") &&
1660 (good_identifier?(tokens[1])))
1667 def write_pid_file(file)
1668 open(file, "w") do |fh|
1669 fh.print Process::pid, "\n"
1673 def mutex_watchdog(mutex, sec)
1682 log_error("mutex watchdog timeout")
1691 mutex_watchdog($mutex, 10)
1694 $options = parse_command_line
1695 if (ARGV.length != 2)
1700 LEAGUE.event = ARGV.shift
1703 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1706 Thread.abort_on_exception = true
1708 server = TCPserver.open(port)
1709 log_message("server started")
1712 Thread::start(server.accept) do |client|
1715 while (str = client.gets_timeout(Login_Time))
1720 if (good_login?(str))
1721 player = Player::new(str, client)
1722 if (LEAGUE.players[player.name])
1723 if ((LEAGUE.players[player.name].password == player.password) &&
1724 (LEAGUE.players[player.name].status != "game"))
1725 log_message(sprintf("user %s login forcely", player.name))
1726 LEAGUE.players[player.name].kill
1728 client.write_safe("LOGIN:incorrect" + eol)
1729 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1737 client.write_safe("LOGIN:incorrect" + eol)
1738 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1748 log_message(sprintf("user %s login", player.name))
1753 player.game.kill(player)
1756 LEAGUE.delete(player)
1757 log_message(sprintf("user %s logout", player.name))
1766 LEAGUE = League::new