OSDN Git Service

Add a simple rating system
authorbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Mon, 31 Jul 2006 13:15:31 +0000 (13:15 +0000)
committerbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Mon, 31 Jul 2006 13:15:31 +0000 (13:15 +0000)
changelog
shogi-server

index b9cc8ab..246d9c4 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,7 @@
+2006-07-31  Daigo Moriwaki <daigo at debian dot org>
+
+       * Add a simple rating system.
+
 2006-07-30  Daigo Moriwaki <daigo at debian dot org>
 
        * Add a @move_counter in Board class, which is used by Shogi Viewer
index 0241938..2ff785c 100755 (executable)
@@ -38,6 +38,9 @@ require 'getoptlong'
 require 'thread'
 require 'timeout'
 require 'socket'
+require 'yaml'
+require 'yaml/store'
+require 'digest/md5'
 
 TCPSocket.do_not_reverse_lookup = true
 Thread.abort_on_exception = true
@@ -89,15 +92,19 @@ class League
     @games = Hash::new
     @players = Hash::new
     @event = nil
+    @db = YAML::Store.new( File.join(File.dirname(__FILE__), "players.yaml") )
   end
   attr_accessor :players, :games, :event
 
   def add(player)
+    self.load(player) if player.id
     @players[player.name] = player
   end
+  
   def delete(player)
     @players.delete(player.name)
   end
+  
   def get_player(status, game_name, sente, searcher=nil)
     @players.each do |name, player|
       if ((player.status == status) &&
@@ -109,12 +116,38 @@ class League
     end
     return nil
   end
+  
+  def save
+    @db.transaction do
+      @players.each_value do |p|
+        next unless p.id
+        @db[p.id] = {'name' => p.name, 'rate' => p.rate}
+      end
+    end
+  end
+
+  def load(player)
+    hash = search(player.id)
+    if hash
+      # a current user
+      player.rate = hash['rate']
+    end
+  end
+
+  def search(id)
+    hash = nil
+    @db.transaction do
+      hash = @db[id]
+    end
+    hash
+  end
 end
 
 class Player
   def initialize(str, socket)
     @name = nil
     @password = nil
+    @id = nil, @rate = nil      # used by rating
     @socket = socket
     @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
 
@@ -130,9 +163,11 @@ class Player
     login(str)
   end
 
-  attr_accessor :name, :password, :socket, :status
+  attr_accessor :name, :password, :socket, :status, :rate
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
   attr_accessor :main_thread, :writer_thread, :write_queue
+  attr_reader   :id
+  
   def kill
     log_message(sprintf("user %s killed", @name))
     if (@game)
@@ -191,6 +226,8 @@ class Player
     @eol = $1
     str.chomp!
     (login, @name, @password, ext) = str.split
+    @name, trip = @name.split(",") # used by rating
+    @id = trip ? Digest::MD5.hexdigest("#{@name},#{@trip}") : nil
     if (ext)
       @protocol = "x1"
     else
@@ -201,7 +238,7 @@ class Player
       writer()
     end
   end
-    
+  
   def run
     write_safe(sprintf("LOGIN:%s OK\n", @name))
     if (@protocol != "CSA")
@@ -1185,6 +1222,73 @@ class Board
   end
 end
 
+#################################################
+# Rating module
+#
+# http://www2.saganet.ne.jp/a-sim/mhp0726.html
+# http://www10.plala.or.jp/greenstone/content1_3.html
+class Rating
+  K = 16
+
+  def new_rate(me, you, win)
+    w = win ? 1 : 0
+    
+    if me == nil && you != nil
+      return (you + w*400).to_i
+    elsif me == nil && you == nil
+      return (1100 + w*400).to_i
+    elsif you == nil
+      return me.to_i
+    end
+    score = me + K*(w - we(me, you))
+    score.to_i
+  end
+
+  private
+
+  # win expectancy
+  def we(me, you)
+    dr = (me - you)
+    1.0 / ( 10**(-1.0*dr/400) + 1 )
+  end
+end
+
+
+
+class GameResult
+  def initialize(p1, p2)
+    @players = []
+    @players << p1
+    @players << p2
+  end
+end
+
+class GameResultWin < GameResult
+  attr_reader :winner, :loser
+    
+  def initialize(winner, loser)
+    super
+    @winner, @loser = winner, loser
+    rate
+  end
+
+  def win?(player)
+    @winner == player
+  end
+
+  def rate
+    rating = Rating.new
+    new_winner = rating.new_rate(@winner.rate, @loser.rate,  true)
+    new_loser  = rating.new_rate(@loser.rate,  @winner.rate, false)
+    @winner.rate = new_winner if new_winner
+    @loser.rate  = new_loser  if new_loser
+  end
+end
+
+class GameResultDraw < GameResult
+
+end
+
 class Game
   @@mutex = Mutex.new
   @@time  = 0
@@ -1228,11 +1332,13 @@ class Game
     @board.initial
     @start_time = nil
     @fh = nil
+    @result = nil
 
     propose
   end
   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
   attr_accessor :last_move, :current_turn
+  attr_reader   :result
 
   def monitoron(monitor)
     @monitors.delete(monitor)
@@ -1280,6 +1386,7 @@ class Game
     @current_player = nil
     @next_player = nil
     LEAGUE.games.delete(@id)
+    LEAGUE.save
   end
 
   def handle_one_move(str, player)
@@ -1362,6 +1469,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
+    @result = GameResultWin.new(@current_player, @next_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
@@ -1375,6 +1483,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
@@ -1388,6 +1497,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
+    @result = GameResultDraw.new(@current_player, @next_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1400,6 +1510,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
     @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1412,6 +1523,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1424,6 +1536,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1436,6 +1549,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1448,6 +1562,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#TIME_UP\n#LOSE\n")
     @next_player.write_safe("#TIME_UP\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1460,6 +1575,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
+    @result = GameResultWin.new(@current_player, @next_player)
     @fh.printf("%%KACHI\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
@@ -1473,6 +1589,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
+    @result = GameResultWin(@next_player, @current_player)
     @fh.printf("%%KACHI\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
@@ -1486,6 +1603,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
+    @result = GameResultWin.new(@next_player, @current_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
@@ -1499,6 +1617,7 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+    @result = GameResultWin.new(@current_player, @next_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
     @monitors.each do |monitor|
@@ -1628,6 +1747,10 @@ EOM
   end
 end
 
+#################################################
+# MAIN
+#
+
 def usage
     print <<EOM
 NAME
@@ -1699,7 +1822,8 @@ def good_game_name?(str)
 end
 
 def good_identifier?(str)
-  if ((str =~ /\A[\w\d_@\-\.]+\z/) &&
+  if ( ((str =~ /\A[\w\d_@\-\.]+\z/) || 
+        (str =~ /\A[\w\d_@\-\.]+,[\w\d_@\-\.]+\z/)) &&
       (str.length < Max_Identifier_Length))
     return true
   else
@@ -1760,8 +1884,6 @@ def main
 
   write_pid_file($options["pid-file"]) if ($options["pid-file"])
 
-
-
   server = TCPserver.open(port)
   log_message("server started")