教師用Google Apps Script 自動產生題目篇

 

這段程式主要是用 Google Apps Script 撰寫的,目的是部署成 Web App 後,讓使用者在瀏覽器存取時自動產生一個包含數學題目(包括一元一次與一元二次方程式)的 Google 文件,並返回該文件的連結。下面將分段解釋各個部分的功能與流程:


1. Web App 入口:doGet(e)

  • 用途:當有人透過瀏覽器以 GET 請求訪問此 Web App 的 URL 時,這個函式會被執行。
  • 流程
    • 呼叫 createMathDocWithSolutions() 函式來產生含有題目與解析的 Google 文件。
    • 將該文件的 URL 以純文字形式返回給使用者,格式為「已建立文件,連結如下:\n[文件連結]」。

/**
* Web App 入口:處理 GET 請求
*
* 部署為 Web App 後,任何人在瀏覽器打開該 URL,就會執行此函式。
* 函式會呼叫 createMathDocWithSolutions() 產生 Google 文件,最後回傳文件連結。
*/
function doGet(e) {
// 呼叫既有邏輯:產生題目與解析
var docUrl = createMathDocWithSolutions();

// 回傳純文字,內容為「已建立文件 + 文件連結」
return ContentService
.createTextOutput("已建立文件,連結如下:\n" + docUrl)
.setMimeType(ContentService.MimeType.TEXT);
}

/**
* 建立 Google 文件,並隨機生成一元一次 & 一元二次方程題目,含解析
* 執行後會回傳 Google 文件的網址 (docUrl)
*/
function createMathDocWithSolutions() {
// 1. 建立文件,設定文件名稱
var docName = "一元二題自動生成器(含解析) - " + new Date().toLocaleString();
var doc = DocumentApp.create(docName);
var body = doc.getBody();

// 2. 新增標題
body.appendParagraph("一元二題自動生成器(含解析)")
.setHeading(DocumentApp.ParagraphHeading.HEADING1);

// =================== 題目數量設定 ===================
var numberOfLinearEquations = 3; // 一元一次方程的數量
var numberOfPerfectSquareQuads = 2; // 「完全平方」二次方程的數量
var numberOfRandomQuadraticEquations = 3; // 「隨機」一元二次方程的數量

// ============ (A) 一元一次方程 ============
body.appendParagraph("【一元一次方程】")
.setHeading(DocumentApp.ParagraphHeading.HEADING2);

for (var i = 1; i <= numberOfLinearEquations; i++) {
// 亂數產生 a, b, c,確保 a != 0
var a = getRandomNonZeroInt(-10, 10);
var b = getRandomInt(-10, 10);
var c = getRandomInt(-10, 10);

// 題目: ax + b = c
var eqText = a + "x ";
if (b >= 0) {
eqText += "+ " + b + " = " + c;
} else {
eqText += "- " + Math.abs(b) + " = " + c;
}
body.appendParagraph("題目 " + i + ": " + eqText);

// 解析: x = (c - b)/a
var solution = solveLinear(a, b, c);
body.appendParagraph("【解析】" + solution)
.setIndentStart(30); // 縮排以區分題目與解析
}

// ============ (B) 完全平方二次方程 ============
body.appendParagraph("【一元二次方程 - 完全平方】")
.setHeading(DocumentApp.ParagraphHeading.HEADING2);

// 列出幾個常見的 (x ± k)^2 = x² ± 2kx + k² 形式
var perfectSquareSamples = [
{ b: 0, c: 0 }, // (x)^2 = x²
{ b: 2, c: 1 }, // (x+1)^2 = x² + 2x + 1
{ b: -4, c: 4 }, // (x-2)^2 = x² - 4x + 4
{ b: 6, c: 9 }, // (x+3)^2 = x² + 6x + 9
{ b: -2, c: 1 }, // (x-1)^2 = x² - 2x + 1
];

for (var psq = 1; psq <= numberOfPerfectSquareQuads; psq++) {
// 隨機從此清單選一個完全平方組合 (b, c)
var idx = getRandomInt(0, perfectSquareSamples.length - 1);
var chosen = perfectSquareSamples[idx];

// 題目: x² + bx + c = 0
// a=1 => x² + bx + c
var eqPS = formatQuadraticEquation(1, chosen.b, chosen.c);
body.appendParagraph("題目 (完全平方) " + psq + ": " + eqPS);

// 解析
var solText = solveQuadratic(1, chosen.b, chosen.c);
body.appendParagraph("【解析】" + solText)
.setIndentStart(30);
}

// ============ (C) 隨機一元二次方程 ============
body.appendParagraph("【一元二次方程 - 隨機】")
.setHeading(DocumentApp.ParagraphHeading.HEADING2);

for (var j = 1; j <= numberOfRandomQuadraticEquations; j++) {
// 亂數產生 a, b, c,確保 a != 0
var a2 = getRandomNonZeroInt(-5, 5);
var b2 = getRandomInt(-5, 5);
var c2 = getRandomInt(-5, 5);

// 題目: ax² + bx + c = 0
var eqText2 = formatQuadraticEquation(a2, b2, c2);
body.appendParagraph("題目 (隨機) " + j + ": " + eqText2);

// 解析
var solText2 = solveQuadratic(a2, b2, c2);
body.appendParagraph("【解析】" + solText2)
.setIndentStart(30);
}

// 產生完題目後,回傳文件 URL
var docUrl = doc.getUrl();
Logger.log("文件已建立,連結如下:\n" + docUrl);
return docUrl;
}

/**
* 產生區間 [min, max] 的亂數整數
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
* 取得一個「非 0」的亂數整數
*/
function getRandomNonZeroInt(min, max) {
var val = getRandomInt(min, max);
while (val === 0) {
val = getRandomInt(min, max);
}
return val;
}

/**
* 格式化「ax² + bx + c = 0」的文字 (使用 x² 取代 x^2)
*/
function formatQuadraticEquation(a, b, c) {
// ax²
var result = formatTerm(a, "x²");

// bx
if (b >= 0) {
result += " + " + formatTerm(b, "x");
} else {
result += " - " + formatTerm(Math.abs(b), "x");
}

// c
if (c >= 0) {
result += " + " + c;
} else {
result += " - " + Math.abs(c);
}

// 結尾
result += " = 0";
return result;
}

/**
* 格式化「coefficient + variableTerm」,自動處理 ±1
* (3, "x²") => "3x²"
* (1, "x") => "x"
*/
function formatTerm(coefficient, variableTerm) {
if (coefficient === 1) {
return variableTerm; // 1x² => x²
} else if (coefficient === -1) {
return "-" + variableTerm; // -1x² => -x²
} else {
return coefficient + variableTerm;
}
}

/**
* 解一元一次方程 ax + b = c
* 回傳解析文字 (含計算過程)
*/
function solveLinear(a, b, c) {
// ax + b = c => ax = c - b => x = (c - b)/a
var xVal = (c - b) / a; // 可能是小數

var explanation = "\n1. 將 b 移到右邊 => ax = c - b" +
"\n2. 再將 a 移到右邊(除以 a) => x = (c - b) / a" +
"\n3. 帶入數值 => x = (" + c + " - " + b + ") / " + a +
"\n => x = " + xVal;

return explanation;
}

/**
* 解一元二次方程 ax² + bx + c = 0
* 回傳解析文字 (含計算步驟)
*/
function solveQuadratic(a, b, c) {
// 判別式 Δ = b^2 - 4ac
var delta = b*b - 4*a*c;
var explanation = "已知方程:" + a + "x² + " + b + "x + " + c + " = 0\n"
+ "判別式 Δ = b² - 4ac = " + (b*b) + " - 4×" + a + "×" + c + " = " + delta + "\n";

if (delta > 0) {
// 兩個實根
var sqrtDelta = Math.sqrt(delta);
var x1 = (-b + sqrtDelta) / (2*a);
var x2 = (-b - sqrtDelta) / (2*a);
explanation += "Δ > 0,故有兩個不相等的實根:\n"
+ "x₁ = (-b + √Δ) / 2a = " + x1 + "\n"
+ "x₂ = (-b - √Δ) / 2a = " + x2;
} else if (delta === 0) {
// 一個重根
var x0 = -b / (2*a);
explanation += "Δ = 0,故只有一個重根:\n"
+ "x = -b / 2a = " + x0;
} else {
// delta < 0
explanation += "Δ < 0,故無實數解(有複數解)。";
}
return explanation;
}