/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ "./src/deck.ts": /*!*********************!*\ !*** ./src/deck.ts ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Deck: () => (/* binding */ Deck)\n/* harmony export */ });\n/* harmony import */ var _tile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tile */ \"./src/tile.ts\");\n/* harmony import */ var _hand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./hand */ \"./src/hand.ts\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n\nclass Deck {\n constructor(allowRed = false) {\n this.tiles = [];\n this.tileIndexMap = new Map();\n this.initTiles(allowRed);\n }\n /**\n * Displays all tile families on the canvas\n */\n displayFamilies(ctx, x, y, size, xOffset = 0, yOffset = 0) {\n let posX = x;\n let posY = y;\n // Define tile layouts for each family\n const familyLayouts = [\n { family: 1, start: 1, end: 9 }, // First suit\n { family: 2, start: 1, end: 9 }, // Second suit\n { family: 3, start: 1, end: 9 }, // Third suit\n { family: 4, start: 1, end: 4 }, // Winds\n { family: 5, start: 1, end: 3 } // Dragons\n ];\n for (const layout of familyLayouts) {\n for (let j = layout.start; j <= layout.end; j++) {\n const tile = this.find(layout.family, j);\n if (tile) {\n tile.drawTile(ctx, posX, posY, size, false, 0, false);\n posX += (75 + xOffset) * size;\n }\n }\n posX = x;\n posY += (100 + yOffset) * size;\n }\n }\n /**\n * Returns the number of tiles in the deck\n */\n length() {\n return this.tiles.length;\n }\n /**\n * Removes and returns the last tile from the deck\n * @throws Error if the deck is empty\n */\n pop() {\n if (this.tiles.length === 0) {\n throw new Error(\"Cannot pop from an empty deck\");\n }\n const tile = this.tiles.pop();\n // Update the index map\n const key = this.getTileKey(tile.getFamily(), tile.getValue());\n const indices = this.tileIndexMap.get(key);\n if (indices && indices.length > 0) {\n indices.pop(); // Remove the last index\n if (indices.length === 0) {\n this.tileIndexMap.delete(key);\n }\n else {\n this.tileIndexMap.set(key, indices);\n }\n }\n return tile;\n }\n /**\n * Adds a tile to the deck\n */\n push(tile) {\n const index = this.tiles.length;\n this.tiles.push(tile);\n // Update the index map\n const key = this.getTileKey(tile.getFamily(), tile.getValue());\n const indices = this.tileIndexMap.get(key) || [];\n indices.push(index);\n this.tileIndexMap.set(key, indices);\n }\n /**\n * Finds and removes a specific tile from the deck\n */\n find(family, value) {\n const key = this.getTileKey(family, value);\n const indices = this.tileIndexMap.get(key);\n if (!indices || indices.length === 0) {\n return undefined;\n }\n // Get the first occurrence\n const index = indices[0];\n if (index >= this.tiles.length) {\n // Handle potential out-of-sync errors\n this.rebuildIndexMap();\n return this.find(family, value);\n }\n // Swap with the first element for efficient removal\n [this.tiles[index], this.tiles[0]] = [this.tiles[0], this.tiles[index]];\n // Update indices in the map\n this.updateIndicesAfterSwap(0, index);\n // Remove and return the tile\n const tile = this.tiles.shift();\n // Update all indices after shift\n this.decrementIndicesAfterShift();\n return tile;\n }\n /**\n * Counts the number of tiles with specific family and value\n */\n count(family, value) {\n const key = this.getTileKey(family, value);\n const indices = this.tileIndexMap.get(key);\n return indices ? indices.length : 0;\n }\n /**\n * Shuffles the deck using Fisher-Yates algorithm\n */\n shuffle() {\n // Fisher-Yates shuffle algorithm\n for (let i = this.tiles.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.tiles[i], this.tiles[j]] = [this.tiles[j], this.tiles[i]];\n }\n // Rebuild the index map after shuffling\n this.rebuildIndexMap();\n }\n /**\n * Creates a random hand from the deck\n */\n getRandomHand() {\n const hand = new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand();\n this.shuffle();\n // Handle case where deck doesn't have enough tiles\n if (this.tiles.length < 13) {\n throw new Error(\"Not enough tiles in deck to create a hand\");\n }\n for (let i = 0; i < 13; i++) {\n hand.push(this.pop());\n }\n return hand;\n }\n /**\n * Cleans up resources used by tiles and empties the deck\n */\n cleanup() {\n this.tiles.forEach(tile => tile.cleanup());\n this.tiles = [];\n this.tileIndexMap.clear();\n }\n /**\n * Preloads all tile images\n */\n preload() {\n return __awaiter(this, void 0, void 0, function* () {\n const preloadPromises = this.tiles.map(tile => tile.preloadImg());\n yield Promise.all(preloadPromises);\n });\n }\n /**\n * Creates a unique key for each tile type\n */\n getTileKey(family, value) {\n return `${family}-${value}`;\n }\n /**\n * Updates the index map after swapping two tiles\n */\n updateIndicesAfterSwap(index1, index2) {\n if (index1 === index2)\n return;\n const tile1 = this.tiles[index1];\n const tile2 = this.tiles[index2];\n const key1 = this.getTileKey(tile1.getFamily(), tile1.getValue());\n const key2 = this.getTileKey(tile2.getFamily(), tile2.getValue());\n const indices1 = this.tileIndexMap.get(key1) || [];\n const indices2 = this.tileIndexMap.get(key2) || [];\n // Update indices\n const idx1 = indices1.indexOf(index2);\n const idx2 = indices2.indexOf(index1);\n if (idx1 !== -1)\n indices1[idx1] = index1;\n if (idx2 !== -1)\n indices2[idx2] = index2;\n this.tileIndexMap.set(key1, indices1);\n this.tileIndexMap.set(key2, indices2);\n }\n /**\n * Decrements all indices after a shift operation\n */\n decrementIndicesAfterShift() {\n var _a;\n for (const [key, indices] of this.tileIndexMap.entries()) {\n this.tileIndexMap.set(key, indices\n .filter(idx => idx !== 0) // Remove the index 0 that was shifted\n .map(idx => (idx > 0 ? idx - 1 : idx)) // Decrement all indices\n );\n // Clean up empty entries\n if (((_a = this.tileIndexMap.get(key)) === null || _a === void 0 ? void 0 : _a.length) === 0) {\n this.tileIndexMap.delete(key);\n }\n }\n }\n /**\n * Rebuilds the entire index map from scratch\n */\n rebuildIndexMap() {\n this.tileIndexMap.clear();\n for (let i = 0; i < this.tiles.length; i++) {\n const tile = this.tiles[i];\n const key = this.getTileKey(tile.getFamily(), tile.getValue());\n const indices = this.tileIndexMap.get(key) || [];\n indices.push(i);\n this.tileIndexMap.set(key, indices);\n }\n }\n /**\n * Initializes the deck with all tiles\n */\n initTiles(allowRed) {\n // Create suits (families 1-3)\n for (let family = 1; family <= 3; family++) {\n for (let value = 1; value <= 9; value++) {\n // Each value appears 4 times in a suit\n const isRedFive = value === 5 && allowRed;\n // Add 3 regular tiles\n for (let i = 0; i < 3; i++) {\n this.push(new _tile__WEBPACK_IMPORTED_MODULE_0__.Tile(family, value, false));\n }\n // Add the 4th tile (potentially red five)\n this.push(new _tile__WEBPACK_IMPORTED_MODULE_0__.Tile(family, value, isRedFive));\n }\n }\n // Create winds (family 4)\n for (let value = 1; value <= 4; value++) {\n for (let i = 0; i < 4; i++) {\n this.push(new _tile__WEBPACK_IMPORTED_MODULE_0__.Tile(4, value, false));\n }\n }\n // Create dragons (family 5)\n for (let value = 1; value <= 3; value++) {\n for (let i = 0; i < 4; i++) {\n this.push(new _tile__WEBPACK_IMPORTED_MODULE_0__.Tile(5, value, false));\n }\n }\n }\n}\n\n\n//# sourceURL=webpack:///./src/deck.ts?"); /***/ }), /***/ "./src/display/dp3.ts": /*!****************************!*\ !*** ./src/display/dp3.ts ***! \****************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ cleanup: () => (/* binding */ cleanup),\n/* harmony export */ initDisplay: () => (/* binding */ initDisplay)\n/* harmony export */ });\n/* harmony import */ var _deck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../deck */ \"./src/deck.ts\");\n/* harmony import */ var _hand__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../hand */ \"./src/hand.ts\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n\n// Configuration globale\nconst CANVAS_ID = \"myCanvas\";\nconst BG_COLOR = \"#007730\";\nconst BG_RECT = { x: 50, y: 50, w: 1000, h: 1000 };\nconst FPS = 30;\nconst FRAME_INTERVAL = 1000 / FPS;\n/**\n * Classe RiichiDisplay responsable de la gestion de l'affichage du jeu Riichi Mahjong\n */\nclass RiichiDisplay {\n constructor(canvasId = CANVAS_ID) {\n // Ressources du jeu\n this.decks = [];\n this.hands = [];\n // État de l'interface\n this.selectedTile = undefined;\n this.isDirty = true;\n // Animation et événements\n this.animationFrameId = null;\n this.lastFrameTime = 0;\n this.cleanupCallbacks = [];\n // Constantes pour le rendu\n this.BASE_X = 300;\n this.BASE_Y = 150;\n this.X_OFFSET = 250;\n this.Y_OFFSET = 100;\n this.INTERACTIVE_X = 100;\n this.INTERACTIVE_Y = 800;\n this.SIZE = 0.75;\n this.TILE_WIDTH = 80 * this.SIZE;\n this.MAX_TILES = 14;\n // Zones de détection pour l'interaction souris\n this.tileRects = [];\n // Initialisation du canvas\n const canvas = document.getElementById(canvasId);\n if (!canvas) {\n throw new Error(`Canvas avec ID '${canvasId}' introuvable`);\n }\n this.canvas = canvas;\n // Récupération du contexte 2D\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Impossible d'obtenir le contexte 2D du canvas\");\n }\n this.ctx = ctx;\n // Initialisation du canvas pour le double-buffering\n this.staticCanvas = document.createElement('canvas');\n this.staticCanvas.width = canvas.width;\n this.staticCanvas.height = canvas.height;\n const staticCtx = this.staticCanvas.getContext(\"2d\");\n if (!staticCtx) {\n throw new Error(\"Impossible d'obtenir le contexte du canvas statique\");\n }\n this.staticCtx = staticCtx;\n // Pré-calcul des zones de détection\n this.calculateTileHitAreas();\n }\n /**\n * Pré-calcule les zones de détection pour l'interaction souris\n */\n calculateTileHitAreas() {\n this.tileRects = [];\n for (let i = 0; i < this.MAX_TILES; i++) {\n this.tileRects.push({\n x: this.INTERACTIVE_X + i * this.TILE_WIDTH,\n y: this.INTERACTIVE_Y,\n width: 75,\n height: 100 * this.SIZE\n });\n }\n }\n /**\n * Pré-rendu du fond statique\n */\n prerenderBackground() {\n this.staticCtx.clearRect(0, 0, this.staticCanvas.width, this.staticCanvas.height);\n this.staticCtx.fillStyle = BG_COLOR;\n this.staticCtx.fillRect(BG_RECT.x, BG_RECT.y, BG_RECT.w, BG_RECT.h);\n }\n /**\n * Dessine une frame complète\n */\n drawFrame() {\n // Vérifier si un redessinage est nécessaire\n if (!this.isDirty)\n return;\n // Pré-rendu du fond statique\n this.prerenderBackground();\n // Dessin des éléments statiques et dynamiques\n this.drawHandsAndLabels();\n // Affichage des informations sur les groupes\n this.checkAndDisplayGroups();\n // Copie du canvas statique vers le canvas principal\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.ctx.drawImage(this.staticCanvas, 0, 0);\n // Réinitialisation du flag de modification\n this.isDirty = false;\n }\n /**\n * Dessine les mains et leurs étiquettes\n */\n drawHandsAndLabels() {\n const ctx = this.staticCtx;\n // Configurer le style de texte\n ctx.fillStyle = \"#DFDFFF\";\n ctx.font = \"50px serif\";\n // Dessiner les mains \"Chii\"\n ctx.fillText(\"Chii:\", 75, this.BASE_Y + 100 * this.SIZE - 5);\n this.hands[0].drawHand(ctx, this.BASE_X, this.BASE_Y, 5, this.SIZE);\n this.hands[1].drawHand(ctx, this.BASE_X + (75 + this.X_OFFSET) * this.SIZE, this.BASE_Y, 5, this.SIZE);\n // Dessiner les mains \"Pon\"\n ctx.fillText(\"Pon:\", 75, this.BASE_Y + (100 + this.Y_OFFSET) * this.SIZE + 100 * this.SIZE - 5);\n this.hands[2].drawHand(ctx, this.BASE_X, this.BASE_Y + (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n this.hands[3].drawHand(ctx, this.BASE_X + (75 + this.X_OFFSET) * this.SIZE, this.BASE_Y + (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n // Dessiner les mains \"Invalide\"\n ctx.fillText(\"Invalide:\", 75, this.BASE_Y + 2 * (100 + this.Y_OFFSET) * this.SIZE + 100 * this.SIZE - 5);\n this.hands[5].drawHand(ctx, this.BASE_X, this.BASE_Y + 2 * (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n this.hands[6].drawHand(ctx, this.BASE_X + (75 + this.X_OFFSET) * this.SIZE, this.BASE_Y + 2 * (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n this.hands[7].drawHand(ctx, this.BASE_X + 2 * (75 + this.X_OFFSET) * this.SIZE, this.BASE_Y + 2 * (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n // Main supplémentaire (position spéciale)\n this.hands[4].drawHand(ctx, this.BASE_X + 2 * (75 + this.X_OFFSET) * this.SIZE, this.BASE_Y + (100 + this.Y_OFFSET) * this.SIZE, 5, this.SIZE);\n // Main interactive\n this.hands[8].isolate = true;\n this.hands[8].drawHand(ctx, this.INTERACTIVE_X, this.INTERACTIVE_Y, 5, this.SIZE, this.selectedTile);\n }\n /**\n * Vérifie et affiche les informations sur les groupes formés\n */\n checkAndDisplayGroups() {\n const groups = this.hands[8].toGroup();\n if (groups !== undefined) {\n this.staticCtx.fillStyle = \"#FF0000\";\n this.staticCtx.font = \"50px serif\";\n this.staticCtx.fillText(\"Tous les groupes sont formés !\", 100, 750);\n }\n }\n /**\n * Démarre la boucle d'animation\n */\n startAnimationLoop() {\n const animationLoop = (currentTime) => {\n this.animationFrameId = requestAnimationFrame(animationLoop);\n const deltaTime = currentTime - this.lastFrameTime;\n if (deltaTime < FRAME_INTERVAL)\n return;\n this.lastFrameTime = currentTime - (deltaTime % FRAME_INTERVAL);\n this.drawFrame();\n };\n this.animationFrameId = requestAnimationFrame(animationLoop);\n }\n /**\n * Initialise les écouteurs d'événements\n */\n initEventListeners() {\n const mouseMoveHandler = this.handleMouseMove.bind(this);\n const mouseDownHandler = this.handleMouseDown.bind(this);\n this.canvas.addEventListener('mousemove', mouseMoveHandler);\n this.canvas.addEventListener('mousedown', mouseDownHandler);\n // Enregistrer les callbacks de nettoyage\n this.cleanupCallbacks.push(() => {\n this.canvas.removeEventListener('mousemove', mouseMoveHandler);\n this.canvas.removeEventListener('mousedown', mouseDownHandler);\n });\n }\n /**\n * Gère le mouvement de la souris\n */\n handleMouseMove(e) {\n const rect = this.canvas.getBoundingClientRect();\n const mouseX = e.clientX - rect.left;\n const mouseY = e.clientY - rect.top;\n // Sauvegarde de l'état précédent pour détecter les changements\n const previousSelectedTile = this.selectedTile;\n this.selectedTile = undefined;\n // Détection optimisée avec zones pré-calculées\n for (let i = 0; i < this.tileRects.length; i++) {\n const tileRect = this.tileRects[i];\n if (mouseX >= tileRect.x &&\n mouseX <= tileRect.x + tileRect.width &&\n mouseY >= tileRect.y &&\n mouseY <= tileRect.y + tileRect.height) {\n this.selectedTile = i;\n break;\n }\n }\n // Marquer comme \"dirty\" uniquement si la sélection a changé\n if (previousSelectedTile !== this.selectedTile) {\n this.isDirty = true;\n }\n }\n /**\n * Gère le clic de souris\n */\n handleMouseDown() {\n if (this.selectedTile !== undefined) {\n // Exécuter l'action pour la tuile sélectionnée\n this.decks[0].push(this.hands[8].eject(this.selectedTile));\n this.decks[0].shuffle();\n this.hands[8].sort();\n this.hands[8].push(this.decks[0].pop());\n // Marquer comme \"dirty\" pour forcer le redessinage\n this.isDirty = true;\n }\n }\n /**\n * Précharge un deck\n */\n preloadDeck(deck) {\n return __awaiter(this, void 0, void 0, function* () {\n yield deck.preload();\n });\n }\n /**\n * Précharge une main\n */\n preloadHand(hand) {\n return __awaiter(this, void 0, void 0, function* () {\n yield hand.preload();\n });\n }\n /**\n * Initialise l'affichage et démarre l'application\n */\n initialize() {\n return __awaiter(this, void 0, void 0, function* () {\n try {\n // Création du deck principal\n this.decks.push(new _deck__WEBPACK_IMPORTED_MODULE_0__.Deck(false));\n // Création des mains prédéfinies\n this.hands.push(new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"s1s2s3\"), // Chii 1\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"m2m3m4\"), // Chii 2\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"p5p5p5\"), // Pon 1\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"w2w2w2\"), // Pon 2\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"d3d3d3\"), // Pon supplémentaire\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"s4p5m6\"), // Invalide 1\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"m9s9p9\"), // Invalide 2\n new _hand__WEBPACK_IMPORTED_MODULE_1__.Hand(\"d1d2d3\"), // Invalide 3\n this.decks[0].getRandomHand() // Main interactive\n );\n // Préchargement parallèle des ressources\n yield Promise.all([\n ...this.decks.map(deck => this.preloadDeck(deck)),\n ...this.hands.map(hand => this.preloadHand(hand))\n ]);\n // Configuration de la main interactive\n this.hands[8].push(this.decks[0].pop());\n this.hands[8].sort();\n // Initialisation des écouteurs d'événements\n this.initEventListeners();\n // Premier rendu\n this.isDirty = true;\n this.drawFrame();\n // Démarrage de la boucle d'animation\n this.startAnimationLoop();\n }\n catch (error) {\n console.error(\"Erreur d'initialisation:\", error);\n }\n });\n }\n /**\n * Nettoie les ressources\n */\n cleanup() {\n // Arrêter la boucle d'animation\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n // Exécuter tous les callbacks de nettoyage\n this.cleanupCallbacks.forEach(callback => callback());\n this.cleanupCallbacks = [];\n // Nettoyer les ressources des decks et des mains\n this.decks.forEach(deck => deck.cleanup());\n this.hands.forEach(hand => hand.cleanup());\n // Vider les collections\n this.decks = [];\n this.hands = [];\n // Effacer les canvas\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.staticCtx.clearRect(0, 0, this.staticCanvas.width, this.staticCanvas.height);\n }\n}\n// Instance globale pour le nettoyage\nlet displayInstance = null;\n/**\n * Fonction de nettoyage exportée\n */\nfunction cleanup() {\n if (displayInstance) {\n displayInstance.cleanup();\n displayInstance = null;\n }\n}\n/**\n * Fonction d'initialisation exportée\n */\nfunction initDisplay() {\n return __awaiter(this, void 0, void 0, function* () {\n try {\n displayInstance = new RiichiDisplay(CANVAS_ID);\n yield displayInstance.initialize();\n window.cleanup = cleanup;\n }\n catch (error) {\n console.error(\"Erreur lors de l'initialisation:\", error);\n }\n });\n}\n// Initialisation automatique si le script est chargé directement\nif (typeof window !== 'undefined') {\n initDisplay().catch(console.error);\n}\n\n\n//# sourceURL=webpack:///./src/display/dp3.ts?"); /***/ }), /***/ "./src/group.ts": /*!**********************!*\ !*** ./src/group.ts ***! \**********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Group: () => (/* binding */ Group)\n/* harmony export */ });\nclass Group {\n constructor(tiles, stolenFrom, belongsTo) {\n this.tiles = tiles;\n this.stolenFrom = stolenFrom;\n this.belongsTo = belongsTo;\n }\n push(tile) {\n this.tiles.push(tile);\n }\n pop() {\n return this.tiles.pop();\n }\n getTiles() {\n return this.tiles;\n }\n compare(g) {\n // Compare les premiers tiles, puis les seconds si égalité\n const firstComparison = this.tiles[0].compare(g.tiles[0]);\n return firstComparison !== 0 ? firstComparison : this.tiles[1].compare(g.tiles[1]);\n }\n drawGroup(ctx, x, y, os, size, rotation, selectedTile) {\n // Sauvegarde et rotation du contexte\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate(rotation);\n ctx.translate(-525, -525);\n // Calcul des paramètres de dessin\n const v = 75 * size;\n const w = 90 * size;\n const osy = 25 * size / 2;\n const p = (this.belongsTo - this.stolenFrom - 1 + 4) % 4;\n // Détermination du tile sélectionné\n const sf = selectedTile === undefined ? -1 : selectedTile.getFamily();\n const sv = selectedTile === undefined ? 0 : selectedTile.getValue();\n // Fonction helper pour éviter la répétition de code\n const drawTile = (tile, tx, ty, angle) => {\n tile.drawTile(ctx, tx, ty, size, false, angle, tile.isEqual(sf, sv));\n };\n const HALF_PI = Math.PI / 2;\n // Dessin selon la position\n switch (p) {\n case 0:\n drawTile(this.tiles[0], x, y + osy, HALF_PI);\n drawTile(this.tiles[1], x + w, y, 0);\n drawTile(this.tiles[2], x + w + v + os * size, y, 0);\n break;\n case 1:\n drawTile(this.tiles[0], x, y, 0);\n drawTile(this.tiles[1], x + w, y + osy, -HALF_PI);\n drawTile(this.tiles[2], x + w + v + 3 * os * size, y, 0);\n break;\n case 2:\n drawTile(this.tiles[0], x, y, 0);\n drawTile(this.tiles[1], x + v + os * size, y, 0);\n drawTile(this.tiles[2], x + w + v + os * size, y + osy, -HALF_PI);\n break;\n default:\n console.error(`Position non prise en charge: ${p}`);\n }\n ctx.restore();\n }\n}\n\n\n//# sourceURL=webpack:///./src/group.ts?"); /***/ }), /***/ "./src/hand.ts": /*!*********************!*\ !*** ./src/hand.ts ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Hand: () => (/* binding */ Hand)\n/* harmony export */ });\n/* harmony import */ var _tile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tile */ \"./src/tile.ts\");\n/* harmony import */ var _group__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./group */ \"./src/group.ts\");\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n\n// Constants to avoid magic numbers and improve readability\nconst TILE_TYPES = {\n MANZU: { code: \"m\", family: 1 },\n PINZU: { code: \"p\", family: 2 },\n SOUZU: { code: \"s\", family: 3 },\n WINDS: { code: \"w\", family: 4 },\n DRAGONS: { code: \"d\", family: 5 }\n};\nclass Hand {\n /**\n * Create a hand from string representation\n * @param stiles String representation of tiles (e.g., \"m1p2s3w1d1\")\n */\n constructor(stiles = \"\") {\n this.isolate = false;\n this.tiles = [];\n this.initializeFromString(stiles);\n }\n /**\n * Parse string representation into tiles\n */\n initializeFromString(stiles) {\n for (let i = 0; i < stiles.length - 1; i++) {\n const tileCode = stiles.substring(i, i + 2);\n const type = tileCode[0];\n const value = Number(tileCode[1]);\n if (this.isValidTileCode(type, value)) {\n this.addTileFromCode(type, value);\n }\n }\n }\n /**\n * Check if tile code is valid\n */\n isValidTileCode(type, value) {\n return ((type === TILE_TYPES.MANZU.code) ||\n (type === TILE_TYPES.PINZU.code) ||\n (type === TILE_TYPES.SOUZU.code) ||\n (type === TILE_TYPES.WINDS.code) ||\n (type === TILE_TYPES.DRAGONS.code));\n }\n /**\n * Create a tile from type code and value\n */\n addTileFromCode(type, value) {\n const familyMap = {\n [TILE_TYPES.MANZU.code]: TILE_TYPES.MANZU.family,\n [TILE_TYPES.PINZU.code]: TILE_TYPES.PINZU.family,\n [TILE_TYPES.SOUZU.code]: TILE_TYPES.SOUZU.family,\n [TILE_TYPES.WINDS.code]: TILE_TYPES.WINDS.family,\n [TILE_TYPES.DRAGONS.code]: TILE_TYPES.DRAGONS.family\n };\n const family = familyMap[type];\n if (family !== undefined) {\n this.tiles.push(new _tile__WEBPACK_IMPORTED_MODULE_0__.Tile(family, value, false));\n }\n }\n /**\n * Get all tiles in hand\n */\n getTiles() {\n return this.tiles;\n }\n /**\n * Get number of tiles in hand\n */\n length() {\n return this.tiles.length;\n }\n /**\n * Add a tile to hand\n */\n push(tile) {\n this.tiles.push(tile);\n }\n /**\n * Remove and return the last tile\n */\n pop() {\n return this.tiles.pop();\n }\n /**\n * Find and remove a specific tile by family and value\n */\n find(family, value) {\n const index = this.findTileIndex(family, value);\n if (index !== -1) {\n // Swap with first tile and remove\n [this.tiles[index], this.tiles[0]] = [this.tiles[0], this.tiles[index]];\n const tile = this.tiles.shift();\n this.sort();\n return tile;\n }\n return undefined;\n }\n /**\n * Find index of tile with specific family and value\n */\n findTileIndex(family, value) {\n return this.tiles.findIndex(tile => tile.getFamily() === family && tile.getValue() === value);\n }\n /**\n * Remove tile at specific index\n */\n eject(idTile) {\n if (idTile < 0 || idTile >= this.tiles.length) {\n throw new Error(\"Invalid tile index\");\n }\n // Swap with first tile and remove\n [this.tiles[0], this.tiles[idTile]] = [this.tiles[idTile], this.tiles[0]];\n const tile = this.tiles.shift();\n this.sort();\n return tile;\n }\n /**\n * Get tile at specific index without removing\n */\n get(idTile) {\n if (idTile === undefined || idTile < 0 || idTile >= this.tiles.length) {\n return undefined;\n }\n return this.tiles[idTile];\n }\n /**\n * Sort tiles in ascending order\n */\n sort() {\n this.tiles.sort((a, b) => a.isLessThan(b) ? -1 : 1);\n }\n /**\n * Count tiles with specific family and value\n */\n count(family, value) {\n return this.tiles.filter(tile => tile.getFamily() === family && tile.getValue() === value).length;\n }\n /**\n * Try to form hand into groups (for winning detection)\n */\n toGroup(pair = false) {\n if (this.tiles.length === 0) {\n return [];\n }\n // Take last tile to try forming a group\n const lastTile = this.tiles.pop();\n const family = lastTile.getFamily();\n const value = lastTile.getValue();\n // Try to form a pair\n if (this.count(family, value) >= 1 && !pair) {\n const result = this.tryFormPair(lastTile);\n if (result)\n return result;\n }\n // Try to form a triplet (pon)\n if (this.count(family, value) >= 2) {\n const result = this.tryFormTriplet(lastTile, pair);\n if (result)\n return result;\n }\n // Try to form a sequence (chii)\n const hasMinusOne = this.count(family, value - 1) > 0;\n const hasMinusTwo = this.count(family, value - 2) > 0;\n if (hasMinusOne && hasMinusTwo) {\n const result = this.tryFormSequence(lastTile, pair);\n if (result)\n return result;\n }\n // If no valid group could be formed, put tile back and return undefined\n this.tiles.push(lastTile);\n this.sort();\n return undefined;\n }\n /**\n * Try to form a pair with the given tile\n */\n tryFormPair(tile) {\n const pairTile = this.find(tile.getFamily(), tile.getValue());\n const groups = this.toGroup(true);\n // Put the tile back\n this.tiles.push(pairTile);\n this.sort();\n if (groups !== undefined) {\n this.tiles.push(tile);\n this.sort();\n groups.push(new _group__WEBPACK_IMPORTED_MODULE_1__.Group([tile, pairTile], 0, 0));\n return groups;\n }\n return undefined;\n }\n /**\n * Try to form a triplet (pon) with the given tile\n */\n tryFormTriplet(tile, pair) {\n const secondTile = this.find(tile.getFamily(), tile.getValue());\n const thirdTile = this.find(tile.getFamily(), tile.getValue());\n const groups = this.toGroup(pair);\n // Put tiles back\n this.tiles.push(secondTile);\n this.tiles.push(thirdTile);\n this.sort();\n if (groups !== undefined) {\n groups.push(new _group__WEBPACK_IMPORTED_MODULE_1__.Group([tile, secondTile, thirdTile], 0, 0));\n this.tiles.push(tile);\n this.sort();\n return groups;\n }\n return undefined;\n }\n /**\n * Try to form a sequence (chii) with the given tile\n */\n tryFormSequence(tile, pair) {\n const secondTile = this.find(tile.getFamily(), tile.getValue() - 1);\n const thirdTile = this.find(tile.getFamily(), tile.getValue() - 2);\n const groups = this.toGroup(pair);\n // Put tiles back\n this.tiles.push(secondTile);\n this.tiles.push(thirdTile);\n this.sort();\n if (groups !== undefined) {\n groups.push(new _group__WEBPACK_IMPORTED_MODULE_1__.Group([thirdTile, secondTile, tile], 0, 0));\n this.tiles.push(tile);\n this.sort();\n return groups;\n }\n return undefined;\n }\n /**\n * Draw hand tiles on canvas\n */\n drawHand(ctx, x, y, offset, size, focusedTile = undefined, hidden = false, rotation = 0) {\n const tileOffset = (75 + offset) * size;\n const offsetX = Math.cos(rotation) * tileOffset;\n const offsetY = Math.sin(rotation) * tileOffset;\n for (let i = 0; i < this.tiles.length; i++) {\n const isLastAndIsolated = (i === this.tiles.length - 1 && this.isolate) ? 10 : 0;\n // Calculate position\n let tileX = x + i * offsetX + isLastAndIsolated * size * Math.cos(rotation);\n let tileY = y + i * offsetY + isLastAndIsolated * size * Math.sin(rotation);\n // Add additional offset for focused tile\n if (i === focusedTile) {\n tileX += 25 * size * Math.sin(rotation);\n tileY -= 25 * size * Math.cos(rotation);\n }\n // Draw tile\n this.tiles[i].drawTile(ctx, tileX, tileY, size, hidden, rotation);\n }\n }\n /**\n * Preload tile images\n */\n preload() {\n return __awaiter(this, void 0, void 0, function* () {\n yield Promise.all(this.tiles.map(tile => tile.preloadImg()));\n });\n }\n /**\n * Clean up resources\n */\n cleanup() {\n this.tiles.forEach(tile => tile.cleanup());\n this.tiles = [];\n }\n}\n\n\n//# sourceURL=webpack:///./src/hand.ts?"); /***/ }), /***/ "./src/tile.ts": /*!*********************!*\ !*** ./src/tile.ts ***! \*********************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Tile: () => (/* binding */ Tile)\n/* harmony export */ });\nvar __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\nclass Tile {\n constructor(family, value, red) {\n this.family = family;\n this.value = value;\n this.red = red;\n this.imgFront = new Image();\n this.imgBack = new Image();\n this.imgGray = new Image();\n this.img = new Image();\n this.imgSrc = \"\";\n this.tilt = 0;\n this.setImgSrc();\n }\n getFamily() {\n return this.family;\n }\n getValue() {\n return this.value;\n }\n isEqual(family, value) {\n return this.family === family && this.value === value;\n }\n isRed() {\n return this.red;\n }\n compare(t) {\n // Compare d'abord par famille, puis par valeur\n if (this.family !== t.family) {\n return this.family < t.family ? -1 : 1;\n }\n if (this.value !== t.value) {\n return this.value < t.value ? -1 : 1;\n }\n return 0;\n }\n isLessThan(t) {\n return this.family < t.family ||\n (this.family === t.family && this.value <= t.value);\n }\n setTilt() {\n this.tilt = (1 - 2 * Math.random()) * 0.04;\n }\n drawTile(ctx, x, y, size, hidden = false, rotation = 0, gray = false, tilted = true) {\n const tileWidth = 75 * size;\n const tileHeight = 100 * size;\n const halfWidth = tileWidth / 2;\n const halfHeight = tileHeight / 2;\n const shadowScale = 0.92;\n // Sauvegarde du contexte et positionnement\n ctx.save();\n ctx.translate(x + halfWidth, y + halfHeight);\n ctx.rotate(rotation + (tilted ? this.tilt : 0));\n // Position de l'ombre (légèrement décalée)\n const shadowX = -(tileWidth * shadowScale) / 2;\n const shadowY = -(tileHeight * shadowScale) / 2;\n // Dessin de l'ombre (commun aux deux cas)\n ctx.drawImage(this.imgGray, shadowX, shadowY, tileWidth, tileHeight);\n if (hidden) {\n // Dessin du dos de la tuile\n ctx.drawImage(this.imgBack, -halfWidth, -halfHeight, tileWidth, tileHeight);\n }\n else {\n // Dessin de la tuile face visible\n ctx.drawImage(this.imgFront, -halfWidth, -halfHeight, tileWidth, tileHeight);\n // Dessin du motif sur la tuile (légèrement plus petit)\n const patternScale = 0.9;\n const patternWidth = tileWidth * patternScale;\n const patternHeight = tileHeight * patternScale;\n const patternX = -((75 - 7) * size) / 2;\n const patternY = -((100 - 10) * size) / 2;\n ctx.drawImage(this.img, patternX, patternY, patternWidth, patternHeight);\n // Appliquer un filtre gris si demandé\n if (gray) {\n ctx.drawImage(this.imgGray, -halfWidth, -halfHeight, tileWidth, tileHeight);\n }\n }\n ctx.restore();\n }\n cleanup() {\n // Supprimer tous les gestionnaires d'événements\n const images = [this.imgFront, this.imgBack, this.imgGray, this.img];\n images.forEach(img => {\n img.onload = null;\n img.onerror = null;\n });\n }\n preloadImg() {\n return __awaiter(this, void 0, void 0, function* () {\n const imagesToLoad = [\n { img: this.imgFront, src: \"img/Regular/Front.svg\" },\n { img: this.imgBack, src: \"img/Regular/Back.svg\" },\n { img: this.imgGray, src: \"img/Regular/Gray.svg\" },\n { img: this.img, src: this.imgSrc }\n ];\n yield Promise.all(imagesToLoad.map(({ img, src }) => this.loadImg(img, src)));\n });\n }\n loadImg(img, src) {\n return new Promise((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject();\n img.src = src;\n });\n }\n setImgSrc() {\n this.imgSrc = \"img/Regular/\";\n if (this.family <= 3) {\n const families = [\"\", \"Man\", \"Pin\", \"Sou\"];\n this.imgSrc += families[this.family] + String(this.value);\n if (this.red) {\n this.imgSrc += \"-Dora\";\n }\n }\n else if (this.family === 4) {\n const winds = [\"\", \"Ton\", \"Nan\", \"Shaa\", \"Pei\"];\n this.imgSrc += winds[this.value];\n }\n else if (this.family === 5) {\n const dragons = [\"\", \"Chun\", \"Hatsu\", \"Haku\"];\n this.imgSrc += dragons[this.value];\n }\n this.imgSrc += \".svg\";\n }\n}\n\n\n//# sourceURL=webpack:///./src/tile.ts?"); /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ /******/ /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module can't be inlined because the eval devtool is used. /******/ var __webpack_exports__ = __webpack_require__("./src/display/dp3.ts"); /******/ /******/ })() ;