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 # Save `self` as a json object.
42 fun save
do game
.store
.store_object
(key
, to_json
)
44 # Json representation of `self`.
45 fun to_json
: JsonObject do return new JsonObject
47 # Pretty print `self` to be displayed in a terminal.
48 fun pretty
: String is abstract
51 # Holder for game data and main services.
53 # Game is a `GameEntity` so it can be saved.
57 redef fun game
do return self
59 # Returns the repo `full_name`.
61 # Example: `"privat/nit"`
62 redef fun key
do return repo
.full_name
64 # We need a `GithubAPI` client to load Github data.
67 # A game takes place in a `github::Repo`.
70 # Directory where game data are stored.
71 var game_dir
: String is lazy
do return "nitrpg_data" / repo
.full_name
73 # Used for data storage.
75 # File are stored in `game_dir`.
76 var store
: JsonStore is lazy
do return new JsonStore(game_dir
)
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
))
81 # Init `self` from a JsonObject.
83 # Used to load entities from saved data.
84 fun from_json
(json
: JsonObject) do end
86 # Create a player from a Github `User`.
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
)
99 # Get a Player from his `name` or null if no player was found.
101 # Looks for the player save file in game data.
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
)
112 # List known players.
114 # This list is reloaded from game data each time its called.
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")
123 res
[name
] = load_player
(name
).as(not null)
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
)
135 res
[player
.name
] = rank
141 # Erase all saved data for this game.
142 fun clear
do store
.clear
144 # Verbosity level used fo stdout.
147 # * `0` error and warnings
150 var verbose_lvl
= 0 is writable
152 # Display `msg` if `lvl` >= `verbose_lvl`
153 fun message
(lvl
: Int, msg
: String) do
154 if lvl
> verbose_lvl
then return
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
168 # Players can battle on nitrpg for nitcoins and glory.
170 # A `Player` is linked to a `Github::User`.
174 # Key is based on player `name`.
175 redef var key
is lazy
do return "players" / name
179 # FIXME contructor should be private
183 # This is the unic key for this player.
184 # Should be equal to the associated `Github::User::login`.
186 # The name is also used to load the user data lazilly from Github API.
189 # Player amount of nitcoins.
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
195 # `Github::User` linked to this player.
196 var user
: User is lazy
do
197 var user
= game
.api
.load_user
(name
)
202 # Init `self` from a `json` object.
204 # Used to load players from saved data.
205 init from_json
(game
: Game, json
: JsonObject) do
207 name
= json
["name"].to_s
208 nitcoins
= json
["nitcoins"].as(Int)
214 json
["nitcoins"] = nitcoins
219 var res
= new FlatBuffer
220 res
.append
"-- {name} ({nitcoins} $)\n"
221 return res
.write_to_string
224 redef fun to_s
do return name
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)
236 # A GameReactor reacts to event sent by a `Github::HookListener`.
238 # Subclasses of `GameReactor` are implemented to handle all kind of
240 # Depending on the received event, the reactor is used to update game data.
242 # Reactors are mostly used with a `Github::HookListener` that dispatchs received
243 # events from the Github API.
248 # import github::hooks
250 # # Reactor that prints received events in console.
254 # redef fun react_event(game, e) do print e
257 # # Hook listener that redirect events to reactors.
258 # class RpgHookListener
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)
269 # See module `reactors` and `listener` for more examples.
270 interface GameReactor
272 # Reacts to this `event` and update `game` accordingly.
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
281 # Sort players by descending number of nitcoins.
283 # The first in the list is the player with the more of nitcoins.
284 class PlayerCoinComparator
287 redef type COMPARED: Player
289 redef fun compare
(a
, b
) do return b
.nitcoins
<=> a
.nitcoins