From 4c65bb0a6536e6308c648d89a766fa8026cc94f5 Mon Sep 17 00:00:00 2001 From: Didictateur Date: Tue, 15 Apr 2025 22:32:14 +0200 Subject: [PATCH] =?UTF-8?q?d=C3=A9but=20de=20chapitre=207=20et=20g=C3=A9n?= =?UTF-8?q?=C3=A9rateur=20de=20yakus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/dp7.js | 1 + build/txt7.js | 1 + src/display/dp7.ts | 182 +++++++++++++++++++++++++++++++++++++++++ src/text/txt7.ts | 18 ++++ src/text/txt7.txt | 23 ++++++ src/yakus/generator.ts | 84 +++++++++++++++++++ 6 files changed, 309 insertions(+) create mode 100644 build/dp7.js create mode 100644 build/txt7.js create mode 100644 src/display/dp7.ts create mode 100644 src/text/txt7.ts create mode 100644 src/text/txt7.txt create mode 100644 src/yakus/generator.ts diff --git a/build/dp7.js b/build/dp7.js new file mode 100644 index 0000000..4196644 --- /dev/null +++ b/build/dp7.js @@ -0,0 +1 @@ +(()=>{"use strict";class t{constructor(t,i,e){this.family=t,this.value=i,this.red=e,this.imgFront=new Image,this.imgBack=new Image,this.imgGray=new Image,this.img=new Image,this.imgSrc="",this.tilt=0,this.setImgSrc()}getFamily(){return this.family}getValue(){return this.value}isEqual(t,i){return this.family===t&&this.value===i}isRed(){return this.red}compare(t){return this.family!==t.family?this.family{t.onload=null,t.onerror=null}))}preloadImg(){return t=this,i=void 0,s=function*(){const t=[{img:this.imgFront,src:"img/Regular/Front.svg"},{img:this.imgBack,src:"img/Regular/Back.svg"},{img:this.imgGray,src:"img/Regular/Gray.svg"},{img:this.img,src:this.imgSrc}];yield Promise.all(t.map((({img:t,src:i})=>this.loadImg(t,i))))},new((e=void 0)||(e=Promise))((function(n,l){function h(t){try{a(s.next(t))}catch(t){l(t)}}function r(t){try{a(s.throw(t))}catch(t){l(t)}}function a(t){var i;t.done?n(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(h,r)}a((s=s.apply(t,i||[])).next())}));var t,i,e,s}loadImg(t,i){return new Promise(((e,s)=>{t.onload=()=>e(),t.onerror=()=>s(),t.src=i}))}setImgSrc(){if(this.imgSrc="img/Regular/",this.family<=3){const t=["","Man","Pin","Sou"];this.imgSrc+=t[this.family]+String(this.value),this.red&&(this.imgSrc+="-Dora")}else if(4===this.family){const t=["","Ton","Nan","Shaa","Pei"];this.imgSrc+=t[this.value]}else if(5===this.family){const t=["","Chun","Hatsu","Haku"];this.imgSrc+=t[this.value]}this.imgSrc+=".svg"}}class i{constructor(t,i,e){this.tiles=t,this.stolenFrom=i,this.belongsTo=e}push(t){this.tiles.push(t)}pop(){return this.tiles.pop()}getTiles(){return this.tiles}compare(t){const i=this.tiles[0].compare(t.tiles[0]);return 0!==i?i:this.tiles[1].compare(t.tiles[1])}drawGroup(t,i,e,s,n,l,h){t.save(),t.translate(525,525),t.rotate(l),t.translate(-525,-525);const r=75*n,a=90*n,o=25*n/2,u=(this.belongsTo-this.stolenFrom-1+4)%4,c=void 0===h?-1:h.getFamily(),d=void 0===h?0:h.getValue(),m=(i,e,s,l)=>{i.drawTile(t,e,s,n,!1,l,i.isEqual(c,d))},f=Math.PI/2;switch(u){case 0:m(this.tiles[0],i,e+o,f),m(this.tiles[1],i+a,e,0),m(this.tiles[2],i+a+r+s*n,e,0);break;case 1:m(this.tiles[0],i,e,0),m(this.tiles[1],i+a,e+o,-f),m(this.tiles[2],i+a+r+3*s*n,e,0);break;case 2:m(this.tiles[0],i,e,0),m(this.tiles[1],i+r+s*n,e,0),m(this.tiles[2],i+a+r+s*n,e+o,-f);break;default:console.error(`Position non prise en charge: ${u}`)}t.restore()}}const e="m",s=1,n="p",l=2,h="s",r=3,a="w",o=4,u="d",c=5;class d{constructor(i){this.tiles=i.map((i=>new t(i.getFamily(),i.getValue(),i.isRed()))),this.sort()}sort(){this.tiles.sort(((t,i)=>t.isLessThan(i)?-1:1))}find(t,i){const e=this.findTileIndex(t,i);if(-1!==e){[this.tiles[e],this.tiles[0]]=[this.tiles[0],this.tiles[e]];const t=this.tiles.shift();return this.sort(),t}}findTileIndex(t,i){return this.tiles.findIndex((e=>e.getFamily()===t&&e.getValue()===i))}count(t,i){return this.tiles.filter((e=>e.getFamily()===t&&e.getValue()===i)).length}findGroups(t=!1){if(0===this.tiles.length)return[];const i=this.tiles.pop(),e=i.getFamily(),s=i.getValue();if(this.count(e,s)>=1&&!t){const t=this.tryFormPair(i);if(t)return t}if(this.count(e,s)>=2){const e=this.tryFormTriplet(i,t);if(e)return e}if(e<=3){const n=this.count(e,s-1)>0,l=this.count(e,s-2)>0;if(n&&l){const e=this.tryFormSequence(i,t);if(e)return e}}this.tiles.push(i),this.sort()}tryFormPair(t){const e=this.find(t.getFamily(),t.getValue()),s=this.findGroups(!0);if(this.tiles.push(e),this.sort(),void 0!==s)return this.tiles.push(t),this.sort(),s.push(new i([t,e],0,0)),s}tryFormTriplet(t,e){const s=this.find(t.getFamily(),t.getValue()),n=this.find(t.getFamily(),t.getValue()),l=this.findGroups(e);if(this.tiles.push(s),this.tiles.push(n),this.sort(),void 0!==l)return l.push(new i([t,s,n],0,0)),this.tiles.push(t),this.sort(),l}tryFormSequence(t,e){const s=this.find(t.getFamily(),t.getValue()-1),n=this.find(t.getFamily(),t.getValue()-2),l=this.findGroups(e);if(this.tiles.push(s),this.tiles.push(n),this.sort(),void 0!==l)return l.push(new i([n,s,t],0,0)),this.tiles.push(t),this.sort(),l}}class m{constructor(t=""){this.isolate=!1,this.tiles=[],this.initializeFromString(t)}initializeFromString(t){for(let i=0;ie.getFamily()===t&&e.getValue()===i))}eject(t){if(t<0||t>=this.tiles.length)throw new Error("Invalid tile index");[this.tiles[0],this.tiles[t]]=[this.tiles[t],this.tiles[0]];const i=this.tiles.shift();return this.sort(),i}get(t){if(!(void 0===t||t<0||t>=this.tiles.length))return this.tiles[t]}sort(){this.tiles.sort(((t,i)=>t.isLessThan(i)?-1:1))}count(t,i){return this.tiles.filter((e=>e.getFamily()===t&&e.getValue()===i)).length}toGroup(t=!1){return new d(this.tiles).findGroups(t)}drawHand(t,i,e,s,n,l=void 0,h=!1,r=0){const a=(75+s)*n,o=Math.cos(r)*a,u=Math.sin(r)*a;for(let s=0;st.preloadImg())))},new((e=void 0)||(e=Promise))((function(n,l){function h(t){try{a(s.next(t))}catch(t){l(t)}}function r(t){try{a(s.throw(t))}catch(t){l(t)}}function a(t){var i;t.done?n(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(h,r)}a((s=s.apply(t,i||[])).next())}));var t,i,e,s}cleanup(){this.tiles.forEach((t=>t.cleanup())),this.tiles=[]}}class f{constructor(t=!1){this.tiles=[],this.tileIndexMap=new Map,this.initTiles(t)}displayFamilies(t,i,e,s,n=0,l=0){let h=i,r=e;const a=[{family:1,start:1,end:9},{family:2,start:1,end:9},{family:3,start:1,end:9},{family:4,start:1,end:4},{family:5,start:1,end:3}];for(const e of a){for(let i=e.start;i<=e.end;i++){const l=this.find(e.family,i);l&&(l.drawTile(t,h,r,s,!1,0,!1),h+=(75+n)*s)}h=i,r+=(100+l)*s}}length(){return this.tiles.length}pop(){if(0===this.tiles.length)throw new Error("Cannot pop from an empty deck");const t=this.tiles.pop(),i=this.getTileKey(t.getFamily(),t.getValue()),e=this.tileIndexMap.get(i);return e&&e.length>0&&(e.pop(),0===e.length?this.tileIndexMap.delete(i):this.tileIndexMap.set(i,e)),t}push(t){const i=this.tiles.length;this.tiles.push(t);const e=this.getTileKey(t.getFamily(),t.getValue()),s=this.tileIndexMap.get(e)||[];s.push(i),this.tileIndexMap.set(e,s)}find(t,i){const e=this.getTileKey(t,i),s=this.tileIndexMap.get(e);if(!s||0===s.length)return;const n=s[0];if(n>=this.tiles.length)return this.rebuildIndexMap(),this.find(t,i);[this.tiles[n],this.tiles[0]]=[this.tiles[0],this.tiles[n]],this.updateIndicesAfterSwap(0,n);const l=this.tiles.shift();return this.decrementIndicesAfterShift(),l}count(t,i){const e=this.getTileKey(t,i),s=this.tileIndexMap.get(e);return s?s.length:0}shuffle(){for(let t=this.tiles.length-1;t>0;t--){const i=Math.floor(Math.random()*(t+1));[this.tiles[t],this.tiles[i]]=[this.tiles[i],this.tiles[t]]}this.rebuildIndexMap()}getRandomHand(){const t=new m;if(this.shuffle(),this.tiles.length<13)throw new Error("Not enough tiles in deck to create a hand");for(let i=0;i<13;i++)t.push(this.pop());return t}cleanup(){this.tiles.forEach((t=>t.cleanup())),this.tiles=[],this.tileIndexMap.clear()}preload(){return t=this,i=void 0,s=function*(){const t=this.tiles.map((t=>t.preloadImg()));yield Promise.all(t)},new((e=void 0)||(e=Promise))((function(n,l){function h(t){try{a(s.next(t))}catch(t){l(t)}}function r(t){try{a(s.throw(t))}catch(t){l(t)}}function a(t){var i;t.done?n(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(h,r)}a((s=s.apply(t,i||[])).next())}));var t,i,e,s}getTileKey(t,i){return`${t}-${i}`}updateIndicesAfterSwap(t,i){if(t===i)return;const e=this.tiles[t],s=this.tiles[i],n=this.getTileKey(e.getFamily(),e.getValue()),l=this.getTileKey(s.getFamily(),s.getValue()),h=this.tileIndexMap.get(n)||[],r=this.tileIndexMap.get(l)||[],a=h.indexOf(i),o=r.indexOf(t);-1!==a&&(h[a]=t),-1!==o&&(r[o]=i),this.tileIndexMap.set(n,h),this.tileIndexMap.set(l,r)}decrementIndicesAfterShift(){var t;for(const[i,e]of this.tileIndexMap.entries())this.tileIndexMap.set(i,e.filter((t=>0!==t)).map((t=>t>0?t-1:t))),0===(null===(t=this.tileIndexMap.get(i))||void 0===t?void 0:t.length)&&this.tileIndexMap.delete(i)}rebuildIndexMap(){this.tileIndexMap.clear();for(let t=0;t1){let h=i.find(s,l),r=i.find(s,l);t.push(e),t.push(h),t.push(r),n+=3}else i.push(e),i.shuffle()}else{let e=i.pop(),s=e.getFamily(),l=e.getValue();if(s<4&&1!==l&&l<6&&i.count(s,l+1)>0&&i.count(s,l+2)>0){let h=i.find(s,l+1),r=i.find(s,l+2);t.push(e),t.push(h),t.push(r),n+=3}else i.push(e),i.shuffle()}return t.sort(),t},sept_pairs(){let i=new m,e=new f;e.shuffle();let s=0;for(;7!==s;){let n=e.pop();i.getTiles().every((t=>0!==t.compare(n)))&&(s++,i.push(n),i.push(new t(n.getFamily(),n.getValue(),!1)))}return i.sort(),i}};var p=function(t,i,e,s){return new(e||(e=Promise))((function(n,l){function h(t){try{a(s.next(t))}catch(t){l(t)}}function r(t){try{a(s.throw(t))}catch(t){l(t)}}function a(t){var i;t.done?n(t.value):(i=t.value,i instanceof e?i:new e((function(t){t(i)}))).then(h,r)}a((s=s.apply(t,i||[])).next())}))};const y="myCanvas",w=1e3/30;class v{constructor(t=y){this.hands=[],this.animationFrameId=null,this.lastFrameTime=0,this.cleanupCallbacks=[],this.BASE_X=50,this.BASE_Y=90,this.DY=180,this.SIZE=.75;const i=document.getElementById(t);if(!i)throw new Error(`Canvas avec ID '${t}' introuvable`);this.canvas=i;const e=i.getContext("2d");if(!e)throw new Error("Impossible d'obtenir le contexte 2D du canvas");this.ctx=e}drawFrame(){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.drawHandsAndLabels()}drawHandsAndLabels(){for(let t=0;t{this.animationFrameId=requestAnimationFrame(t);const e=i-this.lastFrameTime;ethis.preloadHand(t)))]),this.drawFrame(),this.startAnimationLoop()}catch(t){console.error("Erreur d'initialisation:",t)}}))}cleanup(){null!==this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.cleanupCallbacks.forEach((t=>t())),this.cleanupCallbacks=[],this.hands.forEach((t=>t.cleanup())),this.hands=[]}}let I=null;function F(){I&&(I.cleanup(),I=null)}"undefined"!=typeof window&&function(){return p(this,void 0,void 0,(function*(){try{I=new v(y),yield I.initialize(),window.cleanup=F}catch(t){console.error("Erreur lors de l'initialisation:",t)}}))}().catch(console.error)})(); \ No newline at end of file diff --git a/build/txt7.js b/build/txt7.js new file mode 100644 index 0000000..2fca29a --- /dev/null +++ b/build/txt7.js @@ -0,0 +1 @@ +(()=>{"use strict";const t=document.getElementById("myTextCanvas"),n=t.getContext("2d");t.width=800,t.height=1050,n.fillStyle="#007733",n.fillRect(0,0,800,1050),function(t,n){return e=this,o=void 0,f=function*(){console.log(t,"\n");const e=yield fetch(t).then((t=>t.text())),o="#ffffff";n.fillStyle=o,n.font="30px Garamond";let l=!1,f="",i="",c="",a=1,r=0;for(var d of e)"*"===d?(i=""===i?"bold ":"",n.font=c+i+30+"px Garamond"):"~"===d?(c=""===c?"italic ":"",n.font=c+i+30+"px Garamond"):"#"===d?(f="#",l=!0):"{"===d?(l=!1,n.fillStyle=f):"}"===d?(f="",n.fillStyle=o):l?f+=d:"\n"===d?(a++,r=0):(n.fillText(d,10+r,20+35*a),r+=n.measureText(d).width)},new((l=void 0)||(l=Promise))((function(t,n){function i(t){try{a(f.next(t))}catch(t){n(t)}}function c(t){try{a(f.throw(t))}catch(t){n(t)}}function a(n){var e;n.done?t(n.value):(e=n.value,e instanceof l?e:new l((function(t){t(e)}))).then(i,c)}a((f=f.apply(e,o||[])).next())}));var e,o,l,f}("src/text/txt7.txt",n).catch((t=>console.error(t)))})(); \ No newline at end of file diff --git a/src/display/dp7.ts b/src/display/dp7.ts new file mode 100644 index 0000000..a06b374 --- /dev/null +++ b/src/display/dp7.ts @@ -0,0 +1,182 @@ +import { Hand } from "../hand"; +import { function_generator } from "../yakus/generator" + +// Configuration globale +const CANVAS_ID = "myCanvas"; +const FPS = 30; +const FRAME_INTERVAL = 1000 / FPS; + +/** + * Classe RiichiDisplay responsable de la gestion de l'affichage du jeu Riichi Mahjong + */ +class RiichiDisplay { + // Canvas principal et contexte + private canvas: HTMLCanvasElement; + private ctx: CanvasRenderingContext2D; + + // Ressources du jeu + private hands: Array = []; + + // Animation et événements + private animationFrameId: number | null = null; + private lastFrameTime: number = 0; + private cleanupCallbacks: Array<() => void> = []; + + // Constantes pour le rendu + private readonly BASE_X: number = 50; + private readonly BASE_Y: number = 90; + private readonly DY = 180; + private readonly SIZE: number = 0.75; + + constructor(canvasId: string = CANVAS_ID) { + // Initialisation du canvas + const canvas = document.getElementById(canvasId) as HTMLCanvasElement; + if (!canvas) { + throw new Error(`Canvas avec ID '${canvasId}' introuvable`); + } + this.canvas = canvas; + + // Récupération du contexte 2D + const ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("Impossible d'obtenir le contexte 2D du canvas"); + } + this.ctx = ctx; + } + + /** + * Dessine une frame complète + */ + private drawFrame(): void { + // Copie du canvas statique vers le canvas principal + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Dessin des éléments statiques et dynamiques + this.drawHandsAndLabels(); + } + + /** + * Dessine les mains et leurs étiquettes + */ + private drawHandsAndLabels(): void { + for (let i = 0; i < this.hands.length; i++) { + this.hands[i].drawHand( + this.ctx, + this.BASE_X, + this.BASE_Y + i * this.DY, + 5, + this.SIZE + ); + } + } + + /** + * Démarre la boucle d'animation + */ + private startAnimationLoop(): void { + const animationLoop = (currentTime: number) => { + this.animationFrameId = requestAnimationFrame(animationLoop); + + const deltaTime = currentTime - this.lastFrameTime; + if (deltaTime < FRAME_INTERVAL) return; + + this.lastFrameTime = currentTime - (deltaTime % FRAME_INTERVAL); + this.drawFrame(); + }; + + this.animationFrameId = requestAnimationFrame(animationLoop); + } + + /** + * Précharge une main + */ + private async preloadHand(hand: Hand): Promise { + await hand.preload(); + } + + /** + * Initialise l'affichage et démarre l'application + */ + public async initialize(): Promise { + try { + // Création des mains prédéfinies + this.hands.push( + function_generator.ordinaires(), + function_generator.sept_pairs() + ); + + // Préchargement parallèle des ressources + await Promise.all([ + ...this.hands.map(hand => this.preloadHand(hand)) + ]); + + // Premier rendu + this.drawFrame(); + + // Démarrage de la boucle d'animation + this.startAnimationLoop(); + + } catch (error) { + console.error("Erreur d'initialisation:", error); + } + } + + /** + * Nettoie les ressources + */ + public cleanup(): void { + // Arrêter la boucle d'animation + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId); + this.animationFrameId = null; + } + + // Exécuter tous les callbacks de nettoyage + this.cleanupCallbacks.forEach(callback => callback()); + this.cleanupCallbacks = []; + + // Nettoyer les ressources des decks et des mains + this.hands.forEach(hand => hand.cleanup()); + + // Vider les collections + this.hands = []; + } +} + +// Instance globale pour le nettoyage +let displayInstance: RiichiDisplay | null = null; + +/** + * Fonction de nettoyage exportée + */ +export function cleanup(): void { + if (displayInstance) { + displayInstance.cleanup(); + displayInstance = null; + } +} + +/** + * Fonction d'initialisation exportée + */ +export async function initDisplay(): Promise { + try { + displayInstance = new RiichiDisplay(CANVAS_ID); + await displayInstance.initialize(); + window.cleanup = cleanup; + } catch (error) { + console.error("Erreur lors de l'initialisation:", error); + } +} + +// 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); +} diff --git a/src/text/txt7.ts b/src/text/txt7.ts new file mode 100644 index 0000000..784efe7 --- /dev/null +++ b/src/text/txt7.ts @@ -0,0 +1,18 @@ +import { drawText } from "./parse" + +const CANVAS_ID = "myTextCanvas"; +const BG_RECT = { x: 0, y: 0, w: 800, h: 1050, color: "#007733" }; + +const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement; +const ctx = canvas.getContext("2d") as NonNullable; +canvas.width = BG_RECT.w; +canvas.height = BG_RECT.h; + +const path = "src/text/"; + +ctx.fillStyle = BG_RECT.color; +ctx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h); + +drawText(path + "txt7.txt", ctx).catch(error => console.error(error)); + +export {}; diff --git a/src/text/txt7.txt b/src/text/txt7.txt new file mode 100644 index 0000000..f49064f --- /dev/null +++ b/src/text/txt7.txt @@ -0,0 +1,23 @@ +~Chapitre 7: Les yakus~ + +Il est maintenant temps de révéler la vérité. Depuis le début, +bous êtes dans le mensonge. En effet, une main *Valide* (avec +tout ses groupes) n'est pas suffisant pour gagner: la main doit +également respecter une #ff00ff{condition de victoire}. + +Ces conditions sont appelées des #ff00ff{Yakus}, et il y en a +un total de *39*. Mais pas de panique ! En général, une dizaine +d'entre eux est largment suffisant pour la pllupart des parties. + +Sur la gauche, sont présentés les Yakus les plus courant: +- #ff00ff{Tout ordinaire}: Aucun honneur, aucun terminal +- #ff00ff{Brelan de valeur}: + - Brelan de dragon + - Brelan d'Est + - Brelan du vent du joueur +- #ff00ff{Main pure}: une seule famille +- #ff00ff{Main semi-pure}: une seule famille en plus d'honneurs +- #ff00ff{Double suite}: deux suites identiques + #ff0000{Ne fonctionne qu'en main fermée !} +- #ff00ff{Sept paire}: sept pairs distinctes + #ff0000{L'une des deux exceptions sur la forme des groupes} diff --git a/src/yakus/generator.ts b/src/yakus/generator.ts new file mode 100644 index 0000000..18d17c9 --- /dev/null +++ b/src/yakus/generator.ts @@ -0,0 +1,84 @@ +import { Hand } from "../hand" +import { Deck } from "../deck" +import { Tile } from "../tile"; + +export const function_generator = { + + ordinaires(): Hand { + let h = new Hand(); + let deck = new Deck(); + deck.shuffle(); + + // choix d'une paire + let t1 = deck.pop(); + let t2 = deck.find(t1.getFamily(), t1.getValue()) as NonNullable; + h.push(t1); + h.push(t2); + + let count = 2; + while (count !== 14) { + if (Math.random() < 0.5) { // pon + let t1 = deck.pop(); + let f = t1.getFamily(); + let v = t1.getValue(); + if ( + f < 4 && v !== 1 && v !== 9 && + deck.count(f, v) > 1 + ) { + let t2 = deck.find(f, v) as NonNullable; + let t3 = deck.find(f, v) as NonNullable; + h.push(t1); + h.push(t2); + h.push(t3); + count += 3; + } else { + deck.push(t1); + deck.shuffle(); + } + } else { // chii + let t1 = deck.pop(); + let f = t1.getFamily(); + let v = t1.getValue(); + if ( + f < 4 && v !== 1 && v < 6 && + deck.count(f, v + 1) > 0 && + deck.count(f, v + 2) > 0 + ) { + let t2 = deck.find(f, v + 1) as NonNullable; + let t3 = deck.find(f, v + 2) as NonNullable; + h.push(t1); + h.push(t2); + h.push(t3); + count += 3; + } else { + deck.push(t1); + deck.shuffle(); + } + } + } + h.sort(); + return h; + }, + + sept_pairs(): Hand { + let h = new Hand(); + let deck = new Deck(); + deck.shuffle(); + let count = 0; + while (count !== 7) { + let tile = deck.pop(); + if (h.getTiles().every( + t => t.compare(tile) !== 0 + )) { + count++; + h.push(tile); + h.push(new Tile(tile.getFamily(), tile.getValue(), false)); + } + } + h.sort(); + return h; + } + +} + +