Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

しりとりで遊べるChrome拡張機能を作った

Posted at

この記事はSLP-KBIT AdventCalendar2024 20日目の記事です。

目次

1.はじめに
2.作成物の概要
3.使用したもの
4.コード紹介
5.今後の予定
6.おわりに
7.参考にしたサイト

はじめに

9日目の記事では、Chrome拡張機能の概要について、学んだことをざっくりとまとめました。
それを踏まえて、前回作ったものより凝った拡張機能を作りたいと思い、しりとりで遊べる拡張機能を作りました。
本記事では、それについて紹介します。

作成物の概要

今回作ったのはポケモンの名前でしりとりができるChrome拡張機能です。
普通のしりとりだと莫大な数の単語が使用できますが、それら全てに対応するのはほぼ不可能なので、有限でありながらしりとりができるくらい数が多いポケモンをテーマとしました。

実際の動作は以下の通りです。

  • 拡張機能アイコンをクリックすると、ポップアップウィンドウが表示されます
    スクリーンショット 2024-12-20 103754.png

  • 最初に表示されている名前の最後の文字から始まるポケモンを入力すると、コンピュータが返答します
    スクリーンショット 2024-12-20 103845.png
    スクリーンショット 2024-12-20 103849.png

  • 後述するルールに違反すると、エラーメッセージが出ます
    スクリーンショット 2024-12-19 102133.png

  • 勝敗が決まるとメッセージとリスタート用のボタンが表示され、リスタートすると最初から遊べます
    スクリーンショット 2024-12-19 222453.png

ルールは以下の通りです。

rule.txt
・入力できるのはポケモンの名前のみ
・カタカナで入力する
・相手が入力したポケモンの最後の文字から始まるポケモンを入力する
・名前が「ン」で終わるポケモンを入力する、もしくは入力可能なポケモンがいなくなると負け
・どちらかがすでに使ったポケモンは使えない
・最後の文字が小文字で終わるポケモンは、その文字の大文字を最後の文字とする
 例:チラーミィの最後の文字は「イ」として扱う
・最後の文字が伸ばし棒で終わるポケモンは、伸ばし棒の前の文字を最後の文字とする
 例:ミュウツーの最後の文字は「ツ」として扱う
・最後の文字が記号で終わるポケモンは未対応(入力すると絶対勝つ、入力されると絶対負ける(タブンネ))
 例:ニドラン♂、ニドラン♀、ポリゴンZ等
・名前の途中に記号が入るポケモンの記述方法は以下の通り
 タイプ:ヌルの「:」は半角 カプ系の「・」は全角
・入力できる名前は正式名称のみ、フォルムによる名称の違い等は無し
 例:ブラックキュレム、ホワイトキュレムは入力不可

使用したもの

ツール

  • Chrome
  • Visual Studio Code

言語

  • JavaScript
  • HTML
  • CSS

ファイル構成

  • manifest.json:拡張機能の基本設定
  • popup.js:機能を実装
  • popup.html:UIの構築
  • style.css:デザイン
  • words.json:ワードリスト
  • icon.png:アイコン画像

コード紹介

記述したコードを紹介します。

1.manifest.json

前回のものとほとんど変わりはないです。

manifest.json
{
    "name":"ポケモンしりとり",
    "version":"1.0",
    "manifest_version":3,
    "description":"ポケモンの名前でしりとりをするChrome拡張機能です",
    "icons":{
        "16": "icon_16x16.png",
        "48": "icon_48x48.png",
        "128": "icon_128x128.png"
    },
    "action":{
        "default_title":"ポケモンしりとり",
        "default_icon":"icon_48x48.png",
        "default_popup":"popup.html"
    }
}

2.popup.html

ボタンや入力フォーム等、ポップアップウィンドウの構造を定義しています。

popup.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="style.css">
    <title>ポケモンしりとり</title>
</head>
<body>
    <h1>ポケモンしりとり</h1>
    <div id="main">
        <ul id="word-list"></ul>
        <input type="text" id="user-input" placeholder="単語を入力してください">
        <button id="submit-button">送信</button>
        <p id="error-message" style="color: red;"></p>
        <p id="status-message"></p>
        <button id="restart-button" style="display: none;">リスタート</button>
    </div>
    <script src="popup.js"></script>
</body>
</html>

3.style.css

割愛

4.popup.js

しりとりのロジックを記述しています。

popup.js
document.addEventListener("DOMContentLoaded", () => {
  const wordList = document.getElementById("word-list");
  const userInput = document.getElementById("user-input");
  const submitButton = document.getElementById("submit-button");
  const restartButton = document.getElementById("restart-button");
  const errorMessage = document.getElementById("error-message");
  const statusMessage = document.getElementById("status-message");

  //ワードリスト
  let words = [];
  //使用された単語
  let usedWords = [];

  //小文字と大文字の対応
  const charMap = {
    : "", : "", : "", : "", : "",
    : "", : "", : "",
    : "", : ""
  };

  //ワードリストの読み込み
  fetch("words.json")
    .then(response => response.json())
    .then(data => {
      words = data;
      startGame();
    })
    .catch(error => {
      console.error("単語リストの読み込みに失敗しました:", error);
      errorMessage.textContent = "単語リストを読み込めませんでした。";
    });

  //ゲームリスタート用のボタン
  restartButton.addEventListener("click", restartGame);

  //入力ボタンを押した際の処理
  submitButton.addEventListener("click", () => {
    //プレーヤが入力した単語
    let playerWord = userInput.value;
    //プレーヤが入力した単語の最初の文字
    let playerFirst = playerWord.slice(0, 1);
    //プレーヤが入力した単語の最後の文字
    let playerLast = getLastChar(playerWord);
    userInput.value = ""; 
    errorMessage.textContent = "";

    //入力した単語が既出の場合、再入力を求める
    if (usedWords.indexOf(playerWord) !== -1) {
      errorMessage.textContent = "そのポケモンは既出です";
      return;
    }
    //ワードリストに無い言葉を入力した場合、再入力を求める
    if (words.indexOf(playerWord) === -1) {
      errorMessage.textContent = "ポケモン名を入力してください";
      return;
    }

    //1つ前のコンピュータの単語
    let computerWord = usedWords[usedWords.length - 1];
    //入力した単語の最初の文字がコンピュータの単語の最後の文字から始まっていない場合、再入力を求める
    if (playerFirst !== getLastChar(computerWord)) {
      errorMessage.textContent ="" + computerLast  + "」から始まるポケモンの名前を入力してください";
      return;
    }

    //プレーヤが使用したポケモンの名前を格納する
    usedWords.push(playerWord);
    //プレーヤが使用したポケモンの名前を単語リストに加える
    addWordToList(playerWord);

    //プレーヤの入力した単語の最後の文字が「ン」で終わっている場合、メッセージを表示してゲームの終了処理を行う
    if (playerLast === "") {
      endGame("最後に「ン」がついているので、あなたの負けです…");
      return;
    }

    //コンピュータの単語
    computerWord = getComputerWord(playerLast);
    //コンピュータの単語が無いとき、メッセージを表示してゲームの終了処理を行う
    if (!computerWord) {
      endGame("コンピュータが返答できなくなったので、あなたの勝ちです!");
      return;
    }

    //コンピュータが使用したポケモンの名前を格納する
    usedWords.push(computerWord);
    //コンピュータが使用したポケモンの名前を単語リストに加える
    addWordToList(computerWord);

    //コンピュータの単語に返答できない場合、メッセージを表示してゲームの終了処理を行う
    if (!validWord(getLastChar(computerWord))) {
      endGame("入力できるポケモンがいないため、あなたの負けです…");
      return;
    }
  });

  //ゲームの開始処理を行う
  function startGame() {
    //最後の文字が「ン」でない単語をまとめる
    const validWords = words.filter(word => getLastChar(word) !== "");
    const firstWord = validWords[Math.floor(Math.random() * validWords.length)];
    usedWords.push(firstWord);
    addWordToList(firstWord);
  }

  //単語の最後の文字を変換する
  function getLastChar(word) {
    let lastChar =  word.slice(-1);
    //単語が「ー」で終わる場合、最後から2番目の文字を最後の文字とする
    if (lastChar === "") {
      lastChar = word.slice(-2, -1);
    }
    //単語が小文字で終わる場合、小文字を大文字に変換する
    if (charMap[lastChar]) {
      lastChar = charMap[lastChar];
    }
    return lastChar;
  }

  //コンピュータの単語を生成する
  function getComputerWord(playerLast) {
    const validWords = validWord(playerLast);
    if (!validWords) {
      return null;
    }
    //条件を満たす単語があった場合、それらの中からランダムに1つ返す
    return validWords[Math.floor(Math.random() * validWords.length)];
  }

   //単語リストに単語を表示する
  function addWordToList(word) {
    const li = document.createElement("li");
    li.textContent = word;
    wordList.appendChild(li);
  }

  //有効な単語を探す
  function validWord(lastChar) {
    //以下の3つの条件を満たす単語を探す
    //・受け取った文字から始まる
    //・既出でない
    //・最後の文字が「ン」でない
    const validWords = words.filter(word =>
      word[0] === lastChar && !usedWords.includes(word) && getLastChar(word) !== ""
    );
    //無かった場合、nullを返す
    if (validWords.length === 0) {
      return null;
    }
    return validWords;
  }
  
  //ゲームの終了処理を行う
  function endGame(message) {
    statusMessage.textContent = message;
    userInput.disabled = true;
    submitButton.disabled = true;
    restartButton.style.display = "block"; //リスタートボタンを表示
  }

  //ゲームのリスタート処理を行う
  function restartGame() {
    usedWords = [];
    wordList.innerHTML = "";
    userInput.disabled = false;
    submitButton.disabled = false;
    errorMessage.textContent = "";
    statusMessage.textContent = "";
    restartButton.style.display = "none"; //リスタートボタンを隠す
    startGame();
  }
});

5.words.json

ポケモン計1025匹分の名前が記述されています。
以下はその一部です。

words.json
[
    "フシギダネ",
    "フシギソウ",
    "フシギバナ",
    "ヒトカゲ",
    "リザード",
    "リザードン",
    "ゼニガメ",
    "カメール",
    "カメックス",
    "キャタピー",
    "トランセル",
    "バタフリー",
    "ビードル",
    "コクーン",
]

今後の予定

以下の機能を実装してより遊びやすくしたいと考えています。

  • 最後の文字が記号で終わるポケモンに対する対応
    例:ポリゴンZだと最後の文字を「ト」とする
  • ルールを表示するボタンや降参ボタンの実装
  • 新たなルール追加
    例:「ホ」で終わる場合「ボ」や「ポ」から始まるポケモンも入力できるようにする

おわりに

今回はポケモンの名前でしりとりを行うChrome拡張機能について紹介しました。
JavaScriptは初めて触りましたが、自分が今まで使った言語と異なる点が多く難しかったです。
ぎりぎりでの制作・執筆でしたが、予定通りに投稿できてホッとしています。
最後まで読んでいただき、本当に、本当にありがとうございました。

参考にしたサイト

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?