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 if (good_game_name?(@password))
212 csa_1st_str = "%%GAME #{@password} *"
214 csa_1st_str = "%%GAME #{Default_Game_Name} *"
218 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
225 if (@write_queue.size > Max_Write_Queue_Size)
226 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
230 if (@status == "finished")
233 str.chomp! if (str.class == String)
235 when /^[\+\-%][^%]/, :timeout
236 if (@status == "game")
237 s = @game.handle_one_move(str, self)
238 return if (s && @protocol == "CSA")
241 if (@status == "agree_waiting")
243 return if (@protocol == "CSA")
245 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
248 if (@status == "agree_waiting")
249 @status = "start_waiting"
250 if ((@game.sente.status == "start_waiting") &&
251 (@game.gote.status == "start_waiting"))
253 @game.sente.status = "game"
254 @game.gote.status = "game"
257 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
259 when /^%%SHOW\s+(\S+)/
261 if (LEAGUE.games[game_id])
262 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
264 write_safe("##[SHOW] +OK\n")
265 when /^%%MONITORON\s+(\S+)/
267 if (LEAGUE.games[game_id])
268 LEAGUE.games[game_id].monitoron(self)
269 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
270 write_safe("##[MONITOR][#{game_id}] +OK\n")
272 when /^%%MONITOROFF\s+(\S+)/
274 if (LEAGUE.games[game_id])
275 LEAGUE.games[game_id].monitoroff(self)
280 if ((@status == "connected") || (@status == "game_waiting"))
281 @status = "connected"
284 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
286 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
290 if (! good_game_name?(game_name))
291 write_safe(sprintf("##[ERROR] bad game name\n"))
293 elsif ((@status == "connected") || (@status == "game_waiting"))
296 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
299 if ((my_sente_str == "*") ||
300 (my_sente_str == "+") ||
301 (my_sente_str == "-"))
304 write_safe(sprintf("##[ERROR] bad game option\n"))
308 if (my_sente_str == "*")
309 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
310 elsif (my_sente_str == "+")
311 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
312 elsif (my_sente_str == "-")
313 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
318 @game_name = game_name
319 if ((my_sente_str == "*") && (rival.sente == nil))
327 elsif (rival.sente == true) # rival has higher priority
329 elsif (rival.sente == false)
331 elsif (my_sente_str == "+")
334 elsif (my_sente_str == "-")
340 Game::new(@game_name, self, rival)
341 self.status = "agree_waiting"
342 rival.status = "agree_waiting"
343 else # rival not found
344 if (command_name == "GAME")
345 @status = "game_waiting"
346 @game_name = game_name
347 if (my_sente_str == "+")
349 elsif (my_sente_str == "-")
355 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
356 @status = "connected"
361 when /^%%CHAT\s+(.+)/
363 LEAGUE.players.each do |name, player|
364 if (player.protocol != "CSA")
365 player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
370 LEAGUE.games.each do |id, game|
371 buf.push(sprintf("##[LIST] %s\n", id))
373 buf.push("##[LIST] +OK\n")
377 LEAGUE.players.each do |name, player|
378 buf.push(sprintf("##[WHO] %s\n", player.to_s))
380 buf.push("##[WHO] +OK\n")
383 @status = "connected"
384 write_safe("LOGOUT:completed\n")
387 ## ignore null string
389 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
399 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
400 def initialize(board, x, y, sente, promoted=false)
407 if ((x == 0) || (y == 0))
409 hands = board.sente_hands
411 hands = board.gote_hands
418 @board.array[x][y] = self
421 attr_accessor :promoted, :sente, :x, :y, :board
423 def room_of_head?(x, y, name)
428 return adjacent_movable_grids + far_movable_grids
431 def far_movable_grids
436 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
437 if ((@board.array[x][y] == nil) || # dst is empty
438 (@board.array[x][y].sente != @sente)) # dst is enemy
446 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
447 if (@board.array[x][y] == nil) # dst is empty?
454 def adjacent_movable_grids
457 moves = @promoted_moves
459 moves = @normal_moves
461 moves.each do |(dx, dy)|
468 if (jump_to?(cand_x, cand_y))
469 grids.push([cand_x, cand_y])
475 def move_to?(x, y, name)
476 return false if (! room_of_head?(x, y, name))
477 return false if ((name != @name) && (name != @promoted_name))
478 return false if (@promoted && (name != @promoted_name)) # can't un-promote
481 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
483 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
485 return false if ((6 >= @y) && (6 >= y) && (name != @name))
489 if ((@x == 0) || (@y == 0))
490 return jump_to?(x, y)
492 return movable_grids.include?([x, y])
497 if ((@x == 0) || (@y == 0))
499 @board.sente_hands.delete(self)
501 @board.gote_hands.delete(self)
503 @board.array[x][y] = self
504 elsif ((x == 0) || (y == 0))
505 @promoted = false # clear promoted flag before moving to hands
507 @board.sente_hands.push(self)
509 @board.gote_hands.push(self)
511 @board.array[@x][@y] = nil
513 @board.array[@x][@y] = nil
514 @board.array[x][y] = self
547 class PieceFU < Piece
550 @normal_moves = [[0, +1]]
551 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
553 @promoted_name = "TO"
556 def room_of_head?(x, y, name)
559 return false if (y == 1)
561 return false if (y == 9)
567 if ((iy != @y) && # not source position
568 @board.array[x][iy] &&
569 (@board.array[x][iy].sente == @sente) && # mine
570 (@board.array[x][iy].name == "FU") &&
571 (@board.array[x][iy].promoted == false))
581 class PieceKY < Piece
585 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
587 @promoted_name = "NY"
590 def room_of_head?(x, y, name)
593 return false if (y == 1)
595 return false if (y == 9)
600 def far_movable_grids
608 while (jump_to?(cand_x, cand_y))
609 grids.push([cand_x, cand_y])
610 break if (! put_to?(cand_x, cand_y))
616 while (jump_to?(cand_x, cand_y))
617 grids.push([cand_x, cand_y])
618 break if (! put_to?(cand_x, cand_y))
626 class PieceKE < Piece
629 @normal_moves = [[+1, +2], [-1, +2]]
630 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
632 @promoted_name = "NK"
635 def room_of_head?(x, y, name)
638 return false if ((y == 1) || (y == 2))
640 return false if ((y == 9) || (y == 8))
646 class PieceGI < Piece
649 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
650 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
652 @promoted_name = "NG"
656 class PieceKI < Piece
659 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
666 class PieceKA < Piece
670 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
672 @promoted_name = "UM"
675 def far_movable_grids
680 while (jump_to?(cand_x, cand_y))
681 grids.push([cand_x, cand_y])
682 break if (! put_to?(cand_x, cand_y))
689 while (jump_to?(cand_x, cand_y))
690 grids.push([cand_x, cand_y])
691 break if (! put_to?(cand_x, cand_y))
698 while (jump_to?(cand_x, cand_y))
699 grids.push([cand_x, cand_y])
700 break if (! put_to?(cand_x, cand_y))
707 while (jump_to?(cand_x, cand_y))
708 grids.push([cand_x, cand_y])
709 break if (! put_to?(cand_x, cand_y))
716 class PieceHI < Piece
720 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
722 @promoted_name = "RY"
725 def far_movable_grids
730 while (jump_to?(cand_x, cand_y))
731 grids.push([cand_x, cand_y])
732 break if (! put_to?(cand_x, cand_y))
738 while (jump_to?(cand_x, cand_y))
739 grids.push([cand_x, cand_y])
740 break if (! put_to?(cand_x, cand_y))
746 while (jump_to?(cand_x, cand_y))
747 grids.push([cand_x, cand_y])
748 break if (! put_to?(cand_x, cand_y))
754 while (jump_to?(cand_x, cand_y))
755 grids.push([cand_x, cand_y])
756 break if (! put_to?(cand_x, cand_y))
762 class PieceOU < Piece
765 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
775 @sente_hands = Array::new
776 @gote_hands = Array::new
778 @sente_history = Hash::new
779 @gote_history = Hash::new
780 @array = [[], [], [], [], [], [], [], [], [], []]
782 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
785 PieceKY::new(self, 1, 1, false)
786 PieceKE::new(self, 2, 1, false)
787 PieceGI::new(self, 3, 1, false)
788 PieceKI::new(self, 4, 1, false)
789 PieceOU::new(self, 5, 1, false)
790 PieceKI::new(self, 6, 1, false)
791 PieceGI::new(self, 7, 1, false)
792 PieceKE::new(self, 8, 1, false)
793 PieceKY::new(self, 9, 1, false)
794 PieceKA::new(self, 2, 2, false)
795 PieceHI::new(self, 8, 2, false)
796 PieceFU::new(self, 1, 3, false)
797 PieceFU::new(self, 2, 3, false)
798 PieceFU::new(self, 3, 3, false)
799 PieceFU::new(self, 4, 3, false)
800 PieceFU::new(self, 5, 3, false)
801 PieceFU::new(self, 6, 3, false)
802 PieceFU::new(self, 7, 3, false)
803 PieceFU::new(self, 8, 3, false)
804 PieceFU::new(self, 9, 3, false)
806 PieceKY::new(self, 1, 9, true)
807 PieceKE::new(self, 2, 9, true)
808 PieceGI::new(self, 3, 9, true)
809 PieceKI::new(self, 4, 9, true)
810 PieceOU::new(self, 5, 9, true)
811 PieceKI::new(self, 6, 9, true)
812 PieceGI::new(self, 7, 9, true)
813 PieceKE::new(self, 8, 9, true)
814 PieceKY::new(self, 9, 9, true)
815 PieceKA::new(self, 8, 8, true)
816 PieceHI::new(self, 2, 8, true)
817 PieceFU::new(self, 1, 7, true)
818 PieceFU::new(self, 2, 7, true)
819 PieceFU::new(self, 3, 7, true)
820 PieceFU::new(self, 4, 7, true)
821 PieceFU::new(self, 5, 7, true)
822 PieceFU::new(self, 6, 7, true)
823 PieceFU::new(self, 7, 7, true)
824 PieceFU::new(self, 8, 7, true)
825 PieceFU::new(self, 9, 7, true)
828 def have_piece?(hands, name)
829 piece = hands.find { |i|
835 def move_to(x0, y0, x1, y1, name, sente)
842 if ((x0 == 0) || (y0 == 0))
843 piece = have_piece?(hands, name)
844 return :illegal if (! piece.move_to?(x1, y1, name))
845 piece.move_to(x1, y1)
847 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
848 if (@array[x0][y0].name != name) # promoted ?
849 @array[x0][y0].promoted = true
852 if (@array[x1][y1].name == "OU")
853 return :outori # return board update
855 @array[x1][y1].sente = @array[x0][y0].sente
856 @array[x1][y1].move_to(0, 0)
861 @array[x0][y0].move_to(x1, y1)
866 def look_for_ou(sente)
872 (@array[x][y].name == "OU") &&
873 (@array[x][y].sente == sente))
880 raise "can't find ou"
883 def checkmated?(sente) # sente is loosing
884 ou = look_for_ou(sente)
890 (@array[x][y].sente != sente))
891 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
902 def uchifuzume?(sente)
903 rival_ou = look_for_ou(! sente) # rival's ou
904 if (sente) # rival is gote
905 if ((rival_ou.y != 9) &&
906 (@array[rival_ou.x][rival_ou.y + 1]) &&
907 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
908 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
910 fu_y = rival_ou.y + 1
915 if ((rival_ou.y != 0) &&
916 (@array[rival_ou.x][rival_ou.y - 1]) &&
917 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
918 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
920 fu_y = rival_ou.y - 1
926 ## case: rival_ou is moving
928 rival_ou.movable_grids.each do |(cand_x, cand_y)|
929 tmp_board = Marshal.load(Marshal.dump(self))
930 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
931 raise "internal error" if (s != true)
932 if (! tmp_board.checkmated?(! sente)) # good move
937 ## case: rival is capturing fu
943 (@array[x][y].sente != sente) &&
944 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
945 if (@array[x][y].promoted)
946 name = @array[x][y].promoted_name
948 name = @array[x][y].name
950 tmp_board = Marshal.load(Marshal.dump(self))
951 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
952 raise "internal error" if (s != true)
953 if (! tmp_board.checkmated?(! sente)) # good move
964 def oute_sennichite?(sente)
965 if (checkmated?(! sente))
968 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
972 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
980 def sennichite?(sente)
982 if (@history[str] && (@history[str] >= 3)) # already 3 times
988 def good_kachi?(sente)
989 return false if (checkmated?(sente))
990 ou = look_for_ou(sente)
991 return false if (sente && (ou.y >= 4))
992 return false if (! sente && (ou.y <= 6))
1008 (@array[x][y].sente == sente) &&
1009 (@array[x][y].point > 0))
1010 point = point + @array[x][y].point
1016 hands.each do |piece|
1017 point = point + piece.point
1020 return false if (number < 10)
1022 return false if (point < 28)
1024 return false if (point < 27)
1029 def handle_one_move(str)
1030 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1037 elsif (str =~ /^%KACHI/)
1038 if (@sente == @current_player)
1043 if (good_kachi?(sente))
1048 elsif (str =~ /^%TORYO/)
1054 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1055 ((x0 != 0) || (y0 != 0)))
1057 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1063 hands = @sente_hands
1070 if ((x0 == 0) && (y0 == 0))
1071 return :illegal if (! have_piece?(hands, name))
1072 elsif (! @array[x0][y0])
1073 return :illegal # no piece
1074 elsif (@array[x0][y0].sente != sente)
1075 return :illegal # this is not mine
1076 elsif (@array[x0][y0].name != name)
1077 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1080 ## destination check
1081 if (@array[x1][y1] &&
1082 (@array[x1][y1].sente == sente)) # can't capture mine
1084 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1085 return :illegal # can't put on existing piece
1088 tmp_board = Marshal.load(Marshal.dump(self))
1089 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1090 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1091 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1092 return :sennichite if tmp_board.sennichite?(sente)
1094 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1098 move_to(x0, y0, x1, y1, name, sente)
1101 if (checkmated?(! sente))
1103 @sente_history[str] = (@sente_history[str] || 0) + 1
1105 @gote_history[str] = (@gote_history[str] || 0) + 1
1109 @sente_history.clear
1114 @history[str] = (@history[str] || 0) + 1
1122 a.push(sprintf("P%d", y))
1125 piece = @array[x][y]
1134 a.push(sprintf("\n"))
1137 if (! sente_hands.empty?)
1139 sente_hands.each do |p|
1140 a.push("00" + p.name)
1144 if (! gote_hands.empty?)
1146 gote_hands.each do |p|
1147 a.push("00" + p.name)
1157 def initialize(game_name, player0, player1)
1158 @monitors = Array::new
1159 @game_name = game_name
1160 if (@game_name =~ /-(\d+)-(\d+)$/)
1161 @total_time = $1.to_i
1172 @current_player = @sente
1173 @next_player = @gote
1181 @sente.status = "agree_waiting"
1182 @gote.status = "agree_waiting"
1183 @id = sprintf("%s+%s+%s+%s+%s",
1184 LEAGUE.event, @game_name, @sente.name, @gote.name,
1185 Time::new.strftime("%Y%m%d%H%M%S"))
1187 LEAGUE.games[@id] = self
1190 log_message(sprintf("game created %s", @id))
1192 @logfile = @id + ".csa"
1200 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1201 attr_accessor :last_move, :current_turn
1203 def monitoron(monitor)
1204 @monitors.delete(monitor)
1205 @monitors.push(monitor)
1208 def monitoroff(monitor)
1209 @monitors.delete(monitor)
1212 def reject(rejector)
1213 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1214 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1219 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1221 elsif (@current_player == killer)
1228 log_message(sprintf("game finished %s", @id))
1229 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1234 @sente.status = "connected"
1235 @gote.status = "connected"
1237 if (@current_player.protocol == "CSA")
1238 @current_player.finish
1240 if (@next_player.protocol == "CSA")
1243 @monitors = Array::new
1246 @current_player = nil
1248 LEAGUE.games.delete(@id)
1251 def handle_one_move(str, player)
1253 if (@current_player == player)
1254 @end_time = Time::new
1255 t = (@end_time - @start_time).ceil
1256 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1259 if ((@current_player.mytime - t <= 0) && (@total_time > 0))
1261 elsif (str == :timeout)
1262 return false # time isn't expired. players aren't swapped. continue game
1264 if (@current_player.mytime - t < @byoyomi)
1265 @current_player.mytime = @byoyomi
1267 @current_player.mytime = @current_player.mytime - t
1271 move_status = @board.handle_one_move(str)
1273 # log_error("handle_one_move raise exception for #{str}")
1274 # move_status = :illegal
1277 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1278 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1280 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1281 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1282 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1283 @fh.printf("%s\nT%d\n", str, t)
1284 @last_move = sprintf("%s,T%d", str, t)
1285 @current_turn = @current_turn + 1
1288 @monitors.each do |monitor|
1289 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1290 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1295 if (@next_player.status != "game") # rival is logout or disconnected
1297 elsif (status == :timeout)
1299 elsif (move_status == :illegal)
1301 elsif (move_status == :kachi_win)
1303 elsif (move_status == :kachi_lose)
1305 elsif (move_status == :toryo)
1307 elsif (move_status == :outori)
1309 elsif (move_status == :sennichite)
1311 elsif (move_status == :oute_sennichite)
1312 oute_sennichite_lose()
1313 elsif (move_status == :uchifuzume)
1315 elsif (move_status == :oute_kaihimore)
1316 oute_kaihimore_lose()
1320 finish() if finish_flag
1321 (@current_player, @next_player) = [@next_player, @current_player]
1322 @start_time = Time::new
1328 @current_player.status = "connected"
1329 @next_player.status = "connected"
1330 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1331 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1332 @fh.printf("%%TORYO\n")
1333 @fh.print(@board.to_s.gsub(/^/, "\'"))
1334 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1335 @monitors.each do |monitor|
1336 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1341 @current_player.status = "connected"
1342 @next_player.status = "connected"
1343 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1344 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1345 @fh.printf("%%TORYO\n")
1346 @fh.print(@board.to_s.gsub(/^/, "\'"))
1347 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1348 @monitors.each do |monitor|
1349 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1354 @current_player.status = "connected"
1355 @next_player.status = "connected"
1356 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1357 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1358 @fh.print(@board.to_s.gsub(/^/, "\'"))
1359 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1360 @monitors.each do |monitor|
1361 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1365 def oute_sennichite_lose
1366 @current_player.status = "connected"
1367 @next_player.status = "connected"
1368 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1369 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1370 @fh.print(@board.to_s.gsub(/^/, "\'"))
1371 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1372 @monitors.each do |monitor|
1373 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1378 @current_player.status = "connected"
1379 @next_player.status = "connected"
1380 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1381 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1382 @fh.print(@board.to_s.gsub(/^/, "\'"))
1383 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1384 @monitors.each do |monitor|
1385 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1390 @current_player.status = "connected"
1391 @next_player.status = "connected"
1392 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1393 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1394 @fh.print(@board.to_s.gsub(/^/, "\'"))
1395 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1396 @monitors.each do |monitor|
1397 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1401 def oute_kaihimore_lose
1402 @current_player.status = "connected"
1403 @next_player.status = "connected"
1404 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1405 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1406 @fh.print(@board.to_s.gsub(/^/, "\'"))
1407 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1408 @monitors.each do |monitor|
1409 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1414 @current_player.status = "connected"
1415 @next_player.status = "connected"
1416 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1417 @next_player.write_safe("#TIME_UP\n#WIN\n")
1418 @fh.print(@board.to_s.gsub(/^/, "\'"))
1419 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1420 @monitors.each do |monitor|
1421 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1426 @current_player.status = "connected"
1427 @next_player.status = "connected"
1428 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1429 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1430 @fh.printf("%%KACHI\n")
1431 @fh.print(@board.to_s.gsub(/^/, "\'"))
1432 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1433 @monitors.each do |monitor|
1434 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1439 @current_player.status = "connected"
1440 @next_player.status = "connected"
1441 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1442 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1443 @fh.printf("%%KACHI\n")
1444 @fh.print(@board.to_s.gsub(/^/, "\'"))
1445 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1446 @monitors.each do |monitor|
1447 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1452 @current_player.status = "connected"
1453 @next_player.status = "connected"
1454 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1455 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1456 @fh.printf("%%TORYO\n")
1457 @fh.print(@board.to_s.gsub(/^/, "\'"))
1458 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1459 @monitors.each do |monitor|
1460 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1465 @current_player.status = "connected"
1466 @next_player.status = "connected"
1467 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1468 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1469 @fh.print(@board.to_s.gsub(/^/, "\'"))
1470 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1471 @monitors.each do |monitor|
1472 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1477 log_message(sprintf("game started %s", @id))
1478 @sente.write_safe(sprintf("START:%s\n", @id))
1479 @gote.write_safe(sprintf("START:%s\n", @id))
1480 @sente.mytime = @total_time
1481 @gote.mytime = @total_time
1482 @start_time = Time::new
1487 @fh = open(@logfile, "w")
1491 @fh.printf("N+%s\n", @sente.name)
1492 @fh.printf("N-%s\n", @gote.name)
1493 @fh.printf("$EVENT:%s\n", @id)
1495 @sente.write_safe(propose_message("+"))
1496 @gote.write_safe(propose_message("-"))
1498 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1500 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1501 P2 * -HI * * * * * -KA *
1502 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1503 P4 * * * * * * * * *
1504 P5 * * * * * * * * *
1505 P6 * * * * * * * * *
1506 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1507 P8 * +KA * * * * * +HI *
1508 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1517 Protocol_Version:1.0
1518 Protocol_Mode:Server
1521 Name+:#{@sente.name}
1527 Total_Time:#{@total_time}
1529 Least_Time_Per_Move:#{Least_Time_Per_Move}
1530 Remaining_Time+:#{@sente.mytime}
1531 Remaining_Time-:#{@gote.mytime}
1532 Last_Move:#{@last_move}
1533 Current_Turn:#{@current_turn}
1536 Jishogi_Declaration:1.1
1544 return str0 + @board.to_s + str1
1547 def propose_message(sg_flag)
1550 Protocol_Version:1.0
1551 Protocol_Mode:Server
1554 Name+:#{@sente.name}
1556 Your_Turn:#{sg_flag}
1561 Total_Time:#{@total_time}
1563 Least_Time_Per_Move:#{Least_Time_Per_Move}
1566 Jishogi_Declaration:1.1
1567 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1568 P2 * -HI * * * * * -KA *
1569 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1570 P4 * * * * * * * * *
1571 P5 * * * * * * * * *
1572 P6 * * * * * * * * *
1573 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1574 P8 * +KA * * * * * +HI *
1575 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1589 shogi-server - server for CSA server protocol
1592 shogi-server event_name port_number
1595 server for CSA server protocol
1599 specify filename for logging process ID
1602 this file is distributed under GPL version2 and might be compiled by Exerb
1614 def log_message(str)
1615 printf("%s message: %s\n", Time::new.to_s, str)
1618 def log_warning(str)
1619 printf("%s warning: %s\n", Time::new.to_s, str)
1623 printf("%s error: %s\n", Time::new.to_s, str)
1627 def parse_command_line
1629 parser = GetoptLong.new
1630 parser.ordering = GetoptLong::REQUIRE_ORDER
1632 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1636 parser.each_option do |name, arg|
1637 name.sub!(/^--/, '')
1638 options[name] = arg.dup
1642 raise parser.error_message
1647 def good_game_name?(str)
1648 if ((str =~ /^(.+)-\d+-\d+$/) &&
1649 (good_identifier?($1)))
1656 def good_identifier?(str)
1657 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1658 (str.length < Max_Identifier_Length))
1665 def good_login?(str)
1667 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1668 (tokens[0] == "LOGIN") &&
1669 (good_identifier?(tokens[1])))
1676 def write_pid_file(file)
1677 open(file, "w") do |fh|
1678 fh.print Process::pid, "\n"
1682 def mutex_watchdog(mutex, sec)
1691 log_error("mutex watchdog timeout")
1700 mutex_watchdog($mutex, 10)
1703 $options = parse_command_line
1704 if (ARGV.length != 2)
1709 LEAGUE.event = ARGV.shift
1712 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1715 Thread.abort_on_exception = true
1717 server = TCPserver.open(port)
1718 log_message("server started")
1721 Thread::start(server.accept) do |client|
1724 while (str = client.gets_timeout(Login_Time))
1729 if (good_login?(str))
1730 player = Player::new(str, client)
1731 if (LEAGUE.players[player.name])
1732 if ((LEAGUE.players[player.name].password == player.password) &&
1733 (LEAGUE.players[player.name].status != "game"))
1734 log_message(sprintf("user %s login forcely", player.name))
1735 LEAGUE.players[player.name].kill
1737 client.write_safe("LOGIN:incorrect" + eol)
1738 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1746 client.write_safe("LOGIN:incorrect" + eol)
1747 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1757 log_message(sprintf("user %s login", player.name))
1762 player.game.kill(player)
1765 LEAGUE.delete(player)
1766 log_message(sprintf("user %s logout", player.name))
1775 LEAGUE = League::new