mirror of
https://github.com/Smaug123/hanabi-server
synced 2025-10-10 11:08:39 +00:00
Initial commit
This commit is contained in:
0
HanabiWeb/__init__.py
Normal file
0
HanabiWeb/__init__.py
Normal file
58
HanabiWeb/cache.py
Normal file
58
HanabiWeb/cache.py
Normal file
@@ -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)
|
20
HanabiWeb/card.py
Normal file
20
HanabiWeb/card.py
Normal file
@@ -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
|
||||
|
106
HanabiWeb/hanabi.py
Normal file
106
HanabiWeb/hanabi.py
Normal file
@@ -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)
|
0
HanabiWeb/requirements.txt
Normal file
0
HanabiWeb/requirements.txt
Normal file
Reference in New Issue
Block a user