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)
236 if (@status == "game")
237 array_str = str.split(",")
238 move = array_str.shift
239 additional = array_str.shift
240 if /^'(.*)/ =~ additional
241 comment = array_str.unshift("'* #{$1}")
243 s = @game.handle_one_move(move, self)
244 @game.fh.print("#{comment}\n") if comment
245 return if (s && @protocol == "CSA")
247 when /^%[^%]/, :timeout
248 if (@status == "game")
249 s = @game.handle_one_move(str, self)
250 return if (s && @protocol == "CSA")
253 if (@status == "agree_waiting")
255 return if (@protocol == "CSA")
257 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
260 if (@status == "agree_waiting")
261 @status = "start_waiting"
262 if ((@game.sente.status == "start_waiting") &&
263 (@game.gote.status == "start_waiting"))
265 @game.sente.status = "game"
266 @game.gote.status = "game"
269 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
271 when /^%%SHOW\s+(\S+)/
273 if (LEAGUE.games[game_id])
274 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
276 write_safe("##[SHOW] +OK\n")
277 when /^%%MONITORON\s+(\S+)/
279 if (LEAGUE.games[game_id])
280 LEAGUE.games[game_id].monitoron(self)
281 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
282 write_safe("##[MONITOR][#{game_id}] +OK\n")
284 when /^%%MONITOROFF\s+(\S+)/
286 if (LEAGUE.games[game_id])
287 LEAGUE.games[game_id].monitoroff(self)
292 if ((@status == "connected") || (@status == "game_waiting"))
293 @status = "connected"
296 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
298 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
302 if (! good_game_name?(game_name))
303 write_safe(sprintf("##[ERROR] bad game name\n"))
305 elsif ((@status == "connected") || (@status == "game_waiting"))
308 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
311 if ((my_sente_str == "*") ||
312 (my_sente_str == "+") ||
313 (my_sente_str == "-"))
316 write_safe(sprintf("##[ERROR] bad game option\n"))
320 if (my_sente_str == "*")
321 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
322 elsif (my_sente_str == "+")
323 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
324 elsif (my_sente_str == "-")
325 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
330 @game_name = game_name
331 if ((my_sente_str == "*") && (rival.sente == nil))
339 elsif (rival.sente == true) # rival has higher priority
341 elsif (rival.sente == false)
343 elsif (my_sente_str == "+")
346 elsif (my_sente_str == "-")
352 Game::new(@game_name, self, rival)
353 self.status = "agree_waiting"
354 rival.status = "agree_waiting"
355 else # rival not found
356 if (command_name == "GAME")
357 @status = "game_waiting"
358 @game_name = game_name
359 if (my_sente_str == "+")
361 elsif (my_sente_str == "-")
367 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
368 @status = "connected"
373 when /^%%CHAT\s+(.+)/
375 LEAGUE.players.each do |name, player|
376 if (player.protocol != "CSA")
377 player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
382 LEAGUE.games.each do |id, game|
383 buf.push(sprintf("##[LIST] %s\n", id))
385 buf.push("##[LIST] +OK\n")
389 LEAGUE.players.each do |name, player|
390 buf.push(sprintf("##[WHO] %s\n", player.to_s))
392 buf.push("##[WHO] +OK\n")
395 @status = "connected"
396 write_safe("LOGOUT:completed\n")
399 ## ignore null string
401 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
411 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
412 def initialize(board, x, y, sente, promoted=false)
419 if ((x == 0) || (y == 0))
421 hands = board.sente_hands
423 hands = board.gote_hands
430 @board.array[x][y] = self
433 attr_accessor :promoted, :sente, :x, :y, :board
435 def room_of_head?(x, y, name)
440 return adjacent_movable_grids + far_movable_grids
443 def far_movable_grids
448 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
449 if ((@board.array[x][y] == nil) || # dst is empty
450 (@board.array[x][y].sente != @sente)) # dst is enemy
458 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
459 if (@board.array[x][y] == nil) # dst is empty?
466 def adjacent_movable_grids
469 moves = @promoted_moves
471 moves = @normal_moves
473 moves.each do |(dx, dy)|
480 if (jump_to?(cand_x, cand_y))
481 grids.push([cand_x, cand_y])
487 def move_to?(x, y, name)
488 return false if (! room_of_head?(x, y, name))
489 return false if ((name != @name) && (name != @promoted_name))
490 return false if (@promoted && (name != @promoted_name)) # can't un-promote
493 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
495 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
497 return false if ((6 >= @y) && (6 >= y) && (name != @name))
501 if ((@x == 0) || (@y == 0))
502 return jump_to?(x, y)
504 return movable_grids.include?([x, y])
509 if ((@x == 0) || (@y == 0))
511 @board.sente_hands.delete(self)
513 @board.gote_hands.delete(self)
515 @board.array[x][y] = self
516 elsif ((x == 0) || (y == 0))
517 @promoted = false # clear promoted flag before moving to hands
519 @board.sente_hands.push(self)
521 @board.gote_hands.push(self)
523 @board.array[@x][@y] = nil
525 @board.array[@x][@y] = nil
526 @board.array[x][y] = self
559 class PieceFU < Piece
562 @normal_moves = [[0, +1]]
563 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
565 @promoted_name = "TO"
568 def room_of_head?(x, y, name)
571 return false if (y == 1)
573 return false if (y == 9)
579 if ((iy != @y) && # not source position
580 @board.array[x][iy] &&
581 (@board.array[x][iy].sente == @sente) && # mine
582 (@board.array[x][iy].name == "FU") &&
583 (@board.array[x][iy].promoted == false))
593 class PieceKY < Piece
597 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
599 @promoted_name = "NY"
602 def room_of_head?(x, y, name)
605 return false if (y == 1)
607 return false if (y == 9)
612 def far_movable_grids
620 while (jump_to?(cand_x, cand_y))
621 grids.push([cand_x, cand_y])
622 break if (! put_to?(cand_x, cand_y))
628 while (jump_to?(cand_x, cand_y))
629 grids.push([cand_x, cand_y])
630 break if (! put_to?(cand_x, cand_y))
638 class PieceKE < Piece
641 @normal_moves = [[+1, +2], [-1, +2]]
642 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
644 @promoted_name = "NK"
647 def room_of_head?(x, y, name)
650 return false if ((y == 1) || (y == 2))
652 return false if ((y == 9) || (y == 8))
658 class PieceGI < Piece
661 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
662 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
664 @promoted_name = "NG"
668 class PieceKI < Piece
671 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
678 class PieceKA < Piece
682 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
684 @promoted_name = "UM"
687 def far_movable_grids
692 while (jump_to?(cand_x, cand_y))
693 grids.push([cand_x, cand_y])
694 break if (! put_to?(cand_x, cand_y))
701 while (jump_to?(cand_x, cand_y))
702 grids.push([cand_x, cand_y])
703 break if (! put_to?(cand_x, cand_y))
710 while (jump_to?(cand_x, cand_y))
711 grids.push([cand_x, cand_y])
712 break if (! put_to?(cand_x, cand_y))
719 while (jump_to?(cand_x, cand_y))
720 grids.push([cand_x, cand_y])
721 break if (! put_to?(cand_x, cand_y))
728 class PieceHI < Piece
732 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
734 @promoted_name = "RY"
737 def far_movable_grids
742 while (jump_to?(cand_x, cand_y))
743 grids.push([cand_x, cand_y])
744 break if (! put_to?(cand_x, cand_y))
750 while (jump_to?(cand_x, cand_y))
751 grids.push([cand_x, cand_y])
752 break if (! put_to?(cand_x, cand_y))
758 while (jump_to?(cand_x, cand_y))
759 grids.push([cand_x, cand_y])
760 break if (! put_to?(cand_x, cand_y))
766 while (jump_to?(cand_x, cand_y))
767 grids.push([cand_x, cand_y])
768 break if (! put_to?(cand_x, cand_y))
774 class PieceOU < Piece
777 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
787 @sente_hands = Array::new
788 @gote_hands = Array::new
790 @sente_history = Hash::new
791 @gote_history = Hash::new
792 @array = [[], [], [], [], [], [], [], [], [], []]
794 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
797 PieceKY::new(self, 1, 1, false)
798 PieceKE::new(self, 2, 1, false)
799 PieceGI::new(self, 3, 1, false)
800 PieceKI::new(self, 4, 1, false)
801 PieceOU::new(self, 5, 1, false)
802 PieceKI::new(self, 6, 1, false)
803 PieceGI::new(self, 7, 1, false)
804 PieceKE::new(self, 8, 1, false)
805 PieceKY::new(self, 9, 1, false)
806 PieceKA::new(self, 2, 2, false)
807 PieceHI::new(self, 8, 2, false)
808 PieceFU::new(self, 1, 3, false)
809 PieceFU::new(self, 2, 3, false)
810 PieceFU::new(self, 3, 3, false)
811 PieceFU::new(self, 4, 3, false)
812 PieceFU::new(self, 5, 3, false)
813 PieceFU::new(self, 6, 3, false)
814 PieceFU::new(self, 7, 3, false)
815 PieceFU::new(self, 8, 3, false)
816 PieceFU::new(self, 9, 3, false)
818 PieceKY::new(self, 1, 9, true)
819 PieceKE::new(self, 2, 9, true)
820 PieceGI::new(self, 3, 9, true)
821 PieceKI::new(self, 4, 9, true)
822 PieceOU::new(self, 5, 9, true)
823 PieceKI::new(self, 6, 9, true)
824 PieceGI::new(self, 7, 9, true)
825 PieceKE::new(self, 8, 9, true)
826 PieceKY::new(self, 9, 9, true)
827 PieceKA::new(self, 8, 8, true)
828 PieceHI::new(self, 2, 8, true)
829 PieceFU::new(self, 1, 7, true)
830 PieceFU::new(self, 2, 7, true)
831 PieceFU::new(self, 3, 7, true)
832 PieceFU::new(self, 4, 7, true)
833 PieceFU::new(self, 5, 7, true)
834 PieceFU::new(self, 6, 7, true)
835 PieceFU::new(self, 7, 7, true)
836 PieceFU::new(self, 8, 7, true)
837 PieceFU::new(self, 9, 7, true)
840 def have_piece?(hands, name)
841 piece = hands.find { |i|
847 def move_to(x0, y0, x1, y1, name, sente)
854 if ((x0 == 0) || (y0 == 0))
855 piece = have_piece?(hands, name)
856 return :illegal if (! piece.move_to?(x1, y1, name))
857 piece.move_to(x1, y1)
859 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
860 if (@array[x0][y0].name != name) # promoted ?
861 @array[x0][y0].promoted = true
864 if (@array[x1][y1].name == "OU")
865 return :outori # return board update
867 @array[x1][y1].sente = @array[x0][y0].sente
868 @array[x1][y1].move_to(0, 0)
873 @array[x0][y0].move_to(x1, y1)
878 def look_for_ou(sente)
884 (@array[x][y].name == "OU") &&
885 (@array[x][y].sente == sente))
892 raise "can't find ou"
895 def checkmated?(sente) # sente is loosing
896 ou = look_for_ou(sente)
902 (@array[x][y].sente != sente))
903 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
914 def uchifuzume?(sente)
915 rival_ou = look_for_ou(! sente) # rival's ou
916 if (sente) # rival is gote
917 if ((rival_ou.y != 9) &&
918 (@array[rival_ou.x][rival_ou.y + 1]) &&
919 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
920 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
922 fu_y = rival_ou.y + 1
927 if ((rival_ou.y != 0) &&
928 (@array[rival_ou.x][rival_ou.y - 1]) &&
929 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
930 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
932 fu_y = rival_ou.y - 1
938 ## case: rival_ou is moving
940 rival_ou.movable_grids.each do |(cand_x, cand_y)|
941 tmp_board = Marshal.load(Marshal.dump(self))
942 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
943 raise "internal error" if (s != true)
944 if (! tmp_board.checkmated?(! sente)) # good move
949 ## case: rival is capturing fu
955 (@array[x][y].sente != sente) &&
956 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
957 if (@array[x][y].promoted)
958 name = @array[x][y].promoted_name
960 name = @array[x][y].name
962 tmp_board = Marshal.load(Marshal.dump(self))
963 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
964 raise "internal error" if (s != true)
965 if (! tmp_board.checkmated?(! sente)) # good move
976 def oute_sennichite?(sente)
977 if (checkmated?(! sente))
980 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
984 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
992 def sennichite?(sente)
994 if (@history[str] && (@history[str] >= 3)) # already 3 times
1000 def good_kachi?(sente)
1001 return false if (checkmated?(sente))
1002 ou = look_for_ou(sente)
1003 return false if (sente && (ou.y >= 4))
1004 return false if (! sente && (ou.y <= 6))
1010 hands = @sente_hands
1020 (@array[x][y].sente == sente) &&
1021 (@array[x][y].point > 0))
1022 point = point + @array[x][y].point
1028 hands.each do |piece|
1029 point = point + piece.point
1032 return false if (number < 10)
1034 return false if (point < 28)
1036 return false if (point < 27)
1041 def handle_one_move(str)
1042 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1049 elsif (str =~ /^%KACHI/)
1050 if (@sente == @current_player)
1055 if (good_kachi?(sente))
1060 elsif (str =~ /^%TORYO/)
1066 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1067 ((x0 != 0) || (y0 != 0)))
1069 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1075 hands = @sente_hands
1082 if ((x0 == 0) && (y0 == 0))
1083 return :illegal if (! have_piece?(hands, name))
1084 elsif (! @array[x0][y0])
1085 return :illegal # no piece
1086 elsif (@array[x0][y0].sente != sente)
1087 return :illegal # this is not mine
1088 elsif (@array[x0][y0].name != name)
1089 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1092 ## destination check
1093 if (@array[x1][y1] &&
1094 (@array[x1][y1].sente == sente)) # can't capture mine
1096 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1097 return :illegal # can't put on existing piece
1100 tmp_board = Marshal.load(Marshal.dump(self))
1101 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1102 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1103 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1104 return :sennichite if tmp_board.sennichite?(sente)
1106 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1110 move_to(x0, y0, x1, y1, name, sente)
1113 if (checkmated?(! sente))
1115 @sente_history[str] = (@sente_history[str] || 0) + 1
1117 @gote_history[str] = (@gote_history[str] || 0) + 1
1121 @sente_history.clear
1126 @history[str] = (@history[str] || 0) + 1
1134 a.push(sprintf("P%d", y))
1137 piece = @array[x][y]
1146 a.push(sprintf("\n"))
1149 if (! sente_hands.empty?)
1151 sente_hands.each do |p|
1152 a.push("00" + p.name)
1156 if (! gote_hands.empty?)
1158 gote_hands.each do |p|
1159 a.push("00" + p.name)
1169 def initialize(game_name, player0, player1)
1170 @monitors = Array::new
1171 @game_name = game_name
1172 if (@game_name =~ /-(\d+)-(\d+)$/)
1173 @total_time = $1.to_i
1184 @current_player = @sente
1185 @next_player = @gote
1193 @sente.status = "agree_waiting"
1194 @gote.status = "agree_waiting"
1195 @id = sprintf("%s+%s+%s+%s+%s",
1196 LEAGUE.event, @game_name, @sente.name, @gote.name,
1197 Time::new.strftime("%Y%m%d%H%M%S"))
1199 LEAGUE.games[@id] = self
1202 log_message(sprintf("game created %s", @id))
1204 @logfile = @id + ".csa"
1212 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1213 attr_accessor :last_move, :current_turn
1215 def monitoron(monitor)
1216 @monitors.delete(monitor)
1217 @monitors.push(monitor)
1220 def monitoroff(monitor)
1221 @monitors.delete(monitor)
1224 def reject(rejector)
1225 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1226 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1231 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1233 elsif (@current_player == killer)
1240 log_message(sprintf("game finished %s", @id))
1241 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1246 @sente.status = "connected"
1247 @gote.status = "connected"
1249 if (@current_player.protocol == "CSA")
1250 @current_player.finish
1252 if (@next_player.protocol == "CSA")
1255 @monitors = Array::new
1258 @current_player = nil
1260 LEAGUE.games.delete(@id)
1263 def handle_one_move(str, player)
1265 if (@current_player == player)
1266 @end_time = Time::new
1267 t = (@end_time - @start_time).ceil
1268 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1271 if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1273 elsif (str == :timeout)
1274 return false # time isn't expired. players aren't swapped. continue game
1276 @current_player.mytime = @current_player.mytime - t
1277 if (@current_player.mytime < 0)
1278 @current_player.mytime = 0
1282 move_status = @board.handle_one_move(str)
1284 # log_error("handle_one_move raise exception for #{str}")
1285 # move_status = :illegal
1288 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1289 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1291 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1292 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1293 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1294 @fh.printf("%s\nT%d\n", str, t)
1295 @last_move = sprintf("%s,T%d", str, t)
1296 @current_turn = @current_turn + 1
1299 @monitors.each do |monitor|
1300 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1301 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1306 if (@next_player.status != "game") # rival is logout or disconnected
1308 elsif (status == :timeout)
1310 elsif (move_status == :illegal)
1312 elsif (move_status == :kachi_win)
1314 elsif (move_status == :kachi_lose)
1316 elsif (move_status == :toryo)
1318 elsif (move_status == :outori)
1320 elsif (move_status == :sennichite)
1322 elsif (move_status == :oute_sennichite)
1323 oute_sennichite_lose()
1324 elsif (move_status == :uchifuzume)
1326 elsif (move_status == :oute_kaihimore)
1327 oute_kaihimore_lose()
1331 finish() if finish_flag
1332 (@current_player, @next_player) = [@next_player, @current_player]
1333 @start_time = Time::new
1339 @current_player.status = "connected"
1340 @next_player.status = "connected"
1341 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1342 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1343 @fh.printf("%%TORYO\n")
1344 @fh.print(@board.to_s.gsub(/^/, "\'"))
1345 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1346 @monitors.each do |monitor|
1347 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1352 @current_player.status = "connected"
1353 @next_player.status = "connected"
1354 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1355 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1356 @fh.printf("%%TORYO\n")
1357 @fh.print(@board.to_s.gsub(/^/, "\'"))
1358 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1359 @monitors.each do |monitor|
1360 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1365 @current_player.status = "connected"
1366 @next_player.status = "connected"
1367 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1368 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1369 @fh.print(@board.to_s.gsub(/^/, "\'"))
1370 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1371 @monitors.each do |monitor|
1372 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1376 def oute_sennichite_lose
1377 @current_player.status = "connected"
1378 @next_player.status = "connected"
1379 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1380 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1381 @fh.print(@board.to_s.gsub(/^/, "\'"))
1382 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1383 @monitors.each do |monitor|
1384 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\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:illegal move:%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))
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:uchifuzume:%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))
1412 def oute_kaihimore_lose
1413 @current_player.status = "connected"
1414 @next_player.status = "connected"
1415 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1416 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1417 @fh.print(@board.to_s.gsub(/^/, "\'"))
1418 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1419 @monitors.each do |monitor|
1420 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1425 @current_player.status = "connected"
1426 @next_player.status = "connected"
1427 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1428 @next_player.write_safe("#TIME_UP\n#WIN\n")
1429 @fh.print(@board.to_s.gsub(/^/, "\'"))
1430 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1431 @monitors.each do |monitor|
1432 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1437 @current_player.status = "connected"
1438 @next_player.status = "connected"
1439 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1440 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1441 @fh.printf("%%KACHI\n")
1442 @fh.print(@board.to_s.gsub(/^/, "\'"))
1443 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1444 @monitors.each do |monitor|
1445 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1450 @current_player.status = "connected"
1451 @next_player.status = "connected"
1452 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1453 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1454 @fh.printf("%%KACHI\n")
1455 @fh.print(@board.to_s.gsub(/^/, "\'"))
1456 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1457 @monitors.each do |monitor|
1458 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1463 @current_player.status = "connected"
1464 @next_player.status = "connected"
1465 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1466 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1467 @fh.printf("%%TORYO\n")
1468 @fh.print(@board.to_s.gsub(/^/, "\'"))
1469 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1470 @monitors.each do |monitor|
1471 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1476 @current_player.status = "connected"
1477 @next_player.status = "connected"
1478 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1479 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1480 @fh.print(@board.to_s.gsub(/^/, "\'"))
1481 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1482 @monitors.each do |monitor|
1483 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1488 log_message(sprintf("game started %s", @id))
1489 @sente.write_safe(sprintf("START:%s\n", @id))
1490 @gote.write_safe(sprintf("START:%s\n", @id))
1491 @sente.mytime = @total_time
1492 @gote.mytime = @total_time
1493 @start_time = Time::new
1498 @fh = open(@logfile, "w")
1502 @fh.printf("N+%s\n", @sente.name)
1503 @fh.printf("N-%s\n", @gote.name)
1504 @fh.printf("$EVENT:%s\n", @id)
1506 @sente.write_safe(propose_message("+"))
1507 @gote.write_safe(propose_message("-"))
1509 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1511 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1512 P2 * -HI * * * * * -KA *
1513 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1514 P4 * * * * * * * * *
1515 P5 * * * * * * * * *
1516 P6 * * * * * * * * *
1517 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1518 P8 * +KA * * * * * +HI *
1519 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1528 Protocol_Version:1.0
1529 Protocol_Mode:Server
1532 Name+:#{@sente.name}
1538 Total_Time:#{@total_time}
1540 Least_Time_Per_Move:#{Least_Time_Per_Move}
1541 Remaining_Time+:#{@sente.mytime}
1542 Remaining_Time-:#{@gote.mytime}
1543 Last_Move:#{@last_move}
1544 Current_Turn:#{@current_turn}
1547 Jishogi_Declaration:1.1
1555 return str0 + @board.to_s + str1
1558 def propose_message(sg_flag)
1561 Protocol_Version:1.0
1562 Protocol_Mode:Server
1565 Name+:#{@sente.name}
1567 Your_Turn:#{sg_flag}
1572 Total_Time:#{@total_time}
1574 Least_Time_Per_Move:#{Least_Time_Per_Move}
1577 Jishogi_Declaration:1.1
1578 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1579 P2 * -HI * * * * * -KA *
1580 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1581 P4 * * * * * * * * *
1582 P5 * * * * * * * * *
1583 P6 * * * * * * * * *
1584 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1585 P8 * +KA * * * * * +HI *
1586 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1600 shogi-server - server for CSA server protocol
1603 shogi-server event_name port_number
1606 server for CSA server protocol
1610 specify filename for logging process ID
1613 this file is distributed under GPL version2 and might be compiled by Exerb
1625 def log_message(str)
1626 printf("%s message: %s\n", Time::new.to_s, str)
1629 def log_warning(str)
1630 printf("%s warning: %s\n", Time::new.to_s, str)
1634 printf("%s error: %s\n", Time::new.to_s, str)
1638 def parse_command_line
1640 parser = GetoptLong.new
1641 parser.ordering = GetoptLong::REQUIRE_ORDER
1643 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1647 parser.each_option do |name, arg|
1648 name.sub!(/^--/, '')
1649 options[name] = arg.dup
1653 raise parser.error_message
1658 def good_game_name?(str)
1659 if ((str =~ /^(.+)-\d+-\d+$/) &&
1660 (good_identifier?($1)))
1667 def good_identifier?(str)
1668 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1669 (str.length < Max_Identifier_Length))
1676 def good_login?(str)
1678 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1679 (tokens[0] == "LOGIN") &&
1680 (good_identifier?(tokens[1])))
1687 def write_pid_file(file)
1688 open(file, "w") do |fh|
1689 fh.print Process::pid, "\n"
1693 def mutex_watchdog(mutex, sec)
1702 log_error("mutex watchdog timeout")
1711 mutex_watchdog($mutex, 10)
1714 $options = parse_command_line
1715 if (ARGV.length != 2)
1720 LEAGUE.event = ARGV.shift
1723 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1726 Thread.abort_on_exception = true
1728 server = TCPserver.open(port)
1729 log_message("server started")
1732 Thread::start(server.accept) do |client|
1735 while (str = client.gets_timeout(Login_Time))
1740 if (good_login?(str))
1741 player = Player::new(str, client)
1742 if (LEAGUE.players[player.name])
1743 if ((LEAGUE.players[player.name].password == player.password) &&
1744 (LEAGUE.players[player.name].status != "game"))
1745 log_message(sprintf("user %s login forcely", player.name))
1746 LEAGUE.players[player.name].kill
1748 client.write_safe("LOGIN:incorrect" + eol)
1749 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1757 client.write_safe("LOGIN:incorrect" + eol)
1758 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1768 log_message(sprintf("user %s login", player.name))
1773 player.game.kill(player)
1776 LEAGUE.delete(player)
1777 log_message(sprintf("user %s logout", player.name))
1786 LEAGUE = League::new