1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # `nitrpg` achievements.
19 # Players can unlock achievements by performing remarkable actions on the repo.
20 # Achievements are rewarded by nitcoins.
26 redef class GameEntity
28 # Register a new achievement for this game entity.
30 # Saves the achievement in game data.
31 # Do nothing is the achievement is already registered.
33 # TODO should update the achievement?
34 fun add_achievement
(achievement
: Achievement) do
35 var key
= self.key
/ achievement
.key
36 if game
.store
.has_key
(key
) then return
37 stats
.inc
("achievements")
38 achievement
.save_in
(self)
42 # Load the event from its `id`.
44 # Looks for the event save file in game data.
45 # Returns `null` if the event cannot be found.
46 fun load_achievement
(id
: String): nullable Achievement do
47 var key
= self.key
/ "achievements" / id
48 if not game
.store
.has_key
(key
) then return null
49 var json
= game
.store
.load_object
(key
)
50 return new Achievement.from_json
(game
, json
)
53 # List all events registered in this entity.
55 # This list is reloaded from game data each time its called.
57 # To add events see `add_event`.
58 fun load_achievements
: MapRead[String, Achievement] do
59 var res
= new HashMap[String, Achievement]
60 var key
= self.key
/ "achievements"
61 if not game
.store
.has_collection
(key
) then return res
62 var coll
= game
.store
.list_collection
(key
)
64 res
[id
.to_s
] = load_achievement
(id
.to_s
).as(not null)
70 # Achievements are rewarded by `nitcoins`.
72 # An achievement represents a notable action performed by a `Player`.
73 # Player that `unlock` achievements are rewarded by nitcoins.
77 redef var key
is lazy
do return "achievements" / id
81 # Uniq ID for this achievement.
84 # Name of this achievement.
87 # Description of the achievement.
90 # Reward that this achievement give in nitcoins.
93 # Is this achievement unlocked by somebody?
94 var is_unlocked
: Bool is lazy
do return not load_events
.is_empty
96 # Init `self` from a `json` object.
98 # Used to load achievements from storage.
99 init from_json
(game
: Game, json
: JsonObject) do
102 name
= json
["name"].to_s
103 desc
= json
["desc"].to_s
104 reward
= json
["reward"].as(Int)
112 json
["reward"] = reward
119 # Is `a` unlocked for this `Player`?
120 fun has_achievement
(a
: Achievement): Bool do
121 return load_achievement
(a
.id
) != null
124 # Unlocks an achievement for this Player based on a GithubEvent.
126 # Register the achievement and adds the achievement reward to the player
129 # Do nothing is this player has already unlocked the achievement.
131 # TODO: add abstraction so achievements do not depend on GithubEvent.
132 fun unlock_achievement
(a
: Achievement, event
: GithubEvent) do
133 if has_achievement
(a
) then return
136 trigger_unlock_event
(a
, event
)
139 # Create a new event that marks the achievement unlocking.
140 fun trigger_unlock_event
(achievement
: Achievement, event
: GithubEvent) do
141 var obj
= new JsonObject
143 obj
["reward"] = achievement
.reward
144 obj
["achievement"] = achievement
.id
145 obj
["github_event"] = event
.json
146 var ge
= new GameEvent(game
, "achievement_unlocked", obj
)
149 achievement
.add_event
(ge
)
153 # `GameReactor` dedicated to achievements unlocking.
154 interface AchievementReactor
157 # Unic ID of the achievement this reactor unlocks.
158 fun id
: String is abstract
160 # Name of the achievement this reactor unlocks.
161 fun name
: String is abstract
163 # Description of the achievement this reactor unlocks.
164 fun desc
: String is abstract
166 # Amount of nitcoins rewarded for unlocking the achievement.
167 fun reward
: Int is abstract
169 # Return a new instance of the achievement to unlock.
170 fun new_achievement
(game
: Game): Achievement do
171 var achievement
= new Achievement(game
, id
, name
, desc
, reward
)
172 game
.add_achievement
(achievement
)
177 #####################
179 #####################
181 # Unlock achievement after X issues.
183 # Used to factorize behavior.
184 abstract class PlayerXIssues
185 super AchievementReactor
187 # Number of PR required to unlock the achievement.
188 var threshold
: Int is noinit
190 redef fun react_event
(game
, event
) do
191 if not event
isa IssuesEvent then return
192 if not event
.action
== "opened" then return
193 var player
= event
.issue
.user
.player
(game
)
194 if player
.stats
["issues"] == threshold
then
195 var a
= new_achievement
(game
)
196 player
.unlock_achievement
(a
, event
)
201 # Player open his first issue.
205 redef var id
= "player_1_issue"
206 redef var name
= "First complaint"
207 redef var desc
= "Open your first issue."
208 redef var reward
= 10
209 redef var threshold
= 1
212 # Player open 100 issues.
213 class Player100Issues
216 redef var id
= "player_100_issues"
217 redef var name
= "Mature whiner"
218 redef var desc
= "Open 100 issues in the game."
219 redef var reward
= 100
220 redef var threshold
= 100
223 # Player open 1 000 issues.
227 redef var id
= "player_1000_issues"
228 redef var name
= "You, sir, complain a lot"
229 redef var desc
= "Open 1000 issues in the game."
230 redef var reward
= 1000
231 redef var threshold
= 1000
234 # Player open an issue about nitdoc.
235 class IssueAboutNitdoc
236 super AchievementReactor
238 redef var id
= "issue_about_nitdoc"
239 redef var name
= "Say nitdoc again, I double dare you!"
240 redef var desc
= "Open an issue with \"nitdoc\
" in the title."
241 redef var reward
= 10
243 redef fun react_event
(game
, event
) do
244 if not event
isa IssuesEvent then return
245 if not event
.action
== "opened" then return
246 var player
= event
.issue
.user
.player
(game
)
247 var re
= "nitdoc".to_re
248 re
.ignore_case
= true
249 if event
.issue
.title
.has
(re
) then
250 var a
= new_achievement
(game
)
251 player
.unlock_achievement
(a
, event
)
256 # Player open an issue about FFI.
260 redef var id
= "issue_about_ffi"
261 redef var name
= "Polyglot what?"
262 redef var desc
= "Open an issue with `ffi` in the title."
263 redef var reward
= 10
265 redef fun react_event
(game
, event
) do
266 if not event
isa IssuesEvent then return
267 if not event
.action
== "opened" then return
268 var player
= event
.issue
.user
.player
(game
)
269 var re
= "\\bffi\\b".to_re
270 re
.ignore_case
= true
271 if event
.issue
.title
.has
(re
) then
272 var a
= new_achievement
(game
)
273 player
.unlock_achievement
(a
, event
)
278 #####################
280 #####################
282 # Unlock achievement after X pull requests.
284 # Used to factorize behavior.
285 abstract class PlayerXPulls
286 super AchievementReactor
288 # Number of PR required to unlock the achievement.
289 var threshold
: Int is noinit
291 redef fun react_event
(game
, event
) do
292 if not event
isa PullRequestEvent then return
293 if not event
.action
== "opened" then return
294 var player
= event
.pull
.user
.player
(game
)
295 if player
.stats
["pulls"] == threshold
then
296 var a
= new_achievement
(game
)
297 player
.unlock_achievement
(a
, event
)
302 # Open your first pull request.
306 redef var id
= "player_1_pull"
307 redef var name
= "First PR"
308 redef var desc
= "Open your first pull request."
309 redef var reward
= 10
310 redef var threshold
= 1
313 # Author 100 pull requests.
317 redef var id
= "player_100_pulls"
318 redef var name
= "100 pull requests!!!"
319 redef var desc
= "Open 100 pull requests in the game."
320 redef var reward
= 100
321 redef var threshold
= 100
324 # Author 1000 pull requests.
328 redef var id
= "player_1000_pulls"
329 redef var name
= "1000 PULL REQUESTS!!!"
330 redef var desc
= "Open 1000 pull requests in the game."
331 redef var reward
= 1000
332 redef var threshold
= 1000
335 #####################
337 #####################
339 # Unlock achievement after X merged commits.
341 # Used to factorize behavior.
342 abstract class PlayerXCommits
343 super AchievementReactor
345 # Number of PR required to unlock the achievement.
346 var threshold
: Int is noinit
348 redef fun react_event
(game
, event
) do
349 if not event
isa PullRequestEvent then return
350 if not event
.action
== "closed" then return
351 if not event
.pull
.merged
then return
352 var player
= event
.pull
.user
.player
(game
)
353 if player
.stats
["commits"] == threshold
then
354 var a
= new_achievement
(game
)
355 player
.unlock_achievement
(a
, event
)
360 # Author your first commit in the game.
364 redef var id
= "player_1_commit"
365 redef var name
= "First blood"
366 redef var desc
= "Author your first commit in the game."
367 redef var reward
= 10
368 redef var threshold
= 1
371 # Author 100 commits.
372 class Player100Commits
375 redef var id
= "player_100_commits"
376 redef var name
= "100 commits"
377 redef var desc
= "Author 100 commits in the game."
378 redef var reward
= 100
379 redef var threshold
= 100
382 # Author 1 000 commits.
383 class Player1KCommits
386 redef var id
= "player_1000_commits"
387 redef var name
= "1000 commits!!!"
388 redef var desc
= "Author 1000 commits in the game."
389 redef var reward
= 1000
390 redef var threshold
= 1000
393 # Author 10 000 commits.
394 class Player10KCommits
397 redef var id
= "player_10000_commits"
398 redef var name
= "10000 COMMITS!!!"
399 redef var desc
= "Author 10000 commits in the game."
400 redef var reward
= 10000
401 redef var threshold
= 10000