+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.org>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# `nitrpg` game structures.
-#
-# Here we define the main game entities:
-#
-# * `Game` holds all the entities for a game and provides high level services.
-# * `Player` represents a `Github::User` which plays the `Game`.
-#
-# Developpers who wants to extend the game capabilities should look at
-# the `GameReactor` abstraction.
-module game
-
-import mongodb
-import github::events
-
-# An entity within a `Game`.
-#
-# All game entities can be saved in a json format.
-interface GameEntity
- # The game instance containing `self`.
- fun game: Game is abstract
-
- # Collection `self` should be saved in.
- fun collection_name: String is abstract
-
- # Uniq key of this entity within the collection.
- fun key: String is abstract
-
- # Saves `self` in db.
- fun save do game.db.collection(collection_name).save(to_json_object)
-
- # Json representation of `self`.
- fun to_json_object: JsonObject do
- var json = new JsonObject
- json["_id"] = key
- return json
- end
-
- # Pretty print `self` to be displayed in a terminal.
- fun pretty: String is abstract
-end
-
-# Holder for game data and main services.
-#
-# Game is a `GameEntity` so it can be saved.
-class Game
- super GameEntity
-
- redef fun game do return self
-
- # We need a `GithubAPI` client to load Github data.
- var api: GithubAPI
-
- # A game takes place in a `github::Repo`.
- var repo: Repo
-
- # Game name
- var name: String = repo.full_name is lazy
-
- redef var key = name is lazy
-
- # Mongo server url where this game data are stored.
- var mongo_url = "mongodb://mongo:27017" is writable
-
- # Mongo db client.
- var client = new MongoClient(mongo_url) is lazy
-
- # Mongo db name where this game data are stored.
- var db_name = "nitrpg" is writable
-
- # Mongo db instance for this game.
- var db: MongoDb is lazy do return client.database(db_name)
-
- redef var collection_name = "games"
-
- # Init the Game and try to load saved data.
- init from_mongo(api: GithubAPI, repo: Repo) do
- init(api, repo)
- var req = new JsonObject
- req["name"] = repo.full_name
- var res = db.collection("games").find(req)
- if res != null then from_json(res)
- end
-
- # Init `self` from a JsonObject.
- #
- # Used to load entities from saved data.
- fun from_json(json: JsonObject) do end
-
- redef fun to_json_object do
- var json = super
- json["name"] = name
- return json
- end
-
- # Create a player from a Github `User`.
- #
- # Or return the existing one from game data.
- fun add_player(user: User): Player do
- # check if player already exists
- var player = load_player(user.login)
- if player != null then return player
- # create and store new player
- player = new Player(self, user.login)
- player.save
- return player
- end
-
- # Get a Player from his `name` or null if no player was found.
- #
- # Looks for the player save file in game data.
- #
- # Returns `null` if the player cannot be found.
- # In this case, the player can be created with `add_player`.
- fun load_player(name: String): nullable Player do
- var req = new JsonObject
- req["name"] = name
- req["game"] = game.key
- var res = db.collection("players").find(req)
- if res != null then return new Player.from_json(self, res)
- return null
- end
-
- # List known players.
- #
- # This list is reloaded from game data each time its called.
- #
- # To add players see `add_player`.
- fun load_players: MapRead[String, Player] do
- var req = new JsonObject
- req["game"] = game.key
- var res = new HashMap[String, Player]
- for obj in db.collection("players").find_all(req) do
- var player = new Player.from_json(self, obj)
- res[player.name] = player
- end
- return res
- end
-
- # Return a list of player name associated to their rank in the game.
- fun player_ranking: MapRead[String, Int] do
- var arr = load_players.values.to_a
- var res = new HashMap[String, Int]
- (new PlayerCoinComparator).sort(arr)
- var rank = 1
- for player in arr do
- res[player.name] = rank
- rank += 1
- end
- return res
- end
-
- # Erase all saved data for this game.
- fun clear do db.collection(collection_name).remove(to_json_object)
-
- # Verbosity level used fo stdout.
- #
- # * `-1` quiet
- # * `0` error and warnings
- # * `1` info
- # * `2` debug
- var verbose_lvl = 0 is writable
-
- # Display `msg` if `lvl` >= `verbose_lvl`
- fun message(lvl: Int, msg: String) do
- if lvl > verbose_lvl then return
- print msg
- end
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append "-------------------------\n"
- res.append "{repo.full_name}\n"
- res.append "-------------------------\n"
- res.append "# {load_players.length} players \n"
- return res.write_to_string
- end
-end
-
-# Players can battle on nitrpg for nitcoins and glory.
-#
-# A `Player` is linked to a `Github::User`.
-class Player
- super GameEntity
-
- # Stored in collection `players`.
- redef var collection_name = "players"
-
- redef var game
-
- # FIXME contructor should be private
-
- # Player name.
- #
- # This is the unic key for this player.
- # Should be equal to the associated `Github::User::login`.
- #
- # The name is also used to load the user data lazilly from Github API.
- var name: String
-
- redef var key = name is lazy
-
- # Player amount of nitcoins.
- #
- # Nitcoins is the currency used in nitrpg.
- # They can be obtained by performing actions on the `Game::Repo`.
- var nitcoins: Int = 0 is public writable
-
- # `Github::User` linked to this player.
- var user: User is lazy do
- var user = game.api.load_user(name)
- assert user isa User
- return user
- end
-
- # Init `self` from a `json` object.
- #
- # Used to load players from saved data.
- init from_json(game: Game, json: JsonObject) do
- init(game, json["name"].as(String))
- nitcoins = json["nitcoins"].as(Int)
- end
-
- redef fun to_json_object do
- var json = super
- json["game"] = game.key
- json["name"] = name
- json["nitcoins"] = nitcoins
- return json
- end
-
- redef fun pretty do
- var res = new FlatBuffer
- res.append "-- {name} ({nitcoins} $)\n"
- return res.write_to_string
- end
-
- redef fun to_s do return name
-end
-
-redef class User
- # The player linked to `self`.
- fun player(game: Game): Player do
- var player = player_cache.get_or_null(game)
- if player != null then return player
- player = game.load_player(login)
- if player == null then player = game.add_player(self)
- player_cache[game] = player
- return player
- end
-
- private var player_cache = new HashMap[Game, Player]
-end
-
-# A GameReactor reacts to event sent by a `Github::HookListener`.
-#
-# Subclasses of `GameReactor` are implemented to handle all kind of
-# `GithubEvent`.
-# Depending on the received event, the reactor is used to update game data.
-#
-# Reactors are mostly used with a `Github::HookListener` that dispatchs received
-# events from the Github API.
-#
-# Example:
-#
-# ~~~
-# import github::hooks
-#
-# # Reactor that prints received events in console.
-# class PrintReactor
-# super GameReactor
-#
-# redef fun react_event(game, e) do print e
-# end
-#
-# # Hook listener that redirect events to reactors.
-# class RpgHookListener
-# super HookListener
-#
-# redef fun apply_event(event) do
-# var game = new Game(api, event.repo)
-# var reactor = new PrintReactor
-# reactor.react_event(game, event)
-# end
-# end
-# ~~~
-#
-# See module `reactors` and `listener` for more examples.
-interface GameReactor
-
- # Reacts to this `event` and update `game` accordingly.
- #
- # Concrete `GameReactor` implement this method to update game data
- # for each specific GithubEvent.
- fun react_event(game: Game, event: GithubEvent) is abstract
-end
-
-# utils
-
-# Sort games by descending number of players.
-#
-# The first in the list is the game with the more players.
-class GamePlayersComparator
- super Comparator
-
- redef type COMPARED: Game
-
- redef fun compare(a, b) do
- return b.load_players.length <=> a.load_players.length
- end
-end
-
-# Sort players by descending number of nitcoins.
-#
-# The first in the list is the player with the more of nitcoins.
-class PlayerCoinComparator
- super Comparator
-
- redef type COMPARED: Player
-
- redef fun compare(a, b) do return b.nitcoins <=> a.nitcoins
-end