4 ## Copyright (C) 2004 NABEYA Kenichi
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 ((player.sente == nil) || (player.sente == sente)) &&
105 ((searcher == nil) || (player != searcher)))
114 def initialize(str, socket)
118 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
120 @protocol = nil # CSA or x1
121 @eol = "\m" # favorite eol code
124 @mytime = 0 # set in start method also
128 @write_queue = Queue::new
132 attr_accessor :name, :password, :socket, :status
133 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
134 attr_accessor :main_thread, :writer_thread, :write_queue
136 log_message(sprintf("user %s killed", @name))
141 Thread::kill(@main_thread) if @main_thread
145 if (@status != "finished")
147 log_message(sprintf("user %s finish", @name))
148 Thread::kill(@writer_thread) if @writer_thread
150 @socket.close if (! @socket.closed?)
152 log_message(sprintf("user %s finish failed", @name))
158 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
162 while (str = @write_queue.pop)
163 @socket.write_safe(str)
168 if ((status == "game_waiting") ||
169 (status == "start_waiting") ||
170 (status == "agree_waiting") ||
173 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
174 elsif (@sente == false)
175 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
176 elsif (@sente == nil)
177 return sprintf("%s %s %s %s", @name, @protocol, @status, @game_name)
180 return sprintf("%s %s %s", @name, @protocol, @status)
185 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
192 (login, @name, @password, ext) = str.split
198 @main_thread = Thread::current
199 @writer_thread = Thread::start do
205 write_safe(sprintf("LOGIN:%s OK\n", @name))
206 if (@protocol != "CSA")
207 log_message(sprintf("user %s run in %s mode", @name, @protocol))
208 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
210 log_message(sprintf("user %s run in CSA mode", @name))
211 csa_1st_str = "%%GAME #{Default_Game_Name}"
214 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
221 if (@write_queue.size > Max_Write_Queue_Size)
222 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
226 if (@status == "finished")
229 str.chomp! if (str.class == String)
231 when /^[\+\-%][^%]/, :timeout
232 if (@status == "game")
233 s = @game.handle_one_move(str, self)
234 return if (s && @protocol == "CSA")
237 if (@status == "agree_waiting")
239 return if (@protocol == "CSA")
241 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
244 if (@status == "agree_waiting")
245 @status = "start_waiting"
246 if ((@game.sente.status == "start_waiting") &&
247 (@game.gote.status == "start_waiting"))
249 @game.sente.status = "game"
250 @game.gote.status = "game"
253 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
255 when /^%%SHOW\s+(\S+)/
257 if (LEAGUE.games[game_id])
258 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
260 write_safe("##[SHOW] +OK\n")
261 when /^%%MONITORON\s+(\S+)/
263 if (LEAGUE.games[game_id])
264 LEAGUE.games[game_id].monitoron(self)
265 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
266 write_safe("##[MONITOR][#{game_id}] +OK\n")
268 when /^%%MONITOROFF\s+(\S+)/
270 if (LEAGUE.games[game_id])
271 LEAGUE.games[game_id].monitoroff(self)
276 @status = "connected"
278 when /^%%(GAME|CHALLENGE)\s+(\S+)\s*([\+\-]*)\s*$/
282 if (! good_game_name?(game_name))
283 write_safe(sprintf("##[ERROR] bad game name\n"))
285 elsif ((@status == "connected") || (@status == "game_waiting"))
288 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
291 if ((my_sente_str == "") ||
292 (my_sente_str == "+") ||
293 (my_sente_str == "-"))
296 write_safe(sprintf("##[ERROR] bad game option\n"))
300 if (my_sente_str == "")
301 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
302 elsif (my_sente_str == "+")
303 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
304 elsif (my_sente_str == "-")
305 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
310 @game_name = game_name
311 if ((my_sente_str == "") && (rival.sente == nil))
319 elsif (rival.sente == true) # rival has higher priority
321 elsif (rival.sente == false)
323 elsif (my_sente_str == "+")
326 elsif (my_sente_str == "-")
332 Game::new(@game_name, self, rival)
333 self.status = "agree_waiting"
334 rival.status = "agree_waiting"
335 else # rival not found
336 if (command_name == "GAME")
337 @status = "game_waiting"
338 @game_name = game_name
339 if (my_sente_str == "+")
341 elsif (my_sente_str == "-")
347 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
348 @status = "connected"
353 when /^%%CHAT\s+(.+)/
355 LEAGUE.players.each do |name, player|
356 if (player.protocol != "CSA")
357 s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
358 player.status = "zombie" if (! s)
363 LEAGUE.games.each do |id, game|
364 buf.push(sprintf("##[LIST] %s\n", id))
366 buf.push("##[LIST] +OK\n")
370 LEAGUE.players.each do |name, player|
371 buf.push(sprintf("##[WHO] %s\n", player.to_s))
373 buf.push("##[WHO] +OK\n")
376 @status = "connected"
377 write_safe("LOGOUT:completed\n")
380 ## ignore null string
382 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
392 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
393 def initialize(board, x, y, sente, promoted=false)
399 @board.array[x][y] = self
401 attr_accessor :promoted, :sente, :x, :y, :board
403 def room_of_head?(x, y, name)
408 return adjacent_movable_grids + far_movable_grids
411 def far_movable_grids
416 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
417 if ((@board.array[x][y] == nil) || # dst is empty
418 (@board.array[x][y].sente != @sente)) # dst is enemy
426 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
427 if (@board.array[x][y] == nil) # dst is empty?
434 def adjacent_movable_grids
437 moves = @promoted_moves
439 moves = @normal_moves
441 moves.each do |(dx, dy)|
448 if (jump_to?(cand_x, cand_y))
449 grids.push([cand_x, cand_y])
455 def move_to?(x, y, name)
456 return false if (! room_of_head?(x, y, name))
457 return false if ((name != @name) && (name != @promoted_name))
458 return false if (@promoted && (name != @promoted_name)) # can't un-promote
461 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
463 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
465 return false if ((6 >= @y) && (6 <= y) && (name != @name))
468 if ((@x == 0) || (@y == 0))
469 return jump_to?(x, y)
471 return movable_grids.include?([x, y])
476 if ((@x == 0) || (@y == 0))
478 @board.sente_hands.delete(self)
480 @board.gote_hands.delete(self)
482 @board.array[x][y] = self
483 elsif ((x == 0) || (y == 0))
485 @board.sente_hands.push(self)
487 @board.gote_hands.push(self)
489 @board.array[@x][@y] = nil
491 @board.array[@x][@y] = nil
492 @board.array[x][y] = self
521 class PieceFU < Piece
523 @normal_moves = [[0, +1]]
524 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
526 @promoted_name = "TO"
529 def room_of_head?(x, y, name)
532 return false if (y == 0)
534 return false if (y == 9)
540 if ((iy != @y) && # not source position
541 @board.array[x][iy] &&
542 (@board.array[x][iy].sente == @sente) && # mine
543 (@board.array[x][iy].name == "FU") &&
544 (@board.array[x][iy].promoted == false))
554 class PieceKY < Piece
557 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
559 @promoted_name = "NY"
562 def room_of_head?(x, y, name)
565 return false if (y == 0)
567 return false if (y == 9)
572 def far_movable_grids
580 while (jump_to?(cand_x, cand_y))
581 grids.push([cand_x, cand_y])
582 break if (! put_to?(cand_x, cand_y))
588 while (jump_to?(cand_x, cand_y))
589 grids.push([cand_x, cand_y])
590 break if (! put_to?(cand_x, cand_y))
598 class PieceKE < Piece
600 @normal_moves = [[+1, +2], [-1, +2]]
601 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
603 @promoted_name = "NK"
606 def room_of_head?(x, y, name)
609 return false if ((y == 0) || (y == 1))
611 return false if ((y == 9) || (y == 8))
617 class PieceGI < Piece
619 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
620 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
622 @promoted_name = "NG"
626 class PieceKI < Piece
628 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
635 class PieceKA < Piece
638 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
640 @promoted_name = "UM"
643 def far_movable_grids
648 while (jump_to?(cand_x, cand_y))
649 grids.push([cand_x, cand_y])
650 break if (! put_to?(cand_x, cand_y))
657 while (jump_to?(cand_x, cand_y))
658 grids.push([cand_x, cand_y])
659 break if (! put_to?(cand_x, cand_y))
666 while (jump_to?(cand_x, cand_y))
667 grids.push([cand_x, cand_y])
668 break if (! put_to?(cand_x, cand_y))
675 while (jump_to?(cand_x, cand_y))
676 grids.push([cand_x, cand_y])
677 break if (! put_to?(cand_x, cand_y))
684 class PieceHI < Piece
687 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
689 @promoted_name = "RY"
692 def far_movable_grids
697 while (jump_to?(cand_x, cand_y))
698 grids.push([cand_x, cand_y])
699 break if (! put_to?(cand_x, cand_y))
705 while (jump_to?(cand_x, cand_y))
706 grids.push([cand_x, cand_y])
707 break if (! put_to?(cand_x, cand_y))
713 while (jump_to?(cand_x, cand_y))
714 grids.push([cand_x, cand_y])
715 break if (! put_to?(cand_x, cand_y))
721 while (jump_to?(cand_x, cand_y))
722 grids.push([cand_x, cand_y])
723 break if (! put_to?(cand_x, cand_y))
729 class PieceOU < Piece
731 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
741 @sente_hands = Array::new
742 @gote_hands = Array::new
744 @array = [[], [], [], [], [], [], [], [], [], []]
746 attr_accessor :array, :sente_hands, :gote_hands, :history
749 PieceKY::new(self, 1, 1, false)
750 PieceKE::new(self, 2, 1, false)
751 PieceGI::new(self, 3, 1, false)
752 PieceKI::new(self, 4, 1, false)
753 PieceOU::new(self, 5, 1, false)
754 PieceKI::new(self, 6, 1, false)
755 PieceGI::new(self, 7, 1, false)
756 PieceKE::new(self, 8, 1, false)
757 PieceKY::new(self, 9, 1, false)
758 PieceKA::new(self, 2, 2, false)
759 PieceHI::new(self, 8, 2, false)
760 PieceFU::new(self, 1, 3, false)
761 PieceFU::new(self, 2, 3, false)
762 PieceFU::new(self, 3, 3, false)
763 PieceFU::new(self, 4, 3, false)
764 PieceFU::new(self, 5, 3, false)
765 PieceFU::new(self, 6, 3, false)
766 PieceFU::new(self, 7, 3, false)
767 PieceFU::new(self, 8, 3, false)
768 PieceFU::new(self, 9, 3, false)
770 PieceKY::new(self, 1, 9, true)
771 PieceKE::new(self, 2, 9, true)
772 PieceGI::new(self, 3, 9, true)
773 PieceKI::new(self, 4, 9, true)
774 PieceOU::new(self, 5, 9, true)
775 PieceKI::new(self, 6, 9, true)
776 PieceGI::new(self, 7, 9, true)
777 PieceKE::new(self, 8, 9, true)
778 PieceKY::new(self, 9, 9, true)
779 PieceKA::new(self, 8, 8, true)
780 PieceHI::new(self, 2, 8, true)
781 PieceFU::new(self, 1, 7, true)
782 PieceFU::new(self, 2, 7, true)
783 PieceFU::new(self, 3, 7, true)
784 PieceFU::new(self, 4, 7, true)
785 PieceFU::new(self, 5, 7, true)
786 PieceFU::new(self, 6, 7, true)
787 PieceFU::new(self, 7, 7, true)
788 PieceFU::new(self, 8, 7, true)
789 PieceFU::new(self, 9, 7, true)
792 def have_piece?(hands, name)
793 piece = hands.find { |i|
799 def move_to(x0, y0, x1, y1, name)
800 if ((x0 == 0) && (y0 == 0))
801 piece = have_piece?(hands, name)
802 return :illegal if (! piece.move_to?(x1, y1, name))
803 piece.move_to(x1, y1)
805 piece.promoted = false
807 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
808 if (@array[x0][y0].name != name) # promoted ?
809 @array[x0][y0].promoted = true
812 if (@array[x1][y1].name == "OU")
813 return :outori # return board update
815 @array[x1][y1].sente = @array[x0][y0].sente
816 @array[x1][y1].move_to(0, 0)
817 if (@array[x0][y0].sente)
826 @array[x0][y0].move_to(x1, y1)
831 def look_for_ou(sente)
837 (@array[x][y].name == "OU") &&
838 (@array[x][y].sente == sente))
845 raise "can't find ou"
848 def checkmated?(sente) # sente is loosing
849 ou = look_for_ou(sente)
855 (@array[x][y].sente != sente))
856 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
867 def uchifuzume?(sente)
868 rival_ou = look_for_ou(! sente) # rival's ou
869 if (sente) # rival is gote
870 if ((rival_ou.y != 9) &&
871 (@array[rival_ou.x][rival_ou.y + 1]) &&
872 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
873 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
875 fu_y = rival_ou.y + 1
880 if ((rival_ou.y != 0) &&
881 (@array[rival_ou.x][rival_ou.y - 1]) &&
882 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
883 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
885 fu_y = rival_ou.y - 1
891 ## case: rival_ou is moving
893 rival_ou.movable_grids.each do |(cand_x, cand_y)|
894 tmp_board = Marshal.load(Marshal.dump(self))
895 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU")
896 raise "internal error" if (s != true)
897 if (! tmp_board.checkmated?(! sente)) # good move
902 ## case: rival is capturing fu
908 (@array[x][y].sente != sente) &&
909 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
910 if (@array[x][y].promoted)
911 name = @array[x][y].promoted_name
913 name = @array[x][y].name
915 tmp_board = Marshal.load(Marshal.dump(self))
916 s = tmp_board.move_to(x, y, fu_x, fu_y, name)
917 raise "internal error" if (s != true)
918 if (! tmp_board.checkmated?(! sente)) # good move
929 def sennichite?(sente)
931 if (@history[str] && (@history[str] >= 3)) # already 3 times
932 if (checkmated?(! sente)) # rival is loosing
933 return :outesennichite
941 def handle_one_move(str)
942 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
949 elsif (str =~ /^%KACHI/)
951 elsif (str =~ /^%TORYO/)
957 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
958 ((x0 != 0) || (y0 != 0)))
960 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
973 if ((x0 == 0) && (y0 == 0))
974 return :illegal if (! have_piece?(hands, name))
975 elsif (! @array[x0][y0])
976 return :illegal # no piece
977 elsif (@array[x0][y0].sente != sente)
978 return :illegal # this is not mine
979 elsif (@array[x0][y0].name != name)
980 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
984 if (@array[x1][y1] &&
985 (@array[x1][y1].sente == sente)) # can't capture mine
987 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
988 return :illegal # can't put on existing piece
991 tmp_board = Marshal.load(Marshal.dump(self))
992 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name) == :illegal)
993 if (tmp_board.checkmated?(sente))
996 s = tmp_board.sennichite?(sente)
997 return s if (s) # outesennichite or sennichite
998 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1002 move_to(x0, y0, x1, y1, name)
1004 @history[str] = (@history[str] || 0) + 1
1012 a.push(sprintf("P%d", y))
1015 piece = @array[x][y]
1024 a.push(sprintf("\n"))
1027 if (! sente_hands.empty?)
1029 sente_hands.each do |p|
1030 a.push("00" + p.name)
1034 if (! gote_hands.empty?)
1036 gote_hands.each do |p|
1037 a.push("00" + p.name)
1047 def initialize(game_name, player0, player1)
1048 @monitors = Array::new
1049 @game_name = game_name
1050 if (@game_name =~ /-(\d+)-(\d+)$/)
1051 @total_time = $1.to_i
1062 @current_player = @sente
1063 @next_player = @gote
1071 @sente.status = "agree_waiting"
1072 @gote.status = "agree_waiting"
1073 @id = sprintf("%s+%s+%s+%s+%s",
1074 LEAGUE.event, @game_name, @sente.name, @gote.name,
1075 Time::new.strftime("%Y%m%d%H%M%S"))
1077 LEAGUE.games[@id] = self
1080 log_message(sprintf("game created %s", @id))
1082 @logfile = @id + ".csa"
1090 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1091 attr_accessor :last_move, :current_turn
1093 def monitoron(monitor)
1094 @monitors.delete(monitor)
1095 @monitors.push(monitor)
1098 def monitoroff(monitor)
1099 @monitors.delete(monitor)
1102 def reject(rejector)
1103 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1104 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1109 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1111 elsif (@current_player == killer)
1118 log_message(sprintf("game finished %s", @id))
1119 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1124 @sente.status = "connected"
1125 @gote.status = "connected"
1127 if (@current_player.protocol == "CSA")
1128 @current_player.finish
1130 if (@next_player.protocol == "CSA")
1133 @monitors = Array::new
1136 @current_player = nil
1138 LEAGUE.games.delete(@id)
1141 def handle_one_move(str, player)
1143 if (@current_player == player)
1144 @end_time = Time::new
1145 t = (@end_time - @start_time).ceil
1146 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1149 if ((@current_player.mytime - t <= 0) && (@total_time > 0))
1151 elsif (str == :timeout)
1152 return false # time isn't expired. players aren't swapped. continue game
1155 move_status = @board.handle_one_move(str)
1157 # log_error("handle_one_move raise exception for #{str}")
1158 # move_status = :illegal
1160 if (move_status == :illegal)
1161 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1163 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite))
1164 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1165 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1166 @fh.printf("%s\nT%d\n", str, t)
1167 @last_move = sprintf("%s,T%d", str, t)
1168 @current_turn = @current_turn + 1
1170 @monitors.each do |monitor|
1171 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1172 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1177 if (@current_player.mytime - t < @byoyomi)
1178 @current_player.mytime = @byoyomi
1180 @current_player.mytime = @current_player.mytime - t
1183 if (@next_player.status != "game") # rival is logout or disconnected
1185 elsif (status == :timeout)
1187 elsif (move_status == :illegal)
1189 elsif (move_status == :kachi)
1191 elsif (move_status == :toryo)
1193 elsif (move_status == :outori)
1195 elsif (move_status == :sennichite)
1200 finish() if finish_flag
1201 (@current_player, @next_player) = [@next_player, @current_player]
1202 @start_time = Time::new
1208 @current_player.status = "connected"
1209 @next_player.status = "connected"
1210 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1211 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1212 @fh.printf("%%TORYO\n")
1213 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1214 @monitors.each do |monitor|
1215 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1220 @current_player.status = "connected"
1221 @next_player.status = "connected"
1222 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1223 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1224 @fh.printf("%%TORYO\n")
1225 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1226 @monitors.each do |monitor|
1227 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1232 @current_player.status = "connected"
1233 @next_player.status = "connected"
1234 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1235 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1236 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1237 @monitors.each do |monitor|
1238 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1243 @current_player.status = "connected"
1244 @next_player.status = "connected"
1245 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1246 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1247 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1248 @monitors.each do |monitor|
1249 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1254 @current_player.status = "connected"
1255 @next_player.status = "connected"
1256 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1257 @next_player.write_safe("#TIME_UP\n#WIN\n")
1258 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1259 @monitors.each do |monitor|
1260 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1265 @current_player.status = "connected"
1266 @next_player.status = "connected"
1267 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1268 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1269 @fh.printf("%%KACHI\n")
1270 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1271 @monitors.each do |monitor|
1272 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1277 @current_player.status = "connected"
1278 @next_player.status = "connected"
1279 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1280 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1281 @fh.printf("%%TORYO\n")
1282 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1283 @monitors.each do |monitor|
1284 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1289 @current_player.status = "connected"
1290 @next_player.status = "connected"
1291 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1292 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1293 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1294 @monitors.each do |monitor|
1295 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1300 log_message(sprintf("game started %s", @id))
1301 @sente.write_safe(sprintf("START:%s\n", @id))
1302 @gote.write_safe(sprintf("START:%s\n", @id))
1303 @sente.mytime = @total_time
1304 @gote.mytime = @total_time
1305 @start_time = Time::new
1310 @fh = open(@logfile, "w")
1314 @fh.printf("N+%s\n", @sente.name)
1315 @fh.printf("N-%s\n", @gote.name)
1316 @fh.printf("$EVENT:%s\n", @id)
1318 @sente.write_safe(propose_message("+"))
1319 @gote.write_safe(propose_message("-"))
1321 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1323 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1324 P2 * -HI * * * * * -KA *
1325 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1326 P4 * * * * * * * * *
1327 P5 * * * * * * * * *
1328 P6 * * * * * * * * *
1329 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1330 P8 * +KA * * * * * +HI *
1331 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1340 Protocol_Version:1.0
1341 Protocol_Mode:Server
1344 Name+:#{@sente.name}
1350 Total_Time:#{@total_time}
1352 Least_Time_Per_Move:#{Least_Time_Per_Move}
1353 Remaining_Time+:#{@sente.mytime}
1354 Remaining_Time-:#{@gote.mytime}
1355 Last_Move:#{@last_move}
1356 Current_Turn:#{@current_turn}
1359 Jishogi_Declaration:1.1
1367 return str0 + @board.to_s + str1
1370 def propose_message(sg_flag)
1373 Protocol_Version:1.0
1374 Protocol_Mode:Server
1377 Name+:#{@sente.name}
1379 Your_Turn:#{sg_flag}
1384 Total_Time:#{@total_time}
1386 Least_Time_Per_Move:#{Least_Time_Per_Move}
1389 Jishogi_Declaration:1.1
1390 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1391 P2 * -HI * * * * * -KA *
1392 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1393 P4 * * * * * * * * *
1394 P5 * * * * * * * * *
1395 P6 * * * * * * * * *
1396 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1397 P8 * +KA * * * * * +HI *
1398 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1412 shogi-server - server for CSA server protocol
1415 shogi-server event_name port_number
1418 server for CSA server protocol
1422 specify filename for logging process ID
1425 this file is distributed under GPL version2 and might be compiled by Exerb
1437 def log_message(str)
1438 printf("%s message: %s\n", Time::new.to_s, str)
1441 def log_warning(str)
1442 printf("%s warning: %s\n", Time::new.to_s, str)
1446 printf("%s error: %s\n", Time::new.to_s, str)
1450 def parse_command_line
1452 parser = GetoptLong.new
1453 parser.ordering = GetoptLong::REQUIRE_ORDER
1455 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1459 parser.each_option do |name, arg|
1460 name.sub!(/^--/, '')
1461 options[name] = arg.dup
1465 raise parser.error_message
1470 def good_game_name?(str)
1471 if ((str =~ /^(.+)-\d+-\d+$/) &&
1472 (good_identifier?($1)))
1479 def good_identifier?(str)
1480 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1481 (str.length < Max_Identifier_Length))
1488 def good_login?(str)
1490 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1491 (tokens[0] == "LOGIN") &&
1492 (good_identifier?(tokens[1])))
1499 def write_pid_file(file)
1500 open(file, "w") do |fh|
1501 fh.print Process::pid, "\n"
1505 def mutex_watchdog(mutex, sec)
1514 log_error("mutex watchdog timeout")
1523 mutex_watchdog($mutex, 10)
1526 $options = parse_command_line
1527 if (ARGV.length != 2)
1532 LEAGUE.event = ARGV.shift
1535 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1538 Thread.abort_on_exception = true
1540 server = TCPserver.open(port)
1541 log_message("server started")
1544 Thread::start(server.accept) do |client|
1547 while (str = client.gets_timeout(Login_Time))
1552 if (good_login?(str))
1553 player = Player::new(str, client)
1554 if (LEAGUE.players[player.name])
1555 if ((LEAGUE.players[player.name].password == player.password) &&
1556 (LEAGUE.players[player.name].status != "game"))
1557 log_message(sprintf("user %s login forcely", player.name))
1558 LEAGUE.players[player.name].kill
1560 client.write_safe("LOGIN:incorrect" + eol)
1561 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1569 client.write_safe("LOGIN:incorrect" + eol)
1570 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1580 log_message(sprintf("user %s login", player.name))
1585 player.game.kill(player)
1588 LEAGUE.delete(player)
1589 log_message(sprintf("user %s logout", player.name))
1598 LEAGUE = League::new