diff --git a/Dockerfile b/Dockerfile index de36a4e..a0b134d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,4 +23,4 @@ EXPOSE 5000 # Définir PYTHONPATH pour que les imports relatifs fonctionnent ENV PYTHONPATH=/app:$PYTHONPATH -CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:5000", "api:app"] +CMD ["gunicorn", "-w", "1", "-k", "gevent", "-b", "0.0.0.0:5000", "api:app"] diff --git a/backend/src/api.py b/backend/src/api.py index 074c055..a17854e 100644 --- a/backend/src/api.py +++ b/backend/src/api.py @@ -197,5 +197,25 @@ def play_draw_api(): 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/game.py b/backend/src/game/game.py index 524987f..4425a75 100644 --- a/backend/src/game/game.py +++ b/backend/src/game/game.py @@ -35,7 +35,17 @@ class Game: 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: @@ -78,8 +88,15 @@ class Game: self.wall.shuffle() def next_turn(self): - """Passe au tour suivant.""" - pass + """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.).""" @@ -139,10 +156,55 @@ class Game: for tile in hand ] - def get_discards(self, player_id): - """Retourne les tuiles défaussées d'un joueur.""" - pass + 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 \ No newline at end of file + 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/requirements.txt b/backend/src/requirements.txt index 6119aea..75a2ad6 100644 --- a/backend/src/requirements.txt +++ b/backend/src/requirements.txt @@ -1,3 +1,4 @@ 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/frontend/pages/riichi/chap5.html b/frontend/pages/riichi/chap5.html index e1cfaaf..4e48d90 100644 --- a/frontend/pages/riichi/chap5.html +++ b/frontend/pages/riichi/chap5.html @@ -145,25 +145,9 @@ // Fonction pour mettre à jour l'état du jeu function updateGameState() { - if (isDrawing) { - console.log('[JS] draw already in progress, skip'); - return; - } fetch('/api/game/state') .then(r => r.json()) .then(data => { - if (data.state && data.state.has_draw === false) { - console.log('[JS] has_draw is false, will try draw in 10s'); - isDrawing = true; - setTimeout(() => { - fetch('/api/play_draw', { method: 'POST' }) - .then(() => { - isDrawing = false; - console.log('[JS] draw finished, polling resumes'); - }); - }, 500); - return; - } if (data.state && Array.isArray(data.state.hand_player)) { window.lastDrawTile = data.state.draw ? data.state.draw.svg : null; displayHand(data.state.hand_player, 'hand-0'); @@ -221,6 +205,8 @@ tileDiv.innerHTML = ''; } else { tileDiv.innerHTML = tile.svg; + tileDiv.style.cursor = 'pointer'; + tileDiv.addEventListener('click', () => discard_tile(tile.id)); } display.appendChild(tileDiv); }); @@ -234,6 +220,41 @@ } } + function discard_tile(tileId) { + console.log('[JS] discard_tile', tileId); + fetch('/api/discard_tile', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tile_id: tileId }) + }) + .then(r => r.json()) + .then(data => { + console.log('[JS] discard response', data); + if (data.state && Array.isArray(data.state.hand_player)) { + window.lastDrawTile = data.state.draw ? data.state.draw.svg : null; + displayHand(data.state.hand_player, 'hand-0'); + } + // Affiche la défausse du joueur + if (data.state) { + fetch('/api/game/discards/0') + .then(r => r.json()) + .then(discardsData => { + displayDiscards(discardsData.discards, 'discards-0'); + }); + } + }); + } + + function displayDiscards(discards, displayId) { + const display = document.getElementById(displayId); + display.innerHTML = ''; + discards.forEach(tile => { + const tileDiv = document.createElement('div'); + tileDiv.innerHTML = tile.svg; + display.appendChild(tileDiv); + }); + } + // Polling pour récupérer la main d'un bot function fetchBotHandWithRetry(id, tries = 8, delay = 500) { fetch(`/api/game/hand/${id}`)