update game and display player's hand

This commit is contained in:
Didictateur 2026-02-25 16:14:57 +01:00
parent 8964165e78
commit 75d8cba654
4 changed files with 252 additions and 57 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ node_modules/
package-lock.json package-lock.json
build/ build/
__pycache__ __pycache__
restart.sh

View file

@ -7,10 +7,12 @@ from game.game import *
from game.error import * from game.error import *
app = Flask(__name__) app = Flask(__name__)
import threading
CORS(app) CORS(app)
tiles = Tile.generateAll() tiles = Tile.generateAll()
current_game = None # Stockage de la partie actuelle current_game = None # Stockage de la partie actuelle
game_lock = threading.Lock()
# ============= GET ================ # ============= GET ================
@app.route('/api/tiles', methods=['GET']) @app.route('/api/tiles', methods=['GET'])
@ -60,65 +62,129 @@ def get_random_hand():
def get_hand_with_draw(): def get_hand_with_draw():
"""Crée une partie et retourne le hand_player avec une tuile piochée""" """Crée une partie et retourne le hand_player avec une tuile piochée"""
global current_game global current_game
current_game = Game() with game_lock:
current_game.draw_tile(current_game.hand_player) current_game = Game()
current_game.draw_tile(current_game.hand_player)
hand_data = [ hand_data = [
{ {
'id': tile.id, 'id': tile.id,
'family': str(tile.family), 'family': str(tile.family),
'value': str(tile.value), 'value': str(tile.value),
'svg': tile.get_img() 'svg': tile.get_img()
} }
for tile in current_game.hand_player.tiles for tile in current_game.hand_player.tiles
] ]
draw_data = {
draw_data = { 'id': current_game.hand_player.draw.id,
'id': current_game.hand_player.draw.id, 'family': str(current_game.hand_player.draw.family),
'family': str(current_game.hand_player.draw.family), 'value': str(current_game.hand_player.draw.value),
'value': str(current_game.hand_player.draw.value), 'svg': current_game.hand_player.draw.get_img()
'svg': current_game.hand_player.draw.get_img() } if getattr(current_game.hand_player, 'draw', None) else None
} if current_game.hand_player.draw else None return jsonify({'hand': hand_data, 'draw': draw_data})
return jsonify({'hand': hand_data, 'draw': draw_data})
@app.route('/api/discard', methods=['POST']) @app.route('/api/discard', methods=['POST'])
def discard_tile(): def discard_tile():
"""Défausse une tuile de la main du joueur et pioche une nouvelle""" """Défausse une tuile de la main du joueur et pioche une nouvelle"""
global current_game global current_game
if current_game is None: with game_lock:
return jsonify({'error': 'Aucune partie en cours'}), 400 if current_game is None:
return jsonify({'error': 'Aucune partie en cours'}), 400
data = request.json data = request.json
tile_id = data.get('tile_id') tile_id = data.get('tile_id')
if tile_id is None:
if tile_id is None: return jsonify({'error': 'tile_id manquant'}), 400
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)
current_game.put_tile(current_game.hand_player, tile_id) hand_data = [
current_game.draw_tile(current_game.hand_player) {
'id': tile.id,
# Retourner la main mise à jour 'family': str(tile.family),
hand_data = [ 'value': str(tile.value),
{ 'svg': tile.get_img()
'id': tile.id, }
'family': str(tile.family), for tile in current_game.hand_player.tiles
'value': str(tile.value), ]
'svg': tile.get_img() draw_data = {
} 'id': current_game.hand_player.draw.id,
for tile in current_game.hand_player.tiles 'family': str(current_game.hand_player.draw.family),
] 'value': str(current_game.hand_player.draw.value),
'svg': current_game.hand_player.draw.get_img()
draw_data = { } if getattr(current_game.hand_player, 'draw', None) else None
'id': current_game.hand_player.draw.id, return jsonify({'hand': hand_data, 'draw': draw_data})
'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})
# ============== POST ================ # ============== 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/<int:player_id>', 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/<int:player_id>', 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/<int:player_id>', 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__': if __name__ == '__main__':
app.run(debug=True, port=5000) app.run(debug=True, port=5000)

View file

@ -5,11 +5,57 @@ from .wall import *
import random as rd import random as rd
class Game: class Game:
def __init__(self): def __init__(self, wind=0 , yakus=False, red=False):
self.wall = Wall() self.wall = Wall()
self.hand_player = self.wall.draw_hand() 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): def draw_tile(self, hand: Hand):
tile = self.wall.draw() tile = self.wall.draw()
@ -18,3 +64,46 @@ class Game:
def put_tile(self, hand: Hand, id: int): def put_tile(self, hand: Hand, id: int):
self.wall.tiles.append(hand.discard_tile(id)) self.wall.tiles.append(hand.discard_tile(id))
self.wall.shuffle() 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

View file

@ -39,6 +39,22 @@
.speech-bubble.visible { .speech-bubble.visible {
display: flex; 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;
}
</style> </style>
</head> </head>
<body> <body>
@ -49,6 +65,8 @@
</div> </div>
<div id="mascotte"></div> <div id="mascotte"></div>
<div id="hand-0"></div>
<script src="/frontend/js/fr/texts.js"></script> <script src="/frontend/js/fr/texts.js"></script>
<script> <script>
// Charger le menu // Charger le menu
@ -69,6 +87,27 @@
? '<img class="explaining" src="/img/tilineau/explaining.png" alt="Tilineau">' ? '<img class="explaining" src="/img/tilineau/explaining.png" alt="Tilineau">'
: '<img src="/img/tilineau/idle.png" alt="Tilineau">'; : '<img src="/img/tilineau/idle.png" alt="Tilineau">';
}); });
// Initialiser une partie au chargement du chapitre 5
fetch('/api/game/start', { method: 'POST' })
.then(r => r.json())
.then(data => {
console.log('Réponse API', data);
if (data.state && data.state.hand_player) {
displayHand(data.state.hand_player, 'hand-0');
}
})
.catch(e => console.error('Erreur initialisation partie', e));
function displayHand(hand, displayId) {
const display = document.getElementById(displayId);
display.innerHTML = '';
hand.forEach(tile => {
const tileDiv = document.createElement('div');
tileDiv.innerHTML = tile.svg;
display.appendChild(tileDiv);
});
}
</script> </script>
</body> </body>
</html> </html>