exemples de mains dans chap 2
This commit is contained in:
parent
c471c84239
commit
d23d6a24f7
10 changed files with 571 additions and 2 deletions
|
|
@ -2,6 +2,8 @@ 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__)
|
||||
|
|
@ -38,6 +40,46 @@ def get_tile(tile_id):
|
|||
'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"""
|
||||
game = Game()
|
||||
game.draw_tile(game.hand_player)
|
||||
|
||||
hand_data = [
|
||||
{
|
||||
'id': tile.id,
|
||||
'family': str(tile.family),
|
||||
'value': str(tile.value),
|
||||
'svg': tile.get_img()
|
||||
}
|
||||
for tile in game.hand_player.tiles
|
||||
]
|
||||
|
||||
draw_data = {
|
||||
'id': game.hand_player.draw.id,
|
||||
'family': str(game.hand_player.draw.family),
|
||||
'value': str(game.hand_player.draw.value),
|
||||
'svg': game.hand_player.draw.get_img()
|
||||
} if game.hand_player.draw else None
|
||||
|
||||
return jsonify({'hand': hand_data, 'draw': draw_data})
|
||||
|
||||
# ============== POST ================
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
19
backend/src/game/game.py
Normal file
19
backend/src/game/game.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from .hand import *
|
||||
from .wall import *
|
||||
|
||||
import random as rd
|
||||
|
||||
class Game:
|
||||
def __init__(self):
|
||||
self.wall = Wall()
|
||||
|
||||
self.hand_player = self.wall.draw_hand()
|
||||
self.bot_hands = [self.wall.draw_hand() for _ in range(3)]
|
||||
|
||||
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()
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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
|
||||
return draw
|
||||
for i, tile in enumerate(self.tiles):
|
||||
if tile.id == id:
|
||||
return self.tiles.pop(i)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def random() -> Hand:
|
||||
tiles = Tile.generateAll()
|
||||
rd.shuffle(tiles)
|
||||
hand = Hand(tiles[:13])
|
||||
hand.sort()
|
||||
return hand
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
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
|
||||
|
|
@ -74,7 +76,11 @@ class Tile:
|
|||
return self.value == other.value and self.family == other.family
|
||||
|
||||
def __lt__(self, other: Tile):
|
||||
return (self.family < other.family) or (self.family == other.family and self.value < other.value)
|
||||
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():
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
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)
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<a href="#">Riichi</a>
|
||||
<ul class="submenu">
|
||||
<li><a href="/frontend/pages/chap1.html">Chapitre 1 : Les tuiles</a></li>
|
||||
<li><a href="">Chapitre 2 : La main</a></li>
|
||||
<li><a href="/frontend/pages/chap2.html">Chapitre 2 : La main</a></li>
|
||||
<li><a href="">Chapitre 3 : Les groupes</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
|
|||
102
frontend/css/hand.css
Normal file
102
frontend/css/hand.css
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/* Hand Display Styles */
|
||||
|
||||
.hand-display {
|
||||
margin-bottom: 30px;
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.hand-label {
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hand-tiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(13, minmax(0, 1fr));
|
||||
gap: 3px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hand-tile-item {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Hand container - gère l'affichage des tuiles et du draw */
|
||||
.hand-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hand-tile-item.draw-tile {
|
||||
/* Le draw s'affiche simplement à droite grâce à flex */
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* Orientation variations */
|
||||
.hand-display.vertical {
|
||||
grid-template-columns: 100px 1fr;
|
||||
}
|
||||
|
||||
.hand-display.horizontal {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hand-display.horizontal .hand-label {
|
||||
grid-column: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hand-display.horizontal .hand-tiles {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
/* Position variations */
|
||||
.hand-display.left-label {
|
||||
grid-template-columns: 100px 1fr;
|
||||
}
|
||||
|
||||
.hand-display.top-label {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hand-display.top-label .hand-label {
|
||||
grid-column: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hand-display.top-label .hand-tiles {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
/* Tilt variations */
|
||||
.hand-display.tilt-left {
|
||||
transform: rotate(90deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hand-display.tilt-right {
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hand-display.tilt-upside-down {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.hand-display.tilt-none {
|
||||
transform: rotate(0deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
|
@ -32,6 +32,19 @@ const texts = {
|
|||
<br>
|
||||
Les vents et dragons quant à eux n'ont pas de numéro. On les appelle<br>
|
||||
les honneurs, et ne sont caractérisés que par leur nom.
|
||||
`,
|
||||
chap2:`
|
||||
Les joueurs ne se contentent pas de regarder les tuiles. Ils<br>
|
||||
constituent une main composée de 13 tuiles.<br>
|
||||
<br>
|
||||
Au début de leur tour, ils commencent par récupérer une<br>
|
||||
tuile, soit du mur (la pioche), soit de la défausse d'un<br>
|
||||
adversaire (nous verrons les détails plus tard).<br>
|
||||
<br>
|
||||
Après avoir récupéré une tuile, le joueur doit se défausser<br>
|
||||
d'une tuile de sa main, pour revenir à 13 tuiles. C'est à ce<br>
|
||||
moment que les autres joueurs peuvent réagir à la tuile<br>
|
||||
défaussée, en la récupérant pour compléter leur propre main.
|
||||
`
|
||||
}
|
||||
};
|
||||
|
|
|
|||
64
frontend/js/hand.js
Normal file
64
frontend/js/hand.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Hand Display Object
|
||||
* Affiche une main de Riichi avec options de position et orientation
|
||||
*/
|
||||
|
||||
class Hand {
|
||||
constructor(options = {}) {
|
||||
this.label = options.label || 'Main';
|
||||
this.position = options.position || 'left-label'; // 'left-label' or 'top-label'
|
||||
this.tilt = options.tilt || 'none'; // 'none', 'left' (90deg), 'right' (-90deg), 'upside-down' (180deg)
|
||||
this.containerId = options.containerId || 'tiles-display';
|
||||
this.apiUrl = options.apiUrl || 'http://localhost:5000/api/random_hand';
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une main aléatoire et l'affiche
|
||||
*/
|
||||
async render() {
|
||||
try {
|
||||
const response = await fetch(this.apiUrl);
|
||||
const data = await response.json();
|
||||
this.displayHand(data.hand);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération de la main:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche la main dans le DOM
|
||||
* @param {Array} tiles - Tableau des tuiles à afficher
|
||||
*/
|
||||
displayHand(tiles) {
|
||||
const container = document.getElementById(this.containerId);
|
||||
|
||||
// Créer la section de la main
|
||||
const handDiv = document.createElement('div');
|
||||
const classNames = `hand-display ${this.position}`;
|
||||
if (this.tilt !== 'none') {
|
||||
handDiv.className = `${classNames} tilt-${this.tilt}`;
|
||||
} else {
|
||||
handDiv.className = classNames;
|
||||
}
|
||||
|
||||
// Créer le label
|
||||
const label = document.createElement('div');
|
||||
label.className = 'hand-label';
|
||||
label.textContent = this.label;
|
||||
handDiv.appendChild(label);
|
||||
|
||||
// Créer la grille de tuiles
|
||||
const handTiles = document.createElement('div');
|
||||
handTiles.className = 'hand-tiles';
|
||||
|
||||
tiles.forEach(tile => {
|
||||
const tileDiv = document.createElement('div');
|
||||
tileDiv.className = 'hand-tile-item';
|
||||
tileDiv.innerHTML = tile.svg;
|
||||
handTiles.appendChild(tileDiv);
|
||||
});
|
||||
|
||||
handDiv.appendChild(handTiles);
|
||||
container.appendChild(handDiv);
|
||||
}
|
||||
}
|
||||
263
frontend/pages/chap2.html
Normal file
263
frontend/pages/chap2.html
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Tuto Riichi</title>
|
||||
<link rel="stylesheet" href="../../frontend/css/style.css">
|
||||
<link rel="stylesheet" href="../../frontend/css/speech-bubble.css">
|
||||
<link rel="stylesheet" href="../../frontend/css/hand.css">
|
||||
<style>
|
||||
#menu {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#mascotte {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
right: 160px;
|
||||
z-index: 999;
|
||||
transform: scaleX(-1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mascotte img {
|
||||
width: 250px;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.speech-bubble {
|
||||
position: fixed;
|
||||
bottom: 320px;
|
||||
right: 170px;
|
||||
z-index: 998;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.speech-bubble.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#tiles-display {
|
||||
padding: 20px;
|
||||
max-width: 900px;
|
||||
margin: 20px auto;
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
grid-column: 1 / -1;
|
||||
color: #ffffff;
|
||||
font-size: 1.5rem;
|
||||
margin: 20px 0 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hands-container {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.hands-container .hand-display {
|
||||
grid-column: auto;
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tiles-family {
|
||||
margin-bottom: 0;
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: 100px 1fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.tiles-family h3 {
|
||||
color: #ffffff;
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tiles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, minmax(0, 1fr));
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.tile-item {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tiles-numbers {
|
||||
grid-column: 2;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(9, minmax(0, 1fr));
|
||||
gap: 3px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tiles-numbers div {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
padding: 5px 0;
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="menu"></div>
|
||||
<div id="tile-container"></div>
|
||||
<div id="tiles-display"></div>
|
||||
<div class="speech-bubble">
|
||||
<div class="speech-text" id="speech-text"></div>
|
||||
</div>
|
||||
<div id="mascotte"></div>
|
||||
|
||||
<script src="../../frontend/js/fr/texts.js"></script>
|
||||
<script src="../../frontend/js/hand.js"></script>
|
||||
<script>
|
||||
// Charger le menu
|
||||
fetch('../../components/menu.html')
|
||||
.then(r => r.text())
|
||||
.then(html => document.getElementById('menu').innerHTML = html);
|
||||
|
||||
// Afficher la mascotte
|
||||
const mascolteEl = document.getElementById('mascotte');
|
||||
mascolteEl.innerHTML = '<img src="../../img/tilineau/speaking.png" alt="Tilineau">';
|
||||
|
||||
// Afficher le texte de bienvenue
|
||||
document.getElementById('speech-text').innerHTML = texts.riichi.chap2;
|
||||
|
||||
// Toggle bulle de dialogue au clic sur la mascotte
|
||||
const speechBubble = document.querySelector('.speech-bubble');
|
||||
mascolteEl.addEventListener('click', () => {
|
||||
speechBubble.classList.toggle('visible');
|
||||
if (speechBubble.classList.contains('visible')) {
|
||||
mascolteEl.innerHTML = '<img class="explaining" src="../../img/tilineau/explaining.png" alt="Tilineau">';
|
||||
} else {
|
||||
mascolteEl.innerHTML = '<img src="../../img/tilineau/speaking.png" alt="Tilineau">';
|
||||
}
|
||||
});
|
||||
|
||||
// Créer le titre de section
|
||||
const tilesDisplay = document.getElementById('tiles-display');
|
||||
const title = document.createElement('div');
|
||||
title.className = 'section-title';
|
||||
title.textContent = 'Exemples de mains';
|
||||
tilesDisplay.appendChild(title);
|
||||
|
||||
// Créer le conteneur des mains
|
||||
const handsContainer = document.createElement('div');
|
||||
handsContainer.className = 'hands-container';
|
||||
tilesDisplay.appendChild(handsContainer);
|
||||
|
||||
// Créer et afficher trois mains aléatoires
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const hand = new Hand({
|
||||
label: `Main ${i + 1}`,
|
||||
position: 'top-label',
|
||||
tilt: 'none',
|
||||
containerId: 'tiles-display'
|
||||
});
|
||||
|
||||
// Modifier temporairement le containerId pour ajouter à handsContainer
|
||||
hand.containerId = null;
|
||||
|
||||
fetch(hand.apiUrl)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const handDiv = document.createElement('div');
|
||||
handDiv.className = `hand-display ${hand.position} tilt-${hand.tilt}`;
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'hand-label';
|
||||
label.textContent = `Main ${i + 1}`;
|
||||
handDiv.appendChild(label);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'hand-container';
|
||||
|
||||
const handTiles = document.createElement('div');
|
||||
handTiles.className = 'hand-tiles';
|
||||
|
||||
data.hand.forEach(tile => {
|
||||
const tileDiv = document.createElement('div');
|
||||
tileDiv.className = 'hand-tile-item';
|
||||
tileDiv.innerHTML = tile.svg;
|
||||
handTiles.appendChild(tileDiv);
|
||||
});
|
||||
|
||||
container.appendChild(handTiles);
|
||||
handDiv.appendChild(container);
|
||||
handsContainer.appendChild(handDiv);
|
||||
})
|
||||
.catch(error => console.error('Erreur:', error));
|
||||
}
|
||||
|
||||
// Créer le titre pour la main interactive
|
||||
const interactiveTitle = document.createElement('div');
|
||||
interactiveTitle.className = 'section-title';
|
||||
interactiveTitle.textContent = 'Main interactive';
|
||||
tilesDisplay.appendChild(interactiveTitle);
|
||||
|
||||
// Créer et afficher la main interactive avec une tuile piochée
|
||||
fetch('http://localhost:5000/api/hand_with_draw')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const handDiv = document.createElement('div');
|
||||
handDiv.className = 'hand-display top-label tilt-none';
|
||||
handDiv.id = 'interactive-hand';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'hand-label';
|
||||
label.textContent = 'Votre main';
|
||||
handDiv.appendChild(label);
|
||||
|
||||
// Conteneur qui gère l'affichage avec flex (gère auto le draw si présent)
|
||||
const container = document.createElement('div');
|
||||
container.className = 'hand-container';
|
||||
|
||||
// Créer le grid des 13 tuiles
|
||||
const handTiles = document.createElement('div');
|
||||
handTiles.className = 'hand-tiles';
|
||||
handTiles.id = 'interactive-tiles';
|
||||
|
||||
data.hand.forEach(tile => {
|
||||
const tileDiv = document.createElement('div');
|
||||
tileDiv.className = 'hand-tile-item';
|
||||
tileDiv.innerHTML = tile.svg;
|
||||
handTiles.appendChild(tileDiv);
|
||||
});
|
||||
|
||||
container.appendChild(handTiles);
|
||||
|
||||
const drawTileDiv = document.createElement('div');
|
||||
drawTileDiv.className = 'hand-tile-item draw-tile';
|
||||
drawTileDiv.innerHTML = data.draw.svg;
|
||||
container.appendChild(drawTileDiv);
|
||||
|
||||
handDiv.appendChild(container);
|
||||
tilesDisplay.appendChild(handDiv);
|
||||
})
|
||||
.catch(error => console.error('Erreur:', error));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue