c1a803682ba5492a6c1a0ac45945dcb8e0285dab
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 stats
.inc
("achievements")
36 achievement
.owner
= self
40 # Is `a` unlocked for this `Player`?
41 fun has_achievement
(a
: Achievement): Bool do return load_achievement
(a
.id
) != null
43 # Load the event from its `id`.
45 # Looks for the event save file in game data.
46 # Returns `null` if the event cannot be found.
47 fun load_achievement
(id
: String): nullable Achievement do
48 var req
= new JsonObject
50 req
["game"] = game
.key
52 var obj
= game
.db
.collection
("achievements").find
(req
)
53 if obj
isa JsonObject then
54 return new Achievement.from_json
(game
, obj
)
59 # List all events registered in this entity.
61 # This list is reloaded from game data each time its called.
63 # To add events see `add_event`.
64 fun load_achievements
: MapRead[String, Achievement] do
65 var req
= new JsonObject
66 req
["game"] = game
.key
68 var res
= new HashMap[String, Achievement]
69 for obj
in game
.db
.collection
("achievements").find_all
(req
) do
70 var achievement
= new Achievement.from_json
(game
, obj
)
71 res
[achievement
.id
] = achievement
77 # Achievements are rewarded by `nitcoins`.
79 # An achievement represents a notable action performed by a `Player`.
80 # Player that `unlock` achievements are rewarded by nitcoins.
84 redef var collection_name
= "achievements"
89 var owner
= self.owner
90 if owner
== null then return id
91 return "{owner.key}-{id}"
94 # Uniq ID for this achievement.
97 # Name of this achievement.
100 # Description of the achievement.
103 # Reward that this achievement give in nitcoins.
106 # Is this achievement unlocked by somebody?
107 var is_unlocked
: Bool is lazy
do return not load_events
.is_empty
109 # Game entity this achievement is about.
110 var owner
: nullable GameEntity = null
112 # Init `self` from a `json` object.
114 # Used to load achievements from storage.
115 init from_json
(game
: Game, json
: JsonObject) do
116 init(game
, json
["id"].to_s
, json
["name"].to_s
, json
["desc"].to_s
, json
["reward"].as(Int))
124 json
["reward"] = reward
125 json
["game"] = game
.key
126 if owner
!= null then json
["owner"] = owner
.key
132 # Unlocks an achievement for this Player based on a GithubEvent.
134 # Register the achievement and adds the achievement reward to the player
137 # Do nothing is this player has already unlocked the achievement.
139 # TODO: add abstraction so achievements do not depend on GithubEvent.
140 fun unlock_achievement
(a
: Achievement, event
: GithubEvent) do
141 if has_achievement
(a
) then return
144 trigger_unlock_event
(a
, event
)
148 # Create a new event that marks the achievement unlocking.
149 fun trigger_unlock_event
(achievement
: Achievement, event
: GithubEvent) do
150 var obj
= new JsonObject
152 obj
["reward"] = achievement
.reward
153 obj
["achievement"] = achievement
.id
154 obj
["github_event"] = event
.json
155 var ge
= new GameEvent(game
, "achievement_unlocked", obj
)
158 achievement
.add_event
(ge
)
162 # `GameReactor` dedicated to achievements unlocking.
163 interface AchievementReactor
166 # Unic ID of the achievement this reactor unlocks.
167 fun id
: String is abstract
169 # Name of the achievement this reactor unlocks.
170 fun name
: String is abstract
172 # Description of the achievement this reactor unlocks.
173 fun desc
: String is abstract
175 # Amount of nitcoins rewarded for unlocking the achievement.
176 fun reward
: Int is abstract
178 # Return a new instance of the achievement to unlock.
179 fun new_achievement
(game
: Game): Achievement do
180 var achievement
= new Achievement(game
, id
, name
, desc
, reward
)
181 game
.add_achievement
(achievement
)
186 #####################
188 #####################
190 # Unlock achievement after X issues.
192 # Used to factorize behavior.
193 abstract class PlayerXIssues
194 super AchievementReactor
196 # Number of PR required to unlock the achievement.
197 var threshold
: Int is noinit
199 redef fun react_event
(game
, event
) do
200 if not event
isa IssuesEvent then return
201 if not event
.action
== "opened" then return
202 var player
= event
.issue
.user
.player
(game
)
203 if player
.stats
["issues"] == threshold
then
204 var a
= new_achievement
(game
)
205 player
.unlock_achievement
(a
, event
)
210 # Player open his first issue.
214 redef var id
= "player_1_issue"
215 redef var name
= "First complaint"
216 redef var desc
= "Open your first issue."
217 redef var reward
= 10
218 redef var threshold
= 1
221 # Player open 100 issues.
222 class Player100Issues
225 redef var id
= "player_100_issues"
226 redef var name
= "Mature whiner"
227 redef var desc
= "Open 100 issues in the game."
228 redef var reward
= 100
229 redef var threshold
= 100
232 # Player open 1 000 issues.
236 redef var id
= "player_1000_issues"
237 redef var name
= "You, sir, complain a lot"
238 redef var desc
= "Open 1000 issues in the game."
239 redef var reward
= 1000
240 redef var threshold
= 1000
243 # Player open an issue about nitdoc.
244 class IssueAboutNitdoc
245 super AchievementReactor
247 redef var id
= "issue_about_nitdoc"
248 redef var name
= "Say nitdoc again, I double dare you!"
249 redef var desc
= "Open an issue with \"nitdoc\
" in the title."
250 redef var reward
= 10
252 redef fun react_event
(game
, event
) do
253 if not event
isa IssuesEvent then return
254 if not event
.action
== "opened" then return
255 var player
= event
.issue
.user
.player
(game
)
256 var re
= "nitdoc".to_re
257 re
.ignore_case
= true
258 if event
.issue
.title
.has
(re
) then
259 var a
= new_achievement
(game
)
260 player
.unlock_achievement
(a
, event
)
265 # Player open an issue about FFI.
269 redef var id
= "issue_about_ffi"
270 redef var name
= "Polyglot what?"
271 redef var desc
= "Open an issue with `ffi` in the title."
272 redef var reward
= 10
274 redef fun react_event
(game
, event
) do
275 if not event
isa IssuesEvent then return
276 if not event
.action
== "opened" then return
277 var player
= event
.issue
.user
.player
(game
)
278 var re
= "\\bffi\\b".to_re
279 re
.ignore_case
= true
280 if event
.issue
.title
.has
(re
) then
281 var a
= new_achievement
(game
)
282 player
.unlock_achievement
(a
, event
)
287 #####################
289 #####################
291 # Unlock achievement after X pull requests.
293 # Used to factorize behavior.
294 abstract class PlayerXPulls
295 super AchievementReactor
297 # Number of PR required to unlock the achievement.
298 var threshold
: Int is noinit
300 redef fun react_event
(game
, event
) do
301 if not event
isa PullRequestEvent then return
302 if not event
.action
== "opened" then return
303 var player
= event
.pull
.user
.player
(game
)
304 if player
.stats
["pulls"] == threshold
then
305 var a
= new_achievement
(game
)
306 player
.unlock_achievement
(a
, event
)
311 # Open your first pull request.
315 redef var id
= "player_1_pull"
316 redef var name
= "First PR"
317 redef var desc
= "Open your first pull request."
318 redef var reward
= 10
319 redef var threshold
= 1
322 # Author 100 pull requests.
326 redef var id
= "player_100_pulls"
327 redef var name
= "100 pull requests!!!"
328 redef var desc
= "Open 100 pull requests in the game."
329 redef var reward
= 100
330 redef var threshold
= 100
333 # Author 1000 pull requests.
337 redef var id
= "player_1000_pulls"
338 redef var name
= "1000 PULL REQUESTS!!!"
339 redef var desc
= "Open 1000 pull requests in the game."
340 redef var reward
= 1000
341 redef var threshold
= 1000
344 #####################
346 #####################
348 # Unlock achievement after X merged commits.
350 # Used to factorize behavior.
351 abstract class PlayerXCommits
352 super AchievementReactor
354 # Number of PR required to unlock the achievement.
355 var threshold
: Int is noinit
357 redef fun react_event
(game
, event
) do
358 if not event
isa PullRequestEvent then return
359 if not event
.action
== "closed" then return
360 if not event
.pull
.merged
then return
361 var player
= event
.pull
.user
.player
(game
)
362 if player
.stats
["commits"] >= threshold
then
363 var a
= new_achievement
(game
)
364 player
.unlock_achievement
(a
, event
)
369 # Author your first commit in the game.
373 redef var id
= "player_1_commit"
374 redef var name
= "First blood"
375 redef var desc
= "Author your first commit in the game."
376 redef var reward
= 10
377 redef var threshold
= 1
380 # Author 100 commits.
381 class Player100Commits
384 redef var id
= "player_100_commits"
385 redef var name
= "100 commits"
386 redef var desc
= "Author 100 commits in the game."
387 redef var reward
= 100
388 redef var threshold
= 100
391 # Author 1 000 commits.
392 class Player1KCommits
395 redef var id
= "player_1000_commits"
396 redef var name
= "1000 commits!!!"
397 redef var desc
= "Author 1000 commits in the game."
398 redef var reward
= 1000
399 redef var threshold
= 1000
402 # Author 10 000 commits.
403 class Player10KCommits
406 redef var id
= "player_10000_commits"
407 redef var name
= "10000 COMMITS!!!"
408 redef var desc
= "Author 10000 commits in the game."
409 redef var reward
= 10000
410 redef var threshold
= 10000
413 #####################
415 #####################
417 # Unlock achievement after X issue comments.
419 # Used to factorize behavior.
420 abstract class PlayerXComments
421 super AchievementReactor
423 # Number of comments required to unlock the achievement.
424 var threshold
: Int is noinit
426 redef fun react_event
(game
, event
) do
427 if not event
isa IssueCommentEvent then return
428 if not event
.action
== "created" then return
429 var player
= event
.comment
.user
.player
(game
)
430 if player
.stats
["comments"] == threshold
then
431 var a
= new_achievement
(game
)
432 player
.unlock_achievement
(a
, event
)
437 # Player author his first comment in issues.
439 super PlayerXComments
441 redef var id
= "player_1_comment"
442 redef var name
= "From lurker to member"
443 redef var desc
= "Comment on an issue."
444 redef var reward
= 10
445 redef var threshold
= 1
448 # Player author 100 issue comments.
449 class Player100Comments
450 super PlayerXComments
452 redef var id
= "player_100_comments"
453 redef var name
= "Chatter"
454 redef var desc
= "Comment 100 times on issues."
455 redef var reward
= 100
456 redef var threshold
= 100
459 # Player author 1000 issue comments.
460 class Player1KComments
461 super PlayerXComments
463 redef var id
= "player_1000_comments"
464 redef var name
= "You sir, talk a lot!"
465 redef var desc
= "Comment 1000 times on issues."
466 redef var reward
= 1000
467 redef var threshold
= 1000
470 # Ping @privat in a comment.
472 super AchievementReactor
474 redef var id
= "player_ping_god"
475 redef var name
= "Ping god"
476 redef var desc
= "Ping the owner of the repo for the first time."
477 redef var reward
= 50
479 redef fun react_event
(game
, event
) do
480 if not event
isa IssueCommentEvent then return
481 var owner
= game
.repo
.owner
.login
482 if event
.comment
.body
.has
("@{owner}".to_re
) then
483 var player
= event
.comment
.user
.player
(game
)
484 var a
= new_achievement
(game
)
485 player
.unlock_achievement
(a
, event
)
491 class PlayerFirstReview
492 super AchievementReactor
494 redef var id
= "player_first_review"
495 redef var name
= "First +1"
496 redef var desc
= "Give a +1 for the first time."
497 redef var reward
= 10
499 redef fun react_event
(game
, event
) do
500 if not event
isa IssueCommentEvent then return
501 # FIXME use a more precise way to locate reviews
502 if event
.comment
.is_ack
then
503 var player
= event
.comment
.user
.player
(game
)
504 var a
= new_achievement
(game
)
505 player
.unlock_achievement
(a
, event
)
510 # Talk about nitcoin in issue comments.
511 class PlayerSaysNitcoin
512 super AchievementReactor
514 redef var id
= "player_says_nitcoin"
515 redef var name
= "Talking about money"
516 redef var desc
= "Say something about nitcoins in a comment."
517 redef var reward
= 10
519 redef fun react_event
(game
, event
) do
520 if not event
isa IssueCommentEvent then return
521 if event
.comment
.body
.has
("(n|N)itcoin".to_re
) then
522 var player
= event
.comment
.user
.player
(game
)
523 var a
= new_achievement
(game
)
524 player
.unlock_achievement
(a
, event
)