added chapter 6
This commit is contained in:
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
1
build/dp6.js
Normal file
File diff suppressed because one or more lines are too long
1
build/txt6.js
Normal file
1
build/txt6.js
Normal 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)))})();
|
||||||
|
|
@ -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
256
src/display/dp6.ts
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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
18
src/text/txt6.ts
Normal 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
28
src/text/txt6.txt
Normal 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~
|
||||||
Loading…
Reference in a new issue