※ 表示が正しくないと思う場合は、JavaScript を有効にしてください。
※ 入力欄が表示されないなどの場合は、キャッシュを削除をお試しください。
現在は、出題レベル 1 〜 3 に対応しています。
より難しい問題もできますが、今はここまでです。
問題文の下で、回答を入力、正解チェックができます。
※ 回答機能は検証途中となっています。
「解答」を見て、答え合わせすることもできます。
出題レベル
【No.】
青枠をタップすると、ボタンで数値を入力できます。
Let's play factorization!!
因数分解を楽しもう!
表示された数式を、因数分解してみよう! パズルを解く感覚で、楽しくテスト対策しよう。 すぐにわからなくても大丈夫。 「公式を見る」や 「ヒント1」、 「ヒント2」を参考にしてみよう。
「別の問題に挑戦」ボタンで、新しい数式に更新されます。
問題番号は「問題番号をリセット」ボタンで初期化できます。 なお、連続 10 問ごとにページがリロードされますので、ご了承ください。
素因数分解トレーニング
75 は、何で割り切れるかな?
素数ボタンを押して、出題された数字を素因数分解せよ!
現時点でのテスト済みブラウザは、
です。
スマホやタブレットで開く場合は、
https://www.straightapps.com/web/js-insuu-bunkai-1.html
)
投稿 May 21, 2021
ここから先は、上記「因数分解徹底トレーニング」開発を行った際に調べたりした JavaScript コードについての情報です。
コードに入る前に、ご参考までに、お知らせです。 私は普段は、Windows デスクトップアプリを C++/MFC で書いています。 HTML は手書き(テキストエディタ)で、JavaScript も基本は手書きです。 Visual Studio 2015 エディタを使って書く部分もありますが、タブとスペースの処理の関係で、あまり好んでいません。 ですので、自然に、Windows っぽくなっている部分が出ています。 また、C++ で Android アプリ作成にもチャレンジしているので、その比較もするかも知れません。
このプログラムを書き始めたときの JavaScript のレベルとしては、 言語仕様としては C/C++ と似ている部分が多いので、苦労しないレベルです。 関数や書き方については、ネットでコードを参照すれば、いくらか書ける程度です。 JavaScript で書いたコードは、1文字でも間違えていると全体が動作しなくなるようなのですが、 どこがおかしいか、タイプミスや文字不足、カッコの閉じ忘れや変数名の書き間違いなどに気付く能力は、 おそらく十分にあるようです。
前置きは以上として、まずは、HTML 部です。
JavaScript ソースは、すべて1ファイル insuu-bunkai-1.js に書いています。 <head> 〜 </head> タグ内でロードする初期化部分と、 実際の処理を行う実装部分に、あるいは今後作成する同様のプログラムのために共通関数を別ファイルにすべきかも知れません。 この JavaScript ソースは、 body タグ内でロードしています。
「出題レベル」を選択するところ、 問題番号、 問題式は、JavaScript で更新されます。 回答入力欄は canvas 要素で、これも JavaScript で制御しています。
初期状態では非表示で、回答入力しようとすると表示される数字ボタンも、 表示と非表示の切り替え、ボタン入力時の処理が JavaScript で書かれています。
「使う公式を見る」やヒント、解答は、 PC のブラウザではマウスオーバーしたときに表示されるポップアップになっていますが、 スマホのブラウザでは(マウスオーバーできないので)タップすると表示されます。 表示内容を、JavaScript で変更しています。
「別の問題に挑戦」ボタンも、 「問題番号をリセット」ボタンも、 JavaScript の関数を呼び出して、処理しています。
要素がたくさんありますが、いずれも
なお、「因数分解の公式」で、公式(画像)が展開されたりたたまれたりするのは css で実現していますので、JavaScript ではありません。
<p> 出題レベル <span id="levelCombo"></span> </p>
1 〜 3 を選択できる「出題レベル」は、HTML では出力テキストのない span 要素になっています。
JavaScript の setLevelCombo 関数で、
その内容を select 要素に置き換えて、
<p> 【No.<span id="consecutivePlay"></span>】 </p> <p id="question" style="font-size: 200%;"></p>
問題番号は consecutivePlay という名前を付けた span 要素で、数字だけ表示するようにします。 問題は p 要素で、css を行内に書いて、この部分だけ 200% のサイズで大きく表示されるようにしています。
※ 以下は作成途中のため、未更新です。
<div id="left-value">ここに素因数分解されるべき値が表示されます。</div> <div id="answering">ここに回答途中の式が表示されます。</div> <!-- button 要素部を省略(このあとすぐ) -->
「別の問題に挑戦」ボタンの下にある、計算途中のエリアは canvas に描画しています。 canvas とは、自由に絵を描ける領域のことです。
<!-- button 要素から続きます(このあとすぐ) --> <p> <canvas id="canvas-calc"></canvas> </p>
素因数(数字)選択ボタンや「別の問題に挑戦」ボタンは、button 要素 で、 JavaScript 関数を呼び出す形になっています。 これが、上2つのコードの間に挟まっています。
<p> <button onclick="numPressed(2)" title="2で割る"> 2 </button> <button onclick="numPressed(3)" title="3で割る"> 3 </button> <button onclick="numPressed(5)" title="5で割る"> 5 </button> <button onclick="numPressed(7)" title="7で割る"> 7 </button> <button onclick="numPressed(11)" title="11で割る"> 11 </button> <button onclick="numPressed(13)" title="13で割る"> 13 </button> </p> <p> <button onclick="makeNewNumber()">別の問題に挑戦</button> </p>
canvas の定義のあとで、soinsuu.js を読み込んでいます。
以降のセクションで、それぞれ詳細を確認していきます。
なお、ご参考までに、JavaScript ソースコードそのものを、拡張子 js を拡張子 txt に変えて、次のリンクより開けるようにしてあります。
soinsuu-init.txt
soinsuu.txt
※ 作成時はタブを4文字としていますので、環境によってはタブが8文字のため、
コメント等がずれて見えるかも知れません。
※ 念のため書き添えますが、このソースコードをこのまま転載・公開することはご遠慮ください。
なお、本サイトのご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
投稿 September 26, 2020
soinsuu-init.js は、 <head> 〜 </head> タグ内でロードしていますが、 この場所で読み込む場合は、ページ本体のデータ(body 部)が読み込まれる前に、ロードされるようです。
なので、この中で、例えばあとで定義されている div id="left-value" にアクセスしようとしても、アクセスできません。
var pow = new Object(); // 正解を保持するオブジェクト var numOrg = 1; // 素因数分解される元の値 var num = 1; // 残りの素因数分解される値 var ans = new Object(); // プレーヤーの回答を保持するオブジェクト var inp = []; // 要素数未定の配列
soinsuu-init.js にあるのは上記のものだけです。
C/C++ と違うのは、変数の型が緩いということです。
(古い)Visual Basic の Variant 型と同じように、整数でも実数でも文字列でも入ります。
今の Visual Basic には Variant 型はないようですので、比較はできませんが。
実際には、ちゃんと
ほぼすべての機能が実装されている soinsuu.js は、 body タグ内でロードされています。 これは、HTML の body を先頭から読み込んでいき、 そのコードに到達したときに読み込まれる、というものです。
<script type="text/javascript" src="js/soinsuu.js"></script>
ここでは、これより前で定義された div id="left-value" や canvas id="canvas-calc" にアクセスできます。
なお、JavaScript のコードは、head や body タグ内に直接記述することも可能です。 機能テストには有効かもしれませんが、ごちゃごちゃになるので、おすすめできる方法ではありません。 また、HTML タグの onClick などに直接コードを書くこともできるようですが、小規模でない限りは、混乱のもとになるかと思います。
<script type="text/javascript"> <!-- <!-- JavaScript コードを記述 --> // --> </script> <noscript> <!-- JavaScript が無効の場合の注意書きなどを記述 --> </noscript>
投稿 September 26, 2020
body が読み進まれていき、script の行まで到達すると、src で指定されたファイルが読み込まれます。
<script type="text/javascript" src="js/soinsuu.js"></script>
soinsuu.js は、 div id="left-value" や canvas id="canvas-calc" にアクセスしますので、 この script の行は、それらよりあとに記述されていなくてはなりません。 一般的には </body> の直前でいいようですが、 このページでは、読みにくくならないように、 必要なすべての HTML 要素の定義が終わった直後、 つまり canvas の定義の直後に置いています。
なお、ただ単純に、ブラウザがその行を解析するときに読む、というだけのようですから、 script 行が複数あれば、それぞれそのタイミングで読み込まれるようです。
soinsuu.js の先頭部分は、次のような流れになっています。
まずは、canvas のサイズを決定しています。
var canvas = document.getElementById('canvas-calc'); var w = document.documentElement.clientWidth; var h = 48; if (w > 480){ // 十分広い場合は w = 480; // 480 ピクセルまでとします } canvas.width = w; // canvas の幅設定 canvas.height = h; // canvas の高さ設定
変数 canvas に、canvas id="canvas-calc" を取得しています。 変数 w にブラウザの表示可能領域の横幅を取得し、変数 h に最小の高さ 48 を設定しています。 48 は、このプログラムで使うテキストを、1行表示できるだけのサイズです。
横幅が 480 ピクセル以上ある場合は、480 ピクセルまでとしています。
今この解説文を書いている時点では、
w = Math.min( 480, w );
と書くと思いますが、このときはまだ知りませんでした。
自由に利用できる Math クラスの min 関数を呼び出して、480 と w の小さいほうを w にセットする、という意味です。
サイズが決まったら、canvas の width と height プロパティにセットします。 body タグ内で読み込まれた JavaScript ファイルは先頭から順に実行されていきますので、 このコード(および続くコード)は、自動的に実行されることになります。
続けて、「コンテキストを取得」しています。 これは、canvas の描画設定を行うための「手続き」です。
var ctx = canvas.getContext('2d');
例えば、テキストを描画するためのフォントの設定は、コンテキスト ctx を使用して、次のように記述します。
ctx.font = "36px 'ヒラギノ角ゴ Pro', 'Hiragino Kaku Gothic Pro', 'MS ゴシック', 'MS Gothic', sans-serif";
サイズは 36 ピクセルで、フォント名を記述している順に探し、最初に見つかったフォントを使用する、という意味です。
そのあと正確には canvas の幅の調整を行っていますが、それは重要ではないので飛ばし、 新しい問題を作成するための関数呼び出しに進みます。
makeNewNumber();
これは、自分で作成した関数 makeNewNumber を、引数なしで呼び出している、という意味です。 どこでこの関数を定義しているかと言えば、このファイルのあとのほうです。 同一ファイル内にあるからいいのか、別のファイルでもいいのかどうかは調べていませんが、 C/C++ なら、定義前の関数を呼び出すことはできません。 融通が利くというのか、甘い言語仕様なのかはわかりません。
関数本体はあとにまわし、続きの処理を見ていくと、 次にメインループを定義しています。
function main() { <!-- 繰り返し実行する処理を記述 --> requestAnimationFrame( main ); }
まず、function で定義されている関数は、呼び出されるまで実行されません。 なんだか変な気分ですが、そうなので、そういうことです。 ですので、処理上は、関数の終了まで読み飛ばされます。
もし main 関数が呼び出され、実行されると、記述した処理を実行できます。 最後に書くべき requestAnimationFrame 関数は、 引数に main を渡しているので、このあとまた main 関数を実行する、という意味だそうです。 極めて厳密にはどのようになっているかわかりませんが、 メインループを構築するときの一般的な手続きのようですから、こうしています。
また、この素因数分解プログラムでは、ユーザーの入力なく自動的に表示が更新されることがありませんので、 本来ループで描画する必要はないと思われますが、 今後もこの形で進もうとしていましたので、この形で残しています。
main 関数の定義のあと、いよいよ起動部分が記述されています。
addEventListener('load', main(), false);
「リスナー」の概念が、個人的にはわかりにくいのですが、 「監視する者」という感じで解釈しています。 この場合は、load、すなわちページの読み込みが完了したのを検知し、 main 関数を実行する、という意味になるようです。 最後の false については、今は気にしていません。
このあとは関数の定義しかありませんので、上記 addEventListener 関数が実行されたら この soinsuu.js の実行は終わりで、続きの HTML が読み込まれていくことになると思います。 そしてすべて読み込まられた、main 関数を開始し、ループし続ける、という流れです。
もちろん、main 関数の前に呼び出している makeNewNumber 関数も定義しています。 詳細はあとで触れます。
投稿 September 26, 2020
HTML 要素を取得し、 innerHTML プロパティを書き換えることにより、 HTML 要素にある内部の文字列を動的に書き換えることができます。
div id="left-value" 部分の書き換えは、 updateValue 関数で行っています。 この関数の呼び出しは、 新しい問題が作成されたとき makeNewNumber 関数からと、 素因数ボタンが押されたときに呼び出される numPressed 関数 からのみです。
var elem = document.getElementById('left-value'); var data = '置き換える文字列'; elem.innerHTML = data;
div 〜 /div の文字列が、「置き換える文字列」に置き換えられます。 もともとあった文字列は削除されます。
このプログラムでは、left-value には、updateValue 関数で、 「を、下の素数ボタンを押して、素因数分解せよ!」と、 「は、何で割り切れるかな?」の2行を表示するようにしています。 また、正解が出たときには「おめでとう!」表示に切り替わります。
投稿 September 28, 2020
C/C++ で言うところの構造体が、 なんと定義することなく自由に作成できてしまいます。 メンバーを完全に自由に定義でき、自由に参照できてしまうため、 タイプミスを犯した場合もエラーとならず、問題点を探すのが大変難しそうです。
soinsuu-init.js で次のように定義した pow に、 makeNewNumber 関数 で、値を設定している部分です。
var pow = new Object(); // 正解を保持するオブジェクト
pow.n2 = Math.floor(Math.random() * 6); // 2の0乗〜5乗までを適当に決める pow.n3 = Math.floor(Math.random() * 5); // 3は4乗まで pow.n5 = Math.floor(Math.random() * 4); // 5は3乗まで <!-- 同様の部分は省略 -->
n2 や n3 といった
ここでは整数ばかり設定していますが、結局は変数ですので、自由に設定可能です。
ball.img = new Image(); ball.img.src = 'img/pin.png';
このように、画像データを設定することも可能です(このプログラムでは画像は利用していません)。
投稿 September 28, 2020
Math クラスがどんな関数を持っているかは、ネットを検索すればすぐわかりますので、ここでは基本的なもののみ触れておきます。
Math.random() は、0 以上
多くの場合は 0 〜 1 の乱数が欲しいのではなく、「1 〜 10 の間の数」のような値が欲しいと思います。 このため、Math.floor( 値 ) 関数を組み合わせます。 この関数は、「値」以下の最大の整数を返してくれます。 正の値なら小数点以下を切り捨てですが、負の値の場合はそうではありませんので、ご注意ください。
pow.n2 = Math.floor(Math.random() * 6); // 2の0乗〜5乗までを適当に決める
この場合は、コメントにある通り、pow.n2 に 0 〜 5 のいずれかの値を設定します。 Math.random() * 6 では、0 以上 6 未満の値が返り、それを Math.floor で小数部をカットしている形です。
このほか、よく使うと思われるものを挙げておきます。
Math.abs( 値 ) は、値の絶対値を返します。 つまり、1 なら 1 を、-1 でも 1 を返してくれます。
Math.max( 値1, 値2 ) は、値1と値2を比較し、 大きいほうの値を返してくれます。
逆に、Math.min( 値1, 値2 ) は、値1と値2を比較し、 小さいほうの値を返してくれます。
このプログラムでも使っている、Math.pow( 値1, 値2 ) だと、 値1の「値2」乗を返してくれます。 つまり、Math.pow( 2, 3 ) だと2の3乗なので、8 が返されます。
まだまだあるようですので、計算が必要になったときに、関数を調べてみると、見つかるかも知れません。
投稿 September 28, 2020
ここでは触れませんが、2次元配列はとても使いにくいと思います。 が、ここで触れる1次元配列は、自由度が高く、とても使いやすいです。
soinsuu-init.js で次のように定義した inp を、 makeNewNumber 関数 で、初期化しています。
var inp = []; // 要素数未定の配列
inp.length = 1; // 要素数1 inp[0] = 0; // 値は0
このようにして、要素数を 1 に設定しています。 C/C++ 等と同じように、添字は 0 から開始されます。 最初の要素(この場合は1つなので唯一です)の値を 0 としています。 もちろん、設定できるのは数値に限られるわけではなく、文字列を設定したりもできます。
inp.length ++;
inp[ inp.length - 1 ] = value;
inp.length に1を足し、要素数を1つ拡張しています。 そして新しく追加された要素に、値 value をセットしています。 value は押されたボタンの値を引数で渡されたもので、「2」なら 2、「3」なら 3 です。 最後の要素は、例えば要素数 3 なら添字は 0 〜 2 なので、inp.length - 1 になっています。
ここで、length は関数ではなくプロパティですので、参照にカッコは不要で、 読み出し・設定とも可能です。
投稿 September 28, 2020
新しい問題を作成する関数 makeNewNumber は、メインループ定義のあと、 addEventListener('load', main(), false); 呼び出しのすぐあとに、 記述されています。 記述位置は、同一ファイル内であれば、どこでもいいのではないかと思います。
HTML や JavaScript の新しい要素は特にありませんので、ご覧になりたい方は、 折りたたまれている部分「コード詳細を確認」を展開してください。
概要としては、 C/C++ でいうところの構造体のように使えるオブジェクト pow に、 正解となる値を設定します。 「素因数分解される値」を構成するのが、2の何乗、3の何乗・・・13の何乗かをランダムに決め、 設定しています。 自由に設定しすぎると、とても大きな値になってしまいかねませんので、 2は5乗まで、3は4乗まで・・・11は2乗まで、13は1乗までと制限し、 さらに11と13は同時にあると大きくなるので、どちらかのみに制限しています。
それをもとに、変数 numOrg に、pow から計算された「素因数分解される値」を設定しています。 プレーヤーの回答は、オブジェクト ans に格納するため、 各値をゼロクリアしています。 最後に、それを表示し、また、オブジェクト inp を初期化し、 このあとユーザーが入力した値を順番に記録できるようにしています。 同時に、canvas のサイズも初期化しています。
投稿 September 29, 2020
<button onclick="numPressed(2)" title="2で割る"> 2 </button> <button onclick="numPressed(3)" title="3で割る"> 3 </button> <!-- 同様の記述は省略 -->
button 要素の onclick イベント、すなわちボタンが押されたとき、 numPressed 関数に、押されたボタンの数字を渡して、呼び出しています。
numPressed 関数では、その値で割り切れるかどうかを判断し、 割り切れるならその入力を配列 inp に記録して、 途中経過を更新します。 div タグの文字列の更新については「HTML 要素のテキストの書き換え」に、 canvas への途中経過の描画については「途中計算式の描画(canvas)」に書いています。
HTML や JavaScript の新しい要素は特にありませんので、ご覧になりたい方は、 折りたたまれている部分「コード詳細を確認」を展開してください。
「別の問題に挑戦」ボタンは、初期化時に呼び出したものと同じ、 makeNewNumber 関数を呼び出します。
<button onclick="makeNewNumber()">別の問題に挑戦</button>
新しい問題を作成し、ユーザーの回答や途中経過などを初期化しています。
投稿 September 29, 2020
canvas への 途中計算式の描画は、main 関数で行っています。 先にも書いた通り、ユーザーの入力なく自動的に更新されることはありませんので、 ループ内で描画する必要がないと言えますが、このあとのプログラムに続く部分ですので、 このままにしています。
まず最初に、canvas 全体をグレーで塗りつぶしています。
Windows デスクトップアプリとしては、
ctx.fillStyle = "rgb( 230, 230, 230 )"; ctx.fillRect(0, 0, canvas.width, canvas.height);
位置を決めて、一番最初の数字(問題とした数字) numOrg を記入します。
var x; var y = 40; ctx.font = "36px 'ヒラギノ角ゴ Pro', 'Hiragino Kaku Gothic Pro', 'MS ゴシック', 'MS Gothic', sans-serif"; ctx.fillStyle = '#000'; // 黒文字で x = x_num - ctx.measureText( numOrg ).width; // 右端を x_num にあわせる ctx.fillText( numOrg, x, y ); // canvas 領域の座標(y座標は'下'を指している) y += 40;
ctx.font に、フォント情報を設定しています。 ここではサイズを 36 ピクセルにして、その後に続くフォントを順番に探して、見つかったフォントを使います。 ですので、このままなら、可能性としては、利用環境ごとに正しい表示が得られません。
ctx.fillStyle では、文字の色を設定しています。 塗りつぶしの時は rgb で指定していますが、CSS での指定と同じように、# のあとに RGB 値を 16 進数で並べても OK です。
y 座標には、変数定義時に 40 を設定しています。 この値は、Windows とは違い、文字の下の座標を示していますので、注意が必要です。 そいて x 座標には、ctx.measureText( 文字列 ).width で、 選択中のフォントで「文字列」を描画するとき、どれだけの幅が必要かを返す関数を使用しています。 x_num は、事前に10文字表示するための x 座標の右端を設定していますので、 「10桁表示で右詰め」となる x となります。
ctx.fillText( 文字列, x 座標, y 座標 ) で、テキストを描画しています。 なお、numOrg は数値として扱っていますが、文字列として渡すと文字列として解釈されます。 個人的には便利なので好きですが、バグの温床になることは間違いありません。
var numTmp = numOrg; // 途中経過の表示用 for ( var i = 1; i < inp.length; i++ ){
途中経過表示用に numTmp を定義し、もともとの値 numOrg をコピーしています。 以下ループで、配列 inp の要素数(正確にはそれマイナス1)だけ繰り返されるようにしています。 配列 inp の要素数は、ユーザーの入力回数(正確にはそれマイナス1)です。
numTmp /= inp[i]; if (numTmp > 1){ // 割ったあと1になるなら表示しません ctx.fillStyle = '#F00'; // 赤文字で x = 50 - ctx.measureText( inp[i] ).width; // 右端を 50 にあわせる ctx.fillText(inp[i], x, y - 40); // 1行上に表示
途中経過表示用の numTmp を、ユーザー入力の値 inp[ i ] で割ります。 この値が1になるなら、表示しません(そういうルールにしているため)。
この部分では、割られる数字の左に表示している赤い文字(入力文字)を描画しています。 赤を指定し、文字の右端を 50 ピクセル位置で右詰め表示できる位置を決定しています。 表示位置は、今の y より1行上になるべきなので、1行のピクセル数 40 を引いて描画しています。
ctx.fillStyle = '#000'; ctx.fillText(')', 55, y - 40); // 1行上に表示すべき ctx.fillRect(55, y - 40 + 2, x_num - 55 + 10, 2);// ラインを引く
この部分では、ラインの部分を書いています。 描画が面倒なので、閉じかっこを描画したあと、下に横ラインを引いています。 デバイスや環境によっては、少しずれてしまいますが、今は良しとします。
x = x_num - ctx.measureText( numTmp ).width; // 右端を x_num にあわせる ctx.fillText( numTmp, x, y); // クライアント領域の座標(y座標は'下'を指しているよう) } y += 40; }
最後に、今 y が指す行に、割られた後の数値を描画しています。
投稿 September 26, 2020
このプログラムの次に作成した DaysUntil では、日付の取得やコンボボックス(ドロップダウンリスト)の操作を扱っています。
ウェブ開発に関するトピックは、「ウェブ開発トップ」にまとめられています。