Files
hanabi-server/client.py
2018-02-24 17:09:20 +00:00

277 lines
8.4 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import enum
import requests
from HanabiWeb import card
def get_game_id():
input_str = None
while input_str is None:
try:
input_str = input("Please enter an integer game ID: ")
int(input_str)
except ValueError:
input_str = None
return int(input_str)
_DEFAULT_PLAYER_FILE = os.path.join(os.path.expanduser("~"),
".hanabi", "name.txt")
_DEFAULT_SERVER_FILE = os.path.join(os.path.expanduser("~"),
".hanabi", "server.txt")
def get_player():
with open(_DEFAULT_PLAYER_FILE) as f:
default_player = f.readlines()[0].strip()
input_str = input("Enter your player name (default is {}): ".format(
default_player))
if not input_str:
return default_player
return input_str
def get_server():
with open(_DEFAULT_SERVER_FILE) as f:
default_server = f.readlines()[0].strip()
input_str = input("Enter the server (default is {}): ".format(
default_server))
if not input_str:
return default_server
return input_str
def get_board_state(game_id, player):
pass
def print_welcome():
print("Hanabi client.")
print("Command-line version. Press Ctrl+C to exit.")
class Actions(enum.Enum):
PRINT_GAMESTATE = 0
PLAY = 1
DISCARD = 2
HISTORY = 3
INFORM = 4
_recognised_actions = {'print': (Actions.PRINT_GAMESTATE, 0),
'play': (Actions.PLAY, 1),
'discard': (Actions.DISCARD, 1),
'history': (Actions.HISTORY, 0),
'inform': (Actions.INFORM, 1)}
def get_action():
"""
Generator yielding the actions the user takes.
Yields e.g. ((Actions.PLAY, 1), ["inputstr"]), indicating that one of the
elements of the list is an argument.
"""
while True:
words = input("Please enter your action ({}): >".format(','.join(_recognised_actions))).strip().split()
if not words:
continue
if words[0] in _recognised_actions:
act = _recognised_actions[words[0]]
remaining = words[1:act[1]+1]
yield (_recognised_actions[words[0]], remaining)
def request_gamestate(server, player, gameid):
"""
Requests the current game state from the player's perspective.
If successful, returns a dictionary as output by the REST API for the
/game/<id>/<player> endpoint.
"""
print("Requesting game state...")
url = server + '/game/{id}/{player}'.format(id=gameid, player=player)
r = requests.get('http://' + url)
js = r.json()
return js
def get_top(cards, colour):
"""
Get the top card played of the given colour string.
"""
iter = [card.rank
for card in cards
if card.colour.lower() == colour.lower()]
if not iter:
return 0
return max(iter)
def request_history(server, player, game_id):
"""
Request the history from the server from the point of view of a player.
"""
# print("Requesting history...")
# url = server + '/history/{}'.format(game_id)
print("History is not yet implemented.")
def print_history(history):
"""
Print the history object retrieved from the server.
"""
pass
def print_gamestate(state):
cards_played = state['played']
players = state['players']
lives_available = state['lives']['available']
lives_used = state['lives']['used']
knowledge_available = state['knowledge']['available']
knowledge_used = state['knowledge']['used']
hands = state['hands']
discarded = state['discards']
print('----------------- Metadata ------------------------')
print('Players: {}'.format(', '.join(players)))
print('Lives: {} remaining, {} used'.format(lives_available, lives_used))
print('Knowledge: {} remaining, {} used'.format(knowledge_available,
knowledge_used))
print('------------------ Piles --------------------------')
any_played = False
for colour in card.HanabiColour:
# Get top card of the pile
top = get_top(cards_played, colour.name)
if top:
print('Top card {}: {}'.format(colour.name, top))
any_played = True
if not any_played:
print("No cards played.")
print('----------------- Discard -------------------------')
any_discarded = False
for colour in card.HanabiColour:
disc = sorted([card['rank']
for card in discarded
if card['colour'].lower() == colour.name.lower()])
if disc:
print("Colour {}: {}".format(colour.name, disc))
any_discarded = True
if not any_discarded:
print("No cards discarded.")
print('------------------ Hands ------------------------')
for player in sorted(players):
if player in hands:
hand = ['{} {}'.format(card['colour'], card['rank'])
for card in hands[player]]
print('{}: {}'.format(player, ', '.join(hand)))
print('-------------------------------------------------')
def validate_args(numargs, args):
"""
Check that there are enough args in the list, and truncate accordingly.
Raises ValueError if not.
"""
if len(args) < numargs:
raise ValueError("Not enough elements in list {}, need "
"{}.".format(args, numargs))
return args
def request_discard(server, player, id, card):
"""
Discard a card.
The card is specified as a zero-indexed card from the left positionally in
someone's hand.
"""
url = server + '/discard/{id}/{player}'.format(id=id, player=player)
r = requests.post('http://' + url, data={'card_index': card})
return r.text
def request_play(server, player, id, card):
"""
Play a card.
The card is specified as a zero-indexed card from the left positionally in
someone's hand.
"""
url = server + '/play/{id}/{player}'.format(id=id, player=player)
r = requests.post('http://' + url, data={'card_index': card})
return r.text
def request_inform(server, requester, player, id, colour=None, rank=None):
"""
Give a player information about a card.
"""
data = {'recipient': player}
if colour is None:
assert(rank is not None)
data['rank'] = rank
if rank is None:
assert(colour is not None)
data['colour'] = colour.lower().capitalize()
url = server + '/inform/{id}/{player}'.format(id=id, player=requester)
r = requests.post('http://' + url, data=data)
return r.text
if __name__ == '__main__':
game_id = get_game_id()
player = get_player()
server = get_server()
print_welcome()
actions = get_action()
while True:
((action, numargs), args) = next(actions)
try:
args = validate_args(numargs, args)
except ValueError as e:
print("{}".format(e))
continue
if action == Actions.PRINT_GAMESTATE:
state = request_gamestate(server, player, game_id)
print_gamestate(state)
elif action == Actions.DISCARD:
outcome = request_discard(server, player, game_id, args[0])
if outcome.strip() != 'true':
print('May have failed: {}'.format(outcome))
state = request_gamestate(server, player, game_id)
print_gamestate(state)
elif action == Actions.PLAY:
outcome = request_play(server, player, game_id, args[0])
if outcome.strip() != 'true':
print('May have failed: {}'.format(outcome))
state = request_gamestate(server, player, game_id)
print_gamestate(state)
elif action == Actions.INFORM:
recipient = args[0]
which = input('Enter a colour string or a number: ')
try:
int(which)
except ValueError:
# which is a colour
outcome = request_inform(server, player, recipient, game_id, colour=which)
else:
outcome = request_inform(server, player, recipient, game_id, rank=which)
state = request_gamestate(server, player, game_id)
print_gamestate(state)
elif action == Actions.HISTORY:
history = request_history(server, player, game_id)
print_history(history)
# TODO: need to pip install requests[security] when installing this