import game
import github::hooks
+import counter
+
+redef class GameEntity
+
+ # Statistics manager for this entity.
+ fun stats: GameStatsManager is abstract
+end
redef class Game
- # Statistics for this game instance.
- var stats = new GameStats
+ redef var stats is lazy do return new GameStatsManager(game, self)
- redef fun from_json(json) do
+ redef fun save do
super
- if json.has_key("statistics") then
- stats.from_json(json["statistics"].as(JsonObject))
- end
+ stats.save_in(self.key)
end
- redef fun to_json do
- var obj = super
- obj["statistics"] = stats.to_json
- return obj
+ redef fun pretty do
+ var res = new FlatBuffer
+ res.append super
+ res.append "# stats:\n"
+ res.append stats.pretty
+ return res.write_to_string
end
+end
+
+redef class Player
+
+ redef var stats is lazy do return new GameStatsManager(game, self)
+
+ redef fun save do
+ super
+ stats.save_in(self.key)
+ end
+
+ redef fun nitcoins do return stats["nitcoins"]
+ redef fun nitcoins=(nc) do stats["nitcoins"] = nc
redef fun pretty do
var res = new FlatBuffer
res.append stats.pretty
return res.write_to_string
end
+end
- redef fun clear do
- super
- stats.clear
+# Store game stats for defined period.
+class GameStatsManager
+ super GameEntity
+ super Counter[String]
+
+ redef var game
+
+ # The GameEntity monitored by these statistics.
+ var owner: GameEntity
+
+ redef var key = "stats"
+
+ # Returns the `GameStats` instance for the overall statistics.
+ var overall: GameStats is lazy do
+ return load_stats_for("all")
+ end
+
+ # Returns the `GameStats` instance for the current year statistics.
+ var yearly: GameStats is lazy do
+ var date = new Tm.gmtime
+ var key = date.strftime("%Y")
+ return load_stats_for(key)
end
+
+ # Returns the `GameStats` instance for the current month statistics.
+ var monthly: GameStats is lazy do
+ var date = new Tm.gmtime
+ var key = date.strftime("%Y-%m")
+ return load_stats_for(key)
+ end
+
+ # Returns the `GameStats` instance for the current day statistics.
+ var daily: GameStats is lazy do
+ var date = new Tm.gmtime
+ var key = date.strftime("%Y-%m-%d")
+ return load_stats_for(key)
+ end
+
+ # Returns the `GameStats` instance for the current week statistics.
+ var weekly: GameStats is lazy do
+ var date = new Tm.gmtime
+ var key = date.strftime("%Y-W%U")
+ return load_stats_for(key)
+ end
+
+ # Load statistics for a `period` key.
+ fun load_stats_for(period: String): GameStats do
+ var key = owner.key / self.key / period
+ if not game.store.has_key(key) then
+ return new GameStats(game, period)
+ end
+ var json = game.store.load_object(key)
+ return new GameStats.from_json(game, period, json)
+ end
+
+ redef fun [](key) do return overall[key]
+
+ redef fun []=(key, value) do
+ overall[key] = value
+ yearly[key] = value
+ monthly[key] = value
+ daily[key] = value
+ weekly[key] = value
+ end
+
+ redef fun inc(e) do
+ overall.inc(e)
+ yearly.inc(e)
+ monthly.inc(e)
+ daily.inc(e)
+ weekly.inc(e)
+ end
+
+ redef fun dec(e) do
+ overall.dec(e)
+ yearly.dec(e)
+ monthly.dec(e)
+ daily.dec(e)
+ weekly.dec(e)
+ end
+
+ redef fun save_in(key) do
+ overall.save_in(key / self.key)
+ yearly.save_in(key / self.key)
+ monthly.save_in(key / self.key)
+ daily.save_in(key / self.key)
+ weekly.save_in(key / self.key)
+ end
+
+ redef fun pretty do return overall.pretty
end
# Game statistics structure that can be saved as a `GameEntity`.
class GameStats
super GameEntity
+ super Counter[String]
- redef var key = "statistics"
+ redef var game
- # Used internally to stats values.
- private var stats = new HashMap[String, Int]
+ # The pedriod these stats are about.
+ var period: String
- init do clear
+ redef fun key do return period
- # Load `self` from saved data
- private fun from_json(json: JsonObject) do
- for k, v in json do stats[k] = v.as(Int)
+ # Load `self` from saved data.
+ init from_json(game: Game, period: String, json: JsonObject) do
+ for k, v in json do self[k] = v.as(Int)
end
redef fun to_json do
var obj = new JsonObject
- for k, v in stats do obj[k] = v
+ for k, v in self do obj[k] = v
return obj
end
- # Retrieves the current value of `key` statistic entry.
- fun [](key: String): Int do return stats[key]
-
- # Increments the value of `key` statistic entry by 1.
- fun incr(key: String) do stats[key] += 1
-
- # Decrements the value of `key` statistic entry by 1.
- fun decr(key: String) do stats[key] -= 1
-
- # Reset game stats.
- fun clear do
- stats["issues"] = 0
- stats["issues_open"] = 0
- stats["pulls"] = 0
- stats["pulls_open"] = 0
- end
-
redef fun pretty do
var res = new FlatBuffer
- for k, v in stats do
+ for k, v in self do
res.append "# {v} {k}\n"
end
return res.write_to_string
class StatisticsReactor
super GameReactor
- redef fun react_event(game, e) do
- super # log events
- e.react_stats_event(game)
- game.save
- end
+ redef fun react_event(game, e) do e.react_stats_event(game)
end
redef class GithubEvent
# Count opened and closed issues.
redef fun react_stats_event(game) do
+ var player = issue.user.player(game)
if action == "opened" then
- game.stats.incr("issues")
- game.stats.incr("issues_open")
+ game.stats.inc("issues")
+ game.stats.inc("issues_open")
+ game.save
+ player.stats.inc("issues")
+ player.stats.inc("issues_open")
+ player.save
else if action == "reopened" then
- game.stats.incr("issues_open")
+ game.stats.inc("issues_open")
+ game.save
+ player.stats.inc("issues_open")
+ player.save
else if action == "closed" then
- game.stats.decr("issues_open")
+ game.stats.dec("issues_open")
+ game.save
+ player.stats.dec("issues_open")
+ player.save
end
end
end
# Count opened and closed pull requests.
redef fun react_stats_event(game) do
+ var player = pull.user.player(game)
if action == "opened" then
- game.stats.incr("pulls")
- game.stats.incr("pulls_open")
+ game.stats.inc("pulls")
+ game.stats.inc("pulls_open")
+ game.save
+ player.stats.inc("pulls")
+ player.stats.inc("pulls_open")
+ player.save
else if action == "reopened" then
- game.stats.incr("pulls_open")
+ game.stats.inc("pulls_open")
+ game.save
+ player.stats.inc("pulls_open")
+ player.save
else if action == "closed" then
- game.stats.decr("pulls_open")
+ game.stats.dec("pulls_open")
+ player.stats.dec("pulls_open")
+ if pull.merged then
+ game.stats["commits"] += pull.commits
+ player.stats["commits"] += pull.commits
+ end
+ game.save
+ player.save
end
end
end
+
+redef class IssueCommentEvent
+
+ # Count posted comments
+ redef fun react_stats_event(game) do
+ if action == "created" then
+ var player = comment.user.player(game)
+ game.stats.inc("comments")
+ player.stats.inc("comments")
+ # FIXME use a more precise way to locate reviews
+ if comment.has_ok_review then
+ game.stats.inc("reviews")
+ player.stats.inc("reviews")
+ end
+ game.save
+ player.save
+ end
+ end
+end
+
+redef class IssueComment
+ # Does this comment contain a "+1"?
+ fun has_ok_review: Bool do return body.has("\\+1\\b".to_re)
+end