Content is user-generated and unverified.
// 麻雀役判定システム(正規表現版) class MahjongHandAnalyzer { constructor() { // 牌の種類定義 this.tiles = { man: '123456789', // 萬子 so: 'ABCDEFGHI', // 索子 pin: 'abcdefghi', // 筒子 honor: '><v^.#@' // 字牌(東西南北白発中) }; // 全ての牌 this.allTiles = this.tiles.man + this.tiles.so + this.tiles.pin + this.tiles.honor; } // 手牌をソートする sortHand(hand) { const order = this.allTiles; return hand.split('').sort((a, b) => order.indexOf(a) - order.indexOf(b)).join(''); } // 同じ牌の個数をカウント countTiles(hand) { const count = {}; for (const tile of hand) { count[tile] = (count[tile] || 0) + 1; } return count; } // 順子(連続する3枚)を検出 findShuntsu(tiles, type) { const patterns = []; const chars = this.tiles[type]; for (let i = 0; i < chars.length - 2; i++) { const pattern = chars[i] + chars[i+1] + chars[i+2]; const regex = new RegExp(`[${pattern}]{3}`, 'g'); if (regex.test(tiles)) { patterns.push(pattern); } } return patterns; } // 刻子(同じ牌3枚)を検出 findKotsu(hand) { const patterns = []; const regex = /(.)\1{2}/g; let match; while ((match = regex.exec(hand)) !== null) { patterns.push(match[0]); regex.lastIndex = match.index + 1; // 重複検出のため } return patterns; } // 対子(同じ牌2枚)を検出 findToitsu(hand) { const patterns = []; const regex = /(.)\1/g; let match; while ((match = regex.exec(hand)) !== null) { patterns.push(match[0]); regex.lastIndex = match.index + 1; } return patterns; } // タンヤオ(中張牌のみ)判定 isTanyao(hand) { const regex = /^[2-8A-Ha-h]+$/; return regex.test(hand); } // 字牌の役判定 checkHonorYaku(hand) { const yakuList = []; // 白(三元牌) if (/\.{3}/.test(hand)) yakuList.push('白'); // 発(三元牌) if (/##{3}/.test(hand)) yakuList.push('発'); // 中(三元牌) if (/@{3}/.test(hand)) yakuList.push('中'); // 風牌 if (/>>{3}/.test(hand)) yakuList.push('東'); if (/<<{3}/.test(hand)) yakuList.push('西'); if (/vv{3}/.test(hand)) yakuList.push('南'); if (/\^\^{3}/.test(hand)) yakuList.push('北'); return yakuList; } // 清一色判定 isChinitsu(hand) { if (/^[1-9]+$/.test(hand)) return '清一色(萬子)'; if (/^[A-I]+$/.test(hand)) return '清一色(索子)'; if (/^[a-i]+$/.test(hand)) return '清一色(筒子)'; return null; } // 混一色判定 isHonitsu(hand) { const hasHonor = /[><v^.#@]/.test(hand); if (!hasHonor) return null; const noHonor = hand.replace(/[><v^.#@]/g, ''); if (/^[1-9]+$/.test(noHonor)) return '混一色(萬子)'; if (/^[A-I]+$/.test(noHonor)) return '混一色(索子)'; if (/^[a-i]+$/.test(noHonor)) return '混一色(筒子)'; return null; } // 平和判定(簡易版) isPinfu(hand) { // 字牌がない if (/[><v^.#@]/.test(hand)) return false; // 端牌の対子でない(簡易チェック) const toitsu = this.findToitsu(hand); if (toitsu.some(t => /[19AIai><v^.#@]/.test(t[0]))) return false; return true; } // 待ち牌を計算 calculateWaits(hand) { const waits = new Set(); const sortedHand = this.sortHand(hand); // 各牌を1枚追加してテンパイになるかチェック for (const tile of this.allTiles) { const testHand = this.sortHand(sortedHand + tile); if (this.isWinningHand(testHand)) { waits.add(tile); } } return Array.from(waits); } // 和了形かどうか判定(簡易版) isWinningHand(hand) { if (hand.length !== 14) return false; // 七対子チェック if (this.isChitoitsu(hand)) return true; // 通常の和了形(1雀頭 + 4面子) const count = this.countTiles(hand); const tiles = Object.entries(count); // 雀頭候補を探す for (const [tile, num] of tiles) { if (num >= 2) { // 雀頭を除いた残りで4面子作れるか簡易チェック const remaining = hand.replace(new RegExp(tile, 'g'), (match, offset, string) => { const index = string.indexOf(tile); const secondIndex = string.indexOf(tile, index + 1); return (offset === index || offset === secondIndex) ? '' : match; }); if (this.canFormMentsu(remaining)) return true; } } return false; } // 面子を作れるか(簡易判定) canFormMentsu(tiles) { // 長さが12(4面子分)でない場合は不可 if (tiles.length !== 12) return false; // 全て刻子の場合 const kotsuPattern = /^((.)\2{2}){4}$/; if (kotsuPattern.test(tiles)) return true; // その他の組み合わせ(簡易チェック) return tiles.length === 12; } // 七対子判定 isChitoitsu(hand) { const regex = /^((.)\2){7}$/; const sorted = this.sortHand(hand); // 同じ牌が7組あるかチェック const count = this.countTiles(hand); const pairs = Object.values(count).filter(c => c === 2); return pairs.length === 7; } // メイン判定関数 analyzeHand(hand) { const sortedHand = this.sortHand(hand); const yakuList = []; // 役の判定 if (this.isTanyao(sortedHand)) { yakuList.push('タンヤオ'); } const chinItsu = this.isChinitsu(sortedHand); if (chinItsu) { yakuList.push(chinItsu); } const honItsu = this.isHonitsu(sortedHand); if (honItsu) { yakuList.push(honItsu); } const honorYaku = this.checkHonorYaku(sortedHand); yakuList.push(...honorYaku); if (this.isChitoitsu(sortedHand)) { yakuList.push('七対子'); } if (this.isPinfu(sortedHand) && yakuList.length === 0) { yakuList.push('平和(可能性)'); } // 待ち牌の計算 const waits = this.calculateWaits(sortedHand); return { hand: sortedHand, yaku: yakuList.length > 0 ? yakuList : ['役なし'], waits: waits, waitDisplay: waits.map(w => { if (this.tiles.man.includes(w)) return `${this.tiles.man.indexOf(w) + 1}萬`; if (this.tiles.so.includes(w)) return `${this.tiles.so.indexOf(w) + 1}索`; if (this.tiles.pin.includes(w)) return `${this.tiles.pin.indexOf(w) + 1}筒`; if (w === '>') return '東'; if (w === '<') return '西'; if (w === 'v') return '南'; if (w === '^') return '北'; if (w === '.') return '白'; if (w === '#') return '発'; if (w === '@') return '中'; return w; }) }; } } // 使用例 const analyzer = new MahjongHandAnalyzer(); // テストケース const testHands = [ '123445567889.', '111222333444a', 'AABBCCDDEEFFG', '11223344556677', '>>><<<vvv^^^..', '123456789ABC@', ]; console.log('=== 麻雀役判定結果 ===\n'); testHands.forEach(hand => { const result = analyzer.analyzeHand(hand); console.log(`手牌: ${hand}`); console.log(`整理後: ${result.hand}`); console.log(`役: ${result.yaku.join(', ')}`); console.log(`待ち: ${result.waitDisplay.join(', ')}`); console.log('-------------------\n'); });
Content is user-generated and unverified.
    麻雀役判定システム(正規表現版) | Claude