réduction du lague
This commit is contained in:
parent
09dd8a70b2
commit
e82cc8fc7a
2 changed files with 265 additions and 39 deletions
255
src/game.ts
255
src/game.ts
|
|
@ -43,6 +43,13 @@ export class Game {
|
||||||
private selectedTile: number | undefined = undefined;
|
private selectedTile: number | undefined = undefined;
|
||||||
private canCall: boolean = false;
|
private canCall: boolean = false;
|
||||||
private declaredRiichi: boolean[] = [];
|
private declaredRiichi: boolean[] = [];
|
||||||
|
// Version counters to detect when a hand changed (to avoid recomputing expensive sims every frame)
|
||||||
|
private handVersion: number[] = [];
|
||||||
|
// Tenpai cache per player
|
||||||
|
private tenpaiCache: boolean[] = [];
|
||||||
|
private tenpaiCacheVersion: number[] = [];
|
||||||
|
// canRon cache for player 0 relative to lastDiscard
|
||||||
|
private canRonCache: { lastDiscard?: number; handVer?: number; result: boolean } = { result: false };
|
||||||
private hasPicked: boolean = false;
|
private hasPicked: boolean = false;
|
||||||
private hasPlayed: boolean = false;
|
private hasPlayed: boolean = false;
|
||||||
private lastPlayed: number = Date.now();
|
private lastPlayed: number = Date.now();
|
||||||
|
|
@ -85,9 +92,93 @@ export class Game {
|
||||||
this.initializeGame();
|
this.initializeGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private drawDynamic(ctx: CanvasRenderingContext2D): void {
|
||||||
|
// Redraw player's hand on top to show focused tile
|
||||||
|
const { HAND_SIZE } = GAME_CONSTANTS.DISPLAY;
|
||||||
|
this.hands[0].drawHand(
|
||||||
|
ctx,
|
||||||
|
2.5 * 75 * 0.75,
|
||||||
|
1000 - 150 * HAND_SIZE,
|
||||||
|
5 * HAND_SIZE,
|
||||||
|
0.75,
|
||||||
|
this.selectedTile,
|
||||||
|
false,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// If a tile is selected, highlight matching discarded tiles
|
||||||
|
if (this.selectedTile !== undefined) {
|
||||||
|
const highlighted = this.hands[0].get(this.selectedTile) as Tile;
|
||||||
|
const sizeDiscard = GAME_CONSTANTS.DISPLAY.DISCARD_SIZE;
|
||||||
|
for (let p = 0; p < GAME_CONSTANTS.PLAYERS; p++) {
|
||||||
|
const d = this.discards[p];
|
||||||
|
if (!d || d.length === 0) continue;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(525, 525);
|
||||||
|
ctx.rotate(this.rotations[p]);
|
||||||
|
for (let i = 0; i < d.length; i++) {
|
||||||
|
const tile = d[i];
|
||||||
|
if (!highlighted.isEqual(tile.getFamily(), tile.getValue())) continue;
|
||||||
|
let tx, ty;
|
||||||
|
if (i < 12) {
|
||||||
|
tx = -sizeDiscard * 475 / 2 + (i % 6) * 80 * sizeDiscard;
|
||||||
|
ty = sizeDiscard * (475 / 2 + 5) + Math.floor(i / 6) * 105 * sizeDiscard;
|
||||||
|
} else {
|
||||||
|
tx = -sizeDiscard * 475 / 2 + (i - 12) * 80 * sizeDiscard;
|
||||||
|
ty = sizeDiscard * (475 / 2 + 5) + 2 * 105 * sizeDiscard;
|
||||||
|
}
|
||||||
|
// Draw highlighted version on top
|
||||||
|
tile.drawTile(ctx, tx, ty, sizeDiscard, false, 0, true);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw call buttons / chiis on top
|
||||||
|
if (this.chooseChii) {
|
||||||
|
drawChiis(ctx, this.getChii(0));
|
||||||
|
} else {
|
||||||
|
const canTsumoLocal = this.hasPicked && this.hasWin(0);
|
||||||
|
let canRonLocal = false;
|
||||||
|
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
|
||||||
|
if (this.canRonCache.lastDiscard === this.lastDiscard && this.canRonCache.handVer === this.handVersion[0]) {
|
||||||
|
canRonLocal = this.canRonCache.result;
|
||||||
|
} else {
|
||||||
|
const d = this.discards[this.lastDiscard];
|
||||||
|
if (d && d.length > 0) {
|
||||||
|
const last = d[d.length - 1];
|
||||||
|
const sim = new Hand();
|
||||||
|
for (const t of this.hands[0].getTiles()) {
|
||||||
|
sim.push(new Tile(t.getFamily(), t.getValue(), t.isRed()));
|
||||||
|
}
|
||||||
|
sim.push(new Tile(last.getFamily(), last.getValue(), last.isRed()));
|
||||||
|
sim.sort();
|
||||||
|
canRonLocal = sim.toGroup() !== undefined;
|
||||||
|
} else {
|
||||||
|
canRonLocal = false;
|
||||||
|
}
|
||||||
|
this.canRonCache = { lastDiscard: this.lastDiscard, handVer: this.handVersion[0], result: canRonLocal };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canRiichiLocal = this.canDeclareRiichi(0);
|
||||||
|
drawButtons(
|
||||||
|
ctx,
|
||||||
|
this.canDoAChii().length > 0,
|
||||||
|
this.canDoAPon(),
|
||||||
|
false && this.level > 1,
|
||||||
|
canRonLocal,
|
||||||
|
canTsumoLocal,
|
||||||
|
canRiichiLocal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Last detected yakus for the most recent win (player index => list)
|
// Last detected yakus for the most recent win (player index => list)
|
||||||
private lastYakus: Array<{ name: string; han: number }> = [];
|
private lastYakus: Array<{ name: string; han: number }> = [];
|
||||||
private lastTotalHan: number = 0;
|
private lastTotalHan: number = 0;
|
||||||
|
// Static/dynamic rendering optimization: when true we redraw the static buffer
|
||||||
|
private staticDirty: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute yakus for a given player and populate lastYakus/lastTotalHan.
|
* Compute yakus for a given player and populate lastYakus/lastTotalHan.
|
||||||
|
|
@ -133,6 +224,7 @@ export class Game {
|
||||||
|
|
||||||
this.end = true;
|
this.end = true;
|
||||||
this.result = resultCode;
|
this.result = resultCode;
|
||||||
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeGame(): void {
|
private initializeGame(): void {
|
||||||
|
|
@ -149,17 +241,25 @@ export class Game {
|
||||||
this.discards.push([]);
|
this.discards.push([]);
|
||||||
this.groups.push([]);
|
this.groups.push([]);
|
||||||
this.declaredRiichi.push(false);
|
this.declaredRiichi.push(false);
|
||||||
|
this.handVersion.push(0);
|
||||||
|
this.tenpaiCache.push(false);
|
||||||
|
this.tenpaiCacheVersion.push(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastDiscard = undefined;
|
this.lastDiscard = undefined;
|
||||||
this.hands[0].sort();
|
this.hands[0].sort();
|
||||||
if (this.turn === 0) this.pick(0);
|
if (this.turn === 0) this.pick(0);
|
||||||
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(mp: MousePos): void {
|
public draw(mp: MousePos): void {
|
||||||
// Clear static canvas once per frame
|
// Update selection and game state
|
||||||
this.staticCtx.clearRect(0, 0, this.cv.width, this.cv.height);
|
this.getSelected(mp);
|
||||||
|
this.play();
|
||||||
|
|
||||||
|
// Rebuild static buffer only when needed
|
||||||
|
if (this.staticDirty) {
|
||||||
|
this.staticCtx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||||
// Draw background
|
// Draw background
|
||||||
this.staticCtx.fillStyle = this.BG_RECT.color;
|
this.staticCtx.fillStyle = this.BG_RECT.color;
|
||||||
this.staticCtx.fillRect(
|
this.staticCtx.fillRect(
|
||||||
|
|
@ -169,12 +269,70 @@ export class Game {
|
||||||
this.BG_RECT.h
|
this.BG_RECT.h
|
||||||
);
|
);
|
||||||
|
|
||||||
this.getSelected(mp);
|
// Draw mostly-static elements
|
||||||
this.drawGame();
|
drawState(this.staticCtx, this.turn, GAME_CONSTANTS.PI / 2 * this.windPlayer);
|
||||||
|
this.drawDiscardSize();
|
||||||
|
this.drawResult();
|
||||||
|
// Draw hands without focused (focused tile rendered in dynamic overlay)
|
||||||
|
const showHands = false;
|
||||||
|
const { HAND_SIZE, HIDDEN_HAND_SIZE } = GAME_CONSTANTS.DISPLAY;
|
||||||
|
this.hands[0].drawHand(
|
||||||
|
this.staticCtx,
|
||||||
|
2.5 * 75 * 0.75,
|
||||||
|
1000 - 150 * HAND_SIZE,
|
||||||
|
5 * HAND_SIZE,
|
||||||
|
0.75,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
this.selectedTile
|
||||||
|
);
|
||||||
|
this.hands[1].drawHand(
|
||||||
|
this.staticCtx,
|
||||||
|
1000 - 150 * HIDDEN_HAND_SIZE,
|
||||||
|
1000 - 75 * 5 * HIDDEN_HAND_SIZE,
|
||||||
|
5 * HIDDEN_HAND_SIZE,
|
||||||
|
HIDDEN_HAND_SIZE,
|
||||||
|
undefined,
|
||||||
|
!showHands,
|
||||||
|
-GAME_CONSTANTS.PI / 2
|
||||||
|
);
|
||||||
|
this.hands[2].drawHand(
|
||||||
|
this.staticCtx,
|
||||||
|
1000 - 75 * 5 * HIDDEN_HAND_SIZE,
|
||||||
|
150 * HIDDEN_HAND_SIZE,
|
||||||
|
5 * HIDDEN_HAND_SIZE,
|
||||||
|
HIDDEN_HAND_SIZE,
|
||||||
|
undefined,
|
||||||
|
!showHands,
|
||||||
|
-GAME_CONSTANTS.PI
|
||||||
|
);
|
||||||
|
this.hands[3].drawHand(
|
||||||
|
this.staticCtx,
|
||||||
|
150 * HIDDEN_HAND_SIZE,
|
||||||
|
75 * 5 * HIDDEN_HAND_SIZE,
|
||||||
|
5 * HIDDEN_HAND_SIZE,
|
||||||
|
HIDDEN_HAND_SIZE,
|
||||||
|
undefined,
|
||||||
|
!showHands,
|
||||||
|
GAME_CONSTANTS.PI / 2
|
||||||
|
);
|
||||||
|
|
||||||
// Use a single drawImage operation instead of clearing and redrawing
|
// Draw groups and discards (no per-frame highlights)
|
||||||
|
this.drawGroups(GAME_CONSTANTS.DISPLAY.GROUP_SIZE);
|
||||||
|
for (let i = 0; i < GAME_CONSTANTS.PLAYERS; i++) {
|
||||||
|
this.drawDiscard(i, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.staticDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blit static buffer
|
||||||
this.ctx.clearRect(0, 0, this.cv.width, this.cv.height);
|
this.ctx.clearRect(0, 0, this.cv.width, this.cv.height);
|
||||||
this.ctx.drawImage(this.staticCv, 0, 0);
|
this.ctx.drawImage(this.staticCv, 0, 0);
|
||||||
|
|
||||||
|
// Draw dynamic overlay: selection, highlights, buttons, chiis
|
||||||
|
this.drawDynamic(this.ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDeck(): Deck {
|
public getDeck(): Deck {
|
||||||
|
|
@ -268,7 +426,7 @@ export class Game {
|
||||||
this.chii(chiis[0], 0);
|
this.chii(chiis[0], 0);
|
||||||
} else {
|
} else {
|
||||||
this.chooseChii = true;
|
this.chooseChii = true;
|
||||||
this.drawGame();
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
} else if (action === 2) { // Pon
|
} else if (action === 2) { // Pon
|
||||||
this.pon(this.turn);
|
this.pon(this.turn);
|
||||||
|
|
@ -309,17 +467,24 @@ export class Game {
|
||||||
|
|
||||||
const tileIndex = Math.floor(mouseX / (tileWidth * sizeHand));
|
const tileIndex = Math.floor(mouseX / (tileWidth * sizeHand));
|
||||||
const relativeX = mouseX - tileIndex * tileWidth * sizeHand;
|
const relativeX = mouseX - tileIndex * tileWidth * sizeHand;
|
||||||
|
// Determine whether selection changed and only mark static buffer dirty
|
||||||
if (
|
// when it did. This prevents ghosting where the static layer still
|
||||||
|
// contains the tile at its original position while the dynamic layer
|
||||||
|
// draws the lifted tile.
|
||||||
|
const inBounds = (
|
||||||
relativeX <= (tileWidth - 3) * sizeHand &&
|
relativeX <= (tileWidth - 3) * sizeHand &&
|
||||||
tileIndex >= 0 &&
|
tileIndex >= 0 &&
|
||||||
tileIndex < this.hands[0].length() &&
|
tileIndex < this.hands[0].length() &&
|
||||||
mouseY >= y &&
|
mouseY >= y &&
|
||||||
mouseY <= y + 100 * sizeHand
|
mouseY <= y + 100 * sizeHand
|
||||||
) {
|
);
|
||||||
this.selectedTile = tileIndex;
|
|
||||||
} else {
|
const newSelected = inBounds ? tileIndex : undefined;
|
||||||
this.selectedTile = undefined;
|
if (newSelected !== this.selectedTile) {
|
||||||
|
this.selectedTile = newSelected;
|
||||||
|
// Force static buffer rebuild so the static hand skips drawing the
|
||||||
|
// selected tile (we pass skipIndex when drawing static hand).
|
||||||
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,6 +604,12 @@ export class Game {
|
||||||
private pick(player: number): void {
|
private pick(player: number): void {
|
||||||
this.hands[player].push(this.deck.pop());
|
this.hands[player].push(this.deck.pop());
|
||||||
this.hands[player].isolate = true;
|
this.hands[player].isolate = true;
|
||||||
|
// mark hand as changed
|
||||||
|
this.handVersion[player] = (this.handVersion[player] || 0) + 1;
|
||||||
|
this.tenpaiCacheVersion[player] = -1;
|
||||||
|
// changing hand invalidates ron cache
|
||||||
|
this.canRonCache = { result: false };
|
||||||
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private discard(player: number, n: number): void {
|
private discard(player: number, n: number): void {
|
||||||
|
|
@ -453,6 +624,11 @@ export class Game {
|
||||||
|
|
||||||
this.lastDiscard = player;
|
this.lastDiscard = player;
|
||||||
this.lastPlayed = Date.now();
|
this.lastPlayed = Date.now();
|
||||||
|
// mark hand as changed
|
||||||
|
this.handVersion[player] = (this.handVersion[player] || 0) + 1;
|
||||||
|
this.tenpaiCacheVersion[player] = -1;
|
||||||
|
this.canRonCache = { result: false };
|
||||||
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private canDoAChii(p: number = 0): Array<number> {
|
private canDoAChii(p: number = 0): Array<number> {
|
||||||
|
|
@ -515,6 +691,11 @@ export class Game {
|
||||||
// Create new group
|
// Create new group
|
||||||
this.groups[p].push(new Group([t, tt[0], tt[1]], discardPlayer, p));
|
this.groups[p].push(new Group([t, tt[0], tt[1]], discardPlayer, p));
|
||||||
|
|
||||||
|
// mark hand changed for player p
|
||||||
|
this.handVersion[p] = (this.handVersion[p] || 0) + 1;
|
||||||
|
this.tenpaiCacheVersion[p] = -1;
|
||||||
|
this.canRonCache = { result: false };
|
||||||
|
|
||||||
if (this.hasWin(p)) {
|
if (this.hasWin(p)) {
|
||||||
this.resolveWin(p, p === 0 ? 1 : 2);
|
this.resolveWin(p, p === 0 ? 1 : 2);
|
||||||
}
|
}
|
||||||
|
|
@ -582,9 +763,18 @@ export class Game {
|
||||||
* We simulate adding any possible tile and check if it yields a winning hand.
|
* We simulate adding any possible tile and check if it yields a winning hand.
|
||||||
*/
|
*/
|
||||||
private isTenpai(player: number): boolean {
|
private isTenpai(player: number): boolean {
|
||||||
|
// Use cached result when hand hasn't changed
|
||||||
|
if (this.tenpaiCacheVersion[player] === this.handVersion[player]) {
|
||||||
|
return this.tenpaiCache[player];
|
||||||
|
}
|
||||||
|
|
||||||
const h = this.hands[player];
|
const h = this.hands[player];
|
||||||
// quick guard: hand should not already be winning
|
// quick guard: hand should not already be winning
|
||||||
if (h.toGroup() !== undefined) return false;
|
if (h.toGroup() !== undefined) {
|
||||||
|
this.tenpaiCache[player] = false;
|
||||||
|
this.tenpaiCacheVersion[player] = this.handVersion[player];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Try all possible tile types
|
// Try all possible tile types
|
||||||
// suits 1..3 values 1..9
|
// suits 1..3 values 1..9
|
||||||
|
|
@ -596,7 +786,11 @@ export class Game {
|
||||||
}
|
}
|
||||||
sim.push(new Tile(fam, val, false));
|
sim.push(new Tile(fam, val, false));
|
||||||
sim.sort();
|
sim.sort();
|
||||||
if (sim.toGroup() !== undefined) return true;
|
if (sim.toGroup() !== undefined) {
|
||||||
|
this.tenpaiCache[player] = true;
|
||||||
|
this.tenpaiCacheVersion[player] = this.handVersion[player];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -608,7 +802,11 @@ export class Game {
|
||||||
}
|
}
|
||||||
sim.push(new Tile(4, val, false));
|
sim.push(new Tile(4, val, false));
|
||||||
sim.sort();
|
sim.sort();
|
||||||
if (sim.toGroup() !== undefined) return true;
|
if (sim.toGroup() !== undefined) {
|
||||||
|
this.tenpaiCache[player] = true;
|
||||||
|
this.tenpaiCacheVersion[player] = this.handVersion[player];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dragons family 5 values 1..3
|
// dragons family 5 values 1..3
|
||||||
|
|
@ -619,9 +817,16 @@ export class Game {
|
||||||
}
|
}
|
||||||
sim.push(new Tile(5, val, false));
|
sim.push(new Tile(5, val, false));
|
||||||
sim.sort();
|
sim.sort();
|
||||||
if (sim.toGroup() !== undefined) return true;
|
if (sim.toGroup() !== undefined) {
|
||||||
|
this.tenpaiCache[player] = true;
|
||||||
|
this.tenpaiCacheVersion[player] = this.handVersion[player];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
this.tenpaiCache[player] = false;
|
||||||
|
this.tenpaiCacheVersion[player] = this.handVersion[player];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -640,8 +845,8 @@ export class Game {
|
||||||
private declareRiichi(player: number): void {
|
private declareRiichi(player: number): void {
|
||||||
this.declaredRiichi[player] = true;
|
this.declaredRiichi[player] = true;
|
||||||
// For now we only set the flag. In a full implementation we'd place the riichi stick,
|
// For now we only set the flag. In a full implementation we'd place the riichi stick,
|
||||||
// lock the hand, and handle the bet. We redraw to update UI.
|
// lock the hand, and handle the bet. Mark static buffer dirty so UI updates next frame.
|
||||||
this.drawGame();
|
this.staticDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private pon(p: number, thief: number = 0): void {
|
private pon(p: number, thief: number = 0): void {
|
||||||
|
|
@ -655,6 +860,11 @@ export class Game {
|
||||||
|
|
||||||
this.groups[thief].push(new Group([t, t2, t3], p, thief));
|
this.groups[thief].push(new Group([t, t2, t3], p, thief));
|
||||||
|
|
||||||
|
// mark thief hand changed
|
||||||
|
this.handVersion[thief] = (this.handVersion[thief] || 0) + 1;
|
||||||
|
this.tenpaiCacheVersion[thief] = -1;
|
||||||
|
this.canRonCache = { result: false };
|
||||||
|
|
||||||
if (this.hasWin(thief)) {
|
if (this.hasWin(thief)) {
|
||||||
this.resolveWin(thief, thief === 0 ? 1 : 2);
|
this.resolveWin(thief, thief === 0 ? 1 : 2);
|
||||||
}
|
}
|
||||||
|
|
@ -696,6 +906,10 @@ export class Game {
|
||||||
const canTsumoLocal = this.hasPicked && this.hasWin(0);
|
const canTsumoLocal = this.hasPicked && this.hasWin(0);
|
||||||
let canRonLocal = false;
|
let canRonLocal = false;
|
||||||
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
|
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
|
||||||
|
// Use cache when lastDiscard and player's hand version are unchanged
|
||||||
|
if (this.canRonCache.lastDiscard === this.lastDiscard && this.canRonCache.handVer === this.handVersion[0]) {
|
||||||
|
canRonLocal = this.canRonCache.result;
|
||||||
|
} else {
|
||||||
const d = this.discards[this.lastDiscard];
|
const d = this.discards[this.lastDiscard];
|
||||||
if (d && d.length > 0) {
|
if (d && d.length > 0) {
|
||||||
const last = d[d.length - 1];
|
const last = d[d.length - 1];
|
||||||
|
|
@ -706,6 +920,11 @@ export class Game {
|
||||||
sim.push(new Tile(last.getFamily(), last.getValue(), last.isRed()));
|
sim.push(new Tile(last.getFamily(), last.getValue(), last.isRed()));
|
||||||
sim.sort();
|
sim.sort();
|
||||||
canRonLocal = sim.toGroup() !== undefined;
|
canRonLocal = sim.toGroup() !== undefined;
|
||||||
|
} else {
|
||||||
|
canRonLocal = false;
|
||||||
|
}
|
||||||
|
// update cache
|
||||||
|
this.canRonCache = { lastDiscard: this.lastDiscard, handVer: this.handVersion[0], result: canRonLocal };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -327,13 +327,20 @@ export class Hand {
|
||||||
size: number,
|
size: number,
|
||||||
focusedTile: number | undefined = undefined,
|
focusedTile: number | undefined = undefined,
|
||||||
hidden: boolean = false,
|
hidden: boolean = false,
|
||||||
rotation: number = 0
|
rotation: number = 0,
|
||||||
|
// If provided, skip drawing this tile index (useful when the focused
|
||||||
|
// tile is rendered on a dynamic overlay to avoid seeing the unshifted
|
||||||
|
// tile under the lifted one).
|
||||||
|
skipIndex?: number
|
||||||
): void {
|
): void {
|
||||||
const tileOffset = (75 + offset) * size;
|
const tileOffset = (75 + offset) * size;
|
||||||
const offsetX = Math.cos(rotation) * tileOffset;
|
const offsetX = Math.cos(rotation) * tileOffset;
|
||||||
const offsetY = Math.sin(rotation) * tileOffset;
|
const offsetY = Math.sin(rotation) * tileOffset;
|
||||||
|
|
||||||
for (let i = 0; i < this.tiles.length; i++) {
|
for (let i = 0; i < this.tiles.length; i++) {
|
||||||
|
// Optionally skip drawing a specific tile (prevents ghosting when the
|
||||||
|
// focused tile is drawn separately in a dynamic overlay).
|
||||||
|
if (skipIndex !== undefined && i === skipIndex) continue;
|
||||||
const isLastAndIsolated = (i === this.tiles.length - 1 && this.isolate) ? 10 : 0;
|
const isLastAndIsolated = (i === this.tiles.length - 1 && this.isolate) ? 10 : 0;
|
||||||
|
|
||||||
// Calculate position
|
// Calculate position
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue