projet-reseau-1/shared/server/game.py

251 lines
8.2 KiB
Python

import asyncio
import logging
import random
import socket
from enum import Enum
from typing import List, Dict, Tuple, Optional
from .message import parse_message, BiCommand
class Color(Enum):
TREFLE = "trefle"
CARRAUX = "carraux"
COEUR = "cœurs"
PIQUES = "piques"
class Carte:
color: Color
value: int
def __init__(self, color: Color, value: int):
self.color = color
self.value = value
def __str__(self):
return str(self.color.value) + " " + str(self.value)
def create_deck() -> List[Carte]:
cartes = []
for color in [Color.TREFLE, Color.CARRAUX, Color.COEUR, Color.PIQUES]:
for num in range(1, 14):
cartes.append(Carte(color, num))
random.shuffle(cartes)
return cartes
class Table:
__name: str
__delay: int
def __init__(self, name: str, delay: int) -> None:
self.__name = name
self.__delay = delay
@property
def delay(self) -> int:
return self.__delay
@property
def name(self) -> str:
return self.__name
class Joueur:
__socket: socket.socket
cartes: List[Carte]
def __init__(self, client_socket: socket.socket):
self.__socket = client_socket
self.cartes = list()
@property
def socket(self) -> socket.socket:
return self.__socket
@property
def points(self) -> int:
return points_cartes(self.cartes)
def points_cartes(cartes: List[Carte]) -> int:
value = 0
as_count = 0
for carte in cartes:
if carte.value == 1:
as_count += 1
else:
value += carte.value
if as_count > 0:
if value + 11 + as_count - 1 < 21:
value += 11 + as_count - 1
else:
value += as_count
return value
class Game:
__players: List[Joueur]
__table: 'Table'
__started: bool
__accepting: bool
__cards: List[Carte]
__dealer_cards: List[Carte]
__logger: logging.Logger
def __init__(self, table: 'Table') -> None:
self.__table = table
self.__started = False
self.__players = []
self.__accepting = True
self.__cards = create_deck()
self.__dealer_cards = list()
self.__logger = logging.getLogger(f"Game-{table.name}")
async def add_client(self, client: socket.socket, data: 'ConcurrentData') -> bool:
if self.__accepting:
loop = asyncio.get_event_loop()
joueur = Joueur(client)
self.__players.append(joueur)
if not self.__started:
loop.create_task(self.run_game(data))
return True
else:
return False
async def run_game(self, data: 'ConcurrentData'):
self.__logger.error(f"Waiting {self.__table.delay}s")
self.__started = True
await asyncio.sleep(self.__table.delay)
self.__accepting = False
for _ in range(0, 2):
self.__dealer_cards.append(self.__cards.pop(0))
for joueur in self.__players:
joueur.cartes.append(self.__cards.pop(0))
loop = asyncio.get_event_loop()
for c in self.__players:
await loop.sock_sendall(c.socket, (
"Vos cartes sont : " + ", ".join(str(carte) for carte in c.cartes) + "\r\n").encode("utf8"))
await loop.sock_sendall(c.socket, (
"La premiere carte du donneur est : " + str(self.__dealer_cards[0]) + "\r\n").encode('utf8'))
await self.run_game_loop(loop)
for c in self.__players:
await loop.sock_sendall(c.socket, b"Service termine\r\n")
await loop.sock_sendall(c.socket,
f"L'autre carte du donneur est : {self.__dealer_cards[1]}\r\n".encode(
'utf8'))
await loop.sock_sendall(c.socket,
f"Vous avez {c.points} points\r\n".encode('utf8'))
max_player, max_player_index = self.max_player()
self.__logger.debug(f"Max player have {max_player} points")
while max_player is not None and len(self.__cards) > 0 and points_cartes(self.__dealer_cards) < max_player:
carte = self.__cards.pop(0)
for c in self.__players:
await loop.sock_sendall(c.socket, f"Le donneur a pioché : {carte}\r\n".encode('utf8'))
self.__dealer_cards.append(carte)
await self.send_victory_status(loop, max_player, max_player_index)
await data.remove_table(self.__table.name)
self.__logger.debug(f"Removed table \"{self.__table.name}\"")
async def run_game_loop(self, loop):
for i, joueur in enumerate(self.__players):
for j in self.__players:
await loop.sock_sendall(j.socket, f"C'est au tour de joueur {i + 1}\r\n".encode('utf8'))
playing = True
while playing:
if len(self.__cards) > 0 and joueur.points < 21:
await loop.sock_sendall(joueur.socket, b".\r\n")
request = parse_message((await loop.sock_recv(joueur.socket, 4096)).decode('utf8'))
if len(request) == 2 and request[0] == BiCommand.MORE:
if request[1] == "0":
playing = False
elif request[1] == "1":
carte = self.__cards.pop(0)
joueur.cartes.append(carte)
await loop.sock_sendall(joueur.socket,
f"Votre carte est : {carte}, il reste {len(self.__cards)} cartes\r\n".encode(
'utf8'))
else:
await loop.sock_sendall(joueur.socket, b"ERROR : INVALID COMMAND\r\n")
else:
await loop.sock_sendall(joueur.socket, b"ERROR : INVALID COMMAND\r\n")
else:
playing = False
async def send_victory_status(self, loop, max_player, max_player_index: List[int]):
points_donneur = points_cartes(self.__dealer_cards)
self.__logger.debug(f"Dealer have {points_donneur} points")
if (-1 if max_player is None else max_player) < points_donneur < 21:
for c in self.__players:
await loop.sock_sendall(c.socket, b"Vous avez perdu !\r\nEND\r\n")
c.socket.close()
else:
for i, c in enumerate(self.__players):
if i in max_player_index:
if max_player == points_donneur or len(max_player_index) > 1:
await loop.sock_sendall(c.socket, b"Egalite !\r\nEND\r\n")
else:
await loop.sock_sendall(c.socket, b"Vous avez gagne !\r\nEND\r\n")
else:
await loop.sock_sendall(c.socket, b"Vous avez perdu !\r\nEND\r\n")
c.socket.close()
def max_player(self) -> Tuple[Optional[int], Optional[List[int]]]:
m = None
mp = []
for i, j in enumerate(self.__players):
p = j.points
if p < 21:
if m is None or m < p:
m = p
mp = [i]
elif m == p:
mp.append(i)
return m, mp
class TableAlreadyExistException(Exception):
pass
class ConcurrentData:
def __init__(self) -> None:
self.__lock = asyncio.Lock()
self.__data: Dict[str, Game] = dict()
async def add_table(self, table_name: str, delay: int):
async with self.__lock:
if table_name in self.__data.keys():
raise TableAlreadyExistException()
else:
self.__data[table_name] = Game(Table(table_name, delay))
logging.debug("Created a new table called %s" % table_name)
async def has_table(self, table_name: str) -> bool:
async with self.__lock:
return table_name in self.__data.keys()
async def get_table(self, table_name: str) -> Game:
async with self.__lock:
return self.__data[table_name]
async def remove_table(self, table_name: str):
async with self.__lock:
self.__data.pop(table_name)