手写控制台五子连珠游戏
前言
我们如何使用一些对游戏引擎不友好的、或者没有专业游戏支持库的语言,或者不使用游戏库来制作小游戏。
在这里 我们一起制作一个基于c++的“五子连珠”小游戏
游戏需求
用C++编写控制台程序,模拟完成一个单人益智小游戏“五子连珠”。棋盘大小是9X9,一共会出现6种颜色的珠子。初始状态棋盘上7个随机位置分布着7个随机颜色的珠子。通过移动珠子将同色的珠子连在一起来消除后得分。当无珠子可以移动时程序结束。游戏记录所有游戏者得分并排序。
游戏规则如下:
1,一次只允许移动一个珠子。
2,每移动一颗珠子以后,如果不满足同色的5颗珠子相连,将会出现3个随机颜色珠子分布到棋盘任意空置的位置上。如果同色的珠子能有5颗连在一起排成横向、纵向或者斜向时,这5颗珠子从棋盘上消失,不产生3颗珠子。同时游戏者可以得10分。
3,当同色的珠子有6颗连在一起排成横向、纵向或者斜向时,游戏者可以得12分。同时6颗珠子从棋盘上消失。(即:在同一方向上连在一起的珠子每增加一颗,游戏者多得2分。依此类推。)
4, 如果移动一个珠子之后,有两个方向都可以同时消除(即:任何单一方向上的同色珠子数至少为5颗),则两个方向的所有珠子都消除。按每个珠子2分获得分值。
5,两个方向同时消除的规则,同样适用于三个或四个方向。
6, 如果系统随机产生的珠子正好能凑成了同色的5颗及以上一起排成横向、纵向或者斜向,则这几颗同向的珠子自行消除,游戏者得分。
7,当棋盘被珠子占满时游戏结束。
实现说明:
1,用课程所讲的类的方式(编程思想)来实现项目。
2,源代码里必须写足够的注释,提高程序可读性。
3,最基本的要求是单纯使用键盘作为输入设备在命令行控制,珠子的移动通过输入起始坐标和终止坐标来模拟完成。当然也可以自学C++的鼠标控制,来替代键盘完成的功能,提高用户体验感。原游戏中不同色的珠子,也可以用不同的符号来代替。
框架
总体框架
0.确定游戏中所用的类
1.初始化
2.显示游戏帧
3.获取输入
4.更新游戏帧
main代码
int main() {
srand(time(NULL));
init();
while (1) {
print();
input();
if(update()==NO_PLACE){
gameover();
}
}
return 0;
}
补充和完善
确定游戏中的类
在此处 我们只需要定义棋盘类就OK
并且主要功能在棋盘类中体现
class MAP {
char **maparr;//存储棋盘数据
map<pair<int, int>, int> isempty;//记录空位置
int score;//分数
public:
MAP() {//构造
score = 0;
maparr = new char*[maxsize];
for (int i = 0; i < maxsize; i++) {
maparr[i] = new char[maxsize];
}
for (int i = 0; i < maxsize; i++) {
for (int j = 0; j < maxsize; j++) {
maparr[i][j] = typearr[NONE];
if (i > 0 && i < maxsize && j > 0 && j < maxsize) {
isempty[ {i, j}] = 1;
}
}
}
}
void print() {//输出棋盘
cout << "score:" << score << endl;
cout << '\t';
for (int i = 1; i < maxsize; i++) {
cout << i << '\t';
}
cout << endl;
for (int i = 1; i < maxsize; i++) {
cout << i << '\t';
for (int j = 1; j < maxsize; j++) {
cout << maparr[i][j] << '\t' ;
}
cout << endl;
}
}
int addpoint(int addnum) {//加入点
if ((int)isempty.size() < addnum) {
return NO_PLACE;
}
for (int i = 0; i < addnum; i++) {
int randnum = rand() % isempty.size();
int x, y, t = 0;
auto n = isempty.begin();
for (; t < randnum; n++, t++);
x = n->first.first;
y = n->first.second;
isempty.erase(n);
maparr[x][y] = typearr[rand() % 5 + 1];
}
return OK;
}
int check() {//检查是否有可以清除的
vector<pair<int, int>> cancleararr;
for (int x = 1; x < maxsize; x++) {
for (int y = 1; y < maxsize; y++) {
if (maparr[x][y] == typearr[NONE]) {
continue;
}
canclear(x, y, cancleararr);
}
}
for (auto i : cancleararr) {
//cout << "i.first:"<<i.first << "i.second:" << i.second << endl;
if(maparr[i.first][i.second]!=typearr[NONE]){
score+=2;
}
maparr[i.first][i.second]=typearr[NONE];
}
return cancleararr.size() == 0 ? ADD : NO_ADD;
}
//对某个点判断是否可以清除
bool canclear(int x, int y, vector<pair<int, int>> &cancleararr) {
//cout << x << '\t' << y << endl;
cancleararr.push_back({x,y});
bool res = false;
int count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y]) {
cancleararr.push_back({x + i, y});
count++;
} else {
break;
}
}
//cout << "x+i" << count << endl;
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; y + i < maxsize; i++) {
if (maparr[x][y] == maparr[x][y + i]) {
cancleararr.push_back({x, y + i});
count++;
} else {
break;
}
}
//cout << "y+i" << count << endl;
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y + i]) {
cancleararr.push_back({x + i, y + i});
count++;
} else {
break;
}
}
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y - i]) {
cancleararr.push_back({x + i, y - i});
count++;
} else {
break;
}
}
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
if(!res){
cancleararr.pop_back();
}
return res;
}
//交换两个点
int swap(int y1, int x1, int y2, int x2) {
if (maparr[x1][y1] == typearr[NONE] || maparr[x2][y2] == typearr[NONE]) {
return SWAP_E;
}
char temp = maparr[x1][y1];
maparr[x1][y1] = maparr[x2][y2];
maparr[x2][y2] = temp;
return OK;
}
//获取分数
int get_score(){
return score;
}
};
常量的定义
#define maxsize 10
//棋盘大小
#define clearnum 4
//可以清除的最小数量
enum {
NONE = 0,
TYPE1,
TYPE2,
TYPE3,
TYPE4,
TYPE5,
TYPE6,
NO_PLACE,
//没有位置
OK,
ADD,
//可以添加
NO_ADD,
//不能添加
SWAP_E
//交换错误
};
char typearr[] = " ABCDEF";
//各类棋子的类型
初始化
void init() {
m.addpoint(7);
//加入7个随机点
m.check();
}
显示游戏帧
void print() {
system("cls");
m.print();
}
获取输入
//获取输入
void input(string name, int &val) {
cout << "请输入" << name << ":";
cin >> val;
}
void input() {
int x1, x2, y1, y2, sure;
while (1) {
input("要移动的第一个横坐标", x1);
input("要移动的第一个纵坐标", y1);
input("要移动的第二个横坐标", x2);
input("要移动的第二个纵坐标", y2);
cout << "要移动的两个点分别是 (" << x1 << "," << y1 << "),("
<< x2 << "," << y2 << ")";
input("是否确认(1/0)", sure);
if (sure == 1 && m.swap(x1, y1, x2, y2) == OK) {
return;
}
}
}
更新游戏帧
void update() {
if(m.check()==ADD){
m.addpoint(3);
//如果可以加入 就加三个点
}
m.check();
//加完后检查
}
游戏结束
void gameover(){
cout << "游戏结束 你的得分为:"<< m.get_score() <<endl;
}
游戏截图
最终代码(和上面有些不同 如果想cv请看这)
head.h
#include <iostream>
#include <vector>
#include <map>
using namespace std;
#define maxsize 10 //棋盘大小-1(也就是9*9的棋盘)
#define clearnum 4 //清除数量-1(也就是5个清除)
//常量定义
enum {
NONE = 0,
TYPE1,
TYPE2,
TYPE3,
TYPE4,
TYPE5,
TYPE6,
NO_PLACE,
OK,
ADD,
NO_ADD,
SWAP_E
};
//每种类型用什么表示
char typearr[] = " ABCDEF";
class MAP {
char **maparr;//棋盘数组
map<pair<int, int>, int> isempty;//记录空点
int score;//记录分数
//对某个点检查此点是否可以消除
//因为这个函数应该只在check中调用 所以不让他为public
bool canclear(int x, int y, vector<pair<int, int>> &cancleararr) {
//cout << x << '\t' << y << endl;
cancleararr.push_back({x,y});
bool res = false;
int count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y]) {
cancleararr.push_back({x + i, y});
count++;
} else {
break;
}
}
//cout << "x+i" << count << endl;
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; y + i < maxsize; i++) {
if (maparr[x][y] == maparr[x][y + i]) {
cancleararr.push_back({x, y + i});
count++;
} else {
break;
}
}
//cout << "y+i" << count << endl;
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y + i]) {
cancleararr.push_back({x + i, y + i});
count++;
} else {
break;
}
}
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
count = 0;
for (int i = 1; x + i < maxsize; i++) {
if (maparr[x][y] == maparr[x + i][y - i]) {
cancleararr.push_back({x + i, y - i});
count++;
} else {
break;
}
}
if (count > clearnum) {
//score += 2*count;
res = true;
} else {
for (int i = 0; i < count; i++) {
cancleararr.pop_back();
}
}
if(!res){
cancleararr.pop_back();
}
return res;
}
public:
//构造
MAP() {
score = 0;
maparr = new char*[maxsize];
for (int i = 0; i < maxsize; i++) {
maparr[i] = new char[maxsize];
}
for (int i = 0; i < maxsize; i++) {
for (int j = 0; j < maxsize; j++) {
maparr[i][j] = typearr[NONE];
if (i > 0 && i < maxsize && j > 0 && j < maxsize) {
isempty[{i, j}] = 1;
}
}
}
}
//更新空点集合
void update_empty(){
isempty.clear();
for(int i=1;i<maxsize;i++){
for(int j=1;j<maxsize;j++){
if(maparr[i][j] == typearr[NONE]){
isempty[{i, j}] = 1;
}
}
}
}
//打印棋盘
void print() {
cout << "score:" << score << endl;
cout << '\t';
for (int i = 1; i < maxsize; i++) {
cout << i << '\t';
}
cout << endl;
for (int i = 1; i < maxsize; i++) {
cout << i << '\t';
for (int j = 1; j < maxsize; j++) {
cout << maparr[i][j] << '\t' ;
}
cout << endl;
}
}
//随机加点
int addpoint(int addnum) {
if ((int)isempty.size() < addnum) {
return NO_PLACE;
}
for (int i = 0; i < addnum; i++) {
int randnum = rand() % isempty.size();
int x, y, t = 0;
auto n = isempty.begin();
for (; t < randnum; n++, t++);
x = n->first.first;
y = n->first.second;
isempty.erase(n);
maparr[x][y] = typearr[rand() % 5 + 1];
}
return OK;
}
//检查是否有清除的点
int check() {
vector<pair<int, int>> cancleararr;
for (int x = 1; x < maxsize; x++) {
for (int y = 1; y < maxsize; y++) {
if (maparr[x][y] == typearr[NONE]) {
continue;
}
canclear(x, y, cancleararr);
}
}
for (auto i : cancleararr) {
//cout << "i.first:"<<i.first << "i.second:" << i.second << endl;
if(maparr[i.first][i.second]!=typearr[NONE]){
score+=2;
}
maparr[i.first][i.second]=typearr[NONE];
}
return cancleararr.size() == 0 ? ADD : NO_ADD;
}
//交换
int swap(int y1, int x1, int y2, int x2) {
if ((maparr[x1][y1] == typearr[NONE] && maparr[x2][y2] != typearr[NONE]) ||
(maparr[x1][y1] != typearr[NONE] && maparr[x2][y2] == typearr[NONE])) {
char temp = maparr[x1][y1];
maparr[x1][y1] = maparr[x2][y2];
maparr[x2][y2] = temp;
return OK;
}
return SWAP_E;
}
//获取分数
int get_score(){
return score;
}
};
main.cpp
#include <iostream>
#include <map>
#include <time.h>
#include "head.h"
using namespace std;
//自定义输入函数
void input(string name, int &val) {
cout << "请输入" << name << ":";
cin >> val;
}
//定义个棋盘
MAP m;
//输出棋盘
void print() {
system("cls");
m.print();
}
//输入要移动的
void input() {
int x1, x2, y1, y2, sure;
while (1) {
input("要移动的第一个横坐标", x1);
input("要移动的第一个纵坐标", y1);
input("要移动的第二个横坐标", x2);
input("要移动的第二个纵坐标", y2);
cout << "要移动的两个点分别是 (" << x1 << "," << y1 << "),("
<< x2 << "," << y2 << ")";
input("是否确认(1/0)", sure);
if (sure == 1 && m.swap(x1, y1, x2, y2) == OK) {
return;
}else if(sure==1){
cout << "请输入有效位置" << endl;
}else{
cout << "取消了移动" << endl;
}
}
}
//初始化
void init() {
m.addpoint(7);
m.check();
}
//更新游戏帧
int update() {
int t=OK;
m.update_empty();
if(m.check()==ADD){
t = m.addpoint(3);
}
m.check();
return t;
}
//游戏结束
void gameover(){
cout << "游戏结束 你的得分为:"<< m.get_score() <<endl;
}
//main函数控制流程
int main() {
srand(time(NULL));
init();
while (1) {
print();
input();
if(update()==NO_PLACE){
gameover();
break;
}
}
return 0;
}
注意与不足
避免在update方法中添加耗时或阻塞类逻辑
因为每次(帧)都要调用此函数,耗时或阻塞会严重影响流畅性 当然 必须有用户输入才能继续的不算
不应使用遍历判断是否有可消除块
当棋盘变大 时间会越来越长 应使用维护变化量(交换的点或者插入的点)来查看是否有可消除的块
可以使用建图方式来进行更方便的查找块操作
用户输入输出不友好
因为时间不足 直接摆烂写输入输出