原标题:用C语言编写小游戏——“井字棋”

作者:Milo Yip 来源:知乎

原文链接:https://zhuanlan.zhihu.com/p/39581573

在 Milo Yip:用C++编写游戏容易吗?有什么开源的小游戏吗?能分享一下吗?这个答案中,我提到学习游戏编程可从回合制游戏开始,例如井字棋。

考虑到一些初学者的学习需求,我就写一个井字棋的教程吧。

1. 游戏状态的表示

首先,我认为表示方法(representation)是编程中应最先要考虑的事情。对于回合制游戏,我们需要存储一个回合中的游戏状态(game state)。

以下用一个结构体表示井字棋一个回合中的状态,并加入函数作初始化:

typedefstruct{ intboard[3][3]; // -1 = empty, 0 = O, 1 = Xintturn; // O first} state;voidinit(state*s) { inti, j; for(j =0; j <3; j++) for(i =0; i <3; i++) s->board[j][i] =-1; s->turn =0;}

以上用二维数组存储棋盘(board)是其中一种表示方式,另一种方式则是记录每个回合下棋子的位置。我们采用前者是因为它较容易实现胜负判定。有些回合制游戏可能使用冗余的表示方式,以方便实现各种规则。

而使用结构体而不是直接用全局变量,可带来一些优点,例如增强可读性及内聚性。

2. 显示游戏状态

编写游戏时,我们通常希望先显示游戏状态,之后才加入其他规则,因为这样可以方便测试。

我希望用这样的文本显示游戏状态,当空置时写上位置编号(1-9),以方便玩家输入下棋位置:

1 | 2 | 3 ---+---+--- 4 | 5 | 6 ---+---+--- 7 | 8 | 9

简单直白地编写代码的话:

voiddisplay(conststate*s) { inti, j; for(j =0; j <3; j++) { for(i =0; i <3; i++) { switch(s->board[j][i]) { case-1:printf(" %d ", j *3+i +1); break; case0:printf(" O "); break; case1:printf(" X "); break; } if(i <2) printf("|"); elseprintf("n"); } if(j <2) printf("---+---+---n"); elseprintf("n"); }}

由于display()只读而不改变游戏状态,所以其参数类型为const state*。

我们稍压缩一下代码:

voiddisplay(conststate*s) { inti, j; for(j =0; j <3; printf(++j <3?"---+---+---n":"n")) for(i =0; i <3; putchar("||n"[i++])) printf(" %c ", s->board[j][i] ==-1?'1'+j *3+i : "OX"[s->board[j][i]]);}

我们可以加入main()函数去显示初始化的状态:

intmain() { state s; init(&s); display(&s);}

此阶段的完整代码位于:

https://github.com/miloyip/misc/blob/master/tictactoe/tictactoe0.c。

3. 实现下棋

然后,我们加入第一个游戏规则,就是下棋:

intmove(state*s, inti, intj) { if(s->board[j][i] !=-1) return0; s->board[j][i] =s->turn++%2; return1;}

函数内做了一个合法性判断,如果该位置已有棋子,则返回 0 表示失败。成功的话,在偶数回合填入 0,表示 O;奇数回合填入 1,表示 X;然后都把回合加一。

更改main()简单测试:

intmain() { state s; init(&s); display(&s); move(&s, 1, 1); display(&s); move(&s, 0, 1); display(&s);}

输出:

此阶段的完整代码位于

https://github.com/miloyip/misc/blob/master/tictactoe/tictactoe1.c

4. 处理输入

在每一回合中,提示当前玩家(O 或 X),并让玩家输入一个下棋位置(1-9),如果位置不合法,则重新输入:

voidhuman(state*s) { charc; do{ printf("%c: ", "OX"[s->turn %2]); c =getchar(); while(getchar() !='n'); printf("n"); } while(c '9'||!move(s, (c -'1') %3, (c -'1') /3));}

在标准输入中,要到回车键才能处理输入,所以这里我们读了第一个输入字符后,就忽略其他字符直到读到换行符。我们把表示位置的字符转换成二维数组索引。

然后,就可以修改main()实现二人下棋的流程:

intmain() { state s; init(&s); display(&s); while(s.turn <9) { human(&s); display(&s); } }

此阶段的完整代码位于

https://github.com/miloyip/misc/blob/master/tictactoe/tictactoe2.c

5. 胜负判定

众所周知,井字棋的胜利条件,是有三个棋子在横线、直线或斜线连成一线。我们实现一个evaluate()函数去评估棋局的状态,如果 O 胜出则返回 1,X 胜出则返回 -1,不分胜负则返回 0:

#define CHECK(j1, i1, j2, i2, j3, i3) if (s->board[j1][i1] != -1 && s->board[j1][i1] == s->board[j2][i2] && s->board[j1][i1] == s->board[j3][i3]) return s->board[j1][i1] == 0 ? 1 : -1;intevaluate(conststate*s) { inti; for(i =0; i <3; i++) { CHECK(i, 0, i, 1, i, 2); // horizontalCHECK(0, i, 1, i, 2, i); // vertical} CHECK(0, 0, 1, 1, 2, 2); // diagnoalCHECK(0, 2, 1, 1, 2, 0); // diagnoalreturn0;}

上面的代码使用了一个宏CHECK()去检测三个位置是否都为相同的棋子,如是则直接返回胜方。

最后,我们在main()中,待每次下棋及显示状态后, 判定是否出现胜方,如果到达第 9 个回合(回合从 0 开始),则判定是平局(draw):

intmain() { state s; init(&s); display(&s); while(s.turn <9) { human(&s); display(&s); switch(evaluate(&s)) { case1:printf("O winn"); return0; case-1:printf("X winn"); return0; } } printf("Drawn");}

此阶段的完整代码位于

https://github.com/miloyip/misc/blob/master/tictactoe/tictactoe3.c

6. 总结

本篇实现了二人井字棋,它是一个简单的回合制游戏。我们先选择了游戏的状态表示方式(state结构体及init()函数),然后把状态以文本形式显示(display()函数),加入每回合下棋规则(move()函数),以及人类玩家的输入处理(human()函数),并作胜负判定(evaluate()函数),最后在main()里则实现了按回合的循环及输出胜负结果。

虽然这个游戏本身以及 60 行的示例代码都很简单,但这个框架可以用于实现其他(更复杂的)回合制游戏。实时游戏(如动作游戏)的主要区别,其实也只在于把输入部分做成非阻塞的函数,而该循环则称为游戏循环(game loop)。返回搜狐,查看更多

责任编辑:

用c语言编写的打字母游戏,用C语言编写小游戏——“井字棋”相关推荐

  1. 通过游戏编程学Python(7)— 井字棋(下)

    通过游戏编程学Python 通过游戏编程学Python(7)- 井字棋(上) 通过游戏编程学Python(番外篇)- 单词小测验 通过游戏编程学Python(6)- 英汉词典.背单词 文章目录 通过游 ...

  2. C语言小项目——井字棋游戏(升级版)

  3. 从井字棋程序总结C语言初学的知识

    #井字棋程序的可实现性以及实现思路# 目录 #井字棋程序的可实现性以及实现思路# 1.程序的可实现性 2.实现程序的思路 菜单界面 游戏主体 棋盘的显示 玩家下棋子 电脑随机下棋子 判断输赢 1.程序 ...

  4. python小游戏系列井字棋,儿时的回忆

    hello大家好,今天我又发现了个有趣的小玩意.我是专写有趣小玩意的老诗. 相信大家对于井字棋都并不陌生.现在也能找到各种各样的井字棋小游戏玩.那么你们自己是否会编写呢?接下来老诗用python教大家 ...

  5. Minimax 和 Alpha-beta 剪枝算法简介,及以此实现的井字棋游戏(Tic-tac-toe)

    前段时间用 React 写了个2048 游戏来练练手,准备用来回顾下 React 相关的各种技术,以及试验一下新技术.在写这个2048的过程中,我考虑是否可以在其中加入一个 AI 算法来自动进行游戏, ...

  6. python井字棋游戏代码_python实现井字棋游戏

    python实现井字棋游戏 来源:中文源码网    浏览: 次    日期:2018年9月2日 [下载文档:  python实现井字棋游戏.txt ] (友情提示:右键点上行txt文档名->目标 ...

  7. php井字游戏代码_JS实现井字棋游戏步骤详解

    这次给大家带来JS实现井字棋游戏步骤详解,JS实现井字棋游戏的注意事项有哪些,下面就是实战案例,一起来看一下. 最近有一门课结束了,需要做一个井字棋的游戏,我用JavaScript写了一个.首先界面应 ...

  8. C语言第十课:编写井字棋游戏(综合练习1)

    目录 前言: 一.文件建立: 1.头文件game.h: 2.函数定义文件game.c: 3.工程测试文件test.c: 二.编写井字棋游戏: 1.程序整体执行思路: 2.menu菜单函数实现: 3.g ...

  9. C语言实现小游戏之井字棋

    目录 前言 一.井字棋游戏的主流程 二.游戏部分 1.游戏函数 2.初始化棋盘 3.打印棋盘 4.玩家下棋 5.电脑下棋(两个难度等级) 6.判断游戏是否结束 三. 源码展示 总结 前言 这是我在学习 ...

最新文章

  1. java排序为什么会出现多次排序结果不一样_并发理论基础:指令重排序问题
  2. mojoportal学习——文章翻译之多行横排菜单
  3. django后台接收form-data 格式上传的文件
  4. Azure pipeline 配置根据条件执行脚本
  5. 部署Spring Boot Angular App(Maven和Tomcat)的4种方法
  6. CCNP-第二篇-SLA扩展+EIGRP高级版(上)
  7. String length must be a multiple of four.
  8. lynda ux_举办UX午餐并学习并成为UX英雄
  9. BAT程序员必备技能调研,你中了几招?
  10. 商务办公软件应用与实践【9】
  11. 吴恩达深度学习tensorflow版本问题
  12. FMEA软件测试工资,目前运用的比较广泛的是【FMEA不良模式效应分析】,很多人都不知道还有...
  13. ffmpeg 转换flv压缩大小_ffmpeg 视频压缩 转换
  14. 支付宝sdk集成,报系统繁忙 请稍后再试(ALI64)
  15. 软件构造作业——100道算术题
  16. canvas在PC端实现振幅大小可变的动态波浪图
  17. 华为3D建模服务(3D Modeling Kit),轻松构建高质量3D模型
  18. java桌面端开发为什么没就行起来,大部分人选qt,winform,electron?
  19. PPT中如何制作两圆交叉阴影图
  20. 清理电脑,使其加速!

热门文章

  1. Galera Cluster一致性问题
  2. 键盘win键无法使用,win+r不生效、win键没反应、Windows键失灵万能解决方案
  3. 力扣 2042检查句子中地数字是否递增
  4. 静态NAT 如何配置?
  5. 向量、矩阵、张量之间的计算
  6. python实现在线翻译
  7. 《Microduino实战》——2.4 Microduino WRT系列
  8. SpringBoot:Whitelabel Error Page 404
  9. 视频监控一般都存储在哪里?如何实现云端集中存储?
  10. 《正点原子嵌入式linux驱动开发指南V1.4》学习笔记