OSDN Git Service

finished status added
[shogi-server/shogi-server.git] / shogi-server
index ba7ad06..3594fc3 100755 (executable)
@@ -1,7 +1,7 @@
 #! /usr/bin/env ruby
 ## -*-Ruby-*- $RCSfile$ $Revision$ $Name$
 
-## Copyright (C) 2004 773@2ch
+## Copyright (C) 2004 nanami@2ch
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
@@ -21,7 +21,6 @@ DEFAULT_TIMEOUT = 10            # for single socket operation
 Total_Time = 1500
 Least_Time_Per_Move = 1
 Watchdog_Time = 30              # time for ping
-Agree_Time = 300                # time for AGREE
 Login_Time = 300                # time for LOGIN
 
 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
@@ -51,11 +50,23 @@ class TCPSocket
       return nil
     end
   end
-  def gets_safe
-    begin
-      return self.gets
-    rescue
-      return nil
+  def gets_safe(t = nil)
+    if (t && t > 0)
+      begin
+        timeout(t) do
+          return self.gets
+        end
+      rescue TimeoutError
+        return :timeout
+      rescue
+        return nil
+      end
+    else
+      begin
+        return self.gets
+      rescue
+        return nil
+      end
     end
   end
   def write_safe(str)
@@ -70,51 +81,99 @@ end
 
 class League
   def initialize
-    @hash = Hash::new
+    @games = Hash::new
+    @players = Hash::new
   end
-  attr_accessor :hash
+  attr_accessor :players, :games
 
   def add(player)
-    @hash[player.name] = player
+    @players[player.name] = player
   end
   def delete(player)
-    @hash.delete(player.name)
+    @players.delete(player.name)
   end
   def duplicated?(player)
-    if (@hash[player.name])
+    if (@players[player.name])
       return true
     else
       return false
     end
   end
+  def get_player(status, game_name, sente, searcher=nil)
+    @players.each do |name, player|
+      if ((player.status == status) &&
+          (player.game_name == game_name) &&
+          ((player.sente == nil) || (player.sente == sente)) &&
+          ((searcher == nil) || (player != searcher)))
+        return player
+      end
+    end
+    return nil
+  end
 end
 
-
-
-
 class Player
   def initialize(str, socket)
     @name = nil
     @password = nil
     @socket = socket
-    @state = "connected"        # wait_game -> game
+    @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
 
-    @x1 = false                 # extention protocol
+    @protocol = nil             # CSA or x1
     @eol = "\m"                 # favorite eol code
     @game = nil
-    @mytime = Total_Time
+    @game_name = ""
+    @mytime = Total_Time        # set in start method also
     @sente = nil
     @watchdog_thread = nil
 
     login(str)
   end
 
-  attr_accessor :name, :password, :socket, :state
-  attr_accessor :x1, :eol, :game, :mytime, :watchdog_thread
+  attr_accessor :name, :password, :socket, :status
+  attr_accessor :protocol, :eol, :game, :mytime, :watchdog_thread, :game_name, :sente
 
+  def finish
+    if (@status != "finished")
+      @status = "finished"
+      log_message(sprintf("user %s finish", @name))    
+      Thread::kill(@watchdog_thread) if @watchdog_thread
+      @socket.close if (! @socket.closed?)
+    end
+  end
+
+  def watchdog(time)
+    while true
+      begin
+        Ping.pingecho(@socket.addr[3])
+      rescue
+      end
+      sleep(time)
+    end
+  end
+
+  def to_s
+    if ((status == "game_waiting") ||
+        (status == "agree_waiting") ||
+        (status == "game"))
+      if (@sente)
+        return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
+      elsif (@sente == false)
+        return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
+      elsif (@sente == nil)
+        return sprintf("%s %s %s %s +-", @name, @protocol, @status, @game_name)
+      end
+    else
+      return sprintf("%s %s %s", @name, @protocol, @status)
+    end
+  end
+
+  def write_help
+    @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
+  end
 
   def write_safe(str)
-    @socket.write_safe(str + @eol)
+    @socket.write_safe(str.gsub(/[\r\n]+/, @eol))
   end
 
   def login(str)
@@ -122,78 +181,445 @@ class Player
     @eol = $1
     str.chomp!
     (login, @name, @password, ext) = str.split
-    @x1 = true if (ext)
+    if (ext)
+      @protocol = "x1"
+    else
+      @protocol = "CSA"
+    end
+    @watchdog_thread = Thread::start do
+      watchdog(Watchdog_Time)
+    end
   end
-
+    
   def run
-    if (@x1)
-      log_message(sprintf("user %s run in x1 mode", @name))
-      write_safe("## LOGIN in x1 mode")
+    write_safe(sprintf("LOGIN:%s OK\n", @name))
+    if (@protocol != "CSA")
+      log_message(sprintf("user %s run in %s mode", @name, @protocol))
+      write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
     else
       log_message(sprintf("user %s run in CSA mode", @name))
+      csa_1st_str = "%%GAME default +-"
     end
-
-    while (str = @socket.gets_safe)
-      str.chomp!
-      case str
-      when /^%%HELP/
-        write_help
-      when /^%%GAME\s+(\S+)\s+([\+\-])/
-        game_name = $1
-        @state = "game_waiting"
-        if ($2 == "+")
-          @sente = true
-          rival_sente = false
-        else
-          @sente = false
-          rival_sente = true
-        end
-        rival = LEAGUE.get_player(game_name, rival_sente)
-        if (rival)
-          @state = "game"
-          LEAGUE.start_game(game_name, self, rival)
+    
+    while (csa_1st_str || (str = @socket.gets_safe(@mytime)))
+      begin
+        $mutex.lock
+        if (csa_1st_str)
+          str = csa_1st_str
+          csa_1st_str = nil
         end
-      when /^%%CHAT\s+(\S+)/
-        message = $1
-        LEAGUE.hash.each do |name, player|
-          s = player.write_safe(sprintf("## [%s] %s", @name, message))
-          player.status = "zombie" if (! s)
+        if (@status == "finished")
+          return
         end
-      when /^%%WHO/
-        LEAGUE.hash.each do |name, player|
-          write_safe(sprintf("## %s %s", name, player.state))
+        str.chomp! if (str.class == String)
+        case str
+        when /^[\+\-%][^%]/, :timeout
+          if (@status == "game")
+            s = @game.handle_one_move(str, self)
+            return if (s && @protocol == "CSA")
+          else
+            next
+          end
+        when /^AGREE/
+          if (@status == "agree_waiting")
+            @status = "start_waiting"
+            if ((@game.sente.status == "start_waiting") &&
+                (@game.gote.status == "start_waiting"))
+              @game.start
+              @game.sente.status = "game"
+              @game.gote.status = "game"
+            end
+          else
+            write_safe("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status)
+            next
+          end
+        when /^%%HELP/
+          write_help
+        when /^%%GAME\s+(\S+)\s+([\+\-]+)/
+          if ((@status == "connected") || (@status == "game_waiting"))
+            @status = "game_waiting"
+          else
+            write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
+            next
+          end
+          @status = "game_waiting"
+          @game_name = $1
+          sente_str = $2
+          if (sente_str == "+")
+            @sente = true
+            rival_sente = false
+          elsif (sente_str == "-")
+            @sente = false
+            rival_sente = true
+          else
+            @sente = nil
+            rival_sente = nil
+          end
+          rival = LEAGUE.get_player("game_waiting", @game_name, rival_sente, self)
+          rival = LEAGUE.get_player("game_waiting", @game_name, nil, self) if (! rival)
+          if (rival)
+            if (@sente == nil)
+              if (rand(2) == 0)
+                @sente = true
+                rival_sente = false
+              else
+                @sente = false
+                rival_sente = true
+              end
+            elsif (rival_sente == nil)
+              if (@sente)
+                rival_sente = false
+              else
+                rival_sente = true
+              end
+            end
+            rival.sente = rival_sente
+            Game::new(@game_name, self, rival)
+            self.status = "agree_waiting"
+            rival.status = "agree_waiting"
+          end
+        when /^%%CHAT\s+(.+)/
+          message = $1
+          LEAGUE.players.each do |name, player|
+            if (player.protocol != "CSA")
+              s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
+              player.status = "zombie" if (! s)
+            end
+          end
+        when /^%%LIST/
+          buf = Array::new
+          LEAGUE.games.each do |id, game|
+            buf.push(sprintf("##[LIST] %s\n", id))
+          end
+          buf.push("##[LIST] +OK\n")
+          write_safe(buf.join)
+        when /^%%SHOW\s+(\S+)/
+          id = $1
+          if (LEAGUE.games[id])
+            write_safe(LEAGUE.games[id].board.to_s.gsub(/^/, '##[SHOW] '))
+          end
+          write_safe("##[SHOW] +OK\n")
+        when /^%%WHO/
+          buf = Array::new
+          LEAGUE.players.each do |name, player|
+            buf.push(sprintf("##[WHO] %s\n", player.to_s))
+          end
+          buf.push("##[WHO] +OK\n")
+          write_safe(buf.join)
+        when /^LOGOUT/
+          write_safe("LOGOUT:completed\n")
+          return
+        else
+          write_safe(sprintf("##[ERROR] unknown command %s\n", str))
         end
-      when /^%%LOGOUT/
-        break
-      else
-        write_safe(sprintf("## unknown command %s", str))
+      ensure
+        $mutex.unlock
       end
+    end                         # enf of while
+  end
+end
+
+class Piece
+  PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
+  def initialize(name, sente)
+    @name = name
+    @sente = sente
+    @promoted = false
+  end
+  attr_accessor :name, :promoted, :sente
+
+  def promoted_name
+    PROMOTE[name]
+  end
+
+  def to_s
+    if (@sente)
+      sg = "+"
+    else
+      sg = "-"
     end
+    if (@promoted)
+      n = PROMOTE[@name]
+    else
+      n = @name
+    end
+    return sg + n
   end
 end
 
+
+
 class Board
+  def initialize
+    @sente_hands = Array::new
+    @gote_hands = Array::new
+    @array = [[], [], [], [], [], [], [], [], [], []]
+  end
+  attr_accessor :array, :sente_hands, :gote_hands
+
+  def initial
+    @array[1][1] = Piece::new("KY", false)
+    @array[2][1] = Piece::new("KE", false)
+    @array[3][1] = Piece::new("GI", false)
+    @array[4][1] = Piece::new("KI", false)
+    @array[5][1] = Piece::new("OU", false)
+    @array[6][1] = Piece::new("KI", false)
+    @array[7][1] = Piece::new("GI", false)
+    @array[8][1] = Piece::new("KE", false)
+    @array[9][1] = Piece::new("KY", false)
+    @array[2][2] = Piece::new("KA", false)
+    @array[8][2] = Piece::new("HI", false)
+    @array[1][3] = Piece::new("FU", false)
+    @array[2][3] = Piece::new("FU", false)
+    @array[3][3] = Piece::new("FU", false)
+    @array[4][3] = Piece::new("FU", false)
+    @array[5][3] = Piece::new("FU", false)
+    @array[6][3] = Piece::new("FU", false)
+    @array[7][3] = Piece::new("FU", false)
+    @array[8][3] = Piece::new("FU", false)
+    @array[9][3] = Piece::new("FU", false)
+
+    @array[1][9] = Piece::new("KY", true)
+    @array[2][9] = Piece::new("KE", true)
+    @array[3][9] = Piece::new("GI", true)
+    @array[4][9] = Piece::new("KI", true)
+    @array[5][9] = Piece::new("OU", true)
+    @array[6][9] = Piece::new("KI", true)
+    @array[7][9] = Piece::new("GI", true)
+    @array[8][9] = Piece::new("KE", true)
+    @array[9][9] = Piece::new("KY", true)
+    @array[2][8] = Piece::new("HI", true)
+    @array[8][8] = Piece::new("KA", true)
+    @array[1][7] = Piece::new("FU", true)
+    @array[2][7] = Piece::new("FU", true)
+    @array[3][7] = Piece::new("FU", true)
+    @array[4][7] = Piece::new("FU", true)
+    @array[5][7] = Piece::new("FU", true)
+    @array[6][7] = Piece::new("FU", true)
+    @array[7][7] = Piece::new("FU", true)
+    @array[8][7] = Piece::new("FU", true)
+    @array[9][7] = Piece::new("FU", true)
+  end
+
+  def get_piece_from_hands(hands, name)
+    p = hands.find { |i|
+      i.name == name
+    }
+    if (p)
+      hands.delete(p)
+    end
+    return p
+  end
+
+  def handle_one_move(str)
+    if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
+      p = $1
+      x0 = $2.to_i
+      y0 = $3.to_i
+      x1 = $4.to_i
+      y1 = $5.to_i
+      name = $6
+    elsif (str =~ /^%/)
+      return true
+    else
+      return false              # illegal move
+    end
+    if (p == "+")
+      sente = true
+      hands = @sente_hands
+    else
+      sente = false
+      hands = @gote_hands
+    end
+    if (@array[x1][y1])
+      if (@array[x1][y1] == sente) # this is mine
+        return false            
+      end
+      hands.push(@array[x1][y1])
+      @array[x1][y1] = nil
+    end
+    if ((x0 == 0) && (y0 == 0))
+      p = get_piece_from_hands(hands, name)
+      return false if (! p)     # i don't have this one
+      @array[x1][y1] = p
+      p.sente = sente
+      p.promoted = false
+    else
+      @array[x1][y1] = @array[x0][y0]
+      @array[x0][y0] = nil
+      if (@array[x1][y1].name != name) # promoted ?
+        return false if (@array[x1][y1].promoted_name != name) # can't promote
+        @array[x1][y1].promoted = true
+      end
+    end
+    return true                 # legal move
+  end
+
+  def to_s
+    a = Array::new
+    y = 1
+    while (y <= 9)
+      a.push(sprintf("P%d", y))
+      x = 9
+      while (x >= 1)
+        piece = @array[x][y]
+        if (piece)
+          s = piece.to_s
+        else
+          s = " * "
+        end
+        a.push(s)
+        x = x - 1
+      end
+      a.push(sprintf("\n"))
+      y = y + 1
+    end
+    if (! sente_hands.empty?)
+      a.push("P+")
+      sente_hands.each do |p|
+        a.push("00" + p.name)
+      end
+      a.push("\n")
+    end
+    if (! gote_hands.empty?)
+      a.push("P-")
+      gote_hands.each do |p|
+        a.push("00" + p.name)
+      end
+      a.push("\n")
+    end
+    return a.join
+  end
 end
 
 class Game
-  def initialize(event, sente, gote)
-    @id = sprintf("%s-%s-%s-%s", event, sente.name, gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
+  def initialize(game_name, player0, player1)
+    @game_name = game_name
+    if (player0.sente)
+      @sente = player0
+      @gote = player1
+    else
+      @sente = player1
+      @gote = player0
+    end
+    @current_player = @sente
+    @next_player = @gote
+
+    @sente.game = self
+    @gote.game = self
+
+    @sente.status = "agree_waiting"
+    @gote.status = "agree_waiting"
+    @id = sprintf("%s-%s-%s-%s", @game_name, @sente.name, @gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
+    LEAGUE.games[@id] = self
+
+
+    log_message(sprintf("game created %s %s %s", game_name, sente.name, gote.name))
+
     @logfile = @id + ".csa"
-    @sente = sente
-    @gote = gote
-    @sente.sg_flag = "+"
-    @gote.sg_flag = "-"
     @board = Board::new
-    @currnet_player = sente
-    @next_player = gote
+    @board.initial
+    @start_time = nil
     @fh = nil
-    printf("%s: new game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
+
+    propose
+  end
+  attr_accessor :game_name, :sente, :gote, :id, :board, :current_player, :next_player, :fh
+
+  def finish
+    log_message(sprintf("game finished %s %s %s", game_name, sente.name, gote.name))
+    @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
+    @fh.close
+
+    @sente.game = nil
+    @gote.game = nil
+    @sente.status = "connected"
+    @gote.status = "connected"
+    if (@current_player.protocol == "CSA")
+      @current_player.finish
+    end
+    if (@next_player.protocol == "CSA")
+      @next_player.finish
+    end
+    LEAGUE.games.delete(@id)
+  end
+
+  def handle_one_move(str, player)
+    finish_flag = false
+    if (@current_player == player)
+      @end_time = Time::new
+      t = @end_time - @start_time
+      t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
+      if (str != :timeout)
+        legal_move = @board.handle_one_move(str)
+        if (legal_move)
+          @sente.write_safe(sprintf("%s,T%d\n", str, t))
+          @gote.write_safe(sprintf("%s,T%d\n", str, t))
+          @fh.printf("%s\nT%d\n", str, t)
+        else
+          @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
+        end
+        @current_player.mytime = @current_player.mytime - t
+      else
+        @current_player.mytime = 0
+      end
+      if (!legal_move)
+        illegal_end()
+        finish_flag = true
+      elsif (@current_player.mytime <= 0)
+        timeout_end()
+        finish_flag = true
+      elsif (str =~ /%KACHI/)
+        kachi_end()
+        finish_flag = true
+      elsif (str =~ /%TORYO/)
+        toryo_end()
+        finish_flag = true
+      end
+      (@current_player, @next_player) = [@next_player, @current_player]
+      @start_time = Time::new
+      return finish_flag
+    end
+  end
+
+  def illegal_end
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+    @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
   end
+
+  def timeout_end
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#TIME_UP\n#LOSE\n")
+    @next_player.write_safe("#TIME_UP\n#WIN\n")
+  end
+
+  def kachi_end
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#JISHOGI\n#WIN\n")
+    @next_player.write_safe("#JISHOGI\n#LOSE\n")
+  end
+
+  def toryo_end
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#RESIGN\n#LOSE\n")
+    @next_player.write_safe("#RESIGN\n#WIN\n")
+  end
+
   def start
-    begin
-      @sente.watchdog_start(Watchdog_Time)
-      @gote.watchdog_start(Watchdog_Time)
+    log_message(sprintf("game started %s %s %s", game_name, sente.name, gote.name))
+    @sente.write_safe(sprintf("START:%s\n", @id))
+    @gote.write_safe(sprintf("START:%s\n", @id))
+    @mytime = Total_Time    
+    @start_time = Time::new
+  end
 
+  def propose
+    begin
       @fh = open(@logfile, "w")
       @fh.sync = true
 
@@ -201,10 +627,9 @@ class Game
       @fh.printf("N+%s\n", @sente.name)
       @fh.printf("N-%s\n", @gote.name)
       @fh.printf("$EVENT:%s\n", @id)
-      @sente.write(start_message("+"))
-      @gote.write(start_message("-"))
-      @sente.wait_agree(Agree_Time)
-      @gote.wait_agree(Agree_Time)
+
+      @sente.write_safe(propose_message("+"))
+      @gote.write_safe(propose_message("-"))
 
       @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
       @fh.print <<EOM
@@ -219,84 +644,13 @@ P8 * +KA *  *  *  *  * +HI *
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 +
 EOM
-
-      @sente.write(sprintf("START:%s\n", @id))
-      @gote.write(sprintf("START:%s\n", @id))
-      while(true)
-        @currnet_player = @sente
-        @next_player = @gote
-        handle_one_move(@currnet_player, @next_player)
-
-        @currnet_player = @gote
-        @next_player = @sente
-        handle_one_move(@currnet_player, @next_player)
-      end
-    rescue ShogiWatchdogTimeout
-      sg_flag_of_timeout = $!.message
-      if (sg_flag_of_timeout == "+")
-        loser = @sente
-        winner = @gote
-      else
-        loser = @sente
-        winner = @gote
-      end
-      printf("watchdog timeout by %s\n", loser.name)
-      loser.write("#TIME_UP\n#LOSE\n")
-      winner.write("#TIME_UP\n#WIN\n")
-    rescue TimeoutError, ShogiTimeout
-      printf("%s: end timeup by %s\n", Time::new.to_s, @currnet_player.name)
-      @currnet_player.write("#TIME_UP\n#LOSE\n")
-      @next_player.write("#TIME_UP\n#WIN\n")
-    rescue ShogiReject
-      sender = $!.message
-      printf("%s: reject by %s\n", Time::new.to_s, sender)
-      str = sprintf("REJECT:%s by %s\n", @id, sender)
-      @sente.write(str)
-      @gote.write(str)
-    rescue ShogiIllegalMove
-      printf("%s: end illegal move by %s\n", Time::new.to_s, @currnet_player.name)
-      move = $!.message
-      @fh.printf("%%ERROR\n")
-      @currnet_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#LOSE\n", move))
-      @next_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#WIN\n", move))
-    rescue ShogiEnd
-      printf("%s: end by %s\n", Time::new.to_s, @currnet_player.name)
-      move = $!.message
-      case move
-      when "%TORYO"
-        @currnet_player.write(sprintf("%s\n#RESIGN\n#LOSE\n", move))
-        @next_player.write(sprintf("%s\n#RESIGN\n#WIN\n", move))
-      when "%KACHI"
-        @currnet_player.write(sprintf("%s\n#JISHOGI\n#WIN\n", move))
-        @next_player.write(sprintf("%s\n#JISHOGI\n#LOSE\n", move))
-      end
     end
-    @fh.printf("'END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
-  end
-
-  def handle_one_move(current_player, next_player)
-    start_time = Time::new
-    str = current_player.get_move
-    @fh.printf("%s\n", str)
-    end_time = Time::new
-    time = (end_time - start_time).truncate
-    time = Least_Time_Per_Move if (time < Least_Time_Per_Move)
-    current_player.sub_time(time)
-    raise ShogiEnd, str if (str =~ /\A%/)
-    @sente.write(sprintf("%s,T%d\n", str, time))
-    @gote.write(sprintf("%s,T%d\n", str, time))
-    @fh.printf("T%s\n", time)
-  end
-
-  def finish
-    @sente.finish
-    @gote.finish
-    @fh.close
-    printf("%s: end game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
   end
 
-  def start_message(sg_flag)
+  def propose_message(sg_flag)
     str = <<EOM
+BEGIN Game_Summary
+Protocol_Version:1.0
 Protocol_Mode:Server
 Format:Shogi 1.0
 Game_ID:#{@id}
@@ -324,6 +678,8 @@ P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 P+
 P-
 +
+END Position
+END Game_Summary
 EOM
     return str
   end
@@ -377,8 +733,10 @@ def parse_command_line
   parser.set_options(
                      ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
 
+  parser.quiet = true
   begin
     parser.each_option do |name, arg|
+      name.sub!(/^--/, '')
       options[name] = arg.dup
     end
   rescue
@@ -393,7 +751,8 @@ LEAGUE = League::new
 def good_login?(str)
   return false if (str !~ /^LOGIN /)
   tokens = str.split
-  if ((tokens.length == 3) || (tokens.length == 4))
+  if ((tokens.length == 3) || 
+      ((tokens.length == 4) && tokens[3] == "x1"))
     ## ok
   else
     return false
@@ -401,7 +760,14 @@ def good_login?(str)
   return true
 end
 
+def  write_pid_file(file)
+  open(file, "w") do |fh|
+    fh.print Process::pid, "\n"
+  end
+end
+
 def main
+  $mutex = Mutex::new
   $options = parse_command_line
   if (ARGV.length != 2)
     usage
@@ -410,6 +776,9 @@ def main
   event = ARGV.shift
   port = ARGV.shift
 
+  write_pid_file($options["pid-file"]) if ($options["pid-file"])
+
+
   Thread.abort_on_exception = true
 
   server = TCPserver.open(port)
@@ -418,26 +787,47 @@ def main
   while true
     Thread::start(server.accept) do |client|
       client.sync = true
+      player = nil
       while (str = client.gets_timeout(Login_Time))
-        Thread::kill(Thread::current) if (! str) # disconnected
-        str =~ /([\r\n]*)$/
-        eol = $1
-        if (good_login?(str))
-          player = Player::new(str, client)
-          if (LEAGUE.duplicated?(player))
-            client.write_safe(sprintf("username %s is already connected", player.name))
-            next
+        begin
+          $mutex.lock
+          Thread::kill(Thread::current) if (! str) # disconnected
+          str =~ /([\r\n]*)$/
+          eol = $1
+          if (good_login?(str))
+            player = Player::new(str, client)
+            if (LEAGUE.duplicated?(player))
+              client.write_safe("LOGIN:incorrect" + eol)
+              client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
+              client.close
+              Thread::kill(Thread::current)
+            end
+            LEAGUE.add(player)
+            break
+          else
+            client.write_safe("LOGIN:incorrect" + eol)
+            client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
+            client.close
+            Thread::kill(Thread::current)
           end
-          LEAGUE.add(player)
-          break
-        else
-          client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol)
+        ensure
+          $mutex.unlock
         end
       end                       # login loop
       log_message(sprintf("user %s login", player.name))
       player.run
-      LEAGUE.delete(player)
-      log_message(sprintf("user %s logout", player.name))
+      begin
+        $mutex.lock
+        if (player.game)
+          player.game.finish
+        elsif (player.status != "finished")
+          player.finish
+        end
+        LEAGUE.delete(player)
+        log_message(sprintf("user %s logout", player.name))
+      ensure
+        $mutex.unlock
+      end
     end
   end
 end