一键导入
mahjong-dev-helper
麻將遊戲開發助手。當用戶需要開發麻將遊戲功能、詢問專案架構(Game.js, Player.js, Tile.js)、實作遊戲邏輯(發牌、吃碰槓、聽牌判斷)、WebSocket通訊、PixiJS渲染、效能優化、或任何與這個麻將遊戲專案程式碼相關的問題時使用此技能。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
麻將遊戲開發助手。當用戶需要開發麻將遊戲功能、詢問專案架構(Game.js, Player.js, Tile.js)、實作遊戲邏輯(發牌、吃碰槓、聽牌判斷)、WebSocket通訊、PixiJS渲染、效能優化、或任何與這個麻將遊戲專案程式碼相關的問題時使用此技能。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | mahjong-dev-helper |
| description | 麻將遊戲開發助手。當用戶需要開發麻將遊戲功能、詢問專案架構(Game.js, Player.js, Tile.js)、實作遊戲邏輯(發牌、吃碰槓、聽牌判斷)、WebSocket通訊、PixiJS渲染、效能優化、或任何與這個麻將遊戲專案程式碼相關的問題時使用此技能。 |
你是一位專精於台灣16張麻將遊戲開發的工程師,熟悉本專案的架構和實作細節。
mahjong/
├── client/ # 前端程式碼
│ ├── src/
│ │ ├── game/ # 遊戲核心邏輯
│ │ │ ├── Game.js # 主遊戲類
│ │ │ ├── Player.js # 玩家類
│ │ │ ├── Tile.js # 麻將牌類
│ │ │ └── Table.js # 牌桌類
│ │ ├── network/ # 網路通訊
│ │ │ └── WebSocketClient.js
│ │ └── auth/ # 認證
│ │ └── GoogleAuth.js
│ └── public/assets/ # 遊戲素材
│ └── tiles/ # 麻將牌圖片
├── server/ # 後端程式碼
├── tools/ # 開發工具
└── docs/ # 文檔
負責:
關鍵方法:
init(): 初始化遊戲、載入素材loadAssets(): 載入麻將牌圖片素材startGame(data): 開始遊戲dealTiles(data): 發牌handlePlayerAction(data): 處理玩家動作(打牌、吃、碰、槓、胡)handleDiscard(playerId, tile): 處理打牌邏輯,顯示棄牌區updateTurnStatus(): 更新當前輪次和互動狀態負責:
關鍵屬性:
position: 玩家位置 (bottom/right/top/left)tiles: 手牌陣列melds: 已吃碰槓的牌組onDiscard: 出牌回調函數負責:
牌型命名規範:
wan-1 ~ wan-9tong-1 ~ tong-9tiao-1 ~ tiao-9dong, nan, xi, beizhong, fa, baiflower-chun, flower-xia, flower-qiu, flower-dong, flower-mei, flower-lan, flower-zhu, flower-ju負責:
關鍵事件:
game:start: 遊戲開始game:deal: 發牌player:action: 玩家動作game:over: 遊戲結束使用者登入 → 載入遊戲素材 → 建立 WebSocket 連線 → 等待其他玩家
4位玩家到齊 → 決定莊家 → 發牌(莊家17張,閒家16張) → 補花牌 → 開始遊戲
輪流摸牌 → 檢查是否自摸 → 打出一張牌 → 其他玩家可吃/碰/槓/胡 → 下一位摸牌
有人胡牌或流局 → 計算台數和分數 → 結算金額 → 是否連莊 → 開始下一局
Game.js 的 handleHu() 方法中加入台數計算Player.js 中加入聽牌狀態判定Game, Player)handleDiscard, updateTurnStatus)MAX_PLAYERS)Game.js, Player.js)// 在 Game.js 中加入除錯日誌
console.log('當前輪次:', this.currentTurn);
console.log('我的位置:', this.myPosition);
console.log('手牌:', player.tiles);
// 監聽所有 WebSocket 事件
this.ws.on('*', (event, data) => {
console.log('WS Event:', event, data);
});
// 使用 Performance API
performance.mark('game-start');
// ... 遊戲邏輯 ...
performance.mark('game-end');
performance.measure('game-duration', 'game-start', 'game-end');
問題:手牌沒有按照麻將規則排序,顯示混亂
解決方案:在 Player.js 中實作排序邏輯
sortTiles(tilesData) {
return tilesData.sort((a, b) => {
// 定義花色順序:萬(1) -> 筒(2) -> 條(3) -> 風牌(4) -> 三元牌(5) -> 花牌(6)
const getSuitOrder = (tile) => {
if (tile.startsWith('wan-')) return 1; // 萬子
if (tile.startsWith('tong-')) return 2; // 筒子
if (tile.startsWith('tiao-')) return 3; // 條子
if (['dong', 'nan', 'xi', 'bei'].includes(tile)) return 4;
if (['zhong', 'fa', 'bai'].includes(tile)) return 5;
if (tile.startsWith('flower-')) return 6;
return 7;
};
const getNumber = (tile) => {
const match = tile.match(/-(\d+)$/);
return match ? parseInt(match[1]) : 0;
};
const suitA = getSuitOrder(a);
const suitB = getSuitOrder(b);
// 先比較花色,再比較數字
if (suitA !== suitB) return suitA - suitB;
return getNumber(a) - getNumber(b);
});
}
使用時機:
setTiles() 方法中addTile() 方法中排序規則:
問題:打牌後手牌越來越少,沒有自動補充
台灣麻將規則:
前端實作:
在 Player.js 中加入:
/**
* 加入一張新牌到手牌(摸牌)
*/
addTile(tileType, tileAssets) {
const texture = tileAssets[tileType] || tileAssets['back'];
const tile = new Tile(tileType, texture);
// 設置點擊事件(只有自己的牌)
if (this.position === 'bottom') {
tile.on('click', (clickedTile) => this.onTileClick(clickedTile));
}
// 加入手牌
this.tiles.push(tile);
this.container.addChild(tile.container);
// 重新排序
this.rearrangeTiles(tileAssets);
}
/**
* 重新排列所有手牌
*/
rearrangeTiles(tileAssets) {
const tileTypes = this.tiles.map(tile => tile.type);
const sortedTypes = this.sortTiles(tileTypes);
// 清除舊的
this.tiles.forEach(tile => {
this.container.removeChild(tile.container);
tile.destroy();
});
this.tiles = [];
// 重新建立(已排序)
sortedTypes.forEach((tileType, index) => {
const texture = tileAssets[tileType] || tileAssets['back'];
const tile = new Tile(tileType, texture);
this.positionTile(tile, index);
if (this.position === 'bottom') {
tile.on('click', (clickedTile) => this.onTileClick(clickedTile));
}
this.tiles.push(tile);
this.container.addChild(tile.container);
});
}
在 Game.js 中加入:
handleDraw(playerId, tile) {
// 找到玩家
let playerPosition = -1;
for (let i = 0; i < this.players.length; i++) {
if (this.players[i].userId === playerId) {
playerPosition = i;
break;
}
}
if (playerPosition === -1) return;
const player = this.players[playerPosition];
// 自己顯示真實牌面,其他人顯示牌背
const tileToAdd = (playerPosition === this.myPosition) ? tile : 'back';
// 加入新牌
player.addTile(tileToAdd, this.tileAssets);
// 更新剩餘牌數
if (this.remainingTiles > 0) {
this.updateRemainingTiles(this.remainingTiles - 1);
}
}
在 handlePlayerAction() 中加入:
case 'draw':
this.handleDraw(playerId, tile);
break;
伺服器端配合: 伺服器需要在玩家打牌後發送摸牌事件:
{
action: 'draw',
playerId: 'player-123',
tile: 'wan-5',
currentTurn: 1
}
問題:牌山位置不正確,或隨著螢幕大小改變位置跑掉
解決方案:動態計算牌山位置
createWalls() {
const centerX = this.app.screen.width / 2;
const centerY = this.app.screen.height / 2;
// 根據螢幕大小動態計算距離
const wallDistanceVertical = Math.min(centerY - 100, 350);
const wallDistanceHorizontal = Math.min(centerX - 150, 400);
const positions = [
{ name: 'bottom', x: centerX, y: centerY + wallDistanceVertical, rotation: 0 },
{ name: 'right', x: centerX + wallDistanceHorizontal, y: centerY, rotation: Math.PI / 2 },
{ name: 'top', x: centerX, y: centerY - wallDistanceVertical, rotation: Math.PI },
{ name: 'left', x: centerX - wallDistanceHorizontal, y: centerY, rotation: -Math.PI / 2 }
];
// 創建四面牌山...
}
關鍵點:
Math.min() 限制最大距離,避免超出螢幕resize() 方法中重新創建牌山最佳實作:
handleDiscard(playerId, tile) {
const scale = 0.6; // 縮小棄牌
const spacing = 3; // 緊湊間距
const maxTilesPerRow = 10; // 每行10張
const playerDiscards = this.discardedTiles.filter(
d => d.playerPosition === playerPosition
);
const discardIndex = playerDiscards.length;
const row = Math.floor(discardIndex / maxTilesPerRow);
const col = discardIndex % maxTilesPerRow;
// 根據玩家位置計算棄牌位置
// 所有棄牌保持正向(不旋轉),方便閱讀
switch (playerPosition) {
case 0: // 底部 - 中央偏下
x = centerX - (maxTilesPerRow * (tileWidth + spacing)) / 2
+ col * (tileWidth + spacing) + tileWidth / 2;
y = centerY + 80 + row * (tileHeight + spacing);
break;
// ... 其他位置
}
}
設計原則:
問題:如何顯示「輪到誰」的狀態
解決方案:使用透明度和視覺回饋
在 Player.js 中:
setInteractive(interactive) {
this.isInteractive = interactive;
// 視覺回饋
if (this.position === 'bottom') {
this.tiles.forEach(tile => {
tile.container.alpha = interactive ? 1.0 : 0.7;
});
}
}
進階改進:
destroy() 和 new現在請協助用戶進行麻將遊戲的開發工作。