diff --git a/.gitignore b/.gitignore index 2fa3ae9..48af4fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ package-lock.json build/ -__pycache__ \ No newline at end of file +__pycache__ +restart.sh \ No newline at end of file diff --git a/backend/src/api.py b/backend/src/api.py index 3c8a19b..8e19376 100644 --- a/backend/src/api.py +++ b/backend/src/api.py @@ -7,10 +7,12 @@ 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']) @@ -60,65 +62,129 @@ def get_random_hand(): def get_hand_with_draw(): """Crée une partie et retourne le hand_player avec une tuile piochée""" global current_game - 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 current_game.hand_player.draw else None - - return jsonify({'hand': hand_data, 'draw': draw_data}) + 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 - 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) - - # Retourner la main mise à jour - 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 current_game.hand_player.draw else None - - return jsonify({'hand': hand_data, 'draw': draw_data}) + 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: + 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}) if __name__ == '__main__': app.run(debug=True, port=5000) diff --git a/backend/src/game/game.py b/backend/src/game/game.py index 121d54e..212a8fe 100644 --- a/backend/src/game/game.py +++ b/backend/src/game/game.py @@ -5,11 +5,57 @@ from .wall import * import random as rd class Game: - def __init__(self): + def __init__(self, wind=0 , yakus=False, red=False): self.wall = Wall() self.hand_player = self.wall.draw_hand() - self.bot_hands = [self.wall.draw_hand() for _ in range(3)] + 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.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 + + def new_turn(self, repeat=False): + 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 draw_tile(self, hand: Hand): tile = self.wall.draw() @@ -17,4 +63,47 @@ class Game: def put_tile(self, hand: Hand, id: int): self.wall.tiles.append(hand.discard_tile(id)) - self.wall.shuffle() \ No newline at end of file + self.wall.shuffle() + + def next_turn(self): + """Passe au tour suivant.""" + pass + + def get_state(self): + """Retourne l'état actuel de la partie (joueurs, scores, main, etc.).""" + hand_tiles = list(getattr(self.hand_player, 'tiles', [])) + if getattr(self.hand_player, 'draw', None) is not None: + hand_tiles = hand_tiles + [self.hand_player.draw] + return { + 'scores': list(self.scores), + 'winds': list(self.winds), + 'turn': int(self.turn), + 'riichi': list(self.riichi), + 'repeat': int(self.repeat), + '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 + ] + } + + 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.""" + pass + + def get_discards(self, player_id): + """Retourne les tuiles défaussées d'un joueur.""" + pass + + def get_possible_actions(self, player_id): + """Retourne les actions possibles pour un joueur.""" + pass \ No newline at end of file diff --git a/frontend/pages/riichi/chap5.html b/frontend/pages/riichi/chap5.html index 142c26a..790a12f 100644 --- a/frontend/pages/riichi/chap5.html +++ b/frontend/pages/riichi/chap5.html @@ -39,6 +39,22 @@ .speech-bubble.visible { display: flex; } + + [id^="hand"] { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + padding: 20px; + } + + #tiles-container { + display: flex; + justify-content: center; + align-items: flex-start; + gap: 10px; + padding: 20px; + } @@ -48,7 +64,9 @@
- + +
+ \ No newline at end of file