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 <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1261 elsif (str == :timeout)
1262 return false # time isn't expired. players aren't swapped. continue game
1264 @current_player.mytime = @current_player.mytime - t
1265 if (@current_player.mytime < 0)
1266 @current_player.mytime = 0
1270 move_status = @board.handle_one_move(str)
1272 # log_error("handle_one_move raise exception for #{str}")
1273 # move_status = :illegal
1276 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1277 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1279 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1280 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1281 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1282 @fh.printf("%s\nT%d\n", str, t)
1283 @last_move = sprintf("%s,T%d", str, t)
1284 @current_turn = @current_turn + 1
1287 @monitors.each do |monitor|
1288 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1289 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1294 if (@next_player.status != "game") # rival is logout or disconnected
1296 elsif (status == :timeout)
1298 elsif (move_status == :illegal)
1300 elsif (move_status == :kachi_win)
1302 elsif (move_status == :kachi_lose)
1304 elsif (move_status == :toryo)
1306 elsif (move_status == :outori)
1308 elsif (move_status == :sennichite)
1310 elsif (move_status == :oute_sennichite)
1311 oute_sennichite_lose()
1312 elsif (move_status == :uchifuzume)
1314 elsif (move_status == :oute_kaihimore)
1315 oute_kaihimore_lose()
1319 finish() if finish_flag
1320 (@current_player, @next_player) = [@next_player, @current_player]
1321 @start_time = Time::new
1327 @current_player.status = "connected"
1328 @next_player.status = "connected"
1329 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1330 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1331 @fh.printf("%%TORYO\n")
1332 @fh.print(@board.to_s.gsub(/^/, "\'"))
1333 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1334 @monitors.each do |monitor|
1335 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1340 @current_player.status = "connected"
1341 @next_player.status = "connected"
1342 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1343 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1344 @fh.printf("%%TORYO\n")
1345 @fh.print(@board.to_s.gsub(/^/, "\'"))
1346 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1347 @monitors.each do |monitor|
1348 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1353 @current_player.status = "connected"
1354 @next_player.status = "connected"
1355 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1356 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1357 @fh.print(@board.to_s.gsub(/^/, "\'"))
1358 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1359 @monitors.each do |monitor|
1360 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1364 def oute_sennichite_lose
1365 @current_player.status = "connected"
1366 @next_player.status = "connected"
1367 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1368 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1369 @fh.print(@board.to_s.gsub(/^/, "\'"))
1370 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1371 @monitors.each do |monitor|
1372 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1377 @current_player.status = "connected"
1378 @next_player.status = "connected"
1379 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1380 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1381 @fh.print(@board.to_s.gsub(/^/, "\'"))
1382 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1383 @monitors.each do |monitor|
1384 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1389 @current_player.status = "connected"
1390 @next_player.status = "connected"
1391 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1392 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1393 @fh.print(@board.to_s.gsub(/^/, "\'"))
1394 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1395 @monitors.each do |monitor|
1396 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1400 def oute_kaihimore_lose
1401 @current_player.status = "connected"
1402 @next_player.status = "connected"
1403 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1404 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1405 @fh.print(@board.to_s.gsub(/^/, "\'"))
1406 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1407 @monitors.each do |monitor|
1408 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1413 @current_player.status = "connected"
1414 @next_player.status = "connected"
1415 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1416 @next_player.write_safe("#TIME_UP\n#WIN\n")
1417 @fh.print(@board.to_s.gsub(/^/, "\'"))
1418 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1419 @monitors.each do |monitor|
1420 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1425 @current_player.status = "connected"
1426 @next_player.status = "connected"
1427 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1428 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1429 @fh.printf("%%KACHI\n")
1430 @fh.print(@board.to_s.gsub(/^/, "\'"))
1431 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1432 @monitors.each do |monitor|
1433 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1438 @current_player.status = "connected"
1439 @next_player.status = "connected"
1440 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1441 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1442 @fh.printf("%%KACHI\n")
1443 @fh.print(@board.to_s.gsub(/^/, "\'"))
1444 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1445 @monitors.each do |monitor|
1446 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1451 @current_player.status = "connected"
1452 @next_player.status = "connected"
1453 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1454 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1455 @fh.printf("%%TORYO\n")
1456 @fh.print(@board.to_s.gsub(/^/, "\'"))
1457 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1458 @monitors.each do |monitor|
1459 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1464 @current_player.status = "connected"
1465 @next_player.status = "connected"
1466 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1467 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1468 @fh.print(@board.to_s.gsub(/^/, "\'"))
1469 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1470 @monitors.each do |monitor|
1471 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1476 log_message(sprintf("game started %s", @id))
1477 @sente.write_safe(sprintf("START:%s\n", @id))
1478 @gote.write_safe(sprintf("START:%s\n", @id))
1479 @sente.mytime = @total_time
1480 @gote.mytime = @total_time
1481 @start_time = Time::new
1486 @fh = open(@logfile, "w")
1490 @fh.printf("N+%s\n", @sente.name)
1491 @fh.printf("N-%s\n", @gote.name)
1492 @fh.printf("$EVENT:%s\n", @id)
1494 @sente.write_safe(propose_message("+"))
1495 @gote.write_safe(propose_message("-"))
1497 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1499 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1500 P2 * -HI * * * * * -KA *
1501 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1502 P4 * * * * * * * * *
1503 P5 * * * * * * * * *
1504 P6 * * * * * * * * *
1505 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1506 P8 * +KA * * * * * +HI *
1507 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1516 Protocol_Version:1.0
1517 Protocol_Mode:Server
1520 Name+:#{@sente.name}
1526 Total_Time:#{@total_time}
1528 Least_Time_Per_Move:#{Least_Time_Per_Move}
1529 Remaining_Time+:#{@sente.mytime}
1530 Remaining_Time-:#{@gote.mytime}
1531 Last_Move:#{@last_move}
1532 Current_Turn:#{@current_turn}
1535 Jishogi_Declaration:1.1
1543 return str0 + @board.to_s + str1
1546 def propose_message(sg_flag)
1549 Protocol_Version:1.0
1550 Protocol_Mode:Server
1553 Name+:#{@sente.name}
1555 Your_Turn:#{sg_flag}
1560 Total_Time:#{@total_time}
1562 Least_Time_Per_Move:#{Least_Time_Per_Move}
1565 Jishogi_Declaration:1.1
1566 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1567 P2 * -HI * * * * * -KA *
1568 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1569 P4 * * * * * * * * *
1570 P5 * * * * * * * * *
1571 P6 * * * * * * * * *
1572 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1573 P8 * +KA * * * * * +HI *
1574 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1588 shogi-server - server for CSA server protocol
1591 shogi-server event_name port_number
1594 server for CSA server protocol
1598 specify filename for logging process ID
1601 this file is distributed under GPL version2 and might be compiled by Exerb
1613 def log_message(str)
1614 printf("%s message: %s\n", Time::new.to_s, str)
1617 def log_warning(str)
1618 printf("%s warning: %s\n", Time::new.to_s, str)
1622 printf("%s error: %s\n", Time::new.to_s, str)
1626 def parse_command_line
1628 parser = GetoptLong.new
1629 parser.ordering = GetoptLong::REQUIRE_ORDER
1631 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1635 parser.each_option do |name, arg|
1636 name.sub!(/^--/, '')
1637 options[name] = arg.dup
1641 raise parser.error_message
1646 def good_game_name?(str)
1647 if ((str =~ /^(.+)-\d+-\d+$/) &&
1648 (good_identifier?($1)))
1655 def good_identifier?(str)
1656 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1657 (str.length < Max_Identifier_Length))
1664 def good_login?(str)
1666 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1667 (tokens[0] == "LOGIN") &&
1668 (good_identifier?(tokens[1])))
1675 def write_pid_file(file)
1676 open(file, "w") do |fh|
1677 fh.print Process::pid, "\n"
1681 def mutex_watchdog(mutex, sec)
1690 log_error("mutex watchdog timeout")
1699 mutex_watchdog($mutex, 10)
1702 $options = parse_command_line
1703 if (ARGV.length != 2)
1708 LEAGUE.event = ARGV.shift
1711 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1714 Thread.abort_on_exception = true
1716 server = TCPserver.open(port)
1717 log_message("server started")
1720 Thread::start(server.accept) do |client|
1723 while (str = client.gets_timeout(Login_Time))
1728 if (good_login?(str))
1729 player = Player::new(str, client)
1730 if (LEAGUE.players[player.name])
1731 if ((LEAGUE.players[player.name].password == player.password) &&
1732 (LEAGUE.players[player.name].status != "game"))
1733 log_message(sprintf("user %s login forcely", player.name))
1734 LEAGUE.players[player.name].kill
1736 client.write_safe("LOGIN:incorrect" + eol)
1737 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1745 client.write_safe("LOGIN:incorrect" + eol)
1746 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1756 log_message(sprintf("user %s login", player.name))
1761 player.game.kill(player)
1764 LEAGUE.delete(player)
1765 log_message(sprintf("user %s logout", player.name))
1774 LEAGUE = League::new