156 lines
No EOL
70 KiB
JavaScript
156 lines
No EOL
70 KiB
JavaScript
/*
|
|
* 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/button.ts":
|
|
/*!***********************!*\
|
|
!*** ./src/button.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 */ clickAction: () => (/* binding */ clickAction),\n/* harmony export */ clickChii: () => (/* binding */ clickChii),\n/* harmony export */ drawButtons: () => (/* binding */ drawButtons),\n/* harmony export */ drawChiis: () => (/* binding */ drawChiis)\n/* harmony export */ });\n// Button constants\nconst BUTTON_RADIUS = 8;\nconst BUTTON_WIDTH = 110;\nconst BUTTON_HEIGHT = 50;\nconst BUTTON_SPACING = 120;\nconst BUTTON_AREA_Y_MIN = 838;\nconst BUTTON_AREA_Y_MAX = 888;\nconst BUTTON_MARGIN = 10;\nconst BASE_X_POSITION = 850;\n// Button style configurations\nconst BUTTON_STYLES = {\n pass: { text: \"Ignorer\", color: \"#FF9030\", action: 0 },\n chii: { text: \"Chii\", color: \"#FFCC33\", action: 1 },\n pon: { text: \"Pon\", color: \"#FFCC33\", action: 2 },\n kan: { text: \"Kan\", color: \"#FFCC33\", action: 3 },\n ron: { text: \"Ron\", color: \"#FF3060\", action: 4 },\n tsumo: { text: \"Tsumo\", color: \"#FF3060\", action: 5 },\n back: { text: \"Retour\", color: \"#FF9030\", action: 0 }\n};\n/**\n * Determines which button was clicked based on coordinates\n * @returns The action value of the clicked button or -1 if no button was clicked\n */\nfunction clickAction(x, y, chii, pon, kan, ron, tsumo) {\n const activeButtons = getActiveButtons(chii, pon, kan, ron, tsumo);\n if (activeButtons.length === 0) {\n return -1;\n }\n // Calculate starting X position based on number of buttons\n const xmin = 960 - activeButtons.length * BUTTON_SPACING;\n // Check if Y coordinate is within button area\n const isYInButtonArea = BUTTON_AREA_Y_MIN < y && y < BUTTON_AREA_Y_MAX;\n if (!isYInButtonArea) {\n return -1;\n }\n // Calculate which button was clicked\n const buttonIndex = Math.floor((x - xmin) / BUTTON_SPACING);\n const xOffset = (x - xmin) - BUTTON_SPACING * buttonIndex;\n if (buttonIndex >= 0 &&\n buttonIndex < activeButtons.length &&\n xOffset > BUTTON_MARGIN) {\n return activeButtons[buttonIndex];\n }\n return -1;\n}\n/**\n * Draw all active buttons on the canvas\n */\nfunction drawButtons(ctx, chii, pon, kan, ron, tsumo) {\n const buttonFunctions = [\n [chii, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.chii)],\n [pon, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.pon)],\n [kan, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.kan)],\n [ron, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.ron)],\n [tsumo, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.tsumo)]\n ];\n // Only show the pass button if at least one other button is active\n const hasActiveButtons = buttonFunctions.some(([isActive]) => isActive);\n if (hasActiveButtons) {\n buttonFunctions.unshift([true, (ctx, x, y) => renderButton(ctx, x, y, BUTTON_STYLES.pass)]);\n }\n else {\n return; // No buttons to draw\n }\n // Draw active buttons\n let positionOffset = 0;\n for (const [isActive, renderFunc] of buttonFunctions) {\n if (isActive) {\n renderFunc(ctx, BASE_X_POSITION - positionOffset * BUTTON_SPACING, 835);\n positionOffset++;\n }\n }\n}\n/**\n * Determines which Chi option was clicked\n * @returns The value of the clicked Chi option or -1 if no option was clicked\n */\nfunction clickChii(x, y, chiis) {\n if (chiis.length === 0) {\n return -1;\n }\n // Calculate starting X position based on number of options\n const xmin = 960 - (chiis.length + 1) * BUTTON_SPACING;\n // Check if Y coordinate is within button area\n const isYInButtonArea = BUTTON_AREA_Y_MIN < y && y < BUTTON_AREA_Y_MAX;\n if (!isYInButtonArea) {\n return -1;\n }\n // Calculate which option was clicked\n const optionIndex = Math.floor((x - xmin) / BUTTON_SPACING);\n const xOffset = (x - xmin) - BUTTON_SPACING * optionIndex;\n if (optionIndex >= 0 &&\n optionIndex < (chiis.length + 1) &&\n xOffset > BUTTON_MARGIN) {\n // Return 0 for \"back\" button or the value of the selected Chi option\n return optionIndex === chiis.length ? 0 : chiis[optionIndex][0].getValue();\n }\n return -1;\n}\n/**\n * Draw Chi options on the canvas\n */\nfunction drawChiis(ctx, chiis) {\n // Create a copy to avoid modifying the original array\n const chiiOptions = [...chiis].reverse();\n // Draw \"back\" button\n renderButton(ctx, BASE_X_POSITION, 835, BUTTON_STYLES.back);\n // Draw Chi options\n let positionOffset = 1;\n for (const tiles of chiiOptions) {\n drawOneChii(ctx, BASE_X_POSITION - positionOffset * BUTTON_SPACING, 835, tiles);\n positionOffset++;\n }\n}\n/**\n * Draw a single Chi option\n */\nfunction drawOneChii(ctx, x, y, tiles) {\n const tileOffset = 32;\n const tileStartX = x + 7;\n const tileStartY = y + 5;\n // Draw the button background\n drawButtonShape(ctx, x, y, BUTTON_RADIUS, BUTTON_WIDTH, BUTTON_HEIGHT, BUTTON_STYLES.chii.color);\n // Draw the tiles\n for (let i = 0; i < tiles.length; i++) {\n tiles[i].drawTile(ctx, tileStartX + tileOffset * i, tileStartY, 0.4, false, 0, false, false);\n }\n}\n/**\n * Get the list of active button action values\n */\nfunction getActiveButtons(chii, pon, kan, ron, tsumo) {\n const buttonConfigs = [\n [tsumo, BUTTON_STYLES.tsumo.action],\n [ron, BUTTON_STYLES.ron.action],\n [kan, BUTTON_STYLES.kan.action],\n [pon, BUTTON_STYLES.pon.action],\n [chii, BUTTON_STYLES.chii.action]\n ];\n const activeButtons = buttonConfigs\n .filter(([isActive]) => isActive)\n .map(([, action]) => action);\n // Add pass button if any other buttons are active\n if (activeButtons.length > 0) {\n activeButtons.push(BUTTON_STYLES.pass.action);\n }\n return activeButtons;\n}\n/**\n * Render a button with text\n */\nfunction renderButton(ctx, x, y, config) {\n drawButtonShape(ctx, x, y, BUTTON_RADIUS, BUTTON_WIDTH, BUTTON_HEIGHT, config.color);\n // Add text to the button\n ctx.fillStyle = \"black\";\n ctx.font = \"30px garamond\";\n // Center text based on its length\n const textXPosition = x + BUTTON_WIDTH * (0.5 - config.text.length * 0.025);\n const textYPosition = y + BUTTON_HEIGHT / 2 * 1.3;\n ctx.fillText(config.text, textXPosition, textYPosition);\n}\n/**\n * Draw a rounded rectangle button shape\n */\nfunction drawButtonShape(ctx, x, y, radius, width, height, color) {\n ctx.fillStyle = color;\n ctx.beginPath();\n // Top right corner\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n // Bottom right corner\n ctx.lineTo(x + width, y + height - radius);\n ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n // Bottom left corner\n ctx.lineTo(x + radius, y + height);\n ctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n // Top left corner\n ctx.lineTo(x, y + radius);\n ctx.quadraticCurveTo(x, y, x + radius, y);\n ctx.fill();\n // Add border\n ctx.fillStyle = \"#606060\";\n ctx.stroke();\n}\n\n\n//# sourceURL=webpack:///./src/button.ts?");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./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/dp4.ts":
|
|
/*!****************************!*\
|
|
!*** ./src/display/dp4.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 _game__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../game */ \"./src/game.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\nclass RiichiGameManager {\n /**\n * Constructeur privé pour le Singleton\n */\n constructor() {\n // Configuration globale\n this.CANVAS_ID = \"myCanvas\";\n this.BG_RECT = { x: 0, y: 0, w: 1050, h: 1050 };\n this.FPS = 60;\n this.FRAME_INTERVAL = 1000 / this.FPS;\n // État du jeu\n this.mouse = { x: 0, y: 0 };\n this.game = null;\n this.decks = [];\n this.hands = [];\n // Animation et boucle de jeu\n this.animationFrameId = null;\n this.lastFrameTime = 0;\n this.isInitialized = false;\n // Nettoyage\n this.cleanupCallbacks = [];\n // Initialisation du canvas principal\n const canvas = document.getElementById(this.CANVAS_ID);\n if (!canvas) {\n throw new Error(`Canvas avec ID '${this.CANVAS_ID}' introuvable`);\n }\n this.canvas = canvas;\n this.canvas.width = this.BG_RECT.w;\n this.canvas.height = this.BG_RECT.h;\n // Récupération du contexte 2D\n const ctx = canvas.getContext(\"2d\", { alpha: false });\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\", { alpha: false });\n if (!staticCtx) {\n throw new Error(\"Impossible d'obtenir le contexte du canvas statique\");\n }\n this.staticCtx = staticCtx;\n }\n /**\n * Accès à l'instance Singleton\n */\n static getInstance() {\n if (!RiichiGameManager.instance) {\n RiichiGameManager.instance = new RiichiGameManager();\n }\n return RiichiGameManager.instance;\n }\n /**\n * Dessine une frame du jeu\n */\n drawFrame() {\n if (this.game) {\n this.game.draw(this.mouse);\n }\n }\n /**\n * Démarre la boucle d'animation du jeu\n */\n startAnimationLoop() {\n const animationLoop = (currentTime) => {\n this.animationFrameId = requestAnimationFrame(animationLoop);\n const deltaTime = currentTime - this.lastFrameTime;\n if (deltaTime < this.FRAME_INTERVAL)\n return;\n // Correction du time drift\n this.lastFrameTime = currentTime - (deltaTime % this.FRAME_INTERVAL);\n // Rendu d'une frame\n this.drawFrame();\n };\n this.animationFrameId = requestAnimationFrame(animationLoop);\n }\n /**\n * Initialise les écouteurs d'événements\n */\n initEventListeners() {\n // Handler pour le déplacement de la souris\n const mouseMoveHandler = (e) => {\n // Calcul des coordonnées relatives au canvas\n const rect = this.canvas.getBoundingClientRect();\n this.mouse.x = e.clientX - rect.left;\n this.mouse.y = e.clientY - rect.top;\n };\n // Handler pour le clic de souris\n const mouseDownHandler = (e) => {\n if (this.game) {\n this.game.click(e);\n }\n };\n // Enregistrement des écouteurs\n this.canvas.addEventListener('mousemove', mouseMoveHandler);\n this.canvas.addEventListener('mousedown', mouseDownHandler);\n // Enregistrement des callbacks de nettoyage\n this.cleanupCallbacks.push(() => {\n this.canvas.removeEventListener('mousemove', mouseMoveHandler);\n this.canvas.removeEventListener('mousedown', mouseDownHandler);\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 le jeu\n */\n initialize() {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.isInitialized)\n return;\n try {\n console.log(\"Chargement en cours...\");\n // Création du jeu\n this.game = new _game__WEBPACK_IMPORTED_MODULE_0__.Game(this.ctx, this.canvas, this.staticCtx, this.staticCanvas);\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 this.game.preload()\n ]);\n console.log(\"Chargement terminé\");\n // Initialisation des écouteurs d'événements\n this.initEventListeners();\n // Démarrage de la boucle d'animation\n this.startAnimationLoop();\n // Marquer comme initialisé\n this.isInitialized = true;\n // Définir la fonction de nettoyage global\n window.cleanup = this.cleanup.bind(this);\n }\n catch (error) {\n console.error(\"Erreur lors de l'initialisation:\", error);\n }\n });\n }\n /**\n * Nettoie les ressources\n */\n cleanup() {\n var _a, _b;\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 du jeu\n if (this.game) {\n // Supposons que Game a une méthode cleanup\n (_b = (_a = this.game).cleanup) === null || _b === void 0 ? void 0 : _b.call(_a);\n this.game = null;\n }\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 // Réinitialiser l'état\n this.isInitialized = false;\n this.lastFrameTime = 0;\n this.mouse = { x: 0, y: 0 };\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// Fonction de nettoyage globale exportée\nfunction cleanup() {\n RiichiGameManager.getInstance().cleanup();\n}\n// Fonction d'initialisation globale exportée\nfunction initDisplay() {\n return __awaiter(this, void 0, void 0, function* () {\n yield RiichiGameManager.getInstance().initialize();\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/dp4.ts?");
|
|
|
|
/***/ }),
|
|
|
|
/***/ "./src/game.ts":
|
|
/*!*********************!*\
|
|
!*** ./src/game.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 */ Game: () => (/* binding */ Game)\n/* harmony export */ });\n/* harmony import */ var _deck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./deck */ \"./src/deck.ts\");\n/* harmony import */ var _group__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./group */ \"./src/group.ts\");\n/* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./button */ \"./src/button.ts\");\n/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./state */ \"./src/state.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\n\n// Game constants to avoid magic numbers and improve readability\nconst GAME_CONSTANTS = {\n PLAYERS: 4,\n BACKGROUND: { color: \"#007730\", x: 0, y: 0, w: 1050, h: 1050 },\n DEAD_WALL_SIZE: 14,\n WAITING_TIME: {\n MIN: 500,\n MAX: 2000,\n DEFAULT: 700\n },\n DISPLAY: {\n HAND_SIZE: 0.7,\n HIDDEN_HAND_SIZE: 0.6,\n DISCARD_SIZE: 0.6,\n GROUP_SIZE: 0.6\n },\n PI: Math.PI\n};\nclass Game {\n constructor(ctx, cv, staticCtx, staticCv, red = false, level = 0) {\n this.deadWall = [];\n this.hands = [];\n this.discards = [];\n this.groups = [];\n this.turn = 0;\n this.waitingTime = GAME_CONSTANTS.WAITING_TIME.DEFAULT;\n this.selectedTile = undefined;\n this.canCall = false;\n this.hasPicked = false;\n this.hasPlayed = false;\n this.lastPlayed = Date.now();\n this.chooseChii = false;\n this.end = false;\n this.result = -1;\n // Cached values to avoid recalculations\n this.BG_RECT = GAME_CONSTANTS.BACKGROUND;\n this.rotations = [0, -GAME_CONSTANTS.PI / 2, -GAME_CONSTANTS.PI, GAME_CONSTANTS.PI / 2];\n this.ctx = ctx;\n this.cv = cv;\n this.staticCtx = staticCtx;\n this.staticCv = staticCv;\n this.level = level;\n // Initialize game elements\n this.deck = new _deck__WEBPACK_IMPORTED_MODULE_0__.Deck(red);\n this.initializeGame();\n }\n initializeGame() {\n this.deck.shuffle();\n // Set up dead wall\n for (let i = 0; i < GAME_CONSTANTS.DEAD_WALL_SIZE; i++) {\n this.deadWall.push(this.deck.pop());\n }\n // Create player hands\n for (let i = 0; i < GAME_CONSTANTS.PLAYERS; i++) {\n this.hands.push(this.deck.getRandomHand());\n this.discards.push([]);\n this.groups.push([]);\n }\n this.lastDiscard = undefined;\n this.hands[0].sort();\n this.pick(0);\n }\n draw(mp) {\n // Clear static canvas once per frame\n this.staticCtx.clearRect(0, 0, this.cv.width, this.cv.height);\n // Draw background\n this.staticCtx.fillStyle = this.BG_RECT.color;\n this.staticCtx.fillRect(this.BG_RECT.x, this.BG_RECT.y, this.BG_RECT.w, this.BG_RECT.h);\n this.getSelected(mp);\n this.drawGame();\n // Use a single drawImage operation instead of clearing and redrawing\n this.ctx.clearRect(0, 0, this.cv.width, this.cv.height);\n this.ctx.drawImage(this.staticCv, 0, 0);\n }\n getDeck() {\n return this.deck;\n }\n getHands() {\n return this.hands;\n }\n isFinished() {\n return this.end;\n }\n click(mp) {\n const rect = this.cv.getBoundingClientRect();\n if (this.hasWin(0)) {\n this.end = true;\n this.result = 1;\n return;\n }\n if (this.chooseChii) {\n this.handleChiiSelection(mp, rect);\n return;\n }\n const action = (0,_button__WEBPACK_IMPORTED_MODULE_2__.clickAction)(mp.x - rect.x, mp.y - rect.y, this.canDoAChii().length > 0, this.canDoAPon(), false && 0, false && 0, false && 0);\n if (this.canCall && action !== -1) {\n this.handleCallAction(action);\n }\n else if (this.turn === 0 && this.selectedTile !== undefined) {\n this.handlePlayerDiscard();\n }\n }\n handleChiiSelection(mp, rect) {\n const allChii = this.getChii(0);\n const selection = (0,_button__WEBPACK_IMPORTED_MODULE_2__.clickChii)(mp.x - rect.x, mp.y - rect.y, allChii);\n if (selection === 0) {\n this.chooseChii = false;\n }\n else {\n this.chooseChii = false;\n this.chii(selection, 0);\n }\n }\n handleCallAction(action) {\n if (action === 0) { // Pass\n this.canCall = false;\n this.advanceTurn();\n }\n else if (action === 1) { // Chii\n const chiis = this.canDoAChii();\n if (chiis.length === 1) {\n this.chii(chiis[0], 0);\n }\n else {\n this.chooseChii = true;\n this.drawGame();\n }\n }\n else if (action === 2) { // Pon\n this.pon(this.turn);\n }\n }\n handlePlayerDiscard() {\n this.discard(0, this.selectedTile);\n if (!this.checkPon()) {\n const chiis = this.canDoAChii(1);\n if (chiis.length > 0) {\n const i = Math.floor(Math.random() * chiis.length);\n this.chii(chiis[i], 1);\n }\n else {\n this.advanceTurn();\n }\n }\n }\n advanceTurn() {\n this.updateWaitingTime();\n this.turn = (this.turn + 1) % GAME_CONSTANTS.PLAYERS;\n this.hasPicked = false;\n this.hasPlayed = false;\n }\n getSelected(mp) {\n const rect = this.cv.getBoundingClientRect();\n const x = 2.5 * 75 * 0.75;\n const y = 1050 - 250 * 0.6;\n const sizeHand = GAME_CONSTANTS.DISPLAY.HAND_SIZE;\n const mouseX = mp.x - x;\n const mouseY = mp.y;\n const tileWidth = 83.9;\n const tileIndex = Math.floor(mouseX / (tileWidth * sizeHand));\n const relativeX = mouseX - tileIndex * tileWidth * sizeHand;\n if (relativeX <= (tileWidth - 3) * sizeHand &&\n tileIndex >= 0 &&\n tileIndex < this.hands[0].length() &&\n mouseY >= y &&\n mouseY <= y + 100 * sizeHand) {\n this.selectedTile = tileIndex;\n }\n else {\n this.selectedTile = undefined;\n }\n }\n updateWaitingTime() {\n this.waitingTime = Math.floor(Math.random() *\n (GAME_CONSTANTS.WAITING_TIME.MAX - GAME_CONSTANTS.WAITING_TIME.MIN) +\n GAME_CONSTANTS.WAITING_TIME.MIN);\n }\n play() {\n if (this.turn !== 0 && !this.end) {\n if (!this.hasPicked) {\n // Begin of turn\n this.lastPlayed = Date.now();\n this.pick(this.turn);\n this.hasPicked = true;\n }\n else if (!this.hasPlayed) {\n // Middle of turn\n this.handleBotTurn();\n }\n else if (!this.canCall) {\n // End of turn\n this.advanceBotTurn();\n }\n }\n }\n handleBotTurn() {\n if (this.hasWin(this.turn)) {\n this.end = true;\n this.result = 2;\n return;\n }\n if (Date.now() - this.lastPlayed > this.waitingTime) {\n this.lastPlayed = Date.now();\n // Choose random tile to discard\n const n = Math.floor(this.hands[this.turn].length() * Math.random());\n this.discard(this.turn, n);\n this.hasPlayed = true;\n if (this.deck.length() <= 0) {\n this.result = 0;\n this.end = true;\n return;\n }\n if (!this.end) {\n this.checkPon();\n this.checkForChii();\n this.canCall = this.canDoAChii().length > 0 || this.canDoAPon();\n }\n }\n }\n checkForChii() {\n if (this.turn === 3)\n return;\n const nextPlayer = this.turn + 1;\n const chiis = this.canDoAChii(nextPlayer);\n if (chiis.length > 0) {\n const i = Math.floor(Math.random() * chiis.length);\n this.chii(chiis[i], nextPlayer);\n }\n }\n advanceBotTurn() {\n if (this.turn === 3) {\n this.turn = 0;\n this.pick(0);\n if (this.hasWin(0)) {\n this.end = true;\n this.result = 1;\n }\n }\n else {\n this.turn++;\n }\n this.updateWaitingTime();\n this.hasPicked = false;\n this.hasPlayed = false;\n }\n pick(player) {\n this.hands[player].push(this.deck.pop());\n this.hands[player].isolate = true;\n }\n discard(player, n) {\n const tile = this.hands[player].eject(n);\n this.hands[player].sort();\n tile.setTilt();\n this.discards[player].push(tile);\n this.hands[player].isolate = false;\n this.hands[player].sort();\n this.lastDiscard = player;\n this.lastPlayed = Date.now();\n }\n canDoAChii(p = 0) {\n const chii = [];\n // Check if chii is possible\n if (this.lastDiscard === undefined ||\n (this.lastDiscard + 1) % 4 !== p ||\n (this.turn + 1) % 4 !== p ||\n this.discards[this.lastDiscard][this.discards[this.lastDiscard].length - 1].getFamily() >= 4) {\n return chii;\n }\n const t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length - 1];\n const h = this.hands[p];\n const family = t.getFamily();\n const value = t.getValue();\n // Check for three possible chii patterns\n if (h.count(family, value - 2) > 0 && h.count(family, value - 1) > 0) {\n chii.push(value - 2);\n }\n if (h.count(family, value - 1) > 0 && h.count(family, value + 1) > 0) {\n chii.push(value - 1);\n }\n if (h.count(family, value + 1) > 0 && h.count(family, value + 2) > 0) {\n chii.push(value);\n }\n return chii;\n }\n chii(minValue, p) {\n const discardPlayer = p === 0 ? 3 : p - 1;\n const t = this.discards[discardPlayer].pop();\n this.lastDiscard = undefined;\n const tt = [];\n let tn = 0;\n let v = minValue;\n // Find the right tiles for the chii\n while (tn < 2) {\n if (v === t.getValue()) {\n v++;\n }\n else {\n tt[tn] = this.hands[p].find(t.getFamily(), v);\n tn++;\n v++;\n }\n }\n // Set tilt for all tiles in the group\n [t, tt[0], tt[1]].forEach(tile => tile.setTilt());\n // Create new group\n this.groups[p].push(new _group__WEBPACK_IMPORTED_MODULE_1__.Group([t, tt[0], tt[1]], discardPlayer, p));\n if (this.hasWin(p)) {\n this.end = true;\n this.result = p === 0 ? 1 : 2;\n }\n this.updateWaitingTime();\n this.turn = p;\n this.hasPicked = true;\n this.hasPlayed = false;\n }\n getChii(p) {\n const chiis = [];\n const numChii = this.canDoAChii();\n const discardPlayer = p === 0 ? 3 : p - 1;\n const d = this.discards[discardPlayer];\n const t = d[d.length - 1];\n for (let i = 0; i < numChii.length; i++) {\n const v = numChii[i];\n const chii = [];\n for (let dv = 0; dv < 3; dv++) {\n if (v + dv === t.getValue()) {\n chii.push(t);\n }\n else {\n const tt = this.hands[p].find(t.getFamily(), v + dv);\n chii.push(tt);\n this.hands[p].push(tt);\n this.hands[p].sort();\n }\n }\n chiis.push(chii);\n }\n return chiis;\n }\n checkPon() {\n for (let p = 1; p < GAME_CONSTANTS.PLAYERS; p++) {\n if (this.canDoAPon(p)) {\n this.pon(this.lastDiscard, p);\n return true;\n }\n }\n return false;\n }\n canDoAPon(player = 0) {\n if (this.lastDiscard === undefined ||\n this.lastDiscard === player ||\n this.turn === player ||\n (this.hasPicked && !this.hasPlayed)) {\n return false;\n }\n const t = this.discards[this.lastDiscard][this.discards[this.lastDiscard].length - 1];\n return this.hands[player].count(t.getFamily(), t.getValue()) >= 2;\n }\n pon(p, thief = 0) {\n const t = this.discards[p].pop();\n this.lastDiscard = undefined;\n const t2 = this.hands[thief].find(t.getFamily(), t.getValue());\n const t3 = this.hands[thief].find(t.getFamily(), t.getValue());\n [t, t2, t3].forEach(tile => tile.setTilt());\n this.groups[thief].push(new _group__WEBPACK_IMPORTED_MODULE_1__.Group([t, t2, t3], p, thief));\n if (this.hasWin(thief)) {\n this.end = true;\n this.result = thief === 0 ? 1 : 2;\n }\n this.updateWaitingTime();\n this.turn = thief;\n this.hasPicked = true;\n this.hasPlayed = false;\n }\n hasWin(p) {\n return this.hands[p].toGroup() !== undefined;\n }\n drawGame() {\n // Update game state\n this.play();\n // Draw game elements\n (0,_state__WEBPACK_IMPORTED_MODULE_3__.drawState)(this.staticCtx, this.turn);\n this.drawDiscardSize();\n this.drawResult();\n this.drawHands();\n this.drawGroups(GAME_CONSTANTS.DISPLAY.GROUP_SIZE);\n // Draw discards for all players\n for (let i = 0; i < GAME_CONSTANTS.PLAYERS; i++) {\n this.drawDiscard(i, this.selectedTile !== undefined ? this.hands[0].get(this.selectedTile) : undefined);\n }\n // Draw UI elements\n if (this.chooseChii) {\n (0,_button__WEBPACK_IMPORTED_MODULE_2__.drawChiis)(this.staticCtx, this.getChii(0));\n }\n else {\n (0,_button__WEBPACK_IMPORTED_MODULE_2__.drawButtons)(this.staticCtx, this.canDoAChii().length > 0, this.canDoAPon(), false && 0, false && 0, false && 0);\n }\n }\n drawHands() {\n const showHands = false;\n const { HAND_SIZE, HIDDEN_HAND_SIZE } = GAME_CONSTANTS.DISPLAY;\n // Draw player's hand\n this.hands[0].drawHand(this.staticCtx, 2.5 * 75 * 0.75, 1000 - 150 * HAND_SIZE, 5 * HAND_SIZE, 0.75, this.selectedTile, false, 0);\n // Draw opponents' hands\n this.hands[1].drawHand(this.staticCtx, 1000 - 150 * HIDDEN_HAND_SIZE, 1000 - 75 * 5 * HIDDEN_HAND_SIZE, 5 * HIDDEN_HAND_SIZE, HIDDEN_HAND_SIZE, undefined, !showHands, -GAME_CONSTANTS.PI / 2);\n this.hands[2].drawHand(this.staticCtx, 1000 - 75 * 5 * HIDDEN_HAND_SIZE, 150 * HIDDEN_HAND_SIZE, 5 * HIDDEN_HAND_SIZE, HIDDEN_HAND_SIZE, undefined, !showHands, -GAME_CONSTANTS.PI);\n this.hands[3].drawHand(this.staticCtx, 150 * HIDDEN_HAND_SIZE, 75 * 5 * HIDDEN_HAND_SIZE, 5 * HIDDEN_HAND_SIZE, HIDDEN_HAND_SIZE, undefined, !showHands, GAME_CONSTANTS.PI / 2);\n }\n drawGroups(size) {\n const offset = 25;\n for (let p = 0; p < GAME_CONSTANTS.PLAYERS; p++) {\n const rotation = this.rotations[p];\n const groups = this.groups[p];\n if (groups.length > 0) {\n for (let i = groups.length - 1; i >= 0; i--) {\n groups[i].drawGroup(this.staticCtx, 1050 - 240 - (260 + offset) * size * i, 1050 - 62, 5, 0.6, rotation, this.selectedTile !== undefined ? this.hands[0].get(this.selectedTile) : undefined);\n }\n }\n }\n }\n drawDiscard(p, highlightedTile) {\n const sizeDiscard = GAME_CONSTANTS.DISPLAY.DISCARD_SIZE;\n this.staticCtx.save();\n this.staticCtx.translate(525, 525);\n this.staticCtx.rotate(this.rotations[p]);\n const x = -sizeDiscard * 475 / 2;\n const y = sizeDiscard * (475 / 2 + 5);\n // Draw all discarded tiles\n for (let i = 0; i < this.discards[p].length; i++) {\n const tile = this.discards[p][i];\n let tx, ty;\n if (i < 12) {\n tx = x + (i % 6) * 80 * sizeDiscard;\n ty = y + Math.floor(i / 6) * 105 * sizeDiscard;\n }\n else {\n tx = x + (i - 12) * 80 * sizeDiscard;\n ty = y + 2 * 105 * sizeDiscard;\n }\n tile.drawTile(this.staticCtx, tx, ty, sizeDiscard, false, 0, highlightedTile === null || highlightedTile === void 0 ? void 0 : highlightedTile.isEqual(tile.getFamily(), tile.getValue()));\n }\n // Draw indicator for last discard\n if (this.lastDiscard === p) {\n this.drawLastDiscardIndicator(p);\n }\n this.staticCtx.restore();\n }\n drawLastDiscardIndicator(p) {\n const j = this.discards[p].length - 1;\n const sizeDiscard = GAME_CONSTANTS.DISPLAY.DISCARD_SIZE;\n const x = -sizeDiscard * 475 / 2;\n const y = sizeDiscard * (475 / 2 + 5);\n let tx = j < 12\n ? x + (j % 6) * 80 * sizeDiscard\n : x + (j - 12) * 80 * sizeDiscard;\n let ty = j < 12\n ? y + Math.floor(j / 6) * 105 * sizeDiscard\n : y + 2 * 105 * sizeDiscard;\n tx += 75 / 2 * sizeDiscard;\n ty += 115 * sizeDiscard;\n const triangleSize = 10;\n this.staticCtx.fillStyle = \"#ff0000\";\n this.staticCtx.beginPath();\n this.staticCtx.moveTo(tx, ty);\n this.staticCtx.lineTo(tx + triangleSize / 2, ty + 0.866 * triangleSize);\n this.staticCtx.lineTo(tx - triangleSize / 2, ty + 0.866 * triangleSize);\n this.staticCtx.lineTo(tx, ty);\n this.staticCtx.fill();\n this.staticCtx.stroke();\n }\n drawDiscardSize() {\n this.staticCtx.fillStyle = \"#f070f0\";\n this.staticCtx.font = \"40px garamond\";\n const remainingTiles = this.deck.length();\n const x = remainingTiles < 10 ? 517 : 507;\n this.staticCtx.fillText(remainingTiles.toString(), x, 537);\n }\n drawResult() {\n if (this.result === -1)\n return;\n // Draw result background\n this.staticCtx.fillStyle = \"#e0e0f0\";\n this.staticCtx.fillRect(450, 430, 150, 190);\n this.staticCtx.fillRect(430, 450, 190, 150);\n // Draw result text\n this.staticCtx.fillStyle = \"#ff0000\";\n this.staticCtx.font = \"45px garamond\";\n if (this.result === 0) {\n this.staticCtx.fillText(\"Égalité\", 450, 535);\n }\n else if (this.result === 1) {\n this.staticCtx.fillText(\"Victoire !\", 440, 535);\n }\n else if (this.result === 2) {\n this.staticCtx.fillText(\"Défaite...\", 440, 535);\n }\n }\n preload() {\n return __awaiter(this, void 0, void 0, function* () {\n yield this.deck.preload();\n yield Promise.all(this.hands.map(h => h.preload()));\n });\n }\n}\n\n\n//# sourceURL=webpack:///./src/game.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/state.ts":
|
|
/*!**********************!*\
|
|
!*** ./src/state.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 */ drawState: () => (/* binding */ drawState)\n/* harmony export */ });\nfunction drawState(ctx, turn = 0, rotation = 0) {\n let color = \"#e0e0f0\";\n let r = 30;\n let s = 100;\n let c = 525;\n let pi = Math.PI;\n // turn\n let w = 150;\n let h = 4;\n let rd = 2;\n let y = 525 + s + 5;\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate([0, -pi / 2, pi, pi / 2][turn]);\n ctx.translate(-525, -525);\n ctx.fillStyle = \"#ffcc33\";\n ctx.beginPath();\n ctx.moveTo(c, y);\n ctx.lineTo(c - w / 2 + rd, y);\n ctx.quadraticCurveTo(c - w / 2, y, c - w / 2, y + rd);\n ctx.lineTo(c - w / 2, y + h - rd);\n ctx.quadraticCurveTo(c - w / 2, y + h, c - w / 2 + rd, y + h);\n ctx.lineTo(c + w / 2 - rd, y + h);\n ctx.quadraticCurveTo(c + w / 2, y + h, c + w / 2, y + h - rd);\n ctx.lineTo(c + w / 2, y + rd);\n ctx.quadraticCurveTo(c + w / 2, y, c + w / 2 - rd, y);\n ctx.lineTo(c, y);\n ctx.fill();\n ctx.stroke();\n ctx.restore();\n // big rectangle\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate(rotation);\n ctx.translate(-525, -525);\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.moveTo(c - s, c);\n ctx.lineTo(c - s, c + s - r);\n ctx.quadraticCurveTo(c - s, c + s, c - s + r, c + s);\n ctx.lineTo(c + s - r, c + s);\n ctx.quadraticCurveTo(c + s, c + s, c + s, c + s - r);\n ctx.lineTo(c + s, c - s + r);\n ctx.quadraticCurveTo(c + s, c - s, c + s - r, c - s);\n ctx.lineTo(c - s + r, c - s);\n ctx.quadraticCurveTo(c - s, c - s, c - s, c - s + r);\n ctx.lineTo(c - s, c);\n ctx.fill();\n ctx.stroke();\n // winds\n ctx.fillStyle = \"#000000\";\n ctx.font = \"40px garamond\";\n ctx.fillText(\"Est\", 505, 515 + s);\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate(-3.141592 / 2);\n ctx.translate(-525, -525);\n ctx.fillText(\"Sud\", 500, 515 + s);\n ctx.restore();\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate(3.141592);\n ctx.translate(-525, -525);\n ctx.fillText(\"Ouest\", 480, 515 + s);\n ctx.restore();\n ctx.save();\n ctx.translate(525, 525);\n ctx.rotate(3.141592 / 2);\n ctx.translate(-525, -525);\n ctx.fillText(\"Nord\", 485, 515 + s);\n ctx.restore();\n ctx.restore();\n}\n\n\n//# sourceURL=webpack:///./src/state.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/dp4.ts");
|
|
/******/
|
|
/******/ })()
|
|
; |