3 premiers chapitres
This commit is contained in:
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
1
build/dp3.js
Normal file
File diff suppressed because one or more lines are too long
1
build/dp4.js
Normal file
1
build/dp4.js
Normal file
File diff suppressed because one or more lines are too long
47
index.html
47
index.html
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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
192
src/display/dp3.ts
Normal 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
185
src/display/dp4.ts
Normal 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
127
src/display/template.ts
Normal 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
130
src/game.ts
Normal 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
21
src/group.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
133
src/hand.ts
133
src/hand.ts
|
|
@ -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 {
|
||||
|
|
|
|||
42
src/tile.ts
42
src/tile.ts
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue