世嘉新人培训教材

第一章:第一个游戏

Posted by LudoArt on December 20, 2020

第一章:第一个游戏

第一个游戏是做一个“推箱子”游戏。

以下是自己写的:

#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;
}

自我审视:

  1. 不够抽象,仅专注于完成当前的功能,如果还是推箱子游戏,但是不用字符表示而改用2D图片,整个逻辑就很难复用,应严格遵守MVC框架构建游戏逻辑。
  2. 数组长宽写死了,不利于以后的复用。
  3. 场景布置等内容,主要由策划控制,所以最好能够形成读取外部文件的方式,方便未来修改和扩展。
  4. 错误处理。如场景布置配错等。
  5. 可以把一些通用的逻辑抽象为函数。
  6. 最后一点,与架构无关,虽然表现起来没问题,但是移动逻辑写复杂了。