From 454f3e2988b572cd557abcc1bfff72420d91b9c1 Mon Sep 17 00:00:00 2001 From: Stuart Young Date: Tue, 17 Jul 2018 13:01:44 -0400 Subject: [PATCH] initial draft of game server --- Makefile | 52 +++++++++++ card_lib/Makefile | 14 --- docker/game_server.Dockerfile | 21 +++++ game_server/__init__.py | 0 game_server/game.py | 87 +++++++++++++++++++ game_server/game_server.py | 47 ++++++++++ requirements/game_server.txt | 0 .../requirements.txt => requirements/test.txt | 0 8 files changed, 207 insertions(+), 14 deletions(-) create mode 100644 Makefile delete mode 100644 card_lib/Makefile create mode 100644 docker/game_server.Dockerfile create mode 100644 game_server/__init__.py create mode 100644 game_server/game.py create mode 100644 game_server/game_server.py create mode 100644 requirements/game_server.txt rename card_lib/requirements.txt => requirements/test.txt (100%) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3db0496 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +SHELL := /usr/bin/env bash +.DEFAULT_GOAL := help +.PHONY: clean requirements + + +help: + @echo ' make help show this help information' + @echo ' make clean.card_lib remove any artifacts or compiled files from the card_lib project' + @echo ' make requirements.card_lib install python requirements for the card_lib project and tests' + @echo ' make test.card_lib run unit tests for the card_lib project' + @echo ' make clean.game_server kill the game_server container and remove it, as well as any artifacts or compiled files' + @echo ' make build.game_server build the game_server docker container' + @echo ' make run.game_server run the game_server container' + + +### General + +clean: clean.game_server clean.card_lib + +requirements: requirements.game_server requirements.card_lib + +### Make targets for the card_lib + +clean.card_lib: + find card_lib -name '*pyc' |xargs -I {} rm {} + +requirements.card_lib: + pip install -r requirements/test.txt + +test.card_lib: + pytest card_lib + +### Make targets for the game_server + +clean.game_server: + docker kill game_server || true + docker rm game_server || true + docker rmi game_server || true + find game_server -name '*pyc' |xargs -I {} rm {} + +requirements.game_server: + pip install -r requirements/game_server.txt + pip install -r requirements/test.txt + +test.game_server: + pytest game_server + +build.game_server: + docker build -t game_server -f docker/game_server.Dockerfile . + +run.game_server: + docker run --name game_server -p 8888:8888 --rm game_server diff --git a/card_lib/Makefile b/card_lib/Makefile deleted file mode 100644 index 745a395..0000000 --- a/card_lib/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -SHELL := /usr/bin/env bash -.DEFAULT_GOAL := help - -help: - @echo '' - @echo 'make help show this information' - @echo 'make requirements install requirements for testing' - @echo 'make test run unit tests' - -requirements: - pip install -r requirements.txt - -test: - pytest . diff --git a/docker/game_server.Dockerfile b/docker/game_server.Dockerfile new file mode 100644 index 0000000..e7ddd4f --- /dev/null +++ b/docker/game_server.Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:16.04 + +RUN apt-get update \ + && apt-get install -y make python3-pip python3.5 + +RUN mkdir /game_server + +COPY game_server/* /game_server/ +COPY requirements/* /game_server/requirements/ +COPY Makefile /game_server/ + +EXPOSE 8888 + +# relink python3 and pip3 to common python aliases for convenience +RUN ln -sf /usr/bin/python3.5 /usr/bin/python \ + && ln -s /usr/bin/pip3 /usr/bin/pip + +WORKDIR /game_server +RUN make requirements.game_server + +CMD ["make", "help"] diff --git a/game_server/__init__.py b/game_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game_server/game.py b/game_server/game.py new file mode 100644 index 0000000..e94bb09 --- /dev/null +++ b/game_server/game.py @@ -0,0 +1,87 @@ +""" components of a card game, from the perspective of the game server """ + +import json +import uuid +import threading + + +class PlayerID(object): + """ + Representation of a player (client) in a card game. + + This is purely the the reference to a remote player used to identify + them amongst others in the lobby or game. It does not store what cards + they have nor control their plays. + """ + + def __init__(self, id_num, host, port): + self.id_num = id_num + self.host = host + self.port = port + + @property + def address(self): + """ + return the address of the player client, which will be used + to communicate with it + """ + return "{}:{}".format(self.host, self.port) + + +class Lobby(object): + """ + Representation of the 'lobby' phase of a game, in which players can join + but gameplay has not yet begun + """ + + def __init__(self, min_players, max_players) + self.game = game + self.min_players = min_players + self.max_players = max_players + self.accepting = True + self.players = [] + + def register_player(self, player_request): + """ + given a valid player request, create and add a player to the lobby + if it is not already + """ + player_request = json.loads(player_request) + host = player_request['host'] + port = player_request['port'] + id_num = uuid.uuid4() + player = PlayerID(id_num, host, port) + if player not in self.players: + self.players.append(player) + else: + raise PlayerConflictException() + + def _generate_status(self): + """ + create a json status message describing the current state of the + lobby. This will be sent out to all currently registered players + in the lobby + """ + return '{"accepting":"{}", "players":"{}"}'.format( + self.accepting, + [p.address() for p in self.players] + ) + + def is_at_quorum(self): + """ + determine if the lobby is at `quorum`- in other words, have enough + players registered with the lobby to begin a game + """ + return self.min_players <= len(self.players) < self.max_players + + def serve(self, _socket): + """ + continually serve the game lobby, accepting incoming player requests + until the lobby has filled or timed out once quorum has been met + """ + while True: + pass + + +class PlayerConflictException(Exception): + pass diff --git a/game_server/game_server.py b/game_server/game_server.py new file mode 100644 index 0000000..b802d94 --- /dev/null +++ b/game_server/game_server.py @@ -0,0 +1,47 @@ + +import socket +import sys +import json +import logging + +from game import ( + PlayerID, + PlayerConflictException + Lobby +) + +logging.basicConfig() +LOGGER = logging.getLogger() +LOGGER.setLevel(logging.INFO) + +def create_socket(host, port): + """ + create a socket object and bind it to a port. This will + be used to communicate between the various players within + a game + """ + LOG.debug('Creating a socket and binding to {} on {}'.format( + host, port + )) + _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + _socket.bind((host, port)) + except socket.error: + error_msg = 'Unable to bind socket to {} on {}'.format( + host, port + ) + LOG.error(error_msg) + sys.exit(1) + LOG.debug('Successfully bound socket') + return _socket + +def handle_player_request(connection): + +def main(): + # for now, using hard coded values + _socket = create_socket('localhost', 8080) + lobby = Lobby('go fish', 2, 5) + lobby.serve(_socket) + +if __name__ == "__main__": + main() diff --git a/requirements/game_server.txt b/requirements/game_server.txt new file mode 100644 index 0000000..e69de29 diff --git a/card_lib/requirements.txt b/requirements/test.txt similarity index 100% rename from card_lib/requirements.txt rename to requirements/test.txt