some api interaction

This commit is contained in:
Didictateur 2026-02-26 19:03:05 +01:00
parent 6f7708d368
commit d526332658
5 changed files with 127 additions and 23 deletions

View file

@ -23,4 +23,4 @@ EXPOSE 5000
# Définir PYTHONPATH pour que les imports relatifs fonctionnent # Définir PYTHONPATH pour que les imports relatifs fonctionnent
ENV PYTHONPATH=/app:$PYTHONPATH 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"]

View file

@ -197,5 +197,25 @@ def play_draw_api():
current_game.play_draw() current_game.play_draw()
return jsonify({'state': current_game.get_state()}) 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__': if __name__ == '__main__':
app.run(debug=True, port=5000) app.run(debug=True, port=5000)

View file

@ -35,7 +35,17 @@ class Game:
self.end = False 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): def new_turn(self, repeat=False):
"""Nouvelle manche"""
if repeat: if repeat:
self.repeat += 1 self.repeat += 1
else: else:
@ -78,8 +88,15 @@ class Game:
self.wall.shuffle() self.wall.shuffle()
def next_turn(self): def next_turn(self):
"""Passe au tour suivant.""" """Passe au joueur suivant."""
pass # 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): def get_state(self):
"""Retourne l'état actuel de la partie (joueurs, scores, main, etc.).""" """Retourne l'état actuel de la partie (joueurs, scores, main, etc.)."""
@ -139,10 +156,55 @@ class Game:
for tile in hand for tile in hand
] ]
def get_discards(self, player_id): def get_discards(self, player_id: int):
"""Retourne les tuiles défaussées d'un joueur.""" if player_id == 0:
pass 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): def get_possible_actions(self, player_id):
"""Retourne les actions possibles pour un joueur.""" """Retourne les actions possibles pour un joueur."""
pass 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()

View file

@ -1,3 +1,4 @@
Flask==3.0.0 Flask==3.0.0
flask-cors==4.0.0 flask-cors==4.0.0
gunicorn==21.2.0 gunicorn==21.2.0
gevent>=1.4

View file

@ -145,25 +145,9 @@
// Fonction pour mettre à jour l'état du jeu // Fonction pour mettre à jour l'état du jeu
function updateGameState() { function updateGameState() {
if (isDrawing) {
console.log('[JS] draw already in progress, skip');
return;
}
fetch('/api/game/state') fetch('/api/game/state')
.then(r => r.json()) .then(r => r.json())
.then(data => { .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)) { if (data.state && Array.isArray(data.state.hand_player)) {
window.lastDrawTile = data.state.draw ? data.state.draw.svg : null; window.lastDrawTile = data.state.draw ? data.state.draw.svg : null;
displayHand(data.state.hand_player, 'hand-0'); displayHand(data.state.hand_player, 'hand-0');
@ -221,6 +205,8 @@
tileDiv.innerHTML = '<svg viewBox="0 0 310 410" width="60" height="80"><image href="/img/Regular/Back.svg" width="310" height="410"/></svg>'; tileDiv.innerHTML = '<svg viewBox="0 0 310 410" width="60" height="80"><image href="/img/Regular/Back.svg" width="310" height="410"/></svg>';
} else { } else {
tileDiv.innerHTML = tile.svg; tileDiv.innerHTML = tile.svg;
tileDiv.style.cursor = 'pointer';
tileDiv.addEventListener('click', () => discard_tile(tile.id));
} }
display.appendChild(tileDiv); 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 // Polling pour récupérer la main d'un bot
function fetchBotHandWithRetry(id, tries = 8, delay = 500) { function fetchBotHandWithRetry(id, tries = 8, delay = 500) {
fetch(`/api/game/hand/${id}`) fetch(`/api/game/hand/${id}`)