N子棋(外加双人对战)详解!推荐!!!

news/2025/3/8 6:56:54/

文章目录

  • 准备工作
    • 创建菜单
      • 进入游戏
        • 初始化棋盘、打印棋盘
          • 玩家下棋、电脑下棋、生成随机数
            • 判断输赢

大家好!时隔多天,我终于写博客了,真的是开心!这一次带来的是N子棋有双人对战和单人下棋,请认真看下去,我会竭尽所能讲清楚整个思路!如果还有疑问的话,我们可以进行探讨!
首先给大家展示代码:

这是test.c:

#define _CRT_SECURE_NO_WARNINGS#include"game.h"void game()
{char board[ROW][COL];char ret = 0;init_board(board, ROW, COL);print_board(board, ROW, COL);while (1){player1_move(board, ROW, COL);print_board(board, ROW, COL);ret = is_win(board, ROW, COL);if (ret != 'C'){break;}computer_move(board, ROW, COL);print_board(board, ROW, COL);ret = is_win(board, ROW, COL);if (ret != 'C'){break;}}if (ret == '*')printf("玩家1赢了!\n");else if (ret == '#')printf("电脑赢了!\n");else if (ret == 'P')printf("平局!\n");
}void menu()
{printf("    1. play    \n");printf("    0. exit    \n");printf("    please enter:");
}void test()
{	srand((unsigned int) time(NULL));int i = 0;do{menu();scanf("%d", &i);switch (i){case 1:game();break;case 0:printf("    quit game\n");break;default:printf("    please enter again!\n");break;}} while (i);
}int main()
{test();return 0;
}

这是game.c:

#define _CRT_SECURE_NO_WARNINGS#include"game.h"void init_board(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){board[i][j] = ' ';}}
}void print_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);if (j < col - 1)printf("|");}printf("\n");if (i < row - 1){for (j = 0; j < col; j++){printf("---");if (j < col - 1)printf("|");}}printf("\n");}
}void player1_move(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1){printf("玩家1请下棋:");scanf("%d %d", &x, &y);if (x >= 1 && x - 1 < row && y >= 1 && y - 1 < col){if (board[x - 1][y - 1] == ' '){board[x - 1][y - 1] = '*';break;}else{printf("已被占用!");}}else{printf("非法输入!");}}
}void computer_move(char board[ROW][COL], int row, int col)
{int x = rand() % 3;int y = rand() % 3;while (1){printf("电脑请下棋:");scanf("%d %d", &x, &y);if (board[x][y] == ' '){board[x][y] = '#';break;}
}static int  is_full(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' ')return 0;}}return 1;
}char is_win(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[i][j - 1];}for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[j - 1][i];}int flag = 0;for (i = 1; i < row; i++){if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')flag++;if (flag == KEY - 1)return board[i][i];}flag = 0;int j = col - 1;for (i = 1; i < row; i++){if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' '){flag++;j--;}if (flag == KEY - 1)return board[i][0];}int ret = is_full(board, ROW, COL);if (ret)return 'P';return 'C';
}

这是game.h:

#pragma once#include<stdio.h>
#include<stdlib.h>
#include<time.h>#define ROW 10
#define COL 10#define KEY 5void init_board(char board[ROW][COL], int row, int col);void print_board(char board[ROW][COL], int row, int col);void player1_move(char board[ROW][COL], int row, int col);void computer_move(char board[ROW][COL], int row, int col);char is_win(char board[ROW][COL], int row, int col);

准备工作

在进行设计一个游戏前,我们要思考游戏分为什么部分 ,根据模块逐个进行编程,并且写完一部分就要进行调试,看是否出错!

我们可以从简单入手,先设计一个三子棋,然后再进行扩展。

先创建文件,创建一个test.c 文件作为主函数的部分,然后创建游戏文件game.c 文件作为游戏主体的部分,最后创建一个game.h 文件作为声明函数的文件,如图:
注意:这里可以将<stdio.h>头文件放在game.h中,然后再test.c 和game.c 中引用game.h 文件,#include"stdio.h"。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

创建菜单

当创建完文件之后,就可以进行写代码了。首先进入一个游戏,映入眼帘的是菜单,玩还是不玩?所以先可以创建菜单了。

void menu()//菜单说明
{printf("    1. play    \n");printf("    0. exit    \n");printf("    please enter:");
}void test()
{	int i = 0;//进行选择do{menu();//进行打印菜单scanf("%d", &i);switch (i){case 1://选择1进行游戏game();//游戏主体部分break;case 0://选择0退出游戏printf("    quit game\n");break;default://选择其它数字就进行报错printf("    please enter again!\n");break;}} while (i);//为0跳出循环,退出游戏
}int main()
{test();//测试部分return 0;
}
//这里用到do while 语句很合适,进入游戏,不管你玩不玩,游戏界面是一定
//要显示的,体现了do while 语句的先执行一次的特点!

进入游戏

那么创建完菜单之后,进行选择,选择1进入游戏。接下来进行游戏主体部分,我们把游戏中的各种函数定义放在game.c 中,放在test.c 中会显得臃肿,函数的声明放在game.h 中。

void game()//游戏主体
{
//那么创建一个棋盘变量。棋盘分为行列,所以我们可以用二维数组来作为棋盘,
//ROW代表行,COL代表列,我们进行宏定义ROW为3,COL为3,如下图:char board[ROW][COL];
}

宏定义ROW ,COL 为3,这样宏定义很方便,想要改变棋盘的大小,只要改变这里就行了,其它不用改变!在这里插入图片描述

初始化棋盘、打印棋盘

当我们进入游戏的时候,我们看到应该是棋盘,并且没有下棋的棋盘.如下图:

void game()//游戏主体
{//在这里调用函数,在game.c 中定义函数.char board[ROW][COL];//棋盘大小,ROW为行,COL为列init_board(board, ROW, COL);//初始化棋盘print_board(board, ROW, COL);//打印棋盘
}

我们要思考:如何设计一个棋盘?如何去初始化棋盘,让棋盘每一个位置都是空格?下图是一个模板:
在这里插入图片描述

初始化棋盘:我们直接用双层循环进行每个元素赋值为空格就行,如下图:

//记得要在game.h中声明该函数!
void init_board(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){board[i][j] = ' ';//每个元素赋予空格}}
}

打印棋盘:我们可以将这个图案分为两个部分:

在这里插入图片描述

我们可以观察:无横线的部分中空格是有三个空格,因为下面有三个横线,那我们可以写双重循环,内层循环写无横线的部分,循环三列,外层循环写三行.如下图:

void print_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);//打印三个部分,中间放字符}printf("\n");}
}

这样打印结果就是如下:
在这里插入图片描述

第一个问题:我们可以看到在第三列不需要竖杠,只需要打印小于COL - 1 的列数,所以用一个if语句来执行,如果小于COL就打印竖杠,否则不打印!***
第二个问题:我们看到缺少了横线与竖杠,所以我们可以在内层循环结束后,进行打印横线与竖杠,但是最后一行不需要打印,所以要进行判断行小于ROW - 1 。***
所以解决途径如下:

void print_board(char board[ROW][COL], int row, int col){int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf(" %c ", board[i][j]);//打印三个部分,中间放字符if (j < col - 1)//不打印最后一个竖杠printf("|");//打印竖杠,成为棋盘的一部分呢}printf("\n");if (i < row - 1)//到了最后一行就不打印横线{for (j = 0; j < col; j++)//循环3次,打印竖杠{printf("---");if (j < col - 1)printf("|");}}printf("\n");}
}

那么初始化棋盘,打印棋盘就完成了。

玩家下棋、电脑下棋、生成随机数

既然棋盘已经做完了,那么接下来就是下棋了。下棋分为玩家和电脑下棋。

玩家下棋:作为一个玩家,我们可以输入坐标在棋盘上显示符号,玩家输入坐标是大于等于1的,所以存入数组的时候要进行减一。

规则:输入坐标。首先判断坐标是否合法,什么是合法?合法就是要在棋盘的大小范围之内!然后判断坐标的位置是否为空格?意思就是这个坐标不能有棋子,有棋子就代表被占用了,不能下棋!这两个规则遵守了,就可以进行设计了!如下图:

void player1_move(char board[ROW][COL], int row, int col)
{int x = 0;int y = 0;while (1)//只有坐标合法并且正确才能跳出循环,否则继续输入坐标{printf("玩家1请下棋:");scanf("%d %d", &x, &y);//坐标一定要大于等于1if (x >= 1 && x - 1 < row && y >= 1 && y - 1 < col)//坐标合法的条件{if (board[x - 1][y - 1] == ' ')//坐标减一才能对应数组中的元素{board[x - 1][y - 1] = '*';break;}else{printf("已被占用!");//不是空格就说明}}else{printf("非法输入!");//坐标不合法就说明}}
}

电脑下棋:电脑下棋不需要那么麻烦,所以只需要生成的随机数作为坐标,然后判断是否被占用,不被占用就可以赋予符号并且跳出循环!

生成随机数:我之前在写猜数字游戏的时候讲了随机数的一些知识,不了解可以去看看,这是链接http://t.csdn.cn/IgYyi。把随机数当成坐标,但是随机数很随机,很可能不合法,所以我们可以%3,那么得到的结果只能小于3,就可以作为随机数了。

void computer_move(char board[ROW][COL], int row, int col)
{int x = rand() % 3;//srand可以放在void test()中,因为只需要生成一次就可以了!int y = rand() % 3;//记得引用头文件#include<stdlib.h>,放在game.h中。while (1{printf("电脑请下棋:";if (board[x][y] == ' '){board[x][y] = '#';break;}}

玩家下一个棋,是不是要打印一下棋盘,因为不打印棋盘,就不知道下了什么棋,所以要打印棋盘;电脑也是如此,如果电脑下完不显示,玩家就不能根据形势来下棋获得胜利了。

void game()//游戏主体
{char board[ROW][COL];//棋盘大小,ROW为行,COL为列char ret = 0;init_board(board, ROW, COL);//初始化棋盘print_board(board, ROW, COL);//打印棋盘while (1)//这里用到for循环是因为玩家和电脑下棋,总不可能下一次棋就结束了吧!//待会会讲到如何跳出循环{player1_move(board, ROW, COL);//玩家1下棋print_board(board, ROW, COL);computer_move(board, ROW, COL);//电脑下棋print_board(board, ROW, COL);}
}

当然这个电脑是有点人工智障的,它不会赌棋,让电脑赢反而成为了一种难度。

判断输赢

来到了我们的结束阶段了!
有三种情况:赢、平局、游戏继续。所以我们可以写一个判断输赢的函数,返回值为字符型。

返回值为C,则游戏继续;返回值为*,则跳出循环,并输出玩家赢了;返回值为#,则跳出循环,并输出电脑赢了;返回值为P,则跳出循环,并输出平局。

void game()//游戏主体
{char board[ROW][COL];//棋盘大小,ROW为行,COL为列char ret = 0;init_board(board, ROW, COL);print_board(board, ROW, COL);while (1){player1_move(board, ROW, COL);//玩家1下棋print_board(board, ROW, COL);ret = is_win(board, ROW, COL);//判断是否赢了的函数if (ret != 'C')//返回值只要不是C,就要跳出循环,游戏结束。{break;}computer_move(board, ROW, COL);print_board(board, ROW, COL);ret = is_win(board, ROW, COL);//不管是玩家还是电脑都要判断赢了没有,必须出现在//一个角色下了棋之后进行判断if (ret != 'C'){break;}}if (ret == '*')printf("玩家1赢了!\n");else if (ret == '#')printf("电脑赢了!\n");else if (ret == 'P')printf("平局!\n");
}

赢有四种方式:行、列、对角线、另一条对角线

第一种:行的判断!我们只需要判断同一行的每个元素相同就行了,当然不能等于空格!所以如下:

char is_win(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){//连续两个数相等并且不等于空格if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')flag++;//作为标志if (flag == KEY - 1)//KEY代表玩什么棋break;}if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次return board[i][j - 1];}
}

第二种:列的判断!列的判断类似于行的判断。列不变化,行在变化。如下:

char is_win(char board[ROW][COL], int row, int col)
{//行的判断int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次return board[i][j - 1];}//列的判断for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[j - 1][i];}

第三种:对角线!对角线是有规律的,其中一个是坐标相同,另一个是行列变化1。
先讲一个对角线:如下图:

char is_win(char board[ROW][COL], int row, int col)
{   //行的判断int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次return board[i][j - 1];}//列的判断for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[j - 1][i];}//对角线的判断int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!for (i = 1; i < row; i++){//flag在里面的话,会重新初始化为0,最大为1,不会返回符号,就没有//这个对角线很方便,横纵坐标都相同。if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')flag++;if (flag == KEY - 1)return board[i][i];}

第四种:另一种对角线!另一种对角线有两种方式:1. 右上到左下。2. 左下到右上。
我是第一种方式,如下:

char is_win(char board[ROW][COL], int row, int col)
{	//行的判断int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次return board[i][j - 1];}//列的判断for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[j - 1][i];}//对角线的判断int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!for (i = 1; i < row; i++){if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')flag++;if (flag == KEY - 1)return board[i][i];}//另一个对角线flag = 0;//这个跟上面的一样int j = col - 1;//j如果放在里面的话就发生其它位置的元素对比,不符合对角线原则for (i = 1; i < row; i++){//j如果放在里面的话,进行第二次循环时,i = 2, j - 1 = 1,坐标2 1不符合对角线原则if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' '){flag++;j--;}if (flag == KEY - 1)return board[i][0];}

赢的方式判断完了,就要判断平局了,我们把这函数就放在is_win函数中,因为这个平局函数就是为is_win函数服务的,所以不用在game.h中声明了。

static int  is_full(char board[ROW][COL], int row, int col)
{//赋予static意思是只能在game.c 文件中看到,其它文件看不到,这个函数只是为is_win函数服务的,//所以没必要在game.h中进行声明。int i = 0;//这个函数返回值为整型,只要有一个为空格,就返回0,在is_win函数中设计一个if语句//如果is_full函数返回值为真,就返回P,否则不执行。for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){if (board[i][j] == ' ')return 0;}}return 1;
}
char is_win(char board[ROW][COL], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[i][j - 1] == board[i][j] && board[i][j - 1] != ' ')//flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)//假如为三子棋,flag只要为2就可以赢了!,三个连成一线要判断两次return board[i][j - 1];}for (i = 0; i < row; i++){int j = 0;int flag = 0;for (j = 1; j < col; j++){if (board[j][i] == board[j - 1][i] && board[j - 1][i] != ' ')flag++;if (flag == KEY - 1)break;}if (flag == KEY - 1)return board[j - 1][i];}int flag = 0;//这个flag放在里面就会重新定义为0,这样flag最大只能为1了,不能为2!for (i = 1; i < row; i++){if (board[i - 1][i - 1] == board[i][i] && board[i - 1][i - 1] != ' ')flag++;if (flag == KEY - 1)return board[i][i];}flag = 0;//这个flag也是一样。int j = col - 1;//j如果放在里面的话就发生其它位置的元素对比,不符合对角线原则for (i = 1; i < row; i++){if (board[i][j - 1] == board[i - 1][j] && board[i - 1][j] != ' '){flag++;j--;}if (flag == KEY - 1)return board[i][0];}int ret = is_full(board, ROW, COL);//判断平局函数if (ret)return 'P';//为真就返回P,平局return 'C';//上面情况都不符合,那么游戏只能继续
}

好了,这个N子棋就讲完了,我们可以在game.h中改变ROW,COL的大小,进而改变棋盘的大小;改变KEY的大小,进而决定玩什么棋,都由你决定!

双人对战只需要把电脑下棋的代码换成玩家的代码即可,然后改变一些名称,就大工完成了。

如果这个文章对你有所帮助的话,请你点个赞,谢谢!!!
^ _ ^

位卑未敢忘忧国,事定犹须待阖棺。—陆游《病起书怀》

上善若水

文章来源:https://blog.csdn.net/qq_73614724/article/details/128230232
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ppmy.cn/news/2620.html

相关文章

nginx代理https妈妈级手册

目录 背景说明 相关地址 https证书生成 nginx安装及配置 结果展示​编辑 背景说明 为了保证传输加密、访问安全&#xff0c;我们采用nginx服务器将http服务代理为https。所需材料&#xff1a;openssl&#xff08;用来生成证书&#xff09;、http服务、nginx自身。 相关地址…

给你一张n*m的西湖地图二值图,其中西湖的轮廓用1表示,轮廓内核轮廓外均用0表示。现在请你统计西湖的面积,即轮廓内0的个数。

给你一张n*m的西湖地图二值图&#xff0c;其中西湖的轮廓用1表示&#xff0c;轮廓内核轮廓外均用0表示。 现在请你统计西湖的面积&#xff0c;即轮廓内0的个数。 解析&#xff1a; #include <iostream> #include <string> #include <queue> #include &…

《web课程设计》基于HTML+CSS+JavaScript典的中医药大学网(11个页面)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

移动WEB开发之rem布局--苏宁首页案例制作(flexible.js)

简洁高效的rem适配方案flexible.js 手机淘宝团队出的简洁高效 移动端适配库 我们再也不需要在写不同屏幕的媒体查询&#xff0c;因为里面js做了处理 它的原理是把当前设备划分为10等份&#xff0c;但是不同设备下&#xff0c;比例还是一致的。 我们要做的&#xff0c;就是确…

全新社交电商模式来袭,消费增值结合共享经济完成消费升级

大家好&#xff0c;我是林工&#xff0c;不知道大家是否了解消费增值&#xff1f;这是一个消费储量为基础的理念&#xff0c;体现的是消费者的消费与回报问题&#xff0c;普遍的消费返利&#xff0c;消费全返渐渐地已经不能够满足目前的客户&#xff0c;也就有了一个满足与这部…

HBase的数据模型和存储原理

HBase的数据模型 HBase中表的逻辑结构 Name Space&#xff08;命名空间&#xff09; 类似于关系型数据库的 DatabBase 概念&#xff0c;每个命名空间下有多个表。HBase有两个自带的命名空间&#xff0c;分别是 hbase 和 default&#xff0c;hbase 中存放的是 HBase 内置的表&a…

MySQL进阶篇(二) - 索引

一、索引概述&#xff08;P66&#xff09; 1. 介绍 索引&#xff08;index&#xff09;是帮助 MySQL 高效获取数据的数据结构&#xff08;有序&#xff09;。 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&…

函数传参、返回值、作用域、匿名函数、简易时钟

一、为什么需要函数 函数&#xff1a;function&#xff0c;是被设计为执行特定任务的代码块。代码复用 二、函数使用 1. 函数的声明语法 function 函数名() {函数体}2. 函数名命名规范 和变量名基本一致 尽量小驼峰命名法 前缀应该为动词 命名建议&#xff1a;常用动词约…