See the Pen Untitled by matsunaga kosuke (@matsunaga-kosuke) on CodePen.
参考サイト
p5.js
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- PLEASE NO CHANGES BELOW THIS LINE (UNTIL I SAY SO) -->
<script language="javascript" type="text/javascript" src="libraries/p5.min.js"></script>
<script language="javascript" type="text/javascript" src="sketch_251102b.js"></script>
<!-- OK, YOU CAN MAKE CHANGES BELOW THIS LINE AGAIN -->
<style>
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
</body>
</html>
sketch_251102b.js
// p5.js 計算ドリル
// マウスで5択から選ぶ
// 出題種別:
// 0: 500以下の3桁 × 30未満の2桁
// 1: 4桁 + 4桁
// 2: 2桁 × 2桁
// 3: 2桁 × 1桁 覆面算(□に入る1けた)
// 4: 4桁 ÷ 2桁(割り切れる)
// 5: 1桁 − 小数3桁(負にならない)
// 6: 4桁の覆面算の計算(4桁+4桁の答えの1けたが□)
let problemText = "";
let choices = []; // {label, value, x,y,w,h}
let correctValue = "";
let message = "";
let problemType = 0;
function setup() {
createCanvas(720, 480);
textFont("sans-serif");
textSize(20);
newProblem();
}
function draw() {
background(245);
fill(0);
textSize(22);
text("p5.js 計算ドリル", 20, 30);
textSize(18);
text(problemText, 20, 90, width - 40, 200);
// 選択肢を描画
for (let i = 0; i < choices.length; i++) {
let c = choices[i];
stroke(0);
strokeWeight(1);
fill(255);
rect(c.x, c.y, c.w, c.h, 8);
fill(0);
textAlign(CENTER, CENTER);
text(c.label, c.x + c.w / 2, c.y + c.h / 2);
}
// メッセージ
textAlign(LEFT, BASELINE);
fill(0);
textSize(18);
text(message, 20, height - 40);
}
function mousePressed() {
for (let i = 0; i < choices.length; i++) {
let c = choices[i];
if (
mouseX > c.x && mouseX < c.x + c.w &&
mouseY > c.y && mouseY < c.y + c.h
) {
checkAnswer(c.value);
break;
}
}
}
function checkAnswer(val) {
if (val === correctValue) {
message = "◎ せいかい! もう1問だすね。(クリックで次)";
} else {
message = "× ざんねん… せいかいは「" + correctValue + "」だよ。(クリックで次)";
}
// 次の問題をすぐ出すならここで呼ぶ
// 1クリックで答えを見て、さらにクリックで次にしたいなら、
// ここではフラグにしてもいい。簡単に今回はすぐ次にする。
setTimeout(newProblem, 3000); // 0.8秒で次
}
function newProblem() {
message = "";
choices = [];
// 問題タイプをランダムに
problemType = int(random(0, 7));
switch (problemType) {
case 0:
genMul3digitUnder500x2digitUnder30();
break;
case 1:
genAdd4digit4digit();
break;
case 2:
genMul2digit2digit();
break;
case 3:
genMasked2digitTimes1digit();
break;
case 4:
genDiv4digitBy2digit();
break;
case 5:
gen1digitMinusDecimal3();
break;
case 6:
genMasked4digitCalc();
break;
}
layoutChoices();
}
// 0: 500以下の3桁 × 30未満の2桁
function genMul3digitUnder500x2digitUnder30() {
let a = int(random(100, 501)); // 100〜500
let b = int(random(10, 30)); // 10〜29
let ans = a * b;
problemText = a + " × " + b + " を計算しなさい。";
correctValue = str(ans);
let wrongs = makeNearNumbers(ans, 4, 0.05); // ±5%くらいで
buildChoices(correctValue, wrongs);
}
// 1: 4桁 + 4桁
function genAdd4digit4digit() {
let a = int(random(1000, 10000));
let b = int(random(1000, 10000));
let ans = a + b;
problemText = a + " + " + b + " を計算しなさい。";
correctValue = str(ans);
let wrongs = makeNearNumbers(ans, 4, 0.03);
buildChoices(correctValue, wrongs);
}
// 2: 2桁 × 2桁
function genMul2digit2digit() {
let a = int(random(10, 100));
let b = int(random(10, 100));
let ans = a * b;
problemText = a + " × " + b + " を計算しなさい。";
correctValue = str(ans);
let wrongs = makeNearNumbers(ans, 4, 0.08);
buildChoices(correctValue, wrongs);
}
// 3: 2桁 × 1桁 覆面算(□に入る1けた)
function genMasked2digitTimes1digit() {
let x = int(random(10, 100)); // 2桁
let y = int(random(2, 10)); // 1桁
let prod = x * y;
let xStr = str(x);
// 2桁のうちどちらかを□に
let idx = int(random(0, 2)); // 0:十のくらい, 1:一のくらい
let missingDigit = xStr.charAt(idx);
let shownX =
(idx === 0 ? "□" + xStr.charAt(1) : xStr.charAt(0) + "□");
problemText = shownX + " × " + y + " = " + prod + " □に入る数はどれですか。";
correctValue = missingDigit; // 0〜9の1文字
let wrongs = makeOtherDigits(missingDigit, 4);
buildChoices(correctValue, wrongs);
}
// 4: 4桁 ÷ 2桁(割り切れる)
function genDiv4digitBy2digit() {
// 2桁のわり算で4桁になるように作る
// divisor: 10〜99
// quotient: 10〜99
// dividend = divisor * quotient
let divisor, quotient, dividend;
while (true) {
divisor = int(random(10, 100));
quotient = int(random(10, 100));
dividend = divisor * quotient;
if (dividend >= 1000 && dividend <= 9999) break;
}
problemText = dividend + " ÷ " + divisor + " を計算しなさい。";
correctValue = str(quotient);
let wrongs = makeNearNumbers(quotient, 4, 0.3, true); // ±30%で
buildChoices(correctValue, wrongs);
}
// 5: 1桁 − 小数3桁(負にならない)
function gen1digitMinusDecimal3() {
let a = int(random(1, 10)); // 1〜9
// bは0.001〜a.000くらいで3桁
let b = int(random(1, a * 1000 + 1)) / 1000.0;
let ans = a - b;
ans = float(ans.toFixed(3));
problemText = a + " − " + b.toFixed(3) + " を計算しなさい。(小数第3位まで)";
correctValue = ans.toFixed(3);
let wrongs = makeDecimalNear(ans, 4, 0.02); // ±0.02程度
buildChoices(correctValue, wrongs, true);
}
// 6: 4桁の覆面算の計算(4桁+4桁の答えの1けたが□)
function genMasked4digitCalc() {
let a = int(random(1000, 10000));
let b = int(random(1000, 10000));
let sum = a + b;
let sumStr = str(sum);
// どこか1けたを□に
let idx = int(random(0, sumStr.length));
let missing = sumStr.charAt(idx);
let shown = "";
for (let i = 0; i < sumStr.length; i++) {
if (i === idx) shown += "□";
else shown += sumStr.charAt(i);
}
problemText = a + " + " + b + " = " + shown + " □に入る数はどれですか。";
correctValue = missing;
let wrongs = makeOtherDigits(missing, 4);
buildChoices(correctValue, wrongs);
}
// =============================
// 選択肢ユーティリティ
// =============================
function buildChoices(correct, wrongArray) {
// 正解を入れてからシャッフル
let temp = [correct].concat(wrongArray);
temp = shuffle(temp);
choices = [];
// レイアウトは2列×3行ぶんのうち5コ
let startX = 60;
let startY = 180;
let w = 240;
let h = 50;
for (let i = 0; i < temp.length; i++) {
let col = i % 2;
let row = int(i / 2);
let x = startX + col * (w + 30);
let y = startY + row * (h + 15);
choices.push({
label: temp[i],
value: temp[i],
x: x,
y: y,
w: w,
h: h
});
}
}
function layoutChoices() {
// すでにbuildChoicesでレイアウト済み
}
// 近い数値のダミーをつくる(整数)
function makeNearNumbers(ans, count, rate, positiveOnly = false) {
let arr = [];
let tries = 0;
while (arr.length < count && tries < 100) {
tries++;
let delta = int(ans * rate);
if (delta < 1) delta = 1;
let cand = ans + int(random(-delta, delta + 1));
if (cand === ans) continue;
if (positiveOnly && cand <= 0) continue;
if (!arr.includes(str(cand))) {
arr.push(str(cand));
}
}
// 足りないときは適当に足す
while (arr.length < count) {
let cand = ans + int(random(1, 10));
if (!arr.includes(str(cand))) arr.push(str(cand));
}
return arr;
}
// 小数のダミー
function makeDecimalNear(ans, count, width) {
let arr = [];
let tries = 0;
while (arr.length < count && tries < 200) {
tries++;
let cand = ans + random(-width, width);
cand = float(cand.toFixed(3));
if (cand === ans) continue;
if (cand < 0) continue;
let cstr = cand.toFixed(3);
if (!arr.includes(cstr)) arr.push(cstr);
}
while (arr.length < count) {
let cand = ans + random(0.001, 0.010);
let cstr = cand.toFixed(3);
if (!arr.includes(cstr)) arr.push(cstr);
}
return arr;
}
// ある数字(digit)以外の0〜9からダミーをつくる
function makeOtherDigits(digit, count) {
let arr = [];
for (let d = 0; d <= 9; d++) {
if (str(d) === str(digit)) continue;
arr.push(str(d));
}
arr = shuffle(arr);
return arr.slice(0, count);
}