I wrote a whole Game class, might be unnecessary.

This commit is contained in:
Boyan 2023-11-02 16:51:38 +01:00
parent fa60eeb3d5
commit 41a513f581
5 changed files with 226 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode/
.env/

81
src/app.py Normal file
View File

@ -0,0 +1,81 @@
from flask import Flask, render_template, request, redirect, url_for, flash, session
from uuid import uuid4
from .classes import Game
app = Flask(__name__)
def find_game_by_id(id:str) -> dict:
games = []
with open('games.json', 'r') as f:
games = json.load(f)
index = 0
for game in games:
if game['id'] == id:
return {"game": game, "idx": index}
index += 1
return {"error": "Game not found"}
def start_game(game:dict):
pass
@app.route('/')
def index():
return "Empty for now"
@app.route('/new/<str:name>/<int:players>', methods=['POST'])
def new_game(name, players):
# Log new game into json file and return a websocket url for the game
games = []
with open('games.json', 'r') as f:
games = json.load(f)
# Create new game
game = {
"game": name,
"players": players,
"in": [request.remote_addr],
"join": None,
"id": f"{uuid4().hex}",
"status": "waiting",
}
games.append(game)
with open('games.json', 'w') as f:
json.dump(games, f)
@app.route('/join/<uuid:game_id>', methods=['POST'])
def join_game(game_id):
# Check if game exists
game = find_game_by_id(game_id)
if "error" in game:
return {"error": "Game not found"}
# Check if game is full
game, idx = game['game'], game['idx']
if len(game['in']) >= game['players']:
return {"error": "Game is full"}
# Add player to game
game['in'].append(request.remote_addr)
# Check if game is ready
if len(game['in']) == game['players']:
game['status'] = "ready"
start_game(game)
return {"status": "starting"}
games = []
with open('games.json', 'r') as f:
games = json.load(f)
games[game.index(game)] = game
with open('games.json', 'w') as f:
json.dump(games, f)

141
src/classes/Game.py Normal file
View File

@ -0,0 +1,141 @@
from websockets import serve, ConnectionClosed
import asyncio
from itertools import cycle
import logging
from random import choice, randint
from string import ascii_uppercase
# TODO:
# I misunderstood everything. I need to put every game inside a data structure, and only interact with it in this class.
# The server shouldn't be here, it should be in app.py
# Also flask_websockets is a thing
# I'm off to do logic now
class Game:
def __init__(self, players:list, name:str, id:str):
self.players = players
self.toMove = 0
self.clients = set()
self.players_seen = []
self.rules = [{"player":p, "rules":[]} for p in players]
self.status = "preparing"
self.name = name
self.id = id
self.end = ""
self.moves = []
# Find info about the client from the websocket
def findClient(self, websocket):
for p in self.players_seen:
if p["websocket"] == websocket:
return p["player"]
return None
# Check if everyone has joined
def isEveryoneIn(self):
return len(self.players_seen) == len(self.players)
# Check if rules are the same
def areRulesSame(self):
rules = self.rules
return all(x == rules[0] for x in rules)
# Set an end string
def setEnd(self, length):
letters = ascii_uppercase
return ''.join(choice(letters) for i in range(length))
# Add clients
async def handler(self, websocket):
self.clients.add(websocket)
try:
await websocket.wait_closed()
finally:
self.clients.remove(websocket)
# Messaging system for everyone
async def broadcast(self, message:str):
for websocket in self.clients.copy():
try:
await websocket.send(message + f" <<< SERVER >>> {findClient(websocket)}")
except ConnectionClosed:
self.clients.remove(websocket)
async def waitingRoom(self, websocket):
# Wait until everyone joins
while True:
if (self.status == "preparing"):
# Announce that we are waiting for this id
await websocket.broadcast(f"WAITING 4 {self.id}")
logging.info("Waiting for players to join")
await asyncio.sleep(1)
if self.isEveryoneIn():
return True
# Ask player for name
await websocket.send("WHO")
player = await websocket.recv()
if player not in self.players_seen:
self.handler(websocket)
self.players_seen.append({"player":player, "websocket":websocket})
logging.info(f"Player {player} joined the game")
# Ask player for rules
await websocket.send("RULES")
rules = await websocket.recv()
logging.info(f"Player {player} sent rules: {rules}")
for rule in rules.split(","):
self.rules[self.players.index(player)]["rules"].append(rule)
# [Game preparation](https://git.confest.im/boyan_k/game_server/media/branch/master/media/game_prep.png)
async def prepareGame(self):
# Wait for everyone to join
async with serve(self.waitingRoom, "0.0.0.0", 8765):
await asyncio.Future()
# Check if rules are valid
if not self.areRulesSame():
await self.broadcast("RULES_MISMATCH")
return False
# Start game
self.status = "running"
self.end = self.setEnd(randint(5, 10))
await self.broadcast("START: " + self.end)
self.toMove = cycle(self.clients)
return True
async def turnBased(self, websocket):
# Wait for player to send their move
await websocket.send("MOVE")
move = await websocket.recv()
player = self.findClient(websocket)
logging.info(f"Player {player} sent move: {move}")
# TODO: Include some sort of validation
self.moves.append({"player":player, "move":move})
return player, move
async def runGame(self):
# Run game
while True:
client = self.toMove.__next__()
p, m = await self.turnBased(client)
# We assume that the person who ended is the one who called his loss
if m == self.end:
# We remove them
await self.broadcast(f"{p} OUT")
self.clients.remove(client)
# We check if there is only one player left, in which case, he is the winner
if len(self.clients) == 1:
await self.broadcast(f"{self.findClient(self.clients[0])} WINS")
return True
self.toMove = cycle(self.clients)

1
src/games.json Normal file
View File

@ -0,0 +1 @@
[]

1
src/known_games.json Normal file
View File

@ -0,0 +1 @@
[]