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 into `target` key data.
48 # Data are stored under `target.key / self.key`.
49 fun save_in
(target
: GameEntity) do
50 game
.store
.store_object
(target
.key
/ 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 # Returns the repo `full_name`.
70 # Example: `"privat/nit"`
71 redef fun key
do return repo
.full_name
73 # We need a `GithubAPI` client to load Github data.
76 # A game takes place in a `github::Repo`.
79 # Directory where game data are stored.
80 var game_dir
: String is lazy
do return "nitrpg_data" / repo
.full_name
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
95 # Create a player from a Github `User`.
97 # Or return the existing one from game data.
98 fun add_player
(user
: User): Player do
99 # check if player already exists
100 var player
= load_player
(user
.login
)
101 if player
!= null then return player
102 # create and store new player
103 player
= new Player(self, user
.login
)
108 # Get a Player from his `name` or null if no player was found.
110 # Looks for the player save file in game data.
112 # Returns `null` if the player cannot be found.
113 # In this case, the player can be created with `add_player`.
114 fun load_player
(name
: String): nullable Player do
115 var key
= "players" / name
116 if not store
.has_key
(key
) then return null
117 var json
= store
.load_object
(key
)
118 return new Player.from_json
(self, json
)
121 # List known players.
123 # This list is reloaded from game data each time its called.
125 # To add players see `add_player`.
126 fun load_players
: MapRead[String, Player] do
127 var res
= new HashMap[String, Player]
128 if not store
.has_collection
("players") then return res
129 var coll
= store
.list_collection
("players")
132 res
[name
] = load_player
(name
).as(not null)
137 # Return a list of player name associated to their rank in the game.
138 fun player_ranking
: MapRead[String, Int] do
139 var arr
= load_players
.values
.to_a
140 var res
= new HashMap[String, Int]
141 (new PlayerCoinComparator).sort
(arr
)
144 res
[player
.name
] = rank
150 # Erase all saved data for this game.
151 fun clear
do store
.clear
153 # Verbosity level used fo stdout.
156 # * `0` error and warnings
159 var verbose_lvl
= 0 is writable
161 # Display `msg` if `lvl` >= `verbose_lvl`
162 fun message
(lvl
: Int, msg
: String) do
163 if lvl
> verbose_lvl
then return
168 var res
= new FlatBuffer
169 res
.append
"-------------------------\n"
170 res
.append
"{repo.full_name}\n"
171 res
.append
"-------------------------\n"
172 res
.append
"# {load_players.length} players \n"
173 return res
.write_to_string
177 # Players can battle on nitrpg for nitcoins and glory.
179 # A `Player` is linked to a `Github::User`.
183 # Key is based on player `name`.
184 redef var key
is lazy
do return "players" / name
188 # FIXME contructor should be private
192 # This is the unic key for this player.
193 # Should be equal to the associated `Github::User::login`.
195 # The name is also used to load the user data lazilly from Github API.
198 # Player amount of nitcoins.
200 # Nitcoins is the currency used in nitrpg.
201 # They can be obtained by performing actions on the `Game::Repo`.
202 var nitcoins
: Int = 0 is public
writable
204 # `Github::User` linked to this player.
205 var user
: User is lazy
do
206 var user
= game
.api
.load_user
(name
)
211 # Init `self` from a `json` object.
213 # Used to load players from saved data.
214 init from_json
(game
: Game, json
: JsonObject) do
216 name
= json
["name"].to_s
217 nitcoins
= json
["nitcoins"].as(Int)
223 json
["nitcoins"] = nitcoins
228 var res
= new FlatBuffer
229 res
.append
"-- {name} ({nitcoins} $)\n"
230 return res
.write_to_string
233 redef fun to_s
do return name
237 # The player linked to `self`.
238 fun player
(game
: Game): Player do
239 var player
= player_cache
.get_or_null
(game
)
240 if player
!= null then return player
241 player
= game
.load_player
(login
)
242 if player
== null then player
= game
.add_player
(self)
243 player_cache
[game
] = player
247 private var player_cache
= new HashMap[Game, Player]
250 # A GameReactor reacts to event sent by a `Github::HookListener`.
252 # Subclasses of `GameReactor` are implemented to handle all kind of
254 # Depending on the received event, the reactor is used to update game data.
256 # Reactors are mostly used with a `Github::HookListener` that dispatchs received
257 # events from the Github API.
262 # import github::hooks
264 # # Reactor that prints received events in console.
268 # redef fun react_event(game, e) do print e
271 # # Hook listener that redirect events to reactors.
272 # class RpgHookListener
275 # redef fun apply_event(event) do
276 # var game = new Game(api, event.repo)
277 # var reactor = new PrintReactor
278 # reactor.react_event(game, event)
283 # See module `reactors` and `listener` for more examples.
284 interface GameReactor
286 # Reacts to this `event` and update `game` accordingly.
288 # Concrete `GameReactor` implement this method to update game data
289 # for each specific GithubEvent.
290 fun react_event
(game
: Game, event
: GithubEvent) is abstract
295 # Sort players by descending number of nitcoins.
297 # The first in the list is the player with the more of nitcoins.
298 class PlayerCoinComparator
301 redef type COMPARED: Player
303 redef fun compare
(a
, b
) do return b
.nitcoins
<=> a
.nitcoins