依川 愛瀬
← Works に戻る

花札ゲーム「そふ花」

担当
ゲームロジック・UI 実装
URL
https://packed7ice.github.io/sofuhana/
ソースコード
https://github.com/packed7Ice/sofuhana

サークルの成果物として制作した、ブラウザで遊べる花札「こいこい」ゲーム。フレームワークを使わず、素の HTML5 / CSS3 / JavaScript (ES Modules) のみで SPA として実装しました。山札・手札・場札の状態管理、役判定・得点計算、CPU の簡易 AI、チュートリアル機能までを自前で実装しています。MIT ライセンスでソースコードを公開中です。

技術スタック

HTML5 / CSS3 / JavaScript (ES Modules) / node --test / GitHub Pages

制作のポイント

コード抜粋

役判定ロジック(js/card-data.js より抜粋)
export function checkYaku(cards){
  const yakuList = [];
  const capturedSet = new Set(cards);
  const containsAll = (names) => names.every(name => capturedSet.has(name));

  const inoshikacho = ['萩に猪','紅葉に鹿','牡丹に蝶'];
  if (containsAll(inoshikacho)) yakuList.push('猪鹿蝶');

  if (containsAll(RED_TAN_CARDS)) yakuList.push('赤短');
  if (containsAll(BLUE_TAN_CARDS)) yakuList.push('青短');

  if (capturedSet.has('芒に月') && capturedSet.has('菊に盃')) yakuList.push('月見酒');
  if (capturedSet.has('桜に幕') && capturedSet.has('菊に盃')) yakuList.push('花見酒');

  const lights = cards.filter(c => getCardType(c)==='光' || c==='柳に小野道風');
  const tane = cards.filter(c => getCardType(c)==='タネ');
  const tanzaku = cards.filter(c => getCardType(c)==='短冊');
  let kasu = cards.filter(c => getCardType(c)==='カス').length;
  if (cards.includes('菊に盃')) kasu += 1; // 「菊に盃」はカスにも数える

  const hasRain = cards.includes('柳に小野道風');
  if (lights.length >= 5) yakuList.push('五光');
  else if (lights.length === 4 && !hasRain) yakuList.push('四光');
  else if (lights.length === 4 && hasRain) yakuList.push('雨四光');
  else if (lights.length >= 3 && !hasRain) yakuList.push('三光');

  if (tane.length >= 5) yakuList.push('タネ');
  if (tanzaku.length >= 5) yakuList.push('短冊');
  if (kasu >= 10) yakuList.push('カス');

  return yakuList;
}

取得札の集合から猪鹿蝶・赤短・五光などの役を判定する。光札・タネ・短冊・カスの枚数と特殊札(柳に小野道風・菊に盃)の扱いを自前で管理。

CPU の「こいこい / 上がり」判断(script.js より抜粋)
const cpuEvaluation = scoreFromCaptured(state.cpuCaptured);
if (cpuEvaluation.basePoints > 0){
  let cpuEnds = false;
  if (cpuEvaluation.basePoints >= 7) cpuEnds = true;
  else if (state.deck.length <= 8 && cpuEvaluation.basePoints >= 5) cpuEnds = true;
  else if (state.cpuHand.length === 0 || state.playerKoikoi) cpuEnds = true; // 強制上がり
  else cpuEnds = Math.random() < 0.5;

  if (cpuEnds){
    endRound('cpu');
    return;
  }

  state.cpuKoikoi = true;
  state.cpuKoikoiBasePoints = cpuEvaluation.basePoints || 0;
  showKoikoiDeclaration('cpu');
  showBottomMessage('相手は「こいこい」!');
}

現在の得点・山札の残り枚数・強制上がり条件を加味して、CPU が「上がり」か「こいこい」かを選択する簡易 AI。