ゲームを作って暇つぶし

暇つぶしにやったこと

ジャンケンゲーム Ver.1.0

※記事のタイトルをクリックして、個別でページを開かないと、正しく動作しません。

次のバージョン

organize.hatenablog.jp

クラス図

index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/janken.svg" />
    <link rel="stylesheet" href="./src/style.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>janken</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/Janken.ts"></script>
  </body>
</html>
style.css
h1{
  text-align: center;
}

.explanation{
  padding: 2rem;
  text-align: center;
}

.hand-buttons{
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1rem;
  margin-bottom: 1rem;
}

.hand-button{
  width: 5em;
  height: 5em;
  margin-left: 1em;
  margin-right: 1em;
}

#result{
  text-align: center;
  margin-top: 2rem;
  margin-bottom: 2rem;
}
Janken.ts
import { HandButton } from "./HandButton";
import { GameResult, HandKind, HandButtonsDefine, ResultName } from "./types.d";

/**
 * ジャンケンゲームのメインクラス
 */
class Janken {
    /** グーチョキパーボタン */
    private handButtons: HandButton[] = [];
    /** 結果表示エリア */
    private resultElement: HTMLElement;

    /**
     * コンストラクタ
     * @param perent ジャンケンゲームの親要素
     */
    constructor(perent: HTMLElement){
        // タイトル・説明・ボタンの格納場所を用意
        perent.innerHTML = `
        <h1>ジャンケンゲーム</h1>
        <div class='explanation'>
            出したい手のボタンを押すと、じゃんけんの結果が表示されます。
        </div>
        <div class='hand-buttons'></div>
        `;

        // グーチョキパーボタンの格納場所を取得
        const buttons = document.querySelector('.hand-buttons') as HTMLElement;

        // グーチョキパーボタンを作成
        Object.values(HandButtonsDefine).forEach(value => {
            // ボタン作成
            const handButton = new HandButton(value);
            // クリックイベントを追加
            handButton.getElement().addEventListener('click', () => {
                // 相手の手を取得
                const computerHand = this.getRandomHand();
                // 勝敗を取得
                const result = this.getResult(handButton.getKind(), computerHand);
                // 結果を表示
                this.resultElement.innerHTML = `
                    <div>あなた: ${value.name}</div>
                    <div>相手: ${HandButtonsDefine[computerHand].name}</div>
                    <div>結果: ${ResultName[result]}</div>
                `;
            });

            // 格納場所に追加
            buttons.appendChild(handButton.getElement());

            // ボタンを保持
            this.handButtons.push(handButton);
        })

        // 結果格納場所を作成
        this.resultElement = document.createElement('div');
        // idを設定
        this.resultElement.id = "result";
        // 結果格納場所を追加
        perent.appendChild(this.resultElement);
    }

    /**
     * 手をランダムに取得
     * @returns グー or チョキ or パー
     */
    private getRandomHand(): HandKind {
        const hands = [HandKind.Rock, HandKind.Scissors, HandKind.Paper];
        const randomIndex = Math.floor(Math.random() * hands.length);
        return hands[randomIndex];
    }

    /**
     * 自分と相手の手から勝敗を判定
     * @param playerHand 自分の手 
     * @param computerHand 相手の手
     * @returns 勝敗
     */
    private getResult(playerHand: HandKind, computerHand: HandKind): GameResult {
        if (playerHand === computerHand) {
            return GameResult.Draw;
        } else if (
            (playerHand === HandKind.Rock && computerHand === HandKind.Scissors) ||
            (playerHand === HandKind.Scissors && computerHand === HandKind.Paper) ||
            (playerHand === HandKind.Paper && computerHand === HandKind.Rock)
        ) {
            return GameResult.Win;
        } else {
            return GameResult.Lose;
        }
    }
}

// app要素を取得
const appElement = document.querySelector('#app') as HTMLElement;
// ジャンケンゲーム作成
new Janken(appElement);
HandButton.ts
import type { HandButtonOption, HandKind } from "./types";

/**
 * グーチョキパーボタン
 */
export class HandButton{
    /** 種別 */
    private kind: HandKind;
    /** 要素 */
    private element: HTMLElement;

    /**
     * コンストラクタ
     * @param buttonDefine ボタン定義
     */
    constructor(buttonDefine: HandButtonOption){
        // ボタン要素を作成
        this.element = document.createElement('button');
        // idを設定
        this.element.id = buttonDefine.id;
        // classを設定
        this.element.className = "hand-button";
        // 表示名を設定
        this.element.textContent = buttonDefine.name;
        
        // 種別を設定
        this.kind = buttonDefine.id;
    }

    /** 種別取得 */
    getKind(): HandKind {
        return this.kind;
    }

    /** 要素取得 */
    getElement(): HTMLElement {
        return this.element;
    }

}
types.d.ts
/** グーチョキパーボタンのオプション */
export interface HandButtonOption {
    id: HandKind;
    name: string;
}

/** グーチョキーパー種別 */
export enum HandKind {
    Rock = "rock",
    Scissors = "scissors",
    Paper = "paper"
}

/** グーチョキパーボタン定義 */
export const HandButtonsDefine: Record<HandKind, HandButtonOption> = {
    [HandKind.Rock]: {id:HandKind.Rock, name:"グー"},
    [HandKind.Scissors]: {id:HandKind.Scissors, name:"チョキ"},
    [HandKind.Paper]: {id:HandKind.Paper, name:"パー"},
} as const

/** 勝敗種別 */
export enum GameResult {
    Win = "win",
    Lose = "lose",
    Draw = "draw"
}

/** 勝敗の表示名 */
export const ResultName: Record<GameResult, string> = {
    [GameResult.Win]: "勝ち",
    [GameResult.Lose]: "負け",
    [GameResult.Draw]: "あいこ",
} as const

覚書

Reactを触っていた際、Typescriptのこともよくわかってないのに、Reactを使うなんて早すぎるのではないかと思い、手始めにTypescriptで簡単なゲームを作ってみることにしました。

ViteでBuildしたJavascriptcssを記事にコピペしたら、ちゃんと動いてくれました。

最初にプロジェクトを作成したときのサンプルソースが、innerHTMLにタグを書いていたので、同じようにタイトルとか説明を書きました。

ですが、ボタンはなんとなしにクラス化して、そこでcreateElementをして要素を作っています。

一般的にはどうなんでしょうか?どういう風に作るのが主流なんでしょうか?

そもそもゲームだったらCanvasで作るだろうし、クラス化するならタイトルや説明、結果表示もクラスにした方が良い気がします。

まあ、そこらへんはAIさんに聞いて、修正していこうと思います。

じゃんけんゲーム