数组的应用——三子棋、五子棋
来源:互联网 发布:excel 一列数据递增 编辑:程序博客网 时间:2024/06/05 20:44
- 三子棋
- gameh
- mainc
- gamec
- 五子棋
- gameh
- mainc
- gamec
三子棋
1.在game.h中进行宏定义,头文件引入以及函数声明
2.在main.c中实现主函数
3.在game.c中实现功能函数
game.h
#ifndef __GAME_H_#define __GAME_H_#define _CRT_SECURE_NO_WARNINGS 1#define ROW 3#define COL 3#define CHESS 3 //三子棋#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>typedef struct Location{ int x; int y;}Location;void Menu();void Play();void InitBoard(char board[ROW][COL], int row,int col);void PrintBoard(char board[ROW][COL], int row, int col);Location PlayerMove(char board[ROW][COL], int row, int col);Location ComputerMove(char board[ROW][COL], int row, int col);char CheckWin(char board[ROW][COL], int row, int col);#endif // !__GAME_H_
main.c
#include"game.h"int main(){ Menu(); return 0;}
接下来是最关键的部分
game.c
#include"game.h"void PrintComputer();void PrintPeople();void Welcome();void Menu(){ int n = 0; srand((unsigned int)time(NULL)); do { Welcome(); printf("请选择:>"); scanf("%d", &n); switch (n) { case 1:Play(); break; case 0:printf("\n退出游戏\n\n"); break; default: printf("\n选项不存在,请重新选择:>"); break; } } while (n);}//欢迎界面void Welcome(){ printf("\n 三 子 棋 游 戏 \n"); printf(" /———————————————————\\\n"); printf(" | 1.play 0.exit |\n"); printf(" \\______________________________________/\n");}void Play(){ char Board[ROW][COL] = { 0 }; char ret = 0; Location loc = { -1,-1 }; InitBoard(Board, ROW, COL); while (1) { loc = PlayerMove(Board, ROW, COL); PrintBoard(Board, ROW, COL); ret = CheckWin(Board, ROW, COL); if (ret != ' ') break; loc = ComputerMove(Board, ROW, COL); PrintBoard(Board, ROW, COL); ret = CheckWin(Board, ROW, COL); if (ret != ' ') break; } if (ret == 'X') printf("\n 恭喜你,你赢啦!\n"); else if(ret == '0') printf("\n 很遗憾,你输了!\n"); else printf("\n 平局啦!\n");}//可优化char CheckWin(char board[ROW][COL], int row, int col){ int i = 0, j = 0; for (i = 0; i < row; i++) { if (board[i][1] != ' '&&board[i][0] == board[i][1] && board[i][1] == board[i][2]) return board[i][1]; } for (i = 0; i < col; i++) { if (board[1][i] != ' '&&board[1][i] == board[0][i] && board[1][i] == board[2][i]) return board[1][i]; } if (board[1][1] != ' ' &&board[0][0] == board[1][1] && board[1][1] == board[2][2]) return board[1][1]; if (board[1][1] != ' '&&board[1][1] == board[0][2] && board[1][1] == board[2][0]) return board[1][1]; //判断是否平局 for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if(board[i][j] == ' ') return ' '; } } return 'E';}void InitBoard(char board[ROW][COL], int row, int col) { memset(&board[0][0], ' ', row*col);}void PrintBoard(char board[ROW][COL], int row, int col){ int i, j; printf(" ___ ___ ___\n"); for (i = 0; i < row; i++) { printf(" |"); for (j = 0; j < col; j++) { printf(" %c |", board[i][j]); } printf("\n"); printf(" |___|___|___|\n"); }}void PrintPeople(){ printf("\n ○\n"); printf(" /|\\ Your Round\n"); printf(" / \\\n");}void PrintComputer(){ printf(" ┌ —┐\n"); printf(" └— ┘\n"); printf(" Computer Round __|__ \n");}Location PlayerMove(char board[ROW][COL], int row, int col){ int x = 0; int y = 0; Location loc = { 0,0 }; PrintPeople(); while (1) { printf("\n 请输入坐标:>"); scanf("%d%d", &x, &y); if (x > 0 && x <= row&&y > 0 && y <= col) { if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = 'X'; break; } } printf("\n 坐标不合法!\n"); } loc.x = x - 1; loc.y = y - 1; return loc;}//可优化Location ComputerMove(char board[ROW][COL], int row, int col){ int i = 0, j = 0; Location loc = { 0,0 }; PrintComputer(); while (1) { loc.x = rand() % row; loc.y = rand() % col; if (board[loc.x][loc.y] == ' ') { board[loc.x][loc.y] = '0'; break; } } return loc;}
功能函数的实现如上。
运行效果:
哈哈 我超喜欢这个小人和电脑,可爱可爱,非常可爱,感谢输入法
虽然这个电脑落子的算法过于简单,对弈起来有点弱智,但它还是能赢的,不信你瞧
有两个点需要重点优化:
1.判断是否有人胜出的函数CheckWin需要优化,这次实现是为三子棋量身打造的,单纯的判断是否有三个棋在一条线,并不能做到通用。
2.电脑落子函数ComputerMove需要优化,在这个项目中是随机落子,这让游戏丧失了趣味性,谁会愿意和一个不思考的对手下棋。
考虑到以上两点,我没有在三子棋上继续优化,因为三子棋棋子过少的原因,许多投机取巧的实现方式会比通用的方式更高效,因此我在这基础上扩展到了五子棋,两者本质上是没什么区别的。
由于对三子棋的函数介绍过于简单,我会在五子棋上分析的细致些,不要嫌我啰嗦哦~
五子棋
1.在game.h中进行宏定义,头文件引入以及函数声明
2.在main.c中实现主函数
3.在game.c中实现功能函数
game.h
#ifndef __GAME_H_#define __GAME_H_#define _CRT_SECURE_NO_WARNINGS 1//棋盘大小#define ROW 10#define COL 10#define CHESS 5 //五子棋#include<stdio.h>#include<stdlib.h>#include<string.h>#include<time.h>#include<windows.h>//坐标typedef struct Location{ int x; int y;}Location;void Menu();void Play();void InitBoard(char board[ROW][COL], int row, int col);//五子棋棋盘打印void PrintBigBoard(char board[ROW][COL], int row, int col);Location PlayerMove(char board[ROW][COL], int row, int col);Location ComputerMove(char board[ROW][COL], int row, int col);char CheckWin(char board[ROW][COL], int row, int col, Location loc);//Check1是电脑下棋的算法1,叫Check是因为我觉得功能是查找落子的位置Location Check1(char board[ROW][COL], int row, int col);//Check2是电脑下棋的算法2Location Check2(char board[ROW][COL], int row, int col);#endif // !__GAME_H_
是不是发现头文件和三子棋的惊奇的相似,其实整个文件都是从三子棋复制过来的啦,只做了一些微小的改动
main.c
#include"game.h"int main(){ Menu(); return 0;}
哈哈 这次和三子棋一模一样,没错我就是代码的搬运工,其实这些都不重要啦,关键点在下边 ↓
game.c
和三子棋相同的代码我就不搬运了,我分析几个重要的功能函数
/** 函数名 :CheckWin* 函数介绍:判断棋盘当前状态,是否有人获胜* 输入参数: board 棋盘数组,记录棋子 row 棋盘行数 col 棋盘列数 loc 当前落子坐标* 返回值 :若玩家获胜返回'X',电脑获胜返回'0',棋盘没有空位则 为平局,返回'E',否则返回‘ ’表示游戏继续*/char CheckWin(char board[ROW][COL], int row, int col, Location loc){ int i = 0, j = 0; int x = 0, y = 0;//移动坐标 int flag = 0; //共有4种情况 水平,垂直,斜杠,反斜杠,每种情况分两个部分判断 for (i = 1; i <= 8; i++) { if (i % 2 == 1) flag = 0;//两次循环为一种情况,在该情况最开始标志位清零 switch (i) { //水平向右查看 case 1: x = loc.x; y = loc.y + 1; break; //水平向左 case 2: x = loc.x; y = loc.y - 1; break; //垂直向上 case 3: x = loc.x - 1; y = loc.y; break; //垂直向下 case 4: x = loc.x + 1; y = loc.y; break; //向右下 case 5: x = loc.x + 1; y = loc.y + 1; break; //向左上 case 6: x = loc.x - 1; y = loc.y - 1; break; //向右上 case 7: x = loc.x - 1; y = loc.y + 1; break; //向左下 case 8: x = loc.x + 1; y = loc.y - 1; break; default: break; } while(x >= 0 && x < row&&y >= 0 && y < row) { if (board[x][y] == board[loc.x][loc.y]) flag++; else break;//如果不符合,说明至少已经不连贯了 switch (i) { //8种具体情况的循环变量控制 case 1:y++; break; case 2:y--; break; case 3:x--; break; case 4:x++; break; case 5:x++; y++; break; case 6:x--; y--; break; case 7:x--; y++; break; case 8:x++; y--; break; } } if (i % 2 == 0) {//2种情况在一条线上,每条线进行判断 if (flag >= CHESS - 1) return board[loc.x][loc.y]; } } //判断是否平局 for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (board[i][j] == ' ') return ' '; } } return 'E';}
这里的判断输赢函数比三子棋多了一个参数loc,它表示最后一个人落子的坐标,判断输赢刚开始我准备用比较暴力的方法,从第一个位置开始判断下,右,右下,左下四个方向有没有符合条件的子,直到最后一个有效的位置,但这样未免太耽误时间了。
因为每次落子都会判断一次输赢,所以我想能改变输赢状态的是最后一个落子的位置,所以对最后一个落子的位置进行8个方向判断(其实是4条直线),满足连续有5个子就胜出。
假设棋盘是这个样子的,大红色是己方最后一次落子的位置,粉色是之前己方已经下过的位置
红色棋子的坐标是(5,5),这里就不考虑是从0开始还是从1开始啦,我只是想讲清楚这个函数的实现,就假设进入CheckWin函数的坐标是(5,5),flag是记录有多少个棋子和最后一个棋子是连续且在一条直线上的。
首先进入第一层循环,由循环变量i控制,第一层共循环8次,分别为8种情况——水平向右,水平向左,竖直向下,竖直向上,右上,左下,左上、右下,其中每2种情况在一条直线上,作为一组,所以flag在每种情况的开始要清零。
因为每种情况判断的第一个棋子都是不一样的,所以用switch case分支来实现,刚进来i=1,所以进入第一种情况,查看水平向右方向的棋子,坐标(5,6),然后进入第二层循环,和红色棋子位置的符号进行比较,(5,6)没有己方棋子,所以不会相等,flag不改变,并且跳出循环,(为啥要跳出循环,因为即便再下一个棋子是己方棋子,可已经不连续了,不满足胜出条件了),然后i++,判断水平向左,没有己方棋子,flag仍然不改变,还是0,第一条直线判断结束,然后判断flag的值,是否大于等于CHESS-1,CHESS是一个宏定义,五子棋CHESS定义为5,三子棋就定义为3,为啥是CHESS-1呢,因为最后一次下的棋子没有加在flag里呀。
然后i++,判断第二条直线,肯定不满足,跳过,不看了,直接到i=7,flag=0,x=4,y=6,再次进入内部循环,判断发现board[x][y]==board[loc.x][loc.y],flag++,由于每种情况循环变量的控制也不相同,所以再次使用switch case语句,x- -,y++,现在查看(3,7),然后发现board[3][7]==board[loc.x][loc.y],flag++,现在flag=2了,然后重复刚才的步骤,但这一次board[2][8]不等于board[loc.x][loc.y],所以跳出内部循环,i++,此时i=8,x=5,y=4,和刚才一样,flag又加了两次,最后flag=4,满足flag>=CHESS-1,有人胜出了,把胜出棋子的符号返回。
突然觉得自己变成了一个絮叨的老太婆~
如果路过的大牛有更好的方法或者指导意见,可以留言哦,小生不怕别人指出错误~
Location ComputerMove(char board[ROW][COL], int row, int col){ Location loc = { -1,-1 }; PrintComputer(); loc = Check1(board, row, col); if (loc.x != -1 && loc.y != -1) return loc; loc = Check2(board, row, col); return loc;}
电脑下棋,先使用第一种方法,因为第一种方法不是所有情况都会给出最好的位置,所以再使用第二种下棋方法,这样所有情况电脑都会正确落子。
/** 函数名 :Check1* 函数介绍: 电脑落子算法1,己方有三个一排时会续,对方有三个一排时会堵* 返回值 :落子坐标*/Location Check1(char board[ROW][COL], int row, int col){ int x = 0, y = 0; int i = 0, j = 0, t = 0; Location loc = { -1,-1 }, loc2 = { -1,-1 }; int flag = 0; for (i = 0; i < ROW; i++) {//四种直线情况,右,右下,下,左下 for (j = 0; j < COL; j++) { if (board[i][j] != ' ') { for (t = 1; t <= 4; t++) { flag = 0; switch (t) { case 1://右 x = i; y = j + 1; break; case 2://下 x = i + 1; y = j; break; case 3://右下 x = i + 1; y = j + 1; break; case 4://左下 x = i + 1; y = j - 1; break; default: break; } while (x >= 0 && x < row&&y >= 0 && y < row) { if (board[x][y] == board[i][j]) flag++; else break; if (flag >= CHESS - 3) { switch (t) { case 1: if (board[x][y + 1] == ' ') { if (y + 1 < row) { loc.x = x; loc.y = y + 1; } } else if (board[x][y - flag - 1] == ' ') { if (y - flag - 1 >= 0) { loc.x = x; loc.y = y - flag - 1; } } break; case 2: if (board[x + 1][y] == ' ') { if (x + 1 < row) { loc.x = x + 1; loc.y = y; } } else if (board[x - flag - 1][y] == ' ') { if (y - flag - 1 >= 0) { loc.x = x - flag - 1; loc.y = y; } } break; case 3: if (board[x + 1][y + 1] == ' ') { if ((y + 1 < row) && (x + 1 < col)) { loc.x = x + 1; loc.y = y + 1; } } else if (board[x - flag - 1][y - flag - 1] == ' ') { if ((y - flag - 1 >= 0) && (x - flag - 1 >= 0)) { loc.x = x - flag - 1; loc.y = y - flag - 1; } } break; case 4: if (board[x + 1][y - 1] == ' ') { if ((x + 1 < row) && (y - 1 >= 0)) { loc.x = x + 1; loc.y = y - 1; } } else if (board[x - flag - 1][y + flag + 1] == ' ') { if ((y + flag + 1 < col) && (x - flag - 1 >= 0)) { loc.x = x - flag - 1; loc.y = y + flag + 1; } } break; } break; } switch (t) { case 1:y++; break; case 2:x++; break; case 3:x++; y++; break; case 4:x++; y--; break; } } } } } } if ((loc.x != -1 && loc.y != -1) && board[loc.x][loc.y] == ' ') { board[loc.x][loc.y] = '0'; return loc; } return loc2;}
这里用到的就是刚刚我提到的比较暴力的方法,从头扫到尾,查看对方或者己方有没有3个或者3个以上的棋子在同一条直线上的,如果对方有的话,就堵上,己方有的话,就续接上
我这个方法做的不是很好,有个缺点:
电脑发现有三个棋连在一起,它会先堵一头,下一回合会堵另外一头。其实开始它是只能堵一头,然后我做了修改,我的本意是发现4个棋连在一起再堵另外一头,这个有待优化。
运行效果:
/** 函数名 :Check2* 函数介绍: 电脑落子算法2,找出周围棋子最多的能落子的坐标* 返回值 :落子坐标*/Location Check2(char board[ROW][COL], int row, int col){ int i = 0, j = 0; Location loc = { -1,-1 }; int max = -1; int weight[ROW][COL] = { 0 };//权重,每个棋子附近的棋子数 int flag = 0; //随机放在棋子最多的位置 //不能放的位置 权值为-1,边界太麻烦,不讨论,直接用初始化的值0,与不能下的位置做区别 for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] != ' ') weight[i][j] = -1; } } for (i = 1; i < row - 1; i++) {//计算每个子的权重,己方和对手方的棋子都算 for (j = 1; j < col - 1; j++) { if (weight[i][j]>-1) { //这里可以用两层循环,但是我不想让循环套太多层,所以用if语句 if (board[i - 1][j - 1] != ' ') weight[i][j]++; if (board[i - 1][j] != ' ') weight[i][j]++; if (board[i + 1][j + 1] != ' ') weight[i][j]++; if (board[i][j + 1] != ' ') weight[i][j]++; if (board[i - 1][j + 1] != ' ') weight[i][j]++; if (board[i + 1][j] != ' ') weight[i][j]++; if (board[i + 1][j - 1] != ' ') weight[i][j]++; if (board[i][j - 1] != ' ') weight[i][j]++; } } } for (i = 0; i < row; i++) {//找出权重最大的位置 for (j = 0; j < col; j++) { if (weight[i][j]>max) { max = weight[i][j]; loc.x = i; loc.y = j; } } } board[loc.x][loc.y] = '0'; return loc;}
这个方法是给每个位置都赋一个权重,权重的值表示周围8个位置已经下过棋子的个数,因为边边还需要特殊考虑,所以我直接把权重设成0了,下过棋子的位置权重是-1,两者作区别,剩下的位置就是用很笨的循环动态的判断,当所有位置的权重都确定了之后,找出权重最大的那个返回。
这样下只是为了让电脑不胡乱放棋子,起码看起来不那么蠢
运行一下,电脑一下子聪明多了
- 数组的应用——三子棋、五子棋
- 二维数组的应用举例(生态)/(五子棋)
- [应用发布]Meego平台——五子棋
- JavaSE017_数组之应用举例(利用二维数组实现五子棋功能完善——二人对战)
- JavaSE021_数组之应用举例(利用二维数组实现五子棋图形版——二人对战)
- 五子棋AI的应用思路
- 娄学长的五子棋—牛!
- 关于做人工智能—五子棋的总结
- 一种五子棋棋盘的数组表示
- 基于MFC的五子棋应用(一)
- 基于MFC的五子棋应用(二)
- 基于MFC的五子棋应用(三)
- java 10.29(五子棋—)
- AI—五子棋
- 类似于五子棋的三子棋小游戏
- 三子棋和五子棋的代码
- 浅析五子棋的实现——应用程序算法
- 初涉c++ ——完善五子棋的保存加载功能
- Linux 对80端口进行访问控制
- java语言的特性
- Caffemodel之C++修改参数
- CATALINA_BASE与CATALINA_HOME的区别
- SharedPreferences记录数据
- 数组的应用——三子棋、五子棋
- Android高德地图之基础
- 关于thymeleaf的多条件及多属性设置
- Telephony-C/Java代码分析
- 基于EasyNVR+EasyDSS H5视频直播二次开发实现业务需求:直接使用播放页面
- source filename 与 sh filename 及./filename执行脚本的区别
- MD5加密、DES加密,DES解密
- javascript基础练习-获取和设置行内样式
- javascript浏览器对象之screen对象