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` game structures.
19 # Here we define the main game entities:
21 # * `Game` holds all the entities for a game and provides high level services.
22 # * `Player` represents a `Github::User` which plays the `Game`.
24 # Developpers who wants to extend the game capabilities should look at
25 # the `GameReactor` abstraction.
28 intrude import json
::store
31 # An entity within a `Game`.
33 # All game entities can be saved in a json format.
35 # The game instance containing `self`.
36 fun game
: Game is abstract
38 # Uniq key used for data storage.
39 fun key
: String is abstract
41 # Saves the `self` state in game data.
43 # Date are stored under `self.key`.
44 fun save
do game
.store
.store_object
(key
, to_json
)
46 # Saves `self` state under `key` data.
48 # Data are stored under `key / self.key`.
49 fun save_in
(key
: String) do
50 game
.store
.store_object
(key
/ self.key
, to_json
)
53 # Json representation of `self`.
54 fun to_json
: JsonObject do return new JsonObject
56 # Pretty print `self` to be displayed in a terminal.
57 fun pretty
: String is abstract
60 # Holder for game data and main services.
62 # Game is a `GameEntity` so it can be saved.
66 redef fun game
do return self
68 # We need a `GithubAPI` client to load Github data.
71 # A game takes place in a `github::Repo`.
75 var name
: String = repo
.full_name
is lazy
77 # Directory where game data are stored.
78 var game_dir
: String is lazy
do return "nitrpg_data" / repo
.full_name
80 redef var key
= name
is lazy
82 # Used for data storage.
84 # File are stored in `game_dir`.
85 var store
: JsonStore is lazy
do return new JsonStore(game_dir
)
87 # Init the Game and try to load saved data.
88 init do if store
.has_key
(key
) then from_json
(store
.load_object
(key
))
90 # Init `self` from a JsonObject.
92 # Used to load entities from saved data.
93 fun from_json
(json
: JsonObject) do end
101 # Create a player from a Github `User`.
103 # Or return the existing one from game data.
104 fun add_player
(user
: User): Player do
105 # check if player already exists
106 var player
= load_player
(user
.login
)
107 if player
!= null then return player
108 # create and store new player
109 player
= new Player(self, user
.login
)
114 # Get a Player from his `name` or null if no player was found.
116 # Looks for the player save file in game data.
118 # Returns `null` if the player cannot be found.
119 # In this case, the player can be created with `add_player`.
120 fun load_player
(name
: String): nullable Player do
121 var key
= "players" / name
122 if not store
.has_key
(key
) then return null
123 var json
= store
.load_object
(key
)
124 return new Player.from_json
(self, json
)
127 # List known players.
129 # This list is reloaded from game data each time its called.
131 # To add players see `add_player`.
132 fun load_players
: MapRead[String, Player] do
133 var res
= new HashMap[String, Player]
134 if not store
.has_collection
("players") then return res
135 var coll
= store
.list_collection
("players")
138 res
[name
] = load_player
(name
).as(not null)
143 # Return a list of player name associated to their rank in the game.
144 fun player_ranking
: MapRead[String, Int] do
145 var arr
= load_players
.values
.to_a
146 var res
= new HashMap[String, Int]
147 (new PlayerCoinComparator).sort
(arr
)
150 res
[player
.name
] = rank
156 # Erase all saved data for this game.
157 fun clear
do store
.clear
159 # Verbosity level used fo stdout.
162 # * `0` error and warnings
165 var verbose_lvl
= 0 is writable
167 # Display `msg` if `lvl` >= `verbose_lvl`
168 fun message
(lvl
: Int, msg
: String) do
169 if lvl
> verbose_lvl
then return
174 var res
= new FlatBuffer
175 res
.append
"-------------------------\n"
176 res
.append
"{repo.full_name}\n"
177 res
.append
"-------------------------\n"
178 res
.append
"# {load_players.length} players \n"
179 return res
.write_to_string
183 # Players can battle on nitrpg for nitcoins and glory.
185 # A `Player` is linked to a `Github::User`.
191 # FIXME contructor should be private
195 # This is the unic key for this player.
196 # Should be equal to the associated `Github::User::login`.
198 # The name is also used to load the user data lazilly from Github API.
201 redef var key
= name
is lazy
203 # Player amount of nitcoins.
205 # Nitcoins is the currency used in nitrpg.
206 # They can be obtained by performing actions on the `Game::Repo`.
207 var nitcoins
: Int = 0 is public
writable
209 # `Github::User` linked to this player.
210 var user
: User is lazy
do
211 var user
= game
.api
.load_user
(name
)
216 # Init `self` from a `json` object.
218 # Used to load players from saved data.
219 init from_json
(game
: Game, json
: JsonObject) do
220 init(game
, json
["name"].to_s
)
221 nitcoins
= json
["nitcoins"].as(Int)
226 json
["game"] = game
.key
228 json
["nitcoins"] = nitcoins
233 var res
= new FlatBuffer
234 res
.append
"-- {name} ({nitcoins} $)\n"
235 return res
.write_to_string
238 redef fun to_s
do return name
242 # The player linked to `self`.
243 fun player
(game
: Game): Player do
244 var player
= player_cache
.get_or_null
(game
)
245 if player
!= null then return player
246 player
= game
.load_player
(login
)
247 if player
== null then player
= game
.add_player
(self)
248 player_cache
[game
] = player
252 private var player_cache
= new HashMap[Game, Player]
255 # A GameReactor reacts to event sent by a `Github::HookListener`.
257 # Subclasses of `GameReactor` are implemented to handle all kind of
259 # Depending on the received event, the reactor is used to update game data.
261 # Reactors are mostly used with a `Github::HookListener` that dispatchs received
262 # events from the Github API.
267 # import github::hooks
269 # # Reactor that prints received events in console.
273 # redef fun react_event(game, e) do print e
276 # # Hook listener that redirect events to reactors.
277 # class RpgHookListener
280 # redef fun apply_event(event) do
281 # var game = new Game(api, event.repo)
282 # var reactor = new PrintReactor
283 # reactor.react_event(game, event)
288 # See module `reactors` and `listener` for more examples.
289 interface GameReactor
291 # Reacts to this `event` and update `game` accordingly.
293 # Concrete `GameReactor` implement this method to update game data
294 # for each specific GithubEvent.
295 fun react_event
(game
: Game, event
: GithubEvent) is abstract
300 # Sort games by descending number of players.
302 # The first in the list is the game with the more players.
303 class GamePlayersComparator
306 redef type COMPARED: Game
308 redef fun compare
(a
, b
) do
309 return b
.load_players
.length
<=> a
.load_players
.length
313 # Sort players by descending number of nitcoins.
315 # The first in the list is the player with the more of nitcoins.
316 class PlayerCoinComparator
319 redef type COMPARED: Player
321 redef fun compare
(a
, b
) do return b
.nitcoins
<=> a
.nitcoins