cannot win without yaku

This commit is contained in:
Didictateur 2025-11-18 22:25:30 +01:00
parent e82cc8fc7a
commit 3333dbd011

View file

@ -138,7 +138,7 @@ export class Game {
if (this.chooseChii) { if (this.chooseChii) {
drawChiis(ctx, this.getChii(0)); drawChiis(ctx, this.getChii(0));
} else { } else {
const canTsumoLocal = this.hasPicked && this.hasWin(0); const canTsumoLocal = this.hasPicked && this.hasWin(0) && (!this.enableYakus || this.handHasAnyYaku(this.hands[0], this.groups[0]));
let canRonLocal = false; let canRonLocal = false;
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) { if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
if (this.canRonCache.lastDiscard === this.lastDiscard && this.canRonCache.handVer === this.handVersion[0]) { if (this.canRonCache.lastDiscard === this.lastDiscard && this.canRonCache.handVer === this.handVersion[0]) {
@ -153,7 +153,9 @@ 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; const simGroups = sim.toGroup() as Array<Group> | undefined;
const combinedGroups = this.groups[0].concat(simGroups ? simGroups : []);
canRonLocal = sim.toGroup() !== undefined && (!this.enableYakus || this.handHasAnyYaku(sim, combinedGroups));
} else { } else {
canRonLocal = false; canRonLocal = false;
} }
@ -227,6 +229,27 @@ export class Game {
this.staticDirty = true; this.staticDirty = true;
} }
/**
* Check whether a given hand (possibly simulated) yields at least one yaku.
* Does not modify `lastYakus`/`lastTotalHan` only inspects using yakus detectors.
*/
private handHasAnyYaku(hand: Hand, groups: Array<Group> = []): boolean {
try {
for (const k of Object.keys(yakus)) {
const fn = (yakus as any)[k];
try {
const han = fn(hand, groups, this.windPlayer);
if (han && han > 0) return true;
} catch (e) {
// ignore detector errors
}
}
} catch (e) {
// ignore
}
return false;
}
private initializeGame(): void { private initializeGame(): void {
this.deck.shuffle(); this.deck.shuffle();
@ -350,7 +373,7 @@ export class Game {
public click(mp: MousePos): void { public click(mp: MousePos): void {
const rect = this.cv.getBoundingClientRect(); const rect = this.cv.getBoundingClientRect();
if (this.hasWin(0)) { if (this.hasWin(0) && (!this.enableYakus || this.handHasAnyYaku(this.hands[0], this.groups[0]))) {
this.resolveWin(0, 1); this.resolveWin(0, 1);
return; return;
} }
@ -361,7 +384,7 @@ export class Game {
} }
// Determine if Ron/Tsumo should be available for player 0 // Determine if Ron/Tsumo should be available for player 0
const canTsumo = this.hasPicked && this.hasWin(0); const canTsumo = this.hasPicked && this.hasWin(0) && (!this.enableYakus || this.handHasAnyYaku(this.hands[0], this.groups[0]));
let canRon = false; let canRon = false;
if (this.lastDiscard !== undefined && this.lastDiscard !== 0) { if (this.lastDiscard !== undefined && this.lastDiscard !== 0) {
const d = this.discards[this.lastDiscard]; const d = this.discards[this.lastDiscard];
@ -373,7 +396,9 @@ 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();
canRon = sim.toGroup() !== undefined; const simGroups = sim.toGroup() as Array<Group> | undefined;
const combinedGroups = this.groups[0].concat(simGroups ? simGroups : []);
canRon = sim.toGroup() !== undefined && (!this.enableYakus || this.handHasAnyYaku(sim, combinedGroups));
} }
} }
@ -514,7 +539,7 @@ export class Game {
} }
private handleBotTurn(): void { private handleBotTurn(): void {
if (this.hasWin(this.turn)) { if (this.hasWin(this.turn) && (!this.enableYakus || this.handHasAnyYaku(this.hands[this.turn], this.groups[this.turn]))) {
this.resolveWin(this.turn, 2); this.resolveWin(this.turn, 2);
return; return;
} }
@ -567,7 +592,9 @@ 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();
if (sim.toGroup() !== undefined) return true; const simGroups = sim.toGroup() as Array<Group> | undefined;
const combinedGroups = this.groups[p].concat(simGroups ? simGroups : []);
if (sim.toGroup() !== undefined && (!this.enableYakus || this.handHasAnyYaku(sim, combinedGroups))) return true;
} }
return false; return false;
@ -589,7 +616,7 @@ export class Game {
if (this.turn === 3) { if (this.turn === 3) {
this.turn = 0; this.turn = 0;
this.pick(0); this.pick(0);
if (this.hasWin(0)) { if (this.hasWin(0) && (!this.enableYakus || this.handHasAnyYaku(this.hands[0], this.groups[0]))) {
this.resolveWin(0, 1); this.resolveWin(0, 1);
} }
} else { } else {
@ -696,7 +723,7 @@ export class Game {
this.tenpaiCacheVersion[p] = -1; this.tenpaiCacheVersion[p] = -1;
this.canRonCache = { result: false }; this.canRonCache = { result: false };
if (this.hasWin(p)) { if (this.hasWin(p) && (!this.enableYakus || this.handHasAnyYaku(this.hands[p], this.groups[p]))) {
this.resolveWin(p, p === 0 ? 1 : 2); this.resolveWin(p, p === 0 ? 1 : 2);
} }
@ -865,7 +892,7 @@ export class Game {
this.tenpaiCacheVersion[thief] = -1; this.tenpaiCacheVersion[thief] = -1;
this.canRonCache = { result: false }; this.canRonCache = { result: false };
if (this.hasWin(thief)) { if (this.hasWin(thief) && (!this.enableYakus || this.handHasAnyYaku(this.hands[thief], this.groups[thief]))) {
this.resolveWin(thief, thief === 0 ? 1 : 2); this.resolveWin(thief, thief === 0 ? 1 : 2);
} }
@ -903,7 +930,7 @@ export class Game {
drawChiis(this.staticCtx, this.getChii(0)); drawChiis(this.staticCtx, this.getChii(0));
} else { } else {
// Compute availability of ron/tsumo for player 0 // Compute availability of ron/tsumo for player 0
const canTsumoLocal = this.hasPicked && this.hasWin(0); const canTsumoLocal = this.hasPicked && this.hasWin(0) && (!this.enableYakus || this.handHasAnyYaku(this.hands[0], this.groups[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 // Use cache when lastDiscard and player's hand version are unchanged
@ -919,7 +946,9 @@ 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; const simGroups = sim.toGroup() as Array<Group> | undefined;
const combinedGroups = this.groups[0].concat(simGroups ? simGroups : []);
canRonLocal = sim.toGroup() !== undefined && (!this.enableYakus || this.handHasAnyYaku(sim, combinedGroups));
} else { } else {
canRonLocal = false; canRonLocal = false;
} }