0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ビンゴカードを生成してくれるAPIをつくる

Posted at

こんにちは。

複数プレイヤーが参加可能なビンゴゲームサイトを作成しようとしている者です。

期限は特にないし、完全に趣味でつくるだけなので、気が向いたときに進めていく案件です。

ひとまず、少しずつ機能をつくっていくために、今回はビンゴカードを生成してくれるAPIを、Cloudflare Workers環境でつくりたいと思います。

最終的には、フロントエンドをVue.jsでつくって、Cloudflare Pagesにデプロイし、サーバーサイドをTypeScriptでつくって、Cloudflare Workersにデプロイしようとしています。

ベース

Hello Worldしてくれる状態からスタートします。

export default {
	async fetch(request, env, ctx): Promise<Response> {
		return new Response('Hello World!');
	},
} satisfies ExportedHandler<Env>;

APIの入り口

export default {
	async fetch(request, env, ctx): Promise<Response> {
		if (request.method === 'POST') {
			if (request.url.endsWith('/v1/cards')) {
				return new Response('Hello Bingo Player!');
			}
		}
		return new Response('Hello World!');
	}
} satisfies ExportedHandler<Env>;

POST POST http://127.0.0.1:8787/v1/cardsでアクセスすることにします。

POSTリクエストのため、Webブラウザの一般的なアクセスでは取得できないため、今後も見越して、VSCodeへ「REST Client」拡張機能をインストールします。

そして、request.httpというファイルを準備し、以下のような記述をします。

POST http://127.0.0.1:8787/v1/cards

これで、上記の記載の上に「Send Request」実行ボタンが現れるような気がするので、これをクリックすると、リクエストが送信されます。

ひとつのファイルに複数のリクエストを記載して、必要なときに必要なリクエストをひとつ実行する場合は、###行でリクエストを区切ります。

さて、これで以下のようなレスポンスを取得できました。
OKです。

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain;charset=UTF-8
Content-Encoding: gzip

Hello Bingo Player!

BingoCardクラス作成

コードがとっちらかるのを防ぐため、適宜クラスを導入していく方針とします。

さしあたって、ビンゴカードの生成およびビンゴの判定をしてくれる予定のBingoCardクラスを作成します。

class BingoCard {
	card: number[][];

	constructor() {
		this.card = this.generatedBingoCard();
	}

	private generatedBingoCard(): number[][] {
		return [];
	}

	getCard(): number[][] {
		return this.card;
	}
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		if (request.method === 'POST') {
			if (request.url.endsWith('/v1/cards')) {
				const card = new BingoCard();
				return new Response(
					JSON.stringify({ card: card.getCard() }),
					{ status: 200, headers: { 'Content-Type': 'application/json' } }
				);
			}
		}
		return new Response('Hello World!');
	}
} satisfies ExportedHandler<Env>;

これで、空のビンゴカード配列をレスポンスしてくれるようになりました。

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Content-Encoding: gzip

{
  "card": []
}

ビンゴカード生成

今回の要ともいえる、ビンゴカードの生成です。

今回下調べして知ったのですが、ビンゴカードの数字の配列は単純なランダムではなく、第一列は1~15、第二列は16~30、第三列は31~45までの数字から五つ選ばれるらしいです。(ただし、第三列は中心がフリーであるため、四つ)

(手作業で数字を探す上で、番号を見つけやすくするための措置なのかな?)

今回はこれに従います。

基本の第一列、1~15の数字の列は、以下の規則で作成します。

  1. [1, 2, 3, ..., 14, 15]の配列を作成
  2. 配列内の要素をシャッフル
  3. 配列の先頭5要素を取り出す

連番の配列を作成

const line: number[] = [...Array(15)].map((_, i) => i + 1);

こちらを参考にさせていただきました。

勉強項目

  • Array(15)

  • ...(Spread operator)

配列内の要素のシャッフル

private shuffleArray(array: number[]): number[] {
    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]];
    }
    return array;
}

基本的な戦略は、要素の交換です。

調べた限り、シャッフル手法としてはこの「フィッシャー–イェーツのシャッフル」Fisher–Yates shuffleと呼ばれるものの改良版が一般的らしいです。

配列の末尾の要素から着目し、末尾の要素を前の要素のいずれかと交換して(あるいは交換せずに)、末尾から確定していくもの――と理解しました。

Math.floor(Math.random() * (i + 1))は一見ややこしいですし、実際、未だに頭に入ってきていませんが、シンプルにいえば0~iまでの整数をランダムに生成する記述らしいです。

  1. Math.random()で0以上1未満の小数を生成
  2. 小数に(i + 1)をかけて、0以上i + 1未満の小数へ変換
  3. 小数の小数点以下を切り捨て、0~iまでの整数へ変換

(すんなり理解できずにいるのは、この内の二項目目です。わかりそうでわからない。たとえば0.9999をかけると、i+1はi+1より小さくなるんだなあ、とか思うんですけどね。でも、未だに納得できずにいる)

ひとまず、この戦略で、配列をシャッフルします。

配列の先頭5要素を取り出す

shuffledLine.slice(0, 5);

ここはふつうにスライスするだけです。

拡張

基本の処理を書いたら、これを第五列まで適用できるように一般化します。

1~15を16~30や31~45に置き換えるだけなので、話は単純で、最初の配列の生成に15をいい感じに加算するだけです。

for (let n = 0; n < 5; n++) {
    const line: number[] = [...Array(15)].map((_, i) => i + 1 + n * 15);
    const shuffledLine = this.shuffleArray(line);
    card.push(shuffledLine.slice(0, 5));
}

配列のシャッフルと5要素の取り出しは、手を加える必要がありません。

まとめ

クラス全体は以下のようになりました。

class BingoCard {
	card: number[][];

	constructor() {
		this.card = this.generatedBingoCard();
	}

	private generatedBingoCard(): number[][] {
		let card: number[][] = [];
		for (let n = 0; n < 5; n++) {
			const line: number[] = [...Array(15)].map((_, i) => i + 1 + n * 15);
			const shuffledLine = this.shuffleArray(line);
			card.push(shuffledLine.slice(0, 5));
		}
		return card;
	}

	private shuffleArray(array: number[]): number[] {
		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]];
		}
		return array;
	}

	getCard(): number[][] {
		return this.card;
	}
}

動作確認

これで、POST http://127.0.0.1:8787/v1/cardsリクエストで以下が取得できるようになりました。

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json
Content-Encoding: gzip

{
  "card": [
    [ 1, 13, 5, 2, 4 ],
    [ 17, 21, 20, 16, 27 ],
    [ 43, 44, 38, 33, 41 ],
    [ 47, 58, 55, 48, 56 ],
    [ 74, 66, 70, 63, 65 ]
  ]
}

一般的なビンゴカードとは行列が反対ですが、これは(忘れていなければ)フロントエンドでひっくり返しましょう。

終わりに

ビンゴカードを生成してくれるAPIが完成しました。

いつの日かビンゴゲーム大会が開催できるよう、今後も少しずつ機能を実装していくことにしましょう。

ここまで、読んでいただきありがとうございました。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?