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 ((player.sente == nil) || (player.sente == sente)) &&
105 ((searcher == nil) || (player != searcher)))
114 def initialize(str, socket)
118 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
120 @protocol = nil # CSA or x1
121 @eol = "\m" # favorite eol code
124 @mytime = 0 # set in start method also
128 @write_queue = Queue::new
132 attr_accessor :name, :password, :socket, :status
133 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
134 attr_accessor :main_thread, :writer_thread, :write_queue
136 log_message(sprintf("user %s killed", @name))
141 Thread::kill(@main_thread) if @main_thread
145 if (@status != "finished")
147 log_message(sprintf("user %s finish", @name))
148 Thread::kill(@writer_thread) if @writer_thread
150 @socket.close if (! @socket.closed?)
152 log_message(sprintf("user %s finish failed", @name))
158 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
162 while (str = @write_queue.pop)
163 @socket.write_safe(str)
168 if ((status == "game_waiting") ||
169 (status == "start_waiting") ||
170 (status == "agree_waiting") ||
173 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
174 elsif (@sente == false)
175 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
176 elsif (@sente == nil)
177 return sprintf("%s %s %s %s", @name, @protocol, @status, @game_name)
180 return sprintf("%s %s %s", @name, @protocol, @status)
185 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
192 (login, @name, @password, ext) = str.split
198 @main_thread = Thread::current
199 @writer_thread = Thread::start do
205 write_safe(sprintf("LOGIN:%s OK\n", @name))
206 if (@protocol != "CSA")
207 log_message(sprintf("user %s run in %s mode", @name, @protocol))
208 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
210 log_message(sprintf("user %s run in CSA mode", @name))
211 csa_1st_str = "%%GAME #{Default_Game_Name}"
214 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
221 if (@write_queue.size > Max_Write_Queue_Size)
222 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
226 if (@status == "finished")
229 str.chomp! if (str.class == String)
231 when /^[\+\-%][^%]/, :timeout
232 if (@status == "game")
233 s = @game.handle_one_move(str, self)
234 return if (s && @protocol == "CSA")
237 if (@status == "agree_waiting")
239 return if (@protocol == "CSA")
241 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
244 if (@status == "agree_waiting")
245 @status = "start_waiting"
246 if ((@game.sente.status == "start_waiting") &&
247 (@game.gote.status == "start_waiting"))
249 @game.sente.status = "game"
250 @game.gote.status = "game"
253 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
255 when /^%%SHOW\s+(\S+)/
257 if (LEAGUE.games[game_id])
258 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
260 write_safe("##[SHOW] +OK\n")
261 when /^%%MONITORON\s+(\S+)/
263 if (LEAGUE.games[game_id])
264 LEAGUE.games[game_id].monitoron(self)
265 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
266 write_safe("##[MONITOR][#{game_id}] +OK\n")
268 when /^%%MONITOROFF\s+(\S+)/
270 if (LEAGUE.games[game_id])
271 LEAGUE.games[game_id].monitoroff(self)
276 @status = "connected"
278 when /^%%(GAME|CHALLENGE)\s+(\S+)\s*([\+\-]*)\s*$/
282 if (! good_game_name?(game_name))
283 write_safe(sprintf("##[ERROR] bad game name\n"))
285 elsif ((@status == "connected") || (@status == "game_waiting"))
288 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
291 if ((my_sente_str == "") ||
292 (my_sente_str == "+") ||
293 (my_sente_str == "-"))
296 write_safe(sprintf("##[ERROR] bad game option\n"))
300 if (my_sente_str == "")
301 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
302 elsif (my_sente_str == "+")
303 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
304 elsif (my_sente_str == "-")
305 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
310 @game_name = game_name
311 if ((my_sente_str == "") && (rival.sente == nil))
319 elsif (rival.sente == true) # rival has higher priority
321 elsif (rival.sente == false)
323 elsif (my_sente_str == "+")
326 elsif (my_sente_str == "-")
332 Game::new(@game_name, self, rival)
333 self.status = "agree_waiting"
334 rival.status = "agree_waiting"
335 else # rival not found
336 if (command_name == "GAME")
337 @status = "game_waiting"
338 @game_name = game_name
339 if (my_sente_str == "+")
341 elsif (my_sente_str == "-")
347 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
348 @status = "connected"
353 when /^%%CHAT\s+(.+)/
355 LEAGUE.players.each do |name, player|
356 if (player.protocol != "CSA")
357 s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
358 player.status = "zombie" if (! s)
363 LEAGUE.games.each do |id, game|
364 buf.push(sprintf("##[LIST] %s\n", id))
366 buf.push("##[LIST] +OK\n")
370 LEAGUE.players.each do |name, player|
371 buf.push(sprintf("##[WHO] %s\n", player.to_s))
373 buf.push("##[WHO] +OK\n")
376 @status = "connected"
377 write_safe("LOGOUT:completed\n")
380 ## ignore null string
382 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
392 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
393 def initialize(board, x, y, sente, promoted=false)
400 if ((x == 0) || (y == 0))
402 hands = board.sente_hands
404 hands = board.gote_hands
411 @board.array[x][y] = self
414 attr_accessor :promoted, :sente, :x, :y, :board
416 def room_of_head?(x, y, name)
421 return adjacent_movable_grids + far_movable_grids
424 def far_movable_grids
429 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
430 if ((@board.array[x][y] == nil) || # dst is empty
431 (@board.array[x][y].sente != @sente)) # dst is enemy
439 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
440 if (@board.array[x][y] == nil) # dst is empty?
447 def adjacent_movable_grids
450 moves = @promoted_moves
452 moves = @normal_moves
454 moves.each do |(dx, dy)|
461 if (jump_to?(cand_x, cand_y))
462 grids.push([cand_x, cand_y])
468 def move_to?(x, y, name)
469 return false if (! room_of_head?(x, y, name))
470 return false if ((name != @name) && (name != @promoted_name))
471 return false if (@promoted && (name != @promoted_name)) # can't un-promote
474 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
476 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
478 return false if ((6 >= @y) && (6 <= y) && (name != @name))
482 if ((@x == 0) || (@y == 0))
483 return jump_to?(x, y)
485 return movable_grids.include?([x, y])
490 if ((@x == 0) || (@y == 0))
492 @board.sente_hands.delete(self)
494 @board.gote_hands.delete(self)
496 @board.array[x][y] = self
497 elsif ((x == 0) || (y == 0))
499 @board.sente_hands.push(self)
501 @board.gote_hands.push(self)
503 @board.array[@x][@y] = nil
505 @board.array[@x][@y] = nil
506 @board.array[x][y] = self
535 class PieceFU < Piece
537 @normal_moves = [[0, +1]]
538 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
540 @promoted_name = "TO"
543 def room_of_head?(x, y, name)
546 return false if (y == 1)
548 return false if (y == 9)
554 if ((iy != @y) && # not source position
555 @board.array[x][iy] &&
556 (@board.array[x][iy].sente == @sente) && # mine
557 (@board.array[x][iy].name == "FU") &&
558 (@board.array[x][iy].promoted == false))
568 class PieceKY < Piece
571 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
573 @promoted_name = "NY"
576 def room_of_head?(x, y, name)
579 return false if (y == 1)
581 return false if (y == 9)
586 def far_movable_grids
594 while (jump_to?(cand_x, cand_y))
595 grids.push([cand_x, cand_y])
596 break if (! put_to?(cand_x, cand_y))
602 while (jump_to?(cand_x, cand_y))
603 grids.push([cand_x, cand_y])
604 break if (! put_to?(cand_x, cand_y))
612 class PieceKE < Piece
614 @normal_moves = [[+1, +2], [-1, +2]]
615 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
617 @promoted_name = "NK"
620 def room_of_head?(x, y, name)
623 return false if ((y == 1) || (y == 2))
625 return false if ((y == 9) || (y == 8))
631 class PieceGI < Piece
633 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
634 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
636 @promoted_name = "NG"
640 class PieceKI < Piece
642 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
649 class PieceKA < Piece
652 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
654 @promoted_name = "UM"
657 def far_movable_grids
662 while (jump_to?(cand_x, cand_y))
663 grids.push([cand_x, cand_y])
664 break if (! put_to?(cand_x, cand_y))
671 while (jump_to?(cand_x, cand_y))
672 grids.push([cand_x, cand_y])
673 break if (! put_to?(cand_x, cand_y))
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 class PieceHI < Piece
701 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
703 @promoted_name = "RY"
706 def far_movable_grids
711 while (jump_to?(cand_x, cand_y))
712 grids.push([cand_x, cand_y])
713 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))
727 while (jump_to?(cand_x, cand_y))
728 grids.push([cand_x, cand_y])
729 break if (! put_to?(cand_x, cand_y))
735 while (jump_to?(cand_x, cand_y))
736 grids.push([cand_x, cand_y])
737 break if (! put_to?(cand_x, cand_y))
743 class PieceOU < Piece
745 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
755 @sente_hands = Array::new
756 @gote_hands = Array::new
758 @sente_history = Hash::new
759 @gote_history = Hash::new
760 @array = [[], [], [], [], [], [], [], [], [], []]
762 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
765 PieceKY::new(self, 1, 1, false)
766 PieceKE::new(self, 2, 1, false)
767 PieceGI::new(self, 3, 1, false)
768 PieceKI::new(self, 4, 1, false)
769 PieceOU::new(self, 5, 1, false)
770 PieceKI::new(self, 6, 1, false)
771 PieceGI::new(self, 7, 1, false)
772 PieceKE::new(self, 8, 1, false)
773 PieceKY::new(self, 9, 1, false)
774 PieceKA::new(self, 2, 2, false)
775 PieceHI::new(self, 8, 2, false)
776 PieceFU::new(self, 1, 3, false)
777 PieceFU::new(self, 2, 3, false)
778 PieceFU::new(self, 3, 3, false)
779 PieceFU::new(self, 4, 3, false)
780 PieceFU::new(self, 5, 3, false)
781 PieceFU::new(self, 6, 3, false)
782 PieceFU::new(self, 7, 3, false)
783 PieceFU::new(self, 8, 3, false)
784 PieceFU::new(self, 9, 3, false)
786 PieceKY::new(self, 1, 9, true)
787 PieceKE::new(self, 2, 9, true)
788 PieceGI::new(self, 3, 9, true)
789 PieceKI::new(self, 4, 9, true)
790 PieceOU::new(self, 5, 9, true)
791 PieceKI::new(self, 6, 9, true)
792 PieceGI::new(self, 7, 9, true)
793 PieceKE::new(self, 8, 9, true)
794 PieceKY::new(self, 9, 9, true)
795 PieceKA::new(self, 8, 8, true)
796 PieceHI::new(self, 2, 8, true)
797 PieceFU::new(self, 1, 7, true)
798 PieceFU::new(self, 2, 7, true)
799 PieceFU::new(self, 3, 7, true)
800 PieceFU::new(self, 4, 7, true)
801 PieceFU::new(self, 5, 7, true)
802 PieceFU::new(self, 6, 7, true)
803 PieceFU::new(self, 7, 7, true)
804 PieceFU::new(self, 8, 7, true)
805 PieceFU::new(self, 9, 7, true)
808 def have_piece?(hands, name)
809 piece = hands.find { |i|
815 def move_to(x0, y0, x1, y1, name, sente)
822 if ((x0 == 0) || (y0 == 0))
823 piece = have_piece?(hands, name)
824 return :illegal if (! piece.move_to?(x1, y1, name))
825 piece.move_to(x1, y1)
826 piece.promoted = false
828 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
829 if (@array[x0][y0].name != name) # promoted ?
830 @array[x0][y0].promoted = true
833 if (@array[x1][y1].name == "OU")
834 return :outori # return board update
836 @array[x1][y1].sente = @array[x0][y0].sente
837 @array[x1][y1].move_to(0, 0)
842 @array[x0][y0].move_to(x1, y1)
847 def look_for_ou(sente)
853 (@array[x][y].name == "OU") &&
854 (@array[x][y].sente == sente))
861 raise "can't find ou"
864 def checkmated?(sente) # sente is loosing
865 ou = look_for_ou(sente)
871 (@array[x][y].sente != sente))
872 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
883 def uchifuzume?(sente)
884 rival_ou = look_for_ou(! sente) # rival's ou
885 if (sente) # rival is gote
886 if ((rival_ou.y != 9) &&
887 (@array[rival_ou.x][rival_ou.y + 1]) &&
888 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
889 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
891 fu_y = rival_ou.y + 1
896 if ((rival_ou.y != 0) &&
897 (@array[rival_ou.x][rival_ou.y - 1]) &&
898 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
899 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
901 fu_y = rival_ou.y - 1
907 ## case: rival_ou is moving
909 rival_ou.movable_grids.each do |(cand_x, cand_y)|
910 tmp_board = Marshal.load(Marshal.dump(self))
911 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
912 raise "internal error" if (s != true)
913 if (! tmp_board.checkmated?(! sente)) # good move
918 ## case: rival is capturing fu
924 (@array[x][y].sente != sente) &&
925 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
926 if (@array[x][y].promoted)
927 name = @array[x][y].promoted_name
929 name = @array[x][y].name
931 tmp_board = Marshal.load(Marshal.dump(self))
932 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
933 raise "internal error" if (s != true)
934 if (! tmp_board.checkmated?(! sente)) # good move
945 def oute_sennichite?(sente)
946 if (checkmated?(! sente))
949 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
953 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
961 def sennichite?(sente)
963 if (@history[str] && (@history[str] >= 3)) # already 3 times
969 def handle_one_move(str)
970 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
977 elsif (str =~ /^%KACHI/)
979 elsif (str =~ /^%TORYO/)
985 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
986 ((x0 != 0) || (y0 != 0)))
988 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1001 if ((x0 == 0) && (y0 == 0))
1002 return :illegal if (! have_piece?(hands, name))
1003 elsif (! @array[x0][y0])
1004 return :illegal # no piece
1005 elsif (@array[x0][y0].sente != sente)
1006 return :illegal # this is not mine
1007 elsif (@array[x0][y0].name != name)
1008 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1011 ## destination check
1012 if (@array[x1][y1] &&
1013 (@array[x1][y1].sente == sente)) # can't capture mine
1015 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1016 return :illegal # can't put on existing piece
1019 tmp_board = Marshal.load(Marshal.dump(self))
1020 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1021 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1022 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1023 return :sennichite if tmp_board.sennichite?(sente)
1025 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1029 move_to(x0, y0, x1, y1, name, sente)
1032 if (checkmated?(! sente))
1034 @sente_history[str] = (@sente_history[str] || 0) + 1
1036 @gote_history[str] = (@gote_history[str] || 0) + 1
1040 @sente_history.clear
1045 @history[str] = (@history[str] || 0) + 1
1053 a.push(sprintf("P%d", y))
1056 piece = @array[x][y]
1065 a.push(sprintf("\n"))
1068 if (! sente_hands.empty?)
1070 sente_hands.each do |p|
1071 a.push("00" + p.name)
1075 if (! gote_hands.empty?)
1077 gote_hands.each do |p|
1078 a.push("00" + p.name)
1088 def initialize(game_name, player0, player1)
1089 @monitors = Array::new
1090 @game_name = game_name
1091 if (@game_name =~ /-(\d+)-(\d+)$/)
1092 @total_time = $1.to_i
1103 @current_player = @sente
1104 @next_player = @gote
1112 @sente.status = "agree_waiting"
1113 @gote.status = "agree_waiting"
1114 @id = sprintf("%s+%s+%s+%s+%s",
1115 LEAGUE.event, @game_name, @sente.name, @gote.name,
1116 Time::new.strftime("%Y%m%d%H%M%S"))
1118 LEAGUE.games[@id] = self
1121 log_message(sprintf("game created %s", @id))
1123 @logfile = @id + ".csa"
1131 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1132 attr_accessor :last_move, :current_turn
1134 def monitoron(monitor)
1135 @monitors.delete(monitor)
1136 @monitors.push(monitor)
1139 def monitoroff(monitor)
1140 @monitors.delete(monitor)
1143 def reject(rejector)
1144 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1145 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1150 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1152 elsif (@current_player == killer)
1159 log_message(sprintf("game finished %s", @id))
1160 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1165 @sente.status = "connected"
1166 @gote.status = "connected"
1168 if (@current_player.protocol == "CSA")
1169 @current_player.finish
1171 if (@next_player.protocol == "CSA")
1174 @monitors = Array::new
1177 @current_player = nil
1179 LEAGUE.games.delete(@id)
1182 def handle_one_move(str, player)
1184 if (@current_player == player)
1185 @end_time = Time::new
1186 t = (@end_time - @start_time).ceil
1187 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1190 if ((@current_player.mytime - t <= 0) && (@total_time > 0))
1192 elsif (str == :timeout)
1193 return false # time isn't expired. players aren't swapped. continue game
1196 move_status = @board.handle_one_move(str)
1198 # log_error("handle_one_move raise exception for #{str}")
1199 # move_status = :illegal
1201 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1202 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1204 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1205 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1206 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1207 @fh.printf("%s\nT%d\n", str, t)
1208 @last_move = sprintf("%s,T%d", str, t)
1209 @current_turn = @current_turn + 1
1211 @monitors.each do |monitor|
1212 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1213 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1218 if (@current_player.mytime - t < @byoyomi)
1219 @current_player.mytime = @byoyomi
1221 @current_player.mytime = @current_player.mytime - t
1224 if (@next_player.status != "game") # rival is logout or disconnected
1226 elsif (status == :timeout)
1228 elsif (move_status == :illegal)
1230 elsif (move_status == :kachi)
1232 elsif (move_status == :toryo)
1234 elsif (move_status == :outori)
1236 elsif (move_status == :sennichite)
1238 elsif (move_status == :oute_sennichite)
1239 oute_sennichite_lose()
1240 elsif (move_status == :uchifuzume)
1242 elsif (move_status == :oute_kaihimore)
1243 oute_kaihimore_lose()
1247 finish() if finish_flag
1248 (@current_player, @next_player) = [@next_player, @current_player]
1249 @start_time = Time::new
1255 @current_player.status = "connected"
1256 @next_player.status = "connected"
1257 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1258 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1259 @fh.printf("%%TORYO\n")
1260 @fh.print(@board.to_s.gsub(/^/, "\'"))
1261 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1262 @monitors.each do |monitor|
1263 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1268 @current_player.status = "connected"
1269 @next_player.status = "connected"
1270 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1271 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1272 @fh.printf("%%TORYO\n")
1273 @fh.print(@board.to_s.gsub(/^/, "\'"))
1274 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1275 @monitors.each do |monitor|
1276 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1281 @current_player.status = "connected"
1282 @next_player.status = "connected"
1283 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1284 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1285 @fh.print(@board.to_s.gsub(/^/, "\'"))
1286 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1287 @monitors.each do |monitor|
1288 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1292 def oute_sennichite_lose
1293 @current_player.status = "connected"
1294 @next_player.status = "connected"
1295 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1296 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1297 @fh.print(@board.to_s.gsub(/^/, "\'"))
1298 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1299 @monitors.each do |monitor|
1300 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1305 @current_player.status = "connected"
1306 @next_player.status = "connected"
1307 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1308 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1309 @fh.print(@board.to_s.gsub(/^/, "\'"))
1310 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1311 @monitors.each do |monitor|
1312 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1317 @current_player.status = "connected"
1318 @next_player.status = "connected"
1319 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1320 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1321 @fh.print(@board.to_s.gsub(/^/, "\'"))
1322 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1323 @monitors.each do |monitor|
1324 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1328 def oute_kaihimore_lose
1329 @current_player.status = "connected"
1330 @next_player.status = "connected"
1331 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1332 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1333 @fh.print(@board.to_s.gsub(/^/, "\'"))
1334 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1335 @monitors.each do |monitor|
1336 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1341 @current_player.status = "connected"
1342 @next_player.status = "connected"
1343 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1344 @next_player.write_safe("#TIME_UP\n#WIN\n")
1345 @fh.print(@board.to_s.gsub(/^/, "\'"))
1346 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1347 @monitors.each do |monitor|
1348 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1353 @current_player.status = "connected"
1354 @next_player.status = "connected"
1355 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1356 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1357 @fh.printf("%%KACHI\n")
1358 @fh.print(@board.to_s.gsub(/^/, "\'"))
1359 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1360 @monitors.each do |monitor|
1361 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1366 @current_player.status = "connected"
1367 @next_player.status = "connected"
1368 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1369 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1370 @fh.printf("%%TORYO\n")
1371 @fh.print(@board.to_s.gsub(/^/, "\'"))
1372 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1373 @monitors.each do |monitor|
1374 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1379 @current_player.status = "connected"
1380 @next_player.status = "connected"
1381 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1382 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1383 @fh.print(@board.to_s.gsub(/^/, "\'"))
1384 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1385 @monitors.each do |monitor|
1386 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1391 log_message(sprintf("game started %s", @id))
1392 @sente.write_safe(sprintf("START:%s\n", @id))
1393 @gote.write_safe(sprintf("START:%s\n", @id))
1394 @sente.mytime = @total_time
1395 @gote.mytime = @total_time
1396 @start_time = Time::new
1401 @fh = open(@logfile, "w")
1405 @fh.printf("N+%s\n", @sente.name)
1406 @fh.printf("N-%s\n", @gote.name)
1407 @fh.printf("$EVENT:%s\n", @id)
1409 @sente.write_safe(propose_message("+"))
1410 @gote.write_safe(propose_message("-"))
1412 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1414 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1415 P2 * -HI * * * * * -KA *
1416 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1417 P4 * * * * * * * * *
1418 P5 * * * * * * * * *
1419 P6 * * * * * * * * *
1420 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1421 P8 * +KA * * * * * +HI *
1422 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1431 Protocol_Version:1.0
1432 Protocol_Mode:Server
1435 Name+:#{@sente.name}
1441 Total_Time:#{@total_time}
1443 Least_Time_Per_Move:#{Least_Time_Per_Move}
1444 Remaining_Time+:#{@sente.mytime}
1445 Remaining_Time-:#{@gote.mytime}
1446 Last_Move:#{@last_move}
1447 Current_Turn:#{@current_turn}
1450 Jishogi_Declaration:1.1
1458 return str0 + @board.to_s + str1
1461 def propose_message(sg_flag)
1464 Protocol_Version:1.0
1465 Protocol_Mode:Server
1468 Name+:#{@sente.name}
1470 Your_Turn:#{sg_flag}
1475 Total_Time:#{@total_time}
1477 Least_Time_Per_Move:#{Least_Time_Per_Move}
1480 Jishogi_Declaration:1.1
1481 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1482 P2 * -HI * * * * * -KA *
1483 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1484 P4 * * * * * * * * *
1485 P5 * * * * * * * * *
1486 P6 * * * * * * * * *
1487 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1488 P8 * +KA * * * * * +HI *
1489 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1503 shogi-server - server for CSA server protocol
1506 shogi-server event_name port_number
1509 server for CSA server protocol
1513 specify filename for logging process ID
1516 this file is distributed under GPL version2 and might be compiled by Exerb
1528 def log_message(str)
1529 printf("%s message: %s\n", Time::new.to_s, str)
1532 def log_warning(str)
1533 printf("%s warning: %s\n", Time::new.to_s, str)
1537 printf("%s error: %s\n", Time::new.to_s, str)
1541 def parse_command_line
1543 parser = GetoptLong.new
1544 parser.ordering = GetoptLong::REQUIRE_ORDER
1546 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1550 parser.each_option do |name, arg|
1551 name.sub!(/^--/, '')
1552 options[name] = arg.dup
1556 raise parser.error_message
1561 def good_game_name?(str)
1562 if ((str =~ /^(.+)-\d+-\d+$/) &&
1563 (good_identifier?($1)))
1570 def good_identifier?(str)
1571 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1572 (str.length < Max_Identifier_Length))
1579 def good_login?(str)
1581 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1582 (tokens[0] == "LOGIN") &&
1583 (good_identifier?(tokens[1])))
1590 def write_pid_file(file)
1591 open(file, "w") do |fh|
1592 fh.print Process::pid, "\n"
1596 def mutex_watchdog(mutex, sec)
1605 log_error("mutex watchdog timeout")
1614 mutex_watchdog($mutex, 10)
1617 $options = parse_command_line
1618 if (ARGV.length != 2)
1623 LEAGUE.event = ARGV.shift
1626 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1629 Thread.abort_on_exception = true
1631 server = TCPserver.open(port)
1632 log_message("server started")
1635 Thread::start(server.accept) do |client|
1638 while (str = client.gets_timeout(Login_Time))
1643 if (good_login?(str))
1644 player = Player::new(str, client)
1645 if (LEAGUE.players[player.name])
1646 if ((LEAGUE.players[player.name].password == player.password) &&
1647 (LEAGUE.players[player.name].status != "game"))
1648 log_message(sprintf("user %s login forcely", player.name))
1649 LEAGUE.players[player.name].kill
1651 client.write_safe("LOGIN:incorrect" + eol)
1652 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1660 client.write_safe("LOGIN:incorrect" + eol)
1661 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1671 log_message(sprintf("user %s login", player.name))
1676 player.game.kill(player)
1679 LEAGUE.delete(player)
1680 log_message(sprintf("user %s logout", player.name))
1689 LEAGUE = League::new