24b50add8d34c52fbbe63973030e7ff10b9f1b3c
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.key
)
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.
80 redef var key
is lazy
do
81 var owner
= self.owner
82 if owner
== null then return id
83 return "{owner.key}-{id}"
86 # Uniq ID for this achievement.
89 # Name of this achievement.
92 # Description of the achievement.
95 # Reward that this achievement give in nitcoins.
98 # Is this achievement unlocked by somebody?
99 var is_unlocked
: Bool is lazy
do return not load_events
.is_empty
101 # Game entity this achievement is about.
102 var owner
: nullable GameEntity = null
104 # Init `self` from a `json` object.
106 # Used to load achievements from storage.
107 init from_json
(game
: Game, json
: JsonObject) do
108 init(game
, json
["id"].to_s
, json
["name"].to_s
, json
["desc"].to_s
, json
["reward"].as(Int))
116 json
["reward"] = reward
117 json
["game"] = game
.key
118 if owner
!= null then json
["owner"] = owner
.key
125 # Is `a` unlocked for this `Player`?
126 fun has_achievement
(a
: Achievement): Bool do
127 return load_achievement
(a
.id
) != null
130 # Unlocks an achievement for this Player based on a GithubEvent.
132 # Register the achievement and adds the achievement reward to the player
135 # Do nothing is this player has already unlocked the achievement.
137 # TODO: add abstraction so achievements do not depend on GithubEvent.
138 fun unlock_achievement
(a
: Achievement, event
: GithubEvent) do
139 if has_achievement
(a
) then return
142 trigger_unlock_event
(a
, event
)
145 # Create a new event that marks the achievement unlocking.
146 fun trigger_unlock_event
(achievement
: Achievement, event
: GithubEvent) do
147 var obj
= new JsonObject
149 obj
["reward"] = achievement
.reward
150 obj
["achievement"] = achievement
.id
151 obj
["github_event"] = event
.json
152 var ge
= new GameEvent(game
, "achievement_unlocked", obj
)
155 achievement
.add_event
(ge
)
159 # `GameReactor` dedicated to achievements unlocking.
160 interface AchievementReactor
163 # Unic ID of the achievement this reactor unlocks.
164 fun id
: String is abstract
166 # Name of the achievement this reactor unlocks.
167 fun name
: String is abstract
169 # Description of the achievement this reactor unlocks.
170 fun desc
: String is abstract
172 # Amount of nitcoins rewarded for unlocking the achievement.
173 fun reward
: Int is abstract
175 # Return a new instance of the achievement to unlock.
176 fun new_achievement
(game
: Game): Achievement do
177 var achievement
= new Achievement(game
, id
, name
, desc
, reward
)
178 game
.add_achievement
(achievement
)
183 #####################
185 #####################
187 # Unlock achievement after X issues.
189 # Used to factorize behavior.
190 abstract class PlayerXIssues
191 super AchievementReactor
193 # Number of PR required to unlock the achievement.
194 var threshold
: Int is noinit
196 redef fun react_event
(game
, event
) do
197 if not event
isa IssuesEvent then return
198 if not event
.action
== "opened" then return
199 var player
= event
.issue
.user
.player
(game
)
200 if player
.stats
["issues"] == threshold
then
201 var a
= new_achievement
(game
)
202 player
.unlock_achievement
(a
, event
)
207 # Player open his first issue.
211 redef var id
= "player_1_issue"
212 redef var name
= "First complaint"
213 redef var desc
= "Open your first issue."
214 redef var reward
= 10
215 redef var threshold
= 1
218 # Player open 100 issues.
219 class Player100Issues
222 redef var id
= "player_100_issues"
223 redef var name
= "Mature whiner"
224 redef var desc
= "Open 100 issues in the game."
225 redef var reward
= 100
226 redef var threshold
= 100
229 # Player open 1 000 issues.
233 redef var id
= "player_1000_issues"
234 redef var name
= "You, sir, complain a lot"
235 redef var desc
= "Open 1000 issues in the game."
236 redef var reward
= 1000
237 redef var threshold
= 1000
240 # Player open an issue about nitdoc.
241 class IssueAboutNitdoc
242 super AchievementReactor
244 redef var id
= "issue_about_nitdoc"
245 redef var name
= "Say nitdoc again, I double dare you!"
246 redef var desc
= "Open an issue with \"nitdoc\
" in the title."
247 redef var reward
= 10
249 redef fun react_event
(game
, event
) do
250 if not event
isa IssuesEvent then return
251 if not event
.action
== "opened" then return
252 var player
= event
.issue
.user
.player
(game
)
253 var re
= "nitdoc".to_re
254 re
.ignore_case
= true
255 if event
.issue
.title
.has
(re
) then
256 var a
= new_achievement
(game
)
257 player
.unlock_achievement
(a
, event
)
262 # Player open an issue about FFI.
266 redef var id
= "issue_about_ffi"
267 redef var name
= "Polyglot what?"
268 redef var desc
= "Open an issue with `ffi` in the title."
269 redef var reward
= 10
271 redef fun react_event
(game
, event
) do
272 if not event
isa IssuesEvent then return
273 if not event
.action
== "opened" then return
274 var player
= event
.issue
.user
.player
(game
)
275 var re
= "\\bffi\\b".to_re
276 re
.ignore_case
= true
277 if event
.issue
.title
.has
(re
) then
278 var a
= new_achievement
(game
)
279 player
.unlock_achievement
(a
, event
)
284 #####################
286 #####################
288 # Unlock achievement after X pull requests.
290 # Used to factorize behavior.
291 abstract class PlayerXPulls
292 super AchievementReactor
294 # Number of PR required to unlock the achievement.
295 var threshold
: Int is noinit
297 redef fun react_event
(game
, event
) do
298 if not event
isa PullRequestEvent then return
299 if not event
.action
== "opened" then return
300 var player
= event
.pull
.user
.player
(game
)
301 if player
.stats
["pulls"] == threshold
then
302 var a
= new_achievement
(game
)
303 player
.unlock_achievement
(a
, event
)
308 # Open your first pull request.
312 redef var id
= "player_1_pull"
313 redef var name
= "First PR"
314 redef var desc
= "Open your first pull request."
315 redef var reward
= 10
316 redef var threshold
= 1
319 # Author 100 pull requests.
323 redef var id
= "player_100_pulls"
324 redef var name
= "100 pull requests!!!"
325 redef var desc
= "Open 100 pull requests in the game."
326 redef var reward
= 100
327 redef var threshold
= 100
330 # Author 1000 pull requests.
334 redef var id
= "player_1000_pulls"
335 redef var name
= "1000 PULL REQUESTS!!!"
336 redef var desc
= "Open 1000 pull requests in the game."
337 redef var reward
= 1000
338 redef var threshold
= 1000
341 #####################
343 #####################
345 # Unlock achievement after X merged commits.
347 # Used to factorize behavior.
348 abstract class PlayerXCommits
349 super AchievementReactor
351 # Number of PR required to unlock the achievement.
352 var threshold
: Int is noinit
354 redef fun react_event
(game
, event
) do
355 if not event
isa PullRequestEvent then return
356 if not event
.action
== "closed" then return
357 if not event
.pull
.merged
then return
358 var player
= event
.pull
.user
.player
(game
)
359 if player
.stats
["commits"] == threshold
then
360 var a
= new_achievement
(game
)
361 player
.unlock_achievement
(a
, event
)
366 # Author your first commit in the game.
370 redef var id
= "player_1_commit"
371 redef var name
= "First blood"
372 redef var desc
= "Author your first commit in the game."
373 redef var reward
= 10
374 redef var threshold
= 1
377 # Author 100 commits.
378 class Player100Commits
381 redef var id
= "player_100_commits"
382 redef var name
= "100 commits"
383 redef var desc
= "Author 100 commits in the game."
384 redef var reward
= 100
385 redef var threshold
= 100
388 # Author 1 000 commits.
389 class Player1KCommits
392 redef var id
= "player_1000_commits"
393 redef var name
= "1000 commits!!!"
394 redef var desc
= "Author 1000 commits in the game."
395 redef var reward
= 1000
396 redef var threshold
= 1000
399 # Author 10 000 commits.
400 class Player10KCommits
403 redef var id
= "player_10000_commits"
404 redef var name
= "10000 COMMITS!!!"
405 redef var desc
= "Author 10000 commits in the game."
406 redef var reward
= 10000
407 redef var threshold
= 10000
410 #####################
412 #####################
414 # Unlock achievement after X issue comments.
416 # Used to factorize behavior.
417 abstract class PlayerXComments
418 super AchievementReactor
420 # Number of comments required to unlock the achievement.
421 var threshold
: Int is noinit
423 redef fun react_event
(game
, event
) do
424 if not event
isa IssueCommentEvent then return
425 if not event
.action
== "created" then return
426 var player
= event
.comment
.user
.player
(game
)
427 if player
.stats
["comments"] == threshold
then
428 var a
= new_achievement
(game
)
429 player
.unlock_achievement
(a
, event
)
434 # Player author his first comment in issues.
436 super PlayerXComments
438 redef var id
= "player_1_comment"
439 redef var name
= "From lurker to member"
440 redef var desc
= "Comment on an issue."
441 redef var reward
= 10
442 redef var threshold
= 1
445 # Player author 100 issue comments.
446 class Player100Comments
447 super PlayerXComments
449 redef var id
= "player_100_comments"
450 redef var name
= "Chatter"
451 redef var desc
= "Comment 100 times on issues."
452 redef var reward
= 100
453 redef var threshold
= 100
456 # Player author 1000 issue comments.
457 class Player1KComments
458 super PlayerXComments
460 redef var id
= "player_1000_comments"
461 redef var name
= "You sir, talk a lot!"
462 redef var desc
= "Comment 1000 times on issues."
463 redef var reward
= 1000
464 redef var threshold
= 1000
467 # Ping @privat in a comment.
469 super AchievementReactor
471 redef var id
= "player_ping_god"
472 redef var name
= "Ping god"
473 redef var desc
= "Ping the owner of the repo for the first time."
474 redef var reward
= 50
476 redef fun react_event
(game
, event
) do
477 if not event
isa IssueCommentEvent then return
478 var owner
= game
.repo
.owner
.login
479 if event
.comment
.body
.has
("@{owner}".to_re
) then
480 var player
= event
.comment
.user
.player
(game
)
481 var a
= new_achievement
(game
)
482 player
.unlock_achievement
(a
, event
)
488 class PlayerFirstReview
489 super AchievementReactor
491 redef var id
= "player_first_review"
492 redef var name
= "First +1"
493 redef var desc
= "Give a +1 for the first time."
494 redef var reward
= 10
496 redef fun react_event
(game
, event
) do
497 if not event
isa IssueCommentEvent then return
498 # FIXME use a more precise way to locate reviews
499 if event
.comment
.is_ack
then
500 var player
= event
.comment
.user
.player
(game
)
501 var a
= new_achievement
(game
)
502 player
.unlock_achievement
(a
, event
)
507 # Talk about nitcoin in issue comments.
508 class PlayerSaysNitcoin
509 super AchievementReactor
511 redef var id
= "player_says_nitcoin"
512 redef var name
= "Talking about money"
513 redef var desc
= "Say something about nitcoins in a comment."
514 redef var reward
= 10
516 redef fun react_event
(game
, event
) do
517 if not event
isa IssueCommentEvent then return
518 if event
.comment
.body
.has
("(n|N)itcoin".to_re
) then
519 var player
= event
.comment
.user
.player
(game
)
520 var a
= new_achievement
(game
)
521 player
.unlock_achievement
(a
, event
)