随机种子设置:使用当前时间初始化随机数生成器,确保每次运行游戏的字母排列不同。
用户选择难度:提供3种难度选项(4x2, 5x2, 7x2),根据选择初始化不同大小的游戏板。
字母分配:根据游戏板大小选择不同数量的字母(如4x2用4个字母,各重复一次)。
随机打乱顺序:使用std::shuffle
将字母对随机排列,填充到table
数组中。
空板创建:创建table_empty
数组用于跟踪玩家已翻开的卡片,初始全为0(空白)。
显示界面:print_table
函数根据table_empty
的状态显示棋盘,已匹配的位置显示字母,未匹配的显示空格。
用户输入处理:
输入验证:检查输入是否为有效数字、是否在合法范围内、是否重复选择同一位置。
错误处理:无效输入时重置数据,重新提示输入。
双卡匹配检查:当玩家选择两张卡片后,比较table
中对应位置的字母。
匹配成功:保持卡片翻开状态。
匹配失败:短暂显示后重置为空白,更新table_empty
。
胜利条件:当所有卡片成功匹配时,提示胜利并询问是否重玩。
延时函数:使用条件编译处理Windows(Sleep
)和Unix(sleep
)系统的差异,确保游戏反馈可见。
模块化设计:将游戏逻辑封装在memory_game
命名空间中,函数职责明确(如初始化、输入处理、匹配检查等)。
模板函数:is_number
和SLEEP
使用模板和条件编译,增强跨平台兼容性。
清晰提示:指导玩家输入,显示可用字母类型,错误输入时给出反馈。
胜利处理:游戏结束后提供重玩选项,保持当前难度或退出。
头文件包括了必要的头文件,如
用于随机洗牌,
和
用于随机数生成,
用于输入输出,
和
用于随机数和动态数组的处理。
#include /// for std::srand()
#include /// for std::time()
#include /// for IO operations
#include /// for std::mt19937
#include /// for std::vector
实现一个跨平台的睡眠(延时)功能,根据不同的操作系统使用不同的睡眠函数
// `Sleep` is only available in Windows in milliseconds.
// However, on Unix/Linux systems it is `sleep`, in seconds.
#ifdef _WIN32
#include /// for Sleep()
template
constexpr typename std::enable_if::value, void>::type SLEEP(
T milliseconds) {
Sleep(milliseconds * 1000);
}
#else
#include /// for sleep()
template
constexpr T SLEEP(T seconds) {
return sleep(seconds);
}
#endif
命名空间定义:定义了一个games
命名空间,用于组织游戏相关的代码;在games
命名空间下,又定义了一个memory_game
命名空间,专门用于记忆游戏的实现。games::memory_game
:封装所有游戏相关函数,避免命名冲突。使用泛型编程(template
)增强代码复用性,支持不同类型的游戏板(如 char
、int
)。
核心函数解析:
(1)is_number
template
bool is_number(const T& input) {
if (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(256, '\n');
return false;
}
return true;
}
功能:检查输入是否为有效数字。
逻辑:
若输入失败(如非数字),清除错误状态并忽略错误输入,返回 false
。
否则返回 true
。
用途:防止无效输入导致程序崩溃。
(2)init
template
void init(std::vector* table) {
std::vector letters(7);
// Decrease / increase the number of letters depending on the size.
if ((*table).size() == 10) { // 5x2
letters = { 'A', 'E', 'Z', 'P', 'D' };
}
else if ((*table).size() == 8) { // 4x2
letters = { 'A', 'E', 'Z', 'D' };
}
else if ((*table).size() == 14) { // 7x2
letters = { 'A', 'E', 'Z', 'P', 'D', 'B', 'M' };
}
std::vector pairs;
for (char letter : letters) {
pairs.push_back(letter);
pairs.push_back(letter);
}
std::shuffle(pairs.begin(), pairs.end(),
std::mt19937(std::random_device()()));
for (int i = 0; i < (*table).size(); i++) {
(*table)[i] = pairs[i];
}
std::cout << "All available types are: ";
for (int i = 0; i < letters.size(); i++) {
if (i == letters.size() - 1) {
std::cout << "and " << letters[i] << ".\n\n";
}
else {
std::cout << letters[i] << ", ";
}
}
}
功能:初始化游戏板,随机分配字母对。
关键点:
根据表格大小(4x2、5x2、7x2)选择不同数量的字母。
使用 std::shuffle
随机打乱字母顺序。
(3)print_table
功能:打印当前游戏板状态。
逻辑:
已匹配的字母显示为原值,未匹配的显示为空格。
每行显示5个元素(适应不同表格大小)。
template
void print_table(const std::vector& table) {
std::cout << "| ";
std::vector table_print(table.size());
for (int i = 0; i < table.size(); i++) {
table_print[i] = ' ';
if (table[i] != 0) {
table_print[i] = table[i];
}
}
for (int i = 0; i < table.size(); i++) {
if (i % 5 == 0 && i != 0) {
std::cout << "\n| ";
}
std::cout << table_print[i] << " | ";
}
}
(4) ask_data
与 reset_data
功能:获取用户输入并验证。
验证内容:
是否为数字。
是否在有效范围内(1~表格大小)。
是否重复选择同一位置。
reset_data
:重置输入状态并重新调用 ask_data
。
template
void reset_data(const std::vector&, int*, int*, int*);
template
void ask_data(const std::vector& table, int* answer, int* old_answer,
int* memory_count) {
(*old_answer) = (*answer);
print_table(table);
std::cout << "\n\nType your response here (number index):\n";
std::cin >> (*answer);
if (!is_number((*answer))) {
std::cout << "\nYou must enter a valid number.\n\n";
reset_data(table, answer, old_answer, memory_count);
}
// Increase the memory count, which will be later on used for checking if
// the user has already answered two values.
(*memory_count)++;
if (((*answer) > table.size()) || ((*answer) < 1)) {
std::cout << "\nYou can't check a value that doesn't exist (or an "
"invalid number).\n\n";
reset_data(table, answer, old_answer, memory_count);
}
if ((*old_answer) == (*answer)) {
std::cout << "\nYou can't check the same value twice.\n\n";
reset_data(table, answer, old_answer, memory_count);
}
// If two matches are answered already, but the user checkes a non-answered
// and an answered value, the program will mark it as no match, however, we
// must not allow the user to check the same value twice.
if ((table[(*answer) - 1] != 0) &&
((table[(*old_answer)] == 0) || (table[(*old_answer)] != 0))) {
std::cout << "\nYou can't check the same value twice.\n\n";
reset_data(table, answer, old_answer, memory_count);
}
}
template
void reset_data(const std::vector& table, int* answer, int* old_answer,
int* memory_count) {
(*answer) = (*old_answer);
(*memory_count)--;
ask_data(table, answer, old_answer, memory_count);
}
(5) match
功能:检查两次选择的字母是否匹配。
逻辑:
若匹配,保留字母显示。
若不匹配,重置为空白。
template
bool match(const std::vector& table, std::vector* table_empty,
const int& answer, bool* first_time, int* old_answer,
int* memory_count) {
if ((*first_time) == true) {
return true;
}
// Search across the whole table and if the two values match, keep results,
// otherwise, hide 'em up.
for (int i = 0; i < table.size() + 1; i++) {
if (i == answer) {
if (table[i - 1] == table[(*old_answer) - 1]) {
(*first_time) = true;
(*memory_count) = 0;
(*old_answer) = 0;
return true;
}
else {
std::cout << "\nNo match (value was " << table[i - 1]
<< ", index is " << i << ").\n\n";
(*table_empty)[(*old_answer) - 1] = 0;
(*table_empty)[answer - 1] = 0;
(*first_time) = true;
(*memory_count) = 0;
(*old_answer) = 0;
return false;
}
}
}
return false;
}
assign_results
功能:更新游戏状态并判断胜负。
关键逻辑:
调用 match
检查匹配结果。
若所有字母匹配,提示胜利并询问是否重玩。
递归调用 assign_results
继续游戏。
template
void assign_results(std::vector* table_empty, std::vector* table,
int* answer, bool* first_time, int* old_answer,
int* memory_count) {
// Search through the entire table and if the answer matches the index, show
// the value. If it doesn't match, hide both the values. Don't forget to
// keep older values already answered.
for (int i = 0; i < (*table).size() + 1; i++) {
if (i == (*answer)) {
if (match((*table), table_empty, (*answer), first_time, old_answer,
memory_count) == true) {
(*table_empty)[i - 1] = (*table)[i - 1];
(*first_time) = true;
}
}
}
if ((*memory_count) == 1) {
(*first_time) = false;
(*memory_count) = 0;
}
char try_again = 'n';
// Has the user finished the game? Use a `for` loop, and if the table is
// full, ask the user if he wants to play again.
for (int i = 0; i < (*table).size() + 1; i++) {
if ((*table_empty)[i] == 0) {
break;
}
else if (i == (*table).size() - 1) {
print_table((*table));
std::cout << "\n\nYou won. Congratulations! Do you want to play "
"again? (y/n)\n";
std::cout
<< "Size " << (*table).size()
<< " will be used. This can be changed by re-running the game.";
std::cin >> try_again;
if (try_again == 'y') {
// This is needed when checking if the user has two matches
// already.
for (int i = 0; i < (*table_empty).size(); i++) {
(*table_empty)[i] = 0;
}
init(table);
}
else if (try_again == 'n') {
std::cout << "\nThanks for playing the game!\n";
SLEEP(3);
exit(0);
}
else {
std::cout << "\nInvalid input (exitting...).\n";
SLEEP(3);
exit(0);
}
}
}
// Ask data again.
ask_data((*table_empty), answer, old_answer, memory_count);
assign_results(table_empty, table, answer, first_time, old_answer,
memory_count);
}
} // namespace memory_game
} // namespace games
初始化:根据用户选择的难度生成随机字母对。
游戏循环:
打印当前游戏板。
用户选择两个位置。
检查是否匹配:
匹配则保留显示。
不匹配则隐藏。
胜利条件:所有字母匹配后询问是否重玩。