方格游戏
目录
〇,前言
因为是一边设计一边写代码一边写博客,所以本文的中间代码很多都不是最新版本,
代码以最终完整版为准。
一,数据建模
1,名词澄清
一个玩家有21个块,每个块由1-5个格子组成。
2,块的表示
对每个块,用最多5个点把每个格子的坐标存起来
typedef struct Point
{
int x,y;
}Point;
typedef struct Node
{
int num;
Point p[5];
}Node;
那么,如何建立坐标系呢?
对每个块拓展为它的凸包,并把最左上角的快设为(0,0)那么所有格子的坐标都是2个非负整数组成。
实际上,除了十字交叉的块之外,其他20个块都可以适当的旋转和翻转,使得它的凸包的最左上角的点上有一个格子。
int num[]={5,5,5,5,5,5,5,5,5,5,5,4,4,4,4,3,5,4,3,2,1};
int matrix[]={
0,0,0,1,0,2,1,1,2,1,
0,0,1,0,1,1,1,2,2,1,
0,0,1,0,1,1,1,2,2,2,
0,1,1,0,1,1,1,2,2,1,
0,0,0,1,1,1,1,2,2,2,
0,0,0,1,0,2,1,2,2,2,
0,0,0,1,0,2,1,2,1,3,
0,0,0,1,0,2,0,3,1,2,
0,0,0,1,0,2,0,3,1,3,
0,0,0,1,0,2,1,0,1,2,
0,0,0,1,0,2,1,0,1,1,
0,0,0,1,1,0,1,1,
0,0,0,1,0,2,1,0,
0,0,0,1,1,1,1,2,
0,0,0,1,0,2,1,1,
0,0,0,1,1,0,
0,0,0,1,0,2,0,3,0,4,
0,0,0,1,0,2,0,3,
0,0,0,1,0,2,
0,0,0,1,
0,0,
};
对应代码,有20行都是0,0开头,只有一行不是。
3,块的初始化
void init()
{
int p=0;
for(int i=0;i<21;i++){
node[i].num=num[i];
for(int j=0;j<num[i];j++)node[i].p[j]={matrix[p],matrix[p+1]},p+=2;
}
}
4,玩家数据
typedef struct Node
{
int id;
int num;
Point p[5];
bool flag; //=1表示手里还有,=0表示已经用掉了
}Node;
Node node[4][21]; //四个玩家
5,玩家初始化
void initNode(Node* node)
{
int p=0;
for(int i=0;i<21;i++){
node[i].id=i,node[i].num=num[i],node[i].flag=1;
for(int j=0;j<num[i];j++)node[i].p[j]={matrix[p],matrix[p+1]},p+=2;
}
}
void init()
{
for(int i=0;i<4;i++)initNode(node[i]);
}
二,界面
1,方格输出
只输出当前玩家的所有块,每5个一行
void outNode(int num,Node node[])
{
int x[3][30];
for(int j=0;j<3;j++)for(int k=0;k<30;k++)x[j][k]=0;
for(int i=0;i<num;i++){
for(int j=0;j<node[i].num;j++)x[node[i].p[j].x][node[i].p[j].y+i*6]=1;
}
for(int i=0;i<num;i++)cout<<setw(2)<<node[i].id<<" ";
cout<<endl;
for(int j=0;j<3;j++) {
for (int k = 0; k < 30; k++) {
if (x[j][k])cout << "Y";
else cout << " ";
}
cout<<endl;
}
cout<<endl;
}
void outNode(Node* node)
{
int s=0;
Node nod[5];
for(int i=0;i<21;i++){
if(node[i].flag==0)continue;
nod[s++]=node[i];
if(s==5){
s=0;
outNode(5,nod);
}
}
if(s)outNode(s,nod);
}
运行:
2,游戏界面
复用我五子棋的界面程序 五子棋人机对战完整代码_nameofcsdn的博客-CSDN博客_五子棋代码
void out(int i, int j)
{
if (p[i][j])cout<< col[p[i][j]];
else if (i == 1)
{
if (j == 1)printf("┏");
else if (j == N)printf("┓");
else printf("┯");
}
else if (i == N)
{
if (j == 1)printf("┗");
else if (j == N)printf("┛");
else printf("┷");
}
else if (j == 1)printf("┠");
else if (j == N)printf("┨");
else printf("┼");
}
void DrawBoard(int turn)//画棋盘
{
system("cls");
int row = 0, col = 0;
char alpha = 'A';
printf("\n\n\n ");
for (col = 1; col <= N; col++)printf("%c ", alpha++);
for (row = 1; row <= N; row++) {
printf("\n %2d", row);
for (col = 1; col <= N; col++) {
out(row, col);
}
printf("%d", row);
}
cout << endl;
outNode(node[turn]);
}
三,游戏规则
1,翻转和旋转
每个块都可以翻转、旋转
void reverse(Node &node)//翻转块
{
for(int i=0;i<node.num;i++){
int tmp=node.p[i].x;
node.p[i].x=node.p[i].y,node.p[i].y=tmp;
}
}
void rotate(Node &node)//旋转块
{
for(int i=0;i<node.num;i++){
int tmp=4-node.p[i].x;
node.p[i].x=node.p[i].y,node.p[i].y=tmp;
}
}
void rotate(Node &node,int n)//旋转块
{
if(n<=0||n>3)return;
while(n--)rotate(node);
}
考虑到旋转之后的样子,把输出块的代码改了
void outNode(int num,Node node[])
{
int x[5][30];
for(int j=0;j<5;j++)for(int k=0;k<30;k++)x[j][k]=0;
for(int i=0;i<num;i++){
for(int j=0;j<node[i].num;j++)x[node[i].p[j].x][node[i].p[j].y+i*6]=1;
}
for(int i=0;i<num;i++)cout<<setw(2)<<node[i].id<<" ";
cout<<endl;
for(int j=0;j<5;j++) {
for (int k = 0; k < 30; k++) {
if (x[j][k])cout << "Y";
else if(k%6==5)cout<<" ";
else cout<<".";
}
cout<<endl;
}
cout<<endl;
}
2,核心逻辑、结束控制
int isEnd[4];//=0表示还在继续,=1表示玩家已经不能再放了
bool end()
{
return isEnd[0]+isEnd[1]+isEnd[2]+isEnd[3]==4;
}
采用简单粗暴的方式,让玩家自己上报已经不能放了这个信号。
bool play(int turn)
{
DrawBoard();
cout<<"输入操作类型:0结束,1翻转,2旋转,3放方格";
int op;
CIN(op);
if(op<0||op>3)return false;
//
}
3,主控程序
int main() {
init();
turn=-1;
while(!end()){
turn=(turn+1)%4;
if(isEnd[turn])continue;
while(!play(turn));
}
return 0;
}
4,玩家操作
bool op0(int turn)
{
isEnd[turn]=0;
return true;
}
bool op1(int turn,int id)
{
reverse(node[turn][id]);
return false;
}
bool op2(int turn,int id)
{
cout<<"输入顺时针旋转次数1-3";
int num;
CIN(num);
rotate(node[turn][id],num);
return false;
}
bool check(int turn,int id,int r,int c)
{
return true; // 待更新,不更新也能玩
}
bool op3(int turn,int id)
{
cout<<"输入最左上角的格子放入棋盘的坐标\n";
int r,c;
CIN2(r,c);
if(!check(turn,id,r,c))return false;
Node *pn=&node[turn][id];
for(int i=0;i<pn->num;i++){
p[r+pn->p[i].x][c+pn->p[i].y]=turn;
}
return true;
}
更新play函数:
bool play(int turn)
{
DrawBoard();
cout << "输入操作类型:0结束,1翻转方格,2旋转方格,3放方格";
int op, id;
CIN(op);
if (op < 0 || op>3)return false;
if (op == 0)return op0(turn);
cout << "输入要操作的方格编号";
CIN(id);
if (id < 0 || id >= 21)return false;
if (op == 1)return op1(turn, id);
if (op == 2)return op2(turn, id);
if (op == 3)return op3(turn, id);
return false;
}
四,完整代码
#include<iostream>
#include<iomanip>
#include<windows.h>
using namespace std;
typedef struct Point
{
int x, y;
}Point;
typedef struct Node
{
int id;
int num;
Point p[5];
bool flag; //=1表示手里还有,=0表示已经用掉了
}Node;
Node node[4][21]; //四个玩家
int turn; //0-3,对应蓝黄红绿
string col[4] = { " B"," Y"," R"," G" }; //蓝黄红绿
char ccol[4] = { 'B','Y','R','G' };
const int N = 20;
int p[22][22];//棋盘是20*20
int isEnd[4];//=0表示还在继续,=1表示玩家已经不能再放了
int num[] = { 5,5,5,5,5,5,5,5,5,5,5,4,4,4,4,3,5,4,3,2,1 };
int matrix[] = {
0,0,0,1,0,2,1,1,2,1,
0,0,1,0,1,1,1,2,2,1,
0,0,1,0,1,1,1,2,2,2,
0,1,1,0,1,1,1,2,2,1,
0,0,0,1,1,1,1,2,2,2,
0,0,0,1,0,2,1,2,2,2,
0,0,0,1,0,2,1,2,1,3,
0,0,0,1,0,2,0,3,1,2,
0,0,0,1,0,2,0,3,1,3,
0,0,0,1,0,2,1,0,1,2,
0,0,0,1,0,2,1,0,1,1,
0,0,0,1,1,0,1,1,
0,0,0,1,0,2,1,0,
0,0,0,1,1,1,1,2,
0,0,0,1,0,2,1,1,
0,0,0,1,1,0,
0,0,0,1,0,2,0,3,0,4,
0,0,0,1,0,2,0,3,
0,0,0,1,0,2,
0,0,0,1,
0,0,
};
void initNode(Node* node)
{
int p = 0;
for (int i = 0; i < 21; i++) {
node[i].id = i, node[i].num = num[i], node[i].flag = 1;
for (int j = 0; j < num[i]; j++)node[i].p[j] = { matrix[p],matrix[p + 1] }, p += 2;
}
}
void init()
{
for (int i = 0; i < 4; i++) {
initNode(node[i]);
isEnd[i] = 0;
}
}
void outNode(int num, Node node[])
{
int x[5][66];
for (int j = 0; j < 5; j++)for (int k = 0; k < 66; k++)x[j][k] = 0;
for (int i = 0; i < num; i++) {
for (int j = 0; j < node[i].num; j++)x[node[i].p[j].x][node[i].p[j].y + i * 6] = 1;
}
for (int i = 0; i < num; i++)cout << setw(2) << node[i].id << " ";
cout << endl;
for (int j = 0; j < 5; j++) {
for (int k = 0; k < 66; k++) {
if (x[j][k])cout << ccol[turn];
else if (k % 6 == 5)cout << " ";
else cout << ".";
}
cout << endl;
}
cout << endl;
}
void outNode(Node* node)
{
int s = 0;
Node nod[11];
for (int i = 0; i < 21; i++) {
if (node[i].flag == 0)continue;
nod[s++] = node[i];
if (s == 11) {
outNode(s, nod);
s = 0;
}
}
if (s)outNode(s, nod);
}
void out(int i, int j)
{
if (p[i][j])cout<<col[p[i][j]-1];
else if (i == 1)
{
if (j == 1)printf("┏");
else if (j == N)printf("┓");
else printf("┯");
}
else if (i == N)
{
if (j == 1)printf("┗");
else if (j == N)printf("┛");
else printf("┷");
}
else if (j == 1)printf("┠");
else if (j == N)printf("┨");
else printf("┼");
}
void DrawBoard()//画棋盘
{
system("cls");
int row = 0, col = 0;
char alpha = 'A';
printf(" ");
for (col = 1; col <= N; col++)printf("%2d", col);
for (row = 1; row <= N; row++) {
printf("\n %2d", row);
for (col = 1; col <= N; col++) {
out(row, col);
}
printf("%d", row);
}
printf("\n ");
for (col = 1; col <= N; col++)printf("%2d", col);
cout << endl;
outNode(node[turn]);
}
void reverse(Node& node)//翻转块
{
for (int i = 0; i < node.num; i++) {
int tmp = node.p[i].x;
node.p[i].x = node.p[i].y, node.p[i].y = tmp;
}
}
void rotate(Node& node)//旋转块
{
for (int i = 0; i < node.num; i++) {
int tmp = 4 - node.p[i].x;
node.p[i].x = node.p[i].y, node.p[i].y = tmp;
}
}
void rotate(Node& node, int n)//旋转块
{
if (n <= 0 || n > 3)return;
while (n--)rotate(node);
}
#define CIN(x) while (!(cin >> x)) { \
cin.clear(); \
cin.ignore(); \
}
#define CIN2(x, y) CIN(x)CIN(y)
#define CIN3(x, y, z) CIN(x)CIN(y)CIN(z)
bool op0()
{
isEnd[turn] = 0;
return true;
}
bool op1(int id)
{
reverse(node[turn][id]);
return false;
}
bool op2(int id)
{
cout << "输入顺时针旋转次数1-3\n";
int num;
CIN(num);
rotate(node[turn][id], num);
return false;
}
bool check(int id, int r, int c, Node* pn)
{
//if (r < 1 || r>20 || c < 1 || c>20)return false;
for (int i = 0; i < pn->num; i++) {
int tr = r + pn->p[i].x,tc= c + pn->p[i].y;
if (tr < 1 || tr>20 || tc < 1 || tc>20 || p[tr][tc])return false;
}
return true;
}
bool op3(int id)
{
cout << "输入最左上角的格子放入棋盘的坐标(行,列)\n";
int r, c;
CIN2(r, c);
Node* pn = &node[turn][id];
if (!check(id, r, c, pn))return false;
for (int i = 0; i < pn->num; i++) {
p[r + pn->p[i].x][c + pn->p[i].y] = turn + 1;
}
pn->flag = 0;
return true;
}
bool play()
{
DrawBoard();
cout << "输入操作类型:0结束,1翻转方格,2旋转方格,3放方格\n";
int op, id;
CIN(op);
if (op < 0 || op>3)return false;
if (op == 0)return op0();
cout << "输入要操作的方格编号\n";
CIN(id);
if (id < 0 || id >= 21)return false;
if (op == 1)return op1(id);
if (op == 2)return op2(id);
if (op == 3)return op3(id);
return false;
}
bool end()
{
return isEnd[0] + isEnd[1] + isEnd[2] + isEnd[3] == 4;
}
void p415()
{
cout << endl;
cout << " ..... .. \n"
" . . .\n"
" . . . \n"
" . .. \n";
cout << "\n\n\n";
cout << " | / | \n"
" / | \\ / —|——\n"
" / | \\ /——|———\n"
" \\| | \n";
Sleep(5000);
}
int main() {
p415();
init();
turn = -1;
while (!end()) {
turn = (turn + 1) % 4;
if (isEnd[turn])continue;
while (!play());
}
return 0;
}
五,规则对比
这个程序比较简陋,对于用户输入的放方格的位置,我做的校验是保证放的格子都没有超出棋盘位置,也不会发生覆盖。
但是没有校验放的位置必须和同色格子点邻,不能边邻,第一个放的必须包含最角落的那个格子。