控制台RPG开发教程8: 代码分析和调整


本次教程的内容:

  1. 隐藏光标
  2. 代码分析练习
  3. c++中的名字
  4. 数组越界的分析

上次课程最后代码的运行效果,在我看来至少还有两个明显的不合理之处,不知道你发现没有?

  • 1 英雄移动过程中,身边始终跟着一个闪烁的光标。
  • 2 英雄最初始的位置,虽然看起来是道路,但我们却不可能再次走到那里去了。

我们分析一下代码,研究这两个现象产生的原因。


以前的课程上讲过,控制台输出组在执行cout << 的动作时有两个内容,
一是在当前光标处显示内容,二是把光标的位置移动到内容的后面。
所以光标在这个位置是正常的,只是它的出现破坏了美观,应该隐藏起来。

这个问题不难解决,控制台输出组有隐藏光标的能力。
我们增加一个这样的函数,与前面讲过的gotoxy一样,我们不必研究这个函数的具体内容。
只须把它放到代码里,并在主程序的开头调用一次就可以了。

void HideCursor(){
    CONSOLE_CURSOR_INFO cursor_info = {1, 0};
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}


我们可以装作完全没有看到程序中的指令,因为这些指令将由精灵的分身来操作。
我们只须知道它的作用是隐藏光标,可以把它当作一个普通的电话功能指令来使用。
不过,如果我们至少可以形式化地看一下这些语句,看看有没有什么值得注意的东西。


CONSOLE_CURSOR_INFO 是一个名字,它在这里,由于后面没有括号,显然它不代表指令。
从它出现的位置看,它也不是一个变量,我们可以猜到,它代表着一种房间的格局。
c++的标准间只有固定的几种,int和string在其中,但这个CONSOLE_CURSOR_INFO不是,它是一种自定义的房间格局。
从名字来推测,这种类型的房间是专门用来存储控制台光标信息的。
精灵大楼的管理相当灵活,根据所放数据的不同,可以自己设定不同的房间格局。具体这个房间的格局是怎样的呢?
这种房间有两小房间,第一个存储光标的厚度,第二个存储光标的可见性。
我们继续看。后面一个cursor_info是名字,这里显然它是一个变量,是在给一个房间贴标签,把这个房间设定成了CONSOLE_CURSOR_INFO格局。后面的 = 符号,告诉我们,这是一个赋值表达式,将右边数据保存到左边变量名所指定的房间里。
右边的数据用一对花括号包围,代表初始数据,直接将两个数据保存到房间中。
在这里,光标厚度被定义为1,光标可见性为0,也就是不可见。
下面一条指令,SetConsoleCursorInfo,外部函数。它接收两个参数。
第一个参数通过另外一条电话指令GetStdHandle获得,而GetStdHandle接收一个参数,而这个参数是一个名字
STD_OUTPUT_HANDLE,这个名字不是函数,不是变量,也不是变量类型,它是一个常量。这个名字是给写程序的人看的,这一点和注释有点类似,区别是精灵不能看到注释,但能看到常量,只不过当精灵看它的时候,它变成了一个具体的数字(-11)。我们人类很难记住这些数字的具体值,于是我们给一些常用的数据起了名字,这些都记录在windows.h电话本中。
SetConsoleCursorInfo的第二个参数,是房间cursor_info的实际地址。我们在前面的课程提到过这种表达方式。
我们可以从函数的命名来猜测一下SetConsoleCursorInfo函数的功能,它的用处就是设置光标的信息,因为其中的可见性是0,所以执行之后光标就不可见了。


至此,我们已经见过了大部分c++中名字的类型。我们在这里总结一下。

  • 第一类名字比如if, while, for,它们有着明确的特殊动作含义,另一些如int, string它们代表着c++的基本变量类型(房间格局)我们可以把它们都当作特殊名字。这类名字是c++自带的,命名为关键字。

其余几类都是可以自定义的。

  • 第二类名字,函数名
  • 第三类名字,变量类型名
  • 第四类名字,变量名
  • 第五类名字,常量名

第一类关键字,我们会经常遇到,看见就会认得。后面几类名字怎么区分它们呢?如果一个名字后面跟着圆括号,基本可以肯定就是函数名。如果它独立出现后面跟空格和一个独立的名字或一个赋值表达式,则它代表着一种变量类型。如果一个名字出现在赋值表达式的左边,那可以肯定它是一个变量名。但当它出现在右边或者函数中时,变量名和常量名的使用格式上没有明显的区分,一般习惯是常量名使用大写,而变量名用小写,但这不是必须的。所以如果命名未遵守通常习惯的情况下,如果想搞清楚,就必须找到它们最初定义的位置。
在dev-c++中,我们按住ctrl键的同时,用鼠标点击这个名字,就会跳转到它最初定义的位置。


继续下一个问题,为什么英雄最初的位置是空地,但不能走上去呢?我们看一下最初地图的内容就知道了。最初的地图里,把英雄的图标保存到了地图中,所以当我们做判断的时候,会检测到那里不是空格,所以不能移动。
我们思考一下,从逻辑上,把英雄的标记直接放到地图数据里也确实不合理。我们应当把英雄标记从mapInfo[1]的地图数据中去掉,换为两个空格。

那英雄怎么办呢?我们可以再加一句显示指令,把英雄单独显示出来。
这里把修改后的完整代码贴上来,读者可以找一下,独立显示英雄的语句在哪里?

# include <iostream>
# include <conio.h>
# include <windows.h>
using namespace std;

void HideCursor(){
    CONSOLE_CURSOR_INFO cursor_info = {1, 0};
    SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

void gotoxy(unsigned char x,unsigned char y){
    COORD cor;
    HANDLE hout;
    cor.X = x;
    cor.Y = y;
    hout = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hout, cor);
}

int main(){
    HideCursor(); 
    string mapInfo[5];
    mapInfo[0]= "■■■■■■■■■■■■";
    mapInfo[1]= "        ■■          ■";
    mapInfo[2]= "■  ■    ■■  ■■  ■";
    mapInfo[3]= "■  ■■        ■      ";
    mapInfo[4]= "■■■■■■■■■■■■";
    for (int i=0; i< 5; i++){
        cout << mapInfo[i]<< endl;
    }
    int x, y;    
    x= 0;
    y= 1;
    gotoxy(x,y);
    cout << "♀";
    int a;
    while(1){
        a= getch();
        if (a==72) {
           // 向上 
           if (mapInfo[y- 1][x]== ' ') {
               gotoxy(x,y);
               cout << "  ";
               y= y- 1;
               gotoxy(x,y);
               cout << "♀";
           }
        }
        if (a==80) {
           // 向下 
           if (mapInfo[y+ 1][x]== ' ') {
               gotoxy(x,y);
               cout << "  ";
               y= y+ 1;
               gotoxy(x,y);
               cout << "♀";
           }
        }
        if (a==75) {
           // 向左 
           if (mapInfo[y][x- 2]== ' ') {
               gotoxy(x,y);
               cout << "  ";
               x= x- 2;
               gotoxy(x,y);
               cout << "♀";
           } 
        }
        if (a==77) {
           // 向右 
           if (mapInfo[y][x+ 2]== ' ') {
               gotoxy(x,y);
               cout << "  ";
               x= x+ 2;
               gotoxy(x,y);
               cout << "♀";
           } 
        }
    }
}

好,现在我们代码的运行效果看起来比较正常了。
但如果你仔细阅读代码,或许还能发现一个问题。什么问题呢?
当我们的英雄在屏幕最初始位置的时候,也就是当它的y坐标是1,x坐标是0的时候,我们如果按下键盘向左键。
精灵会访问哪个房间去查找数据呢?
根据代码,我们知道那是mapInfo[y][x-2]当x=0,y=1的时候,实际访问的是mapInfo[1][-2]。
可是我们知道,字符串房间的第一个房间编号应当是0,后面的房间编号应当是越来越大的,这-2访问的是哪个房间呢?
实际上,精灵遇到这种情况,也完全是糊涂的。它会站在mapInfo[1]房间的门口,倒退两步,直接推开一个门去查看是不是空格。完全不管人家房间里有没有可能在进行着某种私密活动。


当然,这种行为是有一定风险。一般情况下,那里不会那么巧正空格,精灵看到不是空格,就确定我们的英雄不能走过去,万事大吉。可如果碰巧正好别人在那个房间里存放了一个空格,精灵就会误报那里可以走。更有甚者,如果房间是私密不可访问的,房间主人还可能会大发脾气。这时我们的精灵就遇到了大麻烦,手头的工作无法继续,程序也就出现了问题。
所以,最稳妥的办法,是我们在访问房间前,先做一个判断,如果编号根本就超出了房间的范围,就不用去访问了。直接报不能移动。
为了做这件事情,我们应当记录地图大小,还应当增加判断条件。这些工作并不难,增加一些代码就能做到。
比如,我们可以增加两个变量:w和h,分别代表地图的宽和高。当向左移动时,我们首先判断是否x-2>=0,同理当向右移动时,我们首先判断是否x+2<w,然后才继续。(为什么后一个是 x+2<w,而不是x+2<= w,大家可以认真思考一下。)

但现在我们的程序已经很长了,而且我们明显在四个方向的代码中看到了一些结构上相似性。
编程的工作,也是一个不断抽象和重整的过程。下次课程,我们将看看是否能够做出一些调整。


课程小结:


c++编程中一共出现4种括号,我们现在已经都遇到了,现在略做总结。

  • 圆括号(小括号()):

    后面跟有圆括号的名字代表它是函数
    圆括号中传递参数(while,if, for语句的圆括号,都可以理解为传递参数)
    圆括号表示指令的优先级

  • 方括号(中括号[]):

    用于访问数组的编号

  • 花括号(大括号{}):

    花括号标志一个指令集合
    花括号中的内容代表一个数组,用于数组初始赋值。

  • 尖括号(其实是一对小于和大于号<>):

    目前我们只见到它们用在include语句中,标志被引用的电话本

本节课比较多地探讨了精灵大楼房间的各种具体问题,并演示了通过阅读分析代码来理解代码,找到并解决问题的过程。


 

发布了24 篇原创文章 · 获赞 0 · 访问量 4574

猜你喜欢

转载自blog.csdn.net/xiaorang/article/details/104838264