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.
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 # Collection `self` should be saved in.
39 fun collection_name
: String is abstract
41 # Uniq key of this entity within the collection.
42 fun key
: String is abstract
45 fun save
do game
.db
.collection
(collection_name
).save
(to_json
)
47 # Json representation of `self`.
48 fun to_json
: JsonObject do
49 var json
= new JsonObject
54 # Pretty print `self` to be displayed in a terminal.
55 fun pretty
: String is abstract
58 # Holder for game data and main services.
60 # Game is a `GameEntity` so it can be saved.
64 redef fun game
do return self
66 # We need a `GithubAPI` client to load Github data.
69 # A game takes place in a `github::Repo`.
73 var name
: String = repo
.full_name
is lazy
75 redef var key
= name
is lazy
77 # Mongo server url where this game data are stored.
78 var mongo_url
= "mongodb://localhost:27017" is writable
81 var client
= new MongoClient(mongo_url
) is lazy
83 # Mongo db name where this game data are stored.
84 var db_name
= "nitrpg" is writable
86 # Mongo db instance for this game.
87 var db
: MongoDb is lazy
do return client
.database
(db_name
)
89 redef var collection_name
= "games"
91 # Init the Game and try to load saved data.
92 init from_mongo
(api
: GithubAPI, repo
: Repo) do
94 var req
= new JsonObject
95 req
["name"] = repo
.full_name
96 var res
= db
.collection
("games").find
(req
)
97 if res
!= null then from_json
(res
)
100 # Init `self` from a JsonObject.
102 # Used to load entities from saved data.
103 fun from_json
(json
: JsonObject) do end
111 # Create a player from a Github `User`.
113 # Or return the existing one from game data.
114 fun add_player
(user
: User): Player do
115 # check if player already exists
116 var player
= load_player
(user
.login
)
117 if player
!= null then return player
118 # create and store new player
119 player
= new Player(self, user
.login
)
124 # Get a Player from his `name` or null if no player was found.
126 # Looks for the player save file in game data.
128 # Returns `null` if the player cannot be found.
129 # In this case, the player can be created with `add_player`.
130 fun load_player
(name
: String): nullable Player do
131 var req
= new JsonObject
133 req
["game"] = game
.key
134 var res
= db
.collection
("players").find
(req
)
135 if res
!= null then return new Player.from_json
(self, res
)
139 # List known players.
141 # This list is reloaded from game data each time its called.
143 # To add players see `add_player`.
144 fun load_players
: MapRead[String, Player] do
145 var req
= new JsonObject
146 req
["game"] = game
.key
147 var res
= new HashMap[String, Player]
148 for obj
in db
.collection
("players").find_all
(req
) do
149 var player
= new Player.from_json
(self, obj
)
150 res
[player
.name
] = player
155 # Return a list of player name associated to their rank in the game.
156 fun player_ranking
: MapRead[String, Int] do
157 var arr
= load_players
.values
.to_a
158 var res
= new HashMap[String, Int]
159 (new PlayerCoinComparator).sort
(arr
)
162 res
[player
.name
] = rank
168 # Erase all saved data for this game.
169 fun clear
do db
.collection
(collection_name
).remove
(to_json
)
171 # Verbosity level used fo stdout.
174 # * `0` error and warnings
177 var verbose_lvl
= 0 is writable
179 # Display `msg` if `lvl` >= `verbose_lvl`
180 fun message
(lvl
: Int, msg
: String) do
181 if lvl
> verbose_lvl
then return
186 var res
= new FlatBuffer
187 res
.append
"-------------------------\n"
188 res
.append
"{repo.full_name}\n"
189 res
.append
"-------------------------\n"
190 res
.append
"# {load_players.length} players \n"
191 return res
.write_to_string
195 # Players can battle on nitrpg for nitcoins and glory.
197 # A `Player` is linked to a `Github::User`.
201 # Stored in collection `players`.
202 redef var collection_name
= "players"
206 # FIXME contructor should be private
210 # This is the unic key for this player.
211 # Should be equal to the associated `Github::User::login`.
213 # The name is also used to load the user data lazilly from Github API.
216 redef var key
= name
is lazy
218 # Player amount of nitcoins.
220 # Nitcoins is the currency used in nitrpg.
221 # They can be obtained by performing actions on the `Game::Repo`.
222 var nitcoins
: Int = 0 is public
writable
224 # `Github::User` linked to this player.
225 var user
: User is lazy
do
226 var user
= game
.api
.load_user
(name
)
231 # Init `self` from a `json` object.
233 # Used to load players from saved data.
234 init from_json
(game
: Game, json
: JsonObject) do
235 init(game
, json
["name"].as(String))
236 nitcoins
= json
["nitcoins"].as(Int)
241 json
["game"] = game
.key
243 json
["nitcoins"] = nitcoins
248 var res
= new FlatBuffer
249 res
.append
"-- {name} ({nitcoins} $)\n"
250 return res
.write_to_string
253 redef fun to_s
do return name
257 # The player linked to `self`.
258 fun player
(game
: Game): Player do
259 var player
= player_cache
.get_or_null
(game
)
260 if player
!= null then return player
261 player
= game
.load_player
(login
)
262 if player
== null then player
= game
.add_player
(self)
263 player_cache
[game
] = player
267 private var player_cache
= new HashMap[Game, Player]
270 # A GameReactor reacts to event sent by a `Github::HookListener`.
272 # Subclasses of `GameReactor` are implemented to handle all kind of
274 # Depending on the received event, the reactor is used to update game data.
276 # Reactors are mostly used with a `Github::HookListener` that dispatchs received
277 # events from the Github API.
282 # import github::hooks
284 # # Reactor that prints received events in console.
288 # redef fun react_event(game, e) do print e
291 # # Hook listener that redirect events to reactors.
292 # class RpgHookListener
295 # redef fun apply_event(event) do
296 # var game = new Game(api, event.repo)
297 # var reactor = new PrintReactor
298 # reactor.react_event(game, event)
303 # See module `reactors` and `listener` for more examples.
304 interface GameReactor
306 # Reacts to this `event` and update `game` accordingly.
308 # Concrete `GameReactor` implement this method to update game data
309 # for each specific GithubEvent.
310 fun react_event
(game
: Game, event
: GithubEvent) is abstract
315 # Sort games by descending number of players.
317 # The first in the list is the game with the more players.
318 class GamePlayersComparator
321 redef type COMPARED: Game
323 redef fun compare
(a
, b
) do
324 return b
.load_players
.length
<=> a
.load_players
.length
328 # Sort players by descending number of nitcoins.
330 # The first in the list is the player with the more of nitcoins.
331 class PlayerCoinComparator
334 redef type COMPARED: Player
336 redef fun compare
(a
, b
) do return b
.nitcoins
<=> a
.nitcoins