X-Git-Url: http://git.sourceforge.jp/view?p=shogi-server%2Fshogi-server.git;a=blobdiff_plain;f=shogi_server%2Fleague%2Ffloodgate.rb;h=f721d3a35f942820345334d9263759cadb2e49fd;hp=317dfe1d8954d744e0a037d0f9259e17d38ec740;hb=b5fcb695396012f886d9fd0828dc8ffe36ed36a9;hpb=6165fe664fa1c71e5a6c16fe75fa30d913b32336 diff --git a/shogi_server/league/floodgate.rb b/shogi_server/league/floodgate.rb index 317dfe1..f721d3a 100644 --- a/shogi_server/league/floodgate.rb +++ b/shogi_server/league/floodgate.rb @@ -1,49 +1,279 @@ +require 'shogi_server/util' +require 'date' +require 'thread' +require 'ostruct' +require 'pathname' + module ShogiServer class League class Floodgate class << self + # ex. "floodgate-900-0" + # def game_name?(str) - return /^floodgate-\d+-\d+$/.match(str) ? true : false + return /^floodgate\-\d+\-\d+$/.match(str) ? true : false end - end - attr_reader :next_time, :league + def history_file_path(gamename) + return nil unless game_name?(gamename) + filename = "floodgate_history_%s.yaml" % [gamename.gsub("floodgate-", "").gsub("-","_")] + file = File.join($topdir, filename) + return Pathname.new(file) + end + end # class method - def initialize(league, next_time=nil) + # @next_time is updated if and only if charge() was called + # + attr_reader :next_time + attr_reader :league, :game_name + + def initialize(league, hash={}) @league = league - @next_time = next_time - charge + @next_time = hash[:next_time] || nil + @game_name = hash[:game_name] || "floodgate-900-0" + charge if @next_time.nil? + end + + def game_name?(str) + return Regexp.new(@game_name).match(str) ? true : false end def charge - now = Time.now - unless $DEBUG - # each 30 minutes - if now.min < 30 - @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30) - else - @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600 - end + ntg = NextTimeGenerator.factory(@game_name) + if ntg + @next_time = ntg.call(Time.now) else - # for test, each 30 seconds - if now.sec < 30 - @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30) - else - @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60 - end + @next_time = nil end end def match_game players = @league.find_all_players do |pl| pl.status == "game_waiting" && - Floodgate.game_name?(pl.game_name) && + game_name?(pl.game_name) && pl.sente == nil end Pairing.match(players) end + + # + # + class NextTimeGenerator + class << self + def factory(game_name) + ret = nil + conf_file_name = File.join($topdir, "#{game_name}.conf") + + if $DEBUG + ret = NextTimeGenerator_Debug.new + elsif File.exists?(conf_file_name) + lines = IO.readlines(conf_file_name) + ret = NextTimeGeneratorConfig.new(lines) + elsif game_name == "floodgate-900-0" + ret = NextTimeGenerator_Floodgate_900_0.new + elsif game_name == "floodgate-3600-0" + ret = NextTimeGenerator_Floodgate_3600_0.new + end + return ret + end + end + end + + # Schedule the next time from configuration files. + # + # Line format: + # # This is a comment line + # DoW Time + # ... + # where + # DoW := "Sun" | "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | + # "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | + # "Friday" | "Saturday" + # Time := HH:MM + # + # For example, + # Sat 13:00 + # Sat 22:00 + # Sun 13:00 + # + class NextTimeGeneratorConfig + + # Constructor. + # Read configuration contents. + # + def initialize(lines) + @lines = lines + end + + def call(now=Time.now) + if now.kind_of?(Time) + now = ::ShogiServer::time2datetime(now) + end + candidates = [] + # now.cweek 1-53 + # now.cwday 1(Monday)-7 + @lines.each do |line| + if %r!^\s*(\w+)\s+(\d{1,2}):(\d{1,2})! =~ line + dow, hour, minute = $1, $2.to_i, $3.to_i + dow_index = ::ShogiServer::parse_dow(dow) + next if dow_index.nil? + next unless (0..23).include?(hour) + next unless (0..59).include?(minute) + time = DateTime::commercial(now.year, now.cweek, dow_index, hour, minute) rescue next + time += 7 if time <= now + candidates << time + end + end + candidates.map! {|dt| ::ShogiServer::datetime2time(dt)} + return candidates.empty? ? nil : candidates.min + end + end + + # Schedule the next time for floodgate-900-0: each 30 minutes + # + class NextTimeGenerator_Floodgate_900_0 + def call(now) + if now.min < 30 + return Time.mktime(now.year, now.month, now.day, now.hour, 30) + else + return Time.mktime(now.year, now.month, now.day, now.hour) + 3600 + end + end + end + + # Schedule the next time for floodgate-3600-0: each 2 hours (odd hour) + # + class NextTimeGenerator_Floodgate_3600_0 + def call(now) + return Time.mktime(now.year, now.month, now.day, now.hour) + ((now.hour%2)+1)*3600 + end + end + + # Schedule the next time for debug: each 30 seconds. + # + class NextTimeGenerator_Debug + def call(now) + if now.sec < 30 + return Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30) + else + return Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60 + end + end + end + + # + # + class History + @@mutex = Mutex.new + + class << self + def factory(pathname) + unless ShogiServer::is_writable_file?(pathname.to_s) + log_error("Failed to write a history file: %s" % [pathname]) + return nil + end + history = History.new pathname + history.load + return history + end + end + + attr_reader :records + + # Initialize this instance. + # @param file_path_name a Pathname object for this storage + # + def initialize(file_path_name) + @records = [] + @max_records = 100 + @file = file_path_name + end + + # Return a hash describing the game_result + # :game_id: game id + # :black: Black's player id + # :white: White's player id + # :winner: Winner's player id or nil for the game without a winner + # :loser: Loser's player id or nil for the game without a loser + # + def make_record(game_result) + hash = Hash.new + hash[:game_id] = game_result.game.game_id + hash[:black] = game_result.black.player_id + hash[:white] = game_result.white.player_id + case game_result + when GameResultWin + hash[:winner] = game_result.winner.player_id + hash[:loser] = game_result.loser.player_id + else + hash[:winner] = nil + hash[:loser] = nil + end + return hash + end + + def load + return unless @file.exist? + + @records = YAML.load_file(@file) + unless @records && @records.instance_of?(Array) + $logger.error "%s is not a valid yaml file. Instead, an empty array will be used and updated." % [@file] + @records = [] + end + end + + def save + begin + @file.open("w") do |f| + f << YAML.dump(@records) + end + rescue Errno::ENOSPC + # ignore + end + end + + def update(game_result) + record = make_record(game_result) + @@mutex.synchronize do + load + @records << record + while @records.size > @max_records + @records.shift + end + save + end + end + + def last_win?(player_id) + rc = last_valid_game(player_id) + return false unless rc + return rc[:winner] == player_id + end + + def last_lose?(player_id) + rc = last_valid_game(player_id) + return false unless rc + return rc[:loser] == player_id + end + + def last_valid_game(player_id) + records = nil + @@mutex.synchronize do + records = @records.reverse + end + rc = records.find do |rc| + rc[:winner] && + rc[:loser] && + (rc[:black] == player_id || rc[:white] == player_id) + end + return rc + end + end # class History + + end # class Floodgate + end # class League end # module ShogiServer