编程语言实用化指南——写在最后
在本书中,我们一起制作了crowbar和Diksam两种编程语言。让我感到欣慰的是,书中的示例程序不只是停留在入门级别,而是达到了实用语言的水准。
亲爱的读者朋友们,希望你们也能尝试制作属于自己的编程语言。不过说出来你们可能会大跌眼镜, 编程语言的魅力基本上是由它的程序库来决定的 ,而这是不容争辩的事实。
例如,Perl由于正则表达式等强有力的字符串处理功能得到了广泛的应用。在Perl4的时候,作为编程语言,Perl既没有引用,也不能创建数据结构,可以说很难用。但是,因为它处理文本文件十分方便,所以得到了广泛使用。同样, PHP也因为提供了很多面向网页应用的功能而得到了普及。语言是否实用,是否能够普及,实际上和语言的设计本身没有太大关系。
因此,我在发明了crowbar和Diksam两种语言后,为它们加载了各自的程序库。
首先,我为crowbar加载了鬼车,使它具有用正则表达式处理文本的能力。
在Diksam中我用crowbar来处理文本。在文本处理这个领域里已经有了Perl、Ruby等语言,因此就算是为此特地制作一门语言也不会得到广泛普及。用来开发Web应用的编程语言更是琳琅满目,比如PHP、Perl、Ruby、Java、ASP、ASP.NET等,在这个领域中还充斥着各种框架,可以说是一个大杂烩。租赁服务器更是让人头疼,好不容易做的网页应用,有可能会因为服务器不能支持而不能使用。就这点来说,已经很让人沮丧了。
因此,我考虑让Diksam定位为“让初学者可以制作简单游戏的语言”。
在很早之前,我自己就是这样走上了编程的道路。
那个时候(1980年左右)的个人电脑,大多将BASIC作为标准配置。那个时候的编程语言没有 IF 语句中 begin~end 或者 {} 之类的程序块的概念,选择分支的时候必须使用 GOTO行号的方式进行跳转。也没有循环结构的 FOR语句。要在循环外面记录循环计数器后,再使用 GOTO 进行跳转。虽然可以使用 GOSUB制作子程序,但是不能定义局部变量,所有变量都要当做全局变量来处理。此外,变量名字不看到第2个字符,是区分不出来的 [1]。当然,这是时代的选择,不过,当时的BASIC作为编程语言来说还真是不怎么样。
即使如此,我当时只用了几十行代码就可以写一款射击游戏(用字符“┴”当做炮台来击落用字符“-o-”做成的飞碟)。我觉得这个过程十分有趣,这也是我踏上编程学习道路的第一步。
但是对于现在的年轻人来说,却不知道怎么去实现一款简单的游戏。
现在这些PC的性能与当时相比可以说有了飞跃性的提高,也出现了各种各样的编程语言和免费的处理器。但是,比如在C语言中,使用不依赖处理器的标准C,就连窗体都打不开。即便只是在Windows中能够运行起来的程序,C语言也要通过Windows的API来创建窗口,如此复杂的程序初学者根本应付不来。
我觉得在现在C语言的入门书中,多半从“ hello,world.”开始介绍许多命令行程序。但是,我在中学时代,从最开始就能写出和“ hello,world.”差不多的程序,接着就编了猜数字游戏,然后就想着要做一款打飞碟的游戏了。当今,计算机已经十分先进,但人们在这个方面反而退化了。
当然,现在不仅可以使用C语言,也考虑使用Java。Java中的GUI可以不依赖于处理器,因此也可以把游戏做成Applet发布在网站上,在朋友面前炫耀一把。但这样一来,在创建类的时候就需要继承一种叫作 java.applet. Applet的类,并重写它的 init()和 paint()之类的方法。这里突然出现了很多面向对象的知识,初学者一时之间很难接受。也许有人会觉得我又在这里老调重弹了,但是仔细想想,为了从GUI接收输入,就必须要创建事件监听器、实现特定的接口,除此之外还需要使用内部类和匿名类。这些还没完,因为制作的是实时游戏,为了实现动画效果还需要使用多线程进行异步处理。这些对于一个新手来说简直是个噩梦。
再者,制作“打飞碟”这样一款游戏对于JavaScript来说也不是很容易。可以制作FLASH的语言ActionScript,它的标准处理器又不是免费的。
HSP(Hot Soup Processor)语言是我在中学时代玩过的类似于BASIC的语言。不过,很对不起这门语言的fans,这门语言本身和与它同时代的BASIC如出一辙,因此对于刚开始学习编程的人来说非常不推荐。它甚至没有GOSUB [2]。
因此,我在Diksam中加载了可以让“打飞碟”游戏实现起来更为简单的程序库 [3]。
因为要制作的游戏非常简单,所以无须特意想着面向对象和事件驱动的概念。例如“打飞碟”游戏可以写成下面这样。
代码清单1-4 “打飞碟”游戏的程序(ufo.dkm)
1: require diksam.window;
2:
3: // 创建设置窗体属性的WindowAttribute对象。
4: WindowAttribute attr = create_window_attribute();
5: // 设置背景色为黑色。
6: attr.background = create_solid_brush(0, 0, 0);
7:
8: // 创建窗体。如果使用默认设置就可以,
9: // 那么不创建attr传入null即可。
10: Window w = create_window(“UFO 游戏”, 800, 600, attr);
11:
12: // 使用x键终止程序。
13: w.set_destroy_proc(window_destroy_and_exit);
14: // 显示窗体。
15: w.show();
16:
17: // 为了描绘窗体取得Graphics接口。
18: Graphic g = w.get_graphics();
19: // 将字符的背景色设置为黑色。
20: g.set_background_color(new Color(0, 0, 0));
21:
22: // 战车、激光、UFO的颜色设置。
23: Color tank_color = new Color(60, 255, 100);
24: Color ufo_color = new Color(60, 255, 255);
25: Color beam_color = new Color(255, 255, 100);
26: // 生成字体。详细的设置(FontAttribute)
27: // 与WindowAttribute相同,当前默认为null。
28: Font font = create_font(25, null);
29:
30: // 随机数的初始化
31: randomize();
32:
33: // 游戏结束后再开始使用的循环
34: for(;;){
35: // 设定炮台(tank)、ufo的坐标。将tank_x,ufo_x,ufo_y的
36: // 当前坐标赋值给prev,作为前一次绘制的坐标(消除时使用)。
37: // ufo_next_x,y作为ufo的移动目标的坐标。
38: // ufo将在ufo_next_x,y的附近移动,
39: // 但如果两次坐标基本相同,则重新设定ufo_next_x,y。
40: int tank_x = 0;
41: int ufo_x = 0;
42: int ufo_y = 0;
43: int ufo_prev_x = ufo_x;
44: int ufo_prev_y = ufo_y;
45: int ufo_next_x = random(700);
46: int ufo_next_y = random(450);
47: // 是否存在炮台发射的激光的标识和激光坐标
48: boolean beam_flag = false;
49: int beam_prev_y;
50: int beam_x;
51: int beam_y;
52:
53: // 游戏的主循环
54: for(;;){
55: // 消除前一次画出来的UFO。
56: g.draw_string(font, ufo_color, ufo_prev_x, ufp_prev_y," ");
57: // 绘制UFO。
58: g.draw_string(font, ufo_color, ufo_x, ufp_y," ├O┤ ");
59: // 为了再次消除,保存本次绘制的坐标。
60: ufo_prev_x = ufo_x;
61: ufo_prev_y = ufo_y;
62: // 绘制炮台。
63: g.draw_string(font, ufo_color, tank_x, 540," /┴\ ");
64: // 发射了激光的话……
65: if(beam_flag){
66: // 消除前面的激光,重画新的激光。
67: g.draw_string(font, ufo_color, ufo_prev_x, ufp_prev_y," ");
68: g.draw_string(font, ufo_color, ufo_x, ufp_y,"|");
69: beam_prev_y = beam_y;
70:
71: // 碰撞判断。可能很幼稚。
72: if(beam_x >= ufo_x && beam_x < ufo_x + 80
73: && beam_y >= ufo_y && beam_y < ufo_y +60){
74: // 被激光打中后跳出循环。
75: break;
76: }
77: }
78:
79: // 通过判断键盘输入移动炮台。
80: if(is_key_pressed(KeyCode.LEFT) && tank_x > 0){
81: tank_x -= 10;
82: } elseif(is_key_pressed(KeyCode.RIGHT) && tank_x < 700){
83: tank_x += 10;
84: }
85: if(is_key_pressed(KeyCode.SPACE) && !beam_flag){
86: beam_flag = true;
87: beam_x = tank_x + 40;
88: beam_y = beam_prev_y = 480;
89: }
90: // UFO的移动。在ufo_next_x,y的附近移动。
91: if(ufo_x < ufo_next_x -10){
92: ufo_x += 10;
93: } elseif(ufo_x > ufo_next_x + 10){
94: ufo_x -= 10;
95: } else {
96: // 如果两次坐标基本相同,则重新设定目标坐标。
97: ufo_next_x = random(700);
98: }
99: if(ufo_y < ufo_next_y - 10) {
100: ufo_y += 10;
101: } elseif (ufo_y > ufo_next_y + 10) {
102: ufo_y -= 10;
103: } else {
104: ufo_next_y = random(450);
105: }
106: // 激光的移动
107: if(beam_flag) {
108: if(beam_y < -20){
109: beam_flag = false;
110: }
111: beam_y -= 20;
112: }
113: // 定时消息循环。无论有没有
114: // 鼠标或者键盘事件,都等待20毫秒。
115: timed_message_loop(w, 20);
116: }
117:
118: // 被激光打中后跳出循环,执行这里。
119: for(;;){
120: // 显示爆炸效果
121: Color explosion_color = new Color(255, 0, 0);
122: g.draw_string(font, explosion_color, ufo_x, ufo_y, "*");
123: timed_message_loop(w, 100);
124: g.draw_string(font, explosion_color, ufo_x, ufo_y, "###");
125: timed_message_loop(w, 100);
126: // 按N重启游戏,按Q退出。
127: if(is_key_pressed(KeyCode.N)){
128: Brush b = create_solic_brush(0, 0, 0);
129: g.fill_rectangle(b, 0, 0, 800, 600);
130: b.dispose();
131: break;
132: } elsif (is_key_pressed(KeyCode.Q)) {
133: exit(0);
134: }
135: }
136: }
游戏的截屏如下所示。
这是个跟我同时代的人都会怀念的画面吧 [4]。
Diksam在这个领域的进化旅程才刚开始,谁也不知道它在未来会变成什么样子。但是,在代码清单“ufo.dkm”的程序中,只能有一架UFO,在同一时间炮台只能发射一发激光(因为表示UFO和激光位置的变量只有一组)。如果觉得这样没意思的话,就必须要使用数组了。x坐标和y坐标要是都使用数组来管理的话,肯定会很不方便,如果有类的话感觉就会方便很多。同样,即使不同时出现多个飞碟,如果想要各种各样的敌人轮番登场,就要使用继承和多态了……一门语言因为追寻着这样的思路而具有了面向对象的概念,真让人兴奋。
各位读者朋友,你们想让自己的编程语言向哪个方向发展呢?希望本书能给各位带来一些启发。
注 释