离散仿真引擎基础作业与练习(二)
二、编程实践,小游戏
- 游戏内容: 井字棋 或 贷款计算器 或 简单计算器 等等
- 技术限制: 仅允许使用 IMGUI 构建 UI
作业目的:
- 提升 debug
- 能力 提升阅读 API 文档能力
制作思路:
首先我们想想井字棋是怎么玩的:有两个玩家(player)、轮流下棋(turn)、棋子分黑白(mark)、棋盘3X3(state)、最后分胜负手(winner);于是我们可以声明这些变量:char markForPlayer1 = '@';//玩家1的棋子 char markForPlayer2 = '#';//玩家2的棋子 int winner;//0表示没有人获胜,1表示玩家1获胜,2表示玩家2获胜; int turn;//表示当前是那个玩家的回合,1表示玩家1的回合,2表示玩家2的回合; char[,] state = new char[3, 3];//使用字符表示每个方格的状态;
开始游戏的时候初始化:
void Start () { //初始化: winner = 0; turn = 1;//玩家1先手 for(int i =0; i < 3; i++) { for(int j = 0; j < 3; j++) { state[i,j] = ' ' ;//棋盘为空 } } }
然后就是渲染棋盘了,重置棋盘(reset)和棋盘上每个位置都做成按钮可以按下;输赢信息和轮次信息不能按下,做成label。
之后这里就需要一些API的帮助了,根据官方文档: bool GUI.Button(new Rect(double x,double y,double width,double height), “name”)这个函数非常好用,它可以在位置(x,y)创建一个名字name,宽度width,高度height的button,并且返回一个bool值表示这个按钮是否被按下。最终写出每次显示的页面(写在OnGUI函数中,这个函数每一帧被多次调用,以保证不停刷新页面):private void OnGUI() { //先检查有没有玩家获胜: getWinner(); //根据是否有胜负手决定是否显示相关信息,使用label显示信息 if(winner != 0) { if (winner == 1) { GUI.Label(new Rect(labelPositionX,labelPositionY-labelHeight,labelWidth,labelHeight), " Player1 win!"); } else { GUI.Label(new Rect(labelPositionX, labelPositionY - labelHeight, labelWidth, labelHeight), " Player2 win!"); } } //显示现在是谁的回合,使用label显示轮次信息 if (turn == 1) { GUI.Label(new Rect(labelPositionX, labelPositionY, labelWidth, labelHeight), "Player1's turn"); } else { GUI.Label(new Rect(labelPositionX, labelPositionY, labelWidth, labelHeight), "Player2's turn"); } //显示整个游戏主界面,每次都打印3X3方格,根据每个方格状态信息(是黑子还是白子占据还是没有棋子)填充button for (int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if (state[i,j] == ' ') { //如果有玩家点击未落子的点就显示落下的棋子并且交换回合 if (GUI.Button(new Rect(buttonPositionX + i * buttonWidth, buttonPositionY + j * buttonHeight, buttonWidth, buttonHeight), "" + state[i, j])&&winner==0) { state[i, j] = (turn==1?markForPlayer1:markForPlayer2); turn = 3 - turn;//交换回合 } } else { //显示棋盘的黑白子 GUI.Button(new Rect(buttonPositionX + i * buttonWidth, buttonPositionY + j * buttonHeight, buttonWidth, buttonHeight), "" + state[i, j]); } } }
其中的getwinner函数如下,直接遍历三行三列以及两个对角线:
void getWinner() { for (int i = 0; i < 3; i++) { if (state[i,0] == state[i, 1]&& state[i, 0] == state[i, 2]&&state[i,0]!=' ') { if (state[i,0] == markForPlayer1) { winner = 1; } else { winner = 2; } } } for (int i = 0; i < 3; i++) { if (state[0, i] == state[1, i] && state[0, i] == state[2, i] && state[0, i] != ' ') { if (state[0,i] == markForPlayer1) { winner = 1; } else { winner = 2; } } } if(state[0,0] == state[2,2]&& state[0, 0] == state[1,1] && state[1, 1] != ' ') { if(state[1,1]==markForPlayer1) { winner = 1; } else { winner = 2; } } if (state[0, 2] == state[1, 1] && state[1, 1] == state[2, 0] && state[1, 1] != ' ') { if (state[1, 1] == markForPlayer1) { winner = 1; } else { winner = 2; } } }
然后基本就完成了,不过这个棋盘还只够玩家玩一次,,,还应该能够重置棋盘(相当于重新初始化一次,所以就直接调用start了):
//玩家可以重玩 if (GUI.Button(new Rect(resetPositionX, resetPositionY, resetWidth, resetHeight), "reset")) { Start(); }
然后就完成了,完整代码如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class littleGame : MonoBehaviour { char markForPlayer1 = '@'; char markForPlayer2 = '#'; int winner;//0表示没有人获胜,1表示玩家1获胜,2表示玩家2获胜; int turn;//表示当前是那个玩家的回合,1表示玩家1的回合,2表示玩家2的回合; char[,] state = new char[3, 3];//使用字符表示每个方格的状态; //这个是两个label的位置大小信息 int labelPositionX = 420; int labelPositionY = 360; int labelWidth = 80; int labelHeight = 40; //这个是reset按钮的位置大小信息 int resetPositionX = 440; int resetPositionY = 520; int resetWidth = 40; int resetHeight = 40; //这个是棋盘每个格子的位置大小信息 int buttonPositionX = 400; int buttonPositionY = 400; int buttonWidth = 40; int buttonHeight = 40; // Use this for initialization void Start () { //初始化: winner = 0; turn = 1; for(int i =0; i < 3; i++) { for(int j = 0; j < 3; j++) { state[i,j] = ' ' ; } } } //检查是否有玩家获胜的函数: void getWinner() { for (int i = 0; i < 3; i++) { if (state[i,0] == state[i, 1]&& state[i, 0] == state[i, 2]&&state[i,0]!=' ') { if (state[i,0] == markForPlayer1) { winner = 1; } else { winner = 2; } } } for (int i = 0; i < 3; i++) { if (state[0, i] == state[1, i] && state[0, i] == state[2, i] && state[0, i] != ' ') { if (state[0,i] == markForPlayer1) { winner = 1; } else { winner = 2; } } } if(state[0,0] == state[2,2]&& state[0, 0] == state[1,1] && state[1, 1] != ' ') { if(state[1,1]==markForPlayer1) { winner = 1; } else { winner = 2; } } if (state[0, 2] == state[1, 1] && state[1, 1] == state[2, 0] && state[1, 1] != ' ') { if (state[1, 1] == markForPlayer1) { winner = 1; } else { winner = 2; } } } //每一帧都刷新屏幕:显示最新的状态 private void OnGUI() { //先检查有没有玩家获胜: getWinner(); if(winner != 0) { if (winner == 1) { GUI.Label(new Rect(labelPositionX,labelPositionY-labelHeight,labelWidth,labelHeight), " Player1 win!"); } else { GUI.Label(new Rect(labelPositionX, labelPositionY - labelHeight, labelWidth, labelHeight), " Player2 win!"); } } //显示现在是谁的回合 if (turn == 1) { GUI.Label(new Rect(labelPositionX, labelPositionY, labelWidth, labelHeight), "Player1's turn"); } else { GUI.Label(new Rect(labelPositionX, labelPositionY, labelWidth, labelHeight), "Player2's turn"); } //显示整个游戏主界面 for (int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if (state[i,j] == ' ') { if (GUI.Button(new Rect(buttonPositionX + i * buttonWidth, buttonPositionY + j * buttonHeight, buttonWidth, buttonHeight), "" + state[i, j])&&winner==0) { state[i, j] = (turn==1?markForPlayer1:markForPlayer2); turn = 3 - turn;//交换回合 } } else { GUI.Button(new Rect(buttonPositionX + i * buttonWidth, buttonPositionY + j * buttonHeight, buttonWidth, buttonHeight), "" + state[i, j]); } } } //玩家可以重玩 if (GUI.Button(new Rect(resetPositionX, resetPositionY, resetWidth, resetHeight), "reset")) { Start(); } } }
题外话:井字棋必定平局
emmm,其实这个东西挺容易做的,所以做完了以后我就想着做个人机对战,但是自己试着画了一下,发现
这个游戏如果双方都使用最优策略是一定会打平的,然后去网上搜了一下的确这样,比较好的分析看这里:井字棋分析