第一章:第一个游戏
第一个游戏是做一个“推箱子”游戏。
以下是自己写的:
#include <iostream>
#include <stdlib.h>
using namespace std;
struct pos
{
int x;
int y;
};
char player = 'P';
char box = 'o';
char wall = '#';
char space = ' ';
char target = '.';
pos targetPos1 = { 1, 2 };
pos targetPos2 = { 1, 3 };
char world[5][8] =
{
'#','#','#','#','#','#','#','#',
'#',' ','.','.',' ','P',' ','#',
'#',' ','o','o',' ',' ',' ','#',
'#',' ',' ',' ',' ',' ',' ','#',
'#','#','#','#','#','#','#','#'
};
char input = ' ';
bool ItCanMoveUp(int x, int y)
{
if (x <= 1)
return false;
if (world[x - 1][y] == box && world[x - 2][y] == box)
return false;
int spaceCount = 0;
// 从下往上扫描
for (int i = x - 1; i > 0; i--)
{
if (world[i][y] == space || world[i][y] == target)
spaceCount++;
}
if (spaceCount > 0)
return true;
else
return false;
}
bool ItCanMoveDown(int x, int y)
{
if (x >= 3)
return false;
if (world[x + 1][y] == box && world[x + 2][y] == box)
return false;
int spaceCount = 0;
// 从上往下扫描
for (int i = x + 1; i < 4; i++)
{
if (world[i][y] == space || world[i][y] == target)
spaceCount++;
}
if (spaceCount > 0)
return true;
else
return false;
}
bool ItCanMoveLeft(int x, int y)
{
if (y <= 1)
return false;
if (world[x][y - 1] == box && world[x][y - 2] == box)
return false;
int spaceCount = 0;
// 从右往左扫描
for (int j = y - 1; j > 0; j--)
{
if (world[x][j] == space || world[x][j] == target)
spaceCount++;
}
if (spaceCount > 0)
return true;
else
return false;
}
bool ItCanMoveRight(int x, int y)
{
if (y >= 6)
return false;
if (world[x][y + 1] == box && world[x][y + 2] == box)
return false;
int spaceCount = 0;
// 从左往右扫描
for (int j = y + 1; j < 7; j++)
{
if (world[x][j] == space || world[x][j] == target)
spaceCount++;
}
if (spaceCount > 0)
return true;
else
return false;
}
void MoveUp(int x, int y)
{
if (world[x][y] != player)
return;
for (int i = 1; i < x; i++)
{
// 目标点不改变位置
if (world[i + 1][y] == target)
continue;
// 箱子改变位置需要和角色贴近
if (world[i + 1][y] == box && world[i + 2][y] != player)
continue;
if (world[i][y] == box && world[i + 1][y] != player)
continue;
world[i][y] = world[i + 1][y];
}
world[x][y] = space;
}
void MoveDown(int x, int y)
{
if (world[x][y] != player)
return;
for (int i = 3; i > x; i--)
{
// 目标点不改变位置
if (world[i - 1][y] == target)
continue;
// 箱子改变位置需要和角色贴近
if (world[i - 1][y] == box && world[i - 2][y] != player)
continue;
if (world[i][y] == box && world[i - 1][y] != player)
continue;
world[i][y] = world[i - 1][y];
}
world[x][y] = space;
}
void MoveLeft(int x, int y)
{
if (world[x][y] != player)
return;
for (int j = 1; j < y; j++)
{
// 目标点不改变位置
if (world[x][j + 1] == target)
continue;
// 箱子改变位置需要和角色贴近
if (world[x][j + 1] == box && world[x][j + 2] != player)
continue;
if (world[x][j] == box && world[x][j + 1] != player)
continue;
world[x][j] = world[x][j + 1];
}
world[x][y] = space;
}
void MoveRight(int x, int y)
{
if (world[x][y] != player)
return;
for (int j = 6; j > y; j--)
{ // 目标点不改变位置
if (world[x][j - 1] == target)
continue;
// 箱子改变位置需要和角色贴近
if (world[x][j - 1] == box && world[x][j - 2] != player)
continue;
if (world[x][j] == box && world[x][j - 1] != player)
continue;
world[x][j] = world[x][j - 1];
}
world[x][y] = space;
}
void draw()
{
system("cls"); // 清空当前屏幕输出
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 8; j++)
{
if ((i == targetPos1.x && j == targetPos1.y) || (i == targetPos2.x && j == targetPos2.y))
{
if (world[i][j] != box && world[i][j] != player)
world[i][j] = target;
}
cout << world[i][j];
}
cout << endl;
}
}
void getInput()
{
cin >> input;
}
void updateGame()
{
if (input == ' ')
return;
// 获取玩家所在位置
int x = -1, y = -1;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 8; j++)
{
if (world[i][j] == player)
{
x = i;
y = j;
break;
}
}
}
switch (input)
{
case 'w':
if (ItCanMoveUp(x, y))
{
MoveUp(x, y);
}
break;
case 'a':
if (ItCanMoveLeft(x, y))
{
MoveLeft(x, y);
}
break;
case 's':
if (ItCanMoveDown(x, y))
{
MoveDown(x, y);
}
break;
case 'd':
if (ItCanMoveRight(x, y))
{
MoveRight(x, y);
}
break;
default:
break;
}
// 重置输入
input = ' ';
}
void JudgeWin()
{
if (world[targetPos1.x][targetPos1.y] == box && world[targetPos2.x][targetPos2.y] == box)
{
cout << "Game Win!" << endl;
}
}
int main()
{
draw();
while (true)
{
getInput();
updateGame();
draw();
JudgeWin();
}
}
以下是书上的demo:
#include <iostream>
#include <stdlib.h>
using namespace std;
//#墙 _空白区 .终点 o砖块 p人
const char gStageData[] = "\
########\n\
# .. p #\n\
# oo #\n\
# #\n\
########";
const int gStageWidth = 8;
const int gStageHeight = 5;
enum Object
{
OBJ_SPACE,
OBJ_WALL,
OBJ_GOAL,
OBJ_BLOCK,
OBJ_BLOCK_ON_GOAL,
OBJ_MAN,
OBJ_MAN_ON_GOAL,
OBJ_UNKNOWN,
};
// 函数原型
void initialize(Object* state, int w, int h, const char* stageData);
void draw(const Object* state, int w, int h);
void update(Object* state, char input, int w, int h);
bool checkClear(const Object* state, int w, int h);
int main()
{
// 创建状态数组
Object* state = new Object[gStageWidth * gStageHeight];
// 初始化场景
initialize(state, gStageWidth, gStageHeight, gStageData);
// 主循环
while (true)
{
// 绘制
draw(state, gStageWidth, gStageHeight);
// 通关检测
if(checkClear(state, gStageWidth, gStageHeight))
break;
// 操作说明
cout << "a:left s:right w:up z:down. command?" << endl;
// 获取输入
char input;
cin >> input;
// 更新
update(state, input, gStageWidth, gStageHeight);
}
// 通关
cout << "Congratulation's! You win." << endl;
delete[] state;
state = nullptr;
//为了避免运行完一闪而过,这里添加一个无限循环。命令行中按下Ctrl-C即可终止
while (true) {
;
}
return 0;
}
void initialize(Object* state, int width, int height, const char* stageData)
{
const char* d = stageData;
int x = 0;
int y = 0;
while (*d != '\0')
{
Object t;
switch (*d)
{
case '#':t = OBJ_WALL; break;
case ' ':t = OBJ_SPACE; break;
case 'o':t = OBJ_BLOCK; break;
case 'O':t = OBJ_BLOCK_ON_GOAL; break;
case '.':t = OBJ_GOAL; break;
case 'p':t = OBJ_MAN; break;
case 'P':t = OBJ_MAN_ON_GOAL; break;
case '\n': // 到下一行
x = 0; // x返回最左边
++y; // y进到下一段
t = OBJ_UNKNOWN; // 没有数据
break;
default:t = OBJ_UNKNOWN; break; // 非法数据
}
++d;
// 如果遇到未知字符则无视
if (t != OBJ_UNKNOWN)
{
state[y*width + x] = t;
++x;
}
}
}
void draw(const Object* state, int width, int height)
{
// Object枚举类型的顺序
const char font[] = { ' ', '#', '.', 'o', 'O', 'p', 'P' };
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
Object o = state[y*width + x];
cout << font[o];
}
cout << endl;
}
}
void update(Object* state, char input, int width, int height)
{
// 转换为移动量
int dx = 0;
int dy = 0;
switch (input)
{
case 'a': dx = -1; break; // 左
case 's': dx = 1; break; // 右
case 'w': dy = -1; break; // 上
case 'z': dy = 1; break; // 下
default: break;
}
// 查找玩家坐标
int i = -1;
for (i = 0; i < width*height; ++i)
{
if (state[i] == OBJ_MAN || state[i] == OBJ_MAN_ON_GOAL)
break;
}
int x = i % width;
int y = i / width;
//移动
//移动后的坐标(t并没有特定的代表含义)
int tx = x + dx;
int ty = y + dy;
//判断坐标的极端值。不允许超出合理值范围
if (tx < 0 || ty < 0 || tx >= width || ty >= height) {
return;
}
//A.该方向上是空白或者终点。小人则移动
int p = y * width + x; //人员位置
int tp = ty * width + tx; //目标位置(TargetPosition)
if (state[tp] == OBJ_SPACE || state[tp] == OBJ_GOAL) {
state[tp] = (state[tp] == OBJ_GOAL) ? OBJ_MAN_ON_GOAL : OBJ_MAN; //如果该位置是终点,则将该位置值变为“终点上站着人”
state[p] = (state[p] == OBJ_MAN_ON_GOAL) ? OBJ_GOAL : OBJ_SPACE; //如果该位置已经是“终点上站着人”,则变为“终点”
//B.如果该方向上是箱子。并且该方向的下下个格子是空白或者终点,则允许移动
}
else if (state[tp] == OBJ_BLOCK || state[tp] == OBJ_BLOCK_ON_GOAL) {
//检测同方向上的下下个格子是否位于合理值范围
int tx2 = tx + dx;
int ty2 = ty + dy;
if (tx2 < 0 || ty2 < 0 || tx2 >= width || ty2 >= height) { //按键无效
return;
}
int tp2 = (ty + dy)*width + (tx + dx); //下下个格子
if (state[tp2] == OBJ_SPACE || state[tp2] == OBJ_GOAL) {
//按顺序替换
state[tp2] = (state[tp2] == OBJ_GOAL) ? OBJ_BLOCK_ON_GOAL : OBJ_BLOCK;
state[tp] = (state[tp] == OBJ_BLOCK_ON_GOAL) ? OBJ_MAN_ON_GOAL : OBJ_MAN;
state[p] = (state[p] == OBJ_MAN_ON_GOAL) ? OBJ_GOAL : OBJ_SPACE;
}
}
}
bool checkClear(const Object* state, int width, int height)
{
for (int i = 0; i < width*height; i++)
{
if (state[i] == OBJ_BLOCK) // 若箱子都推到了目标位置,应只剩下OBJ_BLOCK_ON_GOAL状态
{
return false;
}
}
return true;
}
自我审视:
- 不够抽象,仅专注于完成当前的功能,如果还是推箱子游戏,但是不用字符表示而改用2D图片,整个逻辑就很难复用,应严格遵守MVC框架构建游戏逻辑。
- 数组长宽写死了,不利于以后的复用。
- 场景布置等内容,主要由策划控制,所以最好能够形成读取外部文件的方式,方便未来修改和扩展。
- 错误处理。如场景布置配错等。
- 可以把一些通用的逻辑抽象为函数。
- 最后一点,与架构无关,虽然表现起来没问题,但是移动逻辑写复杂了。