added chapter 6

This commit is contained in:
Didictateur 2025-04-15 16:59:37 +02:00
parent 282fe9d311
commit 9e288a26f7
8 changed files with 315 additions and 7 deletions

File diff suppressed because one or more lines are too long

1
build/dp6.js Normal file

File diff suppressed because one or more lines are too long

1
build/txt6.js Normal file
View file

@ -0,0 +1 @@
(()=>{"use strict";const t=document.getElementById("myTextCanvas"),n=t.getContext("2d");t.width=800,t.height=1050,n.fillStyle="#007733",n.fillRect(0,0,800,1050),function(t,n){return e=this,o=void 0,f=function*(){console.log(t,"\n");const e=yield fetch(t).then((t=>t.text())),o="#ffffff";n.fillStyle=o,n.font="30px Garamond";let l=!1,f="",i="",c="",a=1,r=0;for(var d of e)"*"===d?(i=""===i?"bold ":"",n.font=c+i+30+"px Garamond"):"~"===d?(c=""===c?"italic ":"",n.font=c+i+30+"px Garamond"):"#"===d?(f="#",l=!0):"{"===d?(l=!1,n.fillStyle=f):"}"===d?(f="",n.fillStyle=o):l?f+=d:"\n"===d?(a++,r=0):(n.fillText(d,10+r,20+35*a),r+=n.measureText(d).width)},new((l=void 0)||(l=Promise))((function(t,n){function i(t){try{a(f.next(t))}catch(t){n(t)}}function c(t){try{a(f.throw(t))}catch(t){n(t)}}function a(n){var e;n.done?t(n.value):(e=n.value,e instanceof l?e:new l((function(t){t(e)}))).then(i,c)}a((f=f.apply(e,o||[])).next())}));var e,o,l,f}("src/text/txt6.txt",n).catch((t=>console.error(t)))})();

View file

@ -79,10 +79,10 @@
<a href="#" onclick="loadScript('dp3.js')">Chap 3: Les groupes</a> <a href="#" onclick="loadScript('dp3.js')">Chap 3: Les groupes</a>
<a href="#" onclick="loadScript('dp4.js')">Chap 4: Avoir une main valide en jeu</a> <a href="#" onclick="loadScript('dp4.js')">Chap 4: Avoir une main valide en jeu</a>
<a href="#" onclick="loadScript('dp5.js')">Chap 5: Annoncer la victoire</a> <a href="#" onclick="loadScript('dp5.js')">Chap 5: Annoncer la victoire</a>
<a href="#" onclick="loadScript('dp6.js')">Chap 6: Le furiten</a> <a href="#" onclick="loadScript('dp6.js')">Chap 6: Le vent du joueur</a>
<a href="#" onclick="loadScript('dp7.js')">Chap 7: Le vent du joueur</a> <a href="#" onclick="loadScript('dp7.js')">Chap 7: Les yakus</a>
<a href="#" onclick="loadScript('dp8.js')">Chap 8: Les yakus</a> <a href="#" onclick="loadScript('dp8.js')">Chap 8: Le riichi</a>
<a href="#" onclick="loadScript('dp9.js')">Chap 9: Le riichi</a> <a href="#" onclick="loadScript('dp9.js')">Chap 9: Le furiten</a>
<a href="#" onclick="loadScript('dp10.js')">Chap 10: Première partie complète</a> <a href="#" onclick="loadScript('dp10.js')">Chap 10: Première partie complète</a>
<a href="#" onclick="loadScript('dp11.js')">Chap 11: Score: les Hans</a> <a href="#" onclick="loadScript('dp11.js')">Chap 11: Score: les Hans</a>
<a href="#" onclick="loadScript('dp12.js')">Chap 12: Score: les doras</a> <a href="#" onclick="loadScript('dp12.js')">Chap 12: Score: les doras</a>

256
src/display/dp6.ts Normal file
View file

@ -0,0 +1,256 @@
import { Deck } from "../deck";
import { Hand } from "../hand";
import { Game } from "../game";
class RiichiGameManager {
// Configuration globale
private readonly CANVAS_ID: string = "myCanvas";
private readonly BG_RECT = { x: 0, y: 0, w: 1050, h: 1050 };
private readonly FPS: number = 60;
private readonly FRAME_INTERVAL: number = 1000 / this.FPS;
// Singleton instance
private static instance: RiichiGameManager;
// Canvas et contextes
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private staticCanvas: HTMLCanvasElement;
private staticCtx: CanvasRenderingContext2D;
// État du jeu
private mouse = { x: 0, y: 0 };
private game: Game | null = null;
private decks: Array<Deck> = [];
private hands: Array<Hand> = [];
// Animation et boucle de jeu
private animationFrameId: number | null = null;
private lastFrameTime: number = 0;
private isInitialized: boolean = false;
// Nettoyage
private cleanupCallbacks: Array<() => void> = [];
/**
* Constructeur privé pour le Singleton
*/
private constructor() {
// Initialisation du canvas principal
const canvas = document.getElementById(this.CANVAS_ID) as HTMLCanvasElement;
if (!canvas) {
throw new Error(`Canvas avec ID '${this.CANVAS_ID}' introuvable`);
}
this.canvas = canvas;
this.canvas.width = this.BG_RECT.w;
this.canvas.height = this.BG_RECT.h;
// Récupération du contexte 2D
const ctx = canvas.getContext("2d", { alpha: false });
if (!ctx) {
throw new Error("Impossible d'obtenir le contexte 2D du canvas");
}
this.ctx = ctx;
// Initialisation du canvas pour le double-buffering
this.staticCanvas = document.createElement('canvas');
this.staticCanvas.width = canvas.width;
this.staticCanvas.height = canvas.height;
const staticCtx = this.staticCanvas.getContext("2d", { alpha: false });
if (!staticCtx) {
throw new Error("Impossible d'obtenir le contexte du canvas statique");
}
this.staticCtx = staticCtx;
}
/**
* Accès à l'instance Singleton
*/
public static getInstance(): RiichiGameManager {
if (!RiichiGameManager.instance) {
RiichiGameManager.instance = new RiichiGameManager();
}
return RiichiGameManager.instance;
}
/**
* Dessine une frame du jeu
*/
private drawFrame(): void {
if (this.game) {
this.game.draw(this.mouse);
}
}
/**
* Démarre la boucle d'animation du jeu
*/
private startAnimationLoop(): void {
const animationLoop = (currentTime: number) => {
this.animationFrameId = requestAnimationFrame(animationLoop);
const deltaTime = currentTime - this.lastFrameTime;
if (deltaTime < this.FRAME_INTERVAL) return;
// Correction du time drift
this.lastFrameTime = currentTime - (deltaTime % this.FRAME_INTERVAL);
// Rendu d'une frame
this.drawFrame();
};
this.animationFrameId = requestAnimationFrame(animationLoop);
}
/**
* Initialise les écouteurs d'événements
*/
private initEventListeners(): void {
// Handler pour le déplacement de la souris
const mouseMoveHandler = (e: MouseEvent) => {
// Calcul des coordonnées relatives au canvas
const rect = this.canvas.getBoundingClientRect();
this.mouse.x = e.clientX - rect.left;
this.mouse.y = e.clientY - rect.top;
};
// Handler pour le clic de souris
const mouseDownHandler = (e: MouseEvent) => {
if (this.game) {
this.game.click(e);
}
};
// Enregistrement des écouteurs
this.canvas.addEventListener('mousemove', mouseMoveHandler);
this.canvas.addEventListener('mousedown', mouseDownHandler);
// Enregistrement des callbacks de nettoyage
this.cleanupCallbacks.push(() => {
this.canvas.removeEventListener('mousemove', mouseMoveHandler);
this.canvas.removeEventListener('mousedown', mouseDownHandler);
});
}
/**
* Précharge un deck
*/
private async preloadDeck(deck: Deck): Promise<void> {
await deck.preload();
}
/**
* Précharge une main
*/
private async preloadHand(hand: Hand): Promise<void> {
await hand.preload();
}
/**
* Initialise le jeu
*/
public async initialize(): Promise<void> {
if (this.isInitialized) return;
try {
console.log("Chargement en cours...");
// Création du jeu
this.game = new Game(
this.ctx,
this.canvas,
this.staticCtx,
this.staticCanvas,
false,
0,
Math.floor(Math.random() * 4)
);
// Préchargement parallèle des ressources
await Promise.all([
...this.decks.map(deck => this.preloadDeck(deck)),
...this.hands.map(hand => this.preloadHand(hand)),
this.game.preload()
]);
console.log("Chargement terminé");
// Initialisation des écouteurs d'événements
this.initEventListeners();
// Démarrage de la boucle d'animation
this.startAnimationLoop();
// Marquer comme initialisé
this.isInitialized = true;
// Définir la fonction de nettoyage global
window.cleanup = this.cleanup.bind(this);
} catch (error) {
console.error("Erreur lors de l'initialisation:", error);
}
}
/**
* Nettoie les ressources
*/
public cleanup(): void {
// Arrêter la boucle d'animation
if (this.animationFrameId !== null) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
// Exécuter tous les callbacks de nettoyage
this.cleanupCallbacks.forEach(callback => callback());
this.cleanupCallbacks = [];
// Nettoyer les ressources du jeu
if (this.game) {
// Supposons que Game a une méthode cleanup
(this.game as any).cleanup?.();
this.game = null;
}
// Nettoyer les ressources des decks et des mains
this.decks.forEach(deck => deck.cleanup());
this.hands.forEach(hand => hand.cleanup());
// Vider les collections
this.decks = [];
this.hands = [];
// Réinitialiser l'état
this.isInitialized = false;
this.lastFrameTime = 0;
this.mouse = { x: 0, y: 0 };
// Effacer les canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.staticCtx.clearRect(0, 0, this.staticCanvas.width, this.staticCanvas.height);
}
}
// Fonction de nettoyage globale exportée
export function cleanup(): void {
RiichiGameManager.getInstance().cleanup();
}
// Fonction d'initialisation globale exportée
export async function initDisplay(): Promise<void> {
await RiichiGameManager.getInstance().initialize();
}
// Déclaration globale pour TypeScript
declare global {
interface Window {
cleanup: () => void;
}
}
// Initialisation automatique si le script est chargé directement
if (typeof window !== 'undefined') {
initDisplay().catch(console.error);
}

View file

@ -37,6 +37,7 @@ export class Game {
// Game state // Game state
private level: number; private level: number;
private turn = 0; private turn = 0;
private windPlayer: number;
private waitingTime = GAME_CONSTANTS.WAITING_TIME.DEFAULT; private waitingTime = GAME_CONSTANTS.WAITING_TIME.DEFAULT;
private selectedTile: number | undefined = undefined; private selectedTile: number | undefined = undefined;
private canCall: boolean = false; private canCall: boolean = false;
@ -63,13 +64,16 @@ export class Game {
staticCtx: CanvasRenderingContext2D, staticCtx: CanvasRenderingContext2D,
staticCv: HTMLCanvasElement, staticCv: HTMLCanvasElement,
red: boolean = false, red: boolean = false,
level: number = 0 level: number = 0,
windPlayer: number = 0
) { ) {
this.ctx = ctx; this.ctx = ctx;
this.cv = cv; this.cv = cv;
this.staticCtx = staticCtx; this.staticCtx = staticCtx;
this.staticCv = staticCv; this.staticCv = staticCv;
this.level = level; this.level = level;
this.windPlayer = windPlayer;
this.turn = windPlayer % 2 === 0 ? windPlayer : 4 - windPlayer;
// Initialize game elements // Initialize game elements
this.deck = new Deck(red); this.deck = new Deck(red);
@ -497,7 +501,7 @@ export class Game {
this.play(); this.play();
// Draw game elements // Draw game elements
drawState(this.staticCtx, this.turn); drawState(this.staticCtx, this.turn, GAME_CONSTANTS.PI / 2 * this.windPlayer);
this.drawDiscardSize(); this.drawDiscardSize();
this.drawResult(); this.drawResult();
this.drawHands(); this.drawHands();

18
src/text/txt6.ts Normal file
View file

@ -0,0 +1,18 @@
import { drawText } from "./parse"
const CANVAS_ID = "myTextCanvas";
const BG_RECT = { x: 0, y: 0, w: 800, h: 1050, color: "#007733" };
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
canvas.width = BG_RECT.w;
canvas.height = BG_RECT.h;
const path = "src/text/";
ctx.fillStyle = BG_RECT.color;
ctx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);
drawText(path + "txt6.txt", ctx).catch(error => console.error(error));
export {};

28
src/text/txt6.txt Normal file
View file

@ -0,0 +1,28 @@
~Chapitre 6: Le vent du joueur~
Normalement, vous auriez dû remarquer la présence de directions
cardinales affichées au centre. L'explication est simple: chaque
joueur se voit assigner un vent durant une manche. L'assignation
se fait simplement: un premier joueur est l'#ff00ff{Est}, le joueur
à sa droite est donc le #ff00ff{Sud}, en face l'#ff00ff{Ouest} et à sa
gauche le #ff00ff{Nord}
L'#ff00ff{Est} est celui qui joue en tout premier durant sa manche.
À la fin de cette dernière, les vents tournent dans le sens
anti-horaire
La partie s'arrête lorsque *tous* les joueurs ont été *Est*
#ff0000{Remarque}: Contrairement à une boussole, l'*Est* et
l'*Ouest* sont inversés. La logique qui se cache derrière est
qu'il faut interpréter le jeu comme une carte céleste
#ff0000{Remarque 2}: Si le joueur *Est* gagne la manche, il y a alors
une #ff00f{répétition}, et les vents ne tournent pas
~Dans le jeu ci-contre, le vent est tiré aléatoirement, et change
à chaque manche en fonction du résultat~