From 30b9f068cec856a34fa2eabdf800310704bf5573 Mon Sep 17 00:00:00 2001 From: Smaug123 Date: Mon, 1 Jan 2018 15:31:21 +0000 Subject: [PATCH] Initial commit --- HanabiWeb/__init__.py | 0 HanabiWeb/cache.py | 58 ++++++++++++++++++++ HanabiWeb/card.py | 20 +++++++ HanabiWeb/hanabi.py | 106 +++++++++++++++++++++++++++++++++++++ HanabiWeb/requirements.txt | 0 server.py | 1 + 6 files changed, 185 insertions(+) create mode 100644 HanabiWeb/__init__.py create mode 100644 HanabiWeb/cache.py create mode 100644 HanabiWeb/card.py create mode 100644 HanabiWeb/hanabi.py create mode 100644 HanabiWeb/requirements.txt create mode 100644 server.py diff --git a/HanabiWeb/__init__.py b/HanabiWeb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/HanabiWeb/cache.py b/HanabiWeb/cache.py new file mode 100644 index 0000000..4eae570 --- /dev/null +++ b/HanabiWeb/cache.py @@ -0,0 +1,58 @@ +""" +Utility functions for storing and retrieving Hanabi server data. +""" + +import yaml + + +# Field names for each field +_players = "players" +_hands = "hands" +_discards = "discards" +_knowledge = "knowledge" +_lives = "lives" + +_fieldnames = [_players, _hands, _discards, _knowledge, _lives] + + +class GameDataStore: + """ + Store complete information about a Hanabi game in a file. + """ + + def __init__(self, path): + self.filepath = path + + def get(self): + with open(self.filepath) as f: + data = yaml.safe_load(f) + return data + + def replace(self, data): + with open(self.filepath) as f: + yaml.dump(data, f) + + def replace_field(self, field, data): + existing = self.get() + existing[field] = data + self.replace(existing) + + def append_to_field(self, field, data): + existing = self.get() + existing[field].append(data) + self.replace(existing) + + def add_player(self, player_name): + self.append_to_field(_players, player_name) + + def create(self, players): + """ + Create a new Hanabi game, storing the data in the given file. + :return: + """ + data = {_players: players, + _hands: [], + _discards: [], + _knowledge: {"used": 0, "available": 8}, + _lives: {"used": 0, "available": 3}} + self.replace(data) diff --git a/HanabiWeb/card.py b/HanabiWeb/card.py new file mode 100644 index 0000000..11c30cb --- /dev/null +++ b/HanabiWeb/card.py @@ -0,0 +1,20 @@ +import enum + + +@enum.unique() +class HanabiColour(enum.Enum): + Red = enum.auto() + Green = enum.auto() + White = enum.auto() + Yellow = enum.auto() + Blue = enum.auto() + + +class HanabiCard: + def __str__(self): + return "{} {}".format(self.rank, self.colour) + + def __init__(self): + self.colour = None + self.rank = None + diff --git a/HanabiWeb/hanabi.py b/HanabiWeb/hanabi.py new file mode 100644 index 0000000..94a39ae --- /dev/null +++ b/HanabiWeb/hanabi.py @@ -0,0 +1,106 @@ +import os +import re + +from flask import Flask +from flask_restful import Resource, Api, abort + +from . import hanabi_cache + +app = Flask(__name__) +api = Api(app) + + +_DATA_STORES = os.path.join(os.path.expanduser('~'), '.hanabigames') + + +def _validate_game_id(game_id): + """ + Test whether a game ID is valid. If it is not, raise a 403 Forbidden. + """ + try: + int(str(game_id)) + except ValueError: + abort(403, message="Malformed game ID {}".format(game_id)) + + +def _game_data_path(game_id): + """ + Find the path to the data file for a given game. + + This fully trusts game_id, and is not safe on unsanitised input. + """ + return os.path.join(_DATA_STORES, "{}.dat".format(game_id)) + + +def ls(directory): + onlyfiles = [f + for f in os.listdir(directory) + if os.path.isfile(os.path.join(directory, f))] + return onlyfiles + + +def _get_new_game_index(): + files = ls(_DATA_STORES) + indices = [int(name.rstrip('.dat')) + for name in files + if re.match(r"[0-9]+\.dat$", name)] + if not indices: + return 0 + indices.sort() + return indices[-1] + 1 + + +class Hand(Resource): + def get(self, game_id, player_id): + return {'hello': 'world'} + + +class Play(Resource): + def post(self, game_id, player_id, card): + pass + + +class Game(Resource): + def get(self, game_id, player_id=None): + """ + Return the state of the game as viewed by the given player. + + If no player is specified, return the state of the game as viewed by a + spectator. + + :param game_id: Lookup ID for the given game. + :param player_id: Lookup ID for a certain player in this game. + :return: Dictionary of game state. + {players: [players], + hands: {player1: [cards], player2: [cards]}, + discards: [cards], + knowledge: {used: 5, available: 3}, + lives: {used: 0, available: 3}} + """ + _validate_game_id(game_id) + data_path = _game_data_path(game_id) + + data = hanabi_cache.GameDataStore(data_path) + + if player_id is None: + return data.get() + + def put(self, players): + """ + Create a new game, returning the game ID. + """ + new_id = _get_new_game_index() + data_path = _game_data_path(new_id) + data = hanabi_cache.GameDataStore(data_path) + data.create(players) + + +api.add_resource(Hand, '/hanabi') + + +@app.route("/hanabi") +def hello(): + return "Hello, World!" + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/HanabiWeb/requirements.txt b/HanabiWeb/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/server.py b/server.py new file mode 100644 index 0000000..7b2420d --- /dev/null +++ b/server.py @@ -0,0 +1 @@ +__author__ = 'Patrick'