OSDN Git Service

Merge branch 'master' into wdoor-stable 20151213
authorDaigo Moriwaki <daigo@debian.org>
Sun, 13 Dec 2015 12:28:20 +0000 (21:28 +0900)
committerDaigo Moriwaki <daigo@debian.org>
Sun, 13 Dec 2015 12:28:20 +0000 (21:28 +0900)
Conflicts:
shogi_server.rb

16 files changed:
Makefile
changelog
shogi-server
shogi_server.rb
shogi_server/board.rb
shogi_server/game.rb
shogi_server/league/floodgate.rb
shogi_server/league/floodgate_thread.rb
shogi_server/pairing.rb
shogi_server/util.rb
test/TC_floodgate_next_time_generator.rb
test/TC_functional.rb
test/TC_game.rb
test/TC_game_least_0.rb
test/TC_pairing.rb
test/TC_util.rb

index 941b3fc..c46701c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,12 +6,12 @@ doc: clean
 
 .PHONY: test-run
 test-run: 
-       ./shogi-server --floodgate-games floodgate-900-0,floodgate-3600-0 hoge 4000 
+       ./shogi-server --floodgate-games floodgate-600-10,floodgate-3600-0 hoge 4000
 
 
 .PHONY: test-run-daemon
 test-run-daemon: 
-       ./shogi-server --floodgate-games floodgate-900-0,floodgate-3600-0 --daemon . --pid-file ./shogi-server.pid --player-log-dir ./player-logs hoge 4000
+       ./shogi-server --floodgate-games floodgate-600-10,floodgate-3600-0 --daemon . --pid-file ./shogi-server.pid --player-log-dir ./player-logs hoge 4000
 
 .PHONY: stop-daemn
 stop-daemon:
index 2e21e18..e448a2a 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,27 @@
+2015-12-13  Daigo Moriwaki <daigo at debian dot org>
+
+       * [shogi-server] Enhance capability of Floodgate configuration file
+         - New parameter: Max_Moves, defined in the CSA protocol
+           ex. set Max_Moves 256
+         - New parameter: Least_Time_Per_Move, defined in the CSA protocol
+           ex. set Least_Time_Per_Move 0
+         - Proposed messages distributed to each player upon starting a new
+           game will include Max_Moves as well as Least_Time_Per_Move.
+         - CSA files produced by the server will include settings of
+           Max_Moves and Least_Time_Per_Move in comment lines as follows:
+             'Max_Moves:256
+             'Least_Time_Per_Move:0
+         - The official Shogi-server on wdoor.c.u-tokyo.ac.jp will
+           be running with different parameters, depending on game names.
+           a) Max_Moves will be 256 for floodgate-600-10 games;
+              otherwise, 0.
+           b) Least_Time_Per_Move will be 0 for floodgate-600-10 games;
+              otherwise 1.
+         (Closes: #35839)
+       * [shogi-server] shogi_server/pairing.rb:
+         - LeastDiff attempts more trials, depending of a number of players
+           to be matched, top achieve more optimized matching combinations.
+
 2015-11-27  Daigo Moriwaki <daigo at debian dot org>
 
        * [shogi-server] shogi_server/time_clock.rb:
index 56f92d4..b5e1ecf 100755 (executable)
@@ -278,10 +278,10 @@ def check_command_line
     $options["floodgate-history"] = nil
   end
 
-  $options["max-moves"] ||= 256
+  $options["max-moves"] ||= ShogiServer::Default_Max_Moves
   $options["max-moves"] = $options["max-moves"].to_i
 
-  $options["least-time-per-move"] ||= 0
+  $options["least-time-per-move"] ||= ShogiServer::Default_Least_Time_Per_Move
   $options["least-time-per-move"] = $options["least-time-per-move"].to_i
 end
 
index 8a9d03d..4575aa8 100644 (file)
@@ -48,9 +48,11 @@ module ShogiServer # for a namespace
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
 Default_Game_Name = "default-1500-0"
+Default_Max_Moves = 256
+Default_Least_Time_Per_Move = 0
 One_Time = 10
 Login_Time = 300                # time for LOGIN
-Revision = "20150201"
+Revision = "20151213"
 
 RELOAD_FILES = ["shogi_server/league/floodgate.rb",
                 "shogi_server/league/persistent.rb",
index bcf8887..4c9c607 100644 (file)
@@ -84,18 +84,25 @@ EOF
   end
 
 
-  def initialize(move_count=0)
+  def initialize(options={})
     @sente_hands = Array::new
     @gote_hands  = Array::new
     @history       = Hash::new(0)
     @sente_history = Hash::new(0)
     @gote_history  = Hash::new(0)
     @array = [[], [], [], [], [], [], [], [], [], []]
-    @move_count = move_count
+    @move_count = 0
     @teban = nil # black => true, white => false
     @initial_moves = []
     @move = nil
     @ous = [nil, nil] # keep OU pieces of Sente and Gote
+
+    @max_moves = options[:max_moves] ||
+                 ($options && $options["max-moves"]) ||
+                 Default_Max_Moves
+    @least_time_per_move = options[:least_time_per_move] ||
+                           ($options && $options["least-time-per-move"]) ||
+                           Default_Least_Time_Per_Move
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history, :teban
   attr_reader :move_count
@@ -110,6 +117,12 @@ EOF
   #
   attr_reader :move
 
+  # Max_Moves of the CSA protocol
+  attr_reader :max_moves
+
+  # Least_Time_Per_Move of the CSA protocol
+  attr_reader :least_time_per_move
+
   # See if self equals rhs, including a logical board position (i.e.
   # not see object IDs) and sennichite stuff.
   #
@@ -717,8 +730,7 @@ EOF
     # New rule that CSA introduced in November 2014.
     # If a game with 256 plies does not end, make the game a draw.
     # When running test cases $options might be nil.
-    if $options && $options["max-moves"] &&
-       $options["max-moves"] > 0 && @move_count >= $options["max-moves"]
+    if @max_moves > 0 && @move_count >= @max_moves
       return :max_moves
     end
 
index a43b752..2d0b0a5 100644 (file)
@@ -71,7 +71,7 @@ class Game
       @total_time = $1.to_i
       @byoyomi = $2.to_i
 
-      @time_clock = TimeClock::factory($options["least-time-per-move"], @game_name)
+      @time_clock = TimeClock::factory(board.least_time_per_move, @game_name)
     end
 
     if (player0.sente)
@@ -325,6 +325,8 @@ class Game
     @fh.puts("V2")
     @fh.puts("N+#{@sente.name}")
     @fh.puts("N-#{@gote.name}")
+    @fh.puts("'Max_Moves:#{@board.max_moves}")
+    @fh.puts("'Least_Time_Per_Move:#{@board.least_time_per_move}")
     @fh.puts("$EVENT:#{@game_id}")
 
     @sente.write_safe(propose_message("+"))
@@ -371,11 +373,12 @@ Name+:#{@sente.name}
 Name-:#{@gote.name}
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{@board.max_moves}
 BEGIN Time
 Time_Unit:#{@time_clock.time_unit}
 Total_Time:#{@total_time}
 Byoyomi:#{@byoyomi}
-Least_Time_Per_Move:#{$options["least-time-per-move"]}
+Least_Time_Per_Move:#{@board.least_time_per_move}
 Remaining_Time+:#{@sente.mytime}
 Remaining_Time-:#{@gote.mytime}
 Last_Move:#{@last_move}
@@ -405,11 +408,12 @@ Name-:#{@gote.name}
 Your_Turn:#{sg_flag}
 Rematch_On_Draw:NO
 To_Move:#{@board.teban ? "+" : "-"}
+Max_Moves:#{@board.max_moves}
 BEGIN Time
 Time_Unit:#{@time_clock.time_unit}
 Total_Time:#{@total_time}
 Byoyomi:#{@byoyomi}
-Least_Time_Per_Move:#{$options["least-time-per-move"]}
+Least_Time_Per_Move:#{@board.least_time_per_move}
 END Time
 BEGIN Position
 #{@board.initial_string.chomp}
index ff2d98e..e52c3c3 100644 (file)
@@ -36,8 +36,10 @@ class League
       # Options will be updated by NextTimeGenerator and then passed to a
       # pairing factory.
       @options = {}
-      @options[:pairing_factory] = hash[:pairing_factory] || "default_factory"
-      @options[:sacrifice]       = hash[:sacrifice] || "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+      @options[:pairing_factory]     = hash[:pairing_factory] || "default_factory"
+      @options[:sacrifice]           = hash[:sacrifice] || "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+      @options[:max_moves]           = hash[:max_moves] || Default_Max_Moves
+      @options[:least_time_per_move] = hash[:least_time_per_move] || Default_Least_Time_Per_Move
       charge if @next_time.nil?
     end
 
@@ -53,12 +55,22 @@ class League
       return @options[:sacrifice]
     end
 
+    def max_moves
+      return @options[:max_moves]
+    end
+
+    def least_time_per_move
+      return @options[:least_time_per_move]
+    end
+
     def charge
       ntg = NextTimeGenerator.factory(@game_name)
       if ntg
         @next_time = ntg.call(Time.now)
-        @options[:pairing_factory] = ntg.pairing_factory
-        @options[:sacrifice]       = ntg.sacrifice
+        @options[:pairing_factory]     = ntg.pairing_factory
+        @options[:sacrifice]           = ntg.sacrifice
+        @options[:max_moves]           = ntg.max_moves
+        @options[:least_time_per_move] = ntg.least_time_per_move
       else
         @next_time = nil
       end
@@ -80,7 +92,7 @@ class League
     def match_game
       log_message("Starting Floodgate games...: %s, %s" % [@game_name, @options])
       logics = Pairing.send(@options[:pairing_factory], @options)
-      Pairing.match(select_players(), logics)
+      Pairing.match(select_players(), logics, @options)
     end
     
     #
@@ -110,12 +122,16 @@ class League
 
       attr_reader :pairing_factory
       attr_reader :sacrifice
+      attr_reader :max_moves
+      attr_reader :least_time_per_move
 
       # Constructor. 
       #
       def initialize
-        @pairing_factory = "default_factory"
-        @sacrifice       = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+        @pairing_factory     = "default_factory"
+        @sacrifice           = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+        @max_moves           = Default_Max_Moves
+        @least_time_per_move = Default_Least_Time_Per_Move
       end
     end
 
@@ -146,6 +162,12 @@ class League
     # * sacrifice:
     #   Specifies a sacrificed player.
     #   ex. set sacrifice gps500+e293220e3f8a3e59f79f6b0efffaa931
+    # * max_moves:
+    #   Sepcifies a number of max moves
+    #   ex. set max_moves 256
+    # * least_time_per_move:
+    #   Sepcifies a least time per move
+    #   ex. set least_time_per_move 0
     #
     class NextTimeGeneratorConfig < AbstructNextTimeGenerator
       
@@ -170,6 +192,10 @@ class League
             @pairing_factory = $1.chomp
           when %r!^\s*set\s+sacrifice\s+(.*)!
             @sacrifice = $1.chomp
+          when %r!^\s*set\s+max_moves\s+(\d+)!
+            @max_moves = $1.chomp.to_i
+          when %r!^\s*set\s+least_time_per_move\s+(\d+)!
+            @least_time_per_move = $1.chomp.to_i
           when %r!^\s*(\w+)\s+(\d{1,2}):(\d{1,2})!
             dow, hour, minute = $1, $2.to_i, $3.to_i
             dow_index = ::ShogiServer::parse_dow(dow)
index e45f60a..b1d9bf7 100644 (file)
@@ -73,11 +73,12 @@ module ShogiServer
     #
     def regenerate_leagues(next_instances)
       leagues = next_instances.collect do |prev|
-        log_message("Regenerating a floodgate league...: %s %s %s %s" %
-                    [prev.game_name, prev.next_time, prev.pairing_factory, prev.sacrifice])
+        log_message("Regenerating a floodgate league...: %s %s %s %s %d %d" %
+                    [prev.game_name, prev.next_time, prev.pairing_factory, prev.sacrifice, prev.max_moves, prev.least_time_per_move])
         floodgate = ShogiServer::League::Floodgate.new($league, 
                       {:game_name       => prev.game_name,       :next_time => prev.next_time,
-                       :pairing_factory => prev.pairing_factory, :sacrifice => prev.sacrifice})
+                       :pairing_factory => prev.pairing_factory, :sacrifice => prev.sacrifice,
+                       :max_moves       => prev.max_moves,       :least_time_per_move => prev.least_time_per_move})
       end
       floodgate_reload_log(leagues)
       return leagues
index fec47af..cc2ea62 100644 (file)
@@ -69,14 +69,22 @@ module ShogiServer
                 StartGameWithoutHumans.new]
       end
 
-      def match(players, logics)
+      def match(players, logics, options)
         logics.inject(players) do |result, item|
+          item.set_options(options)
           item.match(result)
           result
         end
       end
     end # class << self
 
+    def initialize
+      @options = {}
+    end
+
+    def set_options(options)
+      @options.merge!(options)
+    end
 
     # Make matches among players.
     # @param players an array of players, which should be updated destructively
@@ -129,7 +137,7 @@ module ShogiServer
       log_message("Floodgate: Starting a game: BLACK %s vs WHITE %s" % [p1.name, p2.name])
       p1.sente = true
       p2.sente = false
-      board = Board.new
+      board = Board.new(@options)
       board.initial
       Game.new(p1.game_name, p1, p2, board)
     end
@@ -506,6 +514,22 @@ module ShogiServer
       ret
     end
 
+    # Total combinations of possible games among n players
+    #   nC2 * (n-2)C2 * ... * 2C2 / (n/2)!
+    def total_posibilities(n)
+      n -= 1 if n.odd?
+      return 1 if n <= 2
+
+      ret = 1
+      i = n
+      while i >= 2 do
+        ret *= ::ShogiServer::nCk(i,2)
+        i -= 2
+      end
+      ret /= ::ShogiServer::factorial(n/2)
+      return ret
+    end
+
     def match(players)
       super
       if players.size < 3
@@ -516,12 +540,16 @@ module ShogiServer
       # Reset estimated rate
       players.each {|p| p.estimated_rate = 0}
 
-      # 10 trials
       matches = []
       scores  = []
       path = ShogiServer::League::Floodgate.history_file_path(players.first.game_name)
       history = ShogiServer::League::Floodgate::History.factory(path)
-      10.times do 
+
+      # Increase trials, depending on a number of players
+      trials = [300, total_posibilities(players.size)/3].min
+      trials = [10, trials].max
+      log_message("Floodgate: %d trials" % [trials])
+      trials.times do
         m = random_match(players)
         matches << m
         scores << calculate_diff_with_penalty(m, history)
index e1813d3..76f4526 100644 (file)
@@ -42,6 +42,25 @@ module ShogiServer
   end
   module_function :shuffle
 
+  def factorial(n)
+    return 1 if n<=1
+    ret = 1
+    while n >= 2
+      ret *= n
+      n -= 1
+    end
+    return ret
+  end
+  module_function :factorial
+
+  def nCk(n, k)
+    return 0 if n < k
+    numerator   = factorial(n)
+    denominator = factorial(k) * factorial(n - k)
+    return numerator / denominator
+  end
+  module_function :nCk
+
   # See if the file is writable. The file will be created if it does not exist
   # yet.
   # Return true if the file is writable, otherwise false.
index 297d63b..96a3006 100644 (file)
@@ -249,4 +249,20 @@ class TestNextTimeGeneratorConfig < Test::Unit::TestCase
     assert_equal Time.parse("10-06-2010 22:00"), ntc.call(now)
     assert_equal("yowai_gps+95908f6c18338f5340371f71523fc5e3", ntc.sacrifice)
   end
+
+  def test_default_max_moves
+    now = DateTime.new(2010, 6, 10, 21, 59, 59) # Thu
+    lines = %w(Thu\ 22:00)
+    ntc = ShogiServer::League::Floodgate::NextTimeGeneratorConfig.new lines
+    assert_equal Time.parse("10-06-2010 22:00"), ntc.call(now)
+    assert_equal(256, ntc.max_moves)
+  end
+
+  def test_read_max_moves
+    now = DateTime.new(2010, 6, 10, 21, 59, 59) # Thu
+    lines = %w(set\ max_moves\ 200 Thu\ 22:00)
+    ntc = ShogiServer::League::Floodgate::NextTimeGeneratorConfig.new lines
+    assert_equal Time.parse("10-06-2010 22:00"), ntc.call(now)
+    assert_equal(200, ntc.max_moves)
+  end
 end
index 7f8d884..ea3f874 100644 (file)
@@ -36,6 +36,8 @@ class TestClientAtmark < BaseClient
 V2
 N+atmark_B@p1
 N-atmark_W@p2
+'Max_Moves:256
+'Least_Time_Per_Move:0
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
@@ -103,6 +105,8 @@ class TestHandicappedGame < BaseClient
 V2
 N+hc2p_hoge_B
 N-hc2p_hoge_W
+'Max_Moves:256
+'Least_Time_Per_Move:0
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
index 08ffcf2..52227e0 100644 (file)
@@ -7,6 +7,7 @@ require 'shogi_server/player'
 
 $options = {}
 $options["least-time-per-move"] = 1
+$options["max-moves"] = 0
 
 def log_message(str)
   $stderr.puts str
@@ -51,6 +52,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -85,6 +87,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -113,6 +116,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
@@ -153,6 +158,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:-
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -188,6 +194,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:-
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -217,6 +224,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
@@ -260,6 +269,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -296,6 +306,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -326,6 +337,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
index 3429f07..127203c 100644 (file)
@@ -7,6 +7,7 @@ require 'shogi_server/player'
 
 $options = {}
 $options["least-time-per-move"] = 0
+$options["max-moves"] = 256
 
 def log_message(str)
   $stderr.puts str
@@ -51,6 +52,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -85,6 +87,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -113,6 +116,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
@@ -153,6 +158,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:-
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -188,6 +194,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:-
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -217,6 +224,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
@@ -260,6 +269,7 @@ Name-:p2
 Your_Turn:+
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -296,6 +306,7 @@ Name-:p2
 Your_Turn:-
 Rematch_On_Draw:NO
 To_Move:+
+Max_Moves:#{$options["max-moves"]}
 BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
@@ -326,6 +337,8 @@ EOF
 V2
 N+p1
 N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
index 94274fa..86970ca 100644 (file)
@@ -499,6 +499,12 @@ class TestLeastDiff < Test::Unit::TestCase
     assert_pairs([@a,@b,@h], players)
   end
 
+  def test_match_many_players
+    players = [@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h,@a,@b,@h]
+    r = @pairing.match(players)
+    assert true
+  end
+
   def test_calculate_diff_with_penalty
     players = [@a,@b]
     assert_equal(@b.rate-@a.rate, @pairing.calculate_diff_with_penalty(players,nil))
@@ -597,6 +603,13 @@ class TestLeastDiff < Test::Unit::TestCase
 
     assert_equal(@b.rate-200, @pairing.get_player_rate(@x, @history))
   end
+
+  def test_total_posibilities
+    assert_equal 1, @pairing.total_posibilities(2)
+    assert_equal 1, @pairing.total_posibilities(3)
+    assert_equal 3, @pairing.total_posibilities(4)
+    assert_equal 945, @pairing.total_posibilities(10)
+  end
 end
 
 class TestExcludeUnratedPlayers < Test::Unit::TestCase
index 79ac945..285c835 100644 (file)
@@ -61,3 +61,22 @@ class TestMkdir < Test::Unit::TestCase
   end
 
 end
+
+class TestFactorial < Test::Unit::TestCase
+  def test_factorial
+    assert_equal 1, ShogiServer::factorial(0)
+    assert_equal 1, ShogiServer::factorial(1)
+    assert_equal 2, ShogiServer::factorial(2)
+    assert_equal 6, ShogiServer::factorial(3)
+  end
+end
+
+class TestnCk < Test::Unit::TestCase
+  def test_nCk
+    assert_equal 0, ShogiServer::nCk(2,5)
+    assert_equal 1, ShogiServer::nCk(2,2)
+    assert_equal 6, ShogiServer::nCk(4,2)
+    assert_equal 11*5*9, ShogiServer::nCk(12,4)
+  end
+end
+