無痛入手 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
查看全部
發表第一個留言支持創作者!
你可能也想看
無痛每月創造被動收入!不能錯過的0擼躺賺區塊鏈項目GrassGrass 是由一家去中心化人工智慧領域的初創公司Wynd Network,在2023年6月所推出的去中心化網路資源共享平台。
avatar
GRAY的離職日記
2024-04-21
姊妹們請進!無痛入手CCD相機!從去年就開始火的CCD相機,美女、網紅現在幾乎人手一台,拍出來的照片張張都有氛圍感!CCD相機不僅記錄下每一個瞬間,更賦予與手機影像不同且難以言喻的深度和質感。今天就來推薦幾款CCD相機。【此篇文章內容引用自新識界】
Thumbnail
avatar
一灘爛泥
2024-03-20
無痛學習的模樣期許未來的自己能幫助更多對英文恐懼或排斥的孩子看到更多這個世界裡頭的有趣與價值。為了達到這個目標,用心準備每一堂課,在課堂中好好陪伴每一位學生學習是現在的我能努力做好的事。
avatar
Evelyn
2023-12-02
「無痛離世」與「山中靜夫的最後尊嚴」觀後感論語裡孔子曾說過:「未知生,焉知死?」過了五十歲之後,我反而有一種「先知死,而後生」的體悟。 最近因為一些因素,對於「死亡」的歷程必須先行預習,所以連續看了兩部日本電影:「無痛離世」跟「山中靜夫的最後尊嚴」。同樣聚焦在癌症末期的主題,同樣超級寫實的演繹了病人在世間最後辛苦的時光,讓我可以先預習癌末
Thumbnail
avatar
soso
2023-09-24
無痛用Bitwarden取代Google Authenticator透過Google Authenticator轉移帳戶的功能,可以一次性提取所有TOTP密鑰,加快整合TOTP到Bitwarden的過程。整合完成後,不論是輸入帳號密碼或是輸入TOTP認證碼,都只需要Bitwarden即可搞定,在安全性和方便性取得平衡。在方便性和安全性之間取得平衡,是資安永遠的課題。
Thumbnail
avatar
GYB
2023-09-14
無痛手術是怎樣出現的?在另一次快速的截肢手術中,李斯頓雖然饒過病人的睪丸,卻意外切斷助理的兩根手指。後來病人與助理雙雙死於壞疽,而一名在旁觀看這場手術的人,看見李斯頓匆忙揮舞手術刀,刀子戳破了外套,還以為李斯頓被戳死,因此嚇得休克,一命嗚呼。在麻醉劑出現之前的年代,手術就是這麼危險。
Thumbnail
avatar
臉譜出版
2023-08-18
無痛考取人生第一張金融證照|金融市場常識與職業道德  為了未來就業穩定,目前打算以成為公股銀行行員的目標邁進!打算花半年至一年的時間考取Fit(金融基測),再慢慢考一些有的沒的法定考試(好痛苦...)   先說我報名證基會2023/7/7的紙筆考試,大概從2023/6/27開始準備,準備時長10天左右。但我不能說自己是非常認真準備的學生,主要原因是
Thumbnail
avatar
高高愛噗大冒險 // Go & Ape's writing
2023-07-10
無痛致富讀書心得2,單筆投資為何比定期定額好? 投資必勝九大金律大綱: 1.股利的三大迷思 2.投資中第1~3級思考 3.大部分的人虧損的原因 4.兩個最有效率的投資策略 5.單筆投資與定期定額的差別。 6.長期投資成功的關鍵是什麼? 7.已實現和未實現報酬的差異。 8.股票賣出的三大理由。 9.投資必勝九大金律。 讀書心得分成兩篇,上篇的連結 本文PODCAS
Thumbnail
avatar
威利財經生活隨筆
2023-04-07
無痛致富讀書心得1,股市長期是秤重機大綱: 1.適合誰閱讀 2.股市秤重機 3.風險管理的兩大誤區 4.長期投資的七大優勢 5.股利是雙面刃 【無痛致富】,去年10/26號上市。作者是佛里曼投資顧問團隊(Freeman Publications),總部位於英國倫敦,提供個人專業的投資理財顧問服務,將最複雜的投資策略以易於理解的語言傳達
Thumbnail
avatar
威利財經生活隨筆
2023-04-05
【迷你水晶洞】錢包無痛入手,招財開運又好看水晶洞功效: 水晶洞又稱作是風水石,裡面充滿漂亮的晶牙、水晶花,彼此能量互相震動凝聚磁場!能夠聚財納福、避邪擋煞、吉祥平安。且晶洞本身就是源源不絕的發電廠,可以幫其他水晶淨化、消磁。 迷你晶洞擺放在辦公桌: 在工作上有貴人幫助、專注辦公、防止小人陷害、被老闆讚許、加薪升官的機會! 迷你晶洞擺放在正財
Thumbnail
avatar
吉祥水晶
2022-03-15