Premiers yakus + tests

This commit is contained in:
Didictateur 2025-03-30 23:57:10 +02:00
parent 8678f46816
commit 9122e245a5
9 changed files with 644 additions and 3 deletions

View file

@ -1,3 +1,5 @@
TESTS = $(wildcard build/test*)
all: all:
npx webpack --mode development npx webpack --mode development

View file

@ -139,7 +139,7 @@
document.body.appendChild(currentScript); document.body.appendChild(currentScript);
} }
function loadScript(scriptName) { function loadScript(scriptName, txt = true) {
if (window.cleanup) { if (window.cleanup) {
window.cleanup(); window.cleanup();
} }
@ -155,10 +155,15 @@
document.body.appendChild(currentScript); document.body.appendChild(currentScript);
const number = scriptName.substring(2, scriptName.length - 3); if (txt) {
loadText("txt" + number + ".js"); const number = scriptName.substring(2, scriptName.length - 3);
loadText("txt" + number + ".js");
}
} }
//tests
loadScript('test_yakus.js', false);
//script initial //script initial
loadScript('dp5.js'); loadScript('dp5.js');
</script> </script>

View file

@ -27,6 +27,14 @@ export class Group {
return this.tiles; return this.tiles;
} }
public compare(g: Group): number {
let c = this.tiles[0].compare(g.tiles[0]);
if (c !== 0) {
return c;
}
return this.tiles[1].compare(g.tiles[1]);
}
public drawGroup( public drawGroup(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
x: number, x: number,

View file

@ -23,6 +23,10 @@ export class Hand {
} }
} }
public getTiles(): Array<Tile> {
return this.tiles;
}
public length(): number { public length(): number {
return this.tiles.length; return this.tiles.length;
} }

9
src/tests/assert.ts Normal file
View file

@ -0,0 +1,9 @@
export function assert(b: boolean, msg: string): number {
if (b) {
console.log("%c[SUCCES] " + msg, "color: green");
return 1;
} else {
console.log("%c[ECHEC] " + msg, "color: red");
return 0;
}
}

42
src/tests/test_yakus.ts Normal file
View file

@ -0,0 +1,42 @@
import { assert } from "./assert"
import { yakus } from "../yakus/yaku"
import { Hand } from "../hand"
let count = 0;
let total = 0;
let h1 = new Hand("m1m2m3 m4m5m6 m7m8m9 p1p2p3 p5p5");
let h2 = new Hand("m1m1m1 m4m4m4 m7m7m7 p1p1p1 p5p5");
let h3 = new Hand("m1m1m1p9p9p9s1s1s1w2w2w2d1d1");
let h4 = new Hand("m2m3m4 p3p3p3 p4p5p6 s4s4 s6s7s8");
let h5 = new Hand("m1m2m3m1m2m3p7p8p9p7p8p9w3w3");
let h6 = new Hand("m1m2m3 m1m2m3 p6p7p8 p7p8p9 w3w3");
let h7 = new Hand("m1m2m3 p1p2p3 s1s2s3 m9m9m9 p9p9");
// lipeikou
count += assert(yakus.lipekou(h5, [], 0) === 1, "m123 m123 p789 p789 w33 is Lipeikou");
count += assert(yakus.lipekou(h3, [], 0) === 0, "m111 p999 s111 w222 d11 is not Lipeikou");
total += 2;
// ryanpeikou
count += assert(yakus.ryanpeikou(h5, [], 0) === 3, "m123 m123 p789 p789 w33 is Ryanpeikou");
count += assert(yakus.ryanpeikou(h6, [], 0) === 0, "m1123 m123 p678 p789 w33 is not Ryanpeikou");
total += 2;
// pinfu
count += assert(yakus.pinfu(h1, [], 0) === 1, "m123 m456 m789 m123 p55 is Pinfu");
count += assert(yakus.pinfu(h2, [], 0) === 0, "m111 m444 m777 p111 p55 is not Pinfu");
total += 2;
// sanshoku doujun
count += assert(yakus.sanshokuDoujun(h7, [], 0) === 2, "m123 p123 s123 m999 p99 is Shanshokou Doujun");
count += assert(yakus.sanshokuDoujun(h2, [], 0) === 0, "m111 m444 m777 p111 p55 is not Shanshokou Doujun");
total += 2;
// chanta
count += assert(yakus.chanta(h3, [], 0) === 2, "m111 p999 s111 w222 d11 is Chanta");
count += assert(yakus.chanta(h1, [], 0) === 0, "m123 m456 m789 m123 p55 is not Chanta");
total += 2;
// total
console.log("Succès: " + count.toString() + "/" + total.toString());

View file

@ -38,6 +38,20 @@ export class Tile {
return this.red; return this.red;
} }
public compare(t: Tile): number {
if (this.family < t.family) {
return -1;
} else if (this.family > t.family) {
return 1;
}
if (this.value < t.value) {
return -1;
} else if (this.value > t.value) {
return 1;
}
return 0;
}
public setTilt(): void { public setTilt(): void {
this.tilt = (1 - 2 * Math.random()) * 0.04; this.tilt = (1 - 2 * Math.random()) * 0.04;
} }

549
src/yakus/yaku.ts Normal file
View file

@ -0,0 +1,549 @@
import { Hand } from "../hand";
import { Group } from "../group";
function ord(g: Group): boolean {
return g.getTiles().every(
t => t.getFamily() < 4 &&
t.getValue() > 1 &&
t.getValue() < 9
)
}
function term(g: Group): boolean {
return g.getTiles().every(
t =>
t.getFamily() < 4 &&
(t.getValue() === 0 ||
t.getValue() === 9)
);
}
function honn(g: Group): boolean {
return g.getTiles().every(
t =>
t.getFamily() >= 4
);
}
function chii(g: Group): boolean {
let t = g.getTiles();
return t[0].getValue() !== t[1].getValue();
}
function pon(g: Group): boolean {
let t = g.getTiles();
return t[0].getValue() === t[1].getValue();
}
export const yakus = {
lipekou: function (
/**
* double suite pure
* 0/1
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
if (groups.length > 0 && !yakus.ryanpeikou(hand, groups, wind)) { // ouvert
return 0;
}
let gr = hand.toGroup() as NonNullable<Array<Group>>;
gr.sort((g1, g2) => g1.compare(g2));
for (let i = 0; i < 3; i++) {
let g1 = gr[i].getTiles();
let g2 = gr[i+1].getTiles();
if (
g1[0].isEqual(g2[0].getFamily(), g2[0].getValue()) &&
chii(gr[i]) && chii(gr[i+1])
) {
return 1;
}
}
return 0;
},
ryanpeikou: function (
/**
* deux doubles suites pures
* 0/3
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
if (groups.length > 0) {
return 0;
}
let gr = hand.toGroup() as NonNullable<Array<Group>>;
gr.filter(g => chii(g));
if (gr.length < 4) { // pas assez de suite
return 0;
}
gr.sort((g1, g2) => g1.compare(g2));
let t1 = gr[0].getTiles()[0];
let t2 = gr[1].getTiles()[0];
let t3 = gr[2].getTiles()[0];
let t4 = gr[3].getTiles()[0];
if (
t1.compare(t2) === 0 &&
t3.compare(t4) === 0
) {
return 3;
}
return 0;
},
pinfu: function(
/**
* tout suite
* 0/1
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number { //TODO: double attente
if (groups.length > 0) {
return 0;
}
let h = hand.toGroup();
if (
h !== undefined &&
h.every(
g => {
let tiles = g.getTiles();
return (chii(g) || tiles.length === 2)
}
)
) {
return 1;
}
return 0;
},
sanshokuDoujun: function( // TODO
/**
* triple suite
* 1/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
ittsuu: function(
/**
* grande suite pure
* 1/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let gr = groups.concat(hand.toGroup() as NonNullable<Array<Group>>);
gr.sort((g1, g2) => g1.compare(g2));
gr.filter(g => chii(g));
if (gr.length < 3) { // trop peu de suite
return 0;
} else if (gr.length === 3) { // pile le bon nombre
let g1 = gr[0].getTiles();
let g2 = gr[1].getTiles();
let g3 = gr[2].getTiles();
if (
g1[0].getFamily() === g2[0].getFamily() &&
g2[0].getFamily() === g3[0].getFamily() &&
g1[0].getValue() === 1 &&
g2[0].getValue() === 4 &&
g3[0].getValue() === 7
) {
return groups.length > 0 ? 1 : 2;
}
} else { // il y a un intrus
return 0 //TODO
}
return 0;
},
tanyao: function(
/**
* tout ordinaire
* 1/1
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
if (h === undefined) {
return 0;
}
if (
groups.every(g => ord(g)) &&
h.every(g => ord(g))
) {
return 1;
}
return 0;
},
yakuhai: function(
/**
* brelan de valeur
* 1/1
*/
// TODO multiplicité !
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
shousangen: function( //TODO
/**
* trois petits dragons
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
daisangen: function( //TODO
/**
* trois grands dragons
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
shousuushii: function( //TODO
/**
* quatre petits vents
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
daisuushi: function( //TODO
/**
* quatre grands vents
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
chanta: function( //TODO
/**
* terminales et honneurs partout
* 1/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
junchan: function( //TODO
/**
* terminales partout
* 2/3
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
honroutou: function( //TODO
/**
* tout terminale et honneur
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
chinroutou: function(
/**
* tout terminale
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
if (h === undefined) {
return 0;
}
if (
groups.every(g => term(g)) &&
h.every(g => term(g))
) {
return 13;
}
return 0;
},
tsuuiisou: function(
/**
* tout honneur
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
if (h === undefined) {
return 0;
}
if (
groups.every(g => honn(g)) &&
h.every(g => honn(g))
) {
return 13;
}
return 0;
},
kokushiMusou: function(
/**
* treize orphelins
* 0/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
if (groups.length > 0) {
return 0;
}
if (yakus.honroutou(hand, groups, wind) === 0) {
return 0;
}
let h = hand.getTiles();
let count = 0;
for (let i = 0; i < h.length - 1; i++) {
if (h[i].isEqual(h[i+1].getFamily(), h[i+1].getValue())) {
count++;
}
if (count > 1) {
break;
}
}
if (count === 1) {
return 13;
}
return 0;
},
chiitoitsu: function(
/**
* sept paires
* 0/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
if (groups.length > 0) {
return 0;
}
//TODO
},
toitoi: function(
/**
* tout brelans
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
if (
groups.every(g => pon(g)) &&
h?.every(g => pon(g))
) {
return 2;
}
return 0;
},
sanankou: function(
/**
* trois brelan cachés
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
let count = 0;
h?.forEach(
g => {
if (pon(g) && g.getTiles().length === 3) {
count++;
}
}
);
if (count === 3) {
return 2;
}
return 0;
},
suuankou: function(
/**
* quatre brelans cachés
* 0/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.toGroup();
let count = 0;
h?.forEach(
g => {
if (pon(g) && g.getTiles().length === 3) {
count++;
}
}
);
if (count === 4) {
return 13;
}
return 0;
},
sanshokuDoukou: function( //TODO
/**
* triple brelan
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
sankantsu: function( //TODO
/**
* trois carrés
* 2/2
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
suukantsu: function( //TODO
/**
* quatre carrés
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
honitsu: function( //TODO
/**
* semie pure
* 2/3
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
return 0;
},
chinitsu: function(
/**
* main pure
* 5/6
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
let h = hand.getTiles();
let t0 = h[0];
if (
h.every(t => t.getFamily() === t0.getFamily()) &&
groups.every(g => g.getTiles().every(t => t.getFamily() === t0.getFamily()))
) {
return groups.length > 0 ? 5 : 6;
}
return 0;
},
ryuuisou: function( //TODO
/**
* main verte
* 13/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
if (yakus.chinitsu(hand, groups, wind) === 0) {
return 0;
}
return 0;
},
chuurenPoutou: function( //TODO
/**
* neuf portes
* 0/13
*/
hand: Hand,
groups: Array<Group>,
wind: number
): number {
if (groups.length > 0 || yakus.chinitsu(hand, groups, wind) === 0) {
return 0;
}
return 0;
}
}

View file

@ -5,8 +5,10 @@ const fs = require('fs');
function getEntryPoints() { function getEntryPoints() {
const displayDir = path.resolve(__dirname, 'src', 'display'); const displayDir = path.resolve(__dirname, 'src', 'display');
const textDir = path.resolve(__dirname, 'src', 'text'); const textDir = path.resolve(__dirname, 'src', 'text');
const testDir = path.resolve(__dirname, 'src', 'tests');
const files = fs.readdirSync(displayDir); // Lire les fichiers du répertoire const files = fs.readdirSync(displayDir); // Lire les fichiers du répertoire
const texts = fs.readdirSync(textDir); const texts = fs.readdirSync(textDir);
const tests = fs.readdirSync(testDir);
const entryPoints = {}; const entryPoints = {};
files.forEach((file) => { files.forEach((file) => {
@ -21,6 +23,12 @@ function getEntryPoints() {
entryPoints[name] = path.join(textDir, file); entryPoints[name] = path.join(textDir, file);
} }
}); });
tests.forEach((file) => {
if (file.startsWith('test') && file.endsWith('.ts')) {
const name = path.basename(file, '.ts');
entryPoints[name] = path.join(testDir, file);
}
});
return entryPoints; return entryPoints;
} }