mirror of
https://github.com/Smaug123/hanabi-server
synced 2025-10-11 03:28:40 +00:00
Add logging of game progress
This commit is contained in:
@@ -6,13 +6,22 @@ from flask_restful import Resource, abort, reqparse
|
||||
from . import cache
|
||||
from . import card
|
||||
|
||||
_DATA_STORES = os.path.join(os.path.expanduser('~'), '.hanabigames')
|
||||
_DATA_STORES = os.path.join(os.path.expanduser('~'), '.hanabi')
|
||||
_EXTENSION = '.han'
|
||||
|
||||
|
||||
_colours = card.HanabiColour.__members__.keys()
|
||||
|
||||
|
||||
def _game_log_path(game_id):
|
||||
return os.path.join(_DATA_STORES, '{}.log'.format(game_id))
|
||||
|
||||
|
||||
def log(string, game):
|
||||
with open(_game_log_path(game), 'a') as f:
|
||||
f.write("{}\n".format(string))
|
||||
|
||||
|
||||
def _validate_game_id(game_id):
|
||||
"""
|
||||
Test whether a game ID is valid. If it is not, raise a 403 Forbidden.
|
||||
@@ -38,7 +47,7 @@ def _validate_player_in_game(data, player):
|
||||
"""
|
||||
Test whether the player is in the given game.
|
||||
"""
|
||||
players = data[cache.players]
|
||||
players = data[cache.players_key]
|
||||
if player not in players:
|
||||
abort(400,
|
||||
message="Player {} not found in game".format(player))
|
||||
@@ -117,22 +126,27 @@ class Discard(Resource):
|
||||
player_hand = data[cache.hands_key][player]
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('card_index', type=int)
|
||||
parser.add_argument('card_index', type=int, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.card_index < 0 or args.card_index >= len(player_hand):
|
||||
abort(400, message="Card {} not valid.".format(args.card_index))
|
||||
|
||||
# Discard the card with given index.
|
||||
drawn_card = data[cache.deck_key].pop()
|
||||
data[cache.discards_key].append(player_hand[args.card_index])
|
||||
player_hand[args.card_index] = drawn_card
|
||||
discarded_card = player_hand[args.card_index]
|
||||
log("Player '{}' discarded card {}.".format(player, discarded_card),
|
||||
game=game_id)
|
||||
|
||||
data[cache.discards_key].append(discarded_card)
|
||||
|
||||
if data[cache.knowledge_key]['used'] != 0:
|
||||
data[cache.knowledge_key]['used'] -= 1
|
||||
data[cache.knowledge_key]['available'] += 1
|
||||
|
||||
data_store.replace(data)
|
||||
drawn_card = data[cache.deck_key].pop()
|
||||
player_hand[args.card_index] = drawn_card
|
||||
|
||||
data_store.replace(data)
|
||||
return True
|
||||
|
||||
|
||||
@@ -150,7 +164,7 @@ class PlayCard(Resource):
|
||||
player_hand = data[cache.hands_key][player]
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('card_index', type=int)
|
||||
parser.add_argument('card_index', type=int, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.card_index < 0 or args.card_index >= len(player_hand):
|
||||
@@ -161,13 +175,19 @@ class PlayCard(Resource):
|
||||
if _can_play(data[cache.played_key], card_to_play):
|
||||
data[cache.played_key].append(player_hand[args.card_index])
|
||||
retval = True
|
||||
log("Player '{}' played card {}.".format(player, card_to_play),
|
||||
game=game_id)
|
||||
else:
|
||||
retval = False
|
||||
log("Player '{}' played card {} wrongly.".format(player,
|
||||
card_to_play),
|
||||
game=game_id)
|
||||
data[cache.discards_key].append(player_hand[args.card_index])
|
||||
if data[cache.lives_key]["available"] > 0:
|
||||
data[cache.lives_key]["used"] += 1
|
||||
data[cache.lives_key]["available"] -= 1
|
||||
if data[cache.lives_key]["available"] <= 0:
|
||||
log("Game over.")
|
||||
return "All lives exhausted. Game over."
|
||||
|
||||
drawn_card = data[cache.deck_key].pop()
|
||||
@@ -178,6 +198,48 @@ class PlayCard(Resource):
|
||||
return retval
|
||||
|
||||
|
||||
class Inform(Resource):
|
||||
def post(self, game_id, player):
|
||||
"""
|
||||
Expects recipient=Patrick and either colour=red or rank=5, for instance.
|
||||
"""
|
||||
_validate_game_id(game_id)
|
||||
_validate_game_exists(game_id)
|
||||
|
||||
data_path = _game_data_path(game_id)
|
||||
data_store = cache.GameDataStore(data_path)
|
||||
data = data_store.get()
|
||||
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('recipient', type=str, required=True)
|
||||
parser.add_argument('colour', choices=tuple(_colours) + ("",))
|
||||
parser.add_argument('rank', type=int)
|
||||
args = parser.parse_args()
|
||||
|
||||
_validate_player_in_game(data, args.recipient)
|
||||
|
||||
if (args.colour and args.rank) or not (args.colour or args.rank):
|
||||
abort(400, message="Supply exactly one of colour and rank.")
|
||||
|
||||
if args.colour:
|
||||
matching = [i
|
||||
for i, c in enumerate(data[cache.hands_key][args.recipient])
|
||||
if c['colour'] == args.colour]
|
||||
description = 'colour {}'.format(args.colour)
|
||||
else:
|
||||
assert args.rank
|
||||
matching = [i
|
||||
for i, c in enumerate(data[cache.hands_key][args.recipient])
|
||||
if c['rank'] == args.rank]
|
||||
description = 'rank {}'.format(args.rank)
|
||||
|
||||
summary = "Player '{}' gave {} in hand of player '{}': positions {}."
|
||||
summary = summary.format(player, description, args.recipient, matching)
|
||||
log(summary, game_id)
|
||||
|
||||
return matching
|
||||
|
||||
|
||||
class Game(Resource):
|
||||
def get(self, game_id, player=None):
|
||||
"""
|
||||
@@ -211,14 +273,16 @@ class Game(Resource):
|
||||
Create a new game, returning the game ID.
|
||||
"""
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('players', type=str,
|
||||
help='Player names, comma-separated')
|
||||
parser.add_argument('player', type=str,
|
||||
action='append',
|
||||
help='Player names',
|
||||
required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
new_id = _get_new_game_index()
|
||||
data_path = _game_data_path(new_id)
|
||||
data = cache.GameDataStore(data_path)
|
||||
data.create(args['players'].split(','))
|
||||
data.create(args['player'])
|
||||
|
||||
return {'id': new_id}
|
||||
|
||||
|
15
README
15
README
@@ -16,6 +16,8 @@ At the moment, the system can track for multiple games:
|
||||
### PUT
|
||||
Create a new game, returning the game ID as `{id: 3}`, for instance.
|
||||
|
||||
Arguments: e.g. `-d "player=bob" -d "player=sue" -d "player=joe"`
|
||||
|
||||
## `/game/<id>`
|
||||
### GET
|
||||
Download a complete dump of the specified game in its current state.
|
||||
@@ -37,7 +39,7 @@ to be discarded. (Card order is maintained strictly, so `0` refers to the first
|
||||
card from the left in one's hand.)
|
||||
|
||||
## `/play/<game>/<player>`
|
||||
### PLAY
|
||||
### POST
|
||||
Have the specified player attempt to play a card in the specified game.
|
||||
|
||||
If the play is unsuccessful, a life will be lost.
|
||||
@@ -46,11 +48,20 @@ Supply the data `card_index=0`, for example, where `0` is the index of the card
|
||||
to be played. (Card order is maintained strictly, so `0` refers to the first
|
||||
card from the left in one's hand.)
|
||||
|
||||
## `/inform/<game>/<player>`
|
||||
### POST
|
||||
Have the specified player give a recipient information about their hand.
|
||||
|
||||
Supply the data `recipient=Patrick` to specify the recipient of the information,
|
||||
and either one of the following:
|
||||
|
||||
* data `colour=Red` to specify that you are pointing out red cards.
|
||||
* data `rank=5` to specify that you are pointing out cards of rank 5.
|
||||
|
||||
# Future work ideas
|
||||
|
||||
* Make the data storage format version-aware.
|
||||
* Track which information has been revealed about each specific card.
|
||||
* Decide/implement a way to tell the players about moves which have been made.
|
||||
* Log the game history.
|
||||
|
||||
[Hanabi]: https://en.wikipedia.org/wiki/Hanabi_(card_game)
|
@@ -28,6 +28,9 @@ HanabiWeb.hanabi.PlayCard.method_decorators.append(limiter.limit("10 per minute"
|
||||
api.add_resource(HanabiWeb.hanabi.PlayCard,
|
||||
'/play/<int:game_id>/<string:player>')
|
||||
|
||||
HanabiWeb.hanabi.Inform.method_decorators.append(limiter.limit("10 per minute"))
|
||||
api.add_resource(HanabiWeb.hanabi.Inform,
|
||||
'/inform/<int:game_id>/<string:player>')
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
|
Reference in New Issue
Block a user