OSDN Git Service

e3902de83b611b0d19924edf3697bbbd05876f86
[shogi-server/shogi-server.git] / shogi_server / league / floodgate.rb
1 require 'thread'
2 require 'ostruct'
3 require 'pathname'
4
5 module ShogiServer
6
7 class League
8   class Floodgate
9     class << self
10       # ex. "floodgate-900-0"
11       #
12       def game_name?(str)
13         return /^floodgate\-\d+\-\d+$/.match(str) ? true : false
14       end
15
16       def history_file_path(gamename)
17         return nil unless game_name?(gamename)
18         filename = "floodgate_history_%s.yaml" % [gamename.gsub("floodgate-", "").gsub("-","_")]
19         file = File.join($topdir, filename)
20         return Pathname.new(file)
21       end
22     end # class method
23
24     attr_reader :next_time, :league, :game_name
25
26     def initialize(league, hash={})
27       @league = league
28       @next_time = hash[:next_time] || nil
29       @game_name = hash[:game_name] || "floodgate-900-0"
30       charge
31     end
32
33     def game_name?(str)
34       return Regexp.new(@game_name).match(str) ? true : false
35     end
36
37     def charge
38       ntg = NextTimeGenerator.factory(@game_name)
39       if ntg
40         @next_time = ntg.call(Time.now)
41       else
42         @next_time = nil
43       end
44     end
45
46     def match_game
47       players = @league.find_all_players do |pl|
48         pl.status == "game_waiting" &&
49         game_name?(pl.game_name) &&
50         pl.sente == nil
51       end
52       Pairing.match(players)
53     end
54     
55     #
56     #
57     class NextTimeGenerator
58       class << self
59         def factory(game_name)
60           ret = nil
61           if $DEBUG
62             ret = NextTimeGenerator_Debug.new
63           elsif game_name == "floodgate-900-0"
64             ret = NextTimeGenerator_Floodgate_900_0.new
65           elsif game_name == "floodgate-3600-0"
66             ret = NextTimeGenerator_Floodgate_3600_0.new
67           end
68           return ret
69         end
70       end
71     end
72
73     class NextTimeGenerator_Floodgate_900_0
74       def call(now)
75         # each 30 minutes
76         if now.min < 30
77           return Time.mktime(now.year, now.month, now.day, now.hour, 30)
78         else
79           return Time.mktime(now.year, now.month, now.day, now.hour) + 3600
80         end
81       end
82     end
83
84     class NextTimeGenerator_Floodgate_3600_0
85       def call(now)
86         # each 2 hours (odd hour)
87         return Time.mktime(now.year, now.month, now.day, now.hour) + ((now.hour%2)+1)*3600
88       end
89     end
90
91     class NextTimeGenerator_Debug
92       def call(now)
93         # for test, each 30 seconds
94         if now.sec < 30
95           return Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
96         else
97           return Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
98         end
99       end
100     end
101
102     #
103     #
104     class History
105       @@mutex = Mutex.new
106
107       class << self
108         def factory(pathname)
109           unless ShogiServer::is_writable_file?(pathname.to_s)
110             log_error("Failed to write a history file: %s" % [pathname]) 
111             return nil
112           end
113           history = History.new pathname
114           history.load
115           return history
116         end
117       end
118
119       attr_reader :records
120
121       # Initialize this instance.
122       # @param file_path_name a Pathname object for this storage
123       #
124       def initialize(file_path_name)
125         @records = []
126         @max_records = 100
127         @file = file_path_name
128       end
129
130       # Return a hash describing the game_result
131       # :game_id: game id
132       # :black:   Black's player id
133       # :white:   White's player id
134       # :winner:  Winner's player id or nil for the game without a winner
135       # :loser:   Loser's player id or nil for the game without a loser
136       #
137       def make_record(game_result)
138         hash = Hash.new
139         hash[:game_id] = game_result.game.game_id
140         hash[:black]   = game_result.black.player_id
141         hash[:white]   = game_result.white.player_id
142         case game_result
143         when GameResultWin
144           hash[:winner] = game_result.winner.player_id
145           hash[:loser]  = game_result.loser.player_id
146         else
147           hash[:winner] = nil
148           hash[:loser]  = nil
149         end
150         return hash
151       end
152
153       def load
154         return unless @file.exist?
155
156         @records = YAML.load_file(@file)
157         unless @records && @records.instance_of?(Array)
158           $logger.error "%s is not a valid yaml file. Instead, an empty array will be used and updated." % [@file]
159           @records = []
160         end
161       end
162
163       def save
164         begin
165           @file.open("w") do |f| 
166             f << YAML.dump(@records)
167           end
168         rescue Errno::ENOSPC
169           # ignore
170         end
171       end
172
173       def update(game_result)
174         record = make_record(game_result)
175         @@mutex.synchronize do 
176           load
177           @records << record
178           while @records.size > @max_records
179             @records.shift
180           end
181           save
182         end
183       end
184       
185       def last_win?(player_id)
186         rc = last_valid_game(player_id)
187         return false unless rc
188         return rc[:winner] == player_id
189       end
190       
191       def last_lose?(player_id)
192         rc = last_valid_game(player_id)
193         return false unless rc
194         return rc[:loser] == player_id
195       end
196
197       def last_valid_game(player_id)
198         records = nil
199         @@mutex.synchronize do
200           records = @records.reverse
201         end
202         rc = records.find do |rc|
203           rc[:winner] && 
204           rc[:loser]  && 
205           (rc[:black] == player_id || rc[:white] == player_id)
206         end
207         return rc
208       end
209     end # class History
210
211
212   end # class Floodgate
213
214
215 end # class League
216 end # module ShogiServer