https://github.com/zhuyali/racing
Racing game on Linux
https://github.com/zhuyali/racing
c linux screen-program signal
Last synced: about 2 months ago
JSON representation
Racing game on Linux
- Host: GitHub
- URL: https://github.com/zhuyali/racing
- Owner: zhuyali
- Created: 2017-07-11T07:45:03.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2017-07-13T07:24:50.000Z (almost 9 years ago)
- Last Synced: 2025-10-07T01:48:47.530Z (9 months ago)
- Topics: c, linux, screen-program, signal
- Language: C
- Size: 7.81 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Racing
## 1.游戏简介
赛车,很多人看到这个词,脑海中首先会浮现出两辆车。但是我所做的赛车并非大家所想的那样。
我所做的赛车从本质上来讲是一个躲避障碍物的游戏:有一辆车会在屏幕的最下方,障碍物不断地从屏幕上方掉落,会造成一种滚屏的效果,这时通过人为地输入‘A’和‘D’来控制赛车的左右移动以躲避障碍物,从画面上看就好像一辆车一直在前进的过程中不断躲避障碍,因而又叫“赛车”。每前进一格,分数加一,当碰上障碍物时,游戏立刻停止,并显示总得分与输赢情况;也可以通过进入游戏后输入‘Q’来退出游戏。关于游戏状态的提示信息会出现在屏幕右上角的提示框中。
## 2.游戏客户端思路
由于第一次对Linux进行屏幕编程,因而整个游戏的编写过程也是由易到难的。大致思路为:
登录界面——>游戏界面边框——>赛车的移动动画——>利用随机数生成障碍物坐标——>制作出屏幕滚动效果——>加入碰撞条件,并制作碰撞画面——>制作提示框——>连接服务器——>与服务器进行通话。
为此,我做了一些准备工作:由于最终整个界面效果位于屏幕的正中间,并且很多坐标也都依赖于屏幕的特殊位置(如中间,边界等),因而我设置了一些常量值以方便以后的计算:MIDROW、MIDCOL、TOPDGE、DOWNDGE、LEFFDGE、RIGHTDGE。
登录界面:对于竞赛的部分,我的设想是不同客户端连接服务器后,服务器通过比分判断输赢,然后将胜者的名字输出给各个客户端,因而需要有一个名字作为标识符更加直观,所以引入了登录功能,目的是接收用户输入的用户名。从登录界面接收username后将其写入socket中以告知服务器。
游戏界面边框:主要是通过反白显示来画出游戏的两条界面分隔线。
赛车移动动画:由于涉及到用户的键盘输入,因而我使用了termios结构体来对终端进行控制,使得终端关闭回显与标准输入处理,然后通过使用while循环不断等待键盘输入,仅当输入‘A’,’D’和’Q’时,会予以左移,右移和退出游戏的操作,其余输入忽略。界面效果主要通过不断地进行move和addstr操作来更新。
障碍物生成:我的想法是在每行设置一个障碍物,障碍物的列数随机生成。因而使用srand和rand函数生成指定范围的一个整数,然后根据该坐标来进行障碍物坐标的确定。但是使用该方法却存在一个明显的问题:由于使用系统时间做随机数种子,因而每秒钟随机数种子才会变化一次,所以导致生成障碍物的时间较长,此处仍待改进。
滚屏效果:实质上就是将障碍物不断地向下移动,最下方的障碍物消失,最上方填充新的障碍物,不断循环。我使用了定时器来为障碍物设置滚动间隔,当经过一定时间后,定时器发出信号,这时在程序中加入信号处理程序signal(SIGALRM,sigResponse)来对本次信号做出处理,处理函数为void sigResponse(int),该函数主要有两个职能。职能一就是使用for循环将每一个障碍物进行滚动操作,同时对全局变量分数grade++。然后在函数中判断grade的值,分数值越高,那么定时器的间隔时间就越短,障碍物的下落速度就越快。
碰撞:每当定时信号来一次,就同时需要判断小车是否与障碍物相撞,此时便涉及到sigResponse的第二个职能:对碰撞条件进行判断,当障碍物下落到与小车的高度相等的位置及以下&&障碍物的纵坐标与小车的纵坐标重合时,判断已碰撞,那么将画面停止并且弹出对话框提示分数,并且将分数输出到socket中,再从socket中接收服务器发送来的输赢信息并进行显示,最后退出游戏。
制作提示框:考虑到游戏界面的生成时间比较长,为了使用户体验更佳,所以加入了提示框以提示用户当前的游戏状态。
连接服务器:主要通过socket与服务器进行通话,客户端连接的建立主要分为两步:socket和connect,前者主要负责建立socket,后者负责通过服务器的IP地址和端口号,将客户端连接到服务器,其中有一些健壮性输出。在做完连接后,运行该客户端程序便需要输入服务器的IP地址和端口号,因而在main函数中也有了关于参数相关健壮性的判断。
与服务器通话:主要有两步:写和读。写出到服务器的是用户名以及对应分数,从服务器端读入的是赢家姓名。不过没有处理两个客户端重名的情况,予以忽略。
## 3.游戏服务端思路
其实一开始做服务器的时候我的思维是很混乱的,因为处理完客户端的输入后要进行返回,但是返回给哪个客户端呢?这一直是困扰我的一个问题。后来我换了一个思路,给每个客户端都发一样的,由客户端自己判断结果即可。因而就有了现在的服务器思路。
首先,服务器一定得实现多线程,否则无法同时处理多客户的请求,所以主要的工作量落在多线程的处理函数handle_msg上。在该函数中,与客户端相对的主要有两次读操作和一次写操作,读到的分别是用户的名字和分数,写出的为最高分者的名字,由于要得出最高分者,因而涉及比较操作;由于用户名和分数相对于,因而我使用结构体存储每个用户的信息。
使用qsort()对结构体进行排序,排序的依据是结构体中的分数,排序后结构体数组下标为0的数据是当前最高分数者的姓名和分数信息,然后仅将最高分者的姓名写入到客户端中。客户端读入服务器消息后,比较自己的用户名与服务器发送来的用户名,若相等,则输出赢;否则,输出输并且显示出赢家的名字。
## 4.关键代码
### 4.1. 客户端关键代码

该函数是用来响应信号SIGALRM的。其中的if_else条件语句的作用是根据用户的分数来控制障碍物下落的速度,但是在此处有一个未能解决的问题:就是当定时器的时间间隔很短的时候,障碍物会变得十分稀疏:我判断出现该问题的原因应该是本次计时器的操作还没有执行完毕,就已经开始执行下次操作。在for循环内部,主要是判断小车的碰撞条件,若碰撞了就直接输出碰撞画面,否则移动障碍物。函数能执行到最后说明没有发生碰撞,此时分数++。

该函数主要封装了连接服务器的代码,包含的两个参数分别为:服务器的IP地址和端口号,返回值为tcp_socket。其中主要进行了socket操作和connect操作,分别负责建立socket和与服务器建立连接。
### 4.2. 服务端关键代码

此while循环主要负责循环不断地接收从客户端来的请求而不阻塞服务器,其中主要用到的技术是多线程技术:对于每一个客户端的请求建立一个线程用于单独处理客户端逻辑,此处usleep(300)是可有可无的,我加入该函数的主要目的是为了尽量避免函数内部对arrIndex与外部对arrIndex的修改同时进行。

该函数是服务器端每一个线程处理客户端逻辑的程序。其中读操作两次:分别为读取用户名,读取分数。写操作一次,用来写出赢家。而赢家的判断主要通过qsort排序函数来根据客户的分数对客户从大到小进行排序(排序的具体逻辑通过cmp函数实现)每次写出的都是位于数组下标为0的用户名称。但是在编写该服务器端的时候,必须加入fflush(stdout)函数,否则会出现读写的问题,原因尚未弄明白。
## 5.运行结果
### 5.1. 登录界面

### 5.2. 游戏运行界面

### 5.3. 显示分数和赢家
