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