设计一个猜数游戏(C/S)

设计一个猜数游戏(c语言)

环境:

Linux系统:

Linux chen-virtual-machine 5.15.0-47-generic #51-Ubuntu SMP Thu Aug 11 07:51:15 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

编译器:

gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0

Copyright © 2021 Free Software Foundation, Inc.

g++ (Ubuntu 11.2.0-19ubuntu1) 11.2.0

Copyright © 2021 Free Software Foundation, Inc.

内容:

1、框架介绍

猜数游戏即计算机事先准备好一个目标数字,然后与我们键盘输入的数字相比较,返回结果为“大了”或者“小了”,直至“猜对了”为止。

服务器先利用随机数生成一个在指定区间范围的目标数字,客户端(用户)向服务器输入其所猜测的数字,服务器根据具体情况向客户端(用户)返回其猜测的数偏大还是偏小,并记录客户端(用户)猜测的次数,如此反复,直到客户端(用户)猜对为止。服务器再将用户的id和猜对数字所用的次数输出到指定文件并维护形成一个排行榜(看看那个用户猜对数字所需的次数最少),最终实现客户端、服务器交互类型的猜数游戏。可以作为闲暇时间的智益游戏,其排行榜也可以实现好友之间的排名对战。

主要系统调用:

主要运用了socket(套接字)来解决网络中进程的通信,实现了本地进程间通信。套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。

Socket编程 总流程:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。


程序文件组成:

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_客户端

2. 演示说明

编译运行:

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_#include_02

服务器运行示例:

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_客户端_03

客户端运行示例:

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_服务器_04

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_linux_05

ubuntu 编辑 Python Path configuration ubuntu 编辑猜数字游戏_网络_06

3. 结果总结

程序实现流程:

客户端与服务器建立套接口(socket),利用 send()recv() 函数进行通信

步骤一:客户端和服务端建立联系

步骤二:客户端将确定开始游戏的信号以及用户名发送给服务端

步骤三:服务端利用 rand() 和 time() 函数生成指定范围内的随机数,作为需要猜测的数字,并告诉客户端可以开始猜测

步骤四:客户端向服务端发送所猜测的数字

步骤五:服务端接收客户端所猜测的数字,并与步骤三生成的随机数对比,比较它们的数值大小,并将结果发送给客户端进程。

步骤六:客户端根据服务端给出的大小情况,确定下次猜测的数字,并发送给服务端

重复步骤四~六,直至猜中数字

步骤七:服务端先将ranking_list.txt(记录猜数排行榜的文件)读出,将本次用户的用户名以及猜对的次数增加进排行榜,并根据猜对的次数降序生成新的排行榜,再次写入ranking_list.txt中

步骤八:客户端界面也可以输出排行榜进行查看,以及实现可以提前退出游戏

程序优点 :利用SOCKET套接口技术实现了客户端与服务端之间的相互通讯,实现了文件的读出和写入的功能,实现了C编写的客户端、服务器交互类应用

程序缺点 :对于用户数据的保存仅是利用一个文件来保存,这样安全系数低,且管理效率低。游戏输出界面过于简单。

改进方向 :可以利用MySQL等数据库语言来实现利用数据库来管理用户信息。并且可以利用JavaScript等前端技术来设计好看的游戏界面。

4. 编程总结

Socket套接口技术通讯的相关内容:

int socket(int af, int type, int protocol);     //  创建套接字      

uint32_t htonl(uint32_t hostlong);              // 将主机数转换成无符号长整型的网络字节顺序
u_short htons(u_short hostshort);               // 将整型变量从主机字节顺序转变成网络字节顺序
int listen(int sockfd, int backlog);            // 让一个套接字处于监听到来的连接请求的状态
int accept(int sockfd, struct spckaddr *addr, skcklen_t *sddlen);  //在一个套接口接受的一个连接
int connect (int sockfd,struct sockaddr *serv_addr, int addrlen);  //用于建立与指定socket的连接

int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
//调用用于s指定的已连接的数据报或流套接字上接收输入数据
int send( SOCKET s, const char FAR *buf, int len, int flags );
//调用用于钥纪纪数s指定的已连接的数据报或流套接字上发送输出数据

C语言文件操作函数:

FILE *fp;

FILE *fopen(const char *filename, const char *mode);
"r" 	打开一个用于读取的文件。该文件必须存在。
"w" 	创建一个用于写入的空文件。如果文件名称与已存在的文件相同,
        则会删除已有文件的内容,文件被视为一个新的空文件。
"a" 	追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。
"r+" 	打开一个用于更新的文件,可读取也可写入。该文件必须存在。
"w+" 	创建一个用于读写的空文件。
"a+" 	打开一个用于读取和追加的文件。
    
int fclose(FILE *stream);

int fscanf(FILE *stream, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...)

刚开始进行服务器和客户端通讯的时候,send() 和 recv() 函数两者的 buf 大小设置的不一致,一开始认为由于发送的数据量每次都很小,所以不满足一致性也可以,结果导致了send() 与 recv() 的发送接收不一致。

当客户端连续两次 send() 发送buf,服务器连续两次 recv() 接收buf时,会存在问题,可以通过在两次 send() 之间增加 sleep() 函数来解决。

Makefile 若需要生成多个可执行文件。由于Makefile只能有一个目标,所以可以构造一个没有规则的终极目标 all,并以多个可执行文件作为依赖。

make clean 可以将之前产生的可执行档及其他档案删除, 有时发现重新编译出来的档案没有更新, 可以先执行 make clean。

在函数体内一般不能用 sizeof 获取数组的长度,因为我们调用函数时,传入的是指向数组首部的指针,用 sizeof 实际上获取的是指针的长度。获取字符串数组的长度可以使用头文件 <string.h> 里的 strlen 函数。

要想读出文本文件后再重新写入,可以指定 fopen 的 mode 为 ‘w’,这样可以删除已有文件的内容,文件被视为一个新的空文件。

5. 附录源程序代码

Makefile:

all: s c
s: server.c
	gcc server.c -o s
c: client.c
	gcc client.c -o c
clean:                         
	rm  s c

服务器:

//tcp-server
#include <sys/types.h>
#include <sys/socket.h>							// 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h>							// 包含AF_INET相关结构
#include <arpa/inet.h>							// 包含AF_INET相关操作的函数
#include <unistd.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#define PORT 3339
//用户信息
typedef struct{
	char username[20];
	int mark;
}user;

//sort 对于用户的mark值进行排序
void Sort(user stu[], int N){
	for(int i = 0; i < N-1 ; i++){
		for(int j = 0; j < N-i-1 ;j++){         //冒泡排序
			if(stu[j].mark > stu[j+1].mark){    //比较mark的大小
				user temp = stu[j+1];           //mark值大的交换所在的结构体元素
				stu[j+1] = stu[j];
				stu[j] = temp;
			}
		}
	}
}

//check
int check_char(char* s1, char * s2){    
    int lop = 1;
	for(int i=0;i<strlen(s1);i++){
		if(s1[i] > s2[i]){
			lop = 0;                            // smaller
			break; 
		}
		else if(s1[i] < s2[i]){
			lop = 2;                            // bigger
		        break;   
		}
	}
	return lop;
}

// 比较两个int数值的大小
int check_int(int a, int b){
	if(a > b) return 0;
	if(a < b) return 2;
	return 1;
};

//change 将char类型转化为int类型
int char2int(char* s){
	int ans=0;
	for(int i=0;i<strlen(s);i++){
		int temp = s[i]-'0';
		ans = ans*10 + temp;
	}
	return ans;
}

int main()
{
//------------------------------message-------------------------------------//
   char sendbuf[256]="---Please guess a positive integer up to 100---";
   char sendbuf1[64]="Your result is CORRECT. Congratulations!!!";
   char sendbuf2[64]="Your guess is too SMALL.  Please try again";
   char sendbuf3[64]="Your number of guesses is too LARGE.  Please try again";
   char usrname[128];
   char buf[512];
//-------------------------------------------------------------------------//

//-----------------------------socket--------------------------------------//
   int s_fd, c_fd;	             // 服务器和客户套接字标识符
   int s_len, c_len;			 // 服务器和客户消息长度
   struct sockaddr_in s_addr;	 // 服务器套接字地址
   struct sockaddr_in c_addr;	 // 客户套接字地址
   s_fd = socket(AF_INET, SOCK_STREAM, 0);     // 创建套接字
   s_addr.sin_family = AF_INET;	               // 定义服务器套接字地址中的地址域为IPv4
   s_addr.sin_addr.s_addr=htonl(INADDR_ANY);   // 定义套接字地址
   s_addr.sin_port = htons(PORT);              // 定义服务器套接字端口
   s_len = sizeof(s_addr);
   bind(s_fd, (struct sockaddr *) &s_addr, s_len);	// 绑定套接字与设置的端口号
   listen(s_fd, 10);                                //监听,最大连接请求设为10
   printf("请稍候,等待客户端发送数据\n");
   c_len = sizeof(c_addr);                          //接收客户端连接请求
   c_fd = accept(s_fd,(struct sockaddr *) &c_addr,(socklen_t *__restrict) &c_len);  
//------------------------------------------------------------------------//  
   
//-----------------------------begin srand---------------------------------//
   
// rand + time

/*---------------------------get user name--------------------------------//
   while(1){
   	if(recv(c_fd,buf,256,0)>0){ 
   		for(int i=0;i < sizeof(buf); i++){
   			usrname[i] = buf[i];
   		}
   		printf("usrname = %s\n",usrname);
   		break;
   	}
   }
//------------------------------------------------------------------------*/
   
//-------------------------保存用户名以及获取随机数--------------------------//     
    int answer;
    while(1){
		if(recv(c_fd,buf,512,0)>0){  		
			strcpy(usrname,buf); 			   		
   			printf("usrname is %s\n",usrname);	
   			srand( time (NULL) );                  //得到随机数
  			answer = rand() % 100;                 //100以内随机数
  	/*
    		int i=0;
   			int length=0;
   			int num = answer;
   			while(1){
   				int temp = num % 10;
   				num /= 10;
   				length++;
   				sendbuf[i++] = temp+'0';
   				if(num == 0) break;
   			}
   	*/			
			printf("Randomly generated guess numbers is %d\n",answer);
			send(c_fd,sendbuf,sizeof(sendbuf),0);
			break;
		}
	}
//-------------------------------------------------------------------------//

//----------------------------GAME RUN-------------------------------------//
   int num=0;                                 // num记录猜数的轮次
   while (1){
        if(recv(c_fd,buf,512,0)>0)            //接收消息recv(c_fd,buf,256,0)>0
        {
        //read(c_fd,buf,256,0)
        //buf[sizeof(buf)+1]='\0';
        num++;
        printf("第%d次猜数\n",num);
        printf("The number the user guessed is %s\n",buf);      //输出到终端

    // send(c_fd,sendbuf,sizeof(sendbuf),0);
    // int res = check(sendbuf,buf);
        int guess = char2int(buf);
    // printf("%d %d\n",answer,guess);
        int res = check_int(answer,guess);

    //根据res的不同结果输出不同的最后语句
      	if(res == 0){
      		send(c_fd,sendbuf2,sizeof(sendbuf2),0);              //回复消息
     	 }
     	else if(res == 1){
		    send(c_fd,sendbuf1,sizeof(sendbuf1),0);              //right
		    break;
     	 }
     	else{
     	 	send(c_fd,sendbuf3,sizeof(sendbuf3),0);              //回复消息
     	 }  	
      }
   }
//--------------------------------------------------------------------------//

//--------------------------输出 RANKING LIST------------------------------//
	puts("");
    puts("------RANKING LIST------");

	int nn;	
	user a[21];

	FILE * f;
	FILE * f1;
	f = fopen("ranking_list.txt","r");	        // 读入文件
	fscanf(f,"%d",&nn);	
//	printf("%d\n",num);	
	for(int i=0;i<nn;i++)
	    fscanf(f,"%s %d",a[i].username, &a[i].mark);
    
	strcpy(a[nn].username,usrname);             // 新数据
	a[nn].mark = num; 	                        // 新数据
	Sort(a,nn+1);	                            // 排序

	for(int i=0;i<nn+1;i++)
		printf("%s %d\n",a[i].username,a[i].mark);  // 输出到终端		
	fclose(f);	

	f1 = fopen("ranking_list.txt","w");		        // 将排序结果重新写入文件
	nn++;
	fprintf(f1,"%d\n",nn);
	for(int i=0;i<nn;i++){
		fprintf(f1,"%s %d\n",a[i].username,a[i].mark);
	}	
	fclose(f1);
//-------------------------------------------------------------------------//    
   close(c_fd);							             // 关闭连接
}

客户端:

/*cp-client*/
#include <sys/types.h>
#include <sys/socket.h>	 // 包含套接字函数库
#include <stdio.h>
#include <netinet/in.h>	 // 包含AF_INET相关结构
#include <arpa/inet.h>	 // 包含AF_INET相关操作的函数
#include <unistd.h>
#include <string.h>
#define PORT 3339
#define MAX  1000

//用户信息
typedef struct{
	char username[20];
	int mark;
}user;

//读入并输出排行榜
void read_list(){ 
	puts("------RANKING LIST------");
	FILE * f;
	f = fopen("ranking_list.txt","r");
	int mark;
	int nn;
	user a[21];
	fscanf(f,"%d",&nn);
	for(int i=0;i<nn;i++){
		fscanf(f,"%s %d",a[i].username, &a[i].mark);
	}	
	for(int i=0;i<nn;i++){
		printf("%s %d\n",a[i].username, a[i].mark);
	}
	fclose(f);	
}

//游戏开始界面
void print_hello(){
	puts("         *********************      ");
	puts("       ******** WELCOME ********    ");
	puts("      ***************************   ");
	puts("     ******* Guessing Game *******  ");
	puts("    ******************************* ");
	puts("    ************ press ************ ");
	puts("     ******* 1-Game Begin ********  ");
	puts("      ****** 0-Quit Game  *******   ");
	puts("       ***** 2-Ranking List ****    ");
	puts("         *********************   ");
	printf("\nPlease enter   ");
}

//将char类型转化为int类型
int char2int(char* s){
	int ans=0;
	for(int i=0;i<strlen(s);i++){
		int temp = s[i]-'0';
		ans = ans*10 + temp;
	}
	return ans;
}

//判断两个字符串是否相等
int check(char* s1, char * s2){    
        int lop = 1;
	for(int i=0;i<strlen(s1);i++){
		if(s1[i] != s2[i]){
			lop = 0; 
			break;
		}
	}
	return lop;
}


int main() {
//-------------------------------socket----------------------------//
   int sockfd;	             // 客户套接字标识符
   int len;	                 // 客户消息长度
   struct sockaddr_in addr;	 // 客户套接字地址
   int newsockfd;
   char buf[512];            //要发送的消息
   char buf_ans[512]="Your result is CORRECT. Congratulations!!!";
   int len2;							
   char rebuf[512];  
   sockfd = socket(AF_INET,SOCK_STREAM, 0);	    // 创建套接字
   addr.sin_family = AF_INET;                   // 客户端套接字地址中的域
   addr.sin_addr.s_addr=htonl(INADDR_ANY);   
   addr.sin_port = htons(PORT);                 // 客户端套接字端口
   len = sizeof(addr);
   newsockfd = connect(sockfd, (struct sockaddr *) &addr, len);	 //发送连接服务器的请求
   if (newsockfd == -1) {
      perror("连接失败");
      return 1;
   }
   len2=sizeof(buf);
//--------------------------------------------------------------------//

//-------------------------------ready--------------------------------//   
   int sign=0;
   int dp[MAX+1];                            //dp数组用来记录每次猜数的结果
   int num=0;
   int answer;
//--------------------------------------------------------------------//

//---------------------------GAME BEGIN-------------------------------//
while(1){
    if(sign == 0){                // 利用sign标记来实现游戏开始界面的不同
     	print_hello();            // 游戏开始界面
     	sign=1;
     	int res;
     	scanf("%d",&res);
     	if(res == 0){             // res=0 直接退出游戏
     		close(sockfd);
     		puts("ByeBye~~~");
  		return 0;
     	}
     	if(res == 2){             // res=2 输出排行榜
     		read_list();
     		puts("Continue(1)  Quit(0)");
     		int sign1;
     		scanf("%d",&sign1);
     		if(sign1 == 0){
     			close(sockfd);
     			puts("ByeBye~~~");
  			return 0;
     		}
     	} 	
     	continue;
    }
    else if(sign == 1){                    // 获取参与游戏的用户名          
    	printf("Please enter your user name:  ");
    	scanf("%s",buf);
    	send(sockfd,buf,len2,0);
    	if(recv(sockfd,rebuf,512,0)>0)     //接收新消息
    	{
        	printf("\n%s\n",rebuf);        //输出到终端
    	}
    	sign = 2;
    	continue;
    }
    else printf("Please enter the number you guessed:  ");
    scanf("%s",buf); 
 //   printf("buf=%s\n",buf);
    int temp = char2int(buf);           // char -> int
/*   
    printf("temp=%d\n",temp);
    dp[num] = temp;
    printf("dp=%d\n",dp[num]);
    num++;
*/   
    dp[num++] = temp;                    //dp数组记录每次猜数的结果
    send(sockfd,buf,len2,0);             //发送消息  
    if(recv(sockfd,rebuf,512,0)>0)       //接收新消息
    {
	//rebuf[sizeof(rebuf)+1]='\0';
        printf("\n%s\n",rebuf);          //输出到终端
        if(check(rebuf,buf_ans)==1){     //游戏结束
        	answer = char2int(buf);
        	break;
        }
    }
  /*  
    if(num==5){
        sleep(1);
    	printf("You've tried five times. Come on!!!");
    	scanf("%s",buf);
    	send(sockfd,buf,len2,0);
    	recv(sockfd,rebuf,512,0);
    }
    */
}

//--------------GAME OVER-----------------------------//
// 输出猜数过程以及结果
	puts("\n\n————RECORD————");
	puts("轮次  猜数  差值");
	for (int i=0; i<num; i++)
	  printf(" %2d : %4d %+4d\n", i+1, dp[i], dp[i]-answer);
	printf("最终用%d次猜中了数字\n",num);
	puts("----GAME OVER----");
	puts("");
//---------------------------------------------------//

//-----------read ranking list----------------------//
	puts("Need to see the rankings? YES(1) NO(0)");
	int lop;
	scanf("%d",&lop);
	if(lop==1){  
		read_list();                       //读入排行榜,并在终端输出显示	
/*	
		puts("------RANKING LIST------");
		FILE * f;
		f = fopen("ranking_list.txt","r");
		int mark;
		int nn;
		user a[21];
		fscanf(f,"%d",&nn);
		for(int i=0;i<nn;i++){
			fscanf(f,"%s %d",a[i].username, &a[i].mark);
		}		
		for(int i=0;i<nn;i++){
			printf("%s %d\n",a[i].username, a[i].mark);
		}
		fclose(f);
*/		
	}
//--------------------------------------------------//
   puts("ByeBye~~~");
   close(sockfd);			           	// 关闭连接
   return 0;
}