Premiers yakus + tests
This commit is contained in:
parent
8678f46816
commit
9122e245a5
9 changed files with 644 additions and 3 deletions
2
Makefile
2
Makefile
|
|
@ -1,3 +1,5 @@
|
|||
TESTS = $(wildcard build/test*)
|
||||
|
||||
all:
|
||||
npx webpack --mode development
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@
|
|||
document.body.appendChild(currentScript);
|
||||
}
|
||||
|
||||
function loadScript(scriptName) {
|
||||
function loadScript(scriptName, txt = true) {
|
||||
if (window.cleanup) {
|
||||
window.cleanup();
|
||||
}
|
||||
|
|
@ -155,9 +155,14 @@
|
|||
|
||||
document.body.appendChild(currentScript);
|
||||
|
||||
if (txt) {
|
||||
const number = scriptName.substring(2, scriptName.length - 3);
|
||||
loadText("txt" + number + ".js");
|
||||
}
|
||||
}
|
||||
|
||||
//tests
|
||||
loadScript('test_yakus.js', false);
|
||||
|
||||
//script initial
|
||||
loadScript('dp5.js');
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ export class Group {
|
|||
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(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ export class Hand {
|
|||
}
|
||||
}
|
||||
|
||||
public getTiles(): Array<Tile> {
|
||||
return this.tiles;
|
||||
}
|
||||
|
||||
public length(): number {
|
||||
return this.tiles.length;
|
||||
}
|
||||
|
|
|
|||
9
src/tests/assert.ts
Normal file
9
src/tests/assert.ts
Normal 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
42
src/tests/test_yakus.ts
Normal 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());
|
||||
14
src/tile.ts
14
src/tile.ts
|
|
@ -38,6 +38,20 @@ export class Tile {
|
|||
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 {
|
||||
this.tilt = (1 - 2 * Math.random()) * 0.04;
|
||||
}
|
||||
|
|
|
|||
549
src/yakus/yaku.ts
Normal file
549
src/yakus/yaku.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,8 +5,10 @@ const fs = require('fs');
|
|||
function getEntryPoints() {
|
||||
const displayDir = path.resolve(__dirname, 'src', 'display');
|
||||
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 texts = fs.readdirSync(textDir);
|
||||
const tests = fs.readdirSync(testDir);
|
||||
const entryPoints = {};
|
||||
|
||||
files.forEach((file) => {
|
||||
|
|
@ -21,6 +23,12 @@ function getEntryPoints() {
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue