分类

课内:
不限
类型:
不限 毕业设计 课程设计 小学期 大作业
汇编语言 C语言 C++ JAVA C# JSP PYTHON PHP
数据结构与算法 操作系统 编译原理 数据库 计算机网络 软件工程 VC++程序设计
游戏 PC程序 APP 网站 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
年份:
不限 2018 2019

资源列表

  • 基于C++语言开发的Windows环境微型操作系统

    一 需求分析用高级语言编写程序,模拟实现一个简单功能的操作系统。

    实现作业调度(先来先服务)、进程调度功能(时间片轮转)
    实现内存管理功能(连续分配)
    实现文件系统功能(选做内容)
    这些功能要有机地连接起来

    二 程序设计2.1 算法简介先来先服务算法:
    如果早就绪的进程排在就绪队列的前面,迟就绪的进程排在就绪队列的后面,那么先来先服务(FCFS: first come first service)总是把当前处于就绪队列之首的那个进程调度到运行状态。也就说,它只考虑进程进入就绪队列的先后,而不考虑它的下一个CPU周期的长短及其他因素。FCFS算法简单易行,但性能却不大好。
    时间片轮转算法:
    时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。
    动态分配算法:
    主要算法有首次适应算法、循环首次适应算法、最坏适应算法、最佳是适应算法等,是一种具有较高性能的内存分配算法。
    三 程序实现3.1 开发与运行环境
    运行系统:Windows 10
    编译工具:Dev C++

    3.2 主要方法介绍


    方法
    功能




    OS()
    操作系统主界面


    jobS()
    作业调度算法(先来先服务)


    courseS()
    进程调度算法(时间片轮转)


    memory()
    内存管理算法



    3.3 关键代码作业调度
    /*作业调度 */ typedef struct JCB { //定义进程控制块 char num[2]; //作业号 char name[10]; //作业名 char state; //运行状态 int tijiaotime; //提交作业时间 int starttime; //作业开始时间 int finishtime; //结束时间 int needtime; //运行需要时间 struct JCB *next; //指向下个作业}jcb;int time=10000,n; //计时器jcb *head=NULL,*p,*q;void run_fcfo(jcb *p1) { time = p1->tijiaotime > time? p1->tijiaotime:time; p1->starttime=time; printf("\n现在时间是%d,开始运行作业%s\n",time,p1->name); time+=p1->needtime; p1->state='F'; p1->finishtime=time; printf("作业名 开始时间 所需时间 结束时间\n"); printf("%s,%d,%d,%d",p1->name,p1->starttime,p1->needtime,p1->finishtime);}void fcfo() { int i,j,t; for(j=0;j<n;j++) { p=head; t=10000; for(i=0;i<n;i++) { //找到当前未完成的作业 if(p->tijiaotime<t && p->state=='W') { t=p->tijiaotime; q=p; //标记当前未完成的作业 } p=p->next; } run_fcfo(q); }}void getInfo() { //创建进程 int num; printf("\n作业个数:"); scanf("%d",&n); for(num=0;num<n;num++) { p=(jcb *)malloc(sizeof(jcb)); if(head==NULL) {head=p;q=p;} printf("依次输入:\n作业号,作业名,提交时间,所需CPU时间\n"); scanf("%s\t%s\t%d\t%d",&p->num,&p->name,&p->tijiaotime,&p->needtime); if(p->tijiaotime < time) time=p->tijiaotime; q->next=p; p->starttime=0; p->finishtime=0; p->next=NULL; p->state='W'; q=p; }}void jobS() { printf("先来先服务算法模拟......"); getInfo(); fcfo();}
    进程调度
    /*进程调度 */ char pro[20] ; //进程 int processNum; //进程数 int timeSlice = 0; //时间片 typedef char QlemTypeChar;typedef int QlemTypeInt;typedef int Status;typedef struct QNode { QlemTypeChar data; QlemTypeInt timeArrive = 0; QlemTypeInt timeService = 0; QlemTypeInt timeCount = 0; QlemTypeInt runCount = 0; QlemTypeInt timeFinal = 0; //完成时间 QlemTypeInt timeRound = 0; //周转时间 float timeRightRound = 0; //带权周转时间 QlemTypeChar proState = 'W'; //进程的状态,W--就绪态,R--执行态,F--完成态 struct QNode *next; //链表指针 }QNode, *QueuePtr;typedef struct { QueuePtr front; //队头指针 QueuePtr rear; //队尾指针}LinkQueue;Status InitQueue(LinkQueue &Q) { Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode)); if(!Q.front) exit(OVERFLOW); Q.front->next = NULL; return OK;}Status EnQueue(LinkQueue &Q, QlemTypeChar e) { QueuePtr p; p = (QueuePtr)malloc(sizeof(QNode)); if (!p) exit(OVERFLOW); p->data = e; p->next = NULL; Q.rear->next = p; Q.rear = p; return OK;}Status DeQueue(LinkQueue &Q, QlemTypeChar &e) { QueuePtr p; if (Q.front == Q.rear) return ERROR; p = Q.front->next; e = p->data; Q.front->next = p->next; if (Q.rear == p) Q.rear = Q.front; free(p); return OK;}LinkQueue QPro;QNode qq[10];void ProGetFirst() { //取出就绪队列队首进程 InitQueue(QPro); printf("请输入要创建的进程名称:\n"); for (int i = 0; i < processNum-1; i++) { fflush(stdin); scanf("%c", &pro[i]); } fflush(stdin); for (int i = 0; i<processNum-1; i++) { qq[i].data = pro[i]; EnQueue(QPro, qq[i].data); }}void scanfData() { printf("请输入要创建的进程数目:"); scanf("%d", &processNum); processNum++; fflush(stdin); printf("\n"); ProGetFirst(); printf("创建进程到达时间:\n"); int time_Arr[10]; for (int i = 0; i < processNum-1; i++) { scanf("%d", &time_Arr[i]); } for (int i =0; i<processNum-1; i++) { qq[i].timeArrive = time_Arr[i]; EnQueue(QPro, qq[i].timeArrive); } printf("创建进程服务时间:\n"); int time_Ser[10]; for (int i = 0; i < processNum-1; i++) { scanf("%d", &time_Ser[i]); } for (int i = 0; i<processNum-1; i++) { qq[i].timeService = time_Ser[i]; EnQueue(QPro, qq[i].timeService); } printf("请输入时间片大小::"); scanf("%d", &timeSlice); printf("\n");}void ProOutPut1(){ //获取进程信息 printf("进程名\t 到达时间\t 服务时间\t 进程状态\t 执行次数\n"); for (int i = 0; i < processNum - 1; i++){ printf("%c\t\t%d\t\t%d\t\t%c\t\t%d\n", qq[i].data, qq[i].timeArrive, qq[i].timeService, qq[i].proState, qq[i].runCount); }}void CalculatetimeFinal() { //计算完成时间 int timecou=0; int countTemp = 0; QlemTypeChar ee; for (int i = 0; i < processNum - 1; i++) { countTemp += qq[i].timeService; } while (timecou < countTemp) { for (int i = 0; i < processNum - 1; i++) { if (qq[i].timeFinal == 0) { if (qq[i].timeService - qq[i].timeCount >= timeSlice) { timecou += timeSlice; } else { timecou += (qq[i].timeService - qq[i].timeCount); //DeQueue(QPro, ee); } if (timeSlice < qq[i].timeService) { //时间片大小< 服务时间 int timetemp = timeSlice > qq[i].timeService ? qq[i].timeService : timeSlice; if ((qq[i].timeCount + timetemp) < qq[i].timeService) { if (qq[i].timeService - qq[i].timeCount >= timeSlice) { qq[i].timeCount += timeSlice; } else { qq[i].timeCount += (qq[i].timeService - qq[i].timeCount); } } else { if (qq[i].timeFinal == 0) { qq[i].timeFinal = timecou; } } } else { //时间片大小>= 服务时间 qq[i].timeFinal = timecou; //该进程的完成时间=count } } } } for (int i = 0; i < processNum - 1; ++i) { qq[i].timeRound = qq[i].timeFinal - qq[i].timeArrive; qq[i].timeRightRound = (float)qq[i].timeRound / qq[i].timeService; }}void ProOutPut2() { //获取进程处理后的信息 printf("进程名\t 到达时间 服务时间 完成时间 周转时间 带权周转\n"); for (int i = 0; i < processNum - 1; i++) { printf("%c\t\t%d\t%d\t%d\t%d\t%.2f\n", qq[i].data, qq[i].timeArrive, qq[i].timeService, qq[i].timeFinal, qq[i].timeRound, qq[i].timeRightRound); }}int courseS() { scanfData(); ProOutPut1(); CalculatetimeFinal(); printf("\n"); printf("CPU处理中......\n"); printf("完成时间:"); for (int i = 0; i < processNum - 1; i++) { printf("%d,", qq[i].timeFinal); } printf("\n"); printf("周转时间:"); for (int i = 0; i < processNum - 1; i++) { printf("%d,",qq[i].timeRound); } printf("\n"); printf("带权周转时间:"); for (int i = 0; i < processNum - 1; i++) { printf("%.2f,", qq[i].timeRightRound); } printf("\n"); ProOutPut2(); return 0;}
    内存管理
    /*内存管理 */typedef struct LNode { int startaddress; int size; int state; }LNode; LNode P[L]={{0,128,0},{200,256,0},{500,512,0},{1500,1600,0},{5000,150,0}}; int N=5; int f=0; void print() { int i; printf("起始地址 分区 状态\n"); for(i=0;i<N;i++) printf("%3d %8d %4d\n",P[i].startaddress,P[i].size,P[i].state);} void First() { int i,l=0,m; printf("\n输入请求分配分区的大小:"); scanf("%d",&m); for(i=0;i<N;i++) { if(P[i].size<m) continue; else if(P[i].size==m) { P[i].state=1; l=1; break; } else { P[N].startaddress=P[i].startaddress+m; P[N].size=P[i].size-m; P[i].size=m;P[i].state=1; l=1;N++; break; } } if(l==1||i<N) { printf("地址成功分配\n\n"); printf("地址分配成功后的状态:\n"); print(); } else printf("没有可以分配的地址空间\n"); } void CirFirst() { int l=0,m,t=0; printf("\n输入请求分配分区的大小:"); scanf("%d",&m); while(f<N) { if(P[f].size<m) { f=f+1; if(f%N==0) { f=0;t=1; } continue; } if(P[f].size==m && P[f].state!=1) { P[f].state=1; l=1;f++; break; } if(P[f].size>m && P[f].state!=1) { P[N].startaddress=P[f].startaddress+m; P[N].size=P[f].size-m; P[f].size=m; P[f].state=1; l=1;N++;f++; break; } } if(l==1) { printf("地址成功分配\n\n"); printf("地址分配成功后的状态:\n");print(); } else printf("没有可以分配的地址空间\n");} void Worst() { int i,t=0,l=0,m; int a[L]; printf("\n输入请求分配分区的大小:"); scanf("%d",&m); for(i=0;i<N;i++) { a[i]=0; if(P[i].size<m) continue; else if(P[i].size==m) { P[i].state=1; l=1;break; } else a[i]=P[i].size-m; } if(l==0) { for(i=0;i<N;i++) { if(a[i]!=0) t=i; } for(i=0;i<N;i++) { if(a[i]!=0 && a[i]>a[t]) t=i; } P[N].startaddress=P[t].startaddress+m; P[N].size=P[t].size-m; P[t].size=m;P[t].state=1; l=1;N++; } if(l==1||i<N) { printf("地址成功分配\n\n"); printf("地址分配成功后的状态:\n"); print(); } else printf("没有可以分配的地址空间\n");} void Best() { int i,t=0,l=0,m; int a[L]; printf("\n输入请求分配分区的大小:"); scanf("%d",&m); for(i=0;i<N;i++) { a[i]=0; if(P[i].size<m) continue; else if(P[i].size==m) { P[i].state=1; l=1; break; } else a[i]=P[i].size-m; } if(l==0) { for(i=0;i<N;i++) { if(a[i]!=0) t=i; } for(i=0;i<N;i++) { if(a[i]!=0 && a[i]<a[t]) t=i; } P[N].startaddress=P[t].startaddress+m; P[N].size=P[t].size-m; P[t].size=m;P[t].state=1; l=1;N++; } if(l==1||i<N) { printf("地址成功分配\n\n"); printf("地址分配成功后的状态:\n"); print(); } else printf("没有可以分配的地址空间\n");} void pr() { int k=0; printf("动态分区分配算法:"); while(k!=5) { printf("\n~~~~~~~~主菜单~~~~~~~~~"); printf("\n1、首次适应算法\n2、循环首次适应算法"); printf("\n3、最坏适应算法\n4、最佳适应算法"); printf("\n5、退出\n"); printf("请选择算法:"); scanf("%d",&k); switch(k) { case 1: printf("\n初始状态为:\n"); print(); First(); continue; case 2: printf("\n初始状态为:\n"); print(); CirFirst(); continue; case 3: printf("\n初始状态为:\n"); print(); Worst(); continue; case 4: printf("\n初始状态为:\n"); print(); Best(); continue; case 5: break; default:printf("选择错误,请重新选择。\n"); } } }int memoryM() { pr(); return 0;}
    四 运行测试运行界面如下:






    1 评论 7 下载 2018-10-31 10:56:59 下载需要13点积分
  • 基于VC++的MFC框架实现的飞机大战小游戏

    一、类介绍1.1 程序使用到的MFC类库中主要的类
    CDC类
    CRect类
    CBitmap类
    CImageList类
    mfc框架:app类、wnd类、doc类、view类

    1.2 项目包含的对象类8个游戏类:

    enemy(敌人)
    bomb(敌人子弹)
    missile2(飞机子弹)、missile3(超级子弹)
    myplane(英雄机)
    explosion(爆炸)
    backgroud(背景类)GameObject(游戏对象类(父类))

    1.3 主要逻辑程序
    planefightview.cpp
    二、功能介绍2.1 飞机游戏项目功能简介飞机大战游戏是基于Windows桌面的射击类游戏,其需要实现的功能为:实现游戏对象的爆炸特效、文字提示功能和界面背景特效,其主要是遵循一定的游戏规则进行游戏。
    2.2 游戏规则游戏中的主要角色可分为如下几个基本部分:战机、敌机、战机的导弹、敌机的子弹。其主要遵循的游戏规则为:战机数量为1,由玩家通过键盘控制(方向键控制位置、空格键发射导弹和shift键发射超级导弹)战机;导弹释放存在间隔,有一定的运行速度;导弹遇到敌机发生爆炸,敌机被炸毁,导弹消失,玩家得分;由计算机控制敌机自动向战机发动攻击;敌机数量可以根据难度大小随机生成,计算机生成敌机时随机选择类别;敌机从游戏区域的上端进入,左右位置随机;普通敌机被导弹攻击即死,敌机行驶期间,不左右移动,不反向移动;运行线路为直线,方向为从上至下,不可左右移动。纵向由发射位置起至游戏区域结束;敌机子弹遇到战机时发生爆炸,战机被炸毁,子弹消失,游戏结束。
    游戏描述:游戏关卡10关,生命值50,游戏积分值每击杀1敌人加一分,积分累加到20升一关卡,敌机数量会随着游戏关卡提升而增加。
    三、相关技术此次实训的飞机大战游戏其中的技术主要就是一些函数、双向链表的使用、内存释放和双缓冲技术。
    3.1 建立基类CGameObject建立基于所有类的父类CGameObject,其中定义了两个纯虚函数作为继承CGameObject类的其他类的某些声明,例如:
    CGameObject(int x=0,int y=0);virtual ~CGameObject()virtual BOOL Draw(CDC* pDC,BOOL bPause)=0;//绘制对象virtual CRect GetRect()=0;//获得矩形区域CPoint GetPoint()//获得左上角坐标{ return m_ptPos;}static BOOL LoadImage(CImageList& imgList,UINT bmpID,COLORREF crMask,int cx,int cy,int nInitial);CPoint m_ptPos;//物体的位置
    3.2 透明图片贴图此次实训中用于贴图的函数:
    //此函数主要用于将图片放入图像链表imageList中static BOOL LoadImage(CImageList& imgList,UINT bmpID,COLORREF crMask,int cx,int cy,int nInitial);//此函数主要是讲ImageList链表中的图像显示在pDC这个句柄中ImageList.Draw(pDC,0,m_ptPos,ILD_TRANSPARENT);
    ILD_TRANSPARENT表示是透明贴图。其中主要就是CImageList图像列表。它是相同大小图像的一个集合,每个集合中均以0为图像的索引序号基数,图像列表通常由大图标或位图构成,其中包含透明位图模式。可以利用WINDOWS32位应用程序接口函数API来绘制、建立和删除图像,并能实现增加、删除、替换和拖动图像等操作。
    3.3 CObList链表//主要是定义一个CObList类的一个链表对象用于存储所有与此种相关的对象,易于后续的添加删除和提取数据CObList elist;CObList plist;CObList bomblist;CObList miss2list;CObList miss3list;;POSITION pos2,pos4,pos5,pos3;//定义指针POSITION ePos=pDoc->miss2list.GetHeadPosition()//指针读取头结点missile2 *el2=(missile2 *)pDoc->miss2list.GetNext(ePos);//指针读取下一个节点
    3.4 获得矩形区域函数CRect c;c.IntersectRect(el1->GetRect(),el2->GetRect())//判断矩形是否有交接//例子为获得敌机的矩形区域CRect GetRect(){ return CRect(m_ptPos,CPoint(m_ptPos.x+35,m_ptPos.y+35));}
    3.5 发射子弹或导弹函数missile2 *bb2;if(GetKeyState(VK_SPACE)<0) // 空格发射导弹{ bb2=new missile2(pDoc->p1.m_ptPos.x+20,pDoc->p1.m_ptPos.y-15); // 导弹 pDoc->miss2list.AddTail(bb2);}
    3.6 添加爆炸效果技术explosion e1;e1.Draw(pMemDC);BOOL Draw(CDC *pDC, BOOL bPause){ ImageList.Draw(pDC,0,m_ptPos,ILD_TRANSPARENT); return TRUE;}
    3.7 对于对象的绘制以及越出边界的处理 //绘制 导弹、爆炸、敌机、 for(POSITION ePos=pDoc->miss2list.GetHeadPosition();(pos3=ePos)!=NULL;) { // 获取用于遍历的下一个元素 missile2 *el2=(missile2 *)pDoc->miss2list.GetNext(ePos); el2->Draw(pMemDC); //发导弹,若导弹飞出屏幕则删除 if(!c.IntersectRect(el2->GetRect(),ck)) { pa2= pDoc->miss2list.GetAt(pos3); pDoc->miss2list.RemoveAt(pos3); delete pa2; } else { for(POSITION ePos=pDoc->plist.GetHeadPosition();(pos5=ePos)!=NULL;) { enemy *el1=(enemy *)pDoc->plist.GetNext(ePos); // 导弹打中敌机 if (c.IntersectRect(el1->GetRect(),el2->GetRect())) { e1.m_ptPos.x=el2->m_ptPos.x; e1.m_ptPos.y=el2->m_ptPos.y; e1.Draw(pMemDC); // 敌机爆炸,显示效果 pa = pDoc->plist.GetAt(pos5); pDoc->plist.RemoveAt(pos5); delete pa; //删除敌机 pa2= pDoc->miss2list.GetAt(pos3); pDoc->miss2list.RemoveAt(pos3); delete pa2; // 删除导弹 pDoc->p1.grade+=1; pDoc->p1.level = pDoc->p1.grade/20; hard = 8 - pDoc->p1.level; if(hard < 0){ hard = 0; } break; } } } }
    3.8 字体的个性化输出函数HFONT font;// 创建一种有特殊性的逻辑字体,此逻辑字体可以在后面被任何设备选择font=CreateFont(20,10,0,0,0,0,0,0,0,0,0,100,10,0); // 设置指定DC的背景混合模式,为透明的pMemDC->SetBkMode(TRANSPARENT); pMemDC->SelectObject(font);pMemDC->SetTextColor(RGB(255,0,0));// 弹出窗口pMemDC->TextOutW(470,200,L"GAME OVER",9); Grade.Format(L"分数:%d",pDoc->p1.grade);pMemDC->TextOutW(490,250,Grade);
    3.9 获取键盘操作函数GetKeyState()返回一个short型的数,short型是16位有符号的数据类型,如果要查询的键被按下,返回值最高位被置1,则这个数表示负数,所以可以用\<0或\>0来判断。\<0表示被按下,\>0表示没按下。
    3.10 用键盘控制战机的移动 //检测四个方向键,移动战机 if(GetKeyState(VK_LEFT)<0) { if(pDoc->p1.m_ptPos.x - 15 <= 0){ pDoc->p1.m_ptPos.x = 0; }else{ pDoc->p1.m_ptPos.x-=15; } } if(GetKeyState(VK_RIGHT)<0) { if(pDoc->p1.m_ptPos.x + 15 >= ck.right - 50){ pDoc->p1.m_ptPos.x = ck.right - 50; }else{ pDoc->p1.m_ptPos.x+=15; } } if(GetKeyState(VK_UP)<0) { if(pDoc->p1.m_ptPos.y - 15 <= 0){ pDoc->p1.m_ptPos.y = 0; }else{ pDoc->p1.m_ptPos.y-=15; } } if(GetKeyState(VK_DOWN)<0) { if(pDoc->p1.m_ptPos.y + 15 >= ck.bottom - 60){ pDoc->p1.m_ptPos.y = ck.bottom - 60; }else{ pDoc->p1.m_ptPos.y+=15; } }
    3.11 设置定时器定时器告诉WINDOWS一个时间间隔,然后WINDOWS以此时间间隔周期性触发程序。通常有两种方法来实现:发送WM_TIMER消息和调用应用程序定义的回调函数。
    this->SetTimer(1,40,NULL);//刷屏this->SetTimer(2, 200,NULL);//敌机enemythis->SetTimer(3,1500,NULL);//子弹bombthis->SetTimer(4,2,NULL);//刷新导弹
    通过定时器可以控制游戏对象的自行移动以及某些定时控制的实现。
    3.12 双缓冲技术关于双缓冲技术主要就是利用缓存的原理进行将所有的东西都先存在一个缓冲得虚拟的区域,然后再一次性的将所有的虚拟缓存中的东西都放入实在的存储器中。
    pDC=new CClientDC(this);pMemDC=new CDC;//创建内存设备上下文,与另一个设备上下文匹配。可以用它在内存中准备图像pMemDC->CreateCompatibleDC(pDC);pMemBitmap=new CBitmap;CRect ck;this->GetClientRect(&ck);//用一个位图初始化对象使之与指定设备兼容pMemBitmap->CreateCompatibleBitmap(pDC,ck.Width(),ck.Height());pMemDC->SelectObject(pMemBitmap);pDC->BitBlt(0,0,ck.Width(),ck.Height(),pMemDC,0,0,SRCCOPY);delete pDC;delete pMemDC;delete pMemBitmap;
    3.13 内存释放技术在此次实训项目中有太多的对象的生成和运用,造成了内存的极度的紧张,当游戏进行到一定的程度的时候就会出现内存溢出现象。解决此问题的技术就是内存释放。内存释放技术的实现主要是通过释放各个满足一定条件的对象和链表来实现的。
    //删除敌机pDoc->plist.RemoveAt(pos4);delete pa4;
    四、总结4.1 功能设计总结本次实训由于时间问题还有些扩展功能可进行增加,主要有:

    给游戏增加敌机类型,丰富画面,增加难度
    给游戏进行增加boss,通过boss的添加进行游戏的关卡和难度的更加进一步的提升。主要思路是:再增加一个类让它继承敌机类,然后再敌机类的基础上进行增加其生命值和被打死后的得分。当战机得分到达一定的值以后boss出现并进行和敌机一个队伍发射另类的子弹进行攻击战机
    完善暂停/重新开始/继续游戏等功能
    设置关卡,关卡越往后,难度越大,增加游戏的挑战性

    增添多种游戏道具产生各种效果从而增加游戏的趣味性。主要思路:可以设计增加新的道具类并让其继承敌机类在满足一定条件时随机从游戏上方落下然后检测道具与战机的碰撞,然后实现道具功能即可。
    4.2 结构设计体会我们认为可以将敌机类、导弹类、道具类等继承CGameObject类的所有类设计成大体规格相同的形式,然后对于View中的函数等模块加以注释区分,将类结构明显的突出出来以便于扩充新功能,其次若要改为通用对战类游戏,只需改变贴图,某些函数的参数使移动轨迹改变,然后对游戏框架进行合理的,灵活的运用。
    4.3 困难和解决的办法此次实训中遇到的问题一共有五个:

    C++的面向对象的思想和逻辑思路的还是没有掌握好,后来主要是通过向老师和同学进一步理解了些面向对象的思想和思路以及其好处
    一些关于C++的语法问题,例如静态成员和静态变量的声明问题,最后还是在同学那里理解到其不仅需要在头文件中声明,其在源文件中也是需要声明的,应为其在头文件中进行声明在源文件中不进行声明则表示其根本就没有被分配内存空间,致使出现错误
    还有在运行过程中经常发生中断,后来经过多次调试发现是链表的声明问题,有时候想当然的随便使用链表,结果发现还没有声明,解决后才发现在此次飞机大战中用了多个链表,觉得对于链表的认识有了加强
    对于在碰撞检测中避免游戏对象多次删除的问题,虽然我们没有遇到的,但是我们觉得还是代码规范的问题,只要在使用完游戏对象后及时删除并对其进行检查应该就可以避免了
    战机会移出界面,原因是没有设置边界,需要在绘制战机时判断战机位置,当位置大于窗体边界时要进行判断,为战机位置赋值为窗体边界

    4.4 项目体会对于c++的不熟悉导致对程序制作和运行的陌生,很多东西都是现学现用,还有很多问的同学才勉强解决。这次实训让我们认识到,知识在于积累,平时不努力,临时是要付出代价的,以后应认真学习知识,不能临时抱佛脚。
    五、演示游戏界面1

    游戏界面2

    游戏结束
    1 评论 28 下载 2018-10-31 10:44:59 下载需要4点积分
  • 基于QT和websocket协议的多线程文件传输

    一、目的与要求
    做两个程序,实现文件收发
    发送端放两个按钮,点击后打开电脑目录选择所要传输的文件,选好以后,把文件名和路径显示在界面上,点击第二个按钮,把文件传到远程机器(或者虚拟机)上由接收端接收
    编写一接收端,把文件接收下来,存进指定的某个目录里
    要能测试通过三个发送端同时发100M的文件,接收端能分别接收
    使用多线程实现

    二、工具/准备工作
    Qt开发环境
    websocket传输协议库
    Win10_x64

    三、分析UI界面设计,使用qtdesigner工具,设计界面如下:
    Server端

    Client端


    多线程实现
    Qt中提供了创建线程的基类,并且新创建dialog默认为多线程,利用此特性,可以实现通过创建多个dialog从而实现多个client与服务器同时传输文件。
    文件传输协议
    使用Websocket协议,实现客户端与服务端的长连接,并且可以双方相互主动推送文本消息或文件流。
    四、实现步骤
    创建项目
    编写客户端主线程程序,为按钮添加创建窗口消息相应事件



    子线程程序


    文件传输协议代码
    /********************************************************************************************************************************************************/#include "sslechoclient.h"#include <QtCore/QDebug>#include <QtWebSockets/QWebSocket>#include <QCoreApplication>#include <iostream>#include <QDebug>#include <QFile>#include <QJsonObject>#include <QJsonDocument>#include <QMessageBox>#include <QTextStream>#include <QPushButton>#include <QFileInfo>using namespace std;QT_USE_NAMESPACE//! [constructor]SslEchoClient::SslEchoClient(const QUrl &url, QObject *parent) : QObject(parent){ ssl_dia.show(); QCoreApplication::processEvents(); connect(&m_webSocket, &QWebSocket::connected, this, &SslEchoClient::onConnected); typedef void (QWebSocket:: *sslErrorsSignal)(const QList<QSslError> &); connect(&m_webSocket, static_cast<sslErrorsSignal>(&QWebSocket::sslErrors), this, &SslEchoClient::onSslErrors); m_webSocket.open(QUrl(url));}void SslEchoClient::onConnected(){ m_webSocket.sendTextMessage(QStringLiteral("Hello, world! Server,I'am client!")); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &SslEchoClient::onTextMessageReceived); connect(ssl_dia.m_pushButton, &QPushButton::clicked,this, &SslEchoClient::sendMessage); connect(ssl_dia.m_pushButton_2, &QPushButton::clicked,this, &SslEchoClient::sendFile);}void SslEchoClient::onTextMessageReceived(QString message){ ssl_dia.m_textEdit->setPlainText(message);}void SslEchoClient::onSslErrors(const QList<QSslError> &errors){ Q_UNUSED(errors); // WARNING: Never ignore SSL errors in production code. // The proper way to handle self-signed certificates is to add a custom root // to the CA store. qDebug() << "ssl Error!"; m_webSocket.ignoreSslErrors();}void SslEchoClient::sendMessage(){ QString message = ssl_dia.m_textEdit->toPlainText(); m_webSocket.sendTextMessage(message);}void SslEchoClient::sendFile(){ try{ QString path = QFileDialog::getOpenFileName(&ssl_dia, tr("Select File To Send"),".",tr("File(*)")); if(path.length()<1) return; QFileInfo info(path); QString file_name = info.fileName(); //文件发送消息标记协议 m_webSocket.sendTextMessage("f:" + file_name); QFile file(path); if(!file.open(QIODevice::ReadOnly)) { qDebug() << "Can't open file for writing"; return; }// out.setVersion(QDataStream::Qt_5_8); qDebug() << "read"; QByteArray byte = file.readAll(); m_webSocket.sendBinaryMessage(byte); qDebug() << "send"; file.close(); }catch(exception e){ e.what(); }}

    服务端消息发送预处理程序
    /********************************************************************************************************/#include "sslechoclient.h"#include <QtCore/QDebug>#include <QtWebSockets/QWebSocket>#include <QCoreApplication>#include <iostream>#include <QDebug>#include <QFile>#include <QJsonObject>#include <QJsonDocument>#include <QMessageBox>#include <QTextStream>#include <QPushButton>#include <QFileInfo>using namespace std;QT_USE_NAMESPACE//! [constructor]SslEchoClient::SslEchoClient(const QUrl &url, QObject *parent) : QObject(parent){ ssl_dia.show(); QCoreApplication::processEvents(); connect(&m_webSocket, &QWebSocket::connected, this, &SslEchoClient::onConnected); typedef void (QWebSocket:: *sslErrorsSignal)(const QList<QSslError> &); connect(&m_webSocket, static_cast<sslErrorsSignal>(&QWebSocket::sslErrors), this, &SslEchoClient::onSslErrors); m_webSocket.open(QUrl(url));}void SslEchoClient::onConnected(){ m_webSocket.sendTextMessage(QStringLiteral("Hello, world! Server,I'am client!")); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &SslEchoClient::onTextMessageReceived); connect(ssl_dia.m_pushButton, &QPushButton::clicked,this, &SslEchoClient::sendMessage); connect(ssl_dia.m_pushButton_2, &QPushButton::clicked,this, &SslEchoClient::sendFile);}void SslEchoClient::onTextMessageReceived(QString message){ ssl_dia.m_textEdit->setPlainText(message);}void SslEchoClient::onSslErrors(const QList<QSslError> &errors){ Q_UNUSED(errors); // WARNING: Never ignore SSL errors in production code. // The proper way to handle self-signed certificates is to add a custom root // to the CA store. qDebug() << "ssl Error!"; m_webSocket.ignoreSslErrors();}void SslEchoClient::sendMessage(){ QString message = ssl_dia.m_textEdit->toPlainText(); m_webSocket.sendTextMessage(message);}void SslEchoClient::sendFile(){ try{ QString path = QFileDialog::getOpenFileName(&ssl_dia, tr("Select File To Send"),".",tr("File(*)")); if(path.length()<1) return; QFileInfo info(path); QString file_name = info.fileName(); //文件发送消息标记协议 m_webSocket.sendTextMessage("f:" + file_name); QFile file(path); if(!file.open(QIODevice::ReadOnly)) { qDebug() << "Can't open file for writing"; return; }// out.setVersion(QDataStream::Qt_5_8); qDebug() << "read"; QByteArray byte = file.readAll(); m_webSocket.sendBinaryMessage(byte); qDebug() << "send"; file.close(); }catch(exception e){ e.what(); }}
    五、测试服务器窗口

    客户端主程序窗口

    客户端简历连接后窗口

    建立多个线程

    选择发送文件

    文件发送成功

    六、总结
    熟悉了网络传输协议WEBSOCKET,该协议可以在客户端与服务器之间建立长连接,并且可以双方互发信息,相比于HTTP协议,该协议更适合用于作为如聊天工具、视频直播等数据交互的领域
    进一步熟悉了QT编程,大一的时候使用过VC编写图形化界面,相比之下,QT更简单高效,并且轻量化,个人更倾向于使用QT编程
    1 评论 10 下载 2018-10-31 10:27:43 下载需要11点积分
  • 基于C语言的Linux环境下socket编程

    一 需求分析柏克莱套接字,又称为BSD 套接字是一种应用程序接口,用于网际插座与Unix域套接字,包括了一个用C语言写成的应用程序开发库,主要用于实现进程间通讯,在计算机网络通讯方面被广泛使用。
    使用Berkeley套接字的系统有很多,本系统是在Ubuntu下用C语言进行socket编程。
    二 程序设计2.1 系统流程设计如下图所示:

    2.2 数据结构设计socket编程问题中涉及的数据结构包括 套接口地址结等。
    为了实现这些数据结构,用C语言定义变量如下:
    structsockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; };
    三 程序实现3.1 TCP通信流程如下图所示:

    3.2 实现技术为实现上述设计,采用C语言,gcc4.9.2 + Vim开发环境。具体采用的技术如下:

    socket()函数 int socket(int domain, int type, int protocol);
    bind()函数 intbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    listen()函数 int listen(int sockfd, int backlog);
    connect()函数 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    accept()函数 int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
    close()函数 int close(int fd);

    实现步骤如下:

    gcc -o server server.c
    gcc -o client client.c
    ./server
    ./client

    3.3 关键代码说明为了进一步了解操作系统内核,学习了Linux操作系统的进程同步程序,主要程序源代码如下:
    -----------------------client.c---------------------#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdlib.h>#include <fcntl.h>#include <sys/shm.h>#define MYPORT 8887#define BUFFER_SIZE 1024int main() { ///定义sockfd int sock_cli = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(MYPORT); ///服务器端口 servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ///服务器ip ///连接服务器,成功返回0,错误返回-1 if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("connect"); exit(1); } char sendbuf[BUFFER_SIZE]; char recvbuf[BUFFER_SIZE]; memset(recvbuf, '\0', sizeof(recvbuf)); while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送 if(strcmp(sendbuf,"q\n")==0) break; recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收 fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock_cli); return 0;}-----------------------server.c-----------------------#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdlib.h>#include <fcntl.h>#include <sys/shm.h>#define MYPORT 8887#define QUEUE 20#define BUFFER_SIZE 1024char ans[BUFFER_SIZE];void _itoa(int n) { char ans2[BUFFER_SIZE]; int l = 0; while (n) { ans2[l++] = n % 10 + '0'; n /= 10; } int l2 = 0; while (l) { ans[l2++] = ans2[--l]; } ans[l2++] = '\n'; ans[l2] = '\0';}int main() { ///定义sockfd int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); ///定义sockaddr_in struct sockaddr_in server_sockaddr; server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(MYPORT); server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); ///bind,成功返回0,出错返回-1 if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1) { perror("bind"); exit(1); } ///listen,成功返回0,出错返回-1 if(listen(server_sockfd,QUEUE) == -1) { perror("listen"); exit(1); } ///客户端套接字 char buffer[BUFFER_SIZE]; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); ///成功返回非负描述字,出错返回-1 int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length); if(conn<0) { perror("connect"); exit(1); } while(1) { memset(buffer,0,sizeof(buffer)); int len = recv(conn, buffer, sizeof(buffer),0); if(strcmp(buffer,"q\n")==0) break; int a = 0; int i, blen = strlen(buffer); for (i = 2; i < blen - 1; i++) { if (buffer[i] != ',') { a = a * 10 + buffer[i] - '0'; } else { i++; break; } } int b = 0; for (; i < blen - 1; i++) { b = b * 10 + buffer[i] - '0'; } _itoa(a+b); fputs(buffer, stdout); send(conn, ans, strlen(ans), 0); } close(conn); close(server_sockfd); return 0;}
    这一段程序的主要功能为:

    向server发送要计算的表达式并接收计算结果
    接收client发送的表达式并计算,在终端输出表达式并把计算结果发回到client

    四 运行测试例如,客户端输入+,100,200,根据分析,运行结果应为:300
    实际运行结果如下:
    1 评论 5 下载 2018-10-31 10:21:27 下载需要4点积分
  • 基于Qt和OpenCV实现彩色图和灰度图的转换

    一、实验目的与要求1.1 目的
    熟悉Qt可视化开发,理解C++的面向对象思想
    熟悉Qt和Opencv开发环境搭建
    了解Qt消息机制
    初步理解Opencv的用法
    学会使用c++异常处理

    1.2 要求
    使用Qt编写一程序,点击按钮从电脑目录选择jpg图片,显示在界面上
    再设置一按钮或者菜单,点击后将图片黑白化或者灰度化,保存到另一个目录里,并显示出来
    再次点击按钮,重新加载彩色图像,实现两种图像的转换
    在读取文件和保存过程中,要加入异常处理(try…catch)来确保错误捕捉到自己的处理程序范围内

    二、工具与准备工作2.2 实验工具
    Qt_5.8_mingw_WIN32
    Opencv_3.4
    Win10_x64

    2.2 环境搭建
    Qt官网下载安装以上所述版本
    Opencv官网下载3.4版本源码
    由于Qt使用Mingw32位编译器,OpenCV需要自己编译。使用Qt打开OpenCV源码目录下的MakeLists.txt文件,构建项目配置为install,选择正确的编译输出目录开始编译
    编译完成后新建项目,配置OpenCV环境依赖包

    三、分析UI界面设计,使用Qt creator的可视化界面编辑工具。需要添加打开文件按钮、色彩转换按钮、退出按钮。

    需要给按钮设置对应的消息槽函数,当点击按钮时调用对应的函数。类声明如下:
    #ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QFileDialog>#include <QDebug>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>#include <opencv2/core/core.hpp>using namespace cv;namespace Ui {class MainWindow;}class MainWindow : public QMainWindow{ Q_OBJECTpublic: explicit MainWindow(QWidget *parent = 0); ~MainWindow();private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked(); void on_pushButton_3_clicked();private: Ui::MainWindow *ui; QImage img; Mat src; bool colorful = true; QString img_name;};#endif // MAINWINDOW_H
    Main函数
    #include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();}
    四、实现步骤
    编辑ui界面,添加3个push_button,并设置好布局
    分别给三个push_button创建对象的相应函数
    打开文件使用文件选择窗口,选择文件后返回文件路径

    img_name = QFileDialog::getOpenFileName( this, tr("Open Image"), ".",tr("Image Files(*.png *.jpg *.jpeg *.bmp)")); QTextCodec *code = QTextCodec::codecForName("gb18030"); string name = code->fromUnicode(img_name).data(); if(name.length()<1) return; qDebug() << "打开图片:" << img_name; try{ src=imread(img_name.toUtf8().data()); namedWindow( "src", WINDOW_NORMAL); if(!src.empty()){ imshow("src",src); } }catch(Exception e){ cout << e.err << endl; }
    色彩转换:设置bool成员变量colorful,为true表示当前为彩色状态,反之为灰度图。使用OpenCV的cvtColor(img, img, COLOR_RGB2GRAY)函数,参数COLOR_RGB2GRAY表示将OpenCV的Mat对象由RBG图转换为gray灰度图
    try{ if(colorful) { cvtColor(src, src, COLOR_RGB2GRAY); imshow("src",src); } else { src=imread(img_name.toUtf8().data());// imshow("src",src); } colorful = !colorful; }catch(Exception e){ cout << e.err << endl;
    打包发布,将程序 所需动态库与exe程序拷贝到同一文件夹。
    五、测试运行程序主界面

    选择图片窗口

    打开图片

    图片灰度化

    重新加载彩色图片

    六、实验总结创新点

    将c++课堂学习的内容应用到QT桌面开发,其中多次用到类继承、多态等面向对象思想
    使用开源图像处理库opencv,完成源代码编译动态库等工作,并学会使用基本的opencv函数显示图片和灰度化图片
    将图形界面与按钮信号关联,实现操作可视化界面

    感悟
    学会C语言、C++的基础语法,对于我们专业来说只是很小的一步,要完成我们所梦想的炫酷的程序,还有很多工具需要了解,比如这次试验的程序,虽然只是实现了很单一的功能,却需要我花大量的时间搭建qt和opencv开发环境,并且学习他们的使用方法。但是,在学习了基础的编程语言之后,的确给了我很多信心去了解并探究更多的工具和技能。
    1 评论 4 下载 2018-10-31 10:17:05 下载需要9点积分
  • 基于Python实现的论坛帖子情感分析

    一、课程项目Scuinfo文本分类分析
    二、项目类容爬取川大匿名社区SCUinfo在一段时间内的帖子,对其进行情感分类分析,包括情绪分类(积极,消极),帖子内容关联分析等。
    三、个人工作完成报告3.1 工作概述负责数据收集、预处理以及简单的情感分析
    3.2 爬虫方案scuinfo为动态加载网页,并且有移动端验证,尝试使用scrapy爬取数据失败,需要对爬虫进行大量定制优化,为了提高开发效率,最终使用了之前爬取QQ空间采用的方式:使用selenium库调用firefox浏览器驱动,该再通过代码模拟人为操作浏览器。获取到对应页面数据后,使用etree和xpath选取相应的目标节点数据。

    优点:轻松解决网页动态加载,登录验证,移动端验证等问题
    缺点:该方式需要一直保持浏览器前台运行,并且只能为单线程模式,爬取效率相对较低

    关键代码如下所示:
    print("准备打开浏览器")driver = webdriver.Firefox() print("打开浏览器")driver.maximize_window()driver.get("https://scuinfo.com/") time.sleep(1)driver.find_element_by_css_selector('body').click()#浏览器翻页控制for i in range(1, 20000): driver.find_element_by_xpath("//body").send_keys(Keys.DOWN) time.sleep(0.001) selector = etree.HTML(driver.page_source)divs = selector.xpath('//div[@id="homeList"]/div[@style="padding: 12px 25px 4px; border-bottom: 12px solid rgb(242, 242, 242);"]') #这里使用 a 表示内容可以连续不清空写入with open('scuinfo.txt','a',encoding='utf-8') as f: for div in divs: content = div.xpath('./div[2]/div[1]/span/text()') comment_num = div.xpath('./div[2]/div[2]/div[2]/div[1]/div[2]/text()') name = div.xpath('./div[1]/div[1]/div[2]/text()') good_num = div.xpath('./div[2]/div[2]/div[2]/div[2]/div[2]/text()')
    3.3 数据存储为了方便初期直观的处理数据,使用Mysql数据库保存爬虫数据集。最终数据5000余条。存储方式使用自己编写的Mysql操作类实现。每次爬虫进程创建一个数据库连接对象。
    3.4 文本分词使用第三方python库jieba分词,以及词汇情感标注开源语料库,将分词结果保存到数据库,每个数据项包括词汇内容,频数,词性,以及情感评分,其中,积极情感分数为正,消极情感分数为负数。分数绝对值代表情感激烈程度。
    3.5 数据展示分词结果合计41795条,帖子总数合计5429条。

    3.6 初步情感分析使用Corpus语料库,该语料库包含一个词汇词汇情感评分的标签库,该标签库数据为微博帖子训练而来,由于微博与scuinfo平台相似度较高,因此理论上数据用在本场景中会有比较不错的效果。
    本次情感评分方式为直接将每个帖子分词后,将所有分词的情感评分相加。此处分别展示积极和消极情绪的前3条。
    积极情绪:
    1. 华为财务精英挑战赛,粤港澳赛区区域赛招募新队员1至3人,截止日期为华为那边案例下发日期之前。我们是Nikolas队伍,原团队成员为三个中山,两个川大,一个暨南,由于一些老队员由于期末考、实习等原因无法参赛,先重新招募新队员和我们一起参加粤港澳赛区的比赛。我们的队伍通过了暨南赛区线上筛选14进4,之...... 54.500145667723522. 2018年腾讯游戏安全技术竞赛,由腾讯游戏安全主办的2018年游戏安全技术竞赛,作为国内首个专注于“游戏安全”领域的技术赛事,旨在提高高校学生以及游戏从业者对游戏安全领域的认知,并通过竞赛的形式,挖掘技术人才,共同完善与捍卫游戏安全的防线。本次竞赛不仅提供了丰厚的现金奖励,参赛者还有机会与专...... 54.500145667723523. 同道共识,方谓同仁;志同道合,共赴理想。地产top10的绿城中国暑期实习生校园招聘“同道人2.0计划”开启啦!现面向川大,招募2名校园大使,我们希望你:•执行力超强,能按照招聘节点计划,完成相应任务;•亲和力、沟通力与团队协作能力nice,能协同其他大使小伙伴,愉快高效地完成任务;•19届...... 54.1780398103219消极情绪:
    1. 真心讨厌双标* 有室友晚上写论文第二天一个劲的说别人吵到自己,别人睡觉又一个劲的说话 有病了解一下?真的今天说话一直被打断只想说敲里嘛敲里马,真的很没素质ok,老是说打断你说话的人有问题,你自己有点 B,真的火大了 -33.03775467582962. 十二舍有个女人,吹头发三角插头,于是我就走过去插了那个两脚插头,于是她吹完后狠狠一拔她的插头,把我的吹风机一下子拔出来然后没有一句话拔腿就跑,我他妈转过头去想问她为啥这样结果人已经跑远,请问你为什么要这么恶意?我妨碍到你了?两个人可以同时用并且不妨碍你你为什么非要我一个人霸占?果然绝大多数长得好看...... -30.857462722284013. t在校大学生的苦逼,四川大学校车站的庸车,尾号为3512,3102,1339的车子,害得我出了丢钱包事故,把我搞的半死不活。 在打电话之前虽然很难过但是知道明明钱包就在三辆车的其中之一上。而现在是打电话后司机找都不找就说钱包肯定不在车上,这样的庸车留着有何用? -29.93353905734由以上结果可知,前三条为消极情绪的帖子,识别结果是比较准确的,而对于积极情绪的帖子,前面多数为招聘广告,从内容来看,广告内容的确多为积极词汇,这也是影响分析结果的主要原因。
    3.7 词云生成为了直观感受川大学生在匿名平台讨论的主题,使用python库按词频生成词云,结果如下图。

    四、总结本学期网络数据挖掘课程上了解了很多机器学习算法,虽然没有很透彻的体会到它们的精妙,但激发了我学习机器学习的兴趣。期末项目选题也不是很理想,但在一定程度上提高了我的代码能力。希望以后能够在数据挖掘和分析的道路上走得更远。
    1 评论 16 下载 2018-10-31 10:08:30 下载需要11点积分
  • 分别基于WIN32 API界面编程和Cocos2d-x实现的两个版本FlappyBird游戏

    1 开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《Flappy Bird 》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。
    2 需求分析2.1 数据分析本项目要开发的是一款游戏,游戏是幻想与现实之间的桥梁,设计一款精美的游戏,既能娱乐,又能提升自我实力,是一次不错的实践。
    开发的系统要求界面友好,方便直观,功能易懂。
    2.2 可行性分析
    经济可行性分析:从支出、收益以及两者之间的关系来分析,还需要进行投资回收期分析、敏感性分析
    技术可行性分析:对提出的主要技术路线进行分析
    社会可行性分析:从组织内外部的社会环境入手来分析,如系统在法律方面和使用方面的可行性

    2.3 设计模式分析在底层win32设计下,程序实体类采用抽象工厂模式,每个实例工厂生产一个产品族内的所有类,包括实体本身与对应实体的画笔等,而游戏中的各个状态可采用状态模式设计,但考虑到游戏状态的拓展性较低以及,游戏的状态较少,故暂不采用状态模式。
    在cocos2d-x引擎下,引擎已经帮我们实现了大部分的功能,其中较为突出的有导演类的单例模式,有限自动机的状态模式,通知中心的观察者模式等等。
    3 项目设计3.1 项目主要设计
    运行可执行文件,即可进入本游戏,点击开始按钮,游戏开始
    在win32下,小鸟通过响应空格按钮进行飞翔,cocos2d-x平台下,通过响应鼠标点击进行飞翔
    小鸟通过飞过每一根柱子实现分数加成
    小鸟碰撞柱子或碰撞陆地或离开屏幕区域死亡,记录死亡分数,游戏结束
    通过分数榜更新每次游戏的分数以及最高分数
    通过点击重新开始按钮开始新一轮的游戏,并且恢复游戏中各个实体的状态
    其中,各个实体的信息会随每次游戏的开始于结束展现不同的表现形式,如小鸟的颜色,背景的切换等等

    3.2 系统功能模块图设计本游戏模块图分为四个模块图,一个模块为实体数据的表示以及操作,如图3.2.1所示,一个模块为数据的显示模块,通过对应实体的画笔来实现,如图3.2.2所示,一个模块为游戏逻辑流程控制模块,如图3.2.3所示,一个模块为实体间的联系处理模块,如图3.2.4所示。
    实体数据管理模块图

    数据显示模块图

    游戏逻辑流程控制模块

    3.3 系统主要功能UML设计游戏时序图
    时序图如下,运行程序进入开始游戏,点击开始按钮后进入游戏界面,如若小鸟未死则一直维持游戏进行的状态,否则结束游戏,再而点击开始按钮再次进行游戏。

    绘制画笔与统筹画笔的画板,以及实体类图,即画板画笔类图
    采用观察者模式,将数据存储(资源基类)与数据显示(画笔基类)分隔开,并由画板类统筹管理,优点如下:

    具体主体(画板)和具体观察者(画笔基类)是松耦合关系,主体画板仅仅依赖于画笔观察者接口,但不需要知道具体是哪个类
    满足“开闭”原则,由于主体画板只针对画板基类编程,而无需关心画板子类,因此便于子类的拓展


    逻辑控制类图
    其中,逻辑类负责游戏状态的切换,即对各个状态的画板的切换,处理类负责实体类数据的处理,用户只需关心逻辑类即可。

    实体抽象工厂类图
    实体以及实体的绘制对象生成采用抽象工厂模式,优点如下:

    为用户创建一系列相关的对象,即每个工厂生产一个产品族,如小鸟工厂生产小鸟类对象以及小鸟画笔类对象,使得用户和创建这些对象的类脱耦
    使用抽象工厂模式可以方便的为用户配置一系列对象。用户使用不同的具体工厂就能得到一组相关的对象,同事也能避免用户混用不同系列中的对象
    在抽象工厂中,可以随时增加“具体工厂”为用户提供一组相关的对象,拓展到较为完善的类图如图3.3.5所示



    4 程序实现4.1 主要功能的实现开始场景的演示,包括动画显示以及按钮的点击事件响应等。

    在win32版本下,动画显示通过消息死循环的游戏绘制方法中实现,按钮点击事件通过响应窗口的回调方法响应。代码如下,效果如图4.1.1所示
    while(msg.message!=WM_QUIT){ if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg); } else{ //获取当前系统运行时间 g_dwNow=GetTickCount(); //根据时间进行游戏绘制 if(g_dwNow-g_dwPre>150){ Game_Paint(); } } } return 0;//回调实现LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){ switch(message){ //窗口重绘处理 case WM_PAINT: ValidateRect(hwnd,NULL); break; case WM_LBUTTONDOWN: //交由逻辑类处理 g_logic.setState(g_logic.treatButton(g_logic.getState(),lParam)); break;


    在cocos2d-x引擎下,与win32下大同小异,通过调用层类的帧更新方法以及精灵的动画动作来实现整个场景的动画效果,而按钮响应则通过菜单的回调方法实现响应,代码如下,效果如图4.1.2所示
    //每帧更新方法void CPlayScene::update(float delta){ p_Bird->fly(); p_Land->move(); p_Pipe1->move(); p_Pipe2->move(); treatPipeWithNum(); treatPipeWithBird();}//鸟类动作实现void CBird::runAnimate(){ //获取精灵帧缓存 CCSpriteFrameCache* frameCache=CCSpriteFrameCache::sharedSpriteFrameCache(); //小鸟动画动作的创建 CCSpriteFrame* pSpriteFrame=NULL;//动画帧对象指针 CCArray* pFrameArray=CCArray::create();//动画帧数组 for(int i=0;i<3;i++){ //通过精灵帧缓存获取每个精灵帧,并加载到动画帧数组中 pSpriteFrame=frameCache->spriteFrameByName(CCString::createWithFormat("bird%d_%d.png",bird_color,i)->getCString()); pFrameArray->addObject(pSpriteFrame); } CCAnimation* pAnimation=CCAnimation::createWithSpriteFrames(pFrameArray);//由动画帧数组初始化动画 pAnimation->setLoops(-1);//动画永久循环 pAnimation->setDelayPerUnit(0.15f);//设置每一帧的播放延迟,可控制小鸟飞行的粒度 CCAnimate* pAnimate=CCAnimate::create(pAnimation);//通过动画创建动画动作 this->runAction(pAnimate);}//按钮菜单回调方法,即点击重新开始按钮void CPlayScene::buttonPlayCallBack(CCObject* pSender){ p_Num->setScore(0); p_Land->setMoveState(true); p_Bird->setAliveAgain(); p_Bird->setPosition(ccp(CCommon::BIRD_X,CCommon::BIRD_Y)); p_Pipe1->setMove(); p_Pipe1->setPipePositionX(CCommon::WIN_WIDTH-CCommon::PIPE_WIDTH/2+250); p_Pipe2->setMove(); p_Pipe2->setPipePositionX(CCommon::WIN_WIDTH-CCommon::PIPE_WIDTH/2+430); p_ScorePane->setVisible(false); p_Button->setVisible(false);}void CPlayScene::treatPipeWithNum(){ static bool b_pipe1=false; static bool b_pipe2=false; if(p_Num && p_Pipe1 && p_Pipe2){ //对管子1的判断 if(p_Pipe1->getPipePositionX()>CCommon::NUM_COUNT){ b_pipe1=true; } if(p_Pipe1->getPipePositionX()<CCommon::NUM_COUNT){ if(b_pipe1){ p_Num->scoreAddCount(); b_pipe1=false; } } //对管子2的判断 if(p_Pipe2->getPipePositionX()>CCommon::NUM_COUNT){ b_pipe2=true; } if(p_Pipe2->getPipePositionX()<CCommon::NUM_COUNT){ if(b_pipe2){ p_Num->scoreAddCount(); b_pipe2=false; } } }}

    游戏主界面主要包括对该场景中的所有元素进行每帧的逻辑判断以及管理,并且通过响应鼠标点击或者空格按钮按下事件来控制鸟类安全度过每根管子来获得高分。

    在win32下,每帧的逻辑判断在场景的绘制中同时处理,空格的响应由窗口的消息回调方法中处理,代码如下,效果如图4.1.3所示
    //空格键处理 case VK_SPACE: //交由逻辑类处理 g_logic.treatSpace(g_logic.getState()); break; }//处理空格void CLogic::treatSpace(int state){//委托给CTreat对象处理空格 p_treat->treatSpace(state);}//处理空格void CTreat::treatSpace(int state){ //只有在游戏进行时才处理空格消息 //其实这里不加入判断也是可以的 //因为在鸟类中开始状态的飞行方式不涉及yspeed这个成员 //故更改了yspeed对于开始状态的飞行鸟类也没有影响 if(p_bird->getState()==CConst::GAME_DOING){ p_bird->setYspeed(CConst::FLY_SPEED); }}


    在cocos2d-x游戏引擎下,逻辑处理则交由游戏层的更新方法实现,鼠标点击事件也交由游戏层来捕获以及处理,关键代码如下所示,效果如图4.1.4所示
    //让小鸟飞bool CPlayScene::ccTouchBegan(CCTouch *pTouches, CCEvent *pEvent){ if(p_Bird->IsAlive()){ p_Bird->setYspeed(-3); } return true;}//每帧更新方法void CPlayScene::update(float delta){ p_Bird->fly(); p_Land->move(); p_Pipe1->move(); p_Pipe2->move(); treatPipeWithNum(); treatPipeWithBird();}void CPlayScene::treatPipeWithBird(){ float birdPosX=p_Bird->getPositionX(); float birdPosY=p_Bird->getPositionY(); if(birdPosY<CCommon::LAND_HEIGHT+20){ //撞到地面死亡 p_Bird->setPositionY(CCommon::LAND_HEIGHT+20); switchToEndState(); return ; } if(birdPosY>CCommon::WIN_HEIGHT+20){ //飞出屏幕死亡 p_Bird->setPositionY(CCommon::WIN_HEIGHT+20); switchToEndState(); return ; } float posX1=p_Pipe1->getPipePositionX(); float posX2=p_Pipe2->getPipePositionX(); float posUpY=0.0f; float posDownY=0.0f; //矩形碰撞检测 if(abs(posX1-birdPosX)<CCommon::PIPE_WIDTH/2+CCommon::BIRD_SIZE/2+10){ posUpY=p_Pipe1->getPipeUpSprite()->getPositionY(); posDownY=p_Pipe1->getPipeDownSprite()->getPositionY(); if(abs(posX1-birdPosX)<CCommon::PIPE_WIDTH/2+CCommon::BIRD_SIZE/2 && (abs(posUpY-birdPosY)<CCommon::PIPE_HEIGHT/2+CCommon::BIRD_SIZE/2 || abs(posDownY-birdPosY)<CCommon::PIPE_HEIGHT/2+CCommon::BIRD_SIZE/2)){ switchToEndState(); return ; } } if(abs(posX2-birdPosX)<CCommon::PIPE_WIDTH/2+CCommon::BIRD_SIZE/2+10){ posUpY=p_Pipe2->getPipeUpSprite()->getPositionY(); posDownY=p_Pipe2->getPipeDownSprite()->getPositionY(); if(abs(posX2-birdPosX)<CCommon::PIPE_WIDTH/2+CCommon::BIRD_SIZE/2 && (abs(posUpY-birdPosY)<CCommon::PIPE_HEIGHT/2+CCommon::BIRD_SIZE/2 || abs(posDownY-birdPosY)<CCommon::PIPE_HEIGHT/2+CCommon::BIRD_SIZE/2)){ switchToEndState(); return ; } }}

    游戏结束界面由得分榜以及所有“死亡”状态的各个元素组成,同时通过响应重新开始按钮重新进入游戏主界面。

    在win32下,通过鸟类的状态来更新整个游戏流程,而鸟类的监控则由每次重绘时实现实时监控,同时通过点击重新开始按钮开始新一局的游戏,代码如下,效果如图4.1.5所示
    //处理小鸟和柱子动作(即小鸟碰撞处理)int CTreat::treatBirdWithPipe(){ int state=CConst::GAME_DOING; if(p_bird && p_pipe && p_pipePre){ //小鸟遁地则go die if(p_bird->getTop()>CConst::BG_HEIGHT-CConst::BIRD_SIZE-CConst::LAND_HEIGHT){ p_bird->setTop(CConst::BG_HEIGHT-CConst::BIRD_SIZE-CConst::LAND_HEIGHT+15); //遁地之时,状态切换 state=CConst::GAME_END; p_bird->setState(CConst::GAME_END); p_bird->setIsAlive(false); p_pipePre->setState(CConst::GAME_END); p_pipe->setState(CConst::GAME_END); p_num->setState(CConst::GAME_END); p_land->setState(CConst::GAME_END); } //判断小鸟与柱子是否接触,接触则go die //小鸟中心点 int i_birdXCore=p_bird->getXCorePoint(); int i_birdYCore=p_bird->getYCorePoint(); //高度宽度还是直接用CConst里面的常量算了 //鸟高宽 //柱子高宽 //一柱子中心点,及高度 int i_pipePreXCore=p_pipePre->getXUpCorePoint(); int i_pipePreYUpCore=p_pipePre->getYUpCorePoint(); int i_pipePreYDownCore=p_pipePre->getYDownCorePoint(); int i_pipePreUpLen=p_pipePre->getUpLen(); int i_pipePreDownLen=p_pipePre->getDownLen(); //处理一柱子}

    在cocos2d-x游戏引擎开发情况下,通过层类的帧更新来捕获小鸟的状态来控制场景的切换,小鸟的状态为死亡状态则切换至结束界面,否则,则场景处于其他状态,同理,通过响应按钮事件来进行场景切换。代码如下所示,效果如图4.1.6所示
    void CPlayScene::treatPipeWithBird(){ float birdPosX=p_Bird->getPositionX(); float birdPosY=p_Bird->getPositionY(); if(birdPosY<CCommon::LAND_HEIGHT+20){ //撞到地面死亡 p_Bird->setPositionY(CCommon::LAND_HEIGHT+20); switchToEndState(); return ; } if(birdPosY>CCommon::WIN_HEIGHT+20){ //飞出屏幕死亡 p_Bird->setPositionY(CCommon::WIN_HEIGHT+20); switchToEndState(); return ; } float posX1=p_Pipe1->getPipePositionX(); float posX2=p_Pipe2->getPipePositionX(); float posUpY=0.0f; float posDownY=0.0f; //矩形碰撞检测}

    在实现基本功能的基础上,对鸟类的颜色变化,背景的切换等进行了拓展优化,其中,通过在鸟类中增加颜色成员变量来实现对颜色变化的实现win32版本与引擎版本大同小异。代码如下所示,对比图如图4.1.7和图4.1.8所示。
    void CBird::setAliveAgain(){ isAlive=true; yspeed=0; g=0.2f; randColor(); runAnimate();}//颜色随机方法void randColor(){ srand((unsigned)time(0)); bird_color=rand()%3;}



    拓展对比图1
    拓展对比图2









    5 系统测试经过测试,两个版本下的游戏皆能完整的走完整个程序流程,对碰撞的检测均可完成,基本实现了游戏的要求,并且在此基础上对鸟的颜色以及背景的切换,以及最高分数的永久存储方面进行了拓展,使得游戏可玩性大大提高。
    6 系统比较两个版本下的游戏皆基本实现,设计思路大同小异,区别在于:
    采用windows底层的API绘制图元的时候,只支持位图,在进行游戏开发时,需要调用API来处理位图,增大了游戏开发的难度,而且在通过底层API处理后,效果依然达不到当今主流的画质,并且在进行每帧图像的绘制时,采用消息循环直接控制帧率达不到很好的流畅效果。
    采用引擎开发,无需关心图片处理细节,引擎封装了图元的处理以及大部分游戏流程的控制功能,使得游戏开发难度大大减小,但是相比于使用windows底层API,对于引擎开发需了解引擎的功能,提高了前期开发的难度,并且在代码细节以及debug的处理方面不如windows底层API处理的直观,但由于引擎为开源的cocos2d-x引擎,也使得游戏开发相比于其他引擎简介。
    7 分析与问题7.1 程序亮点在本次课程设计所做的游戏中,有以下几个亮点:

    在本游戏中,采用了两种方法来实现游戏的开发,便于进行游戏效果的比较以及游戏拓展功能的实现
    在本游戏中,采用用多种设计模式,包括抽象工厂,观察者模式等等,便于整体程序的拓展以及整体代码的调试以及更改
    在本游戏中,吸引用户的主要为每次飞跃过柱子的分数累加以及简易的操作,让人爱不释手

    7.2 程序的不足
    游戏的效率不太高,在数据的处理方面可以寻找更加合适的算法,如图元之间的碰撞算法等
    对引擎了解不太深入,在一些数据处理方面可以采用读取配置文件方式实现,而不是代码中写死,同时,在资源的加载方面可以采用异步加载降低程序场景切换时的卡顿以及提高游戏的流畅性以及可玩性
    拓展功能较少,还有很多地方值得拓展的地方,如增加网络,实现联机对战,以及分数上传等等

    参考文献[1] (美)(AndreLamothe).《WINDOWS游戏编程大师技巧》(第2版). 中国电力出版社, 2004
    [2] 钟迪龙.《Cocos2d-x游戏开发之旅.电子工业出版社, 2013
    [3] 毛星云.《逐梦旅程:Windows游戏编程之从零开始》. 清华大学出版社. 2013年11月
    [4] 耿祥义, 张跃平.《Java设计模式》. 清华大学出版社
    1 评论 7 下载 2018-10-31 10:00:22 下载需要4点积分
  • 基于C#和MYSQL数据库实现的课程自动考试系统

    1 问题定义考试作为教学环节中一个必不可少的部分,是衡量教学质量的重要指标,因此保证考试客观、准确、公平成为永恒的话题,这不仅关系到人才的选拔和教学的质量,而且涉及孩子的未来及千家万户的幸福。随着社会对高知识人才日益增加的需求及高考扩招所带来的学生数量越来越多的社会现状,教师和管理人员的工作量将越来越大,考试管理是一项非常繁琐并且容易出错的事情,可以说传统的考试模式已经不能再适应社会发展的需求。随着科学技术的发展,人们迫切希望能够利用这种技术解决这个问题,减轻教师和管理人员的工作压力,提高工作效率,使管理更科学化、规范化,同时提高考试的公平性和信息的安全性。 为了方便学生随时能参加考试进行查漏补缺,有效的规划未来学习计划。为了减少改卷工作人员的工作量。同时减少批改时的出错率,提高工作效率。主要使用者教师、在校学生以及其他参考人员等。
    2 功能需求
    本系统支持新用户注册功能。注册信息包括用户名、密码。其中用户名要求:4-16个字符;密码要求:6-16个字符
    登录分为学生登录与教师登录,输入用户名和密码,在下方选项中选择学生或教师后登陆
    试卷智能生成,系统根据学生选择科目自动随机从题库整理出一套该科目完整的试卷,题目包括选择题,填空题与判断题
    试卷智能批阅,系统根据题库中所给定的标准答案与提交答案对比后产生分数显示给学生
    题库管理,教师登录后可进行各科目增加、删除和查看试题库中题目等操作
    试卷管理,教师可对该系统生成过的试卷进行查看、及删除
    成绩管理,系统自动统计学生考试成绩。

    3 非功能需求
    在C/S模式下,学生确认提交试卷后,必须在10秒钟之内显示其考试成绩
    本系统客户端支持所有windows系统,但服务器与客户端须在同一设备上运行
    系统需易于维护,投入较少时间就能改善完备系统
    系统题库中对应各题的答案必须正确无误

    4 功能模型学生用例图

    教师用例图

    类图


    学生顺序图

    活动图

    5 数据设计
    设计一个学生表,有姓名,学号,分数三个字段,学号为主键
    设计一个教师表,有id,有姓名两个字段,id为主键
    设计一个试题表,有作业id,题目选项,题目答案三个字段,id为主键
    1 评论 29 下载 2018-10-31 09:21:07 下载需要4点积分
  • JAVA实现的基于PCAP库的网络监视器

    摘 要Wireshark(前称Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换。本网络监视器就是模拟Wireshark的设计思想,是一个简单的网络监视分析软件。
    利用pcap这个抓包库给抓包系统提供了一个高层次的接口。所有网络上的数据包,甚至是那些发送给其他主机的,通过这种机制,都是可以捕获的。当利用pcap获取到包后,再将这个包依据不同的协议进行拆分,封装的方法详细程度超过了pcap库本身的方法。
    通过该网络监视器的实现,能够清晰地看到数据包里面的内容。实现触发功能,让网络监视器在发生某种或某些情况时开始或停止捕获信息。实现数据捕获筛选功能。包括:通过协议筛选、通过地址筛选、通过数据模式筛选。实现捕获数据显示功能。由用户选定显示内容,以清晰易懂的方式显示数据。分析数据功能。将捕获到的数据帧进行拆分分析。最后设计了美观易用的图形界面。
    关键字:网络监视 捕捉数据包 数据帧拆分 筛选
    1 引言抓包(packet capture)就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作,也用来检查网络安全。抓包也经常被用来进行数据截取等。本实验实现了抓包过程中的截获阶段,并将数据包进行分析。在数据包的分析过程中,有助于开发者更加理解网络协议,在真正意义上实现了协议的拆分试验。目的也是让软件使用者能清晰网络捕捉包中的包数据分析以及结构。
    2 总体设计2.1 功能分析
    实现触发功能,让网络监视器在发生某种或某些情况时开始或停止捕获信息
    实现数据捕获筛选功能。通过协议筛选、通过地址筛选、通过数据模式筛选
    实现捕获数据显示功能。由用户选定显示内容,以清晰易懂的方式显示数据
    分析数据功能。将捕获到的数据帧进行拆分分析
    设计美观易用的图形界面。

    2.2 技术分析主要使用JAVA进行后台开发,利用pcap库获取捕捉到的数据包,然后逐一拆分成不同协议的数据。前端利用HTML展示,其中利用了vue.js实现前端数据绑定。
    2.3 使用方法在源码包中找到startCap.jar,点击启动。然后找到monitor中的index.html就可以进行操作。
    3 详细设计3.1 程序设计流程
    3.2 功能模块
    3.3 功能测试初始界面

    点击开始按钮捕捉包

    停止

    获取数据包详细数据分析

    筛选协议为IP,mac地址为48-a1-95-97-65-26的包

    4 总结4.1 工作小结此次网络监视器设计让我对网络协议有了更加清晰明了的了解,在实际上明白了一个数据包如何封装如何解析,让五层网络结构不再是纸上谈兵。“纸上得来终觉浅,绝知此事要躬行”是我这次实验的最大收获,在错误分析,bug修复中使自己的编程能力有了总体提高,在项目结构上有了清晰明了的了解。
    4.2 存在的问题此网络监视器虽然让我受益良多,但任然存在一些问题。最大的问题是,该项目采用web软件的形式,但是前后端分离不够清晰,虽然整体上采用了MVC结构,但是目前比较优秀的网站设计,应该是前后端完全分离。前端模块化,采用MV*架构,后端采用MVC架构,前后端之间的沟通可以采用遵守RESTFUL规范的API接口进行数据传输。
    4.3 改进的方法改进的方法应该是重构整个架构,细节无需改变,将前后端项目分开开发,解决跨域问题。要实现前端模块化,前端可以采用MV*架构,后端采用MVC架构,前后端之间的沟通可以采用遵守RESTFUL规范的API接口进行数据传输。
    1 评论 10 下载 2018-10-31 09:14:43 下载需要7点积分
  • 基于Android实现的电梯调度模拟

    一、使用说明1.1 项目简介某一楼有20层,操作者可以通过使用5部电梯从起始楼层来到达指定的楼层。本项目通过实现电梯调度,来模拟实现操作系统的调度过程,并且学习Android环境下使用Java的多线程编程方法以及调度算法。
    1.2 项目要求
    操作者可以在1~20层任意一层进入电梯并到达指定楼层
    电梯中有一些按键供操作者使用,开门键(缩短开门时间)、关门键(缩短关门时间)、上行键、下行键(前往上层或下层)
    每部电梯外部和内部有一个数码器用于显示当前电梯当前所在楼层
    5部电梯相互联结,即当一个电梯按钮按下去时,其它电梯相应按钮同时点亮,表示也按下去了。所有电梯初始状态都在第一层,且每个电梯没有相应请求情况下,在原地保持不动。在电梯到达起始楼层或终点楼层时,会自动进入电梯内部,进入电梯内部后可以在电梯内部等待到达终点楼层,也可以退回到外部界面进行下次操作
    电梯运行时每层楼需要1秒,开门或关门时需要5秒

    1.3 操作手册运行程序后,首先进入操作界面,即电梯外部。如图,共有5部电梯,且每部电梯有数码显示器和上下行按键。由于界面大小的限制,将电梯数字按键改为手动输入起始点和终点,位于5部电梯下方。以下只演示一部电梯的使用情况,其余电梯使用情况相同,只需根据调度算法去选择不同电梯来执行相应任务。
    1.3.1 情况一:1至15层
    第一步,输入起始点和终点。

    第二步,点击任一上行按键可启动电梯,目前电梯状态都在初始楼层1楼且都处于静止状态,该情况下默认第一部电梯启动。当电梯到达起始点楼层时,会自动进入电梯内部。

    第三步,电梯内部的数码显示器会显示当前楼层。其中刚进入电梯时会有5秒延迟,该时间用于乘客进入电梯,可以提前点击左下方关门按钮让电梯立即启动。由于界面大小问题,无法在保证美观的前提下完全装下20个按键,因此该电梯只需在电梯外输入起始点和终点。
    左上角的返回按钮可以让视角返回上一层界面观察电梯调度情况。



    界面1
    界面2









    第四步,当电梯到达指定楼层后,会有5秒延迟来打开电梯门,此时点击开门按键会立刻开门离开电梯,返回电梯操作界面。可以看到此时第一部电梯已到达15层。可以在该界面继续进行其他操作。

    1.3.2 情况二:电梯从1层前往20层时,有乘客在10层按键希望到达18层第一步:输入起始点和终点,点击向上按键,自动进入电梯界面,电梯开始自动向上前进。



    界面1
    界面2









    第二步:点击左上角返回按键,返回上一层,输入另一乘客的请求楼层,起始点10层,终点18层。调度算法会调用同向的第一部电梯。

    第三步:电梯到达10层后会自动进入电梯内部,直到到达18层后会离开电梯。

    第四步:电梯在将中间乘客送到指定地点后,继续前往原终点20层。

    1.4 注意事项
    输入的起始点和终点必须是1~20的数字,且在按电梯按键时该输入框不可为空,若为空会有提示。


    5部电梯会出现满负荷操作现象,即所有电梯都在运行,若此时操作过快会引起系统错误,此时会有提示禁止操作。


    在进入电梯内部后,只有在电梯停止过程中,即乘客刚进入电梯的过程和电梯刚到终点等待乘客离开电梯时,才可以使用开门与关门键,其他时间使用电梯不会做出任何反应。
    电梯可以接受多个中间请求,但同一中间请求只会有一部电梯响应。如A电梯在从1层前往20层的过程中,如果中间有乘客请求从10层到18层,A电梯会响应该请求,但如果在电梯到达10层前多次按电梯按键,其他电梯不会做出任何反应,只有A电梯会继续前往10层。

    二、概述2.1 基本思路该电梯调度项目所采取的电梯调度算法与操作系统中进程调度算法中的抢占式SJF调度算法类似,因为SJF调度算法对于给定的一组进程,平均等待时间最小,且抢占式SJF调度算法适用于电梯项目,在电梯上升或下降过程中,可能会出现同方向的楼层发出请求,抢占式SJF调度算法满足该现象,可以使同方向的乘客共用一部电梯。因此采用此算法作为基本思路。
    根据抢占式的SJF调度算法,本项目的调度算法的基本思想为三步:

    同方向上有至少一个正在赶到乘客所在楼层的电梯,选择距离最近的。根据抢占式SJF调度算法,如果后来请求的进程所需的时间更短,则提前执行该进程,当该进程执行完后,恢复原进程。电梯调度同理,在某个电梯执行某个请求时,如果后来存在该电梯同向上的请求,且电梯正在前往目标楼层时,电梯会响应该请求,并先将该乘客送往目的地后,继续执行原请求。项目中最重要的步骤为此步,严格按照抢占式SJF调度算法实现,平均等待时间最小
    若没有同方向的电梯,调度距离最近的空闲电梯。这与抢占式SJF调度算法刚开始的情况相同,并没有进程需要执行,一旦有请求,立马放入执行过程中,无需等待
    若既没有同方向也没有闲置的电梯,一直等待到有电梯闲置

    2.2 主要文件
    MainActivity.class & activity_main.xml (主文件,即电梯外部操作界面)
    Inner_elavator.class & inner_elavator.xml(电梯内部文件)
    TextCircleView.class(数码显示器控件)
    Elavator.class(电梯类,相当于进程,既包含了相应的电梯操作,也包含了操作系统中的PCB)
    DeletableEditText.class(输入框控件)

    三、具体实现3.1 PCB在该项目中引用了操作系统中PCB的概念,即进程控制块,其中包含了电梯的一些状态以及一些电梯信息:

    3.2 调度算法由于可以直接得到乘客的起点和终点,电梯可以直接判断乘客的前进方向是向上还是向下。算法中的choice为将要调用的电梯的标号,初始为-1,distance数组为各电梯距离起点的距离。根据抢占式的SJF调度算法,本项目的调度算法的基本思想为三步:
    1.同方向上有至少一个正在赶到乘客所在楼层的电梯,选择距离最近的。

    根据算法要求,首先遍历五部电梯寻找同向电梯,若有同向,修改电梯距离至起始点的位置,默认情况距离为100。在此之后,对数组distance进行遍历,寻找最短距离,若有同向电梯,将该电梯标志赋给choice。若没有则证明无同向电梯,进行下一步判断空闲电梯的距离。
    2.若没有同方向的电梯,调度距离最近的空闲电梯。

    若无同方向电梯,经过第一步后choice仍为-1,之后判断各个电梯是否空闲,若空闲则修改其至起点距离,并寻找最短距离,将其电梯标号赋给choice。
    3.若既没有同方向也没有闲置的电梯,一直等待到有电梯闲置。此时choice仍为-1,只有在有电梯闲置的时候才可以接受请求。
    3.3 电梯方向判断Elavator类提供的Judge方法可以判断电梯此时的方向,并且能够将初值起点、终点、方向赋给当前电梯。
    3.4 电梯执行当前位置到起点的过程Elavator类提供的Pos_to_Start方法可以让电梯在接受到指令后,从当前位置前往目标楼层的起始点。首先判断当前是否到达指定位置,根据方向不同对该电梯的位置count进行修改,若到达指定位置,即到达起点时,进入电梯内部,转入活动Inner_elavator,同时原操作界面在电梯停止五秒后,判断此时乘客要求电梯向上还是向下,修改电梯方向值。

    3.5 电梯调用Elavator类中的startTimer()方法让系统可以调用该电梯。在电梯不是停止的条件下,向线程发送更新UI的消息,之后根据电梯状态,是否有被其他楼层请求,是否到达起点等情况调用不同的方法。
    3.6 电梯接受其他楼层请求Elavator类中的Add()方法实现了其他楼层可以向同向电梯发出请求并得到接受。对正在调用的电梯的midEnd、midStart进行判断,判断该电梯是否存在中间请求,若存在,电梯停止5秒,留给乘客上下行。

    3.7 电梯从起点到终点Elavator类中的Start_to_End()方法实现了电梯从起点前往终点的过程。在未到达指定位置前,通过修改count来修改电梯当前位置,若到达终点后,停止5秒留给乘客离开电梯,同时判断该电梯是否仍存在中间请求,即有人在该电梯前往终点的路上进入了电梯,并选择了其他楼层作为终点,如存在则继续前往下一个终点。
    3.8 电梯内部的实现电梯内部的实现也是依靠创建一个线程。在进入电梯内部时,同时将起点、终点等信息传入该活动。
    首先给1秒钟的思考时间,乘客是否需要快速关门,若关门则取消剩余的等待时间,直接开始运行。在电梯到达之前按下开门键不会有任何反应,只有当电梯到达之后,才会立即退出电梯。

    四、总结这次的项目主要是学习操作系统知识中的进程调度算法,其中涉及的知识点包括进程的创建和状态切换、PCB以及进程调度算法中的SJF调度算法。通过这次项目不仅是对操作系统中这一方面的知识点了解更深,对Android环境下的进程创建和管理有了更多的了解。
    由于创建的文件是APP,受制于屏幕的大小,没有将20个数字键全部装入屏幕,而是采取了更为简便的直接输入。这与项目要求略有不符,没有把20个电梯按键全装入,因为初学Android,虽然能实现全部20个电梯按键在电梯内部的功能,但是会出现大量的重复代码(作为初学者的我认为可能需要20个监听?),而且UI做的很丑,希望助教能手下留情。
    第一次使用Android和Java写有关进程的算法,项目中可能还存在一些问题,比如有时切换电梯内外时略有延迟,会造成电梯内外数据不同步的现象,而且电梯频繁的内外切换可能会影响观察数据,因为一些开关门的时间问题,很多时候会注意到关门出电梯的时候,该电梯已经前往其他有需求的楼层,这一点由于不同操作者对电梯进行操作是在内部,而外部的进程无法突然中断,所以会造成内外不同步的现象,但是这种现象算是比较少数的,比较多数的情况可能是因为进程开太多了,导致有时楼梯的数字时间是对的,但会一下走2个数字,即用2秒从1楼直接显示3楼,这个问题我不是很懂……所以没能解决。
    1 评论 5 下载 2018-10-31 09:05:56 下载需要11点积分
  • 基于C语言的C0文法编译器设计与实现

    一 需求分析1.1 文法说明程序获取的文法为难度系数为3的for文法

    <加法运算符> ::= +|-
    <乘法运算符> ::= *|/
    <关系运算符> ::= <|<=|>|>=|!=|==
    <字母> ::= _|a|...|z|A|...|Z
    <数字> ::= 0|<非零数字>
    <非零数字> ::=1|...|9
    <字符> ::= ‘<加法运算符>’|’<乘法运算符>’|’<字母>’|’<数字>’
    <字符串> ::= “{十进制编码为32,33,35-126的ASCII字符}”
    <程序> ::= [<常量说明>][<变量说明>]{<有返回值函数定义>|<无返回值函数定义>}<主函数>
    <常量说明> ::= const<常量定义>;{const<常量定义>;}
    <常量定义> ::= int<标识符>=<整数>{,<标识符>=<整数>}| char<标识符>=<字符>{,<标识符>=<字符>}
    <无符号整数> ::= <非零数字>{<数字>}
    <整数> ::= [+|-]<无符号整数>|0
    <标识符> ::= <字母>{<字母>|<数字>}
    <声明头部> ::= int<标识符>|char<标识符>
    <变量说明> ::= <变量定义>;{<变量定义>;}
    <变量定义> ::= <类型标识符>(<标识符>|<标识符>‘[’<无符号整数>‘]’){,(<标识符>|<标识符>‘[’<无符号整数>‘]’) }
    <常量> ::= <整数>|<字符>
    <类型标识符> ::= int | char
    <有返回值函数定义> ::= <声明头部>‘(’<参数>‘)’ ‘{’<复合语句>‘}’
    <无返回值函数定义> ::= void<标识符>‘(’<参数>‘)’‘{’<复合语句>‘}’
    <复合语句> ::= [<常量说明>][<变量说明>]<语句列>
    <参数> ::= <参数表>
    <参数表> ::= <类型标识符><标识符>{,<类型标识符><标识符>}|<空>
    <主函数> ::= void main‘(’‘)’ ‘{’<复合语句>‘}’
    <表达式> ::= [+|-]<项>{<加法运算符><项>}
    <项> ::= <因子>{<乘法运算符><因子>}
    <因子> ::= <标识符>|<标识符>‘[’<表达式>‘]’|<整数>|<字符>|<有返回值函数调用语句>|‘(’<表达式>‘)’
    <语句> ::= <条件语句>|<循环语句>|‘{’<语句列>‘}’|<有返回值函数调用语句>; | <无返回值函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<空>;|<情况语句>|<返回语句>;
    <赋值语句> ::= <标识符>=<表达式>|<标识符>‘[’<表达式>‘]’=<表达式>
    <条件语句> ::= if ‘(’<条件>‘)’<语句>[else<语句>]
    <条件> ::= <表达式><关系运算符><表达式>|<表达式>//表达式为0条件为假,否则为真
    <循环语句> ::= for‘(’<标识符>=<表达式>;<条件>;<标识符>=<标识符>(+|-)<步长>‘)’<语句>
    <步长> ::= <非零数字>{<数字>}
    <情况语句> ::= switch ‘(’<表达式>‘)’‘{’<情况表><缺省>‘}’
    <情况表> ::= <情况子语句>{<情况子语句>}
    <情况子语句> ::= case<常量>:<语句>
    <缺省> ::= default : <语句>
    <有返回值函数调用语句>::= <标识符>‘(’<值参数表>‘)’
    <无返回值函数调用语句>::= <标识符>‘(’<值参数表>‘)’
    <值参数表> ::= <表达式>{,<表达式>}|<空>
    <语句列> ::= {<语句>}
    <读语句> ::= scanf ‘(’<标识符>{,<标识符>}‘)’
    <写语句> ::= printf ‘(’ <字符串>,<表达式> ‘)’|printf ‘(’<字符串>‘)’| printf ‘(’<表达式>‘)’
    <返回语句> ::= return[‘(’<表达式>‘)’]

    附加说明:

    char类型的表达式,用字符的ASCII码对应的整数参加运算,在写语句中输出字符
    标识符区分大小写字母
    写语句中的字符串原样输出
    情况语句中,switch后面的表达式和case后面的常量只允许出现int和char类型;每个情况子语句执行完毕后,不继续执行后面的情况子语句
    数组的下标从0开始
    for语句先执行一次循环体中的语句再进行循环变量是否越界的测试

    1.2 目标代码说明目标代码是mips指令。



    指令
    示例
    含义




    Lw
    Lw $t1 0($t0)
    在内存中取字


    Sw
    sw $t1 0($t0)
    在内存中存字


    Add
    Add $t1 $t1 $t2
    T1=t1+t2


    Sub
    sub $t1 $t1 $t2
    T1=t1-t2


    Mult
    MULT $t1 $t1 $t2
    T1=t1*t2


    Div
    div $t1 $t1 $t2
    T1=t1/t2


    Beq
    beq $t1 $t2 label
    If t1==t2 goto label


    Bne
    bne $t1 $t2 label
    If t1!=t2 goto label


    Slt
    slt $t1 $t1 $t2
    If t1<t2 t1=1 else t1=0


    j
    J label
    goto label


    Jar
    Jar label
    Goto label ,$ra=pc+4


    Jr
    jr
    Goto ra


    Syscall

    系统调用



    1.3 优化方案本编译器完成了两部分优化:
    基于DAG图的公共子表达式消除
    实现:遍历四元式,首先划分基本块,在基本块内部循环遍历,找出具有相同操作符(+,-,*,/,=),相同左操作数,相同右操作数的操作符为+,-,*,/,=的四元式,即为在DAG图中具有相同子树的节点。更改该基本块中之后出现相同子树的四元式即为删除公共子表达式。
    基于引用计数的全局寄存器分配
    实现:首先遍历一个函数内部的所有四元式,对所有出现的变量进行引用计数,然后按照引用的次数进行从大到小排序,为前7个变量进行全局寄存器分配$s1-$s7并进行记录。在此之后,对这7个变量的相关操作不必在内存中进行存取,而直接操作寄存器即可。
    二 程序设计2.1 程序结构Main为程序入口,调用syn.cpp中program()的语法分析程序,语法分析程序调用各级语法递归子程序,在语法分析中生成四元式,main再调用Tomips根据生成好的四元式生成汇编,Tomips调用funcasm等各级汇编子函数生成汇编文件。如下图所示:

    2.2 存储分配方案


    栈顶




    全局变量区


    主函数变量区


    参数区 上一个函数的sp 上一级函数的fp 返回地址ra 本级函数变量区


    参数区 上一个函数的sp ……



    2.3 四元式设计


    四元式
    含义




    int , , , a
    int a;


    const,int,5 , a
    const int a = 5;


    char, , 30, a
    char a[30];


    []= , a , i , t
    a[i] = t;


    geta, a , n , b
    b = a[n]


    call, f , , a
    a = f()


    call, f , ,
    f()


    prt , a , b , symb
    print(“a”, b)


    scf , , , a
    scanf(a)


    jmp , , , label
    jump to label


    lab:, , , labx
    set label


    ret , , , (a)
    return a / return


    func,int, , f
    start of function int f()


    para,int, , a
    f(int a, …)


    end , , , f
    end of function f



    三 程序实现3.1 类/方法/函数功能


    函数名
    功能
    关键算法




    getsym
    词法分析
    状态图法


    getchar
    读一个字符



    program
    程序语法分析
    递归子程序法


    conststate
    常量声明
    递归子程序法


    varstate
    变量声明
    递归子程序法


    defhead
    声明头部
    递归子程序法


    scanfsentence
    读语句
    递归子程序法


    prtsentence
    写语句
    递归子程序法


    returnsentence
    返回语句
    递归子程序法


    expression
    表达式
    递归子程序法


    term

    递归子程序法


    factor
    因子
    递归子程序法


    valueofpara
    值参数表
    递归子程序法


    sentencelist
    语句列
    递归子程序法


    ifsentence
    条件语句
    递归子程序法


    condition
    条件
    递归子程序法


    loopsentence
    循环语句
    递归子程序法


    casesentence
    情况语句
    递归子程序法


    sentence
    语句
    递归子程序法


    complexsentence
    复合语句
    递归子程序法


    parametertable
    参数列表
    递归子程序法


    Tomips
    生成汇编



    funasm
    函数调用生成汇编



    生成汇编小函数
    生成汇编子函数



    better
    优化
    DAG图,引用计数


    error
    错误处理



    3.2 符号表管理方案符号表由一个命名为symtable的结构体组成,结构体中第一个元素element为一个symin结构的结构体数组,存储在符号表中每一个变量的具体信息,第二个元素为符号表总个数,第三个元素为分程序总个数,第四个元素为分程序索引数组,通过这个数组找到分程序在符号表中的地址。
    typedef struct{ char name[ MAXIDENLEN ]; //identifier name int kind; //标识符类型:INTTK,CHARTK,CINTET, CCHARET,FUNCTION,PARA int value; //对于函数来说,2表示返回值为char,1表示返回值为Int 0返回值为void int address; //address,元素在符号表element数组中的下标 int paranum; //对函数来说的参数个数 int parakind[MAXPARANUM];}symin;typedef struct { symin element[ MAXSYMTABLENUM ]; int index; int ftotal; //分程序总数 int findextable[ MAXSYMTABLENUM ];//分程序索引数组}symtable;
    3.3 目标代码生成方案数据结构如下所示,用一个结构体数组记录临时变量在栈中的地址,根据地址和相应的四元式语义生成相应的汇编程序代码。
    ap为该地址表计数器。
    Globedge为地址表中全局变量边缘地址。
    int mi = 0; //四元式处理的行数int sp = INITSTACK; //栈指针,此地址为即将分配的!!相对fp!!地址int fp = 0;//帧指针int ap;//地址表计数器int ismain = 0;int globedge = 0;int tlabelnum = 0;int isglob;int paran = 0;int tmi;int funcedge=0;int savespflg=0;int vrnum;int varreg[ 200 ];typedef struct { char name[ 100 ]; int address; int kind; int cnt;}tempvaraddress;//变量表tempvaraddress addrtable[ 1000 ];//临时变量在栈中的地址表
    3.4 出错处理
    语法分析递归子程序发现错误立即调用error()并传递出错信号
    error函数通过接收到的出错信号打印出出错信息和位置信息;出错信息记录在工程文件夹下的Rerrwrite.txt文件中
    通过出错类型型号和位置判断应该如何进行跳读,基本上是遇到错误则跳到同层符号的FIRST集合元素。具体情况具体分析,详见err.cpp
    继续执行编译,直到文件结束

    四 运行测试4.1 运行环境codeblock 13.12
    生成的汇编代码使用Mars进行模拟执行生成的汇编代码。
    4.2 操作步骤
    在codeblock中打开代码工程文件
    将测试文件的文件名改为mytest.txt拖入所在工程文件夹
    在codeblock中编译运行,
    在工程文件夹中的末尾将有5个文件,分别为
    ZerrWrite.txt 错误信息
    ZmdcdWrite.txt 优化前中间代码ZmdcdWritebetter.txt 优化后中间代码ZmipsWrite.txt 优化前目标代码ZmipsWriteBetter.txt 优化后目标代码
    4.3 测试案例测试程序见附件测试程序文件夹中10个txt文件。
    测试结果如下:
    测试程序1:
    输入:2输出:2输入:6输出:8测试程序2:
    输入:3 2 3 4输出:The sum:9The average:3 输入:4 3 4 5 6输出:The sum:18The average:4测试程序3:
    输入:2 3 1输出:5 输入:3 4 3输出:12测试程序4:
    输入:2 4 3 8输出:The result :2 3 4 8输入:7 5 3 1输出:The result:1 3 5 7测试程序5:
    样例输入:1 5 b 5样例输出:1202for-->0for-->1for-->2for-->3for-->4the end样例输入:2 6 c 2样例输出:8 10086Finish!10000for-->0for-->1the end样例输入:5 2 t 3样例输出:23343for-->0for-->1for-->2the end测试程序6:
    编译未通过输出:Error1 : At Line1, Near symbol "SEMICOLONSY" : CONST NOT INITError2 : At Line18, Near symbol "COMMASY" : CONST NOT INITError3 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICT测试程序7:
    编译未通过输出:Error1 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICTError2 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICTError3 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICTError4 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICTError5 : At Line22, Near symbol "COMMASY" : SYMBOL CONFLICTError6 : At Line22, Near symbol "COMMASY" : SYMBOL CONFLICTError7 : At Line22, Near symbol "COMMASY" : SYMBOL CONFLICTError8 : At Line88, Near symbol "ASSIGNSY" : EXPRESSION ERRORError9 : At Line89, Near symbol "ASSIGNSY" : UNACCEPTATLE CHAR测试程序8:
    编译未通过输出:Error1 : At Line51, Near symbol "RSBRASY" : FORMAL PARA NUM UNMATCHError2 : At Line57, Near symbol "RSBRASY" : PARA TYPE NOT MATCHError3 : At Line98, Near symbol "SEMICOLONSY" : RIGHT PARENT LACKError4 : At Line100, Near symbol "SCANFTK" : SEMICOLONLACK测试程序9:
    编译未通过输出:Error1 : At Line71, Near symbol "ASSIGNSY" : RIGHT BRACK LACKError2 : At Line76, Near symbol "RMBRASY" : Warning: index of array out of boundary测试程序10:
    编译未通过输出:Error1 : At Line21, Near symbol "COMMASY" : SYMBOL CONFLICTError2 : At Line28, Near symbol "NUMET" : SEMICOLONLACKError3 : At Line47, Near symbol "IDEN" : SEMICOLONLACKError4 : At Line92, Near symbol "SEMICOLONSY" : VAR NOT DEFINEError5 : At Line92, Near symbol "SEMICOLONSY" : IDENTIFIER LACKError6 : At Line102, Near symbol "SCANFTK" : SEMICOLONLACKError7 : At Line105, Near symbol "IDEN" : RIGHT PARENT LACKError8 : At Line105, Near symbol "IDEN" : SEMICOLONLACKError9 : At Line105, Near symbol "RSBRASY" : UNACCEPTABLE SENTENCE
    1 评论 8 下载 2018-10-30 16:14:09 下载需要6点积分
  • 基于WIN32 API界面编程实现的2048游戏

    一 项目概述本项目是一个2048游戏,基于Win32开发,灵感来自此前网络上流行的2048游戏
    二 功能概述2.1 操作方式该游戏支持三种键盘操作键位:

    方向键操控
    WASD键位操控:

    W - 上, A - 左, S - 下, D - 右
    Vim操作方式:

    H - 左, J - 下, K - 上, L - 右

    2.2 游戏功能
    倒计时

    游戏有一个 15分钟 的倒计时,玩家需要在规定时间内完成游戏(即合并出数字为2048的方块)
    方块出现规则

    在游戏中,方块的出现完全随机,仅出现在空方格中。当方块出现时,方块上的数字有 90% 的几率为2, 10% 的几率为4
    游戏开始时,会在所有空方格中随机选两个方格放入带有数字的方块,方块上的数字依照上述规则
    在玩家每一次移动后,棋盘上会新增一个方块,规则如上所述

    方块合并规则

    两个数字相同的方块才能合并,合并后生成一个数字为原来2倍的方块
    计分规则

    玩家获得分数仅发生在方块合并后,合并后玩家会获得的分数为新生成的方块的数字
    游戏结束规则

    下列几种情况会使得游戏结束:

    倒计时结束合并得到数字为2048的方块棋盘全满,无法再增加新方块,且玩家进行任何移动操作都无法使棋盘新增一个空位


    三 使用场景主界面

    主界面右上方会显示倒计时和分数
    游戏结束

    游戏结束后弹出的窗口中会告诉用户游戏情况(成功/失败/超时),同时也会显示分数。
    弹出的窗口中有两个按钮:”再来一局”和”退出”。如果按”再来一局”,弹窗会关闭,游戏所有数据将会重置,玩家重新开始游戏;如果按”退出”,将直接退出整个程序。
    四 程序架构
    这张图由Visual Studio生成,从中可以看出这是一个Win 32项目,其中有一个核心类Game,用来处理所有的游戏逻辑。
    4.1 核心类 GameGame这个核心类暴露在外的成员变量和方法如下图:

    下面介绍其中比较重要的几个方法:

    draw()
    该方法涉及整个游戏界面的绘制,函数原型为:
    void draw(HDC hdc) const;
    在程序的主消息循环中,一旦接收到WM_PAINT消息,将会调用该方法对界面进行重绘,当然是有选择性的重绘,因为界面上除了棋盘、分数、倒计时,其他部分都没有必要重绘
    该方法在Game类内部还会调用以下私有方法:
    void drawChessboard(HDC hdc) const; // 绘制棋盘背景,绘制上部分数、logo等,会调用下面三个方法void drawTopBar(HDC hdc) const; void drawLogoText(HDC hdc) const;void drawScoreLabel(HDC hdc) const;void drawCell(HDC hdc, const int &row, const int &col, const int &value) const; // 绘制方块
    setRect()
    该方法用于设置游戏画面的区域,函数原型如下:
    void setRect(const RECT clientRect);
    该方法会设置好scoreLabelRect和chessboardRect,便于之后在主消息循环中重绘时,进行有选择的重绘
    restart()
    该方法会重置所有游戏信息,让游戏重新开始,如下:
    void Game::restart() { this->initChessboard(); // Init chessboard}
    initChessboard()
    该方法用于初始化整个游戏,如下:
    void Game::initChessboard() { this->score = 0; this->over = false; this->won = false; this->numberOfEmptyCells = 16; memset(this->chessboard, 0, 4 * 4 * sizeof(int)); // Init chessboard this->initStartCells(); // Add the initial cells}
    枚举变量MoveCommand
    typedef enum { MoveCommandUp, MoveCommandDown, MoveCommandLeft, MoveCommandRight} MoveCommand;
    定义这样的枚举变量用于表示移动方向,更多考虑的是程序源代码的可读性以及可维护性
    canMove()
    该方法有两个重载函数,如下:
    bool canMove() const;bool canMove(const MoveCommand cmd) const;
    含有参数的方法用于判断特定的移动指令下棋盘上的方块能否移动,一般用于玩家按下某个方向键后,如果无法移动,那么将不会改变棋盘状态,也不会进行重绘
    在玩家某一次操作结束后,还会判断接下来用户能否继续移动,这时候需要判断四个方向,故会调用没有参数的canMove()方法
    如果接下来棋盘为满且不能移动,那么玩家输掉这盘游戏,如下:
    if (this->numberOfEmptyCells == 0 && !this->canMove()) { this->over = true; // Game overc}
    doMove()
    函数原型如下:
    void Game::doMove(const MoveCommand cmd) { switch (cmd) { case MoveCommandLeft: this->doLeft(); break; case MoveCommandUp: this->doUp(); break; case MoveCommandRight: this->doRight(); break; case MoveCommandDown: this->doDown(); break; default: break; } this->addRandomCell(); // Add cell if (this->numberOfEmptyCells == 0 && !this->canMove()) { this->over = true; // Game over }}
    可以看到,该函数实际上涉及到四个私有函数:
    void doUp(); void doDown(); void doLeft(); void doRight();
    这四个函数才是真正会修改游戏棋盘状态的函数

    4.2 辅助函数该项目中将一些重用较多的函数提取到Utils.h和Utils.cpp中,如下:
    long getRectWidth(RECT rect);long getRectHeight(RECT rect);void drawLine(HDC hdc, int x1, int y1, int x2, int y2);void drawRoundRect(HDC hdc, RECT cellRect, COLORREF bgColor);void drawNumberOnCell(HDC hdc, RECT cellRect, const int &number, COLORREF fontColor);void drawNumberOnRect(HDC hdc, RECT rect, const int &number, COLORREF fontColor);void drawNumberOnTimeLabel(HDC hdc, RECT rect, LPCWSTR pWStr, COLORREF fontColor);HFONT createFont(int height);void getTimeStringByValue(UINT time, LPCWSTR result);
    五 设计理念因为该项目并不复杂,所以将逻辑和界面都写在一个类里,该项目的实际开发过程如下:

    完成静态界面的绘制,不会响应任何按键信息
    完成命令行版2048,不涉及任何界面,纯逻辑,核心类Game在该阶段完成
    将界面绘制整合到核心类Game中,为之新添诸如chessboardRect等成员变量以及draw()等涉及界面绘制的方法

    六 其他项目开发中有一点值得一提。由于绘图时涉及字体还有颜色填充,所以用到了CreateFont()、CreatePen()和CreateBrush()等函数,它们会返回HBRUSH、HPEN、HFONT等类型的变量,这些变量都属于HGDIOBJ,本质上都是指针
    实际上每次使用时都新建了这样的HGDIOBJ,如下:
    // Draw round rectvoid drawRoundRect(HDC hdc, RECT cellRect, COLORREF bgColor) { HBRUSH brush = CreateSolidBrush(bgColor); HPEN pen = CreatePen(PS_NULL, 0, NULL); SelectObject(hdc, brush); SelectObject(hdc, pen); RoundRect(hdc, cellRect.left, cellRect.top, cellRect.right, cellRect.bottom, 10, 10);}
    这样每次都会在内存中分配空间,重点在于并未回收内存,最开始没有意识到这一点,导致游戏进行到最后整个界面崩溃。在不用HGDIOBJ后,应该调用DeleteObject()来及时释放内存,经过修改后的函数应该如下所示:
    // Draw round rectvoid drawRoundRect(HDC hdc, RECT cellRect, COLORREF bgColor) { HBRUSH brush = CreateSolidBrush(bgColor); HPEN pen = CreatePen(PS_NULL, 0, NULL); SelectObject(hdc, brush); SelectObject(hdc, pen); RoundRect(hdc, cellRect.left, cellRect.top, cellRect.right, cellRect.bottom, 10, 10); DeleteObject(brush); DeleteObject(pen);}
    2 评论 28 下载 2018-10-29 20:24:06 下载需要3点积分
  • 基于Java实现的词法分析程序

    一 需求分析自己编写、调试一个词法分析程序,并对程序输入语句进行词法分析,从而更好的理解词法分析的原理。
    二 程序设计此程序用Java语言编写。程序读入一个内含程序语句的文本文件,对其中的内容进行分析,最终输出形式为<token类型, 识别的单词符号>的Token序列,token类型包括ID,KEYWORD, DIGIT, OPERATOR, ERROR。本词法分析器所分析的语言为C语言的缩减版,可识别C语言的保留字、变量名、操作符、数字等内容,但是不能处理注释、三目操作符(? :)、跳脱符(\)等,且不支持负的数值,引号中内容不可以跨行,所有支持的操作符和关键字将在下面表格中详细给出。



    单词符号
    种别码
    单词符号
    种别码




    int
    1
    +
    25


    unsigned
    2
    -
    26


    short
    3
    *
    27


    char
    4
    /
    28


    long
    5
    &
    29


    float
    6
    &&
    30


    double
    7
    \

    31


    if
    8
    \
    \

    32


    else
    9
    !
    33


    do
    10
    =
    34


    while
    11
    ==
    35


    continue
    12
    +=
    36


    for
    13
    -=
    37


    switch
    14
    *=
    38


    case
    15
    /=
    39


    default
    16
    ++
    40


    break
    17

    41


    struct
    18
    ;
    42


    typedef
    19
    (
    43


    const
    20
    )
    44


    static
    21
    [
    45


    return
    22
    ]
    46


    id(变量名称)
    23
    {
    47


    digit(数值)
    24
    }
    48


    undefned
    -1
    >
    49




    <
    50




    >=
    51




    <=
    52




    %
    53




    !=
    54





    55





    56




    :
    57



    三 程序实现3.1 方法思路本系统是基于最小或状态数以后的DFA来进行编程的,所以准备工作是要人工将以上定义的C语言缩减版转化成对应的NFA再转化成DFA,然后程序通过DFA并读入字符序列来判断并输出识别的Token序列,如果出现不识别的Token则将其归为ERROR类,即上表中种别码为-1的undefined。
    3.2 最小化状态数的DFA通过手工运算,得出本次实验所需的DFA如下图所示。

    上图中的由起点经过other路径到达的状态表示(, ), {, } 等类似的直接由起点到达终点的路径统称,因为他们几乎相同所以没有单独画出来。且每个状态都暗含了一个经过undefined到达ERROR状态的路径,图中也省略了。
    3.3 主要数据结构Token类是表示分析得到的词法单元,其中code可选值为ID,KEYWORD,DIGIT,OPERATOR,STRING,CHARACTER, ERROR,分别表示不同的类型。
    public class Token { /** * 种别码 */ private CodeEnum code; private String value; public Token(CodeEnum code, String value) { this.code = code; this.value = value; } public CodeEnum getCode() { return code; } public void setCode(CodeEnum code) { this.code = code; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String toString () { return "< "+code+", "+value+" >\n"; }}
    3.4 核心算法核心算法为Analyzer类的process()方法,该算法从文件一次读取一个字符,并基于当前的状态和该字符来决定下一个状态。其中0状态是起始状态,起始状态下读到字符或下划线进入23状态,即ID状态,然后在该状态下,如果读到字符或者数字都在该状态循环,如果读到其他字符则输出一个ID的token,并将多度的字符放回缓冲区,并重新从0状态开始。如果在0状态读到0-9之间的数值,则进入24状态,即DIGIT状态,此状态下读入数值字符则继续循环,读入小数点则进入100状态,该状态下只接受数值字符,读入其他字符都会导致将该字符放入缓冲区并输出token,然后回到0状态。其他的都类似,在此不再赘述。
    状态图中0状态有一个发出边为other的边,这表示如果读到不支持的字符,则进入-1状态,即ERROR状态。
    /** * 核心方法 */ public void process() throws IOException { while(true) { int c = readChar(); if (c == -1){ //-1 means EOF break; } else { char ch = (char)c; switch (state) { case -1: { //这个状态表示目前的token已经确定是个错误的token了,比如123abc,该状态等待出现空白符或操作符跳出该错误状态回到0状态 if (isLetter(ch) || isDigit(ch)){ state = -1; buff[buff_index++] = ch; } else { lastChar = ch; outputToken(); } } break; case 0: { //start state if (!isWhiteSpace(ch)) { if (isLetter(ch)){ state = 23; buff[buff_index++] = ch; } else if (ch>='0'&&ch<='9') { state = 24; buff[buff_index++] = ch; } else if (ch == '!') { state = 33; buff[buff_index++] = ch; } else if (ch == '+') { state = 25; buff[buff_index++] = ch; } else if (ch == '-') { state = 26; buff[buff_index++] =ch; } else if (ch == '*') { state = 27; buff[buff_index++] = ch; } else if (ch == '/') { state = 28; buff[buff_index++] = ch; } else if (ch == '&') { state = 29; buff[buff_index++] = ch; } else if (ch == '|') { state = 31; buff[buff_index++] = ch; } else if (ch == '=') { state = 34; buff[buff_index++] = ch; } else if (ch == '>') { state = 49; buff[buff_index++] = ch; } else if (ch == '<') { state = 50; buff[buff_index++] = ch; } else if (isOtherOperator(ch)){ state = 42; //42代表分号,但是在此处代表了所有其他的operator,因为输出结果都是枚举类的OPERATOR,所以无所谓 buff[buff_index++] = ch; outputToken(); } else if (ch == '\"') { state = 55; } else if (ch == '\'') { state = 56; } else { state = -1; buff[buff_index++] = ch; outputToken(); } } else { //ch is blank character, do nothing } } break; case 23: { //id if (isLetter(ch)){ state = 23; buff[buff_index++] = ch; } else if (isDigit(ch)) { state = 23; buff[buff_index++] = ch; } else { lastChar = ch; outputToken(); } } break; case 24: { //digit if (isDigit(ch)) { state = 24; buff[buff_index++] = ch; } else if (ch == '.') { state = 100; //小数点以后的状态 buff[buff_index++] = ch; } else if (isLetter(ch)){ state = -1; buff[buff_index++] = ch; } else { lastChar = ch; outputToken(); } } break; case 100: { //last char is . if (isDigit(ch)) { state = 101; //小数点以后已经有数字了 buff[buff_index++] = ch; } else { lastChar = ch; outputToken(); } } break; case 25: { // + if (ch == '+') { state = 40; buff[buff_index++] = ch; outputToken(); } else if (ch == '=') { state = 36; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 26: { // - if (ch == '-') { state = 41; buff[buff_index++] = ch; outputToken(); } else if (ch == '=') { state = 37; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 27: { // * if (ch == '=') { state = 38; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 28: { // / if (ch == '=') { state = 39; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } case 29: { // & if (ch == '&'){ state = 30; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 31: { // | if (ch == '|'){ state = 32; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 33: { // ! if (ch == '='){ state = 54; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 34: { // = if (ch == '='){ state = 35; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 49: { // > if (ch == '='){ state = 51; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 50: { // < if (ch == '='){ state = 52; buff[buff_index++] = ch; outputToken(); } else { lastChar = ch; outputToken(); } } break; case 55: { // " if (ch != '\"') { state = 55; if ((ch!='\r') && (ch!='\n')) { buff[buff_index++] = ch; } else { state = -1; outputToken(); } } else { outputToken(); } } break; case 56: { // ' if (ch != '\'') { state = 56; if ((ch!='\r') && (ch!='\n')){ buff[buff_index++] = ch; } else { state = -1; outputToken(); } } else { outputToken(); } } break; default: break; } } } ioHelper.closeIO(); }
    四 运行测试输入的待分析文件如下图所示:

    词法分析结果输出如下图所示:
    1 评论 13 下载 2018-10-30 09:45:46 下载需要4点积分
  • 基于Java实现的LR(1)分析法语法分析程序

    一 需求分析自行定义文法, 运用语法分析方法对输入语句进行语法分析并输出结果,加深对语法分析过程的理解。
    二 程序设计2.1 总体思路此次实验使用java编写。程序读取输入的token序列(如input.txt中所示),对其进行语法分析。这里使用LR(1)方法自底向上进行分析,最后输出归约的产生式序列。
    定义的文法如下:
    0 : S’-> S1 : S -> if C S else S2 : S -> id + id ;3 : C -> id > id4 : C -> C && C2.2 假设和依赖
    if后面必须有else,不允许单个if的情况
    所有的非终结符必须是单个字符

    三 程序实现3.1 思路和方法
    构建符号栈,状态栈,建立 parsing table 在程序当中的映射,方便查询处理
    读入用户输入的待分析的表达式, 依次移动读头, 如果根据当前栈顶的情况和读头下的符号来决定当前的动作。根据当前状态栈顶的状态号和读头下的符号查 parsing table,如果查到的是 Si 即 Shift 操作,则将读头下的符号压入符号栈,将 i 压入状态栈,移动读头到下一个字符;如果查到的是 ri 即 Reduce 操作,则将栈顶的所有与文法表达式 i右边相同的部分一次弹出, 同时将状态栈一起弹出, 将文法表达式 i 左边的符号 X 压入符号栈,查当前状态栈栈顶的项 I 在 GOTO 表当中的 GOTO(I,X),将所得的状态号压入状态栈;如果读到 r0,则分析成功;如果有其他读入,则不合法
    在读入过程中,如果出现查找不到 Action 或者 GOTO 表项,则报错,终止语法分析

    3.2 主要数据结构3.2.1 状态栈和符号栈使用java.util.Stack<E>类库可以轻松构造状态栈和符号栈。
    private Stack<Token> tokenStack;private Stack<Integer> stateStack;
    3.2.2 预测分析表预测分析表使用二维数据存储,使用时只需将状态号和token的code编号带入数组下标即可获得action或者goto的操作。
    private static final String [][] actionMap = { // if else id + ; > && $ {"S3", "" , "S2", "" , "" , "" , "" , "" }, {"" , "" , "" , "" , "" , "" , "" , "R0"}, {"" , "" , "" , "S4", "" , "" , "" , "" }, {"" , "" , "S8", "" , "" , "" , "" , "" }, {"" , "" , "S5", "" , "" , "" , "" , "" }, {"" , "" , "" , "" , "S6", "" , "" , "" }, {"" , "" , "" , "" , "" , "" , "" , "R2"}, {"S13","" ,"S14", "" , "" , "" ,"S12", "" }, {"" , "" , "" , "" , "" , "S9", "" , "" }, {"" , "" ,"S10", "" , "" , "" , "" , "" }, {"R3", "" , "R3", "" , "" , "" , "" , "" }, {"" ,"S18", "" , "" , "" , "" , "" , "" }, {"" , "" , "S8", "" , "" , "" , "" , "" }, {"" , "" , "S8", "" , "" , "" , "" , "" }, {"" , "" , "" ,"S15", "" , "" , "" , "" }, {"" , "" ,"S16", "" , "" , "" , "" , "" }, {"" , "" , "" , "" ,"S17", "" , "" , "" }, {"" , "R2", "" , "" , "" , "" , "" , "" }, {"S3", "" , "S2", "" , "" , "" , "" , "" }, {"" , "" , "" , "" , "" , "" , "" , "R1"}, {"R4", "" , "R4", "" , "" , "" ,"S12", "" }, {"S13","" ,"S14", "" , "" , "" ,"S12", "" }, {"" ,"S23", "" , "" , "" , "" , "" , "" }, {"S13","" ,"S14", "" , "" , "" , "" , "" }, {"" , "R1", "" , "" , "" , "" , "" , "" } }; private static final int[][] gotoMap = { // S C { 1, -1}, { -1, -1}, { -1, -1}, { -1, 7 }, { -1, -1}, { -1, -1}, { -1, -1}, { 11, -1}, { -1, -1}, { -1, -1}, { -1, -1}, { -1, -1}, { -1, 20}, { -1, 21}, { -1, -1}, { -1, -1}, { -1, -1}, { -1, -1}, { 19, -1}, { -1, -1}, { -1, -1}, { 22, -1}, { -1, -1}, { 24, -1}, { -1, -1}, };
    3.3 核心算法核心算法为parse() 方法,它从ioHelper获得一个一个的token,并根据状态栈中的状态代号和token中的code从actionMap中选择对应的操作,如果操作String是以‘S’开头的,则属于shift操作,如果以’R’开头,则是reduce操作,否则认为parsing table是非法的。
    public void parse() throws IOException { Token tokenCur = ioHelper.readToken(); while(tokenCur!=null){ String optStr = actionMap[stateStack.peek()][tokenCur.getCode()]; if (optStr == null || optStr.isEmpty()) { ioHelper.write("Error! Can not parse input token: "+tokenCur.getValue()); break; } if (optStr.charAt(0) == 'S') { //shift int nextState = Integer.parseInt(optStr.substring(1)); tokenStack.push(tokenCur); stateStack.push(nextState); } else if (optStr.charAt(0) == 'R') { //reduce int genIndex = Integer.parseInt(optStr.substring(1)); if (genIndex == 0) { //all tokens are parsed successfully! break; } else { ioHelper.write(generations[genIndex]); for (int i=0; i<generationLength[genIndex]; i++) { tokenStack.pop(); stateStack.pop(); } String nonTerminal = generations[genIndex].substring(0,1); int nonCode = -1; for (int i=0; i<nonTerminalCode.length; i++){ if (nonTerminalCode[i].equals(nonTerminal)){ nonCode = i; break; } } if (nonCode<0){ ioHelper.write("Error! Non-terminal symbol: "+nonTerminal+" is not in nonTerminalCode list!"); break; } tokenStack.push(new Token(nonCode, nonTerminal)); int nextState = gotoMap[stateStack.peek()][nonCode]; stateStack.push(nextState); continue; //prevent ioHelper read next token, because current token has not been processed } } else { ioHelper.write("Error! Wrong parsing table caused by Invalid operation string: "+optStr); break; } tokenCur = ioHelper.readToken(); } } public void closeIO(){ ioHelper.closeIO(); }
    四 运行测试输入token序列(code ,value),如下图所示:

    输出归约产生式序列:
    1 评论 30 下载 2018-10-30 09:24:36 下载需要5点积分
  • 基于Qt的图形化界面网络在线对战五子棋游戏

    一 需求分析本软件是一款跨平台的网络实时五子棋对战软件,实现建立主机和连接主机、实时对战、判断输赢和危险提示等功能。支持Windows 、 Linux和OSX平台。
    程序主要功能如下:

    建立服务器

    能创建网络主机。在界面上添加功能按钮,显示创建主机对话框,对话框显示主机IP,还有取消按钮
    连接服务器

    游戏客户端能输入主机。添加软键盘,用来输入主机的IP
    五子棋对战

    需要有棋盘界面,并且可以实现实时对战。并且实现棋子坐标的数据传输,传递报文表示最新的棋子的坐标,并且实时刷新棋盘信息。并且在界面标记游戏状态,显示当前的落子方,如果哪一方赢了就弹出对话框显示输赢
    危险提示

    当用户点击提示功能按钮的时候,可以判断出对方如果在哪些地方下子,你会出现如下的情况:

    再落一子,出现两个无阻挡的连续3子的情况
    再落一子,出现一个无阻挡的连续3子和一个有单侧阻挡的连续4子的情况,并且以炸弹图标标出危险位置



    二 程序设计本软件由棋盘模块GameBoard,下棋模块Chess和网络连接CreateDialog和ConnectDialog连接而成。

    棋盘模块包含了对于棋盘的建立,对棋盘进行更新的事件处理,鼠标按下落子时的事件处理等对于棋盘的事件处理。还有对于棋盘信息的储存方式,和对于接受的信息进行呈现的功能下棋模块主要包含了整个界面的设计,按钮功能的设计和管理发送接收并解析棋盘信息的功能网络连接模块主要有两部分。第一部分是建立主机。程序会先寻找自己的默认IP并将其提供给用户,然后然用户输入主机IP。第二部分是连接主机,弹出的窗口会有软键盘供用户输入想要连接的主机的IP地址
    程序文件包含两部分:

    一部分是程序的源代码,在src文件夹中,包含main.cpp,mainwindow.cpp, mainwindow.h, gameboard.cpp, gameboard.h, create.cpp, create.h,connect.cpp和connect.h另一部分是程序的资源文件,在rec文件夹中,包含危险提示需要的炸弹图标和其他一些需要的音效文件
    三 程序实现3.1 棋盘模块棋盘模块主要有GameBoard类组成,它集成QWidget类,实际上就是一个下棋用的棋盘界面的设计。包含了画图和对于事件处理的头文件。此类中应用vector在储存玩家和对方的棋子的位置,并且在每次更新棋盘的时候,画图函数都会将vector中所有的棋子信息重新画一次。应用bool变量inround对是否己方下棋进行判断。应用struct创建ches:结构,并创建state:数组用来存储每个位置棋子的情况。对于事件的处理,此类中先建立一个even七Fil七er来筛选棋盘界面的事件。然后在画图事件的函数添加了对于棋盘的设计和添加棋子的画图操作。在用户显示提示的时候,也会在危险的地方显示提示图标。对于棋盘的设计,本软件采取了围棋棋盘的设计策略,为19x19的棋盘。
    3.2 下棋模块此模块主要是对于主窗口的界面进行布局和功能的设计。在主界面中,将一个Widget提升为GameBoard类,从而在这个窗口中显示棋盘模块中创建的棋盘。并且在主窗口中加入了功能按钮和,提示当前落子方的Label。同时,其中的readData()函数和sendChessInfo()等函数负责管理棋盘的信息接收后的最后最终处理和发出棋盘的更新信息,包括新的落子和输赢等。
    3.3 网络连接模块此模块主要负责的是网络连接。其中包含CreateDialog类和ConnectDialog类。CreateDialog类负责建立一个自己IP地址的主机,在对话框中,会先显示出自己的电脑现在的IP地址,让用户直接选择。然后点击确定会直接将自己作为主机。ConnectDialog类负责连接一个主机。对话框中设有软键盘用以输入主机的IP。两个类中的按钮均应用QSignalMapper来管理。
    四 运行测试文件运行起来只包含一个主窗口,还有连接网络时需要的两个弹出界面。程序运行界面如下:
    1 评论 17 下载 2018-10-29 16:55:39 下载需要4点积分
显示 795 到 810 ,共 15 条
eject