1.前言
三子棋游戏,想必大家都有玩过吧。在我们这里,还有一个响亮的名字——东风一条龙。玩法其实很简单,玩家双方轮流落子,当一方棋子连成一条线,即为胜出。那么这篇博客就来详细介绍一下如何用C语言实现一个三子棋盘游戏
2.模块化编程
当我们在写一些项目时,为了维护和管理会把不同模块的代码放在不同的.c源文件里。在.h文件里会写宏定义和函数的声明还有各种头文件的包含。然后再在各个.c源文件里包含我们这个头文件就行即#include"XXX.h"。使用模块化编程能够提高代码的可阅读性、可维护性和可移植性等
3.大致逻辑与思路
显示开始游戏界面,包含开始、退出还有选错反馈
初始化棋盘,利用二维数组模拟一个棋盘,对这些二维数组中的元素初始化
显示棋盘,对二维数组适当装饰,然后显示到屏幕上
双方下棋,玩家移动还有电脑移动
结束判断,结束有三种情况,玩家赢、输还有平局,还有一种状态就是双方继续下棋的状态
保存数据,记录总局数、获胜数还有平局数,计算出胜率,显示屏幕上,然后保存到文件
4.代码实现
①.开始游戏界面
1. 创建颜色函数(这个就是装饰函数,对游戏逻辑实现无影响)
int color(int c)//设置文字和背景颜色
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
return 0;
}
2.写一个菜单menu
void menu()
{
color(3);//蓝色
printf("+----------三子棋----------+\n");
printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n");
printf("|★★★★ 1.开始 0.退出 ★★★★|\n");
printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n");
printf("|* = 玩家 -------- # = 电脑|\n");
printf("+--------------------------+\n");
}
看一下效果:
3.为了防止用户没有正确选择,我们在主函数里面利用do...while循环+switch语句来进行循环判断
int input=0;
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出成功!\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
}while(input);
②. 初始化棋盘
1.先定义宏常量
#define ROW 3 #define COL 3
2.先创建一个二维数组
char board[ROW][COL]; InitBoard(board, ROW, COL);
3. 再写一个初始化函数,在主游戏函数中调用
//初始化棋盘 void InitBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row;i++) { for (j = 0; j < col; j++) { board[i][j] = ' '; } } }
③. 显示棋盘
棋盘显示成代码注释中的样子,二维数组每个元素的内容是空格
//显示棋盘 /* +-----+ | | | | +-----+ | | | | +-----+ | | | | +-----+ */ void ShowBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < 2 * row + 1; i++) { printf("\t"); for (j = 0; j < 2 * col + 1; j++) { if (i % 2 == 0) { if (j == 0 || j == 2 * col) { printf("+"); } else { printf("-"); } } else { if (j % 2 == 0) { printf("|"); } else { printf("%c", board[(i - 1) / 2][(j - 1) / 2]); } } } printf("\n"); } }
效果
④. 双方下棋
1.玩家移动,在这里我们利用九键与九宫格棋盘相对应,不需要输入坐标,这样更方便一些
先写一个对应函数turn_xy(int*x, int*y)。在这里要传变量的地址过来哦
void turn_sy(int n, int* x, int* y) { switch (n) { case 1: *x = 2; *y = 0; break; case 2: *x = 2; *y = 1; break; case 3: *x = 2; *y = 2; break; case 4: *x = 1; *y = 0; break; case 5: *x = 1; *y = 1; break; case 6: *x = 1; *y = 2; break; case 7: *x = 0; *y = 0; break; case 8: *x = 0; *y = 1; break; case 9: *x = 0; *y = 2; break; } }
然后再写一个玩家落子的函数,对玩家输入的数字先进行判断,如果数字合法再找到该数字对应的坐标
//玩家落子 void PlayerMove(char(*board)[COL], int row, int col) { int n = 0; int x = 0; int y = 0; printf("玩家落子:>"); while (1) { scanf("%d", &n); if (n > 0 && n < 10) { turn_sy(n, &x, &y); if (board[x][y] == ' ') { board[x][y] = '*'; break; } else { printf("该坐标已被占用,请重新落子\n"); } } else { printf("输入坐标非法,请重新输入\n"); } } }
2.电脑落子
写一个电脑下棋的函数,在这里我们让电脑随机下棋,用随机数函数产生两个0到2的随机数即为电脑下棋的坐标,如果下棋位子不合法,就重新生成随机数(只不过现在写得电脑比较弱智)
//电脑落子 void ComputerMove(char(*board)[COL], int row, int col) { while (1) { int x = rand() % 3; int y = rand() % 3; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
⑤. 结束判断
玩家和电脑每次下完棋后,都要检查棋盘的状态,棋盘有四种状态,我们用四种符号表示,'*'表示玩家胜、'#'表示电脑胜,'C'表示游戏继续,'Q'表示平局'。以此写出一个检查函数返回值为char类型
//判断棋盘状态(只针对3*3的棋盘) //返回*玩家赢了 //返回#电脑赢了 //返回C继续 //返回Q平局 char CheckBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; //判断行 for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ') { return board[i][0]; } } //判断列 for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[0][j] == board[2][j] && board[0][j] != ' ') { return board[0][j]; } } //判断左对角线 if (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != ' ') { return board[0][0]; } //判断右对角线 if (board[1][1] == board[0][2] && board[1][1] == board[2][0] && board[1][1] != ' ') { return board[1][1]; } //判断是否有空格 for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') { return 'C'; } } } return 'Q'; }
⑥. 保存数据
为了增加游戏的丰富性,我们也可以把每一把的结果记录下来,即总局数、获胜数、平局数,然后计算出胜率,显示到屏幕上,在这里我们就写main函数里边创建一个存放三个数的数组来存放总局数、获胜数、平局数。在每一把游戏开始前先载入之前的游戏数据,在游戏退出时,在保存游戏数据。但是在第一次开始游戏的时候我们要进行判断是否有游戏数据
这里我们就写两个函数
1.保存数据函数
//保存数据 void SaveGame(int* arr) { FILE* pf = fopen("data.txtx", "w"); if (pf == NULL) { perror("SaveGame->fopen"); return; } int i = 0; for (i = 0; i < 3; i++) { fwrite(arr + i, 4, 1, pf); } fclose(pf); pf = NULL; }
2.加载游戏数据
int LoadGame(int* arr) { FILE* pf = fopen("data.txtx", "r"); if (pf == NULL) { return 0; } int i = 0; for (i = 0; i < 3; i++) { fread(arr + i, 4, 1, pf); } fclose(pf); pf = NULL; return 1; }
⑦.游戏主函数
我们把以上六步写进一个游戏主函数中,利用while循环使“玩家落子、显示棋盘、棋盘状态判断和电脑落子、显示棋盘、棋盘状态判断”循环起来,直到结束
void game(int*arr) { char ret = 0; char board[ROW][COL]; InitBoard(board, ROW, COL); while (1) { ShowBoard(board, ROW, COL); PlayerMove(board, ROW, COL); ShowBoard(board, ROW, COL); ret = CheckBoard(board, ROW, COL); if (ret != 'C') { break; } ComputerMove(board, ROW, COL); ShowBoard(board, ROW, COL); ret=CheckBoard(board, ROW, COL); if (ret != 'C') { break; } } arr[0]++; if (ret == '*') { printf("你赢了!\n"); arr[1]++; } else if (ret == '#') { printf("你输了\n"); } else { printf("平局\n"); arr[2]++; } }
⑧.main函数
主函数里边调用srand函数产生随机数,创建一个存放总局数、获胜数、平局数这三个数的数组,还有利用do...while循环实现玩一把不过瘾还能接着玩的逻辑
int main() { srand((unsigned)time(NULL)); int input = 0; int arr[3] = { 0 }; int ret = LoadGame(arr); do { menu(); if (ret) printf("总局数:%d\t获胜数:%d\n平局数:%d\t胜率:%.2lf%%\n", arr[0], arr[1], arr[2], ((double)arr[1] * 100 / arr[0])); printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case 1: game(arr); break; case 0: SaveGame(arr); printf("退出成功!\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return 0; }
5.模块化代码实现
①.test.c
测试游戏的逻辑。
#include"game.h" int color(int c)//设置文字和背景颜色 { SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); return 0; } void menu() { color(3);//蓝色 printf("+----------三子棋----------+\n"); printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n"); printf("|★★★★ 1.开始 0.退出 ★★★★|\n"); printf("|★★★★★★★★★★★★★★★★★★★★★★★★★★|\n"); printf("|* = 玩家 -------- # = 电脑|\n"); printf("+--------------------------+\n"); } void game(int*arr) { char ret = 0; char board[ROW][COL]; InitBoard(board, ROW, COL); while (1) { ShowBoard(board, ROW, COL); PlayerMove(board, ROW, COL); ShowBoard(board, ROW, COL); ret = CheckBoard(board, ROW, COL); if (ret != 'C') { break; } ComputerMove(board, ROW, COL); ShowBoard(board, ROW, COL); ret=CheckBoard(board, ROW, COL); if (ret != 'C') { break; } } arr[0]++; if (ret == '*') { printf("你赢了!\n"); arr[1]++; } else if (ret == '#') { printf("你输了\n"); } else { printf("平局\n"); arr[2]++; } } int main() { srand((unsigned)time(NULL)); int input = 0; int arr[3] = { 0 }; int ret = LoadGame(arr); do { menu(); if (ret) printf("总局数:%d\t获胜数:%d\n平局数:%d\t胜率:%.2lf%%\n", arr[0], arr[1], arr[2], ((double)arr[1] * 100 / arr[0])); printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case 1: game(arr); break; case 0: SaveGame(arr); printf("退出成功!\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return 0; }
②.game.h
关于游戏包含的函数声明,符号声明头文件的包含以及宏定义。
#pragma once #include<stdio.h> #include<windows.h> #include<time.h> #define ROW 3 #define COL 3 //初始化棋盘 void InitBoard(char(*board)[COL], int row, int col); //显示棋盘 void ShowBoard(char(*board)[COL], int row, int col); //玩家落子 void PlayerMove(char(*board)[COL], int row, int col); //电脑落子 void ComputerMove(char(*board)[COL], int row, int col); //判断棋盘状态 char CheckBoard(char(*board)[COL], int row, int col); //保存数据 void SaveGame(int*arr); //加载数据 int LoadGame(int* arr);
③.game.c
游戏和相关函数实现
#include"game.h" //初始化棋盘 void InitBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row;i++) { for (j = 0; j < col; j++) { board[i][j] = ' '; } } } //显示棋盘 /* +-----+ | | | | +-----+ | | | | +-----+ | | | | +-----+ */ void ShowBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < 2 * row + 1; i++) { printf("\t"); for (j = 0; j < 2 * col + 1; j++) { if (i % 2 == 0) { if (j == 0 || j == 2 * col) { printf("+"); } else { printf("-"); } } else { if (j % 2 == 0) { printf("|"); } else { printf("%c", board[(i - 1) / 2][(j - 1) / 2]); } } } printf("\n"); } } void turn_sy(int n, int* x, int* y) { switch (n) { case 1: *x = 2; *y = 0; break; case 2: *x = 2; *y = 1; break; case 3: *x = 2; *y = 2; break; case 4: *x = 1; *y = 0; break; case 5: *x = 1; *y = 1; break; case 6: *x = 1; *y = 2; break; case 7: *x = 0; *y = 0; break; case 8: *x = 0; *y = 1; break; case 9: *x = 0; *y = 2; break; } } //玩家落子 void PlayerMove(char(*board)[COL], int row, int col) { int n = 0; int x = 0; int y = 0; printf("玩家落子:>"); while (1) { scanf("%d", &n); if (n > 0 && n < 10) { turn_sy(n, &x, &y); if (board[x][y] == ' ') { board[x][y] = '*'; break; } else { printf("该坐标已被占用,请重新落子\n"); } } else { printf("输入坐标非法,请重新输入\n"); } } } //电脑落子 void ComputerMove(char(*board)[COL], int row, int col) { while (1) { int x = rand() % 3; int y = rand() % 3; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } } //判断棋盘状态(只针对3*3的棋盘) //返回*玩家赢了 //返回#电脑赢了 //返回C继续 //返回Q平局 char CheckBoard(char(*board)[COL], int row, int col) { int i = 0; int j = 0; //判断行 for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ') { return board[i][0]; } } //判断列 for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[0][j] == board[2][j] && board[0][j] != ' ') { return board[0][j]; } } //判断左对角线 if (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != ' ') { return board[0][0]; } //判断右对角线 if (board[1][1] == board[0][2] && board[1][1] == board[2][0] && board[1][1] != ' ') { return board[1][1]; } //判断是否有空格 for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') { return 'C'; } } } return 'Q'; } //保存数据 void SaveGame(int* arr) { FILE* pf = fopen("data.txtx", "w"); if (pf == NULL) { perror("SaveGame->fopen"); return; } int i = 0; for (i = 0; i < 3; i++) { fwrite(arr + i, 4, 1, pf); } fclose(pf); pf = NULL; } int LoadGame(int* arr) { FILE* pf = fopen("data.txtx", "r"); if (pf == NULL) { return 0; } int i = 0; for (i = 0; i < 3; i++) { fread(arr + i, 4, 1, pf); } fclose(pf); pf = NULL; return 1; }