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
43 Thread.abort_on_exception = true
47 def gets_timeout(t = Default_Timeout)
58 def gets_safe(t = nil)
79 return self.write(str)
93 attr_accessor :players, :games, :event
96 @players[player.name] = player
99 @players.delete(player.name)
101 def get_player(status, game_name, sente, searcher=nil)
102 @players.each do |name, player|
103 if ((player.status == status) &&
104 (player.game_name == game_name) &&
105 ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
106 ((searcher == nil) || (player != searcher)))
115 def initialize(str, socket)
119 @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
121 @protocol = nil # CSA or x1
122 @eol = "\m" # favorite eol code
125 @mytime = 0 # set in start method also
129 @write_queue = Queue::new
133 attr_accessor :name, :password, :socket, :status
134 attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
135 attr_accessor :main_thread, :writer_thread, :write_queue
137 log_message(sprintf("user %s killed", @name))
142 Thread::kill(@main_thread) if @main_thread
146 if (@status != "finished")
148 log_message(sprintf("user %s finish", @name))
149 Thread::kill(@writer_thread) if @writer_thread
151 @socket.close if (! @socket.closed?)
153 log_message(sprintf("user %s finish failed", @name))
159 @write_queue.push(str.gsub(/[\r\n]+/, @eol))
163 while (str = @write_queue.pop)
164 @socket.write_safe(str)
169 if ((status == "game_waiting") ||
170 (status == "start_waiting") ||
171 (status == "agree_waiting") ||
174 return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
175 elsif (@sente == false)
176 return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
177 elsif (@sente == nil)
178 return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
181 return sprintf("%s %s %s", @name, @protocol, @status)
186 @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
193 (login, @name, @password, ext) = str.split
199 @main_thread = Thread::current
200 @writer_thread = Thread::start do
206 write_safe(sprintf("LOGIN:%s OK\n", @name))
207 if (@protocol != "CSA")
208 log_message(sprintf("user %s run in %s mode", @name, @protocol))
209 write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
211 log_message(sprintf("user %s run in CSA mode", @name))
212 if (good_game_name?(@password))
213 csa_1st_str = "%%GAME #{@password} *"
215 csa_1st_str = "%%GAME #{Default_Game_Name} *"
219 while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
226 if (@write_queue.size > Max_Write_Queue_Size)
227 log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
231 if (@status == "finished")
234 str.chomp! if (str.class == String)
237 if (@status == "game")
238 array_str = str.split(",")
239 move = array_str.shift
240 additional = array_str.shift
241 if /^'(.*)/ =~ additional
242 comment = array_str.unshift("'*#{$1}")
244 s = @game.handle_one_move(move, self)
245 @game.fh.print("#{comment}\n") if (comment && !s)
246 return if (s && @protocol == "CSA")
248 when /^%[^%]/, :timeout
249 if (@status == "game")
250 s = @game.handle_one_move(str, self)
251 return if (s && @protocol == "CSA")
254 if (@status == "agree_waiting")
256 return if (@protocol == "CSA")
258 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
261 if (@status == "agree_waiting")
262 @status = "start_waiting"
263 if ((@game.sente.status == "start_waiting") &&
264 (@game.gote.status == "start_waiting"))
266 @game.sente.status = "game"
267 @game.gote.status = "game"
270 write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
272 when /^%%SHOW\s+(\S+)/
274 if (LEAGUE.games[game_id])
275 write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
277 write_safe("##[SHOW] +OK\n")
278 when /^%%MONITORON\s+(\S+)/
280 if (LEAGUE.games[game_id])
281 LEAGUE.games[game_id].monitoron(self)
282 write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
283 write_safe("##[MONITOR][#{game_id}] +OK\n")
285 when /^%%MONITOROFF\s+(\S+)/
287 if (LEAGUE.games[game_id])
288 LEAGUE.games[game_id].monitoroff(self)
293 if ((@status == "connected") || (@status == "game_waiting"))
294 @status = "connected"
297 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
299 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
303 if (! good_game_name?(game_name))
304 write_safe(sprintf("##[ERROR] bad game name\n"))
306 elsif ((@status == "connected") || (@status == "game_waiting"))
309 write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
312 if ((my_sente_str == "*") ||
313 (my_sente_str == "+") ||
314 (my_sente_str == "-"))
317 write_safe(sprintf("##[ERROR] bad game option\n"))
321 if (my_sente_str == "*")
322 rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
323 elsif (my_sente_str == "+")
324 rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
325 elsif (my_sente_str == "-")
326 rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
331 @game_name = game_name
332 if ((my_sente_str == "*") && (rival.sente == nil))
340 elsif (rival.sente == true) # rival has higher priority
342 elsif (rival.sente == false)
344 elsif (my_sente_str == "+")
347 elsif (my_sente_str == "-")
353 Game::new(@game_name, self, rival)
354 self.status = "agree_waiting"
355 rival.status = "agree_waiting"
356 else # rival not found
357 if (command_name == "GAME")
358 @status = "game_waiting"
359 @game_name = game_name
360 if (my_sente_str == "+")
362 elsif (my_sente_str == "-")
368 write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
369 @status = "connected"
374 when /^%%CHAT\s+(.+)/
376 LEAGUE.players.each do |name, player|
377 if (player.protocol != "CSA")
378 player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
383 LEAGUE.games.each do |id, game|
384 buf.push(sprintf("##[LIST] %s\n", id))
386 buf.push("##[LIST] +OK\n")
390 LEAGUE.players.each do |name, player|
391 buf.push(sprintf("##[WHO] %s\n", player.to_s))
393 buf.push("##[WHO] +OK\n")
396 @status = "connected"
397 write_safe("LOGOUT:completed\n")
400 ## ignore null string
402 write_safe(sprintf("##[ERROR] unknown command %s\n", str))
412 PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
413 def initialize(board, x, y, sente, promoted=false)
420 if ((x == 0) || (y == 0))
422 hands = board.sente_hands
424 hands = board.gote_hands
431 @board.array[x][y] = self
434 attr_accessor :promoted, :sente, :x, :y, :board
436 def room_of_head?(x, y, name)
441 return adjacent_movable_grids + far_movable_grids
444 def far_movable_grids
449 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
450 if ((@board.array[x][y] == nil) || # dst is empty
451 (@board.array[x][y].sente != @sente)) # dst is enemy
459 if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
460 if (@board.array[x][y] == nil) # dst is empty?
467 def adjacent_movable_grids
470 moves = @promoted_moves
472 moves = @normal_moves
474 moves.each do |(dx, dy)|
481 if (jump_to?(cand_x, cand_y))
482 grids.push([cand_x, cand_y])
488 def move_to?(x, y, name)
489 return false if (! room_of_head?(x, y, name))
490 return false if ((name != @name) && (name != @promoted_name))
491 return false if (@promoted && (name != @promoted_name)) # can't un-promote
494 return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
496 return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
498 return false if ((6 >= @y) && (6 >= y) && (name != @name))
502 if ((@x == 0) || (@y == 0))
503 return jump_to?(x, y)
505 return movable_grids.include?([x, y])
510 if ((@x == 0) || (@y == 0))
512 @board.sente_hands.delete(self)
514 @board.gote_hands.delete(self)
516 @board.array[x][y] = self
517 elsif ((x == 0) || (y == 0))
518 @promoted = false # clear promoted flag before moving to hands
520 @board.sente_hands.push(self)
522 @board.gote_hands.push(self)
524 @board.array[@x][@y] = nil
526 @board.array[@x][@y] = nil
527 @board.array[x][y] = self
560 class PieceFU < Piece
563 @normal_moves = [[0, +1]]
564 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
566 @promoted_name = "TO"
569 def room_of_head?(x, y, name)
572 return false if (y == 1)
574 return false if (y == 9)
580 if ((iy != @y) && # not source position
581 @board.array[x][iy] &&
582 (@board.array[x][iy].sente == @sente) && # mine
583 (@board.array[x][iy].name == "FU") &&
584 (@board.array[x][iy].promoted == false))
594 class PieceKY < Piece
598 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
600 @promoted_name = "NY"
603 def room_of_head?(x, y, name)
606 return false if (y == 1)
608 return false if (y == 9)
613 def far_movable_grids
621 while (jump_to?(cand_x, cand_y))
622 grids.push([cand_x, cand_y])
623 break if (! put_to?(cand_x, cand_y))
629 while (jump_to?(cand_x, cand_y))
630 grids.push([cand_x, cand_y])
631 break if (! put_to?(cand_x, cand_y))
639 class PieceKE < Piece
642 @normal_moves = [[+1, +2], [-1, +2]]
643 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
645 @promoted_name = "NK"
648 def room_of_head?(x, y, name)
651 return false if ((y == 1) || (y == 2))
653 return false if ((y == 9) || (y == 8))
659 class PieceGI < Piece
662 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
663 @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
665 @promoted_name = "NG"
669 class PieceKI < Piece
672 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
679 class PieceKA < Piece
683 @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
685 @promoted_name = "UM"
688 def far_movable_grids
693 while (jump_to?(cand_x, cand_y))
694 grids.push([cand_x, cand_y])
695 break if (! put_to?(cand_x, cand_y))
702 while (jump_to?(cand_x, cand_y))
703 grids.push([cand_x, cand_y])
704 break if (! put_to?(cand_x, cand_y))
711 while (jump_to?(cand_x, cand_y))
712 grids.push([cand_x, cand_y])
713 break if (! put_to?(cand_x, cand_y))
720 while (jump_to?(cand_x, cand_y))
721 grids.push([cand_x, cand_y])
722 break if (! put_to?(cand_x, cand_y))
729 class PieceHI < Piece
733 @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
735 @promoted_name = "RY"
738 def far_movable_grids
743 while (jump_to?(cand_x, cand_y))
744 grids.push([cand_x, cand_y])
745 break if (! put_to?(cand_x, cand_y))
751 while (jump_to?(cand_x, cand_y))
752 grids.push([cand_x, cand_y])
753 break if (! put_to?(cand_x, cand_y))
759 while (jump_to?(cand_x, cand_y))
760 grids.push([cand_x, cand_y])
761 break if (! put_to?(cand_x, cand_y))
767 while (jump_to?(cand_x, cand_y))
768 grids.push([cand_x, cand_y])
769 break if (! put_to?(cand_x, cand_y))
775 class PieceOU < Piece
778 @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
788 @sente_hands = Array::new
789 @gote_hands = Array::new
791 @sente_history = Hash::new
792 @gote_history = Hash::new
793 @array = [[], [], [], [], [], [], [], [], [], []]
795 attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
798 PieceKY::new(self, 1, 1, false)
799 PieceKE::new(self, 2, 1, false)
800 PieceGI::new(self, 3, 1, false)
801 PieceKI::new(self, 4, 1, false)
802 PieceOU::new(self, 5, 1, false)
803 PieceKI::new(self, 6, 1, false)
804 PieceGI::new(self, 7, 1, false)
805 PieceKE::new(self, 8, 1, false)
806 PieceKY::new(self, 9, 1, false)
807 PieceKA::new(self, 2, 2, false)
808 PieceHI::new(self, 8, 2, false)
809 PieceFU::new(self, 1, 3, false)
810 PieceFU::new(self, 2, 3, false)
811 PieceFU::new(self, 3, 3, false)
812 PieceFU::new(self, 4, 3, false)
813 PieceFU::new(self, 5, 3, false)
814 PieceFU::new(self, 6, 3, false)
815 PieceFU::new(self, 7, 3, false)
816 PieceFU::new(self, 8, 3, false)
817 PieceFU::new(self, 9, 3, false)
819 PieceKY::new(self, 1, 9, true)
820 PieceKE::new(self, 2, 9, true)
821 PieceGI::new(self, 3, 9, true)
822 PieceKI::new(self, 4, 9, true)
823 PieceOU::new(self, 5, 9, true)
824 PieceKI::new(self, 6, 9, true)
825 PieceGI::new(self, 7, 9, true)
826 PieceKE::new(self, 8, 9, true)
827 PieceKY::new(self, 9, 9, true)
828 PieceKA::new(self, 8, 8, true)
829 PieceHI::new(self, 2, 8, true)
830 PieceFU::new(self, 1, 7, true)
831 PieceFU::new(self, 2, 7, true)
832 PieceFU::new(self, 3, 7, true)
833 PieceFU::new(self, 4, 7, true)
834 PieceFU::new(self, 5, 7, true)
835 PieceFU::new(self, 6, 7, true)
836 PieceFU::new(self, 7, 7, true)
837 PieceFU::new(self, 8, 7, true)
838 PieceFU::new(self, 9, 7, true)
841 def have_piece?(hands, name)
842 piece = hands.find { |i|
848 def move_to(x0, y0, x1, y1, name, sente)
855 if ((x0 == 0) || (y0 == 0))
856 piece = have_piece?(hands, name)
857 return :illegal if (! piece.move_to?(x1, y1, name))
858 piece.move_to(x1, y1)
860 return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
861 if (@array[x0][y0].name != name) # promoted ?
862 @array[x0][y0].promoted = true
865 if (@array[x1][y1].name == "OU")
866 return :outori # return board update
868 @array[x1][y1].sente = @array[x0][y0].sente
869 @array[x1][y1].move_to(0, 0)
874 @array[x0][y0].move_to(x1, y1)
879 def look_for_ou(sente)
885 (@array[x][y].name == "OU") &&
886 (@array[x][y].sente == sente))
893 raise "can't find ou"
896 def checkmated?(sente) # sente is loosing
897 ou = look_for_ou(sente)
903 (@array[x][y].sente != sente))
904 if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
915 def uchifuzume?(sente)
916 rival_ou = look_for_ou(! sente) # rival's ou
917 if (sente) # rival is gote
918 if ((rival_ou.y != 9) &&
919 (@array[rival_ou.x][rival_ou.y + 1]) &&
920 (@array[rival_ou.x][rival_ou.y + 1].name == "FU") &&
921 (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true
923 fu_y = rival_ou.y + 1
928 if ((rival_ou.y != 0) &&
929 (@array[rival_ou.x][rival_ou.y - 1]) &&
930 (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
931 (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
933 fu_y = rival_ou.y - 1
939 ## case: rival_ou is moving
941 rival_ou.movable_grids.each do |(cand_x, cand_y)|
942 tmp_board = Marshal.load(Marshal.dump(self))
943 s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
944 raise "internal error" if (s != true)
945 if (! tmp_board.checkmated?(! sente)) # good move
950 ## case: rival is capturing fu
956 (@array[x][y].sente != sente) &&
957 @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
958 if (@array[x][y].promoted)
959 name = @array[x][y].promoted_name
961 name = @array[x][y].name
963 tmp_board = Marshal.load(Marshal.dump(self))
964 s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
965 raise "internal error" if (s != true)
966 if (! tmp_board.checkmated?(! sente)) # good move
977 def oute_sennichite?(sente)
978 if (checkmated?(! sente))
981 if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
985 if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
993 def sennichite?(sente)
995 if (@history[str] && (@history[str] >= 3)) # already 3 times
1001 def good_kachi?(sente)
1002 return false if (checkmated?(sente))
1003 ou = look_for_ou(sente)
1004 return false if (sente && (ou.y >= 4))
1005 return false if (! sente && (ou.y <= 6))
1011 hands = @sente_hands
1021 (@array[x][y].sente == sente) &&
1022 (@array[x][y].point > 0))
1023 point = point + @array[x][y].point
1029 hands.each do |piece|
1030 point = point + piece.point
1033 return false if (number < 10)
1035 return false if (point < 28)
1037 return false if (point < 27)
1042 def handle_one_move(str)
1043 if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
1050 elsif (str =~ /^%KACHI/)
1051 if (@sente == @current_player)
1056 if (good_kachi?(sente))
1061 elsif (str =~ /^%TORYO/)
1067 if (((x0 == 0) || (y0 == 0)) && # source is not from hand
1068 ((x0 != 0) || (y0 != 0)))
1070 elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
1076 hands = @sente_hands
1083 if ((x0 == 0) && (y0 == 0))
1084 return :illegal if (! have_piece?(hands, name))
1085 elsif (! @array[x0][y0])
1086 return :illegal # no piece
1087 elsif (@array[x0][y0].sente != sente)
1088 return :illegal # this is not mine
1089 elsif (@array[x0][y0].name != name)
1090 return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
1093 ## destination check
1094 if (@array[x1][y1] &&
1095 (@array[x1][y1].sente == sente)) # can't capture mine
1097 elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
1098 return :illegal # can't put on existing piece
1101 tmp_board = Marshal.load(Marshal.dump(self))
1102 return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
1103 return :oute_kaihimore if (tmp_board.checkmated?(sente))
1104 return :oute_sennichite if tmp_board.oute_sennichite?(sente)
1105 return :sennichite if tmp_board.sennichite?(sente)
1107 if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
1111 move_to(x0, y0, x1, y1, name, sente)
1114 if (checkmated?(! sente))
1116 @sente_history[str] = (@sente_history[str] || 0) + 1
1118 @gote_history[str] = (@gote_history[str] || 0) + 1
1122 @sente_history.clear
1127 @history[str] = (@history[str] || 0) + 1
1135 a.push(sprintf("P%d", y))
1138 piece = @array[x][y]
1147 a.push(sprintf("\n"))
1150 if (! sente_hands.empty?)
1152 sente_hands.each do |p|
1153 a.push("00" + p.name)
1157 if (! gote_hands.empty?)
1159 gote_hands.each do |p|
1160 a.push("00" + p.name)
1173 def initialize(game_name, player0, player1)
1174 @monitors = Array::new
1175 @game_name = game_name
1176 if (@game_name =~ /-(\d+)-(\d+)$/)
1177 @total_time = $1.to_i
1188 @current_player = @sente
1189 @next_player = @gote
1197 @sente.status = "agree_waiting"
1198 @gote.status = "agree_waiting"
1200 @id = sprintf("%s+%s+%s+%s+%s",
1201 LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
1202 @logfile = @id + ".csa"
1204 LEAGUE.games[@id] = self
1206 log_message(sprintf("game created %s", @id))
1215 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
1216 attr_accessor :last_move, :current_turn
1218 def monitoron(monitor)
1219 @monitors.delete(monitor)
1220 @monitors.push(monitor)
1223 def monitoroff(monitor)
1224 @monitors.delete(monitor)
1227 def reject(rejector)
1228 @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1229 @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
1234 if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
1236 elsif (@current_player == killer)
1243 log_message(sprintf("game finished %s", @id))
1244 @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1249 @sente.status = "connected"
1250 @gote.status = "connected"
1252 if (@current_player.protocol == "CSA")
1253 @current_player.finish
1255 if (@next_player.protocol == "CSA")
1258 @monitors = Array::new
1261 @current_player = nil
1263 LEAGUE.games.delete(@id)
1266 def handle_one_move(str, player)
1268 if (@current_player == player)
1269 @end_time = Time::new
1270 t = (@end_time - @start_time).ceil
1271 t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
1274 if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
1276 elsif (str == :timeout)
1277 return false # time isn't expired. players aren't swapped. continue game
1279 @current_player.mytime = @current_player.mytime - t
1280 if (@current_player.mytime < 0)
1281 @current_player.mytime = 0
1285 move_status = @board.handle_one_move(str)
1287 # log_error("handle_one_move raise exception for #{str}")
1288 # move_status = :illegal
1291 if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
1292 @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
1294 if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
1295 @sente.write_safe(sprintf("%s,T%d\n", str, t))
1296 @gote.write_safe(sprintf("%s,T%d\n", str, t))
1297 @fh.printf("%s\nT%d\n", str, t)
1298 @last_move = sprintf("%s,T%d", str, t)
1299 @current_turn = @current_turn + 1
1302 @monitors.each do |monitor|
1303 monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
1304 monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
1309 if (@next_player.status != "game") # rival is logout or disconnected
1311 elsif (status == :timeout)
1313 elsif (move_status == :illegal)
1315 elsif (move_status == :kachi_win)
1317 elsif (move_status == :kachi_lose)
1319 elsif (move_status == :toryo)
1321 elsif (move_status == :outori)
1323 elsif (move_status == :sennichite)
1325 elsif (move_status == :oute_sennichite)
1326 oute_sennichite_lose()
1327 elsif (move_status == :uchifuzume)
1329 elsif (move_status == :oute_kaihimore)
1330 oute_kaihimore_lose()
1334 finish() if finish_flag
1335 (@current_player, @next_player) = [@next_player, @current_player]
1336 @start_time = Time::new
1342 @current_player.status = "connected"
1343 @next_player.status = "connected"
1344 @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1345 @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1346 @fh.printf("%%TORYO\n")
1347 @fh.print(@board.to_s.gsub(/^/, "\'"))
1348 @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
1349 @monitors.each do |monitor|
1350 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1355 @current_player.status = "connected"
1356 @next_player.status = "connected"
1357 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1358 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1359 @fh.printf("%%TORYO\n")
1360 @fh.print(@board.to_s.gsub(/^/, "\'"))
1361 @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
1362 @monitors.each do |monitor|
1363 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1368 @current_player.status = "connected"
1369 @next_player.status = "connected"
1370 @current_player.write_safe("#SENNICHITE\n#DRAW\n")
1371 @next_player.write_safe("#SENNICHITE\n#DRAW\n")
1372 @fh.print(@board.to_s.gsub(/^/, "\'"))
1373 @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
1374 @monitors.each do |monitor|
1375 monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
1379 def oute_sennichite_lose
1380 @current_player.status = "connected"
1381 @next_player.status = "connected"
1382 @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
1383 @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
1384 @fh.print(@board.to_s.gsub(/^/, "\'"))
1385 @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
1386 @monitors.each do |monitor|
1387 monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
1392 @current_player.status = "connected"
1393 @next_player.status = "connected"
1394 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1395 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1396 @fh.print(@board.to_s.gsub(/^/, "\'"))
1397 @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
1398 @monitors.each do |monitor|
1399 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1404 @current_player.status = "connected"
1405 @next_player.status = "connected"
1406 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1407 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1408 @fh.print(@board.to_s.gsub(/^/, "\'"))
1409 @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
1410 @monitors.each do |monitor|
1411 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1415 def oute_kaihimore_lose
1416 @current_player.status = "connected"
1417 @next_player.status = "connected"
1418 @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1419 @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1420 @fh.print(@board.to_s.gsub(/^/, "\'"))
1421 @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
1422 @monitors.each do |monitor|
1423 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1428 @current_player.status = "connected"
1429 @next_player.status = "connected"
1430 @current_player.write_safe("#TIME_UP\n#LOSE\n")
1431 @next_player.write_safe("#TIME_UP\n#WIN\n")
1432 @fh.print(@board.to_s.gsub(/^/, "\'"))
1433 @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
1434 @monitors.each do |monitor|
1435 monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
1440 @current_player.status = "connected"
1441 @next_player.status = "connected"
1442 @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
1443 @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
1444 @fh.printf("%%KACHI\n")
1445 @fh.print(@board.to_s.gsub(/^/, "\'"))
1446 @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
1447 @monitors.each do |monitor|
1448 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1453 @current_player.status = "connected"
1454 @next_player.status = "connected"
1455 @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
1456 @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
1457 @fh.printf("%%KACHI\n")
1458 @fh.print(@board.to_s.gsub(/^/, "\'"))
1459 @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
1460 @monitors.each do |monitor|
1461 monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
1466 @current_player.status = "connected"
1467 @next_player.status = "connected"
1468 @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
1469 @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
1470 @fh.printf("%%TORYO\n")
1471 @fh.print(@board.to_s.gsub(/^/, "\'"))
1472 @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
1473 @monitors.each do |monitor|
1474 monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
1479 @current_player.status = "connected"
1480 @next_player.status = "connected"
1481 @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
1482 @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
1483 @fh.print(@board.to_s.gsub(/^/, "\'"))
1484 @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
1485 @monitors.each do |monitor|
1486 monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
1491 log_message(sprintf("game started %s", @id))
1492 @sente.write_safe(sprintf("START:%s\n", @id))
1493 @gote.write_safe(sprintf("START:%s\n", @id))
1494 @sente.mytime = @total_time
1495 @gote.mytime = @total_time
1496 @start_time = Time::new
1501 @fh = open(@logfile, "w")
1505 @fh.printf("N+%s\n", @sente.name)
1506 @fh.printf("N-%s\n", @gote.name)
1507 @fh.printf("$EVENT:%s\n", @id)
1509 @sente.write_safe(propose_message("+"))
1510 @gote.write_safe(propose_message("-"))
1512 @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
1514 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1515 P2 * -HI * * * * * -KA *
1516 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1517 P4 * * * * * * * * *
1518 P5 * * * * * * * * *
1519 P6 * * * * * * * * *
1520 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1521 P8 * +KA * * * * * +HI *
1522 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1531 Protocol_Version:1.1
1532 Protocol_Mode:Server
1534 Declaration:Jishogi 1.1
1536 Name+:#{@sente.name}
1542 Total_Time:#{@total_time}
1544 Least_Time_Per_Move:#{Least_Time_Per_Move}
1545 Remaining_Time+:#{@sente.mytime}
1546 Remaining_Time-:#{@gote.mytime}
1547 Last_Move:#{@last_move}
1548 Current_Turn:#{@current_turn}
1558 return str0 + @board.to_s + str1
1561 def propose_message(sg_flag)
1564 Protocol_Version:1.1
1565 Protocol_Mode:Server
1567 Declaration:Jishogi 1.1
1569 Name+:#{@sente.name}
1571 Your_Turn:#{sg_flag}
1576 Total_Time:#{@total_time}
1578 Least_Time_Per_Move:#{Least_Time_Per_Move}
1581 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
1582 P2 * -HI * * * * * -KA *
1583 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
1584 P4 * * * * * * * * *
1585 P5 * * * * * * * * *
1586 P6 * * * * * * * * *
1587 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
1588 P8 * +KA * * * * * +HI *
1589 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
1601 def issue_current_time
1602 time = Time::new.strftime("%Y%m%d%H%M%S").to_i
1603 @@mutex.synchronize do
1604 while time <= @@time do
1615 shogi-server - server for CSA server protocol
1618 shogi-server event_name port_number
1621 server for CSA server protocol
1625 specify filename for logging process ID
1628 this file is distributed under GPL version2 and might be compiled by Exerb
1640 def log_message(str)
1641 printf("%s message: %s\n", Time::new.to_s, str)
1644 def log_warning(str)
1645 printf("%s warning: %s\n", Time::new.to_s, str)
1649 printf("%s error: %s\n", Time::new.to_s, str)
1653 def parse_command_line
1655 parser = GetoptLong.new
1656 parser.ordering = GetoptLong::REQUIRE_ORDER
1658 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
1662 parser.each_option do |name, arg|
1663 name.sub!(/^--/, '')
1664 options[name] = arg.dup
1668 raise parser.error_message
1673 def good_game_name?(str)
1674 if ((str =~ /^(.+)-\d+-\d+$/) &&
1675 (good_identifier?($1)))
1682 def good_identifier?(str)
1683 if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
1684 (str.length < Max_Identifier_Length))
1691 def good_login?(str)
1693 if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
1694 (tokens[0] == "LOGIN") &&
1695 (good_identifier?(tokens[1])))
1702 def write_pid_file(file)
1703 open(file, "w") do |fh|
1704 fh.print Process::pid, "\n"
1708 def mutex_watchdog(mutex, sec)
1720 log_error("mutex watchdog timeout")
1730 mutex_watchdog($mutex, 10)
1733 $options = parse_command_line
1734 if (ARGV.length != 2)
1739 LEAGUE.event = ARGV.shift
1742 write_pid_file($options["pid-file"]) if ($options["pid-file"])
1746 server = TCPserver.open(port)
1747 log_message("server started")
1750 Thread::start(server.accept) do |client|
1754 while (str = client.gets_timeout(Login_Time))
1759 if (good_login?(str))
1760 player = Player::new(str, client)
1761 if (LEAGUE.players[player.name])
1762 if ((LEAGUE.players[player.name].password == player.password) &&
1763 (LEAGUE.players[player.name].status != "game"))
1764 log_message(sprintf("user %s login forcely", player.name))
1765 LEAGUE.players[player.name].kill
1767 client.write_safe("LOGIN:incorrect" + eol)
1768 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
1777 client.write_safe("LOGIN:incorrect" + eol)
1778 client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
1789 log_message(sprintf("user %s login", player.name))
1794 player.game.kill(player)
1796 player.finish # socket has been closed
1797 LEAGUE.delete(player)
1798 log_message(sprintf("user %s logout", player.name))
1807 LEAGUE = League::new