無痛入手 C++:應用教學 - 1A2B

閱讀時間約 12 分鐘

須具備知識

  1. 無痛入手 C++:基礎系列8 - vector
  2. 如何產生亂數

遊戲規則

1A2B 是一個猜數字的遊戲,首先會隨機產生一組數字皆不重複的四位數,如: 1234。

接著會讓使用者猜數字,程式要依照使用者猜的數字回答猜中了幾個數字,其中位置猜對的為A,只有數字猜對位置猜錯的為B。

舉例來說,使用者猜了 1528,有一個數字 (1) 有出現在答案中且位置正確,有一個數字 (2) 有出現在答案中但位置錯誤,所以程式要回答 1A1B。假設使用者接著猜1278,程式要回答 2A0B。依此類推,直到使用者猜到正確答案。


架構設計

首先我們要隨機產生一組數字皆不重複的四位數。

接著需要讓使用者持續輸入四位數,直到猜對為止。所以應該要使用無限迴圈來讀取使用者猜的四位數,並且在使用者猜對以後跳出迴圈並結束程式。

每次使用者輸入四位數以後,我們需要將它拆解成四個數字,並比計算有幾個數字在答案中且位置正確 (A),有幾個數字雖然在答案中但位置錯誤 (B)。如果計算出來是 4A 的話就表示使用者猜對了。

最簡單且實用的 debug 技巧: 用 cout 把數值印出來看看是否如自己的預期。


產生四位數

首先要隨機產生一組四位數,而且數字不能重複。我們可以用以下程式隨機產生四個介於 0 ~ 9 的數然後存到 vector 裡面:

vector<int> answer;
srand(time(0));
// generate 4 random digits
for (int i = 0; i < 4; ++i)
answer.push_back(rand() % 10);

不過這樣的寫法可能會產生出重複的數字,所以還要檢查產生的數字是否已經出現過了。我們可以在產生新的亂數以後,走訪一次 vector,檢查是不是有相同的數字在裡面了。

// generate 4 random digits
for (int i = 0; i < 4; ++i) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
}

在檢查前,先宣告一個 bool 變數: hasDuplicate 來記錄是否有出現找到重複的數字,當內層的迴圈檢查到重複的數字時,就會把 hasDuplicate 設成 true 然後直接跳出迴圈 (因為只要有一個重複的數字就需要重新產生亂數,剩下的就不用檢查了)。

假如檢查完後 hasDuplicate 為 false,表示沒有出現重複的數字,可以把新產生的數字加到 answer 中。

有個地方也要調整: 如果出現重複的數字,就需要重新產生新的數字,也就是說,外層 for 可能需要執行不只 4 次。為了確保可以得到四個不重複的數字,我們將外層的 for 改成無限迴圈,並且在得到四個不重複的數字以後跳出迴圈。完整的程式碼如下:

vector<int> answer;
srand(time(0));
// generate 4 random digits
for (;;) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
if (answer.size() == 4)
break;
}


補充

檢查是否有出現重複數字的部分所用到的程式邏輯還算蠻常遇到的,用通用一點的方式描述的話是:

宣告一個用來記錄搜尋結果的變數;
// 走訪 vector 或其他容器的元素
for (...) {
if (檢查元素是否具有特定條件) {
將檢查的結果紀錄在變數裡面;
break;
}
}
if (檢查變數的值)
做出對應的行為​;

現代 C++ 並不鼓勵這種土炮寫法,不過目前學的東西還很有限,等熟練基礎語法以後再學習更好的寫法。


拆解使用者輸入的四位數

我們使用無限迴圈來讓使用者持續輸入四位數,int i 是用來記錄使用者猜了幾次。number 是用來儲存使用者輸入的四位數,digits 則是用來儲存拆開後的四個數字:

// read input from the user
for (int i = 0;;++i) {
int number;
vector<int> digits(4, 0);
cout << "Guess a four digits number(" << i + 1 << "th guess):\n";
cin >> number;
// TODO: split numbers into digits
// TODO: compare the digits with the answer
}
cout << "You win!\n";

我們首先來實作拆解四位數的部分。可以利用 % 來取得個位數字,接著將 number 除以 10,將所有位數往右移,本來的十位數字現在就變成新的個位數字了,重複這個動作 4 次,就可以取出所有的數字。

需要注意的是,我們是從個位數字開始取,最後才取千位數字,也就是說,我們取得的數字順序會剛好跟使用者輸入的數字順序相反。為了解決這個問題,我們需要從 digits 的最後一個位置開始擺數字,這樣個位數字才會是 digits 的最後一個元素,千位數字則會是 digits 的第一個元素

// split numbers into digits
for (int j = 3; j >= 0; --j) {
digits[j] = number % 10;
number /= 10;
}

注意: 外層的 for 使用的 induction variable 叫做 i,記得幫內層的 induction variable 取一個不同的名字。



比對答案

接下來要比對 digits 和我們一開始隨機產生的 answer,計算總共有幾個 A 和幾個 B

用更具體的方式描述的話是: 從 digits 逐一拿出數字,檢查這個數字是否出現在 answer 中,如果有的話,檢查他們在 vector 中的 index 是否一樣。

有了具體的描述以後,就可以很容易的將要做的事情轉換成程式碼: 從 digits 逐一拿出數字這件事情會需要一個 for,而每個數字又需要檢查是否出現在 answer 中,所以還需要第二個巢狀 for。

// compare the digits with the answer​
int numA = 0;
int numB = 0;
for (int j = 0; j < 4; ++j) {
for (int k = 0; k < 4; ++k) {
if (digits[j] == answer[k] && j == k)
numA += 1;
else if (digits[j] == answer[k])
numB += 1;
}
}

注意: 第二個判斷式其實不需要加上 j != k ,因為第一個 if 會攔截 j == k 的情況,所以會進到第二個 if 的一定是 j != k。

假如 numA 是 4,表示使用者猜對了,遊戲結束。不然的話,要將 A 和 B 的數字印出來:

if (numA == 4)
break;
cout << numA << " A " << numB << " B\n";


完整的程式碼

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
using namespace std;

int main() {
vector<int> answer;
srand(time(0));
// generate 4 random digits
for (;;) {
int randomNumber =rand() % 10;
bool hasDuplicate = false;
// check if there are duplicate digits
for (int j = 0; j < answer.size(); ++j) {
if (answer[j] == randomNumber) {
hasDuplicate = true;
break;
}
}
if (!hasDuplicate)
answer.push_back(randomNumber);
if (answer.size() == 4)
break;
}

// read input from the user
for (int i = 0;;++i) {
int number;
vector<int> digits(4, 0);
cout << "Guess a four digits number(" << i + 1 << "th guess):\n";
cin >> number;
// split numbers into digits
for (int j = 3; j >= 0; --j) {
digits[j] = number % 10;
number /= 10;
}
// compare the digits with the answer​
int numA = 0;
int numB = 0;
for (int j = 0; j < 4; ++j) {
for (int k = 0; k < 4; ++k) {
if (digits[j] == answer[k] && j == k)
numA += 1;
else if (digits[j] == answer[k])
numB += 1;
}
}
if (numA == 4)
break;
cout << numA << " A " << numB << " B\n";
}
cout << "You win!\n";
return 0;
}


挑戰題

檢查使用者輸入的數字是否符合要求:
1. 正整數
2. 四位數
3. 沒有出現重複的數字

如果不符合要求的話,請使用者輸入一組新的四位數。





2會員
14內容數
程式設計 & 電腦系統 & 系統軟體
留言0
查看全部
發表第一個留言支持創作者!