Add logging of game progress

This commit is contained in:
Smaug123
2018-01-01 22:17:27 +00:00
parent c661484138
commit c573dea22b
3 changed files with 91 additions and 13 deletions

View File

@@ -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
View File

@@ -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)

View File

@@ -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)