前言
以前在TC上编写了一个小游戏--俄罗斯方块(参见以前的文章: ),现在想试玩一下,现在我的电脑是win10系统,发现TC完全运行不起来了。
TC.exe运行不起来,报错:
不支持的16位应用程序:与64位版本的windows不兼容。
可是,我还是想让它能跑起来。
上网查了一下,发现并不能简单的移植,还是需要做一些工作的。
下面记录一下我将这个小游戏移植到VS2019中的经历。
一、选择移植方案
首先是上网进行搜索,看有哪些移植方案。
在参考了一些网上的文章后,我选择使用 easyx 库来进行画图,这个库的使用方式非常接近原来TC中的画图方式。
先需要安装库,步骤比较简单,如下:
(1)到 www.easyx.cn 下载 EasyX 库,解压。
(2)执行 Setup 安装,点下一步,可以看到 VC 版本选择界面:
(3)找到您希望安装 EasyX 的 VC 版本,点后面的“安装”。
(4)等待安装完成即可。
二、新建工程
在VS2019中新建了一个工程,选择控制台程序。
之所以选择控制台程序,是因为其流程,与TC程序的控制流程基本相同。
三、将原来的C文件,整个拷贝到VC的主要的cpp文件中。
例如,我就是将原来TC中的 BLOCK.C 中的内容,完全拷贝到新建的工程中的 block.cpp 中。
四、开始编译
当然遇到一堆的报错,大体分为4类:
1,未定义的标志符:
1)例如图形模式初始化(initgraph())的参数: VGA
在easyx中,不需要这样的参数了,只需要指定分辨率就行了。
原来的初始化:
initgraph(VGA, VGAHI, "c://tc//egavga.bgi");
修改为:
initgraph(640,480);
2)getch()未定义,添加相应头文件:conio.h
3)randomize()、random()未定义,
修改:
randomize() -> srand((unsigned)time(NULL)) -》相应添加time()函数需要的头文件 time.h
random(100)->rand()%100
2,有一些函数VS提示为不推荐,修改为使用VS推荐的函数来代替:
getch()->_getch()
kbhit()->_kbhit()
sprintf()->sprintf_s()
fp = fopen("bloscore.txt", "rb+"); -> fopen_s(&fp, "bloscore.txt", "rb+");
3,缺少类型说明符
是main()函数,需要返回int类型。
main() -> int main()
并且,在最后简单添加返回值:
return 0;
4,没有与参数列表匹配的重载函数
在界面上显示文本的时候,我调用的这个函数报错了:
outtextxy(20, 20, str);
这个函数有两个定义:
void outtextxy(int x, int y, LPCTSTR str);
void outtextxy(int x, int y, TCHAR c);
由于我是要显示一个字符串,所以希望是上面那个函数,即第三个参数使用 LPCTSTR 。
继续跟踪,
typedef LPCWSTR PCTSTR, LPCTSTR;
typedef _Null_terminated_ WCHAR *NWPSTR, *LPWSTR, *PWSTR;
typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
是16位的UNICODE编码格式的宽字符。
而我传入的是char*,所以需要做类型转换。
当然,有另一种解决方法,就是修改工程属性,不使用宽字符:
配置->属性->高级->字符集 修改为"使用多字节字符集"
但是官方建议使用宽字符,在VS2013及以后版本中默认使用Unicode字符集了。 这样使程序能够更加国际化,在不同语言设置的计算机上运行且文字显示正常。
为了通用,我这里是修改为使用宽字符函数。 考虑到这个函数outtextxy()有多处调用,每个地方都做类型的转换,比较麻烦,所以,封装了一个函数,接收char*的输入,转换为wchar*,然后调用输出显示。如下:
void outtextxy_z(int x, int y, char* str) {
TCHAR lpszBuf[1000];
wmemset(lpszBuf, 0, 1000);
int nLen = strlen(str);
int nwLen = MultiByteToWideChar(CP_ACP, 0, str, nLen, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, str, nLen, lpszBuf, nwLen);
outtextxy(x, y, lpszBuf);
}
这样,就只是简单的进行函数名称的修改,就解决这个编译错误了,如下:
outtextxy(20, 20, str); -> outtextxy_z(20, 20, str);
至此,blcok.cpp编译通过。
五、运行调试
虽然编译通过了,但是,移植工作并没有结束。
此时,运行程序,确实画出了一个界面,左上角的标题栏是block,正常,但是画面里面一片黑,没有我希望的那些方块出现!
心里有点发慌,为什么?怎么办?
既然是程序有问题,那就开始调试吧。
先理理思路:
首先,回归到起点:我能否画出一个基本的方块来?
还记得之前的文章中说的么,能画出一个方块来,就可以画出多个方块来,进行合适的拼装,就是俄罗斯方块了。
先添加调试手段,可以单步调试,也可以添加输出调试信息。
单步执行并没有问题,但是界面显示不出方块来,很可能是画图的方法存在问题。
参考easyx的例程:
图解在VC里使用graphics.h绘图(类似TC)
可以正常运行。
通过对比例程,发现有4个地方的差异需要修改:
1,颜色取值的差异
在TC的setcolor()函数中,参数是一个0-15的值,代表一种典型颜色。
而在easyx中的setcolor()函数中,参数值是一个RGB的值,取值范围为0-0xffffff。
这里,就需要有一个对应关系:
先弄明白原来在TC中的值的含义:
black 0 drakgray 8
blue 1 lightblue 9
green 2 lightgeen 10
cyan 3 lightcyan 11
red 4 lightred 12
magenta 5 lightmagenta 13
brown 6 yellow 14
lightgray 7 white 15
一共是0—15
在easy.h中,有对应的颜色常量值定义:
// Color constant
#define BLACK 0
#define BLUE 0xAA0000
#define GREEN 0x00AA00
#define CYAN 0xAAAA00
#define RED 0x0000AA
#define MAGENTA 0xAA00AA
#define BROWN 0x0055AA
#define LIGHTGRAY 0xAAAAAA
#define DARKGRAY 0x555555
#define LIGHTBLUE 0xFF5555
#define LIGHTGREEN 0x55FF55
#define LIGHTCYAN 0xFFFF55
#define LIGHTRED 0x5555FF
#define LIGHTMAGENTA 0xFF55FF
#define YELLOW 0x55FFFF
#define WHITE 0xFFFFFF
我们只需要一一对应替换即可。
2,设置颜色的方法;
在easyx中,有多个设置颜色的函数
setfillcolor
setlinecolor
settextcolor
setbkcolor
在TC中,就是一个setcolor(),所以,需要根据画图的内容,来选择不同的设置颜色的函数。
3,画图的方法;
在easyx中,画矩形分两个函数:
一个是画矩形的边框:
rectangle
一个是画填充颜色的矩形:
fillrectangle
画圆形也是一样:
circle
This function is used to draw a circle without filling
fillcircle
This function is used to draw a filled circle with a border.
所以,我们要根据画边框还是填充图形,来选择不同的画图函数。
4,设置填充的方式;
setfillstyle()函数的调用方式,参数不同,也需要调整。
举个例子,
以前在TC中是这样画一个矩形:
setfillstyle(1, 14);
setcolor(14);
rectangle(200 , 10 , 215, 25);
现在使用easyx,对于要填充颜色的矩形,要改为这样:
setfillstyle(BS_SOLID);//设置填充方式时,可以不指定颜色了
setfillcolor(YELLOW);//设置颜色时,需要指定是填充方式
fillrectangle(200 , 10 , 215, 25);//调用的函数不同,指明了是要fill的
对于只画边框的,如下改写:
setlinecolor(YELLOW);//设置颜色时,需要指定是用于线条的颜色
rectangle(200 , 10 , 215, 25);//调用的函数不同,指明了只是画矩形的边框
改完上面4类差异后,再次运行,就出画面了。初步测试,游戏可以正常进行。
至此,移植工作可以说是基本完成了。
游戏界面如下:
六、完善程序
毕竟是很久前写的程序了,运行时发现有点问题,就又做了点优化:
1,save()函数中,对存储分值的文件,添加打开失败的处理;
2,程序中减去几个_getch(),减少操作,使用起来更顺畅;
3,添加结束的提示信息;
其实就是一个简单的提示,在界面上显示了一句话:
void showGameOver(void)/* 提示游戏结束 */
{
char a, b = 1;
settextcolor(YELLOW);
sprintf_s(str, "%s", "Game over!");
outtextxy_z(20, 60, str);
}
4,改进结束时的判断;
结束判断时,原来是只判断了最顶端的一行是否有方块存在,现在改为判断两行,更合理一些。
在结束判断前,先把新产生的方块画出来,避免还没有看见导致最终结束的方块就结束了游戏。
5,改进延时函数dl()的实现,使用sleep来替换dl函数中的for循环,降低cpu占用率;
void dl(int a)/* 进行延时 ,避免完全占用cpu */
{
//int r, n;
//for (r = 0; r < a; r++)
// for (n = 0; n < 30000; n++)
// {
// n++;
// n--;
// }
int num = 20;
int min_delay = num * 10;
int ms = 1;
if (a < min_delay) {
ms = 10;
}
else {
ms = a / num;
}
Sleep(ms);
}
6,添加一个调试方法,可以在VS的“输出”窗口中输出调试信息:
bool _trace(char* format, ...)
{
char buf[1000];
TCHAR lpszBuf[1000];
va_list argptr;
va_start(argptr, format);
vsprintf_s(buf, format, argptr);
wmemset(lpszBuf, 0, 1000);
int nLen = strlen(buf);
int nwLen = MultiByteToWideChar(CP_ACP, 0, buf, nLen, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, buf, nLen, lpszBuf, nwLen);
va_end(argptr);
OutputDebugString(lpszBuf);
return true;
}
移植前后的代码对比图(部分):
七、源码地址:
俄罗斯方块移植到VS后的源码:https://download.csdn.net/download/lintax/87419128
后记:
我原来还在TC下编写了一个小游戏,贪吃蛇,也完成了到VS2019的移植,路径如下:https://download.csdn.net/download/lintax/87419133