# TypeGame **Repository Path**: linbei945/type-game ## Basic Information - **Project Name**: TypeGame - **Description**: CTYYPE是一款玩法为飞机大战的打字游戏,具体功能有:可根据一篇文章或文本,自动导入单词;整个单词下落或者淡入淡出;同时多个单词出现;对输入错误的单词分析记忆功能;积分功能。 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-10-24 - **Last Updated**: 2022-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: Qt, Cpp, 面向对象 ## README # CTYYPE ## 介绍 CTYYPE是一款玩法为飞机大战的打字游戏,具体功能有:可根据一篇文章或文本,自动导入单词;整个单词下落或者淡入淡出;同时多个单词出现;对输入错误的单词分析记忆功能;积分功能。 ## 主要界面截图 1. 开始界面 ![](img/image001.jpg) 2. 帮助界面 ![](img/image002.jpg) 3. 设置界面 ![](img/image003.jpg) 4. 运行界面 ![](img/image004.jpg) 5. 结果界面 ![](img/image005.jpg) ## 程序流程图 ![](img/image006.jpg) ## 主要类及功能说明 ![](img/image007.png) ## 主要功能实现 ### 飞机结构体 定义飞机的结构如下,以实现游戏功能 ```c++ struct Plane { int tag=0;//0-正常飞行的敌机 1-被击毁的敌机 2-逃脱的敌机 int x,y;//左上角坐标 int xdirection;//初始时x轴运动方向 char letter;//飞机上的字母 QString word;//飞机上的单词 }; ``` ### 飞机上的单词显示 #### 1. 导入词库 使用QFileDialog下的getOpenFileName方法,打开文件选择框,并将文件类型限定为txt形式,将返回的文件路径以QString类型存储在变量filename中,用于后面的飞机上的单词生成。 ```c++ void GameSeting::SelectFile_Info() { fileName = QFileDialog::getOpenFileName(this,QStringLiteral("选择一个文件作为词库"),"D:",QStringLiteral("文本文件(*txt)")); ui->chooseLE->setText(fileName); } ``` #### 2. 将词库文件中的单词合理分割并取出 用IODevice方式打开文本文件,传入的参数就是文件路径filename,在每一次循环中使用文件构造流读取一行放到字符串中,设置分隔符为空格,将每一个单词存入QString数组中,并对得到的words数组使用removeDuplicates方法,该方法用于去除调用其的QStringList中的重复项目,并且不需要对QStringList中的元素进行排序,去除后仍旧保持原来的顺序,函数返回remove掉的重复条目的数目。 ```c++ void GameUI::getWords() { //用IODevice方式打开文本文件 QFile file(filename); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QString line; QString tempp=""; QTextStream in(&file); //用文件构造流 line = in.readLine();//读取一行放到字符串里 while(!line.isNull())//字符串有内容 { tempp+=line+" "; line = in.readLine();//循环读取下行 } words=tempp.split(" "); words.removeDuplicates ();// 该方法去除调用其的QStringList中的重复项目,注意不需要对QStringList中的元素进行排序,去除后仍旧保持原来的顺序,函数返回remove掉的重复条目的数目 } file.close(); } ``` ### 飞机初始化 通过随机数设置飞机的初始运动方向,0-向左,1-向右 ```c++ static int frontNumber=25; srand(unsigned(time(0)*frontNumber)); frontNumber=rand(); int xdirection=rand()%2; ``` 设置计时器和定时器,以控制游戏时间,同时使界面每450ms刷新一次,由于每次飞机的横纵坐标都会改变,所以刷新后重新绘图,就能展现出飞机运动的不同效果 ```c++ if(options==0) { timeP=new QTime(); timeP->start(); timer=new QTimer(this); timer->start(450); connect(timer,SIGNAL(timeout()),SLOT(planeupdate())); } ``` 飞机其他参数的初始化 ```c++ QString temp; temp=words[planeIndex];//设计单词 Plane newPlane; newPlane.x=x*120; newPlane.y=0; newPlane.word=temp; newPlane.xdirection=xdirection; planes.push_back(newPlane); planeIndex++; ``` ### 对于飞机运动轨迹的控制 #### 1. 运动到左右屏幕边缘改变运动方向 每次改变飞机横纵坐标之前先判断飞机的横坐标是否达到左边缘(0px),或者右边缘(1800px),如果达到就改变变量xdirection的数值,相当于改变了飞机的运动方向 以下代码是向左运动的控制段 ```c++ if(i.xdirection==0)//向左移动 { i.x=(i.x-40>=0?i.x-20:0); i.y=(i.y+60<=770?i.y+20:730); if(i.y==710) { if(i.tag!=2) { wrongCount++; wrongWords[wrongCount]=i.word; } i.tag=2; } if(i.x==0) i.xdirection=1; } ``` #### 2. 运动到底部防线判定该单词错误 此时飞机的y坐标一定大于710px,于是将变量置为2,并将这个飞机上的单词存入错误单词数组内 ```c++ else { if(i.tag!=2) { wrongCount++; wrongWords[wrongCount]=i.word; } i.tag=2; } ``` 在绘制飞机的代码中增加处理逃脱敌机的处理逻辑,将该飞机的显示图案更改为防线爆炸效果,同时不再改变该飞机的坐标,这样该飞机就会变为防线上的爆炸图案 ```c++ else { painter.drawPixmap(i.x,i.y+20,69,99,QPixmap(":/image/enemy2_down4.png")); } ``` ### 对游戏时间的控制 根据用户选择所生成的变量gameTime来定义游戏时间,分别可设置为1分钟,2分钟,3分钟和5分钟,在游戏开始时,新建一个QTimer对象,并调用start函数开始计时 ```c++ if(options==0) { timeP=new QTime(); timeP->start(); timer=new QTimer(this); timer->start(450); connect(timer,SIGNAL(timeout()),SLOT(planeupdate())); } ``` 每次刷新飞机位置时都使用elapsed判断一下已经过的时间是否达到了游戏时间,如果是,则游戏结束 ```c++ // 游戏结束的判断条件:时间到了或者单词失误数达到最大值 if((timeP->elapsed()>=gameTime)||wrongCount>=Max_wrong)//一分钟 blsOver=true; ``` ### 对游戏难易程度的控制 游戏有四种模式,分别是小白、普通、高手和大师,其区别在于不同模式的单词最大错误数不同(四种模式分别对应10,7,4,1),游戏中飞机出现的速度不同(难度越高出新飞机的速度越快)。影响出新飞机的速度的参数有两个,变量newplaneTime(四种模式分别对应15,12,9,6)和变量tplaneCount_time(四种模式分别对应0.05,0.1,0.15,0.2),以下是对于出现新飞机的判定条件逻辑 每当击落三个及以上飞机时,就更改变量newplane的值,并将变量distroynumber重新置为0,以备下一步操作,可以看到不同模式变量planeCount_time在这里的值是不同的 ```c++ if(distroynumber>=3) { if(newplane<3) { distroynumber=0; newplane+=4; } if(newplane>=3&&newplane<9) { distroynumber=0; newplane+=planeCount_time; } } ``` 下面就是新飞机出现的判定条件,由上一步的逻辑可以得出模式越困难,newplaneTime的值就越小,newplane的值就越大,导致后期出飞机的速度就越快 ```c++ if(timeRecord>=newplaneTime-newplane)//要出新飞机了 { timeRecord=0; initplane(1); } update();//更新,重新调用paintEvent() ``` ### 击落飞机功能的实现 #### 1. 从键盘读取用户输入的单词 需要重写键盘事件,持续读取用户输入的每一个字母(使用key->key()),并同步显示在输入框中,以用户输入enter键作为结束(Qt 中的回车键对应的是两个键值: Qt::Key_Enter 以及 Qt::Key_Return,分别对应的是数字键盘的Enter和大键盘区的Enter,我使用的是 Qt::Key_Return),得到一个单词,存储在变量inputWord中 ```c++ QKeyEvent *key=(QKeyEvent *)event; QVector::iterator it; if(key->key() != Qt::Key_Return) { inputWord+=key->key(); ui->UINput->setText(inputWord); } ``` #### 2. 根据输入单词击落飞机 遍历整个飞机容器,每个飞机的word值与变量inputWord进行比对,比对过程不区分大小写,如果相等则说明输入单词是正确的,成功击落对应的飞机,就将tag设置为1,表示该飞机被击落 ```c++ else { for(it=planes.begin();it!=planes.end();it++) { // 注意compare的返回值;比较不区分大小写 if(inputWord.compare(it->word, Qt::CaseInsensitive)==0) { it->tag=1; update(); score++;//增加分数 distroynumber++;//已经摧毁飞机数 ui->lcdNumber->display(score);//显示分数 break; } } inputWord=""; ui->UINput->setText(inputWord); } ``` 在绘制飞机的代码中增加处理被击中敌机的处理逻辑,首先将该飞机的显示图案更改为爆炸效果,然后使用erase函数删除该飞机,这样在之后的界面上就不会再出现已被击落的敌机 ```c++ //敌机被击中的情况 else if(i.tag==1) { if(timebomb1==0) { painter.drawPixmap(i.x,i.y,69,99,QPixmap(":/image/enemy2_down3.png")); timebomb1++; } else if(timebomb1>0) { planes.erase(&i,&i+1); timebomb1=0; } } ```