五子棋第一章
玩家信息部分
前言
接下来的几篇文章我将带领大家实现一个工程实践的项目–五子棋。它是一个比较完整的项目,主要包括了三个部分:
1.玩家信息部分–主要实现对玩家信息的相关操作,包括玩家信息的注册、登录、保存、查看排名等功能的实现。
2.游戏内容部分–主要为五子棋游戏的内容逻辑,即五子棋游戏的实现。
3.人机对战部分–该部分主要实现五子棋游戏中的人机对战部分,我们在这里实现的人机对战不是通过随机数来实现电脑下棋的呆呆鸟人机,而是能真正赢下对局的智慧人机。
本项目完整的代码实现有1600多行(不含注释),对大家的C语言水平有一定的考验,需要大家熟练掌握C语言的语法以及使用,但我相信这个项目一定会让大家有很大的收获。
本次我将先讲解玩家信息部分的实现。
一、所需技术
本次我们使用C语言来实现这个五子棋项目,需要我们能够熟练掌握C语言的语法以及使用,同时还需要有一定的数据结构的基础,因为我们在这个项目将使用带头双向循环链表来存储玩家的信息,同时还会涉及到排序的思想和运用,再加上分文件编写的模式,我们才能很好地完成这个项目。
二、玩家信息部分实现
1.头文件部分
本次由于项目是一个比较完整的项目,所以我们不止一个头文件。我在这里定义了两个头文件,一个test.h和一个game.h。其中test.h里面主要放的就是我们的菜单以及玩家信息功能部分的函数声明和常规的头文件。game.h里面放的主要就是关于五子棋游戏内容部分实现所需的函数的声明。
game.h
#define X 16
#define Y 16
typedef struct player
{
char account[15];
char password[15];
char id[10];
int win;
int lose;
int tie;
int score;
struct player* prev;
struct player* next;
}player;
由于我们在实现玩家信息部分的时候需要用到这个定义玩家的结构体,所以我们先在game.h里面定义一个名为player的结构体来表示一个玩家,里面包含玩家的基本信息–账号,密码,ID,输赢场次,同时还有两个指针来实现双向链表。当然,棋盘的大小我们也可以顺便在这里宏定义出来。
test.h
#include"game.h"
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
player* ListInit();
void menu();
void rule();
void setcolor();
void log_in1(player* head, int num);
player* log_in2(player* head);
int checkaccount(player* head, char* pnew);
int checkid(player* head, char* pnew);
void register_account(player* head, int* psum);
void watch(player* p);
void modify(player* head);
void game(player* p, player* head, int num);
void sort(player* head, int num);
void rank(player* head, int num);
void destory(player** phead);
这个头文件包含了菜单以及玩家信息功能部分的函数声明和常规的头文件。由于是比较正式的项目,所以我们在这里的命名也没有用简单的拼音来命名,而是采用了比较标准的英文翻译来命名,所以每个函数的作用通过名字就能大致知晓。
2.菜单部分
int main()
{
enum
{
toexit,
torule,
tologin,
toregister,
torank,
tosetcolor
};
int num = 0;
player* head = ListInit();
//load(head, &num);
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case torule:
rule();
break;
case tologin:
log_in1(head, num);
break;
case toregister:
register_account(head, &num);
break;
case torank:
rank(head, num);
break;
case tosetcolor:
setcolor();
break;
case toexit:
sort(head, num);
//save(head);
destory(&head);
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
void menu()
{
printf("-------------------------------------\n");
printf("-----------------五子棋--------------\n");
printf("---------------1.查看规则------------\n");
printf("---------------2.登录游戏------------\n");
printf("---------------3.注册账号------------\n");
printf("---------------4.查看排名------------\n");
printf("---------------5.颜色设置------------\n");
printf("---------------0.退出游戏------------\n");
printf("-------------------------------------\n");
}
这里没啥好说的,就是非常常规的do…while语句嵌套switch语句做目录,循环调用menu()函数并让玩家进行选择。
head为带头双向链表的头结点,num为存储的玩家信息数量。
然后通过枚举的方法来代替case后面的1,2等数字来提高代码可读性。
这里我们先注释了两个函数load()和save(),这是最后将玩家信息存储在文件中的函数,但是这个是最后才需考虑的事情,所以我们就先注释掉了,最后才进行编写。
rule()函数,这个函数负责打印五子棋的规则。
void rule()
{
printf("五子棋的基本规则如下:\n");
printf("1.棋盘和棋子。五子棋的棋盘一般为15x15的棋盘,两名玩家分别使用黑棋(*)和白棋(#)对弈;\n");
printf("2.游戏开始:游戏由黑棋(*)先下,然后双方轮流落子;\n");
printf("3.落子规则:每次只能下一枚棋子,且必须落在棋盘的空白处。一旦棋子落在棋盘上,就不能再移动或拿掉;\n");
printf("4.胜利条件:目标是成为第一个将自己的五个棋子在水平、垂直或对角线上连成一条直线的玩家,这可以是横、竖、斜方向上的连接,形成“五连“即可获得胜利。\n");
}
setcolor()函数,负责设置界面的背景颜色。
在所需要改变窗口颜色的位置调用函数 system(“color xx”) xx分别指的是背景颜色和文字(前景)颜色。x为一位16进制数,即1-f都可以使用。可以随意组合。
0 = Black (黑) 8 = Gray (灰)
1 = Blue (蓝) 9 = Light Blue (淡蓝)
2 = Green (绿) A = Light Green (淡绿)
3 = Aqua (水绿) B = Light Aqua
4 = Red (红) C = Light Red
5 = Purple (紫) D = Light Purple
6 = Yellow (黄) E = Light Yellow
7 = White (白) F = Bright White (亮白)
这里只是一个小技巧,与项目整体关系不大,所以我们在这里就不多加赘述了,如果感兴趣的可以自己去研究一下。
void setcolor()
{
int n = 0;
do
{
enum
{
exit,
setred,
setblue,
setgreen,
setyellow,
setcyan,
setwhite,
setblack
};
printf("-------------------------------------\n");
printf("-------------请选择背景颜色----------\n");
printf("---------1.红色 2.蓝色------\n");
printf("---------3.绿色 4.黄色------\n");
printf("---------5.青色 6.白色------\n");
printf("--------------7.默认黑色-------------\n");
printf("--------------0.设置完成-------------\n");
printf("-------------------------------------\n");
scanf("%d", &n);
switch (n)
{
case setred:
system("color 40");
break;
case setblue:
system("color 90");
break;
case setgreen:
system("color 20");
break;
case setyellow:
system("color E0");
break;
case setcyan:
system("color B0");
break;
case setwhite:
system("color F0");
break;
case setblack:
break;
case exit:
system("cls");
break;
default:
printf("输入错误,请重新输入!\n");
break;
}
} while (n);
}
3.玩家信息部分
ListInit()函数,进行链表的初始化操作。这里没有传二级指针,是通过给返回值的方式来改变实参。
player* ListInit()
{
//初始化
player* phead = (player*)malloc(sizeof(player));
if (phead == NULL)
{
perror("malloc");
exit(-1);
}
phead->next = phead;
phead->prev = phead;
return phead;
}
destory(player** phead)函数,进行链表的销毁操作。这里要传二级指针,因为这样才能通过形参的解引用来改变实参,将head也进行销毁并设置为空。
void destory(player** phead)
{
assert(phead);
player* cur = *phead;
while (cur != *phead)
{
player* next = cur->next;
free(cur);
cur = next;
}
free(*phead);
*phead = NULL;
}
checkaccount(player* head, char* pnew),checkid(player* head, char* pnew)函数,顾名思义,检查玩家的账号和检查玩家ID是否存在的函数。通过对链表进行遍历检查是否存在,存在返回0,不存在返回1。
int checkaccount(player* head, char* pnew)
{
player* cur = head->next;
while (cur != head)
{
if (strcmp(cur->account, pnew) == 0)
{
return 0;
}
cur = cur->next;
}
return 1;
}
int checkid(player* head, char* pnew)
{
player* cur = head->next;
while (cur != head)
{
if (strcmp(cur->id, pnew) == 0)
{
return 0;
}
cur = cur->next;
}
return 1;
}
register_account(player* head, int* pnum)函数,进行玩家信息的注册操作。这里就需要调用上面的checkaccount(player* head, char* pnew)和checkid(player* head, char* pnew)函数检查账号和ID是否存在,如果没有存在,即不重复才能成功注册。成功注册之后对玩家的各项信息进行初始化,然后进行带头双向链表的头插,这里就是数据结构的相关知识了。
void register_account(player* head, int* pnum)
{
assert(head);
player* pnew = (player*)malloc(sizeof(player));
if (pnew == NULL)
{
perror("malloc");
exit(-1);
}
while (1)
{
printf("请输入您的账号(15个字符以内):\n");
scanf("%s", pnew->account);
int a = checkaccount(head, pnew->account);
if (a == 0)
{
printf("账号重复,请重新输入!\n");
}
else
{
break;
}
}
printf("请输入您的密码(15个字符以内):\n");
scanf("%s", pnew->password);
while (1)
{
printf("请输入您的昵称(10个字符以内):\n");
scanf("%s", pnew->id);
int b = checkid(head, pnew->id);
if (b == 0)
{
printf("昵称重复,请重新输入!\n");
}
else
{
break;
}
}
printf("恭喜您注册账号成功\n");
pnew->win = 0;
pnew->lose = 0;
pnew->tie = 0;
pnew->score = 0;
//头插
player* next = head->next;
head->next = pnew;
pnew->prev = head;
pnew->next = next;
next->prev = pnew;
(*pnum)++;
}
log_in1(player* head, int num)函数,这个函数实现1号玩家进行登录的操作。即输入账号和密码,然后遍历链表进行确认是否正确。顺便一提,这里可以加一个密码保护功能,即输密码时将密码内容变为***,有兴趣的可以自主实现一下。如果账号和密码都正确,这里就调用game()函数进入游戏部分的菜单。
void log_in1(player* head, int num)
{
assert(head);
printf("请输入您的账号:\n");
char a[15] = { 0 };
char b[15] = { 0 };
scanf("%s", a);
printf("请输入您的密码:\n");
scanf("%s", b);
player* cur = head->next;
while (cur != head)
{
if (strcmp(cur->account, a) == 0 && strcmp(cur->password, b) == 0)
{
//game(cur, head, num);
return;
}
cur = cur->next;
}
printf("账号或密码错误\n");
}
log_in2(player* head)函数,这个函数实现2号玩家进行登录的操作。为后面的玩家对战部分做好铺垫。
player* log_in2(player* head)
{
assert(head);
printf("请二号玩家登录\n");
printf("请输入您的账号:\n");
char a[15] = { 0 };
char b[15] = { 0 };
scanf("%s", a);
printf("请输入您的密码:\n");
scanf("%s", b);
player* cur = head->next;
while (cur != head)
{
if (strcmp(cur->account, a) == 0 && strcmp(cur->password, b) == 0)
{
return cur;
}
cur = cur->next;
}
printf("账号或密码错误,返回前页\n");
return NULL;
}
总结
本次五子棋项目的第一章就先到这里了,我们在这一章完成了基本目录,玩家信息的登录,注册等相关功能,下一章预计将讲解游戏部分的菜单以及对链表的内容进行排序等内容。
如需源码,可在我的gitee上找到,下面是链接。
五子棋源码
每文推荐
后弦–画风
任然–山外小楼夜听雨
邓紫棋–唯一