柴犬育成ゲーム お好きな柴ちゃんを選んで育てていきます

このゲームはブラウザゲームで、ダウンロードなしでプレイできる仕様となっています。ただ、作成者MOMOは、この柴っちGO!ゲームをWEBに公開していません。なので本ゲームプログラムの参考コードを公開して、どなたでもUIのデザイン変更・コードの追加、削除、修正等をご自由に変更してもらい、開発者様が独自のゲーム開発を目的として、参考になればと思う次第です。また、部分的な関数コードや全体もコピペ等の制限は一切ございません。

開発者様がコードを参考にしてWEBで公開された場合、著作権の帰属は開発者様となりますので、ご安心してください。

ただし、全体を変更せずに丸ごとWEB上に公開する事はご縁了ください。個人で使用する限りでは制限はありません。

画像の見本(一部)

仕様
A 各ボタン・表示
1.柴犬選択 2.愛犬名の入力 3.家族に迎える 4.わんちゃんを変更する 5.家族になった日 6.愛犬名 7.愛犬年齢 8.体力・体調バー 9.薬残量バー 10.ごはん・おやつ・おみず・さんぽの各バー 11.柴ちゃんの現在の調子表示 12.ごはん・おやつ・水・クスリ・散歩・お手・お座りの各アイテム 13.柴ちゃんのメッセージ表示 14.成長に従って柴ちゃん・背景を変更 15.終了・継続ボタン
B 保存方式
1.ローカルストレージ採用 ※キャッシュ等のクリアを考慮した場合はサーバー保存を推奨
C ゲームの流れ
1.3種類の中の柴ちゃんを選択する
2.生後2か月で育成スタート
実際のPCのカレンダーと同期して、その日を色々とお世話をする。柴犬のメッセージに従って各アイテムをクリックして体力や体調を維持する。当日23:59分までお世話ができるけど、日付が変わると当日分はお世話できない。
3.1年後は成犬画像に変更
4.1年2か月後は老犬表示
5.1年3か月後は愛犬の老衰でEND ※1か月を犬年齢7年として経過を考慮
6.なので、このゲームは通算で1年3か月を要します
※ゲーム開始から途中でキャンセルする仕様は設けていません。理由としては、折角家族として迎えた柴ちゃんを放棄するような事なので、最後まで面倒を見る事が大事と考えました。なので、最後には”これでお別れにしたい””もう一度家族に迎い入れたい”の選択ボタンを設けています。
D 画像使用
1.APNG画像対応。フレームレート(fps)を利用してアニメーションを表示、静止画像もOK 24種類の柴犬画像を使用
2.背景はバックグランド・キャンバス内を含んで3種類
E その他
1. 任意の日付変更抑止(日付を改ざんした場合のエラーメッセージ)

1.ゲーム構成について

現在のゲーム構成は柴犬の選択(ステージ1)・育成(ステージ2)からスタートして、中間を省略してEND構成にしていますが、switchでステージ3・4・5まで想定した仮のコードを書いていますので、成長に従ってステージを進めると、よりリアルになると思います。また、1年3か月でゲームENDとしていますが、期間の調整や体力・体調が0の状態でENDにするなどの工夫も良いかもしれません。

2.コードについて

JSコードの行数が多くなっていますが、繰り返しコードの関数化、case文の縮小化(関数作成)、1行横並び表示の書き方など工夫をすると、かなりの行数が短縮できます。また、アルゴリズムの変更でさらによりリアルな育成ゲームになると思います。

3.使用画像について

使用している画像については、APNG画像(静止画を含む)で単純な動作アニメです。画像のフレーム数を多くして作成すると、柴犬のリアル感がでるので、作成できる方は取り入れた方が良いと思います このページの最後に使用画像の内容やダウンロードを設けていますので必要であればどうぞ。

4.データ管理について

育成データの保存については、ブラウザ上で本アプリは起動しているため、ブラウザ内のローカルストレージで保存処理を行っています。履歴データの削除、キャッシュの削除などの操作を行った場合は、育成途中でもデータがクリアされ初期状態に戻ります。これらを回避するにはデータをサーバーで管理するのを推奨します。

5.参考コードについて

下記のコードの使用は、全てコードの変更・修正・追加が可能です。
※コードのバグ、動作不具合等は一応確認済みですが、万が一不具合が発生した場合は開発者様で修正をお願いします。

各コードの紹介 順番にHTML JS CSS
HTML(メインはキャンバスで表示)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>柴っちGO!</title>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div id="uiContainer">
<div id="gameContainer">
  <div id="startTitle">柴っちGO!</div>
</div>
</div>

<div id="gameArea">

    <div id="topUI">
        <div id="nameInputArea" style="display:none;">
          <input id="dogName" placeholder="愛犬の名前(全角6文字迄)">
          <button id="startGameBtn">家族に迎える</button>
        </div>
        <button id="changeSelectBtn" style="display:none;">わんちゃんを変更する</button>
    </div>

    <canvas id="gameCanvas" width="800" height="600"></canvas>

    <img id="dogAnim"
         style="
            position:absolute;
            left:240px;
            top:180px;
            width:320px;
            display:none;
            pointer-events:none;
            z-index:10;
         ">

    <div id="dogMessageBox"
         style="
            position:absolute;
            width:360px;
            height:60px;
            left:50%;
            transform:translateX(-50%);
            background:rgba(245, 233, 192, 0.322);
            color: rgb(49, 11, 46);
            border-radius:18px;
            padding:6px;
            font-size:20px;
            display:none;
            z-index:20;
         ">
    </div>
<div id="endingButtons">

  <button id="goodbyeBtn" class="endingBtn goodbye">
    🌈 これでお別れにしたい
  </button>

  <button id="restartBtn" class="endingBtn restart">
    🐾 もう一度家族にしたい
  </button>

</div>
    <p id="copyright"> 2026 MOMO PLAN</p>
</div>
<script src="script.js"></script>
</body>
</html>
JS
// 画像プリロード
let currentDogImage = null;

const preloadList = [
  'img/start_a.png', //スタート画像
  'img/start_k.png',
  'img/start_s.png'
];

let preloadCount = 0;

function preloadImages(callback) {
  preloadList.forEach(src => {
    const img = new Image();
    img.onload = () => {
      preloadCount++;
      if (preloadCount === preloadList.length) {
        callback();  //全画像ロード完了 → 描画開始
      }
    };
    img.src = src;
  });
}

//起動時:画像プリロード → 初期 or ステージ2
window.onload = () => {

  preloadImages(() => {

    const save = localStorage.getItem("shibacchi_save");

    if (!save) {
    // セーブデータなし
      drawSelectScreen();
      drawBottomFrame(
        "私たちとお約束してください",
        [
          "私たちと一緒になった瞬間、ご主人様と一生過ごします🐶",
          "ただ、ごはんやお水がもらえないと、私は力が尽きてしまいます🐶",
          "体調が悪くなったらお薬をください。お薬が貰えないと、私は力が尽きてしまいます🐶",
          "でも、毎日触れ合っても、何年か先はお別れの日が必ず来ます🐶",
          "その時は感謝を込めて・・・・・🐶"
        ]
      );
      return;
    }

  // セーブデータあり
    const state = JSON.parse(save);

    switch (Number(state.stage)) {
      case 2:
        startStage2(state);
        break;

      case 3:
        startStage3(state); //追加枠
        break;

      case 4:
        startStage4(state); //追加枠
        break;

      case 5:
        startStage5(state); //追加枠
        break;

      default:
        drawSelectScreen();
    }

  });

};

// loadImg
function loadImg(src) {
  const img = new Image();
  img.src = src;
  return img;
}

// 犬画像セット
const dogImages = {
  aka: {
    start: loadImg('img/start_a.png'), //スタート
    puppy: loadImg('img/a01.png'), //子犬
    adult: loadImg('img/a02.png'), //成犬
    puppyOte:loadImg('img/a01_ote.png'), //お手
    puppyOsuwari:loadImg('img/a01_osuwari.png'), //お座り
    old:   loadImg('img/a03.png'), //老犬
    sick:  loadImg('img/shiba15.png'), //病気
    end: loadImg('img/r_end_shiba.png') //終了
  },
  kuro: {
    start: loadImg('img/start_k.png'),
    puppy: loadImg('img/k01.png'),
    puppyOte:loadImg('img/k01_ote.png'),
    puppyOsuwari:loadImg('img/k01_osuwari.png'),
    adult: loadImg('img/k02.png'),
    old:   loadImg('img/k03.png'),
    sick:  loadImg('img/shiba17.png'),
    end: loadImg('img/b_end_shiba.png')
  },
  shiro: {
    start: loadImg('img/start_s.png'),
    puppy: loadImg('img/s01.png'),
    puppyOte:loadImg('img/s01_ote.png'),
    puppyOsuwari:loadImg('img/s01_osuwari.png'),
    adult: loadImg('img/s02.png'),
    old:   loadImg('img/s03.png'),
    sick:  loadImg('img/shiba16.png'),
    end: loadImg('img/w_end_shiba.png')
  }
};

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

let selectedDogId = null;
let dogSelectAreas = [];

// 選択画面描画
const bgCanvas = loadImg("img/bg_room.png");
const bgPark = loadImg("img/dog_park.png");

function drawSelectScreen() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Canvas 背景画像
  if (bgCanvas) {
    ctx.drawImage(bgCanvas, 0, 0, canvas.width, canvas.height);
  }

  ctx.fillStyle = "#333";
  ctx.font = "28px sans-serif";
  ctx.fillText("🐶家族にしたい柴犬を選んでね🐶", 200, 60);

  const dogs = [
    { id: 'aka',  img: dogImages.aka.start },
    { id: 'kuro', img: dogImages.kuro.start },
    { id: 'shiro',img: dogImages.shiro.start }
  ];

  const imgW = 200;
  const imgH = 200;
  const startX = 80;
  const y = 150;

  dogSelectAreas = [];

  dogs.forEach((dog, i) => {
    const x = startX + i * 240;
    ctx.drawImage(dog.img, x, y, imgW, imgH);

    // 犬の種類ごとに文字色を変える
    ctx.font = "20px sans-serif";
    ctx.fillStyle =
      dog.id === 'aka'  ? "#694f07bb" :   // 赤柴
      dog.id === 'kuro' ? "#000"      :   // 黒柴
                          "#fff";         // 白柴

    const label =
      dog.id === 'aka'  ? '赤柴(女の子)' :
      dog.id === 'kuro' ? '黒柴(男の子)' :
                          '白柴(女の子)';

    ctx.fillText(label, x + 40, y + imgH + 30);

    dogSelectAreas.push({ id: dog.id, x, y, w: imgW, h: imgH });
  });

  if (selectedDogId) {
    const dog = dogSelectAreas.find(d => d.id === selectedDogId);
    if (dog) highlightSelectedDog(dog);
  }
}

// 案内
function drawBottomFrame(title, lines) {
  ctx.save();

  // 1行あたりの高さ
  const lineHeight = 26;

  // 最終行の下に余白を追加
  const bottomPadding = 20;

  // タイトル + 行数に応じて高さを自動計算
  const frameHeight = 40 + (lines.length * lineHeight) + bottomPadding;

  //柴犬の描画範囲より下に固定(安全領域)
  const y = canvas.height - frameHeight;

  // 半透明の黒帯
  ctx.fillStyle = "rgba(22, 22, 22, 0.45)";
  ctx.fillRect(0, y, canvas.width, frameHeight);

  // 白枠線
  ctx.strokeStyle = "#fff";
  ctx.lineWidth = 2;
  ctx.strokeRect(0, y, canvas.width, frameHeight);

  // タイトル(中央)
  ctx.fillStyle = "#ffffff";
  ctx.font = "18px sans-serif";
  ctx.textAlign = "center";
  ctx.fillText(title, canvas.width / 2, y + 30);

  // 箇条書き(左寄せ)
  ctx.textAlign = "left";
  ctx.font = "16px sans-serif";

  lines.forEach((line, i) => {
    ctx.fillText(
      "・ " + line,
      20,
      y + 60 + i * lineHeight
    );
  });

  ctx.restore();
}

// クリック処理(選択したら他は無効)
function getPointerPosition(e) {
  const rect = canvas.getBoundingClientRect();

  // タッチ操作の場合
  if (e.touches && e.touches.length > 0) {
    return {
      x: e.touches[0].clientX - rect.left,
      y: e.touches[0].clientY - rect.top
    };
  }

  // マウス操作の場合
  return {
    x: e.clientX - rect.left,
    y: e.clientY - rect.top
  };
}

// PC・スマホ共通イベント
canvas.addEventListener("click", handleCanvasClick);
canvas.addEventListener("touchstart", handleCanvasClick);

function handleCanvasClick(e) {

  // タッチ時のスクロール防止
  if (e.cancelable) e.preventDefault();

  const save = localStorage.getItem("shibacchi_save");

  // セーブデータがない=犬選択画面
  if (!save) {
    handleSelect(e);
    return;
  }

  const state = JSON.parse(save);

  switch (state.stage) {

    case 2:
      //handleStage2Click(e, state);
      break;

    case 3:
      //handleStage3Click(e, state);
      break;

    case 4:
      //handleStage4Click(e, state);
      break;

    case 5:
      // 必要になったら追加
      break;
  }
}

function handleSelect(e) {
  if (selectedDogId) return;

  const pos = getPointerPosition(e);
  const mx = pos.x;
  const my = pos.y;

  for (const dog of dogSelectAreas) {
    if (mx >= dog.x && mx <= dog.x + dog.w &&
        my >= dog.y && my <= dog.y + dog.h) {

      selectedDogId = dog.id;

      document.getElementById('nameInputArea').style.display = 'block';
      document.getElementById('changeSelectBtn').style.display = 'inline-block';

      drawSelectScreen();
      break;
    }
  }
}

const dogFrameColors = {
  aka: "#ac730b",   // 赤
  kuro: "#000000",  // 黒
  shiro: "#ffffff"  // 白
};

function highlightSelectedDog(dog) {
  ctx.strokeStyle = dogFrameColors[dog.id];
  ctx.lineWidth = 5;
  ctx.strokeRect(dog.x, dog.y, dog.w, dog.h);
}

document.getElementById('changeSelectBtn').addEventListener('click', () => {
  selectedDogId = null;
  document.getElementById('nameInputArea').style.display = 'none';
  document.getElementById('changeSelectBtn').style.display = 'none';
  drawSelectScreen();
});

// 名前決定&保存
function showCanvasMessage(text) {
  ctx.save();

  // 半透明の黒帯
  ctx.fillStyle = "rgba(0,0,0,0.6)";
  ctx.fillRect(0, canvas.height - 120, canvas.width, 120);

  // メッセージ文字
  ctx.fillStyle = "#fff";
  ctx.font = "24px sans-serif";
  ctx.textAlign = "center";

  const lines = text.split("\n");
  lines.forEach((line, i) => {
    ctx.fillText(line, canvas.width / 2, canvas.height - 80 + i * 30);
  });

  ctx.restore();
}

document.getElementById('startGameBtn').addEventListener('click', () => {
  const raw = document.getElementById('dogName').value.trim();

  if (!raw) {
    showCanvasMessage("私の名前を入れて..🐶");
    return;
  }

  //全角換算で6文字までかチェック
  let count = 0;
  for (const ch of raw) {
    const isFull = /[^\x00-\x7F]/.test(ch); // ASCII以外を全角扱い
    count += isFull ? 1 : 0.5;
  }

  if (count > 6) {
    showCanvasMessage("名前は全角6文字までだよ🐾");
    return;
  }

  const name = raw; // ここまで来たらOK

  if (!selectedDogId) {
    showCanvasMessage("柴犬を選んでください");
    return;
  }

  document.getElementById('nameInputArea').style.display = "none";
  document.getElementById('changeSelectBtn').style.display = "none";
  drawSelectedDogCenter(selectedDogId);

  let message = "";
  if (selectedDogId === "aka" || selectedDogId === "shiro") {
    message = `私を選んでくれてありがとう…\n私の名前は ${name}\nこれからよろしくね。`;
  } else {
    message = `僕を選んでくれてありがとう…\n僕の名前は ${name}\nこれからよろしくね。`;
  }

  showCanvasMessage(message);

  const today = getTodayJP();
  const state = {
    id: selectedDogId,
    name: name,
    adoptedDate: today,
    lastPlayDate: today,
    stage: 2,
    isSick: false,
    lastFeedDate: null,
    lastFeedCountToday: 0,
    lastWaterDate: null,
    lastMedicineDate: null,
    lastWalkDate: null,
    healthStatus: 'normal',
    deathReason: null
  };

  localStorage.setItem('shibacchi_save', JSON.stringify(state));

  setTimeout(() => {
    startStage2(state);
  }, 4000);
});

// 日付取得
function getTodayJP() {
  const d = new Date();
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

// 日付変換
function formatDateJP(dateStr) {
  const [year, month, day] = dateStr.split("-");
  return `${year}${month}${day}日`;
}

// 愛犬取得
function drawAdoptedDate(date) {
  ctx.save();

  const jpDate = formatDateJP(date);

  //白背景(白膜ゼロ)
  ctx.fillStyle = "rgba(255, 255, 255, 0.53)";
  ctx.roundRect(20, 20, 170, 55, 12);
  ctx.fill();

  // 枠線
  ctx.strokeStyle = "rgba(255, 255, 255, 0.9)";
  ctx.lineWidth = 1;
  ctx.roundRect(20, 20, 170, 55, 12);
  ctx.stroke();

  // テキスト(影なし)
  ctx.shadowColor = "transparent";

  ctx.fillStyle = "#4e1cd8";
  ctx.font = "bold 20px sans-serif";
  ctx.fillText("家族になった日", 35, 45);

  ctx.fillStyle = "#0b5e04";
  ctx.font = "bold 18px sans-serif";
  ctx.fillText(jpDate, 35, 65);

  ctx.restore();
}

// 愛犬名
function drawDogName(name) {
  ctx.save();

  // 影なし
  ctx.shadowColor = "transparent";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;

  // 枠線だけ(紫)
  ctx.strokeStyle = "#eeeeee";
  ctx.lineWidth = 1;
  ctx.roundRect(200, 20, 170, 55, 12);
  ctx.stroke();

  // タイトル
  ctx.fillStyle = "#4e1cd8";
  ctx.font = "bold 20px sans-serif";
  ctx.fillText("愛犬名", 215, 45);

  // 名前
  ctx.fillStyle = "#744508";
  ctx.font = "bold 18px sans-serif";
  ctx.fillText(name, 215, 65);

  ctx.restore();
}

function drawDogAge(adoptedDateStr) {
  ctx.save();

  const ageText = calcDogAgeText(adoptedDateStr);

  // 完全に影なし
  ctx.shadowColor = "transparent";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;

  // 枠線
  ctx.strokeStyle = "#eeeeee";
  ctx.lineWidth = 1;
  ctx.roundRect(380, 20, 170, 55, 12);
  ctx.stroke();

  // タイトル
  ctx.fillStyle = "#4e1cd8";
  ctx.font = "bold 20px sans-serif";
  ctx.fillText("愛犬年齢", 395, 45);

  // 年齢表示
  ctx.fillStyle = "#9c0790";
  ctx.font = "bold 18px sans-serif";
  ctx.fillText(ageText, 395, 65);

  ctx.restore();
}

// 該当画像演出
function drawSelectedDogCenter(dogId) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 背景
  ctx.drawImage(bgCanvas, 0, 0, canvas.width, canvas.height);

  // 選んだ柴犬の画像
  const img = dogId === "aka" ? dogImages.aka.start
            : dogId === "kuro" ? dogImages.kuro.start
            : dogImages.shiro.start;

  const w = 300;
  const h = 300;
  const x = (canvas.width - w) / 2;
  const y = (canvas.height - h) / 2 - 40;

  ctx.drawImage(img, x, y, w, h);
}

function getDogAgeMonths(adoptedDateStr) {

  const start = new Date(adoptedDateStr);
  const today = new Date();

  start.setHours(0,0,0,0);
  today.setHours(0,0,0,0);

  let months =
    (today.getFullYear() - start.getFullYear()) * 12 +
    (today.getMonth() - start.getMonth());

  //生後2か月スタート分を加算
  months += 2;

  return months;
}

// 現在表示画像を決定
function redrawStage2(state) {

const ageMonths = getDogAgeMonths(state.adoptedDate);

  // エンディング判定
// 15か月(1年3か月)以上
if (isGameEnd(state)) {

  // 初回だけ終了処理
  if (!state.gameEnd) {

    state.gameEnd = true;

    localStorage.setItem("shibacchi_save",JSON.stringify(state));
    showDogMessage(`${state.name} は今までありがとう🐶🌈\n`+ `ずっと大切な家族でした・・・😢`);
  }
  document.getElementById("endingButtons").style.display = "block";
  currentDogImage =dogImages[state.id].end;

  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.drawImage(bgStage2,0,0,canvas.width,canvas.height);
  drawStage2Dog(state.id,state);

 return;
}

  let stageKey;

    if (ageMonths >= 14) {
      stageKey = "old";
    } else if (ageMonths >= 12) {
      stageKey = "adult";
    } else {
      stageKey = "puppy";
    }

    if (state.isSick) {
      currentDogImage = dogImages[state.id].sick;
    } else if (
      state.stage === 2 && state.dogAction === "ote"
    ) {
      currentDogImage = dogImages[state.id].puppyOte;
    } else if (
      state.stage === 2 && state.dogAction === "osuwari"
    ) {
      currentDogImage = dogImages[state.id].puppyOsuwari;
    } else {
      currentDogImage = dogImages[state.id][stageKey];
    }

  // 描画
  ctx.clearRect(0,0,canvas.width,canvas.height);

// 年齢による背景変更
// 0〜11か月  : 子犬 → 部屋
// 12〜13か月 : 成犬 → 公園
// 14か月以上 : 老犬 → 部屋
  let bg;

  if (ageMonths >= 14) {

  // 老犬
  bg = bgStage2;

  } else if (ageMonths >= 12) {

  // 成犬
  bg = bgPark;

  } else {

  // 子犬
  bg = bgStage2;
  }
  ctx.drawImage(bg,0,0,canvas.width,canvas.height);

    drawAdoptedDate(state.adoptedDate);
    drawDogName(state.name);
    drawDogAge(state.adoptedDate);

    drawStage2Dog(state.id,state);
    drawStage2Items();
    drawCareBars(state);
    drawProgressBar(30,110,state.power,10,"体力");
    drawProgressBar(30,150,state.health,10,"体調");
    drawDogMood(state);
}

function showDogMessage(text) {
  const box = document.getElementById("dogMessageBox");
  box.innerText = text;
  positionDogMessageBox();   // 位置をCanvasに合わせる
  box.style.display = "block";
}
function hideDogMessage() {
  const box = document.getElementById("dogMessageBox");
  box.style.display = "none";
}

function drawProgressBar(x, y, value, maxValue, label) {
  const width = 200;
  const height = 10;

  // 枠
  /*ctx.strokeStyle = "#fff";
  ctx.lineWidth = 2;
  ctx.strokeRect(x, y, width, height);*/

  //色を条件で変更
  let barColor = "lime"; // 通常色

  if (value <= 5) {

  if(label === "体力")
    barColor = "orange";

  if(label === "体調")
    barColor = "orange";
}

if(value <= 2){

  if(label === "体力")
    barColor = "red";

  if(label === "体調")
    barColor = "red";
}

  // 内側(割合)
  const ratio = value / maxValue;
  ctx.fillStyle = barColor;
  ctx.fillRect(x, y, width * ratio, height);

  const displayValue = Math.floor(value);
  // ラベル
  ctx.fillStyle = "#91108a";
  ctx.font = "16px sans-serif";
  ctx.fillText(
  `${label}: ${displayValue}/${maxValue}`,
  x,
  y - 5
);

  //4以下なら ⚠️ を左側に表示
if (value <= 4) {
  ctx.fillStyle = "yellow";
  ctx.font = "20px sans-serif";

  ctx.fillText("⚠️", x - 30, y + height - 2);
  }
}

// 柴犬の気持ち表示
function getDogMood(state) {

  const mood =
    (state.power + state.health) / 2;

  if (state.isSick) {
    return "少しつらそう…🐶";
  }

  if (mood >= 8) {
    return "満足しているよ🐶✨";
  }

  if (mood >= 5) {
    return "やや満足しているよ🐶";
  }

  if (mood >= 3) {
    return "少し不満そう…🐶";
  }

  return "元気がないみたい…🐶";
}

function drawDogMood(state) {

  const moodText = getDogMood(state);

  const text =
    `${state.name} の気持ちは${moodText}`;

  ctx.fillStyle = "#251c31";
  ctx.font = "bold 16px sans-serif";
  //元気がない時だけ点滅
  const blink =
    Math.floor(Date.now() / 1000) % 2 === 0;
  if (
  moodText.includes("元気がないみたい…🐶") ||
  moodText.includes("少しつらそう…🐶")
) {

  if (blink) {

    ctx.fillText(
      text,
      210,
      200
    );
  }
} else {
  ctx.fillText(
    text,
    210,
    200
    );
  }
}

// ステージ2(仮表示)
const bgStage2 = loadImg("img/bg_room.png");

function startStage2(state) {

  const today = getTodayJP();

  // 初期データ確認
  state.power ??= 10;
  state.health ??= 10;
  state.dogAction ??= "normal";
  // ===== 要求回数 =====
  state.dailyCare ??= {date: today,food: 0,snack: 0,water: 0,walk: 0};
  // ===== 各バー値 =====
  state.careBar ??= {food: 2,snack: 3,water: 3,walk: 3,medicine: 1};

  // 日付チェック
  state.lastPlayDate ??= today;

  const now = new Date(today);
  const last = new Date(state.lastPlayDate);

  // 時計を戻した場合
  if (now < last) {
    showDogMessage(
      `${state.name} は何才かわからなくなっちゃった。\nお世話になりました・・・🐶`
    );
    return;
  }
  //未来ジャンプ確認
  const diff =
    (now - last) / (1000 * 60 * 60 * 24);

  if (diff > 2) {
    showDogMessage(
      `${state.name} の時間が急に進みました🐶`
    );
    return;
  }

// 日付変更処理
if (state.dailyCare.date !== today) {

  // 前日の世話不足を反映
  checkDailyPenalty(state);

  // 自然病気チェック
  checkNaturalSick(state);

  // 今日の要求回数リセット
  state.dailyCare = {
    date: today,
    food: 0,
    snack: 0,
    water: 0,
    walk: 0
  };

  // おねだり終了状態解除
  state.todayCareFinished = false;

}

  // 最終保存日更新
  state.lastPlayDate = today;
  localStorage.setItem(
    "shibacchi_save",
    JSON.stringify(state)
  );

  // 画像設定
const ageMonths = getDogAgeMonths(state.adoptedDate);

let stageKey;

if (ageMonths >= 14) {
  stageKey = "old";
} else if (ageMonths >= 12) {
  stageKey = "adult";
} else {
  stageKey = "puppy";
}

  if (state.isSick) {

  currentDogImage = dogImages[state.id].sick;

  } else if (
  state.stage === 2 &&
  state.dogAction === "ote"
  ) {
  currentDogImage = dogImages[state.id].puppyOte;
  } else if (
  state.stage === 2 &&
  state.dogAction === "osuwari"
  ) {
  currentDogImage = dogImages[state.id].puppyOsuwari;
  } else {currentDogImage = dogImages[state.id][stageKey];
  }

  // 描画
  ctx.clearRect(0,0,canvas.width,canvas.height);

//年齢で背景変更
const bg =
  getDogAgeYears(state.adoptedDate) >= 1
    ? bgPark
    : bgStage2;

ctx.drawImage(bg,0,0,canvas.width,canvas.height);

  drawAdoptedDate(state.adoptedDate);
  drawStage2Dog(state.id,state);
  drawDogName(state.name);
  drawDogAge(state.adoptedDate);
  drawStage2Items();
  drawCareBars(state);
  drawDogMood(state);
  drawProgressBar(30, 110, state.power, 10, "体力");
  drawProgressBar(30, 150, state.health, 10, "体調");

  if (!state.pendingMessage) {

  drawBottomFrame(
    "子犬期の案内",
    [
      "食事と水をしっかり与えてね🐾",
      "具合が悪そうだったらクスリをあげてね🐾",
      "散歩も定期的に連れてってあげてね、それと遊びとスキンシップが大切だよ🐶🐾"
    ]
  );

}

if (state.pendingMessage) {

  showDogMessage(state.pendingMessage);

  state.pendingMessage = null;

  localStorage.setItem("shibacchi_save",JSON.stringify(state));
}
  startRandomDogMessages(state);
}

let dogMessageTimer = null;

// 待機フラグ
let waitingForFood = false;
let waitingForSnack = false;
let waitingForWater = false;
let waitingForMedicine = false;
let waitingForWalk = false;

// タイマーID
let foodTimeoutId = null;
let snackTimeoutId = null;
let waterTimeoutId = null;
let medicineTimeoutId = null;
let walkTimeoutId = null;

function startRandomDogMessages(state) {

  if (dogMessageTimer) clearInterval(dogMessageTimer);

  dogMessageTimer = setInterval(() => {

    const state =
    JSON.parse(localStorage.getItem("shibacchi_save"));

  //ゲーム終了なら何もしない
  if (state.gameEnd) {

  clearInterval(dogMessageTimer);
  dogMessageTimer = null;

  return;
}
    // 日付が変わったら回数リセット
    const today = new Date().toLocaleDateString("ja-JP");

  state.dailyCare ??= {
      date: today,
      food: 0,
      snack: 0,
      water: 0,
      walk: 0
    };

  if (state.dailyCare.date !== today) {
  state.dailyCare = {
    date: today,
    food: 0,
    snack: 0,
    water: 0,
    walk: 0
  };

  localStorage.setItem("shibacchi_save", JSON.stringify(state));
}
    // 病気中はクスリ要求固定
    if (state.isSick) {
      redrawStage2(state);
      showDogMessage(`${state.name} はクスリを欲しがっています💊\nすぐに飲ませてください🐶`);
      return;
    }

    // 今日まだ表示できるイベントを作成
    const events = [];

    if (state.dailyCare.food < 2)
      events.push("food");

    if (state.dailyCare.snack < 3)
      events.push("snack");

    if (state.dailyCare.water < 3)
      events.push("water");

    if (state.dailyCare.walk < 3)
      events.push("walk");

    //全て終了なら今日のおねだり終了
    if (events.length === 0) {

    if (!state.todayCareFinished) {

    state.todayCareFinished = true;

    localStorage.setItem("shibacchi_save",JSON.stringify(state));

    showDogMessage(
      `${state.name} は今日のおねだりを終えました🐶\nまた明日よろしくね`
    );
  }
  return;
}
  //ランダムに処理
  const event =
    events[Math.floor(Math.random() * events.length)];

    // 案内を消す
    const guide = document.getElementById("stage2Guide");
    if (guide) guide.style.display = "none";

    redrawStage2(state);

    switch (event) {

// ごはん
case "food":

    waitingForFood = true;

    //今日の表示回数をカウント
    state.dailyCare.food++;
    localStorage.setItem("shibacchi_save", JSON.stringify(state));

    showDogMessage(`${state.name} はごはんを食べたいみたい🐶`);

    foodTimeoutId = setTimeout(() => {

    if (waitingForFood) {

        //最新のセーブデータを取得
        const save = JSON.parse(localStorage.getItem("shibacchi_save"));

        save.careBar.food = Math.max(0, save.careBar.food - 1);
        save.power = Math.max(0, save.power - 1);

        waitingForFood = false;

        localStorage.setItem("shibacchi_save", JSON.stringify(save));
        redrawStage2(save);
      }
    }, 15000);
    break;
// おやつ
case "snack":

    waitingForSnack = true;

    state.dailyCare.snack++;
    localStorage.setItem("shibacchi_save", JSON.stringify(state));

    showDogMessage(`${state.name} はおやつを食べたそうにしています🍪`);

    snackTimeoutId = setTimeout(() => {

    if (waitingForSnack) {

        //最新のセーブデータを取得
        const save = JSON.parse(localStorage.getItem("shibacchi_save"));

        save.careBar.snack = Math.max(0, save.careBar.snack - 1);

        waitingForSnack = false;

        localStorage.setItem("shibacchi_save", JSON.stringify(save));
        redrawStage2(save);
    }

}, 15000);

    break;

// 水
case "water":

    waitingForWater = true;

    state.dailyCare.water++;
    localStorage.setItem("shibacchi_save", JSON.stringify(state));

    showDogMessage(`${state.name} は水を飲みたいみたい🐶`);

    waterTimeoutId = setTimeout(() => {

    if (waitingForWater) {

        //最新のセーブデータを取得
        const save = JSON.parse(localStorage.getItem("shibacchi_save"));

        save.careBar.water = Math.max(0, save.careBar.water - 1);
        save.health = Math.max(0, save.health - 1);

        waitingForWater = false;

        localStorage.setItem("shibacchi_save", JSON.stringify(save));
        redrawStage2(save);
    }

}, 15000);

    break;

// クスリ
case "medicine":

    waitingForMedicine = true;
    state.isSick = true;

    //クスリバーを0にする
    state.careBar.medicine = 0;

    // 病気画像へ切替
    currentDogImage = dogImages[state.id].sick;

    localStorage.setItem("shibacchi_save", JSON.stringify(state));

    redrawStage2(state);
    showDogMessage(`${state.name} は具合が悪そう…💊`);

    medicineTimeoutId = setTimeout(() => {

    if (waitingForMedicine) {

        //最新のセーブデータを取得
        const save = JSON.parse(localStorage.getItem("shibacchi_save"));

        //クスリバー減少
        save.careBar.medicine =
            Math.max(0, save.careBar.medicine - 1);

        //体調減少
        save.health =
            Math.max(0, save.health - 1);

        waitingForMedicine = false;

        localStorage.setItem("shibacchi_save", JSON.stringify(save));
        redrawStage2(save);
    }

}, 15000);

    break;
      // 散歩
      case "walk":

    waitingForWalk = true;

    state.dailyCare.walk++;
    localStorage.setItem("shibacchi_save", JSON.stringify(state));

    showDogMessage(`${state.name} は散歩に行きたいみたい🚶‍♂️`);

    walkTimeoutId = setTimeout(() => {

    if (waitingForWalk) {

      //最新のセーブデータを取得
        const save = JSON.parse(localStorage.getItem("shibacchi_save"));

        save.careBar.walk = Math.max(0, save.careBar.walk - 1);
        save.power = Math.max(0, save.power - 1);

        waitingForWalk = false;

        localStorage.setItem("shibacchi_save", JSON.stringify(save));
        redrawStage2(save);
    }

    }, 15000);

    break;
    }

  }, 16000);
}

canvas.addEventListener("click", (e) => {

  const pos = getPointerPosition(e);
  const mx = pos.x;
  const my = pos.y;

  const startX = 680;
  const w = 110;
  const h = 50;

  const startY = 26;
  const gap = 60;

  // ごはん
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY && my <= startY + h
  ) {
    giveFood();
    return;
  }
  // おやつ
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap &&
    my <= startY + gap + h
  ) {
    giveSnack();
    return;
  }
  // 水
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap * 2 &&
    my <= startY + gap * 2 + h
  ) {
    giveWater();
    return;
  }
  // クスリ
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap * 3 &&
    my <= startY + gap * 3 + h
  ) {
    giveMedicine();
    return;
  }
  // 散歩
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap * 4 &&
    my <= startY + gap * 4 + h
  ) {
    giveWalk();
    return;
  }
  // お手
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap * 5 &&
    my <= startY + gap * 5 + h
  ) {
    giveOte();
    return;
  }
  // お座り
  if (
    mx >= startX && mx <= startX + w &&
    my >= startY + gap * 6 &&
    my <= startY + gap * 6 + h
  ) {
    giveOsuwari();
    return;
  }

});

function giveFood() {
  const save = JSON.parse(localStorage.getItem("shibacchi_save"));

  //おねだり中以外は無効
  if (!waitingForFood) {
    return;
  }

  waitingForFood = false;
  
  if (foodTimeoutId) {
    clearTimeout(foodTimeoutId);
    foodTimeoutId = null;
  }

  save.power = Math.min(10, save.power + 1);

   //ごはんバー回復
  save.careBar.food = 2;

  showDogMessage(`${save.name} はごはんを食べました🍚`);

  localStorage.setItem("shibacchi_save", JSON.stringify(save));
  redrawStage2(save);
}

function giveWater() {

  const save = JSON.parse(localStorage.getItem("shibacchi_save"));

  //おねだり中以外は無効
  if (!waitingForWater) {
    return;
  }

  waitingForWater = false;

  if (waterTimeoutId) {
    clearTimeout(waterTimeoutId);
    waterTimeoutId = null;
  }

  //体調回復
  save.health = Math.min(10, save.health + 1);

  //水バー回復
  save.careBar.water = 3;

  showDogMessage(
    `${save.name} は水を飲みました💧`
  );

  localStorage.setItem(
    "shibacchi_save",
    JSON.stringify(save)
  );

  redrawStage2(save);
}

function giveSnack() {
  
  const save = JSON.parse(localStorage.getItem("shibacchi_save"));

  //おねだり中以外は無効
  if (!waitingForSnack) {
    return;
  }

  waitingForSnack = false;

  if (snackTimeoutId) {
    clearTimeout(snackTimeoutId);
    snackTimeoutId = null;
  }

  //おやつバー回復
  save.careBar.snack = 3;

  showDogMessage(`${save.name} はおやつを食べました🍪`);

  localStorage.setItem("shibacchi_save", JSON.stringify(save));
  redrawStage2(save);
}

function giveMedicine() {

  const save = JSON.parse(localStorage.getItem("shibacchi_save"));
  // 病気ではない時は使用不可
  if (!save.isSick) {
    return;
  }
  // クスリがない場合
  if (save.careBar.medicine <= 0) {

    showDogMessage(
      "クスリがありません💊"
    );

    return;
  }

  waitingForMedicine = false;

  if (medicineTimeoutId) {
    clearTimeout(medicineTimeoutId);
    medicineTimeoutId = null;
  }

  //病気解除
  save.isSick = false;
  save.healthStatus = "normal";

  //体調回復
  save.health = Math.min(10, save.health + 1);

  //クスリ消費
  save.careBar.medicine =
    Math.max(0, save.careBar.medicine - 1);

  //ステージ画像へ戻す
  const ageMonths = getDogAgeMonths(save.adoptedDate);

  let stageKey;

  if (ageMonths >= 14) {
    stageKey = "old";
  } else if (ageMonths >= 12) {
    stageKey = "adult";
  } else {
    stageKey = "puppy";
  }

  currentDogImage =
      dogImages[save.id][stageKey];

  showDogMessage(
    `${save.name} はクスリを飲みました💊`
  );

  localStorage.setItem(
    "shibacchi_save",
    JSON.stringify(save)
  );

  redrawStage2(save);
}

function giveWalk() {
  const save = JSON.parse(localStorage.getItem("shibacchi_save"));
  //おねだり中以外は無効
  if (!waitingForWalk) {
    return;
  }

  waitingForWalk = false;
  if (walkTimeoutId) {
    clearTimeout(walkTimeoutId);
    walkTimeoutId = null;
  }

  showDogMessage(`${save.name} は散歩に行ってきました🚶‍♂️`);

 save.careBar.walk = 3;

  localStorage.setItem("shibacchi_save", JSON.stringify(save));
  redrawStage2(save);
}

function giveOte(){

  const save =
  JSON.parse(localStorage.getItem("shibacchi_save"));

  if(save.dogAction === "ote"){

   save.dogAction = "normal";

  }else{

   save.dogAction = "ote";

  }

  localStorage.setItem("shibacchi_save",JSON.stringify(save));

  redrawStage2(save);

}

function giveOsuwari(){

 const save =
 JSON.parse(
 localStorage.getItem("shibacchi_save")
 );

 if(save.dogAction === "osuwari"){

   save.dogAction = "normal";

 } else {

   save.dogAction = "osuwari";

 }

 localStorage.setItem("shibacchi_save",JSON.stringify(save));
 redrawStage2(save);

}

function checkDailyPenalty(state) {
  // ごはん
  if (state.careBar.food === 0) {
    state.power = Math.max(0, state.power - 1);
  }
  // おみず
  if (state.careBar.water === 0) {
    state.health = Math.max(0, state.health - 1);
  }
  // さんぽ
  if (state.careBar.walk === 0) {
    state.power = Math.max(0, state.power - 0.5);
  }
  // おやつ
  if (state.careBar.snack === 0) {
    state.health = Math.max(0, state.health - 0.2);
  }
  // 体調0なら病気になる
  if (state.health <= 0) {

    state.health = 0;

    state.isSick = true;

    state.healthStatus = "sick";


  //後で表示する
    state.pendingMessage =
      `${state.name} は少し具合が悪そうです🐶`;
  }

  if (
    getDogAgeYears(state.adoptedDate) >= 1 &&
    state.stage === 2
  ) {

    state.stage = 3;

  if (!state.pendingMessage) {
    state.pendingMessage =
      `${state.name} は立派な成犬になりました🐶✨`;
  }
}
  // 翌日用にバーを満タンへ
  state.careBar.food = 2;
  state.careBar.snack = 3;
  state.careBar.water = 3;
  state.careBar.walk = 3;
  state.careBar.medicine = 1;

  localStorage.setItem("shibacchi_save", JSON.stringify(state));
}

function checkNaturalSick(state) {

  // すでに病気なら不要
  if (state.isSick) {
    return;
  }

  // 年齢による病気率

  let sickRate = 0.005; // 子犬 0.5%

  if (state.stage === 3) {
    sickRate = 0.002;   // 成犬 0.2%
  }

  if (state.stage >= 4) {
    sickRate = 0.02;    // 老犬 2%
  }

  // 体調が低い場合は少し上昇
  if (state.health <= 3) {
    sickRate += 0.03;
  }

  // ランダム判定
  if (Math.random() < sickRate) {

  state.isSick = true;
  state.healthStatus = "sick";

  state.health = Math.max(
    0,
    state.health - 3
  );

  state.pendingMessage =
    `${state.name} は少し具合が悪そうです🐶`;
  }
}
// アイテム
function drawItemBox(label, x, y) {
  ctx.save();

  ctx.shadowColor = "transparent";
  ctx.shadowBlur = 0;
  ctx.shadowOffsetX = 0;
  ctx.shadowOffsetY = 0;

  // 枠線だけ
  ctx.strokeStyle = "#0d942e";
  ctx.lineWidth = 1;
  ctx.roundRect(x, y, 110, 50, 12);
  ctx.stroke();

  // 絵文字+テキスト
  ctx.font = "bold 18px sans-serif";
  ctx.fillStyle = "#423908";   // 通常色
  ctx.font = "bold 18px sans-serif";
  ctx.fillText(label, x + 15, y + 32);

  ctx.restore();
}

function drawStage2Items() {

  const startX = 680;
  let startY = 26;

  const gap = 60;

  drawItemBox("🍚 ごはん", startX, startY);
  startY += gap;

  drawItemBox("🍪 おやつ", startX, startY);
  startY += gap;

  drawItemBox("💧 水", startX, startY);
  startY += gap;

  drawItemBox("💊 クスリ", startX, startY);
  startY += gap;

  drawItemBox("🚶‍♂️ 散歩", startX, startY);
  startY += gap;

  drawItemBox("🐾 お手", startX, startY);
  startY += gap;

  drawItemBox("🐾 お座り", startX, startY);
}

function drawCareBars(state) {

const y1 = 120;   // 1行目
const y2 = 160;   // 2行目
const y3 = 200;   // 3行目

drawOneCareBar("🍚 ごはん", state.careBar.food, 2, 250, y1);
drawOneCareBar("💧 おみず", state.careBar.water, 3, 460, y1);
drawOneCareBar("🍪 おやつ", state.careBar.snack, 3, 250, y2);
drawOneCareBar("🚶 さんぽ", state.careBar.walk, 3, 460, y2);
drawOneCareBar("💊 薬残量", state.careBar.medicine, 1, 2, y3);
}

function drawOneCareBar(title, value, max, x, y) {

  ctx.font = "16px sans-serif";

if (title === "💊 薬残量") {
  ctx.shadowColor = 'rgba(8, 8, 8, 0.5)';
  ctx.shadowOffsetX = 6;
  ctx.shadowOffsetY = 6;
  ctx.shadowBlur = 4;

  // 図形の描画
  ctx.fillStyle = '#d9ff00';

} else {
  ctx.fillStyle = "#220433";
}

  ctx.fillText(title, x, y);

  const barX = x + 80;
  const barY = y - 14;

  const barWidth = 80;
  const barHeight = 14;

  // 枠
  /*ctx.strokeStyle = "#acacac";
  ctx.strokeRect(barX, barY, barWidth, barHeight);*/

  // 塗り
  ctx.fillStyle = "#3cb371";
  ctx.fillRect(
    barX,
    barY,
    barWidth * (value / max),
    barHeight
  );

  // 数値
  ctx.fillStyle = "#5c52ec";
  ctx.fillText(`${value}/${max}`, barX + 90, y);
}

// ステージ2選択された画像表示
const DOG_X = 220;
const DOG_Y = 220;
const DOG_W = 260;
const DOG_H = 260;
let currentDogImageType = "normal";

function drawStage2Dog(dogId, state) {
  const dog = document.getElementById("dogAnim");

  if (dog.src !== currentDogImage.src) {
    dog.src = currentDogImage.src;
  }

  dog.style.left = DOG_X + "px";
  dog.style.top = DOG_Y + "px";
  dog.style.width = DOG_W + "px";
  dog.style.height = DOG_H + "px";
  dog.style.objectFit = "contain";
  dog.style.display = "block";
  
}

function hideSelectUI() {
  document.getElementById('nameInputArea').style.display = 'none';
  document.getElementById('changeSelectBtn').style.display = 'none';
}

// 月齢表示
function calcDogAgeText(adoptedDateStr) {

  const start = new Date(adoptedDateStr);
  const today = new Date();

  start.setHours(0,0,0,0);
  today.setHours(0,0,0,0);

  let years = today.getFullYear() - start.getFullYear();
  let months = today.getMonth() - start.getMonth();
  let days = today.getDate() - start.getDate();

  if (days < 0) {
    months--;

    const prevMonth =
      new Date(today.getFullYear(), today.getMonth(), 0);

    days += prevMonth.getDate();
  }

  if (months < 0) {
    years--;
    months += 12;
  }

  // 初期2か月を加算
  months += 2;

  if (months >= 12) {
    years += Math.floor(months / 12);
    months = months % 12;
  }

  if (years === 0) {
    return `${months}か月と${days}日`;
  }

  return `${years}${months}か月と${days}日`;
}

function getDogAgeYears(adoptedDateStr) {

  const start = new Date(adoptedDateStr);
  const today = new Date();

  start.setHours(0,0,0,0);
  today.setHours(0,0,0,0);

  let years = today.getFullYear() - start.getFullYear();
  let months = today.getMonth() - start.getMonth();
  let days = today.getDate() - start.getDate();

  if (days < 0) {
    months--;
    days += new Date(
      today.getFullYear(),
      today.getMonth(),
      0
    ).getDate();
  }

  if (months < 0) {
    years--;
    months += 12;
  }

  // 初期2か月を加算
  months += 2;

  if (months >= 12) {
    years += Math.floor(months / 12);
  }

  return years;
}
//終了判定
function isGameEnd(state) {

  const ageMonths =
    getDogAgeMonths(state.adoptedDate);

  return ageMonths >= 15;
}

// 終了ボタン
document.getElementById("goodbyeBtn").onclick = () => {

  localStorage.removeItem("shibacchi_save");

  window.close();

};

// 再開処理
document.getElementById("restartBtn").onclick = () => {

  localStorage.removeItem("shibacchi_save");

  location.reload();

};

//年齢判定
function drawAdultScreen(data) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#333";
  ctx.font = "28px sans-serif";
  ctx.fillText("ステージ3(成犬画面)※まだ未実装", 200, 300);
}

function drawOldScreen(data) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#333";
  ctx.font = "28px sans-serif";
  ctx.fillText("ステージ4(老犬画面)※まだ未実装", 200, 300);
}

function drawDeadScreen(data) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#333";
  ctx.font = "28px sans-serif";
  ctx.fillText("ステージ5(寿命)※まだ未実装", 200, 300);
}

const MSG_W = 440;  //メッセージの幅をここで決める

function positionDogMessageBox() {
  const box = document.getElementById("dogMessageBox");

  box.style.width = MSG_W + "px";

  const x = DOG_X - (MSG_W - 620);  //幅に合わせて中央に配置
  const y = DOG_Y + DOG_H + 50;

  box.style.left = x + "px";
  box.style.top  = y + "px";
}

window.addEventListener("resize", positionDogMessageBox);
CSS
body {
  margin: 0;
  background-image: url("img/bg_park.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;

  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
}

html {
  touch-action: manipulation;
}

canvas {
  background: transparent;
  border: 4px solid #12c43f;
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.6);
  border-radius: 12px;
}

canvas {
  touch-action: manipulation;
}

button {
  touch-action: manipulation;
  font-size: 16px;
  height: 40px;
}

input {
  font-size: 16px;
  padding: 10px;
}

#startTitle {
  color: #fff;
  font-size: 32px;
  text-align: center;
  margin-bottom: 20px;
  text-shadow: 0 0 8px rgba(0,0,0,0.6);
}

#startMessage {
  color: #ccc;
  font-size: 20px;
  text-align: center;
  margin-bottom: 30px;
}

#uiContainer {
  position: absolute;
  top: 60px;
  width: 100%;
  text-align: center;
  pointer-events: none;
}

#uiContainer button,
#uiContainer input {
  pointer-events: auto;
}

#nameInputArea {
  margin-top: 20px;
  text-align: center;
}

#dogName {
  width: 200px;
  border-radius: 6px;
  border: 2px solid #12c43f;
  background-color: #ffffff;
  color: #690ee0;

}

#startGameBtn {
  width: 200px;
  border-radius: 6px;
  border: 2px solid #12c43f;
  background-color: #222;
  color: #fff;

}

#changeSelectBtn {
  margin-top: 20px;
  background-color: #692ef3;
  color: #fff;
  border: 2px solid #12c43f;
  border-radius: 6px;
}

#gameContainer {
  position: fixed;
  left: 50%;
  top: 48%;
  width: 800px;
  height: 600px;
  transform: translate(-50%, -50%);
}

#startTitle {
  position: absolute;
  top: -42px;
  left: 0;
  width: 100%;
  text-align: center;
  font-size: 32px;
  font-weight: bold;
  color: #fff;
  text-shadow: 2px 2px 4px #000;
  pointer-events: none;
}

#gameCanvas {
  display: block;
}

#topUI {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  width: 800px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  pointer-events: auto;
  z-index: 30;
}

#gameArea {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 800px;
  height: 600px;
  transform: translate(-50%, -50%);
}

#copyright{
  color: #fff;
  font-size: 12px;
  text-align: center;
  margin-bottom: 20px;
  text-shadow: 0 0 8px rgba(0,0,0,0.6);
}

#endingButtons {
  display:none;
  position:absolute;
  left:50%;
  top:430px;
  width:560px;
  transform:translateX(-50%);
  flex-direction:row;
  gap:20px;
  align-items:center;
  z-index:50;
}

.endingBtn {
  display: inline-block;
  padding: 0 20px;
  height:55px;
  border:none;
  border-radius:30px;
  font-size:18px;
  font-weight:bold;
  cursor:pointer;
  letter-spacing:1px;
  box-shadow:0 5px 10px rgba(0,0,0,0.25);
  transition:transform 0.2s, box-shadow 0.2s;
}

/* ホバー */
.endingBtn:hover {

  transform:translateY(-5px);
  box-shadow:
  0 10px 18px rgba(0,0,0,0.3);

}
/* お別れ */
.goodbye {

  background:#fff0f5;

  color:#8b3a62;

  border:2px solid #e6a7bf;

}
/* 再スタート */
.restart {

  background:#fff8dc;

  color:#8a5a00;

  border:2px solid #e7c86e;

}

以上です。

画像に付いてですが、全ての画像が圧縮ファイル(ing.zip)に入っていますが、こちらの画像を使用してWEBで公開した場合はクレジット表記が必要です。コード開発用として試験的に使用される場合は除きます。

圧縮ファイルの内容(柴犬画像・背景画像)

  • imgフォルダー (a01.png a01_osuwari.png a01_ote.png a02.png a03.png b_end_shiba.png bg_park.png bg_room.png dog_park.png k01.png k01_osuwari.png k01_ote.png k02.png k03.png r_end_shiba.png s01.png s01_osuwari.png s01_ote.png s02.png s03.png shiba15.png shiba16.png shiba17.png start_k.png start_s.png w_end_shiba.png)
  • ファイルの階層(配置)

shiba
 |
 |------index.html
 |------script.js
 |------style.css
 |--img
   |
   |--a01.png
     ・
     ・
     ・

柴犬画像のダウンロード