JavaScriptでスロットマシンをつくります。
HTML部分
最初にHTML部分を示します。スロットの絵柄の部分はcanvasに描画します。スロットを回転させたり停止させるボタンは絶対配置とします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>鳩でもわかるスロット</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel = "stylesheet" href = "./style.css" type = "text/css" media = "all"> </head> <body> <div id = "field"> <canvas id = "canvas"></canvas> <button id = "start" class = "button">スタート</button> <button id = "stop0" class = "button">ストップ</button> <button id = "stop1" class = "button">ストップ</button> <button id = "stop2" class = "button">ストップ</button> </div> <div id = "money"></div> <input type="number" min="1" id = "bet"> <script src= "./index.js"></script> </body> </html> |
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
body { background-color: #000; color: #fff; } #field { position: relative; height: 280px; } .button { display: block; position: absolute; } #start { width: 160px; height: 60px; top: 200px; left: 100px; } #stop0, #stop1, #stop2 { width: 100px; height: 40px; top: 120px; } #stop0 { left: 10px; } #stop1 { left: 130px; } #stop2 { left: 250px; } .red { color: #f00; font-weight: bold; } |
グローバル変数と定数
グローバル変数と定数を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// canvasのサイズ const CANVAS_WIDTH = 360; const CANVAS_HEIGHT = 240; // 各列の絵柄を表示させる部分のサイズ const COLUMN_SIZE = 100; // 画像ファイルのサイズ(正方形のものを使う) const SOURCE_IMAGE_SIZE = 120; const IMAGE_COUNT = 7; // 使用する画像の数 const MAX_SPEED = 0.4; // 回転の最大速度 const MIN_SPEED = 0.05; // 回転の最小速度 // canvas要素とコンテキスト const $canvas = document.getElementById('canvas'); const ctx = $canvas.getContext('2d'); // ボタン要素とコンテキスト const $start = document.getElementById('start'); const $stop0 = document.getElementById('stop0'); const $stop1 = document.getElementById('stop1'); const $stop2 = document.getElementById('stop2'); // ここに所持金が表示される const $money = document.getElementById('money'); // 賭け金を指定するためのテキストボックス const $bet = document.getElementById('bet'); const images = []; let positions = [6, 6, 6]; // 何番目の絵柄が表示されるか?(とりうる値は0以上、IMAGE_COUNT未満の小数) let speeds = [0, 0, 0]; // 各列の現在の回転速度 let stopings = [false, false, false]; // 各列は停止しようとしているか? // 効果音 const startSound = new Audio('./sounds/start.mp3'); const loseSound = new Audio('./sounds/lose.mp3'); const winSound = new Audio('./sounds/win.mp3'); const gameoverSound = new Audio('./sounds/gameover.mp3'); // 各列が停止したときの効果音 // 停止するタイミングが重なってもすべて再生されるようにオブジェクトは3つ作る const stopedSounds = [ new Audio('./sounds/stoped.mp3'), new Audio('./sounds/stoped.mp3'), new Audio('./sounds/stoped.mp3') ]; let monney = 100; // 所持金 |
ページが読み込まれたときの処理
ページが読み込まれたときにおこなわれる処理を示します。
canvasサイズを調整して背景を黒で塗りつぶします。そのあとイベントリスナを追加して画像ファイルを読み込みと更新処理、金額を表示する処理をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
window.onload = () => { $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); addEventListeners(); // 後述 initImages(); // 後述 update(); // 後述 $bet.min = 1; // 賭け金の下限は 1 とする $bet.value = 1; showMoney(); // 後述 } |
イベントリスナを追加する処理を示します。スタートボタンをクリックしたらスロットが回転し、ストップをクリックしたら停止させます。賭け金を設定する部分に所持金以上の値や設定できる値より小さい値を入力したときは自動的に値が修正されるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function addEventListeners(){ $start.addEventListener('click', () => moveStart()); $stop0.addEventListener('click', () => moveStop(0)); $stop1.addEventListener('click', () => moveStop(1)); $stop2.addEventListener('click', () => moveStop(2)); $bet.addEventListener('change', () => { if(Number($bet.value) > monney) // 所持金以上の値を入力したとき $bet.value = monney; if(Number($bet.value) < $bet.min) // 設定できる値より小さい値を入力したとき $bet.value = $bet.min; }); } |
画像ファイルを読み込む処理を示します。
1 2 3 4 5 6 7 8 9 10 |
function initImages(){ for(let i = 0; i < IMAGE_COUNT; i++){ const image = new Image(); image.src = `./images/${i}.png`; images.push(image); } const image = new Image(); image.src = `./images/${0}.png`; images.push(image); } |
所持金を表示と賭け金を設定するテキストボックスの設定をする処理を示します。
1 2 3 4 5 6 7 |
function showMoney(){ $money.innerText = `残金 ${monney}`; $bet.max = monney; // 賭け金の上限は所持金と同額とする if($bet.value > monney) // 所持金が表示されている値を下回ったら表示を所持金と同額に変更する $bet.value = monney; } |
更新処理
更新処理を示します。
canvasの背景を黒で塗りつぶし、positions[i]の値を回転速度分だけ増加させます。そしてIMAGE_COUNTの剰余をとります。そのためここには0以上IMAGE_COUNT未満の値が格納されます。そのあとshowImage関数を呼び出して絵柄を描画します。
ストップボタンがクリックされるとstopings[i]がtrueになるので、この場合はspeeds[i]の値を減少させます。ただし0より大きくMIN_SPEEDより小さい値にはならないようにします。回転速度がMIN_SPEEDのときはMath.floor(positions[i])との差がMIN_SPEED以下のときは停止させます。そしてpositions[i] には Math.floor(positions[i])を格納します。
停止したら効果音を鳴らし、3つとも停止した場合はonStoped関数を呼び出して数字がそろっているか調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function update(){ ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); for(let i = 0; i < 3; i++){ positions[i] += speeds[i]; positions[i] %= IMAGE_COUNT; showImage(i, positions[i]); // 後述 if(stopings[i] && speeds[i] != 0){ // ストップボタンがクリックされた場合 speeds[i] -= 0.01; if(speeds[i] < MIN_SPEED){ speeds[i] = MIN_SPEED; if(Math.abs(positions[i] - Math.floor(positions[i])) <= MIN_SPEED){ // この場合は完全停止させる speeds[i] = 0; positions[i] = Math.floor(positions[i]); // 停止したら効果音、3つとも停止したらonStoped関数を呼び出す stopedSounds[i].play(); if(speeds[0] == 0 && speeds[1] == 0 && speeds[2] == 0) onStoped(); // 後述 } } } } requestAnimationFrame(update); } |
絵柄を描画する処理を示します。
まず絵柄をcanvasのどの部分に描画するのかを取得する処理を示します。
1 2 3 4 5 6 7 |
function getColumnX(num){ return num * 120 + 10; // 10, 130, 250 } function getColumnY(num){ return 10; } |
showImage関数は左からcolNum番目の列の絵柄をcanvasに描画します。このとき第二引数が整数ではない場合は画像ファイルの下側の一部と次の画像の上側の一部を切り取って絵柄を描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function showImage(colNum, value){ const colX = getColumnX(colNum); const colY = getColumnY(colNum); // 絵柄の背景部分は白 ctx.fillStyle = '#fff'; ctx.fillRect(colX, colY, COLUMN_SIZE, COLUMN_SIZE); const index = Math.floor(value); const pos1 = value - index; // valueの小数部分 const pos2 = 1 - pos1; ctx.drawImage( images[index], 0, (SOURCE_IMAGE_SIZE * pos1), SOURCE_IMAGE_SIZE, SOURCE_IMAGE_SIZE * pos2, // どの部分を切り取るか? colX + 5, colY + 5, COLUMN_SIZE - 10, COLUMN_SIZE * pos2 - 10); // 描画する座標と大きさ ctx.drawImage( images[index + 1], 0, 0, SOURCE_IMAGE_SIZE, SOURCE_IMAGE_SIZE * pos1, // 次の画像のどの部分を切り取るか? colX + 5, colY + 5 + COLUMN_SIZE * pos2, COLUMN_SIZE - 10, COLUMN_SIZE * pos1 - 10); // 画像の境界線に黒い線を描画する ctx.lineWidth = 4; ctx.strokeStyle = '#000'; ctx.beginPath(); ctx.moveTo(colX, colY + COLUMN_SIZE * pos2); ctx.lineTo(colX + COLUMN_SIZE, colY + COLUMN_SIZE * pos2); ctx.stroke(); } |
スロットが停止したときの処理
スロットが停止したときにおこなわれる処理を示します。
数字がそろっているか調べます。そろっている場合は所持金を増やし、そうでない場合は減らします。所持金が0になったらゲームオーバーです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function onStoped(){ // 停止音と同時に判定音が鳴ると不自然なので0.5秒あける setTimeout(() => { if(positions[0] == positions[1] && positions[0] == positions[2]){ monney += $bet.value * 50; showMoney(); winSound.play(); } else { monney -= $bet.value; showMoney(); loseSound.play(); if(monney <= 0){ setTimeout(() => { $money.innerHTML = `<span class = "red">あなたは破産しました</span>`; gameoverSound.play(); }, 1000); } } if(monney > 0) $start.style.display = 'block'; // 非表示にしていたスタートボタンを再表示させる }, 500); } |
スロットの回転の開始と停止の処理
スロットの回転を開始する処理と停止する処理を示します。
回転を開始するときはstopingフラグをfalseにして回転速度をMAX_SPEEDにします。そしてスタートボタンを非表示にします。停止させるときは該当する部分のstopingフラグをtrueにします。すると回転速度が減速し停止します。
1 2 3 4 5 6 7 8 9 10 11 |
function moveStart(){ $start.style.display = 'none'; stopings = [false, false, false]; speeds = [MAX_SPEED, MAX_SPEED, MAX_SPEED]; startSound.play(); // 効果音 } function moveStop(num){ stopings[num] = true; } |