From 0c15a9e3cbc6153d9e5a259ccc6b4b65169ec555 Mon Sep 17 00:00:00 2001 From: Didictateur Date: Thu, 26 Feb 2026 21:12:56 +0100 Subject: [PATCH] removed backend --- Dockerfile | 28 +--- backend/src/api.py | 221 --------------------------- backend/src/game/__init__.py | 0 backend/src/game/discard.py | 0 backend/src/game/error.py | 15 -- backend/src/game/game.py | 210 -------------------------- backend/src/game/hand.py | 45 ------ backend/src/game/tile.py | 116 -------------- backend/src/game/value.py | 19 --- backend/src/game/wall.py | 23 --- backend/src/requirements.txt | 4 - docker-compose.yml | 13 -- frontend/pages/riichi/chap2.html | 251 ++++++++++++++----------------- frontend/pages/riichi/chap3.html | 26 ++-- frontend/pages/riichi/chap4.html | 26 ++-- frontend/pages/riichi/chap8.html | 33 ++-- index.html | 21 +-- nginx.conf | 9 +- 18 files changed, 160 insertions(+), 900 deletions(-) delete mode 100644 backend/src/api.py delete mode 100644 backend/src/game/__init__.py delete mode 100644 backend/src/game/discard.py delete mode 100644 backend/src/game/error.py delete mode 100644 backend/src/game/game.py delete mode 100644 backend/src/game/hand.py delete mode 100644 backend/src/game/tile.py delete mode 100644 backend/src/game/value.py delete mode 100644 backend/src/game/wall.py delete mode 100644 backend/src/requirements.txt diff --git a/Dockerfile b/Dockerfile index a0b134d..5dc78ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,2 @@ -FROM python:3.11-slim - -WORKDIR /app - -# Installer les dépendances Python -COPY backend/src/requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copier le code backend -COPY backend/src/ /app/ - -# Copier le frontend -COPY frontend /app/frontend -COPY components /app/components -COPY img /app/img -COPY index.html /app/ -COPY *.json /app/ -COPY vite.config.ts /app/ - -# Port Flask -EXPOSE 5000 - -# Définir PYTHONPATH pour que les imports relatifs fonctionnent -ENV PYTHONPATH=/app:$PYTHONPATH - -CMD ["gunicorn", "-w", "1", "-k", "gevent", "-b", "0.0.0.0:5000", "api:app"] +FROM nginx:alpine +# Ce Dockerfile n'est plus utilisé, le service est servi par nginx via docker-compose. diff --git a/backend/src/api.py b/backend/src/api.py deleted file mode 100644 index a17854e..0000000 --- a/backend/src/api.py +++ /dev/null @@ -1,221 +0,0 @@ -from flask import Flask, jsonify, request -from flask_cors import CORS - -from game.tile import * -from game.hand import * -from game.game import * -from game.error import * - -app = Flask(__name__) -import threading -CORS(app) - -tiles = Tile.generateAll() -current_game = None # Stockage de la partie actuelle -game_lock = threading.Lock() - -# ============= GET ================ -@app.route('/api/tiles', methods=['GET']) -def get_all_tiles(): - """Récupère toutes les tuiles""" - tiles_data = [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in tiles - ] - return jsonify({'tiles': tiles_data}) - -@app.route('/api/tiles/', methods=['GET']) -def get_tile(tile_id): - if tile_id < 0 or tile_id >= len(tiles): - return jsonify({'error': 'Tuile non trouvée'}), 404 - - tile = tiles[tile_id] - svg = f'{tile.get_img()}' - return jsonify({ - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': svg - }) - -@app.route('/api/random_hand', methods=['GET']) -def get_random_hand(): - """Récupère une main aléatoire de 13 tuiles""" - hand = Hand.random() - hand_data = [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in hand.tiles - ] - return jsonify({'hand': hand_data}) - -@app.route('/api/hand_with_draw', methods=['GET']) -def get_hand_with_draw(): - """Crée une partie et retourne le hand_player avec une tuile piochée""" - global current_game - with game_lock: - current_game = Game() - current_game.draw_tile(current_game.hand_player) - hand_data = [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in current_game.hand_player.tiles - ] - draw_data = { - 'id': current_game.hand_player.draw.id, - 'family': str(current_game.hand_player.draw.family), - 'value': str(current_game.hand_player.draw.value), - 'svg': current_game.hand_player.draw.get_img() - } if getattr(current_game.hand_player, 'draw', None) else None - return jsonify({'hand': hand_data, 'draw': draw_data}) - -@app.route('/api/discard', methods=['POST']) -def discard_tile(): - """Défausse une tuile de la main du joueur et pioche une nouvelle""" - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - data = request.json - tile_id = data.get('tile_id') - if tile_id is None: - return jsonify({'error': 'tile_id manquant'}), 400 - current_game.put_tile(current_game.hand_player, tile_id) - current_game.draw_tile(current_game.hand_player) - hand_data = [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in current_game.hand_player.tiles - ] - draw_data = { - 'id': current_game.hand_player.draw.id, - 'family': str(current_game.hand_player.draw.family), - 'value': str(current_game.hand_player.draw.value), - 'svg': current_game.hand_player.draw.get_img() - } if getattr(current_game.hand_player, 'draw', None) else None - return jsonify({'hand': hand_data, 'draw': draw_data}) - -# ============== POST ================ -# ============== GAME MANAGEMENT ================ - -# Démarrer une nouvelle partie -@app.route('/api/game/start', methods=['POST']) -def start_game(): - global current_game - with game_lock: - current_game = Game() - return jsonify({'status': 'started', 'state': current_game.get_state()}) - -# Récupérer l'état de la partie -@app.route('/api/game/state', methods=['GET']) -def game_state(): - global current_game - with game_lock: - print(f"[API] /api/game/state called. current_game is {'None' if current_game is None else 'OK'}") - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - return jsonify({'state': current_game.get_state()}) - -# Passer au tour suivant -@app.route('/api/game/next_turn', methods=['POST']) -def game_next_turn(): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - current_game.next_turn() - return jsonify({'state': current_game.get_state()}) - -# Déclarer Riichi -@app.route('/api/game/riichi', methods=['POST']) -def game_declare_riichi(): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - data = request.json - player_id = data.get('player_id') - if player_id is None: - return jsonify({'error': 'player_id manquant'}), 400 - current_game.declare_riichi(player_id) - return jsonify({'state': current_game.get_state()}) - -# Récupérer la main d'un joueur -@app.route('/api/game/hand/', methods=['GET']) -def game_get_hand(player_id): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - hand = current_game.get_hand(player_id) - return jsonify({'hand': hand}) - -# Récupérer les défausses d'un joueur -@app.route('/api/game/discards/', methods=['GET']) -def game_get_discards(player_id): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - discards = current_game.get_discards(player_id) - return jsonify({'discards': discards}) - -# Récupérer les actions possibles pour un joueur -@app.route('/api/game/actions/', methods=['GET']) -def game_get_actions(player_id): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - actions = current_game.get_possible_actions(player_id) - return jsonify({'actions': actions}) - -@app.route('/api/play_draw', methods=['POST']) -def play_draw_api(): - global current_game - with game_lock: - print('[API] /api/play_draw called') - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - current_game.play_draw() - return jsonify({'state': current_game.get_state()}) - -@app.route('/api/discard_tile', methods=['POST']) -def api_discard_tile(): - global current_game - with game_lock: - if current_game is None: - return jsonify({'error': 'Aucune partie en cours'}), 400 - data = request.json - tile_id = data.get('tile_id') - if tile_id is None: - return jsonify({'error': 'tile_id manquant'}), 400 - discarded = current_game.discard_tile(tile_id) - if not discarded: - return jsonify({'error': 'Tuile non trouvée ou non défaussée'}), 400 - return jsonify({'state': current_game.get_state(), 'discarded': { - 'id': discarded.id, - 'family': str(discarded.family), - 'value': str(discarded.value), - 'svg': discarded.get_img() - }}) - -if __name__ == '__main__': - app.run(debug=True, port=5000) diff --git a/backend/src/game/__init__.py b/backend/src/game/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/src/game/discard.py b/backend/src/game/discard.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/src/game/error.py b/backend/src/game/error.py deleted file mode 100644 index 3ee77e3..0000000 --- a/backend/src/game/error.py +++ /dev/null @@ -1,15 +0,0 @@ -class TileError(Exception): - """Exception de base pour les erreurs de tuiles""" - pass - -class InvalidFamilyError(TileError): - """Famille de tuile invalide""" - pass - -class InvalidValueError(TileError): - """Valeur de tuile invalide""" - pass - -class GameError(Exception): - """Exception de base pour les erreurs de jeu""" - pass \ No newline at end of file diff --git a/backend/src/game/game.py b/backend/src/game/game.py deleted file mode 100644 index 4425a75..0000000 --- a/backend/src/game/game.py +++ /dev/null @@ -1,210 +0,0 @@ -from __future__ import annotations -from .hand import * -from .wall import * - -import random as rd - -class Game: - def __init__(self, wind=0 , yakus=False, red=False): - self.wall = Wall() - - self.hand_player = self.wall.draw_hand() - self.discard_player = [] - self.hidden_discard_player = [] - self.hands_bots = [self.wall.draw_hand() for _ in range(3)] - self.discards_bots = [[] for _ in range(3)] - self.hidden_discards_bots = [[] for _ in range(3)] - - self.has_draw = False - self.has_played = True - - self.scores = [25000] * 4 - - self.winds = [ - wind % 4, # player - (wind+1) % 4, # right of player - (wind + 2) % 4, # front of player - (wind +3) % 4 # left of player - ] - - self.initial_est = (-wind) % 4 - self.turn = self.winds.index(0) - - self.riichi = [False] * 4 - self.repeat = 0 - - self.end = False - - # L'Est pioche une tuile dès le début - if self.turn == 0: - tile = self.wall.draw() - self.hand_player.draw_tile(tile) - self.has_draw = True - else: - tile = self.wall.draw() - self.hands_bots[self.turn-1].draw_tile(tile) - - def new_turn(self, repeat=False): - """Nouvelle manche""" - if repeat: - self.repeat += 1 - else: - self.repeat = 0 - self.turn = (self.turn + 1) % 4 - if self.turn == self.initial_est: - self.end = True - for i in range(4): - self.winds[self.turn + i] = i - - self.turn = self.winds.index(0) - - self.riichi = [False] * 4 - self.repeat = 0 - - self.wall = Wall() - - self.hand_player = self.wall.draw_hand() - self.discard_player = [] - self.hidden_discard_player = [] - self.hands_bots = [self.wall.draw_hand() for _ in range(3)] - self.discards_bots = [[] for _ in range(3)] - self.hidden_discards_bots = [[] for _ in range(3)] - - def play_draw(self): - print(f"[Game] play_draw called: has_draw={self.has_draw}, turn={self.turn}") - if not self.has_draw: - if self.turn == 0: - tile = self.wall.draw() - self.hand_player.draw_tile(tile) - self.has_draw = True - print(f"Change has_draw to {self.has_draw}") - - def draw_tile(self, hand: Hand): - tile = self.wall.draw() - hand.draw_tile(tile) - - def put_tile(self, hand: Hand, id: int): - self.wall.tiles.append(hand.discard_tile(id)) - self.wall.shuffle() - - def next_turn(self): - """Passe au joueur suivant.""" - # TODO: check for pon/chii - self.turn = (self.turn + 1) % 4 - self.has_draw = False - - if self.turn != 0: # a bot has to play - self.bot_turn() - else: - self.play_draw() - - def get_state(self): - """Retourne l'état actuel de la partie (joueurs, scores, main, etc.).""" - hand_tiles = list(getattr(self.hand_player, 'tiles', [])) - return { - 'scores': list(self.scores), - 'winds': list(self.winds), - 'turn': int(self.turn), - 'riichi': list(self.riichi), - 'repeat': int(self.repeat), - 'has_draw': bool(self.has_draw), - 'end': bool(self.end), - 'hand_player': [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in hand_tiles - ], - 'draw': ( - { - 'id': self.hand_player.draw.id, - 'family': str(self.hand_player.draw.family), - 'value': str(self.hand_player.draw.value), - 'svg': self.hand_player.draw.get_img() - } - if self.hand_player.draw is not None else None - ) - } - - def declare_riichi(self, player_id): - """Déclare Riichi pour un joueur.""" - pass - - def get_hand(self, player_id): - """Retourne la main d'un joueur (0 = joueur humain, 1-3 = bots).""" - if player_id == 0: - hand = list(getattr(self.hand_player, 'tiles', [])) - if getattr(self.hand_player, 'draw', None) is not None: - hand = hand + [self.hand_player.draw] - elif 1 <= player_id <= 3: - hand_obj = self.hands_bots[player_id - 1] - hand = list(getattr(hand_obj, 'tiles', [])) - if getattr(hand_obj, 'draw', None) is not None: - hand = hand + [hand_obj.draw] - else: - return [] - return [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in hand - ] - - def get_discards(self, player_id: int): - if player_id == 0: - return [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in self.discard_player - ] - elif 1 <= player_id <= 3: - return [ - { - 'id': tile.id, - 'family': str(tile.family), - 'value': str(tile.value), - 'svg': tile.get_img() - } - for tile in self.discards_bots[player_id - 1] - ] - else: - return [] - - def get_possible_actions(self, player_id): - """Retourne les actions possibles pour un joueur.""" - pass - - def bot_turn(self): - # Joue un tour de bot sans sleep - if self.turn != 0: - # Pioche une tuile pour le bot - tile = self.wall.draw() - self.hands_bots[self.turn - 1].draw_tile(tile) - # Défausse une tuile aléatoire - hand = self.hands_bots[self.turn - 1] - n = rd.randint(0, len(hand.tiles) - 1) - tile_to_discard = hand.tiles[n] - discarded = hand.discard_tile(tile_to_discard.id) - if discarded: - self.discards_bots[self.turn - 1].append(discarded) - # Passe au joueur suivant - self.turn = (self.turn + 1) % 4 - self.has_draw = True if self.turn == 0 else False - - def discard_tile(self, tile_id: int): - # Défausse la tuile (de la main ou de la tuile piochée) - if self.turn == 0 and self.has_draw: - discarded = self.hand_player.discard_tile(tile_id) - if discarded: - self.discard_player.append(discarded) - self.next_turn() \ No newline at end of file diff --git a/backend/src/game/hand.py b/backend/src/game/hand.py deleted file mode 100644 index 3ee6a5f..0000000 --- a/backend/src/game/hand.py +++ /dev/null @@ -1,45 +0,0 @@ -from __future__ import annotations -from .tile import * - -import random as rd - -class Hand: - def __init__(self, tiles: list[Tile], draw: Tile|None = None): - self.tiles: list[Tile] = tiles - self.draw: Tile|None = draw - - def sort(self) -> None: - self.tiles.sort() - - def fold(self) -> None: - if self.draw is not None: - self.tiles.append(self.draw) - self.draw = None - self.sort() - - def draw_tile(self, tile: Tile) -> None: - self.draw = tile - - def discard_tile(self, id: int) -> Tile|None: - if self.draw is not None and self.draw.id == id: - draw = self.draw - self.draw = None - self.fold() - self.sort() - return draw - for i, tile in enumerate(self.tiles): - if tile.id == id: - t = self.tiles.pop(i) - self.fold() - self.sort() - return t - self.sort() - return None - - @staticmethod - def random() -> Hand: - tiles = Tile.generateAll() - rd.shuffle(tiles) - hand = Hand(tiles[:13]) - hand.sort() - return hand \ No newline at end of file diff --git a/backend/src/game/tile.py b/backend/src/game/tile.py deleted file mode 100644 index 4749262..0000000 --- a/backend/src/game/tile.py +++ /dev/null @@ -1,116 +0,0 @@ -from __future__ import annotations -from game.error import * -from game.value import * -from functools import total_ordering - -@total_ordering -class Tile: - def __init__(self, family, value, id): - self.family = family - self.value = value - self.id = id - self.hidden = False - self.img = None - - self.img_back = "Back.svg" - self.img_front = "Front.svg" - self.img_shadow = "Gray.svg" - if self.family == Family.MAN: - if self.value in range(1, 10): - self.img_symbol = f"Man{self.value}.svg" - else: - raise InvalidValueError(f"Invalide value: {self.value}") - elif self.family == Family.PIN: - if self.value in range(1, 10): - self.img_symbol = f"Pin{self.value}.svg" - else: - raise InvalidValueError(f"Invalide value: {self.value}") - elif self.family == Family.SOU: - if self.value in range(1, 10): - self.img_symbol = f"Sou{self.value}.svg" - else: - raise InvalidValueError(f"Invalide value: {self.value}") - elif self.family == Family.WIND: - if self.value == Wind.EAST: - self.img_symbol = "Ton.svg" - elif self.value == Wind.SOUTH: - self.img_symbol = "Nan.svg" - elif self.value == Wind.WEST: - self.img_symbol = "Shaa.svg" - elif self.value == Wind.NORTH: - self.img_symbol = "Pei.svg" - else: - raise InvalidValueError(f"Invalide value: {self.value}") - elif self.family == Family.DRAGON: - if self.value == Dragon.RED: - self.img_symbol = "Chun.svg" - elif self.value == Dragon.GREEN: - self.img_symbol = "Hatsu.svg" - elif self.value == Dragon.WHITE: - self.img_symbol = "Haku.svg" - else: - raise InvalidValueError(f"Invalide value: {self.value}") - else: - raise InvalidFamilyError(f"Invalide family: {self.family}") - - def get_img(self): - if self.img == None: - shadow = self._load_svg(self.img_shadow, x=10, y=10) - front = self._load_svg(self.img_front, x=0, y=0) - symbol = self._load_svg(self.img_symbol, x=12, y=20, scale=0.9) - - ratio = 400 / 300 - width = 60 - self.img = f"""{shadow}{front}{symbol}""" - return self.img - - @staticmethod - def _load_svg(filename, x=0, y=0, scale=1): - try: - with open(f'img/Regular/{filename}', 'r', encoding='utf-8') as f: - svg_content = f.read() - return f'{svg_content}' - except FileNotFoundError: - raise InvalidValueError(f"SVG non trouvé: {filename}") - - def __eq__(self, other: Tile): - return self.value == other.value and self.family == other.family - - def __lt__(self, other: Tile): - if self.family.value != other.family.value: - return self.family.value < other.family.value - if hasattr(self.value, 'value'): - return self.value.value < other.value.value - return self.value < other.value - - @staticmethod - def generateAll(): - tiles = [] - id = 0 - for _ in range(4): - # man - for i in range(1, 10): - tiles.append(Tile(Family.MAN, i, id)) - id += 1 - - # pin - for i in range(1, 10): - tiles.append(Tile(Family.PIN, i, id)) - id += 1 - - # sou - for i in range(1, 10): - tiles.append(Tile(Family.SOU, i, id)) - id += 1 - - # wind - for wind in [Wind.EAST, Wind.SOUTH, Wind.WEST, Wind.NORTH]: - tiles.append(Tile(Family.WIND, wind, id)) - id += 1 - - # dragon - for dragon in [Dragon.RED, Dragon.GREEN, Dragon.WHITE]: - tiles.append(Tile(Family.DRAGON, dragon, id)) - id += 1 - - return tiles \ No newline at end of file diff --git a/backend/src/game/value.py b/backend/src/game/value.py deleted file mode 100644 index b5a607b..0000000 --- a/backend/src/game/value.py +++ /dev/null @@ -1,19 +0,0 @@ -from enum import Enum - -class Family(Enum): - MAN = 0 - PIN = 1 - SOU = 2 - WIND = 3 - DRAGON = 4 - -class Wind(Enum): - EAST = 0 - SOUTH = 1 - WEST = 2 - NORTH = 3 - -class Dragon(Enum): - RED = 0 - GREEN = 1 - WHITE = 2 \ No newline at end of file diff --git a/backend/src/game/wall.py b/backend/src/game/wall.py deleted file mode 100644 index b353bdd..0000000 --- a/backend/src/game/wall.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations -from .tile import * -from .hand import * - -import random as rd - -class Wall: - def __init__(self): - self.tiles = Tile.generateAll() - self.shuffle() - - def shuffle(self): - rd.shuffle(self.tiles) - - def draw(self) -> Tile: - if len(self.tiles) == 0: - raise Exception("No more tiles in the wall") - return self.tiles.pop() - - def draw_hand(self) -> Hand: - hand_tiles = [self.draw() for _ in range(13)] - hand_tiles.sort() - return Hand(hand_tiles) \ No newline at end of file diff --git a/backend/src/requirements.txt b/backend/src/requirements.txt deleted file mode 100644 index 75a2ad6..0000000 --- a/backend/src/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==3.0.0 -flask-cors==4.0.0 -gunicorn==21.2.0 -gevent>=1.4 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 0ae4d9b..8b85fbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,4 @@ services: - riichi: - build: . - ports: - - "5000:5000" - environment: - - FLASK_ENV=production - volumes: - - ./frontend:/app/frontend - - ./backend/src:/app/backend/src - restart: unless-stopped - nginx: image: nginx:latest ports: @@ -20,6 +9,4 @@ services: - ${PWD}/index.html:/usr/share/nginx/html/index.html:ro - ${PWD}/components:/usr/share/nginx/html/components - ${PWD}/img:/usr/share/nginx/html/img - depends_on: - - riichi restart: unless-stopped diff --git a/frontend/pages/riichi/chap2.html b/frontend/pages/riichi/chap2.html index a4d6b57..3b3a317 100644 --- a/frontend/pages/riichi/chap2.html +++ b/frontend/pages/riichi/chap2.html @@ -134,7 +134,7 @@
- + \ No newline at end of file diff --git a/frontend/pages/riichi/chap3.html b/frontend/pages/riichi/chap3.html index af6a541..01a1945 100644 --- a/frontend/pages/riichi/chap3.html +++ b/frontend/pages/riichi/chap3.html @@ -85,6 +85,7 @@
+ + + \ No newline at end of file diff --git a/index.html b/index.html index f699e40..72e1d8c 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,7 @@
+