こんにちは。
複数プレイヤーが参加可能なビンゴゲームサイトを作成しようとしている者です。
期限は特にないし、完全に趣味でつくるだけなので、気が向いたときに進めていく案件です。
ひとまず、少しずつ機能をつくっていくために、今回はビンゴカードを生成してくれる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, 2, 3, ..., 14, 15]の配列を作成
- 配列内の要素をシャッフル
- 配列の先頭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
までの整数をランダムに生成する記述らしいです。
-
Math.random()
で0以上1未満の小数を生成 - 小数に
(i + 1)
をかけて、0以上i + 1未満の小数へ変換 - 小数の小数点以下を切り捨て、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が完成しました。
いつの日かビンゴゲーム大会が開催できるよう、今後も少しずつ機能を実装していくことにしましょう。
ここまで、読んでいただきありがとうございました。