1 require 'shogi_server/util'
12 # ex. "floodgate-900-0"
15 return /^floodgate\-\d+\-\d+$/.match(str) ? true : false
18 def history_file_path(gamename)
19 return nil unless game_name?(gamename)
20 filename = "floodgate_history_%s.yaml" % [gamename.gsub("floodgate-", "").gsub("-","_")]
21 file = File.join($topdir, filename)
22 return Pathname.new(file)
26 # @next_time is updated if and only if charge() was called
28 attr_reader :next_time
29 attr_reader :league, :game_name
32 def initialize(league, hash={})
34 @next_time = hash[:next_time] || nil
35 @game_name = hash[:game_name] || "floodgate-900-0"
36 # Options will be updated by NextTimeGenerator and then passed to a
39 @options[:pairing_factory] = hash[:pairing_factory] || "default_factory"
40 @options[:sacrifice] = hash[:sacrifice] || "gps500+e293220e3f8a3e59f79f6b0efffaa931"
41 charge if @next_time.nil?
45 return Regexp.new(@game_name).match(str) ? true : false
49 return @options[:pairing_factory]
53 return @options[:sacrifice]
57 ntg = NextTimeGenerator.factory(@game_name)
59 @next_time = ntg.call(Time.now)
60 @options[:pairing_factory] = ntg.pairing_factory
61 @options[:sacrifice] = ntg.sacrifice
67 # Returns an array of players who are allowed to participate in this
71 players = @league.find_all_players do |pl|
72 pl.status == "game_waiting" &&
73 game_name?(pl.game_name) &&
75 pl.rated? # Only players who have player ID can participate in Floodgate (rating match)
81 log_message("Starting Floodgate games...: %s, %s" % [@game_name, @options])
82 logics = Pairing.send(@options[:pairing_factory], @options)
83 Pairing.match(select_players(), logics)
88 class NextTimeGenerator
90 def factory(game_name)
92 conf_file_name = File.join($topdir, "#{game_name}.conf")
95 ret = NextTimeGenerator_Debug.new
96 elsif File.exists?(conf_file_name)
97 lines = IO.readlines(conf_file_name)
98 ret = NextTimeGeneratorConfig.new(lines)
99 elsif game_name == "floodgate-900-0"
100 ret = NextTimeGenerator_Floodgate_900_0.new
101 elsif game_name == "floodgate-3600-0"
102 ret = NextTimeGenerator_Floodgate_3600_0.new
109 class AbstructNextTimeGenerator
111 attr_reader :pairing_factory
112 attr_reader :sacrifice
117 @pairing_factory = "default_factory"
118 @sacrifice = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
122 # Schedule the next time from configuration files.
125 # # This is a comment line
126 # set <parameter_name> <value>
130 # DoW := "Sun" | "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" |
131 # "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" |
132 # "Friday" | "Saturday"
143 # Specifies a factory function name generating a pairing
144 # method which will be used in a specific Floodgate game.
145 # ex. set pairing_factory floodgate_zyunisen
147 # Specifies a sacrificed player.
148 # ex. set sacrifice gps500+e293220e3f8a3e59f79f6b0efffaa931
150 class NextTimeGeneratorConfig < AbstructNextTimeGenerator
153 # Read configuration contents.
155 def initialize(lines)
160 def call(now=Time.now)
161 if now.kind_of?(Time)
162 now = ::ShogiServer::time2datetime(now)
166 # now.cwday 1(Monday)-7
167 @lines.each do |line|
169 when %r!^\s*set\s+pairing_factory\s+(\w+)!
170 @pairing_factory = $1.chomp
171 when %r!^\s*set\s+sacrifice\s+(.*)!
172 @sacrifice = $1.chomp
173 when %r!^\s*(\w+)\s+(\d{1,2}):(\d{1,2})!
174 dow, hour, minute = $1, $2.to_i, $3.to_i
175 dow_index = ::ShogiServer::parse_dow(dow)
176 next if dow_index.nil?
177 next unless (0..23).include?(hour)
178 next unless (0..59).include?(minute)
179 time = DateTime::commercial(now.cwyear, now.cweek, dow_index, hour, minute) rescue next
180 time += 7 if time <= now
187 log_warning("Floodgate: Unsupported syntax in a next time generator config file: %s" % [line])
190 candidates.map! {|dt| ::ShogiServer::datetime2time(dt)}
191 return candidates.empty? ? nil : candidates.min
195 # Schedule the next time for floodgate-900-0: each 30 minutes
197 class NextTimeGenerator_Floodgate_900_0 < AbstructNextTimeGenerator
207 return Time.mktime(now.year, now.month, now.day, now.hour, 30)
209 return Time.mktime(now.year, now.month, now.day, now.hour) + 3600
214 # Schedule the next time for floodgate-3600-0: each 2 hours (odd hour)
216 class NextTimeGenerator_Floodgate_3600_0 < AbstructNextTimeGenerator
225 return Time.mktime(now.year, now.month, now.day, now.hour) + ((now.hour%2)+1)*3600
229 # Schedule the next time for debug: each 30 seconds.
231 class NextTimeGenerator_Debug < AbstructNextTimeGenerator
241 return Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
243 return Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
254 def factory(pathname)
255 unless ShogiServer::is_writable_file?(pathname.to_s)
256 log_error("Failed to write a history file: %s" % [pathname])
259 history = History.new pathname
267 # Initialize this instance.
268 # @param file_path_name a Pathname object for this storage
270 def initialize(file_path_name)
273 @file = file_path_name
276 # Return a hash describing the game_result
278 # :black: Black's player id
279 # :white: White's player id
280 # :winner: Winner's player id or nil for the game without a winner
281 # :loser: Loser's player id or nil for the game without a loser
283 def make_record(game_result)
285 hash[:game_id] = game_result.game.game_id
286 hash[:black] = game_result.black.player_id
287 hash[:white] = game_result.white.player_id
290 hash[:winner] = game_result.winner.player_id
291 hash[:loser] = game_result.loser.player_id
300 return unless @file.exist?
303 @records = YAML.load_file(@file)
304 unless @records && @records.instance_of?(Array)
305 $logger.error "%s is not a valid yaml file. Instead, an empty array will be used and updated." % [@file]
309 $logger.error "%s is not a valid yaml file. Instead, an empty array will be used and updated." % [@file]
316 @file.open("w") do |f|
317 f << YAML.dump(@records)
324 def update(game_result)
325 record = make_record(game_result)
326 @@mutex.synchronize do
329 while @records.size > @max_records
336 def last_win?(player_id)
337 rc = last_valid_game(player_id)
338 return false unless rc
339 return rc[:winner] == player_id
342 def last_lose?(player_id)
343 rc = last_valid_game(player_id)
344 return false unless rc
345 return rc[:loser] == player_id
348 def last_opponent(player_id)
349 rc = last_valid_game(player_id)
351 if rc[:black] == player_id
353 elsif rc[:white] == player_id
360 def last_valid_game(player_id)
362 @@mutex.synchronize do
363 records = @records.reverse
365 rc = records.find do |rc|
368 (rc[:black] == player_id || rc[:white] == player_id)
373 def win_games(player_id)
375 @@mutex.synchronize do
376 records = @records.reverse
378 rc = records.find_all do |rc|
379 rc[:winner] == player_id && rc[:loser]
384 def loss_games(player_id)
386 @@mutex.synchronize do
387 records = @records.reverse
389 rc = records.find_all do |rc|
390 rc[:winner] && rc[:loser] == player_id
397 end # class Floodgate
401 end # module ShogiServer