3 premiers chapitres

This commit is contained in:
Didictateur 2025-03-09 00:54:44 +01:00
parent 84aeb36f83
commit 86596a14e7
15 changed files with 892 additions and 37 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
build/dp3.js Normal file

File diff suppressed because one or more lines are too long

1
build/dp4.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -8,13 +8,14 @@
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #F0EDFF;
background-color: #007730;
}
.menu {
display: flex;
justify-content: center;
background-color: #4CAF50;
padding: 10px 0;
padding: 0px 0;
font-size: 26px;
}
.menu-item {
position: relative;
@ -33,15 +34,22 @@
display: none;
position: absolute;
top: 100%;
left: 0;
left: 50%;
transform: translateX(-40%);
background-color: white;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
box-shadow: 0px 5px 8px rgba(0, 0, 0, 0.1);
min-width: 200px;
white-space: nowrap;
font-size: 20px;
}
.dropdown a {
color: black;
text-decoration: none;
padding: 10px 20px;
padding: 10px 10px;
display: block;
white-space: nowrap;
width: 100%;
box-sizing: border-box;
}
.dropdown a:hover {
background-color: #ddd;
@ -53,20 +61,43 @@
</head>
<body>
<nav class="menu">
<ul class="menu-item">
<a href="#" onclick="loadScript('accueil.js')">Accueil</a>
</ul>
<ul class="menu-item">
<a href="#">Riichi Mahjong</a>
<div class="dropdown">
<a href="#" onclick="loadScript('dp1.js')">Présentation des tuiles</a>
<a href="#" onclick="loadScript('dp2.js')">La main</a>
<a href="#" onclick="loadScript('dp3.js')">Les groupes</a>
<a href="#" onclick="loadScript('dp1.js')">Chap 1: Présentation des tuiles</a>
<a href="#" onclick="loadScript('dp2.js')">Chap 2: La main</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('dp5.js')">Chap 5: Le vent du joueur</a>
<a href="#" onclick="loadScript('dp6.js')">Chap 6: Les yakus</a>
<a href="#" onclick="loadScript('dp7.js')">Chap 7: Le riichi</a>
<a href="#" onclick="loadScript('dp8.js')">Chap 8: Première partie complète</a>
<a href="#" onclick="loadScript('dp9.js')">Chap 9: Score: les Hans</a>
<a href="#" onclick="loadScript('dp10.js')">Chap 10: Score: les doras</a>
<a href="#" onclick="loadScript('dp11.js')">Chap 11: Score: les Fus</a>
<a href="#" onclick="loadScript('dp12.js')">Chap 12: Score: le calcul des points</a>
<a href="#" onclick="loadScript('dp13.js')">Chap 13: Partie avec les scores</a>
</div>
</ul>
<ul class="menu-item">
<a href="#" onclick="loadScript('')">Riichi à trois</a>
</ul>
<ul class="menu-item">
<a href="#">Yaku trainer</a>
<div class="dropdown">
<a href="#" onclick="loadScript('')">Yaku 1</a>
</div>
</ul>
<ul class="menu-item">
<a href="#">MCR</a>
<div class="dropdown">
<a href="#" onclick="loadScript('')">Chap 1:</a>
<a href="#" onclick="loadScript('')">Chap 2:</a>
</div>
</ul>
</nav>
<div id="canvasContainer"></div>

View file

@ -46,12 +46,20 @@ export class Deck {
}
}
public length(): number {
return this.tiles.length;
}
public pop(): Tile {
if (this.tiles.length === 0) {
}
return this.tiles.pop() as NonNullable<Tile>;
}
public push(tile: Tile) {
this.tiles.push(tile);
}
public find(family: number, value: number): Tile | undefined {
let n = undefined;
for (let i = 0; i < this.tiles.length; i++) {

View file

@ -31,7 +31,7 @@ async function display () {
const interval = 1000 / FPS;
// tuiles
let x = 150;
let x = 100;
let y = 150;
let os = 75;
let size = 0.75;
@ -49,6 +49,7 @@ async function display () {
const edeck = new Deck(false);
await edeck.preload();
const ehand = edeck.getRandomHand();
ehand.push(edeck.pop());
ehand.sort();
let selectedTile: number|undefined = undefined;
@ -106,6 +107,17 @@ async function display () {
}
}
);
canvas.addEventListener(
"mousedown",
(event) => {
if (selectedTile !== undefined) {
edeck.push(ehand.eject(selectedTile));
edeck.shuffle();
ehand.push(edeck.pop());
ehand.sort();
}
}
);
requestAnimationFrame(animationLoop);

192
src/display/dp3.ts Normal file
View file

@ -0,0 +1,192 @@
import { Deck } from "../deck";
import { Hand } from "../hand";
import { Group } from "../group"
// Configuration globale
const CANVAS_ID = "myCanvas";
const BG_COLOR = "#007730";
const BG_RECT = { x: 50, y: 50, w: 1000, h: 1000 };
const FPS = 30;
const FRAME_INTERVAL = 1000 / FPS;
// variables globales
const DECKS: Array<Deck> = [];
const HANDS: Array<Hand> = [];
let selectedTile: number|undefined = undefined;
// Optimisation des références
let animationFrameId: number;
let lastFrameTime = 0;
const callbacks: Array<() => void> = [];
// Pré-calcul des dimensions
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
// Cache statique
const staticCanvas = document.createElement('canvas') as HTMLCanvasElement;
const staticCtx = staticCanvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
staticCanvas.width = canvas.width;
staticCanvas.height = canvas.height;
// Pré-rendu du fond
function prerenderBackground() {
staticCtx.clearRect(0, 0, canvas.width, canvas.height);
staticCtx.fillStyle = BG_COLOR;
staticCtx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);
};
function drawFrame() {
if (!ctx) return;
// Effacement intelligent (uniquement la zone nécessaire)
prerenderBackground();
// Ici viendrait le dessin des éléments dynamiques
// Par exemple:
// drawDeck();
// drawHands();
let x = 300;
let y = 150;
let xos = 250;
let yos = 100;
let size = 0.75;
staticCtx.fillStyle = "#DFDFFF";
staticCtx.font = "50px serif";
staticCtx.fillText("Chii:", 75, y + 100 * size - 5);
HANDS[0].drawHand(staticCtx, x, y, 5, 0.75); // chii
HANDS[1].drawHand(staticCtx, x + (75+xos)*size, y, 5, 0.75);
staticCtx.fillText("Pon:", 75, y + (100+yos)*size + 100 * size - 5);
HANDS[2].drawHand(staticCtx, x, y + (100+yos)*size, 5, 0.75); // pon
HANDS[3].drawHand(staticCtx, x + (75+xos)*size, y + (100+yos)*size, 5, 0.75);
HANDS[4].drawHand(staticCtx, x + 2*(75+xos)*size, y + 2*(100+yos)*size, 5, 0.75);
staticCtx.fillText("Invalide:", 75, y + 2*(100+yos)*size + 100 * size - 5);
HANDS[5].drawHand(staticCtx, x, y + 2*(100+yos)*size, 5, 0.75); // wrong
HANDS[6].drawHand(staticCtx, x + (75+xos)*size, y + 2*(100+yos)*size, 5, 0.75);
HANDS[7].drawHand(staticCtx, x + 2*(75+xos)*size, y + 2*(100+yos)*size, 5, 0.75);
HANDS[8].drawHand(staticCtx, 100, 800, 5, size, selectedTile);
let groups = HANDS[8].toGroup();
if (groups !== undefined) {
staticCtx.fillStyle = "#FF0000";
staticCtx.font = "50px serif";
staticCtx.fillText("Tous les groupes sont formés !", 100, 750);
}
groups = [];
// Dessin du cache statique
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(staticCanvas, 0, 0);
}
function animationLoop(currentTime: number) {
animationFrameId = requestAnimationFrame(animationLoop);
const deltaTime = currentTime - lastFrameTime;
if (deltaTime < FRAME_INTERVAL) return;
lastFrameTime = currentTime - (deltaTime % FRAME_INTERVAL);
drawFrame();
}
function initEventListeners() {
const handlers = {
mousemove: (e: MouseEvent) => {
let size = 0.75;
let x = 100;
// Logique de gestion du mouvement de la souris
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left - x;
const mouseY = e.clientY - rect.top;
let q = Math.floor(mouseX / (80 * size));
let r = mouseX - q * 80 * size;
if (r <= 75 && q >= 0 && q < 14 && mouseY >= 800 && mouseY <= 800 + 100*size) {
selectedTile = q;
} else {
selectedTile = undefined;
}
},
mousedown: (e: MouseEvent) => {
// Logique de gestion du clic de souris
if (selectedTile !== undefined) {
DECKS[0].push(HANDS[8].eject(selectedTile));
DECKS[0].shuffle();
HANDS[8].push(DECKS[0].pop());
HANDS[8].sort();
}
}
};
callbacks.push(() => {
canvas.removeEventListener('mousemove', handlers.mousemove);
canvas.removeEventListener('mousedown', handlers.mousedown);
});
canvas.addEventListener('mousemove', handlers.mousemove);
canvas.addEventListener('mousedown', handlers.mousedown);
}
async function preloadDeck(deck: Deck) {
await deck.preload();
}
async function preloadHand(hand: Hand) {
await hand.preload();
}
export function cleanup() {
cancelAnimationFrame(animationFrameId);
callbacks.forEach(fn => fn());
}
export async function initDisplay() {
if (!ctx) {
console.error("Context canvas indisponible");
return;
}
// Préchargement des ressources si nécessaire
// const deck = new Deck();
// await preloadDeck(deck);
DECKS.push(
new Deck(false)
);
HANDS.push(
new Hand("s1s2s3"),
new Hand("m2m3m4"),
new Hand("p5p5p5"),
new Hand("w2w2w2"),
new Hand("d3d3d3"),
new Hand("s4p5m6"),
new Hand("m9s9p9"),
new Hand("d1d2d3"),
DECKS[0].getRandomHand()
);
await Promise.all(DECKS.map(d => preloadDeck(d)));
await Promise.all(HANDS.map(h => preloadHand(h)));
HANDS[8].push(DECKS[0].pop());
HANDS[8].sort();
initEventListeners();
requestAnimationFrame(animationLoop);
window.cleanup = cleanup;
}
// 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);
}

185
src/display/dp4.ts Normal file
View file

@ -0,0 +1,185 @@
import { Deck } from "../deck";
import { Hand } from "../hand";
import { Group } from "../group";
import { Game } from "../game"
// Configuration globale
const CANVAS_ID = "myCanvas";
const BG_COLOR = "#007730";
const BG_RECT = { x: 50, y: 50, w: 1000, h: 1000 };
const FPS = 30;
const FRAME_INTERVAL = 1000 / FPS;
// variables globales
const DECKS: Array<Deck> = [];
const HANDS: Array<Hand> = [];
var GAME: Game|undefined;
// variables for the game
var selectedTile: number|undefined;
var turn: number = 0;
var hasPlayed: boolean = false;
var pCurrentTime = 0;
var pDeltaTime = 500;
// Optimisation des références
let animationFrameId: number;
let lastFrameTime = 0;
const callbacks: Array<() => void> = [];
// Pré-calcul des dimensions
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
// Cache statique
const staticCanvas = document.createElement('canvas') as HTMLCanvasElement;
const staticCtx = staticCanvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
staticCanvas.width = canvas.width;
staticCanvas.height = canvas.height;
// Pré-rendu du fond
function prerenderBackground() {
staticCtx.clearRect(0, 0, canvas.width, canvas.height);
staticCtx.fillStyle = BG_COLOR;
staticCtx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);
};
function drawFrame() {
if (!ctx) return;
let size = 0.65;
let os = 5;
let w = size * (450 + 5 * os);
let center = 525;
// Effacement intelligent (uniquement la zone nécessaire)
prerenderBackground();
// Ici viendrait le dessin des éléments dynamiques
// Par exemple:
// drawDeck();
// drawHands();
// staticCtx.fillStyle = "#005530";
// staticCtx.fillRect(center - w/2, center - w/2, w, w);
GAME?.drawGame(staticCtx, size, 0.6, selectedTile);
// Dessin du cache statique
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(staticCanvas, 0, 0);
}
function animationLoop(currentTime: number) {
animationFrameId = requestAnimationFrame(animationLoop);
// bot playing
if (turn !== 0 && (currentTime - pCurrentTime) > pDeltaTime && (GAME as NonNullable<Game>).getDeck().length() > 0) {
pCurrentTime = currentTime;
let n = Math.floor(Math.random() * (GAME as NonNullable<Game>).getHands()[turn].length());
GAME?.discard(turn, n);
turn = (turn + 1) % 4;
GAME?.pick(turn);
GAME?.getHands()[0].sort();
}
const deltaTime = currentTime - lastFrameTime;
if (deltaTime < FRAME_INTERVAL) return;
lastFrameTime = currentTime - (deltaTime % FRAME_INTERVAL);
drawFrame();
}
function initEventListeners() {
const handlers = {
mousemove: (e: MouseEvent) => {
// Logique de gestion du mouvement de la souris
let size = 0.75;
let x = 2.5 * 75 * 0.75;
let y = 1000 - 150 * 0.6;
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left - x;
const mouseY = e.clientY - rect.top;
let q = Math.floor(mouseX / (80 * size));
let r = mouseX - q * 80 * size;
if (
r <= 75 &&
q >= 0 &&
q < (GAME as NonNullable<Game>).getHands()[0].length() &&
mouseY >= y &&
mouseY <= y + 100 * size
) {
selectedTile = q;
} else {
selectedTile = undefined;
}
},
mousedown: (e: MouseEvent) => {
// Logique de gestion du clic de souris
if (turn === 0 && selectedTile !== undefined && (GAME as NonNullable<Game>).getDeck().length() > 0) {
GAME?.discard(0, selectedTile);
turn++;
GAME?.pick(turn);
}
}
};
callbacks.push(() => {
canvas.removeEventListener('mousemove', handlers.mousemove);
canvas.removeEventListener('mousedown', handlers.mousedown);
});
canvas.addEventListener('mousemove', handlers.mousemove);
canvas.addEventListener('mousedown', handlers.mousedown);
}
async function preloadDeck(deck: Deck) {
await deck.preload();
}
async function preloadHand(hand: Hand) {
await hand.preload();
}
export function cleanup() {
cancelAnimationFrame(animationFrameId);
callbacks.forEach(fn => fn());
}
export async function initDisplay() {
if (!ctx) {
console.error("Context canvas indisponible");
return;
}
// Préchargement des ressources si nécessaire
// const deck = new Deck();
// await preloadDeck(deck);
DECKS.push(
);
HANDS.push(
);
GAME = new Game();
await Promise.all(DECKS.map(d => preloadDeck(d)));
await Promise.all(HANDS.map(h => preloadHand(h)));
await GAME?.preload();
GAME.pick(0);
GAME.getHands()[0].sort();
initEventListeners();
requestAnimationFrame(animationLoop);
window.cleanup = cleanup;
}
// 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);
}

127
src/display/template.ts Normal file
View file

@ -0,0 +1,127 @@
import { Deck } from "../deck";
import { Hand } from "../hand";
import { Group } from "../group"
// Configuration globale
const CANVAS_ID = "myCanvas";
const BG_COLOR = "#007730";
const BG_RECT = { x: 50, y: 50, w: 1000, h: 1000 };
const FPS = 30;
const FRAME_INTERVAL = 1000 / FPS;
// variables globales
const DECKS: Array<Deck> = [];
const HANDS: Array<Hand> = [];
// Optimisation des références
let animationFrameId: number;
let lastFrameTime = 0;
const callbacks: Array<() => void> = [];
// Pré-calcul des dimensions
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
// Cache statique
const staticCanvas = document.createElement('canvas') as HTMLCanvasElement;
const staticCtx = staticCanvas.getContext("2d") as NonNullable<CanvasRenderingContext2D>;
staticCanvas.width = canvas.width;
staticCanvas.height = canvas.height;
// Pré-rendu du fond
function prerenderBackground() {
staticCtx.clearRect(0, 0, canvas.width, canvas.height);
staticCtx.fillStyle = BG_COLOR;
staticCtx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);
};
function drawFrame() {
if (!ctx) return;
// Effacement intelligent (uniquement la zone nécessaire)
prerenderBackground();
// Ici viendrait le dessin des éléments dynamiques
// Par exemple:
// drawDeck();
// drawHands();
// Dessin du cache statique
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(staticCanvas, 0, 0);
}
function animationLoop(currentTime: number) {
animationFrameId = requestAnimationFrame(animationLoop);
const deltaTime = currentTime - lastFrameTime;
if (deltaTime < FRAME_INTERVAL) return;
lastFrameTime = currentTime - (deltaTime % FRAME_INTERVAL);
drawFrame();
}
function initEventListeners() {
const handlers = {
mousemove: (e: MouseEvent) => {
// Logique de gestion du mouvement de la souris
},
mousedown: (e: MouseEvent) => {
// Logique de gestion du clic de souris
}
};
callbacks.push(() => {
canvas.removeEventListener('mousemove', handlers.mousemove);
canvas.removeEventListener('mousedown', handlers.mousedown);
});
canvas.addEventListener('mousemove', handlers.mousemove);
canvas.addEventListener('mousedown', handlers.mousedown);
}
async function preloadDeck(deck: Deck) {
await deck.preload();
}
async function preloadHand(hand: Hand) {
await hand.preload();
}
export function cleanup() {
cancelAnimationFrame(animationFrameId);
callbacks.forEach(fn => fn());
}
export async function initDisplay() {
if (!ctx) {
console.error("Context canvas indisponible");
return;
}
// Préchargement des ressources si nécessaire
// const deck = new Deck();
// await preloadDeck(deck);
DECKS.push(
);
HANDS.push(
);
await Promise.all(DECKS.map(d => preloadDeck(d)));
await Promise.all(HANDS.map(h => preloadHand(h)));
initEventListeners();
requestAnimationFrame(animationLoop);
window.cleanup = cleanup;
}
// 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);
}

130
src/game.ts Normal file
View file

@ -0,0 +1,130 @@
import { Deck } from "./deck";
import { Hand } from "./hand";
import { Tile } from "./tile";
export class Game {
private deck: Deck;
private hands: Array<Hand>;
private discards: Array<Array<Tile>>;
public constructor(red: boolean = false) {
this.deck = new Deck(red);
this.hands = [
this.deck.getRandomHand(),
this.deck.getRandomHand(),
this.deck.getRandomHand(),
this.deck.getRandomHand()
];
this.discards = [[], [], [], []];
}
public getDeck(): Deck {
return this.deck;
}
public getHands(): Array<Hand> {
return this.hands;
}
public drawGame(
ctx: CanvasRenderingContext2D,
discardSize: number,
handSize: number,
selectedTile: number|undefined = undefined
): void {
const pi = 3.141592;
this.hands[0].drawHand(
ctx,
2.5 * 75 * 0.75,
1000 - 150 * handSize,
5 * handSize,
0.75,
selectedTile,
false,
0
);
this.hands[1].drawHand(
ctx,
1000 - 150 * handSize,
1000 - 75 * 5 * handSize,
5 * handSize,
handSize,
undefined,
true,
- pi / 2
);
this.hands[2].drawHand(
ctx,
1000 - 75 * 5 * handSize,
150 * handSize,
5 * handSize,
handSize,
undefined,
true,
- pi
);
this.hands[3].drawHand(
ctx,
150 * handSize,
75 * 5 * handSize,
5 * handSize,
handSize,
undefined,
true,
pi / 2
);
for (let i = 0; i < 4; i++) {
this.drawDiscrad(ctx, i, discardSize);
}
}
public pick(player: number): void {
this.hands[player].push(this.deck.pop());
}
public discard(player: number, n: number): void {
let tile = this.hands[player].eject(n);
tile.setTilt();
this.discards[player].push(tile);
}
public async preload(): Promise<void> {
await this.deck.preload();
await Promise.all(this.hands.map(h => h.preload()));
}
private drawDiscrad(
ctx: CanvasRenderingContext2D,
p: number,
discardSize: number
): void {
const pi = 3.141592;
ctx.save();
ctx.translate(525, 525);
ctx.rotate([0, -pi/2, -pi, pi/2][p])
let x = - discardSize * 475 / 2;
let y = discardSize * (475 / 2 + 5);
for (let i = 0; i < this.discards[p].length; i++) {
if (i < 12) {
this.discards[p][i].drawTile(
ctx,
x + (i % 6) * 80 * discardSize,
y + Math.floor(i / 6) * 105 * discardSize,
discardSize
);
} else {
this.discards[p][i].drawTile(
ctx,
x + (i - 12) * 80 * discardSize,
y + 2 * 105 * discardSize,
discardSize
);
}
}
ctx.restore();
}
}

21
src/group.ts Normal file
View file

@ -0,0 +1,21 @@
import { Tile } from "./tile"
export class Group {
private tiles: Array<Tile>;
public constructor(tiles: Array<Tile> = []) {
this.tiles = tiles;
}
public push(tile: Tile): void {
this.tiles.push(tile);
}
public pop(): Tile|undefined {
return this.tiles.pop();
}
public getTiles(): Array<Tile> {
return this.tiles;
}
}

View file

@ -1,4 +1,5 @@
import { Tile } from "./tile"
import { Group } from "./group"
export class Hand {
private tiles: Array<Tile>;
@ -21,6 +22,10 @@ export class Hand {
}
}
public length(): number {
return this.tiles.length;
}
public push(tile: Tile): undefined {
this.tiles.push(tile);
}
@ -29,14 +34,103 @@ export class Hand {
return this.tiles.pop();
}
public sort(): undefined {
public find(family: number, value: number) :Tile|undefined {
let n = undefined;
for (let i = 0; i < this.tiles.length; i++) {
for (let j = 0; j < this.tiles.length-1; j++) {
if (!this.tiles[j].isLessThan(this.tiles[j+1])) {
[this.tiles[j], this.tiles[j+1]] = [this.tiles[j+1], this.tiles[j]];
}
if (this.tiles[i].getFamily() === family && this.tiles[i].getValue() === value) {
n = i;
break;
}
}
if (n !== undefined) {
[this.tiles[n], this.tiles[0]] = [this.tiles[0], this.tiles[n]];
let t = this.tiles.shift();
this.sort();
return t;
} else {
return undefined;
}
}
public eject(idTile: number): Tile {
[this.tiles[0], this.tiles[idTile]] = [this.tiles[idTile], this.tiles[0]];
let tile = this.tiles.shift();
this.sort();
return tile as NonNullable<Tile>;
}
public sort(): undefined {
this.tiles.sort((a, b) => a.isLessThan(b) ? -1 : 1);
}
public count(family: number, value: number): number {
let c = 0;
this.tiles.forEach(
t => {
if (t.getFamily() === family && t.getValue() === value) {
c++;
}
}
);
return c;
}
public toGroup(pair: boolean = false): Array<Group>|undefined {
if (this.tiles.length > 0) {
let t1 = this.tiles.pop() as NonNullable<Tile>;
let c = this.count(t1.getFamily(), t1.getValue());
if (c >= 1 && !pair) { //can do a pair
let t2 = this.find(t1.getFamily(), t1.getValue()) as NonNullable<Tile>;
let groups = this.toGroup(true);
this.tiles.push(t2);
this.sort();
if (groups !== undefined) {
this.tiles.push(t1);
this.sort();
groups.push(new Group([t1, t2]));
return groups;
}
}
if (c >= 2) { //can do a pon
let t2 = this.find(t1.getFamily(), t1.getValue()) as NonNullable<Tile>;
let t3 = this.find(t1.getFamily(), t1.getValue()) as NonNullable<Tile>;
let groups = this.toGroup(pair);
this.tiles.push(t2);
this.tiles.push(t3);
this.sort();
if (groups !== undefined) {
groups.push(new Group([t1, t2, t3]));
this.tiles.push(t1);
this.sort();
return groups;
}
}
let c2 = this.count(t1.getFamily(), t1.getValue()-1);
let c3 = this.count(t1.getFamily(), t1.getValue()-2);
if (c2 * c3 > 0) { //can do a chii
let t2 = this.find(t1.getFamily(), t1.getValue()-1) as NonNullable<Tile>;
let t3 = this.find(t1.getFamily(), t1.getValue()-2) as NonNullable<Tile>;
let groups = this.toGroup(pair);
this.tiles.push(t2);
this.tiles.push(t3);
this.sort();
if (groups !== undefined) {
groups.push(new Group([t3, t2, t1]));
this.tiles.push(t1);
this.sort();
return groups;
}
}
this.tiles.push(t1);
this.tiles.sort();
} else {
return [];
}
return undefined
}
public drawHand (
@ -45,31 +139,38 @@ export class Hand {
y: number,
offset: number,
size: number,
focusedTiled: number|undefined = undefined
focusedTiled: number|undefined = undefined,
hidden: boolean = false,
rotation: number = 0
): void {
for (var i = 0; i < this.tiles.length; i++) {
let v = (75 + offset) * size;
let vx = Math.cos(rotation) * v;
let vy = Math.sin(rotation) * v;
for (let i = 0; i < this.tiles.length; i++) {
if (i === focusedTiled) {
this.tiles[i].drawTile(
ctx,
x + i * 75 * size + i * offset * size,
y - 25 * size,
size
x + i * vx + 25 * size * Math.sin(rotation),
y + i * vy - 25 * size * Math.cos(rotation),
size,
hidden,
rotation
);
} else {
this.tiles[i].drawTile(
ctx,
x + i * 75 * size + i * offset * size,
y,
size
x + i * vx,
y + i * vy,
size,
hidden,
rotation
);
}
}
}
public async preload(): Promise<void> {
for (let i = 0; i < this.tiles.length; i++) {
await this.tiles[i].preloadImg();
}
await Promise.all(this.tiles.map(t => t.preloadImg()));
}
public cleanup(): void {

View file

@ -2,21 +2,21 @@ export class Tile {
private family: number;
private value: number;
private red: boolean;
private loadded: boolean;
private imgSrc: string;
private imgFront: HTMLImageElement;
private imgBack: HTMLImageElement;
private img: HTMLImageElement;
private tilt: number;
public constructor(family: number, value: number , red: boolean) {
this.family = family;
this.value = value;
this.red = red;
this.loadded = false;
this.imgSrc = "";
this.imgFront = new Image();
this.imgBack = new Image();
this.img = new Image();
this.tilt = 0;
this.setImgSrc();
}
@ -32,14 +32,46 @@ export class Tile {
return this.red;
}
public setTilt(): void {
this.tilt = (1 - 2 * Math.random()) * 0.05;
}
public drawTile(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
size: number
size: number,
hidden: boolean = false,
rotation: number = 0
): void {
ctx.drawImage(this.imgFront, x, y, 75 * size, 100 * size);
ctx.drawImage(this.img, x, y, 75 * size, 100 * size);
ctx.save();
ctx.translate(x + (75 * size) / 2, y + (100 * size) / 2);
ctx.rotate(rotation + this.tilt);
if (hidden) {
ctx.drawImage(
this.imgBack,
-(75 * size) / 2,
-(100 * size) / 2,
75 * size, 100 * size
);
} else {
ctx.drawImage(
this.imgFront,
-(75 * size) / 2,
-(100 * size) / 2,
75 * size, 100 * size
);
ctx.drawImage(
this.img,
-(75 * size) / 2,
-(100 * size) / 2,
75 * size,
100 * size
);
}
ctx.restore();
}
public isLessThan(t: Tile): boolean {

View file

@ -1,10 +1,24 @@
const path = require('path');
const fs = require('fs');
// Fonction pour détecter automatiquement les fichiers dp*.ts
function getEntryPoints() {
const displayDir = path.resolve(__dirname, 'src', 'display');
const files = fs.readdirSync(displayDir); // Lire les fichiers du répertoire
const entryPoints = {};
files.forEach((file) => {
if (file.startsWith('dp') && file.endsWith('.ts')) {
const name = path.basename(file, '.ts'); // Nom du fichier sans extension
entryPoints[name] = path.join(displayDir, file); // Chemin complet du fichier
}
});
return entryPoints;
}
module.exports = {
entry: {
dp1: './src/display/dp1.ts',
dp2: './src/display/dp2.ts'
},
entry: getEntryPoints(), // Générer automatiquement les points d'entrée
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'build'),