# This file is part of NIT ( http://www.nitlanguage.org ). # # Copyright 2014-2015 Alexandre Terrasa # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # `nitrpg` achievements. # # Players can unlock achievements by performing remarkable actions on the repo. # Achievements are rewarded by nitcoins. module achievements import events import statistics redef class GameEntity # Register a new achievement for this game entity. # # Saves the achievement in game data. # Do nothing is the achievement is already registered. # # TODO should update the achievement? fun add_achievement(achievement: Achievement) do var key = self.key / achievement.key if game.store.has_key(key) then return stats.inc("achievements") achievement.save_in(self) save end # Load the event from its `id`. # # Looks for the event save file in game data. # Returns `null` if the event cannot be found. fun load_achievement(id: String): nullable Achievement do var key = self.key / "achievements" / id if not game.store.has_key(key) then return null var json = game.store.load_object(key) return new Achievement.from_json(game, json) end # List all events registered in this entity. # # This list is reloaded from game data each time its called. # # To add events see `add_event`. fun load_achievements: MapRead[String, Achievement] do var res = new HashMap[String, Achievement] var key = self.key / "achievements" if not game.store.has_collection(key) then return res var coll = game.store.list_collection(key) for id in coll do res[id.to_s] = load_achievement(id.to_s).as(not null) end return res end end # Achievements are rewarded by `nitcoins`. # # An achievement represents a notable action performed by a `Player`. # Player that `unlock` achievements are rewarded by nitcoins. class Achievement super GameEntity redef var key is lazy do return "achievements" / id redef var game # Uniq ID for this achievement. var id: String # Name of this achievement. var name: String # Description of the achievement. var desc: String # Reward that this achievement give in nitcoins. var reward: Int # Is this achievement unlocked by somebody? var is_unlocked: Bool is lazy do return not load_events.is_empty # Init `self` from a `json` object. # # Used to load achievements from storage. init from_json(game: Game, json: JsonObject) do self.game = game id = json["id"].to_s name = json["name"].to_s desc = json["desc"].to_s reward = json["reward"].as(Int) end redef fun to_json do var json = super json["id"] = id json["name"] = name json["desc"] = desc json["reward"] = reward return json end end redef class Player # Is `a` unlocked for this `Player`? fun has_achievement(a: Achievement): Bool do return load_achievement(a.id) != null end # Unlocks an achievement for this Player based on a GithubEvent. # # Register the achievement and adds the achievement reward to the player # nitcoins. # # Do nothing is this player has already unlocked the achievement. # # TODO: add abstraction so achievements do not depend on GithubEvent. fun unlock_achievement(a: Achievement, event: GithubEvent) do if has_achievement(a) then return nitcoins += a.reward add_achievement(a) trigger_unlock_event(a, event) end # Create a new event that marks the achievement unlocking. fun trigger_unlock_event(achievement: Achievement, event: GithubEvent) do var obj = new JsonObject obj["player"] = name obj["reward"] = achievement.reward obj["achievement"] = achievement.id obj["github_event"] = event.json var ge = new GameEvent(game, "achievement_unlocked", obj) add_event(ge) game.add_event(ge) achievement.add_event(ge) end end # `GameReactor` dedicated to achievements unlocking. interface AchievementReactor super GameReactor # Unic ID of the achievement this reactor unlocks. fun id: String is abstract # Name of the achievement this reactor unlocks. fun name: String is abstract # Description of the achievement this reactor unlocks. fun desc: String is abstract # Amount of nitcoins rewarded for unlocking the achievement. fun reward: Int is abstract # Return a new instance of the achievement to unlock. fun new_achievement(game: Game): Achievement do var achievement = new Achievement(game, id, name, desc, reward) game.add_achievement(achievement) return achievement end end ##################### ### Issues ##################### # Unlock achievement after X issues. # # Used to factorize behavior. abstract class PlayerXIssues super AchievementReactor # Number of PR required to unlock the achievement. var threshold: Int is noinit redef fun react_event(game, event) do if not event isa IssuesEvent then return if not event.action == "opened" then return var player = event.issue.user.player(game) if player.stats["issues"] == threshold then var a = new_achievement(game) player.unlock_achievement(a, event) end end end # Player open his first issue. class Player1Issue super PlayerXIssues redef var id = "player_1_issue" redef var name = "First complaint" redef var desc = "Open your first issue." redef var reward = 10 redef var threshold = 1 end # Player open 100 issues. class Player100Issues super PlayerXIssues redef var id = "player_100_issues" redef var name = "Mature whiner" redef var desc = "Open 100 issues in the game." redef var reward = 100 redef var threshold = 100 end # Player open 1 000 issues. class Player1KIssues super PlayerXIssues redef var id = "player_1000_issues" redef var name = "You, sir, complain a lot" redef var desc = "Open 1000 issues in the game." redef var reward = 1000 redef var threshold = 1000 end # Player open an issue about nitdoc. class IssueAboutNitdoc super AchievementReactor redef var id = "issue_about_nitdoc" redef var name = "Say nitdoc again, I double dare you!" redef var desc = "Open an issue with \"nitdoc\" in the title." redef var reward = 10 redef fun react_event(game, event) do if not event isa IssuesEvent then return if not event.action == "opened" then return var player = event.issue.user.player(game) var re = "nitdoc".to_re re.ignore_case = true if event.issue.title.has(re) then var a = new_achievement(game) player.unlock_achievement(a, event) end end end # Player open an issue about FFI. class IssueAboutFFI super PlayerXIssues redef var id = "issue_about_ffi" redef var name = "Polyglot what?" redef var desc = "Open an issue with `ffi` in the title." redef var reward = 10 redef fun react_event(game, event) do if not event isa IssuesEvent then return if not event.action == "opened" then return var player = event.issue.user.player(game) var re = "\\bffi\\b".to_re re.ignore_case = true if event.issue.title.has(re) then var a = new_achievement(game) player.unlock_achievement(a, event) end end end ##################### ### Pull requests ##################### # Unlock achievement after X pull requests. # # Used to factorize behavior. abstract class PlayerXPulls super AchievementReactor # Number of PR required to unlock the achievement. var threshold: Int is noinit redef fun react_event(game, event) do if not event isa PullRequestEvent then return if not event.action == "opened" then return var player = event.pull.user.player(game) if player.stats["pulls"] == threshold then var a = new_achievement(game) player.unlock_achievement(a, event) end end end # Open your first pull request. class Player1Pull super PlayerXPulls redef var id = "player_1_pull" redef var name = "First PR" redef var desc = "Open your first pull request." redef var reward = 10 redef var threshold = 1 end # Author 100 pull requests. class Player100Pulls super PlayerXPulls redef var id = "player_100_pulls" redef var name = "100 pull requests!!!" redef var desc = "Open 100 pull requests in the game." redef var reward = 100 redef var threshold = 100 end # Author 1000 pull requests. class Player1KPulls super PlayerXPulls redef var id = "player_1000_pulls" redef var name = "1000 PULL REQUESTS!!!" redef var desc = "Open 1000 pull requests in the game." redef var reward = 1000 redef var threshold = 1000 end ##################### ### Commits ##################### # Unlock achievement after X merged commits. # # Used to factorize behavior. abstract class PlayerXCommits super AchievementReactor # Number of PR required to unlock the achievement. var threshold: Int is noinit redef fun react_event(game, event) do if not event isa PullRequestEvent then return if not event.action == "closed" then return if not event.pull.merged then return var player = event.pull.user.player(game) if player.stats["commits"] == threshold then var a = new_achievement(game) player.unlock_achievement(a, event) end end end # Author your first commit in the game. class Player1Commit super PlayerXCommits redef var id = "player_1_commit" redef var name = "First blood" redef var desc = "Author your first commit in the game." redef var reward = 10 redef var threshold = 1 end # Author 100 commits. class Player100Commits super PlayerXCommits redef var id = "player_100_commits" redef var name = "100 commits" redef var desc = "Author 100 commits in the game." redef var reward = 100 redef var threshold = 100 end # Author 1 000 commits. class Player1KCommits super PlayerXCommits redef var id = "player_1000_commits" redef var name = "1000 commits!!!" redef var desc = "Author 1000 commits in the game." redef var reward = 1000 redef var threshold = 1000 end # Author 10 000 commits. class Player10KCommits super PlayerXCommits redef var id = "player_10000_commits" redef var name = "10000 COMMITS!!!" redef var desc = "Author 10000 commits in the game." redef var reward = 10000 redef var threshold = 10000 end