建议:理解好TCP,UDP两种协议的套接口编程,在拿到题目时,根据题目再选择一种方式,然后在代码上进行更改来满足题目要求。
上机测试部分(题目要求)
设计一个多用户交互性文字游戏,具体要求如下:
⑴ 实现单个用户进入游戏界面,并描述游戏中单一场景下的,当前状况。
欢迎XXX用户进入游戏大厅,大厅内设置2个任务(简单可交互性任务),请用户可以选择完成。用户可以选择完成相应任务,得到相应积分,能累计积分,并通报,通关说明界面。
⑵单用户下,设计多场景下,并能实现多场景的切换,可以实现多任务的完成,并累计积分。
⑶设计多用户协作任务,多用户进出游戏的情景通报,多用户积分更新通报。
(编程完成以后,要求课下写一份报告。)题目要求比较泛,也没有要求具体是什么形式的游戏,全看临场发挥以及个人想法,所以看不懂也没关系,主要要会灵活运用两种协议的套接口编程。以下只是我个人看到题目想到的idea,仅供参考。(当然你也可以有其他的想法。)
设计思路:
服务端里设置两个关卡,每个关卡有两个任务(每个任务的分值不同),用户第一次登录都是从第一关卡开始闯关,完成第一关卡才能够进入第二关卡,完成所有关卡则顺利通关。用户的标识是用户的ID(我们假设用户的ID不会重复),用户在进行游戏前都要先向服务器发出要玩游戏的请求(game命令),再输入自己的ID号,才可以进行游戏。服务器端会在游戏大厅通报进入和退出大厅的用户,并随时通告每个用户的得分情况。
实现方法:
服务器端的实现主要是采用复杂UDP并发服务器,为每个发出请求的用户创建一个子进程为其服务,父进程继续等待用户的请求数据到达。服务器端使用score数组来记录每个用户的总积分,每个用户的ID为数组的下标,方便服务器通报用户积分情况和用户查询自己的积分;使用task数组来标记每关中每个任务的完成情况,也可以避免用户重复答题,重复加分(0:未完成,1:已完成);使用cut数组来标记该用户的每关完成情况(0:未通关,1:已通关),全部为1,说明用户全部通关,即闯关成功。
多个字符串数组只是为了让用户清楚菜单以及自己的闯关结果。
程序流程图:
客户端与服务器端:
第一关的大致流程:
(第二关类似)
第一关循环:
(第二关类似)
(下图太长,截成两张了)
主要代码
客户端:(gamecli.c)
int main(int argc, const char *argv[])
{
int sockfd;
int n;
char buf[1024];
struct sockaddr_in server_addr;
struct sockaddr_in peer_addr;
int addrlen = sizeof(struct sockaddr);
if(argc < 3){
fprintf(stderr,"Usage : %s ip port.\n",argv[0]);
exit(EXIT_FAILURE);
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){
perror("socket");
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
while(1)
{
printf(">");
fgets(buf,1024,stdin);
buf[strlen(buf)-1] = '\0';
//第一次输入game命令,向服务器说明你要玩游戏
n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&server_addr,addrlen);
if(n < 0){
perror("recvfrom");
}
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,&addrlen);
if(n < 0){
perror("recvfrom");
}
buf[n] = '\0';
printf("%s\n",buf);
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&server_addr,&addrlen);
if(n < 0){
perror("recvfrom");
}
buf[n] = '\0';
printf("%s\n",buf);
}
return 0;
}
服务器端(gameser.c)
void handler(int signum)
{
waitpid(-1,NULL,WNOHANG);
return ;
}
time_t ticks;
char tt[1024]; //专门用来显示用户进入游戏的时间
int score[1024];//记录每个用户的积分,数组下标是用户的ID
char welcome[]="welcome to the game world!";
char whatid[]="What is your ID?";
char request[]="enter your request:";
char commond[]="1:task1(10scores)\n2:task2(20scores)\n3:scores\n4:quit";
char commond2[]="1:task1(30scores)\n2:task2(40scores)\n3:scores\n4:quit";
char commond3[]="1:scores\n2:quit";
char answer[]="Please enter your answer";
char wrong[]="Wrong answer\nDon't be down-hearted.Try again!";
char right[]="Right answer\nChoose new options";
char task1[]="5*5=?";//25
char task2[]="3+5+8+1+20+11+22=?";//70
char task3[]="3*4*5*6=?";//360
char task4[]="3+5+8+1+20+11+22+11+22+22+33=?";//180
char already_finish[]="You have finished the task!Enter your commond again.";
int id;//当前用户ID
char buff[1024];
int do_client(struct sockaddr_in peer_addr,char *buf)
{
int task[1024];//每一关卡的任务列表
int cut[3];//关卡
memset(cut,0,sizeof(cut));
int n,i;
int sockfd;
int addrlen = sizeof(struct sockaddr);
int cmd;//收到的用户命令
int ans;
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){
perror("socket");
}
//发欢迎消息给客户端
sendto(sockfd,welcome,strlen(welcome),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
//询问ID
sendto(sockfd,whatid,strlen(whatid),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
if(n < 0)
perror("recvfrom");
buf[n] = '\0';
id=atoi(buf); /*将字符串转换为整型值。*/
printf("welcome the user :%s ",buf); //客户发来自己的ID号
ticks = time(NULL);
snprintf(tt, sizeof(tt), "%.24s\r\n", ctime(&ticks));
printf("time: %s\n",tt);//显示客户登录时间
//关卡1
memset(task,0,sizeof(task));
//第一关菜单(客户刚登上来都是第一关)
sendto(sockfd,request,strlen(request),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
while(1){
if(score[id]>=30)
{
printf("#user %d made the first cut!!!\n",id);//用户完成第一关
cut[1]=1;
break;
}
sendto(sockfd,commond,strlen(commond),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
if(n < 0)
perror("recvfrom");
buf[n] = '\0';
cmd=atoi(buf);
if(cmd==1)
{
if(task[1]==0){
sendto(sockfd,task1,strlen(task1),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
sendto(sockfd,answer,strlen(answer),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
ans=atoi(buf);
if(ans==25){//正确答案
task[1]=1;
score[id]+=10;
printf("user %d finished task1:+10 (total: %d)\n",id,score[id]);
sendto(sockfd,right,strlen(right),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else{
sendto(sockfd,wrong,strlen(wrong),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else{//已完成这个任务
sendto(sockfd,already_finish,strlen(already_finish),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(cmd==2)
{
if(task[2]==0){
sendto(sockfd,task2,strlen(task2),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
sendto(sockfd,answer,strlen(answer),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
ans=atoi(buf);
if(ans==70){
task[2]=1;
score[id]+=20;
printf("user %d finished task2:+20 (total: %d)\n",id,score[id]);
sendto(sockfd,right,strlen(right),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else{
sendto(sockfd,wrong,strlen(wrong),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else {
sendto(sockfd,already_finish,strlen(already_finish),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(cmd==3)//用户查询自己积分
{
int s=score[id];
sprintf(buf,"%d",s);
int len=strlen(buf);
buf[len]='\0';
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else if(cmd==4)
return 0;
}
memset(task,0,sizeof(task));//关卡2任务
//进入关卡2
while(1){
if(score[id]>=100)
{
printf("#user %d made the second cut!!!\n",id);//用户完成第2关
cut[2]=1;
break;
}
sendto(sockfd,commond2,strlen(commond2),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
if(n < 0)
perror("recvfrom");
buf[n] = '\0';
cmd=atoi(buf);
if(cmd==1)
{
if(task[1]==0){
sendto(sockfd,task3,strlen(task3),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
sendto(sockfd,answer,strlen(answer),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
ans=atoi(buf);
if(ans==360){
score[id]+=30;
printf("user %d finished task1:+30 (total: %d)\n",id,score[id]);
task[1]=1;
sendto(sockfd,right,strlen(right),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else{
sendto(sockfd,wrong,strlen(wrong),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(task[1]==1)//已经完成这个任务
{
sendto(sockfd,already_finish,strlen(already_finish),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(cmd==2)
{
if(task[2]==0){
sendto(sockfd,task4,strlen(task4),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
sendto(sockfd,answer,strlen(answer),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
ans=atoi(buf);
if(ans==180){
score[id]+=40;
printf("user %d finished task2:+40 (total: %d)\n",id,score[id]);
task[2]=1;
sendto(sockfd,right,strlen(right),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else{
sendto(sockfd,wrong,strlen(wrong),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(task[2]==1)//已经完成这个任务
{
sendto(sockfd,already_finish,strlen(already_finish),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
}
else if(cmd==3)//用户查询自己积分
{
int s=score[id];
sprintf(buf,"%d",s);
int len=strlen(buf);
buf[len]='\0';
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else if(cmd==4)
return 0;
}
if(cut[1]==1&&cut[2]==1)
printf("#user %d finished all tasks!!!\n",id);
while(1)//用户已完全通关,因为已完成的游戏不能重复玩,所以现在菜单只有退出和查询积分两个选项
{
sendto(sockfd,commond3,strlen(commond3),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
if(n < 0)
perror("recvfrom");
buf[n] = '\0';
cmd=atoi(buf);
if(cmd==1)//用户查询自己积分
{
int s=score[id];
sprintf(buf,"%d",s);
int len=strlen(buf);
buf[len]='\0';
sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&peer_addr,sizeof(peer_addr));
}
else if(cmd==2)
return 0;
}
return 0;
}
//./server ip port
int main(int argc, const char *argv[])
{
memset(score,0,sizeof(score));
int sockfd;
int n;
int pid;
char buf[1024];
struct sockaddr_in server_addr;
struct sockaddr_in peer_addr;
int addrlen = sizeof(struct sockaddr);
if(argc < 3)
{
fprintf(stderr,"Usage : %s ip port.\n",argv[0]);
exit(EXIT_FAILURE);
}
if(signal(SIGCHLD,handler) == SIG_ERR) {
perror("signal");
}
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){
perror("socket");
}
int on = 1;
if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0) {
perror("setsockopt failed");
exit(1);
}
//绑定地址
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
perror("bind");
printf("Word game world\n");
while(1)
{
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);
if(n < 0){
perror("recvfrom ");
}
buf[n] = '\0';
if(!strcmp(buf,"game"))//用户发出游戏请求
{
pid = fork(); //创建一个子进程来为用户服务
if(pid < 0)
{
perror("Fail to fork");
exit(EXIT_FAILURE);
}
if(pid == 0){
close(sockfd);
do_client(peer_addr,buf);
printf("user %d leave\n",id);
exit(EXIT_SUCCESS);
}
}
else{
continue;}//继续接受请求消息
}
return 0;
}
运行和测试结果:
①单个用户:ID为1 的用户成功通关的截图:(以及对应的服务器端截图)
服务器端:
用户查询自己的积分:
注:因为已完成的任务不能重复完成,此时该客户已全部通关,所以菜单只有两个选项。
上面都是回答正确的情况,下面给出用户回答错误的情况:
还有用户重复答题的情况:
②多个用户的情况:(服务器会及时通报用户进入游戏大厅以及用户加分情况)
分析结果:
如果用户答对,服务器就会给该客户加上相应的积分,并在游戏大厅进行通报。当用户通过第一关的时候,就会直接进入第二关。两关都完成的时候,闯关成功。用户若是答错,用户会卡在当前关卡,直到用户成功完成该关卡的所有任务。