絵文字探し わたしをみつけて

絵文字探し わたしをみつけて | ひまあそび-ミニゲーム hi0a.com

view source

JavaScript

document.title = '絵文字探し わたしをみつけて';

var canvas = document.createElement('canvas');
var w = document.body.clientWidth;
var h = document.body.clientHeight;
var min = Math.min(w,h);

canvas.setAttribute('width',w);
canvas.setAttribute('height',h);
let demo = document.getElementById('demo');
demo.appendChild(canvas);

var ctx = canvas.getContext('2d');

var buttonContainer = document.createElement('div');
buttonContainer.id = 'buttonContainer';
demo.appendChild(buttonContainer);

document.body.style.overflow = 'hidden';

const emojiStr = "😀 😃 😄 😁 😆 😅 🤣 😂 🙂 🙃 😉 🫠 😊 😇 🥰 😍 🤩 😘 😗 😚 😙 🥲 😋 😛 😜 🤪 😝 🤑 🤗 🤭 🫢 🫣 🤫 🤔 🫡 🤐 🤨 😐 😑 😶 🫥 😏 😒 🙄 😬 😮‍💨 🤥 🫨 😌 😔 😪 🤤 😴 😷 🤒 🤕 🤢 🤮 🤧 🥵 🥶 🥴 😵 😵‍💫 🤯 🤠 🥳 🥸 😎 🤓 🧐 😕 🫤 😟 🙁 ☹ 😮 😯 😲 😳 🥺 🥹 😦 😧 😨 😰 😥 😢 😭 😱 😖 😣 😞 😓 😩 😫 🥱 😤 😡 😠";

// 不要なゼロ幅文字や空白を除去
const emojis = emojiStr
  .split(" ")
  .map(e => e.trim())
  .filter(e => e.length > 0 && !/\u200d/.test(e)); // ゼロ幅結合子を含むもの除外


const margin = min / 10;
const emojiSize = min / 12;

let sampleEmojiChar;
const emojiObjs = [];

let showSampleHint = true;
setTimeout(() => showSampleHint = false, 5000);


class Emoji {
  constructor(char, x, y) {
    this.char = char;
    this.originX = x;
    this.originY = y;
    this.x = x;
    this.y = y;
    this.speedX = (Math.random() - 0.5) * 1.5;
    this.speedY = (Math.random() - 0.5) * 1.5;
    this.size = emojiSize;
    this.expanding = false;
    this.expansionSize = 0;
  }

  move() {
    if (!this.expanding) {
      this.x += this.speedX;
      this.y += this.speedY;

      const dx = this.x - this.originX;
      const dy = this.y - this.originY;
      const limit = this.size / 2;

      if (Math.abs(dx) > limit) {
        this.speedX *= -1;
        this.x = this.originX + Math.sign(dx) * limit;  // はみ出さないよう補正
      }

      if (Math.abs(dy) > limit) {
        this.speedY *= -1;
        this.y = this.originY + Math.sign(dy) * limit;  // はみ出さないよう補正
      }
    }
  }


  draw(ctx) {
    ctx.font = `${this.expanding ? this.expansionSize : this.size}px serif`;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText(this.char, this.x, this.y);
  }

  isClicked(mx, my) {
    const dx = this.x - mx;
    const dy = this.y - my;
    return Math.sqrt(dx * dx + dy * dy) < this.size;
  }
}

let stageCleared = false;
let showClearMessage = false;
let expandingEmoji = null;


function shuffleArray(array) {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

function drawSampleEmoji() {
  const centerX = margin;
  const centerY = margin;

  ctx.font = `${emojiSize * 2}px serif`;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(sampleEmojiChar, centerX, centerY);

  // 赤い太線の円で囲む(絵文字の中心に合わせる)
  ctx.beginPath();
  ctx.strokeStyle = "red";
  ctx.lineWidth = 8;
  ctx.arc(centerX, centerY, emojiSize + 10, 0, Math.PI * 2);
  ctx.stroke();

  // ヒント文字(5秒で消える)
  if (showSampleHint) {
    ctx.font = "bold 32px sans-serif";
    ctx.fillStyle = "red";
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    ctx.fillText("← わたしをみつけて", centerX + emojiSize + 20, centerY);
  }
}



function drawClearMessage() {
  if (showClearMessage) {
    ctx.font = "bold 60px sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillStyle = "#222";
    ctx.fillText("🎉 クリア!", w / 2, h / 2);
  }
}

function generateNonOverlappingPositions(count, radius) {
  const positions = [];
  const safeZone = { x: 0, y: 0, width: 200, height: 200 };
  const maxTries = 10000;
  const minX = radius + margin;
  const maxX = w - radius - margin;
  const minY = radius + margin;
  const maxY = h - radius - margin;

  // 最初にサンプル用の座標を必ず追加
  const sampleX = minX + Math.random() * (maxX - minX);
  const sampleY = minY + Math.random() * (maxY - minY);
  positions.push({ x: sampleX, y: sampleY });

  let tries = 0;
  while (positions.length < count && tries < maxTries) {
    const x = minX + Math.random() * (maxX - minX);
    const y = minY + Math.random() * (maxY - minY);

    // 安全ゾーン回避
    if (x > safeZone.x && x < safeZone.x + safeZone.width &&
        y > safeZone.y && y < safeZone.y + safeZone.height) {
      tries++;
      continue;
    }

    // 他との重なり回避
    let overlaps = false;
    for (const pos of positions) {
      const dx = pos.x - x;
      const dy = pos.y - y;
      if (Math.sqrt(dx * dx + dy * dy) < radius * 1.1) {
        overlaps = true;
        break;
      }
    }

    if (!overlaps) positions.push({ x, y });
    tries++;
  }

  return positions;
}



function resetStage() {
  const displayedEmojiCount = 50;

  // 1. 表示対象の絵文字をシャッフルして選定
  const selectedEmojis = [...emojis].sort(() => Math.random() - 0.5).slice(0, displayedEmojiCount);

  // 2. サンプルはその中からランダムに選ぶ
  sampleEmojiChar = selectedEmojis[Math.floor(Math.random() * selectedEmojis.length)];

  // 3. 状態リセット
  stageCleared = false;
  showClearMessage = false;
  expandingEmoji = null;
  showSampleHint = true;

  // 4. 5秒後にヒントを消す
  setTimeout(() => showSampleHint = false, 5000);

  // 5. 新しい配置座標を生成
  const positions = generateNonOverlappingPositions(displayedEmojiCount, emojiSize);

  // 6. 絵文字オブジェクトを再構築
  emojiObjs.length = 0;
  for (let i = 0; i < positions.length; i++) {
    emojiObjs.push(new Emoji(selectedEmojis[i], positions[i].x, positions[i].y));
  }
}


setInterval(() => {
  shuffleArray(emojiObjs);
}, 3000);

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


  // 1. 通常の絵文字を描画(expanding中のものは除外)
  emojiObjs.forEach(e => {
    if (e !== expandingEmoji) {
      e.move();
      e.draw(ctx);
    }
  });

  // 2. 拡大中の絵文字は最後に描画 → 最前面になる
  if (expandingEmoji) {
    expandingEmoji.move();
    expandingEmoji.draw(ctx);

    // 拡大は常に続ける(上限を設定したいなら別途設定)
    const scaleSpeed = 0.1; // 速度係数
    expandingEmoji.expansionSize += expandingEmoji.expansionSize * scaleSpeed;

    // ステージクリアは特定サイズに達したとき1回だけ実行
    if (!stageCleared && expandingEmoji.expansionSize >= Math.max(w, h) * 3 / 4) {
      showClearMessage = true;
      stageCleared = true;
      setTimeout(resetStage, 1500);
    }
  }


  drawSampleEmoji();
  drawClearMessage();

  requestAnimationFrame(animate);
}

resetStage();
animate();

canvas.addEventListener('click', e => {
  const rect = canvas.getBoundingClientRect();
  const mx = e.clientX - rect.left;
  const my = e.clientY - rect.top;

  emojiObjs.forEach(emoji => {
    if (emoji.isClicked(mx, my)) {
      if (emoji.char === sampleEmojiChar && !stageCleared && !expandingEmoji) {
        emoji.expanding = true;
        emoji.expansionSize = emoji.size;
        expandingEmoji = emoji;
      }
    }
  });
});

CSS

HTML

ページのソースを表示 : Ctrl+U , DevTools : F12

view-source:https://hi0a.com/game/emoji-find-me/

ABOUT

hi0a.com 「ひまあそび」は無料で遊べるミニゲームや便利ツールを公開しています。

プログラミング言語の動作デモやWEBデザイン、ソースコード、フロントエンド等の開発者のための技術を公開しています。

必要な機能の関数をコピペ利用したり勉強に活用できます。

プログラムの動作サンプル結果は画面上部に出力表示されています。

環境:最新のブラウザ GoogleChrome / Windows / Android / iPhone 等の端末で動作確認しています。

画像素材や音素材は半分自作でフリー素材配布サイトも利用しています。LINK参照。

仕様変更、API廃止等、実験途中で動かないページもあります。