I wrote a whole Game class, might be unnecessary.
This commit is contained in:
parent
fa60eeb3d5
commit
41a513f581
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.vscode/
|
||||
.env/
|
81
src/app.py
Normal file
81
src/app.py
Normal 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
141
src/classes/Game.py
Normal 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
1
src/games.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
1
src/known_games.json
Normal file
1
src/known_games.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
Loading…
x
Reference in New Issue
Block a user