
React × Framer Motion で作る一人麻雀ゲーム
作成日:
更新日:
ブラウザで動く一人麻雀ゲームを作ってみました。対戦相手はおらず、配牌から自分のペースで手役を組み立てていく、パズル的なゲームです。
実際に遊んでみる
まずは実際に遊んでみてください。「ゲーム開始」ボタンを押すと配牌され、ツモと捨て牌を繰り返してあがりを目指します。
読み込み中...
遊び方
- ゲーム開始 - 13枚の牌が配られ、自動的に1枚ツモります
- 捨て牌 - 手牌または引いた牌を選択し、もう一度クリックで捨てます(捨てると自動で次の牌をツモります)
- リーチ - テンパイ時にリーチ宣言ができます
- ツモあがり - あがり形になったら「ツモ!」ボタンが表示されます
手牌はドラッグ&ドロップで並べ替えることができます(リーチ後は並べ替え不可)。
技術構成
このゲームは以下の技術で構成されています。
- React 19 - UIフレームワーク
- Framer Motion - アニメーションライブラリ
- TypeScript - 型安全な開発
特別なゲームエンジンは使用せず、既存のWebフロントエンド技術だけで実装しています。
実装のポイント
牌のアニメーション
Framer Motion の motion.div を使って、牌のホバー・選択・移動アニメーションを実装しています。
<motion.div
layout
layoutId={tile.id}
whileHover={{ y: -6 }}
whileTap={{ scale: 0.95 }}
animate={{
y: isSelected ? -12 : 0,
boxShadow: isSelected
? '0 8px 20px rgba(0,0,0,0.3)'
: '0 2px 4px rgba(0,0,0,0.2)',
}}
transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
{/* 牌の内容 */}
</motion.div>layoutId を指定することで、牌が手牌から河に移動する際にスムーズなアニメーションが実現できます。
手牌の並べ替え
Framer Motion の Reorder コンポーネントを使って、ドラッグ&ドロップでの並べ替えを実装しています。
<Reorder.Group
axis="x"
values={tiles}
onReorder={handleReorder}
>
{tiles.map((tile) => (
<Reorder.Item key={tile.id} value={tile}>
<MahjongTile tile={tile} />
</Reorder.Item>
))}
</Reorder.Group>ゲーム状態管理
カスタムフック useMahjongGame でゲームの状態を管理しています。
export type GamePhase =
| 'waiting' // ゲーム開始待ち
| 'dealing' // 配牌中
| 'draw' // ツモ待ち
| 'discard' // 捨て牌選択中
| 'win' // あがり
| 'draw_game'; // 流局
export interface GameState {
phase: GamePhase;
wall: Tile[]; // 牌山
hand: Tile[]; // 手牌
river: Tile[]; // 河(捨て牌)
drawnTile: Tile | null;
doraIndicators: Tile[];
isReach: boolean;
// ...
}役判定
あがり形の判定は、雀頭(対子)を抜いて残りが4面子(刻子または順子)で構成されるかを再帰的にチェックしています。
// 雀頭候補を試す
for (const [key, group] of groups) {
if (group.length >= 2) {
const pair = group.slice(0, 2);
const remaining = tiles.filter((t) => !pair.includes(t));
const result = extractSets(remaining, []);
if (result && result.sets.length === 4) {
return { isWinning: true, sets: result.sets, pair };
}
}
}特殊形として七対子と国士無双にも対応しています。
対応している役
Mリーグルールに準拠した役を判定できます。
1翻役
| 役名 | 説明 |
|---|---|
| リーチ | 門前でリーチ宣言 |
| 一発 | リーチ後1巡以内にあがり |
| 門前清自摸和 | 門前でツモあがり |
| 平和 | 全て順子で役牌以外の雀頭 |
| 断么九 | 么九牌を含まない |
| 一盃口 | 同じ順子が2つ |
| 役牌 | 白・發・中の刻子 |
| 海底摸月 | 最後のツモであがり |
2翻役
| 役名 | 説明 |
|---|---|
| ダブルリーチ | 第一巡でリーチ |
| 七対子 | 7つの対子 |
| 一気通貫 | 同じ色で123, 456, 789の順子 |
| 三色同順 | 3色で同じ数字の順子 |
| 三色同刻 | 3色で同じ数字の刻子 |
| 対々和 | 全て刻子 |
| 三暗刻 | 暗刻が3つ |
| 小三元 | 白發中のうち2刻子+1雀頭 |
| 混老頭 | 么九牌のみ |
| チャンタ | 全ての面子と雀頭に么九牌 |
3翻役
| 役名 | 説明 |
|---|---|
| 混一色 | 一種類の数牌と字牌のみ |
| 純チャン | 全ての面子と雀頭に1,9(字牌なし) |
| 二盃口 | 一盃口が2組 |
6翻役
| 役名 | 説明 |
|---|---|
| 清一色 | 一種類の数牌のみ |
役満
| 役名 | 説明 |
|---|---|
| 天和 | 配牌であがり |
| 国士無双 | 13種の么九牌+1枚 |
| 四暗刻 | 4つの暗刻 |
| 大三元 | 白發中の刻子 |
| 小四喜 | 東南西北のうち3刻子+1雀頭 |
| 大四喜 | 東南西北の刻子 |
| 字一色 | 字牌のみ |
| 緑一色 | 緑色の牌のみ(索子2,3,4,6,8と發) |
| 清老頭 | 1と9の数牌のみ |
| 九蓮宝燈 | 同色で1112345678999+1枚 |
まとめ
React と Framer Motion を使えば、特別なゲームエンジンなしでもブラウザゲームを作ることができます。
今回のポイント:
- Framer Motion で手軽にアニメーションを実装
- Reorder でドラッグ&ドロップの並べ替え
- カスタムフック でゲームロジックを分離
- TypeScript で型安全に牌や役を管理
麻雀のルールは複雑ですが、一人用にすることで鳴きや点数計算を省略でき、実装がシンプルになります。ぜひ遊んでみてください。