编程环境:windows10、c++17、clion但是设置为在外部控制台(其实就是windows的cmd)中输出。
说是c++,其实基本上全是c语言的语法。
不多说,直接上代码。
源代码:
#include <iostream>
#include "windows.h"
#include "conio.h"
//======================================================================================================================
// 定义颜色代码
enum ConsoleColor {
BLACK = 0,
BLUE = 1,
GREEN = 2,
CYAN = 3,
RED = 4,
MAGENTA = 5,
BROWN = 6,
LIGHT_GRAY = 7,
DARK_GRAY = 8,
LIGHT_BLUE = 9,
LIGHT_GREEN = 10,
LIGHT_CYAN = 11,
LIGHT_RED = 12,
LIGHT_MAGENTA = 13,
YELLOW = 14,
WHITE = 15
};
int board[4][4]; //存放十六个格子中每一个元素,零代表空格
int score; //计算当前得分
struct cursor_position{
//这个结构体用来记录光标的位置
short x;
short y;
};
cursor_position cp;
//======================================================================================================================
void print_board(); //打印棋盘
void print_number(); //打印数字
void set_cursor_position(); //设置当前光标的位置
void set_console_color(ConsoleColor color);// 改变控制台文本颜色的函数
void initial_game(); //初始化游戏
void running_game(); //游戏运行的主体函数
bool is_success(); //判断是否赢得游戏
void game_over(); //游戏结束
void set_cursor_invisible(); //将控制台的光标设置为不可见
void print_score(); //在棋盘右下角打印得分
//======================================================================================================================
int main() {
initial_game();
running_game();
system("pause");
return 0;
}
void print_board(){
printf("+-------+-------+-------+-------+\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("+-------+-------+-------+-------+\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("+-------+-------+-------+-------+\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("+-------+-------+-------+-------+\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("|\t|\t|\t|\t|\n");
printf("+-------+-------+-------+-------+\n");
}
void print_number(){
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 4; ++j){
if(board[i][j] == 0){
//如果该位置数字为零,则输出\t,表示空着
cp.x = i * 4 + 2;
cp.y = j * 8 + 2;
set_cursor_position();
printf("\t");
continue;
}
// x 是行,y 是列
cp.x = i * 4 + 2;
cp.y = j * 8 + 2;
set_cursor_position();
printf("\t");
set_cursor_position();
set_console_color(GREEN);
printf("%d", board[i][j]);
set_console_color(WHITE);
}
}
}
void set_cursor_position(){
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE) {
std::cerr << "Error getting console output handle!\n";
exit(-1);
}
// 设置光标位置为(x, y),注意这里的坐标是从0开始的
COORD coord = {cp.y , cp.x}; // 列(y)和行(x)
SetConsoleCursorPosition(hOut, coord);
}
void set_console_color(ConsoleColor color) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, color);
}
void initial_game(){
score = 0; //将得分置为零
set_cursor_invisible(); //将光标设置为不可见
memset(board, 0, sizeof(board));//将所有数字置为零
//将随机两个格子设置为2
srand(unsigned(time(nullptr)));
int r = rand() % 4, c = rand() % 4;
board[r][c] = 2;
r = rand() % 4, c = rand() % 4;
board[r][c] = 2;
print_board();//打印棋盘
print_number();//打印数字
}
void running_game(){
while(true){
if(is_success()){
//判断玩家是否已经取胜
cp.x = 20;
cp.y = 22;
set_cursor_position();
set_console_color(RED);
printf("游戏胜利!\n\t\t\t");
system("pause");
}
bool is_full = true; //判断格子是否满了
//===========================接下来判断格子是否满了================================
//如果所有数字都不为零,那么说明格子满了
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 4; ++j){
if(board[i][j] == 0){
is_full = false;
}
}
}
bool can_move_left = false; //判断是否可以向左移动
bool can_move_right = false; //判断是否可以向右移动
bool can_move_up = false; //判断是否可以向上移动
bool can_move_down = false; //判断是否可以向下移动
//===========================接下来判断是否可以向下移动================================
/* 例子:
* 0 4 0 2
* 0 0 4 4
* 8 4 2 4
* 4 16 8 16
* 怎么判断是否可以向下移动?
* 第1列(j == 1),board[0][1] == 4,而board[1][1] == 0,board[0][1]下面是零,所以第1列可以向下移动
* 第3列(j == 3),board[1][3] == 4,而board[2][3] == 4,board[1][3]下面的数字和他相同,所以第3列可以向下移动
* 所有得出判断依据:只要某个数下面的数为零,或者它下面的数和它相等,那么就可以移动
*/
for(int j = 0; j < 4; ++j){
int row = -1;//临时存放某个数字所在行数,row = -1表示未存放
for(int i = 0; i < 4; ++i){
if(board[i][j] == 0){
if(row == -1){
//如果board[i][j] == 0 且 row == -1,那么continue即可
continue;
}else{
//如果board[i][j] == 0 且 row 不为 -1,那么对应了“某个数字下面是零”的情况,说明可以移动
can_move_down = true;
break;
}
}else{
if(row == -1){
//如果board[i][j] != 0 且 row == -1,那么将 row 为 i 即可
row = i;
}else{
//如果board[i][j] != 0 且 row != -1,那么需要判断board[i][j] 和 board[row][j]是否相等
if(board[i][j] == board[row][j]){
//如果它们相等,那么对应了“某个数字下面的数和它相等”的情况,说明可以移动
can_move_down = true;
break;
}else{
//如果它们不等,那么不能移动,将 row 为 i 即可
row = i;
}
}
}
}
}
//===========================接下来判断是否可以向上移动================================
for(int j = 0; j < 4; ++j){
int row = -1;
for(int i = 3; i >= 0; --i){
if(board[i][j] == 0){
if(row == -1){
continue;
}else{
can_move_up = true;
break;
}
}else{
if(row == -1){
row = i;
}else{
if(board[i][j] == board[row][j]){
can_move_up = true;
break;
}else{
row = i;
}
}
}
}
}
//===========================接下来判断是否可以向左移动================================
for(int i = 0; i < 4; ++i){
int col = -1;
for(int j = 3; j >= 0; --j){
if(board[i][j] == 0){
if(col == -1){
continue;
}else{
can_move_left = true;
break;
}
}else{
if(col == -1){
col = j;
}else{
if(board[i][j] == board[i][col]){
can_move_left = true;
break;
}else{
col = j;
}
}
}
}
}
//===========================接下来判断是否可以向右移动================================
for(int i = 0; i < 4; ++i){
int col = -1;
for(int j = 0; j < 4; ++j){
if(board[i][j] == 0){
if(col == -1){
continue;
}else{
can_move_right = true;
break;
}
}else{
if(col == -1){
col = j;
}else{
if(board[i][j] == board[i][col]){
can_move_right = true;
break;
}else{
col = j;
}
}
}
}
}
//===========================接下来判断是否游戏结束================================
int operator_char = getch(); //操作符:w代表向上,a代表左,s代表下,d代表右
if(is_full && (!can_move_up && !can_move_down && !can_move_left && !can_move_right)){
//如果格子满了,而且上下左右都不能移动,那说明游戏结束
game_over();
}else if(!is_full && !can_move_up && !can_move_down && !can_move_left && !can_move_right) {
//如果格子未满,而且上下左右都不能移动,那用户无论输入哪个方向键都无所谓,直接随机一个空格子将数字置为2即可
//我知道用goto语句不好,这里为了偷懒将就用了
if(operator_char == 'w' || operator_char == 'W' || operator_char == 'a' || operator_char == 'A' || operator_char == 's' || operator_char == 'S' || operator_char == 'd' || operator_char == 'D'){
goto loop;
}
continue;
}
//只要上下左右有一个方向可以移动就无所谓,继续执行后面语句即可
if(operator_char == 's' || operator_char == 'S'){
if(can_move_down){
//如果可以向下移动,那直接移动即可
for(int j = 0; j < 4; ++j){
int row = -1;//临时存放某个数字所在行数,row = -1表示未存放
for(int i = 0; i < 4; ++i){
if(board[i][j] == 0){
if(row == -1){
//如果board[i][j] == 0 且 row == -1,那么continue即可
continue;
}else{
//如果board[i][j] == 0 且 row 不为 -1,那么对应了“某个数字下面是零”的情况,说明可以移动,将board[row][j]赋值给board[i][j]并归零
board[i][j] = board[row][j];
board[row][j] = 0;
row = i; //为什么把 i 赋值给 row ?
//比如某一列从下到上依次为 2 0 2 4,如果2上面是零就变成 0 2 2 4,下一次循环就可以把 2 和 2 合并了
}
}else{
if(row == -1){
//如果board[i][j] != 0 且 row == -1,那么将 row 为 i 即可
row = i;
}else{
//如果board[i][j] != 0 且 row != -1,那么需要判断board[i][j] 和 board[row][j]是否相等
if(board[i][j] == board[row][j]){
//如果它们相等,那么对应了“某个数字下面的数和它相等”的情况,说明可以移动,将两数相加并归零board[row][j]
board[i][j] *= 2;
board[row][j] = 0;
score += board[i][j]; //合并成功了,就把合并后的数字算作得分加上去
row = -1; //为什么把row置为-1?
//比如某一列从下到上依次为 2 0 2 4,
//先把 2 和 0 交换变成 0 2 2 4
//将 2 和 2 合并后得到 0 0 4 4
//但是前面那个 4 就是合并之后得到的,不能拿去和最后一个 4 合并了,将row = -1避免这种情况
}else{
//如果它们不等,那么不能移动,将 row 为 i 即可
row = i;
}
}
}
}
}
/* 以上代码是我第一遍写的,其实是错的
* 例如:
* 2 4 4 8
* 2 0 2 4
* 4 0 2 2
* 4 4 0 2
* 使用这个算法向下移动会变成:
* 0 0 4 8
* 4 0 0 4
* 0 0 0 0
* 8 8 4 4
* 可是实际应该是:
* 0 0 0 0
* 0 0 0 8
* 4 0 4 4
* 8 8 4 4
* 可以发现这个算法虽然把该合并的东西合并了,但是某些该移动的东西没有动
* 所以得把这个算法后面加一个补丁:想办法让下面有零的数字“往下落”
* 即:依次按列-行遍历,如果遇到零,就将它和它上面的数字不停地交换位置,直到它上面为零或者它位于第零行为止(类似于冒泡排序)
*
*/
for(int j = 0; j < 4; ++j){
for(int i = 0; i < 4; ++i){
if(board[i][j] == 0){
int k = i;
while(k > 0){
if(board[k - 1][j] == 0){
//如果它上面的数为零,那么不需要交换了,跳出本次循环
break;
}else{
//否则将它和它上面的数字不停地交换位置
board[k][j] = board[k - 1][j];
board[k - 1][j] = 0;
}
k--;
}
}
}
}
}else{
//如果不能向下移动,那按s键没用,直接跳出本次循环
continue;
}
}else if(operator_char == 'a' || operator_char == 'A'){
if(can_move_left){
for(int i = 0; i < 4; ++i){
int col = -1;
for(int j = 3; j >= 0; --j){
if(board[i][j] == 0){
if(col == -1){
continue;
}else{
board[i][j] = board[i][col];
board[i][col] = 0;
col = j;
}
}else{
if(col == -1){
col = j;
}else{
if(board[i][j] == board[i][col]){
board[i][j] *= 2;
board[i][col] = 0;
score += board[i][j]; //合并成功了,就把合并后的数字算作得分加上去
col = -1;
}else{
col = j;
}
}
}
}
}
for(int i = 0; i < 4; ++i){
for(int j = 3; j >= 0; --j){
if(board[i][j] == 0){
int k = j;
while(k < 3){
if(board[i][k + 1] == 0){
break;
}else{
board[i][k] = board[i][k + 1];
board[i][k + 1] = 0;
}
k++;
}
}
}
}
}else{
continue;
}
}else if(operator_char == 'w' || operator_char == 'W'){
if(can_move_up){
for(int j = 0; j < 4; ++j){
int row = -1;
for(int i = 3; i >= 0; --i){
if(board[i][j] == 0){
if(row == -1){
continue;
}else{
board[i][j] = board[row][j];
board[row][j] = 0;
row = i;
}
}else{
if(row == -1){
row = i;
}else{
if(board[i][j] == board[row][j]){
board[i][j] *= 2;
board[row][j] = 0;
score += board[i][j]; //合并成功了,就把合并后的数字算作得分加上去
row = -1;
}else{
row = i;
}
}
}
}
}
for(int j = 0; j < 4; ++j){
for(int i = 3; i >= 0; --i){
if(board[i][j] == 0){
int k = i;
while(k < 3){
if(board[k + 1][j] == 0){
//如果它下面的数为零,那么不需要交换了,跳出本次循环
break;
}else{
//否则将它和它下面的数字不停地交换位置
board[k][j] = board[k + 1][j];
board[k + 1][j] = 0;
}
k++;
}
}
}
}
}else{
continue;
}
}else if(operator_char == 'd' || operator_char == 'D'){
if(can_move_right){
for(int i = 0; i < 4; ++i){
int col = -1;
for(int j = 0; j < 4; ++j){
if(board[i][j] == 0){
if(col == -1){
continue;
}else{
board[i][j] = board[i][col];
board[i][col] = 0;
col = j;
}
}else{
if(col == -1){
col = j;
}else{
if(board[i][j] == board[i][col]){
board[i][j] *= 2;
board[i][col] = 0;
score += board[i][j]; //合并成功了,就把合并后的数字算作得分加上去
col = -1;
}else{
col = j;
}
}
}
}
}
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 4; ++j){
if(board[i][j] == 0){
int k = j;
while(k > 0){
if(board[i][k - 1] == 0){
break;
}else{
board[i][k] = board[i][k - 1];
board[i][k - 1] = 0;
}
k--;
}
}
}
}
}else{
continue;
}
}else{
//如果按的不是上下左右,那么跳出本次循环
continue;
}
loop:
//每次移动棋盘之后,一定有空位。因为能够移动就说明合并了某些格子,说明一定有空的格子
// //随机一个空格子将数字置为2
// while(true){
// int r = rand() % 4, c = rand() % 4;
// if(board[r][c] == 0){
// board[r][c] = 2;
// break;
// }
// }
//一开始我是随机把一个空格子置为2,后来发现这样难度有点高,那就改一下,空格子2/3概率出现2, 1/3概率出现4
int value = (rand()%2 == 0)?4:2;
while(true){
int r = rand() % 4, c = rand() % 4;
if(board[r][c] == 0){
board[r][c] = value;
break;
}
}
print_number(); //打印数字
print_score(); //打印得分
}
}
bool is_success() {
//如果存在一个数字的值为2048,那么说明游戏获胜
for(int i = 0; i < 4; ++i){
for(int j = 0; j < 4; ++j){
if(board[i][j] == 2048){
return true;
}
}
}
return false;
}
void game_over(){
cp.x = 20;
cp.y = 14;
set_cursor_position();
set_console_color(RED);
printf("无法移动,游戏结束!得分:%d\n\n\t\t", score);
system("pause");
}
void set_cursor_invisible(){
CONSOLE_CURSOR_INFO cursor;
cursor.bVisible = 0;
cursor.dwSize = 1;
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(hOut, &cursor);
}
void print_score(){
cp.x = 18;
cp.y = 20;
set_cursor_position();
set_console_color( BLUE);
printf("当前得分:%d\n\t\t\t", score);
}
代码逻辑就不讲了,我认为注释已经讲的比较清楚了。