有效率地猜中數字

題目來源: Alpha Camp 學期一作業
這是第一個讓我覺得”卡卡”的題目,也是一個讓我在寫出來後深刻認知到: “想不到解決問題的方法,會寫程式也沒用“的題目。

藉著記錄解題的過程,把當初解決問題的方法和思考方式寫下來,達成再次深入複習的目的。

解題工具

  1. Math.random() 會回傳一個偽隨機小數介於0到1之間(包含0,不包含1)
  2. Math.floor() 會回傳小於等於所給數字的最大整數(無條件捨去)。
  3. while 迴圈
  4. if statement

基本題

題目要求

  1. 指定介於 1-100 之間的數字,存在 answer 變數裡
  2. 設定一個 guess 變數,代表挑戰者 (電腦) 猜的數字
  3. 電腦可重覆「猜數字」,比對 guessanswer,判斷太大或太小
  4. 若兩者相等(猜對)則結束遊戲,且結束時須計算電腦猜了幾次

解題過程

使用 Math.random() 和 Math.floor() 指定一個介於 1-100 之間的數字,存在 answer 變數裡。

原本想要直接用 Math.ceil() 無條件進位,但後來發現 Math.random() 有可能產生 “0”。所以如果直接用 Math.ceil() 可能會發生 guess === 0 的問題(不符合猜數範圍)。

先用 Math.random() * 100 取得一個介於0到100之間(包含0,不包含100)的隨機小數。把這個小數套進 Math.floor() 裡面進行無條件捨去再加1,就能取得一個介於1到100之間(包含1,包含100)的隨機整數了。

這裡還犯過一個非常不應該的錯誤,我把 Math 寫成 math ,然後就又跳錯誤了……


到目前為止,條件一和條件二都可以達成了。

1
2
3
4
5
// 指定介於 1-100 之間的數字,存在 `answer` 變數裡
const answer = Math.floor(Math.random() * 100) + 1

// 設定一個 `guess` 變數,代表挑戰者 (電腦) 猜的數字
let guess = Math.floor(Math.random() * 100) + 1

for / while 的選擇—-因為這次的比大小是條件判斷,所以選擇 while 迴圈。
先把邏輯寫出來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 生成並印出答案
宣告 anwer
// 進入迴圈的條件(重複執行的原因)
while (猜數 不等於 答案) ,則進入迴圈

// 每一回合會重複的動作(放進迴圈裡)
猜一次數字,並且回合數 + 1 (接著進入結果判斷)
IF 猜數 > 答案
則 印出回合數、印出猜數、印出莊家回答:太大了
ELSE IF 猜數 < 答案
則 印出回合數、印出猜數、印出莊家回答:太小了
ELSE (猜數 = 答案)
則 印出回合數、印出猜數、印出莊家回答:恭喜答對!
結束迴圈

好像差不多了,條件三和條件四也都可以達成了。但跟著邏輯再想一遍以後又發現了問題:

  1. 迴圈裡回合數每次 + 1,但不知道是從多少開始加,所以要先宣告回合數 = 0
  2. 一開始只生成答案無法達成進入迴圈條件,為了達成條件,先宣告猜數 = 0

以上問題都解決了,可以組合起來寫出程式碼了!

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
// ========= 宣告變數 ==========
const answer = Math.floor(Math.random() * 100) + 1 //正確答案
let guess = 0 //挑戰者猜的數字
let count = 0 //回合數

// ========= 從這裡開始 ==========
// 先印出正確數字
console.log(`正確答案為 ${answer}`)

// 設計判斷式和迴圈解決問題
while (guess !== answer) {

guess = Math.floor(Math.random() * 100) + 1 // 挑戰者猜一個本回合的數字
count += 1

// 條件判斷
if (guess > answer) {
console.log(`第 ${count} 回合,挑戰者猜 ${guess},莊家回答:太大了,再猜一次`)
} else if (guess < answer) {
console.log(`第 ${count} 回合,挑戰者猜 ${guess},莊家回答:太小了,再猜一次`)
} else {
console.log(`第 ${count} 回合,挑戰者猜 ${guess},莊家回答:恭喜答對!`)
console.log('===============遊戲結束===============')
}
}


進階題

題目要求

  • 讓電腦在 10 次以內猜對

解題過程

花了三個小時都沒想出來,最後決定上網找找靈感,幸運發現一個前輩的解題思路。認真看完一遍,發現就跟以前玩終極密碼一樣,最快的方式就是縮小範圍,砍半再砍半。如果用對半切的方式,已知 2 的 10 次方為 1024,即範圍 1~1024 內的數字用砍半方式一定可以在 10 次內猜到,符合題目要求。

知道方法後要做的事只有2件:

  1. 設定猜數的上限和下限
  2. 讓猜數取上限和下限的中位數
1
2
3
4
5
6
7
8
9
10
11
// 設定猜數的起始上限和下限(放起始宣告位置)
let rangeMax = 100
let rangeMin = 1

// 設定迴圈裡猜數的上限和下限重新賦值的方式(放入迴圈)
// 猜過的數字不能包含在範圍內
rangeMax = guess - 1
rangeMin = guess + 1

// 以猜數範圍的中位數再猜一次(放入迴圈)
guess = Math.floor((rangeMin + rangeMax) / 2)

該作的變數調整都完成了,接下來就是把它們塞進該放的位置。

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
// 宣告並產生答案
const answer = Math.floor(Math.random() * 100) + 1

// ========= start coding ==========
let guess = 0 // 宣告挑戰者猜的數字為0
let count = 0 // 宣告起始回合數為0
let rangeMax = 100 // 宣告猜測最大值為100
let rangeMin = 1 // 宣告猜測最小值為1

console.log(`莊家數字是 ${answer}`) //印出正確答案

while (guess !== answer) {
guess = Math.floor((rangeMin + rangeMax) / 2)
count += 1

if (guess > answer) {
rangeMax = guess - 1
console.log(`第 ${count} 局|電腦猜 ${guess} ,太大,再猜一次! 範圍 ${rangeMin} ~ ${rangeMax} 。`)
} else if (guess < answer) {
rangeMin = guess + 1
console.log(`第 ${count} 局|電腦猜 ${guess} ,太小,再猜一次! 範圍 ${rangeMin} ~ ${rangeMax} 。`)
} else {
console.log(`第 ${count} 局|電腦猜 ${guess} ,恭喜答對!`)
}
}

終於成功了!! 這也是文章開頭說的: 如果知道解決問題的方法,只要能正確地把程式碼寫出來就好;如果不知道解決問題的方法,就算程式語言再精深都沒用,因為連怎麼開始都不知道。

其他小題目還好,就不特別寫解題筆記了。預計下一篇會寫更卡一點的期末考 😅

文章內容如有錯誤,歡迎留言討論!


本 Blog 上的所有文章除特别聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!