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

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したJavascriptやcssを記事にコピペしたら、ちゃんと動いてくれました。
最初にプロジェクトを作成したときのサンプルソースが、innerHTMLにタグを書いていたので、同じようにタイトルとか説明を書きました。
ですが、ボタンはなんとなしにクラス化して、そこでcreateElementをして要素を作っています。
一般的にはどうなんでしょうか?どういう風に作るのが主流なんでしょうか?
そもそもゲームだったらCanvasで作るだろうし、クラス化するならタイトルや説明、結果表示もクラスにした方が良い気がします。
まあ、そこらへんはAIさんに聞いて、修正していこうと思います。
