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
117 json
["id"].as(String),
118 json
["name"].as(String),
119 json
["desc"].as(String),
120 json
["reward"].as(Int))
123 redef fun to_json_object
do
128 json
["reward"] = reward
129 json
["game"] = game
.key
130 var owner
= self.owner
131 if owner
!= null then json
["owner"] = owner
.key
137 # Unlocks an achievement for this Player based on a GithubEvent.
139 # Register the achievement and adds the achievement reward to the player
142 # Do nothing is this player has already unlocked the achievement.
144 # TODO: add abstraction so achievements do not depend on GithubEvent.
145 fun unlock_achievement
(a
: Achievement, event
: GithubEvent) do
146 if has_achievement
(a
) then return
149 trigger_unlock_event
(a
, event
)
153 # Create a new event that marks the achievement unlocking.
154 fun trigger_unlock_event
(achievement
: Achievement, event
: GithubEvent) do
155 var obj
= new JsonObject
157 obj
["reward"] = achievement
.reward
158 obj
["achievement"] = achievement
.id
159 obj
["github_event"] = event
160 var ge
= new GameEvent(game
, "achievement_unlocked", obj
)
163 achievement
.add_event
(ge
)
167 # `GameReactor` dedicated to achievements unlocking.
168 interface AchievementReactor
171 # Unic ID of the achievement this reactor unlocks.
172 fun id
: String is abstract
174 # Name of the achievement this reactor unlocks.
175 fun name
: String is abstract
177 # Description of the achievement this reactor unlocks.
178 fun desc
: String is abstract
180 # Amount of nitcoins rewarded for unlocking the achievement.
181 fun reward
: Int is abstract
183 # Return a new instance of the achievement to unlock.
184 fun new_achievement
(game
: Game): Achievement do
185 var achievement
= new Achievement(game
, id
, name
, desc
, reward
)
186 game
.add_achievement
(achievement
)
191 #####################
193 #####################
195 # Unlock achievement after X issues.
197 # Used to factorize behavior.
198 abstract class PlayerXIssues
199 super AchievementReactor
201 # Number of PR required to unlock the achievement.
202 var threshold
: Int is noinit
204 redef fun react_event
(game
, event
) do
205 if not event
isa IssuesEvent then return
206 if not event
.action
== "opened" then return
207 var player
= event
.issue
.user
.player
(game
)
208 if player
.stats
["issues"] == threshold
then
209 var a
= new_achievement
(game
)
210 player
.unlock_achievement
(a
, event
)
215 # Player open his first issue.
219 redef var id
= "player_1_issue"
220 redef var name
= "First complaint"
221 redef var desc
= "Open your first issue."
222 redef var reward
= 10
223 redef var threshold
= 1
226 # Player open 100 issues.
227 class Player100Issues
230 redef var id
= "player_100_issues"
231 redef var name
= "Mature whiner"
232 redef var desc
= "Open 100 issues in the game."
233 redef var reward
= 100
234 redef var threshold
= 100
237 # Player open 1 000 issues.
241 redef var id
= "player_1000_issues"
242 redef var name
= "You, sir, complain a lot"
243 redef var desc
= "Open 1000 issues in the game."
244 redef var reward
= 1000
245 redef var threshold
= 1000
248 # Player open an issue about nitdoc.
249 class IssueAboutNitdoc
250 super AchievementReactor
252 redef var id
= "issue_about_nitdoc"
253 redef var name
= "Say nitdoc again, I double dare you!"
254 redef var desc
= "Open an issue with \"nitdoc\
" in the title."
255 redef var reward
= 10
257 redef fun react_event
(game
, event
) do
258 if not event
isa IssuesEvent then return
259 if not event
.action
== "opened" then return
260 var player
= event
.issue
.user
.player
(game
)
261 var re
= "nitdoc".to_re
262 re
.ignore_case
= true
263 if event
.issue
.title
.has
(re
) then
264 var a
= new_achievement
(game
)
265 player
.unlock_achievement
(a
, event
)
270 # Player open an issue about FFI.
274 redef var id
= "issue_about_ffi"
275 redef var name
= "Polyglot what?"
276 redef var desc
= "Open an issue with `ffi` in the title."
277 redef var reward
= 10
279 redef fun react_event
(game
, event
) do
280 if not event
isa IssuesEvent then return
281 if not event
.action
== "opened" then return
282 var player
= event
.issue
.user
.player
(game
)
283 var re
= "\\bffi\\b".to_re
284 re
.ignore_case
= true
285 if event
.issue
.title
.has
(re
) then
286 var a
= new_achievement
(game
)
287 player
.unlock_achievement
(a
, event
)
292 #####################
294 #####################
296 # Unlock achievement after X pull requests.
298 # Used to factorize behavior.
299 abstract class PlayerXPulls
300 super AchievementReactor
302 # Number of PR required to unlock the achievement.
303 var threshold
: Int is noinit
305 redef fun react_event
(game
, event
) do
306 if not event
isa PullRequestEvent then return
307 if not event
.action
== "opened" then return
308 var player
= event
.pull
.user
.player
(game
)
309 if player
.stats
["pulls"] == threshold
then
310 var a
= new_achievement
(game
)
311 player
.unlock_achievement
(a
, event
)
316 # Open your first pull request.
320 redef var id
= "player_1_pull"
321 redef var name
= "First PR"
322 redef var desc
= "Open your first pull request."
323 redef var reward
= 10
324 redef var threshold
= 1
327 # Author 100 pull requests.
331 redef var id
= "player_100_pulls"
332 redef var name
= "100 pull requests!!!"
333 redef var desc
= "Open 100 pull requests in the game."
334 redef var reward
= 100
335 redef var threshold
= 100
338 # Author 1000 pull requests.
342 redef var id
= "player_1000_pulls"
343 redef var name
= "1000 PULL REQUESTS!!!"
344 redef var desc
= "Open 1000 pull requests in the game."
345 redef var reward
= 1000
346 redef var threshold
= 1000
349 #####################
351 #####################
353 # Unlock achievement after X merged commits.
355 # Used to factorize behavior.
356 abstract class PlayerXCommits
357 super AchievementReactor
359 # Number of PR required to unlock the achievement.
360 var threshold
: Int is noinit
362 redef fun react_event
(game
, event
) do
363 if not event
isa PullRequestEvent then return
364 if not event
.action
== "closed" then return
365 if not event
.pull
.merged
then return
366 var player
= event
.pull
.user
.player
(game
)
367 if player
.stats
["commits"] >= threshold
then
368 var a
= new_achievement
(game
)
369 player
.unlock_achievement
(a
, event
)
374 # Author your first commit in the game.
378 redef var id
= "player_1_commit"
379 redef var name
= "First blood"
380 redef var desc
= "Author your first commit in the game."
381 redef var reward
= 10
382 redef var threshold
= 1
385 # Author 100 commits.
386 class Player100Commits
389 redef var id
= "player_100_commits"
390 redef var name
= "100 commits"
391 redef var desc
= "Author 100 commits in the game."
392 redef var reward
= 100
393 redef var threshold
= 100
396 # Author 1 000 commits.
397 class Player1KCommits
400 redef var id
= "player_1000_commits"
401 redef var name
= "1000 commits!!!"
402 redef var desc
= "Author 1000 commits in the game."
403 redef var reward
= 1000
404 redef var threshold
= 1000
407 # Author 10 000 commits.
408 class Player10KCommits
411 redef var id
= "player_10000_commits"
412 redef var name
= "10000 COMMITS!!!"
413 redef var desc
= "Author 10000 commits in the game."
414 redef var reward
= 10000
415 redef var threshold
= 10000
418 #####################
420 #####################
422 # Unlock achievement after X issue comments.
424 # Used to factorize behavior.
425 abstract class PlayerXComments
426 super AchievementReactor
428 # Number of comments required to unlock the achievement.
429 var threshold
: Int is noinit
431 redef fun react_event
(game
, event
) do
432 if not event
isa IssueCommentEvent then return
433 if not event
.action
== "created" then return
434 var player
= event
.comment
.user
.player
(game
)
435 if player
.stats
["comments"] == threshold
then
436 var a
= new_achievement
(game
)
437 player
.unlock_achievement
(a
, event
)
442 # Player author his first comment in issues.
444 super PlayerXComments
446 redef var id
= "player_1_comment"
447 redef var name
= "From lurker to member"
448 redef var desc
= "Comment on an issue."
449 redef var reward
= 10
450 redef var threshold
= 1
453 # Player author 100 issue comments.
454 class Player100Comments
455 super PlayerXComments
457 redef var id
= "player_100_comments"
458 redef var name
= "Chatter"
459 redef var desc
= "Comment 100 times on issues."
460 redef var reward
= 100
461 redef var threshold
= 100
464 # Player author 1000 issue comments.
465 class Player1KComments
466 super PlayerXComments
468 redef var id
= "player_1000_comments"
469 redef var name
= "You sir, talk a lot!"
470 redef var desc
= "Comment 1000 times on issues."
471 redef var reward
= 1000
472 redef var threshold
= 1000
475 # Ping @privat in a comment.
477 super AchievementReactor
479 redef var id
= "player_ping_god"
480 redef var name
= "Ping god"
481 redef var desc
= "Ping the owner of the repo for the first time."
482 redef var reward
= 50
484 redef fun react_event
(game
, event
) do
485 if not event
isa IssueCommentEvent then return
486 var owner
= game
.repo
.owner
.login
487 if event
.comment
.body
.has
("@{owner}".to_re
) then
488 var player
= event
.comment
.user
.player
(game
)
489 var a
= new_achievement
(game
)
490 player
.unlock_achievement
(a
, event
)
496 class PlayerFirstReview
497 super AchievementReactor
499 redef var id
= "player_first_review"
500 redef var name
= "First +1"
501 redef var desc
= "Give a +1 for the first time."
502 redef var reward
= 10
504 redef fun react_event
(game
, event
) do
505 if not event
isa IssueCommentEvent then return
506 # FIXME use a more precise way to locate reviews
507 if event
.comment
.is_ack
then
508 var player
= event
.comment
.user
.player
(game
)
509 var a
= new_achievement
(game
)
510 player
.unlock_achievement
(a
, event
)
515 # Talk about nitcoin in issue comments.
516 class PlayerSaysNitcoin
517 super AchievementReactor
519 redef var id
= "player_says_nitcoin"
520 redef var name
= "Talking about money"
521 redef var desc
= "Say something about nitcoins in a comment."
522 redef var reward
= 10
524 redef fun react_event
(game
, event
) do
525 if not event
isa IssueCommentEvent then return
526 if event
.comment
.body
.has
("(n|N)itcoin".to_re
) then
527 var player
= event
.comment
.user
.player
(game
)
528 var a
= new_achievement
(game
)
529 player
.unlock_achievement
(a
, event
)