/*---------------------------------------- * flash-anzan.js * Copyright (C) 2022 StraightApps.com, All Rights Reserved * April 22, 2022 初期版 *-----------------------------------------*/ //---------------------------------------- // グローバル変数/オブジェクト var gameLevel = 1; // ゲームレベル var gameCount = 5; // 1ゲームで表示する数字数 var gameSpeed = 2; // 表示速度 var num = 0; // 0 はゲーム開始前、1 以上は表示済みの数字数 var numNow = 0; // 現在表示中の数字 var gameAnswer = 0; // 正解 var userAnswer = 0; // 入力した回答 var numHistory = []; // 要素数未定の配列 var gameRetry = 0; // リトライ中? var startTime = 0; // 現在の数字の表示開始カウント var endTime = 0; var sepTime = 0; // セパレート終了カウント var drawNumber = 0; // 数字を描画するタイミングかどうか var sepDuration = 200; // 数字が消えているのは 200ms //---------------------------------------- // canvas の設定 // この部分は、id "canvas-number" の定義後でないと、失敗します。 var canvas = document.getElementById('canvas-number'); var w = document.documentElement.clientWidth; var h = 48; if (w > 240){ // 十分広い場合は w = 240; // 240 ピクセルまでとします } canvas.width = w; // canvas の幅設定 canvas.height = w; // canvas の高さ設定 //---------------------------------------- // コンテキストを取得 var ctx = canvas.getContext('2d'); //---------------------------------------- // 数値を右詰め表示するときの x 位置を決めます。 ctx.font = "36px 'ヒラギノ角ゴ Pro', 'Hiragino Kaku Gothic Pro', 'MS ゴシック', 'MS Gothic', sans-serif"; //---------------------------------------- // 新しい問題を作成します。 //makeNewNumber(); //---------------------------------------- // レベル・コンボボックスを作成します。 setLevelCombo(); //---------------------------------------- // 表示個数コンボボックスを作成します。 setCountCombo(); //---------------------------------------- // 表示速度コンボボックスを作成します。 setSpeedCombo(); //---------------------------------------- // 初期状態で利用できないボタンをグレーにします。 setButtonState(0); /*---------------------------------------- * メインループ *-----------------------------------------*/ function main() { //---------------------------------------- // canvas を指定の色で塗りつぶします。 ctx.fillStyle = "rgb( 230, 230, 230 )"; ctx.fillRect(0, 0, canvas.width, canvas.height); //---------------------------------------- // デバッグ用文字列を出力します。 //var elem = document.getElementById('debug'); //elem.innerHTML= data; //---------------------------------------- // ゲーム中は、数字を canvas に描画します。 if (num > 0){ // 現在の時刻(ミリ秒単位)を取得します。 var now = new Date(); var curTime = (now.getTime() % 10000); // 9秒 999までの値 var data = curTime + ' 秒とミリ秒'; // canvas の背景色を、数字表示ごとに切り替えます。 // これに該当しない場合は、rgb(230,230,230) でクリアされています。 if ((num % 2) == 0){ ctx.fillStyle = "rgb( 200, 200, 200 )"; ctx.fillRect(0, 0, canvas.width, canvas.height); } // 新しい数字を設定後、最初の 100ms は描画しないこととします。 if (startTime < sepTime){ // 例えば 1000 開始で 1100 終わりのとき //elem.innerHTML= curTime + ' 時点 ' + startTime + ' から ' + sepTime + ' と ' + endTime + ' まで '; if (curTime > sepTime){ // 経過した場合 drawNumber = 1; } } else{ // 例えば 9950 開始で 50 終わりのとき //elem.innerHTML= curTime + ' 時点 ' + startTime + ' から ' + sepTime + ' と ' + endTime + ' まで '; if (curTime > sepTime && curTime < startTime){ drawNumber = 1; } } // 数字を描画します。 if (drawNumber > 0){ // フォントを決定します。 var fontSize = Math.floor(canvas.width * 4 / 5); ctx.font = fontSize + "px 'ヒラギノ角ゴ Pro', 'Hiragino Kaku Gothic Pro', 'MS ゴシック', 'MS Gothic', sans-serif"; //ctx.font = "36px 'ヒラギノ角ゴ Pro', 'Hiragino Kaku Gothic Pro', 'MS ゴシック', 'MS Gothic', sans-serif"; // 加算は黒で、減算は赤+枠付きで描画します。 if (numNow >= 0){ var x = (canvas.width - ctx.measureText( numNow ).width) / 2; // 中央あわせ var y = canvas.height - (canvas.height - fontSize ) / 2; ctx.fillStyle = '#000'; // 黒字 ctx.fillText( numNow, x, y ); // canvas 領域の座標(y座標は'下'を指している) } else{ var x = (canvas.width - ctx.measureText( -numNow ).width) / 2; // 中央あわせ var y = canvas.height - (canvas.height - fontSize ) / 2; ctx.fillStyle = '#000'; // 黒 ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1); //ctx.fillRect(0, canvas.height - canvas.height * 0.15, canvas.width, canvas.height * 0.15); ctx.fillStyle = '#d00'; // 赤字 ctx.fillText( -numNow, x, y ); // 符号は表示しません } // 時間になったら、新しい数字を関数で作成するか、 // あるいは最後まで表示したら終わり。 // 数字と数字の間は一瞬消すべき? 背景色をトグルさせるべき? // 連続で同じ数字を否定はしないが、わからなくならないようにすること。 var nextNumber = 0; if (startTime < endTime){ // 例えば 1000 開始で 1800 終わりのとき //elem.innerHTML= '順接 ' + curTime + ' 時点 ' + startTime + ' から ' + sepTime + ' と ' + endTime + ' まで '; if (curTime > endTime){ // 経過した場合 nextNumber = 1; } } else{ // 例えば 9500 開始で 300 終わりのとき //elem.innerHTML= '逆接 ' + curTime + ' 時点 ' + startTime + ' から ' + sepTime + ' と ' + endTime + ' まで '; if (curTime > endTime && curTime < startTime){ nextNumber = 1; } } if (nextNumber > 0){ // 必要な数だけ表示済み? if (num >= gameCount){ // ゲームを止めます。 num = 0; /* var data = '正解は ' + gameAnswer + '
'; // 経過を for ( var i = 0; i < numHistory.length; i++ ){ if (i > 0 ){ data += ' + '; } data += numHistory[i]; } var elem = document.getElementById('debug'); elem.innerHTML= data; */ // 回答をクリアして、ボタンを利用可能にします。 numReset(); setButtonState(1); } // 続きがある場合 else{ num ++; // リトライ中の場合 if (gameRetry == 1){ numNow = numHistory[num - 1]; } // リトライ中ではない場合 else{ // 新しい数字を設定 // レベルによって、プラスマイナスなど // 最終的な正解が 0 になることは避ける!! //numNow = Math.floor(Math.random() * 9) + 1; // 関数で!! numNow = getNextNumber(); gameAnswer += numNow; // 入力履歴配列を記録します。 numHistory.length ++; numHistory[numHistory.length - 1] = numNow; } // 次の数字の表示時間を設定します。 setTimers(curTime); //elem.innerHTML= curTime + ' 時点 ' + startTime + ' から ' + sepTime + ' と ' + endTime + ' まで '; } } } } requestAnimationFrame( main ); } /*---------------------------------------- * ページと、依存している全てのデータが読み込まれたら、 * メインループを開始します。 *-----------------------------------------*/ addEventListener('load', main(), false); /*---------------------------------------- * ボタンの状態を設定します。 * btnState が 0 のとき、利用不可にします。 * btnState が 1 のとき、利用可能にします。 *-----------------------------------------*/ function setButtonState(btnState) { var btnDisabled = true; if (btnState > 0){ btnDisabled = false; } // 「やりなおす」ボタンの状態を設定します。 document.getElementById("btnRetry").disabled = btnDisabled; // 回答入力用の数字ボタンの状態を設定します。 for (var i = 0; i < 10; i++ ){ document.getElementById("btnNum" + i).disabled = btnDisabled; } // リセットボタンの状態を設定します。 document.getElementById("btnNumReset").disabled = btnDisabled; // ギブアップボタンの状態を設定します。 document.getElementById("btnGiveUp").disabled = btnDisabled; } /*---------------------------------------- * 「スタート」ボタンで新しいゲームを開始します。 *-----------------------------------------*/ function gameStart() { // num = 0 のときはゲーム開始前で、 // ゲーム開始後は、表示済みの数字数をカウントしています。 num = 1; // ゲームの正解をクリアします。 gameAnswer = 0; // 最初に表示する数字を決定します。 numNow = getNextNumber(); // 入力履歴配列を初期化します。 numHistory.length = 1; numHistory[0] = numNow; gameAnswer = numNow; // リトライ中ではありません。 gameRetry = 0; // ゲーム中であることを示すメッセージにします。 //var elem = document.getElementById("answer"); document.getElementById("answer").innerHTML = '数字の表示が終わったら、回答を入力できます。'; document.getElementById("history").innerHTML = '「ギブアップ」ボタンを押すか正解すると、ここに式が表示されます。'; // 数字の表示時間を設定します。 setTimers(-1); } /*---------------------------------------- * 「やりなおす」ボタンで前回のゲームを繰り返します。 *-----------------------------------------*/ function gameRestart() { // num = 0 のときはゲーム開始前で、 // ゲーム開始後は、表示済みの数字数をカウントしています。 num = 1; // 前回の履歴をたどります。 numNow = numHistory[num - 1]; //gameAnswer = numNow; // リトライ中です。 gameRetry = 1; // ゲーム中であることを示すメッセージにします。 //var elem = document.getElementById("answer"); document.getElementById("answer").innerHTML = '数字の表示が終わったら、回答を入力できます。'; document.getElementById("history").innerHTML = '「ギブアップ」ボタンを押すか正解すると、ここに式が表示されます。'; // 数字の表示時間を設定します。 setTimers(-1); } /*---------------------------------------- * 数字の表示時間を設定します。 *-----------------------------------------*/ function setTimers(curTime) { if (curTime < 0){ var now = new Date(); curTime = (now.getTime() % 10000); // 9秒 999までの値 } startTime = curTime; endTime = ((startTime + getInterval()) % 10000); sepTime = (startTime + sepDuration) % 10000; // 描画タイミングではありません。 drawNumber = 0; } /*---------------------------------------- * 数字の表示間隔(ms)を返します。 *-----------------------------------------*/ function getInterval() { switch (gameSpeed){ case '0': return 1500 + sepDuration; case '1': return 1000 + sepDuration; case '2': return 800 + sepDuration; case '3': return 600 + sepDuration; case '4': return 400 + sepDuration; } return 800 + sepDuration; } /*---------------------------------------- * 次に表示する数字を決定します。 * Level 1: 加算のみ * Level 2: 減算あり、途中でマイナスなし(加算が多めになる) * Level 3: 減算あり、途中でマイナスあり * ※ 解答はマイナスなし *-----------------------------------------*/ function getNextNumber() { // 1〜9 の乱数を発生させます。 var num = Math.floor(Math.random() * 9) + 1; // Level 1: 加算のみ if (gameLevel == 1){ return num; } // Level 2: 減算あり、途中でマイナスなし if (gameLevel == 2){ // 25% の確率でマイナス値にします。 if (Math.random() < 0.25){ num = -num; // 符号反転 // これにより解答が(一時的に)マイナスになるなら、正に戻します。 if (gameAnswer + num < 0){ num = -num; // 符号反転(戻し) } } return num; } // Level 3: 減算あり、途中でマイナスあり if (gameLevel == 3){ // 40% の確率でマイナス値にします。 if (Math.random() < 0.4){ num = -num; // 符号反転 // これにより -9 より小さくなると、最終解答を正に戻せない可能性があるので禁止します。 if (gameAnswer + num < -9){ num = -num; // 符号反転(戻し) } // 最終回答がマイナスになるなら、プラスになるように調整します。 if (num == gameCount){ // 最終数字を作成中 while (gameAnswer + num < 0){ // 最悪でも 0 までは戻せます num = Math.floor(Math.random() * 9) + 1; } } } } return num; } /*---------------------------------------- * 「ギブアップ」ボタンで解答を表示します。 *-----------------------------------------*/ function gameGiveUp() { var data = '

正解は ' + gameAnswer + ' です。
'; // 経過を表示します。 for ( var i = 0; i < numHistory.length; i++ ){ if (i > 0 ){ if (numHistory[i] < 0){ data += ' - '; } else{ data += ' + '; } } data += Math.abs(numHistory[i]); } var elem = document.getElementById('history'); elem.innerHTML= data + '

'; } /*---------------------------------------- * レベル・コンボボックスの作成 *-----------------------------------------*/ function setLevelCombo() { var str = ''; var elem = document.getElementById("gameLevel"); elem.innerHTML = str; gameLevel = 1; } /*---------------------------------------- * レベル・コンボボックスの値が変更されたとき *-----------------------------------------*/ function changeLevel(selValue) { gameLevel = selValue; // レベルを変更した場合、「やりなおす」ボタンは使えなくなります。 document.getElementById("btnRetry").disabled = true; } /*---------------------------------------- * 表示個数コンボボックスの作成 *-----------------------------------------*/ function setCountCombo() { var str = ''; var elem = document.getElementById("gameCount"); elem.innerHTML = str; // 選択中の値を記録 gameCount = 7; // "selected"指定のため } /*---------------------------------------- * 表示個数コンボボックスの値が変更されたとき *-----------------------------------------*/ function changeCount(selValue) { gameCount = selValue; // 表示個数を変更した場合、「やりなおす」ボタンは使えなくなります。 document.getElementById("btnRetry").disabled = true; } /*---------------------------------------- * 表示速度コンボボックスの作成 *-----------------------------------------*/ function setSpeedCombo() { var str = ''; var elem = document.getElementById("gameSpeed"); elem.innerHTML = str; // 選択中の値を記録 gameSpeed = 2; // "selected"指定のため } /*---------------------------------------- * 表示速度コンボボックスの値が変更されたとき *-----------------------------------------*/ function changeSpeed(selValue) { gameSpeed = selValue; } /*---------------------------------------- * 数字ボタンが押されたときの処理 * value は、押されたボタンの値 *-----------------------------------------*/ function numPressed(value) { if (userAnswer == 0){ userAnswer = value; } else{ userAnswer *= 10; userAnswer += value; } // 正解になったかどうかは自動的に判定します。 if (userAnswer == gameAnswer){ var elem = document.getElementById("answer"); elem.innerHTML = '

あなたの回答  ' + userAnswer + ' で、正解です!

'; // 「ギブアップ」ボタンと同様に、式も表示します。 gameGiveUp(); } // まだ正解ではない場合 else{ var elem = document.getElementById("answer"); elem.innerHTML = 'あなたの回答は ' + userAnswer + ' です。'; } } /*---------------------------------------- * 回答リセットボタンが押されたときの処理 *-----------------------------------------*/ function numReset() { userAnswer = 0; var elem = document.getElementById("answer"); elem.innerHTML = '回答を入力してください。'; }