C++控制台贪吃蛇移植QT贪吃蛇

前言

  • Task:用QT实现一个可视化项目。
  • Skill:已经学的C++控制台程序设计。

本次“C++控制台贪吃蛇移植QT贪吃蛇”是在已经有了C++控制台版本的贪吃蛇代码的情况下,将其移植到QT平台,做成一个可视化的项目。告别黑框框,进入白框框!

控制台部分任务分析

控制台源码

声明:这份源码不是博主写的,是博主的同学写的。博主的工作只是移植!
声明:这份源码不是博主写的,是博主的同学写的。博主的工作只是移植!
声明:这份源码不是博主写的,是博主的同学写的。博主的工作只是移植!

源码放在下面

#include<iostream>
#include<windows.h>
#include<conio.h>
#include<ctime>
#include<vector>
#include<string>
using namespace std;
#define high 25
#define width 50
int snake[high+5][width+5]={0};
int food_x,food_y;
int movedirection;
int score,sum;
void gotoxy(int x, int y)
{
	HANDLE h;
	COORD c;
	c.X = x; c.Y = y;
	h = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(h, c);
}
void hide_cursor()
{
	HANDLE h_GAME = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(h_GAME, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(h_GAME, &cursor_info);
}
void start()
{
	for(int i=0;i<high;i++)
	{
		snake[i][0]=-1;
		snake[i][width-1]=-1;
	}
	for(int i=0;i<width;i++)
	{
		snake[0][i]=-1;
		snake[high-1][i]=-1;
	}
	snake[high/2][width/2]=1;
	for(int i=1;i<=4;i++)
	snake[high/2][width/2-i]=i+1;
	
	movedirection=4;srand(time(NULL));
	food_x=rand()%(high-5)+2;
	food_y=rand()%(width-5)+2;
	snake[food_x][food_y]=-2;
	snake[high+2][2]=0;sum=0;
}
void movesnake()
{
	int max=0;
    int oldtail_i,oldtail_j;
	int oldhead_i,oldhead_j;
	for(int i=1;i<high-1;i++)
	{
		for(int j=1;j<width-1;j++)
		{
			if(snake[i][j]>0) 
			{
				snake[i][j]++;
				if(max<snake[i][j])
				{
					max=snake[i][j];
					oldtail_i=i;oldtail_j=j;
				}
				if(snake[i][j]==2)
				{
					oldhead_i=i;oldhead_j=j;	
				}	
			}
			
		}
	} 
	int newhead_i,newhead_j;
	switch (movedirection)
	{
		case 1:
            newhead_i=oldhead_i-1;
			newhead_j=oldhead_j;
            break;
		case 2:
            newhead_i=oldhead_i+1;
			newhead_j=oldhead_j;
            break;
		case 3:
            newhead_i=oldhead_i;
			newhead_j=oldhead_j-1;
            break;
		case 4:
            newhead_i=oldhead_i;
			newhead_j=oldhead_j+1;
            break;
	}
	if(snake[newhead_i][newhead_j]==-2)
	{
		snake[food_x][food_y]=0;
		srand(time(NULL));
		food_x=rand()%(high-5)+2;
		food_y=rand()%(width-5)+2;
		snake[food_x][food_y]=-2;
		sum++;
		snake[high+2][2]+=sum*2;
	}
	else 
	{
		snake[oldtail_i][oldtail_j]=0;
	}
	if(snake[newhead_i][newhead_j]>0||snake[newhead_i][newhead_j]==-1)
	{
		cout<<"游戏失败"<<endl;
		exit(0);
	}
	else 
	{
		snake[newhead_i][newhead_j]=1;
	}
}
void show()
{
	gotoxy(0,0);
	for(int i=0;i<high+3;i++)
	{
		for(int j=0;j<width;j++)
		{
			if(snake[i][j]==0&&i<high)
				cout<<" ";
			else if(snake[i][j]==-1&&i<high)
				cout<<"#";
			else if(snake[i][j]==1&&i<high)
				cout<<"@";
			else if(snake[i][j]>1&&i<high)
				cout<<"*";
			else if(snake[i][j]==-2&&i<high)
				cout<<"F";
			if(i==high+2&&j==2)
				cout<<snake[i][j];
		}
		printf("\n");
	}
	Sleep(50);
}
void input()
{
	char put;
	if(kbhit())
	{
		put=getch();
		if(put=='a')
			movedirection=3;
		if(put=='d')
			movedirection=4;
		if(put=='w')
			movedirection=1;
		if(put=='s')
			movedirection=2;
	}
}
int main()
{
	start();
	while(true)
	{
		hide_cursor();
		input();
		show();
		movesnake();
	}
	return 0;
}

源码分析

假设我刚拿到这个代码,我将以这种状态对这个代码进行功能、实现上的分析。

  1. 首先分析主函数的内容
int main()
{
	start();
    //猜测这个是一个初始化函数,因为只执行一次
	
    while(true)
	//无限循环,应该是一个刷新界面的东西,不然不会无限循环的。
    {
		hide_cursor();
        //字面意思,隐藏光标
        
		input();
		//字面意思,输入
        
        show();
		//字面意思,画图输出
        
        movesnake();
		//字面意思,移动蛇
    }
    
	return 0;
}
//总结,命名很好懂
//而且,如果你只想移植这个程序,分析到这里就可以了。

接下来我们一个一个函数进行分析,按照顺序搞下来。

  1. start函数
void start()
//目前猜测它是一个初始化函数
{
	for(int i=0;i<high;i++)
    //high,看一下前面的定义,是一个宏定义。应该是地图高度
    //#define high 25
	{
		snake[i][0]=-1;
        snake[i][width-1]=-1;
		//把每个坐标的0和width-1都搞成了-1,应该是一个边界条件
    }
    
	for(int i=0;i<width;i++)
	{
		snake[0][i]=-1;
		snake[high-1][i]=-1;
	}
    //这个分析同上,应该是处理另一个边界
    
	snake[high/2][width/2]=1;
    //取一个中间数,然后把它搞成1,应该是蛇头?
    
	for(int i=1;i<=4;i++)
		snake[high/2][width/2-i]=i+1;
    //然后把宽度的后4格置为i+1,应该是一种特殊的标志。或许是蛇身?
	
	movedirection=4;
    //字面意思,移动方向
    
    srand(time(NULL));
	food_x=rand()%(high-5)+2;
	food_y=rand()%(width-5)+2;
	snake[food_x][food_y]=-2;
	//这边应该是确定了第一个食物的位置。
    
    snake[high+2][2]=0;
    sum=0;
	//应该是两个初始需要统计的变量
    
    //综合上述的分析,snake数组应该是一个地图一样的东西
}
  1. hide_cursor()与gotoxy()
void gotoxy(int x, int y)
{
	HANDLE h;
	COORD c;
	c.X = x; c.Y = y;
	h = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(h, c);
}
void hide_cursor()
{
	HANDLE h_GAME = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;
	GetConsoleCursorInfo(h_GAME, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(h_GAME, &cursor_info);
}
//控制台代码,隐藏光标和移动光标到指定位置
  1. input
void input()
//我们猜测是相应输入的函数
{
	char put;
	if(kbhit())
    //相应键盘事件
	{
		put=getch();
        //读取到输入的字符(而且没有返回值)
		if(put=='a')
			movedirection=3;
        //3是左方向
		if(put=='d')
			movedirection=4;
		//4是右方向
        if(put=='w')
			movedirection=1;
		//1是上方向
        if(put=='s')
			movedirection=2;
		//2是下方向
    }
}
  1. show
void show()
{
	gotoxy(0,0);
    //转到开头
	for(int i=0;i<high+3;i++)
	{
		for(int j=0;j<width;j++)
		{
        //枚举每个snake数组的位置
			if(snake[i][j]==0&&i<high)
				cout<<" ";
            //应该是空白地图
			else if(snake[i][j]==-1&&i<high)
				cout<<"#";
            //边界
			else if(snake[i][j]==1&&i<high)
				cout<<"@";
            //蛇头
			else if(snake[i][j]>1&&i<high)
				cout<<"*";
            //蛇身
			else if(snake[i][j]==-2&&i<high)
				cout<<"F";
			//食物
            if(i==high+2&&j==2)
				cout<<snake[i][j];
			//暂时不知道是啥,但它在单独位置输出了一个数字,应该是一个计分表之类的东西?
        }
		printf("\n");
		//朴实无华的换行
    }
	Sleep(50);
    //朴实无华的等待
}
  1. movesnake
void movesnake()
//移动蛇,算法核心部分
{
	int max=0;
    int oldtail_i,oldtail_j;
	int oldhead_i,oldhead_j;
	//命名很好懂
    
    for(int i=1;i<high-1;i++)
	{
		for(int j=1;j<width-1;j++)
		{
        //枚举这个每一个格子,好像是边界内的每一个格子
			if(snake[i][j]>0) 
			{
            //是蛇的部分,因为之前初始化看出来了
				snake[i][j]++;
                //让每个位置都+1,暂时不懂啥意思
				if(max<snake[i][j])
				{
                    //这个应该是找蛇尾,因为一开始蛇尾对应的int值是最大的,那么所有的都+1之后它还是最大的。
					max=snake[i][j];
                    //更新一下
					oldtail_i=i;
                    oldtail_j=j;
					//旧蛇尾
                }
                
				if(snake[i][j]==2)
				{
                    //这波应该是默认所有蛇头都是1了
					oldhead_i=i;
                    oldhead_j=j;	
				}	
			}
			
		}
	} 
	int newhead_i,newhead_j;
    //字面意思,新头
	switch (movedirection)
	{
		case 1:
            newhead_i=oldhead_i-1;
			newhead_j=oldhead_j;
            break;
		case 2:
            newhead_i=oldhead_i+1;
			newhead_j=oldhead_j;
            break;
		case 3:
            newhead_i=oldhead_i;
			newhead_j=oldhead_j-1;
            break;
		case 4:
            newhead_i=oldhead_i;
			newhead_j=oldhead_j+1;
            break;
	}
    //按照之前说的那个规则更新一下蛇头的信息
    
	if(snake[newhead_i][newhead_j]==-2)
	{
		snake[food_x][food_y]=0;
		
        srand(time(NULL));
		food_x=rand()%(high-5)+2;
		food_y=rand()%(width-5)+2;
		snake[food_x][food_y]=-2;
		
        sum++;
        //这波应该是sum统计吃到的食物
        
		snake[high+2][2]+=sum*2;
        //应该是计分板吧
	}
    //这波是吃到食物了,但是我觉得srand应该去了。
	else 
	{
		snake[oldtail_i][oldtail_j]=0;
	}
    //不然就把旧的尾巴去了,吃到的话不去。
    
	if(snake[newhead_i][newhead_j]>0||snake[newhead_i][newhead_j]==-1)
	{
		cout<<"游戏失败"<<endl;
		exit(0);
	}
   	//判定一波,不能撞界限和自己
	else 
	{
		snake[newhead_i][newhead_j]=1;
	}
    //更新一下头
}

QT部分分析

任务分析

  1. 显示部分,也就是show的部分。我们应该把它换成QT里面的定点输出。
  2. 键盘输入部分,也就是input的部分。应该换成QT的相应键盘函数。
  3. 计时器部分,考虑使用QT的计时器来完成这个工作。

组件挑选

  1. 显示部分

    我们考虑使用QPainter来实现这个定点输出的问题,但是这样会出现不对应的问题。即QT中的xy与控制台并不是对应关系。是关于y=x对称的关系。

  2. 键盘输入部分

    考虑使用Keypress函数,重写这个虚函数。就可以在每次按下按键的时候相应一次操作。完成input这个函数的功能。

  3. 计时器部分

    考虑使用QTimer,然后设置一个计时时间间隔。这样每次到时间间隔之后将其与一个槽函数update关联。能够让QT的系统帮我们优化一波显示效果。

QT源码

  1. mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPainter>
#include <QTime>
#include <QTimer>
#include <QString>
#include <string>
#include <QMessageBox>
#include <QKeyEvent>

#define high 25
#define width 50

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void paintEvent(QPaintEvent *);
    QTimer *ctime;
    int snake[high+5][width+5];
    int food_x,food_y;
    int movedirection;
    int score,sum,x;
    void start();
    void ready();
    void keyPressEvent(QKeyEvent  *event);
public slots:
    void move();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
  1. main.cpp
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    w.ready();
    return a.exec();
}
  1. mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    start();
    this->setFocusPolicy(Qt::StrongFocus);
    ctime = new QTimer(this);
}

void MainWindow::ready(){
    QMessageBox::about(this,"How to play","Press Space to start");
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QFont font;
    font.setPointSize(10);
    painter.setFont(font);

    for(int i=0;i<high+3;i++)
    {
        for(int j=0;j<width;j++)
        {
            if(snake[i][j]==-1&&i<high)
                painter.drawText(10+10*i, 10+10*j, "#");
            else if(snake[i][j]==1&&i<high)
                painter.drawText(10+10*i, 10+10*j, "@");
            else if(snake[i][j]>1&&i<high)
                painter.drawText(10+10*i, 10+10*j, "*");
            else if(snake[i][j]==-2&&i<high)
                painter.drawText(10+10*i, 10+10*j, "F");
            if(i==high+2&&j==2)
                painter.drawText(10+10*i, 10+10*j, QString::number(snake[i][j]));
        }
    }
}

void MainWindow::start(){
    for(int i=0;i<high;i++)
    {
        snake[i][0]=-1;
        snake[i][width-1]=-1;
    }
    for(int i=0;i<width;i++)
    {
        snake[0][i]=-1;
        snake[high-1][i]=-1;
    }
    snake[high/2][width/2]=1;
    for(int i=1;i<=4;i++)
    snake[high/2][width/2-i]=i+1;

    movedirection=4;srand(time(NULL));
    food_x=rand()%(high-5)+2;
    food_y=rand()%(width-5)+2;
    snake[food_x][food_y]=-2;
    snake[high+2][2]=0;sum=0;
}

void MainWindow::move(){
    int max=0;int oldtail_i,oldtail_j;
    int oldhead_i,oldhead_j;
    for(int i=1;i<high-1;i++)
    {
        for(int j=1;j<width-1;j++)
        {
            if(snake[i][j]>0)
            {
                snake[i][j]++;
                if(max<snake[i][j])
                {
                    max=snake[i][j];
                    oldtail_i=i;oldtail_j=j;
                }
                if(snake[i][j]==2)
                {
                    oldhead_i=i;oldhead_j=j;
                }
            }

        }
    }
    int newhead_i,newhead_j;
    switch (movedirection)
    {
        case 1:newhead_i=oldhead_i-1;
                newhead_j=oldhead_j;break;
        case 2:newhead_i=oldhead_i+1;
                newhead_j=oldhead_j;break;
        case 3:newhead_i=oldhead_i;
                newhead_j=oldhead_j-1;break;
        case 4:newhead_i=oldhead_i;
                newhead_j=oldhead_j+1;break;
    }
    if(snake[newhead_i][newhead_j]==-2)
    {
        snake[food_x][food_y]=0;
        srand(time(NULL));
        food_x=rand()%(high-5)+2;
        food_y=rand()%(width-5)+2;
        snake[food_x][food_y]=-2;
        sum++;
        snake[high+2][2]+=sum*2;
    }
    else
    {
        snake[oldtail_i][oldtail_j]=0;
    }
    if(snake[newhead_i][newhead_j]>0||snake[newhead_i][newhead_j]==-1)
    {
        QMessageBox::about(this, "See", "Game Over!");
        exit(0);
    }
    else
    {
        snake[newhead_i][newhead_j]=1;
    }
}


void MainWindow::keyPressEvent(QKeyEvent  *event){
    if(x == 0 && event->key() == Qt::Key_Space){
        x = 1;
        connect(ctime, SIGNAL(timeout()), this, SLOT(move()));
        connect(ctime, SIGNAL(timeout()), this, SLOT(update()));
        ctime->start(100);
    }
    switch (event->key()) {
        case Qt::Key_W:
            if(movedirection != 4)
                movedirection=3;
            break;
        case Qt::Key_S:
            if(movedirection != 3)
                movedirection=4;
            break;
        case Qt::Key_A:
            if(movedirection != 2)
                movedirection=1;
            break;
        case Qt::Key_D:
            if(movedirection != 1)
                movedirection=2;
            break;
        default:
            movedirection=movedirection;
            break;
    }
}

分部解析

  1. 算法核心部分move

    没有改动,只是把它计时器ctime连接起来,使得每次达到计时周期的时候都能执行一次move函数,也就是更新一次坐标信息。

    同样,在达到更新周期的时候,update刷新页面。

  2. 相应键盘操作

    重写了虚函数keyPressEvent,然后利用其事件对应的key,来判断是按下了哪个按键,从而完成之前的kbhit+getch的操作。

  3. 画出指定的图形

    利用了QPainter,和重写了虚函数paintEvent。利用每次update都会调用一次paintEvent来实现每次move后都paint一次。

  4. 计时器

    利用QTimer,设置计时周期后,将周期到达时发出的timeout信号函数连接到move和update上,使得每次到达周期都能进行一次更新。

简单用法

  1. keyPressEvent

    利用其参数QKeyEvent *event,其中存储了按键的信息。我们利用event->key()就可以得到其键值。与QT基层的按键进行比较,即可完成之前的getch然后进行比较的工作。不过还要看第五条才能正常工作。

  2. QPainter

    QPainter打印字符(串)的用法是QPainter::drawText(x,y,字符串),其中xy都是需要打印位置的左下角(如果看作是矩形的话)。

    QFont可以设置字体,通过设置QFont的一些参数,然后QPainter :: setFont(font)就可以把所设置的字体应用到对应的QPainter里面。

    关于QPainter画其他东西可以参考:Qt 之图形(QPainter 的基本绘图)

  3. QMessageBox

    这个是一个小弹窗,为了美化使用体验而制作的。最简单的用法如下:QMessageBox::about(parent(经常用this就行),title_name,message)其中title_name是说框框左上角的那个名字,message就是框中间的内容。

  4. connect

    是一个手动连接信号和槽的函数,最简单用法参考如下:

    connect(name0,SIGNAL(name1()),name2,SLOT(name3()))

    含义是当name0.name1()信号被发出的时候,name2.name3()这个函数就会被系统调用。具体实现原理是QT给做的。

  5. 关于键盘响应

    只有在焦点的窗口才能接受键盘事件。而焦点设置,默认每个窗口都不是焦点。如果你想能够通过Tab和点击来将焦点确定在该窗口(就是说,点一下它,它就是焦点。Tab一下它,它就是焦点),那么你需要设置一个强聚焦。QMainWindow::this->setFocusPolicy(Qt::StrongFocus)。当然还有单独使用某种聚焦方式的,可以查阅相关参数(对应Qt::StrongFocus)的部分。

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值