src/doc: uniformize HTML output for topmenu
[nit.git] / contrib / nitrpg / src / game.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # `nitrpg` game structures.
18 #
19 # Here we define the main game entities:
20 #
21 # * `Game` holds all the entities for a game and provides high level services.
22 # * `Player` represents a `Github::User` which plays the `Game`.
23 #
24 # Developpers who wants to extend the game capabilities should look at
25 # the `GameReactor` abstraction.
26 module game
27
28 intrude import json::store
29 import github::events
30
31 # An entity within a `Game`.
32 #
33 # All game entities can be saved in a json format.
34 interface GameEntity
35 # The game instance containing `self`.
36 fun game: Game is abstract
37
38 # Uniq key used for data storage.
39 fun key: String is abstract
40
41 # Save `self` as a json object.
42 fun save do game.store.store_object(key, to_json)
43
44 # Json representation of `self`.
45 fun to_json: JsonObject do return new JsonObject
46
47 # Pretty print `self` to be displayed in a terminal.
48 fun pretty: String is abstract
49 end
50
51 # Holder for game data and main services.
52 #
53 # Game is a `GameEntity` so it can be saved.
54 class Game
55 super GameEntity
56
57 redef fun game do return self
58
59 # Returns the repo `full_name`.
60 #
61 # Example: `"privat/nit"`
62 redef fun key do return repo.full_name
63
64 # We need a `GithubAPI` client to load Github data.
65 var api: GithubAPI
66
67 # A game takes place in a `github::Repo`.
68 var repo: Repo
69
70 # Directory where game data are stored.
71 var game_dir: String is lazy do return "nitrpg_data" / repo.full_name
72
73 # Used for data storage.
74 #
75 # File are stored in `game_dir`.
76 var store: JsonStore is lazy do return new JsonStore(game_dir)
77
78 # Init the Game and try to load saved data.
79 init do if store.has_key(key) then from_json(store.load_object(key))
80
81 # Init `self` from a JsonObject.
82 #
83 # Used to load entities from saved data.
84 fun from_json(json: JsonObject) do end
85
86 # Create a player from a Github `User`.
87 #
88 # Or return the existing one from game data.
89 fun add_player(user: User): Player do
90 # check if player already exists
91 var player = load_player(user.login)
92 if player != null then return player
93 # create and store new player
94 player = new Player(self, user.login)
95 player.save
96 return player
97 end
98
99 # Get a Player from his `name` or null if no player was found.
100 #
101 # Looks for the player save file in game data.
102 #
103 # Returns `null` if the player cannot be found.
104 # In this case, the player can be created with `add_player`.
105 fun load_player(name: String): nullable Player do
106 var key = "players" / name
107 if not store.has_key(key) then return null
108 var json = store.load_object(key)
109 return new Player.from_json(self, json)
110 end
111
112 # List known players.
113 #
114 # This list is reloaded from game data each time its called.
115 #
116 # To add players see `add_player`.
117 fun load_players: MapRead[String, Player] do
118 var res = new HashMap[String, Player]
119 if not store.has_collection("players") then return res
120 var coll = store.list_collection("players")
121 for id in coll do
122 var name = id.to_s
123 res[name] = load_player(name).as(not null)
124 end
125 return res
126 end
127
128 # Return a list of player name associated to their rank in the game.
129 fun player_ranking: MapRead[String, Int] do
130 var arr = load_players.values.to_a
131 var res = new HashMap[String, Int]
132 (new PlayerCoinComparator).sort(arr)
133 var rank = 1
134 for player in arr do
135 res[player.name] = rank
136 rank += 1
137 end
138 return res
139 end
140
141 # Erase all saved data for this game.
142 fun clear do store.clear
143
144 # Verbosity level used fo stdout.
145 #
146 # * `-1` quiet
147 # * `0` error and warnings
148 # * `1` info
149 # * `2` debug
150 var verbose_lvl = 0 is writable
151
152 # Display `msg` if `lvl` >= `verbose_lvl`
153 fun message(lvl: Int, msg: String) do
154 if lvl > verbose_lvl then return
155 print msg
156 end
157
158 redef fun pretty do
159 var res = new FlatBuffer
160 res.append "-------------------------\n"
161 res.append "{repo.full_name}\n"
162 res.append "-------------------------\n"
163 res.append "# {load_players.length} players \n"
164 return res.write_to_string
165 end
166 end
167
168 # Players can battle on nitrpg for nitcoins and glory.
169 #
170 # A `Player` is linked to a `Github::User`.
171 class Player
172 super GameEntity
173
174 # Key is based on player `name`.
175 redef var key is lazy do return "players" / name
176
177 redef var game
178
179 # FIXME contructor should be private
180
181 # Player name.
182 #
183 # This is the unic key for this player.
184 # Should be equal to the associated `Github::User::login`.
185 #
186 # The name is also used to load the user data lazilly from Github API.
187 var name: String
188
189 # Player amount of nitcoins.
190 #
191 # Nitcoins is the currency used in nitrpg.
192 # They can be obtained by performing actions on the `Game::Repo`.
193 var nitcoins: Int = 0 is public writable
194
195 # `Github::User` linked to this player.
196 var user: User is lazy do
197 var user = game.api.load_user(name)
198 assert user isa User
199 return user
200 end
201
202 # Init `self` from a `json` object.
203 #
204 # Used to load players from saved data.
205 init from_json(game: Game, json: JsonObject) do
206 self.game = game
207 name = json["name"].to_s
208 nitcoins = json["nitcoins"].as(Int)
209 end
210
211 redef fun to_json do
212 var json = super
213 json["name"] = name
214 json["nitcoins"] = nitcoins
215 return json
216 end
217
218 redef fun pretty do
219 var res = new FlatBuffer
220 res.append "-- {name} ({nitcoins} $)\n"
221 return res.write_to_string
222 end
223
224 redef fun to_s do return name
225 end
226
227 redef class User
228 # The player linked to `self`.
229 fun player(game: Game): Player is lazy do
230 var player = game.load_player(login)
231 if player == null then player = game.add_player(self)
232 return player
233 end
234 end
235
236 # A GameReactor reacts to event sent by a `Github::HookListener`.
237 #
238 # Subclasses of `GameReactor` are implemented to handle all kind of
239 # `GithubEvent`.
240 # Depending on the received event, the reactor is used to update game data.
241 #
242 # Reactors are mostly used with a `Github::HookListener` that dispatchs received
243 # events from the Github API.
244 #
245 # Example:
246 #
247 # ~~~
248 # import github::hooks
249 #
250 # # Reactor that prints received events in console.
251 # class PrintReactor
252 # super GameReactor
253 #
254 # redef fun react_event(game, e) do print e
255 # end
256 #
257 # # Hook listener that redirect events to reactors.
258 # class RpgHookListener
259 # super HookListener
260 #
261 # redef fun apply_event(event) do
262 # var game = new Game(api, event.repo)
263 # var reactor = new PrintReactor
264 # reactor.react_event(game, event)
265 # end
266 # end
267 # ~~~
268 #
269 # See module `reactors` and `listener` for more examples.
270 interface GameReactor
271
272 # Reacts to this `event` and update `game` accordingly.
273 #
274 # Concrete `GameReactor` implement this method to update game data
275 # for each specific GithubEvent.
276 fun react_event(game: Game, event: GithubEvent) is abstract
277 end
278
279 # utils
280
281 # Sort players by descending number of nitcoins.
282 #
283 # The first in the list is the player with the more of nitcoins.
284 class PlayerCoinComparator
285 super Comparator
286
287 redef type COMPARED: Player
288
289 redef fun compare(a, b) do return b.nitcoins <=> a.nitcoins
290 end