JS精彩实战之<智能迷宫>
———宝贵编程经验分享会———
hello大家好,这里是Web云课堂,之前的一年里我们经历了Html和CSS的系统攻城,此时的你们已经是做静态(动静结合)网页的高手了,本堂课的主讲师JimJin将带领大家进入成为Web安全专家的第4阶段第三小节:JavaScript的实战实验。本节课我将演示如何运用Web编程语言JS来制作一个完整的系统的网页应用程序,相比之前的理论和小实验,本次的任务将是前所未有的巨大,因为我们要做的是一个系统,一个软件,一个面向客户的Application,所以这也是对之前JS基础学习的一个总结。本次实验完成后JavaScript也就告一段落了,之后我们将进入服务器脚本语言的学习。
OK,以上都是废话:)如果说html和css语言都没必要学(因为有图形软件),但不同于H5的标记语言和CSS的渲染语言,程序语言(涉及到大量的数值逻辑计算)JavaScript是非学不可,笔记JS是所有程序语言中最方便同时最易学的语言(有之一),由于它凌驾于browser之上的特性,它是最方便的小软件制作器。OK,let’s进入主题。
诶,实在抱歉又说了一堆废话。。但有原因的因为今天。。咳咳,不说了,我这次的做的软件是一款游戏——迷宫。有所不同的是,这个迷宫是一个智能迷宫,何谓智能?在我的字典里,智能迷宫是一个*探索*调整,功能齐全的系统迷宫,其中包括如下功能:
1.能够更改所有元素的颜色。
2.能够变换任意形状的尺寸。
3.能够*调整地图的大小。
4.能够随机生成迷宫的地图。
5.能够*绘制路线的位置。
6.能够从起点或者从终点走。
7.能够沿途记录走过的路径。
8.能够瞬间移动到之前的点。
9.能够有多种难度迷宫算法。
10.能够自定义背景音乐图片。
11.能够支持计时和分数系统。
12.能够拥有快速冲刺的技能。
13.能够提供完善的帮助提示。
很好,先贴出成品H5文件,网盘地址为:
https://pan.baidu.com/s/1hrAVM3U
同学们先下载这个安全文件,用自家的浏览器打开,但因为本人只Safari和Firefox上测试过,并不能保证所有浏览器兼容,所以建议你们使用Firefox、Chrome、Safari、Opera之一的浏览器打开,最好不要IE。。下面进入游戏的全流程讲解。
以下是作者我花了两周时间(挤压下来)中的逻辑步骤,当然这是整合版的,因为这其中我经历了无数的困难,专业程序员debug的步骤都是:发现问题、解决问题;而我:感觉不对劲、发现问题、寻找问题、研究问题、就决问题失败、换条思路、发现新问题、喝口茶、重思考问题。。。。好了,所以我总结出了一个人编写软件的核心十个步骤,不仅仅是这个游戏:
第一步:确定底层原理
1)画布canvas
作为一个唯物主义共青团员,我首先需要确定智能迷宫及其周边环境的物质组成原理。我们需要确定迷宫的物理结构,换句话说,它是用什么做的。这里我一开始也思考了许多方案:比如可以采用HTML5提供的画布元素来绘制迷宫,通过画布上的重复性的擦写操作来完成游戏;当然我们也可不用画布而是采用表格元素,在晶格阵中每格都是可走的位置;我们还可以引入大量的元素并用CSS进行表格布局;最后我们还可以通过一张张的地图图片img来表现迷宫,不过这种办法真是,即使最坏的情况也不会用它。。
2)绝对定位
因为方便最终我选择了迷宫部分使用画布canvas,而行走对象借用一个上层元素绝对定位来完成行走。绝对定位是从html流中剔除出而凌驾于画布之上的元素,因而两者间相互独立,绝对定位也很方便,属于“动态元素”。
3)浮动元素
智能迷宫还需要一个控制台,这个我决定使用一个div,其中包括一些表单控件。下面是html控制台部分的代码:
<div style="float: right;" id="controller">
键盘控制:<br>
<input type="radio" id="move_origin" name="keyboard" disabled>起点
<input type="radio" id="move_destination" name="keyboard" disabled>终点
<input type="radio" id="move_pen" name="keyboard" checked>笔尖<br>
设置颜色:<br>
背景:<input type="text" id="canvas_color" value="#eee"><br>
起点:<input type="text" id="origin_color" value="red"><br>
终点:<input type="text" id="destination_color" value="lime"><br>
墙体:<input type="text" id="wall_color" value="black"><br>
路径:<input type="text" id="path_color" value="yellow"><br>
是否记录路径:<input type="checkbox" id="record" ><br>
<button id="paint" onClick="paint()" style="font-size: 1em;">开始制图</button><br>
坐标(x,y):<br>
起点:<input type="number" id="ori_x" value="1" min="1" max="50" step="1" onChange="ori_x_cha()">
<input type="number" id="ori_y" value="1" min="1" max="50" step="1" onChange="ori_y_cha()"><br>
终点:<input type="number" id="des_x" value="50" min="1" max="50" step="1" onChange="des_x_cha()">
<input type="number" id="des_y" value="50" min="1" max="50" step="1" onChange="des_y_cha()"><br>
笔尖:<input type="number" id="pen_x" value="0.5" min="0.5" max="50.5" step="1" >
<input type="number" id="pen_y" value="0.5" min="0.5" max="50.5" step="1" ><br>
地图设置:<br>
墙体厚度:<input type="number" min="1" max="10" step="1" value="3" id="wall_size"><br>
方块边长:<input type="number" min="1" max="50" step="1" value="12" id="square_size"><br>
水平数量:<input type="number" min="1" max="99" step="1" value="50" id="amount_x"><br>
垂直数量:<input type="number" min="1" max="99" step="1" value="50" id="amount_y"><br>
迷宫生成算法:<br>
<button id="generate" onClick="generate()" style="font-size: 1em;">简单模式</button>
<button id="advanced_generate" onClick="advanced_generate()" style="font-size: 1em;">高级模式</button><br>
其他:<br>
<button onClick="alert('尽请期待')">背景音乐</button><br>
<button onClick="alert('尽请期待')">得分系统</button>
</div>
第二步:选择合适的框架
1)内核很重要
迷宫的框架!!至少这是个2d的游戏(3d本人正在研究),又至少排除了曲线图,全部采用“横平竖直”的矩阵结构。然后我考虑了目标对象该如何移动:是渐变还是突变?墙该怎么画:虚拟还是实体?还有——对象目标是一个色块还是一个图像?最后我决定采用实体墙和正方格,移动对象也是正方形,用背景色标注。考虑到很多浏览器不支持颜色选择器,颜色采用input text,支持CSS的三种颜色书写格式:常用名称、16进制和RGB格式。其他参数使用input number,剩下的控件都是button,哦对了,还有一个input radio,待会有用。
2)分层分类
正如《星港》中上帝(就是平行宇宙外的那个五维生物)所说,“你们人类永远只知道分工而不会合作”。当然这不是反人类,而是揭露了我们三维宇宙的一些不变定理:系统都是由不同部门分层分类地分工完成的任务。
站在最高层,总共4个H5元素,分别是canvas,div控制台(float:right),起点和终点div(position:absolute)。canvas的最底层是背景色,上层由方块square和墙wall组成:起点div的z_index大于终点div的z-index以便到达终点时可以覆盖,但他们都是直接覆盖在canvas之上的。控制台div很灵活,因为是浮动定位,可以随浏览器窗口随意漂浮。控制台主要由一些表单元素和说明文字组成:(打开文件就能看到,这里就不截图了)。
第三步:建立虚拟媒介
1)虚拟坐标
为什么呢?画布的原始坐标是以像素为单位,但是为了满足需求,可以*变换尺寸和大小,需要一个内外模式之间的中介,那就是虚拟坐标。因为语言上说明有点难度,所以直接上图:
第四步:定义一些关键的变量
1)数值字段
这里小编总结了一句philosophy:算法工程师靠经验来CODING,码农靠知识来DEBUGING!!fuck重复的变量:小编在这上面也是吃了很多亏。。并不是每个可变的量都给他分配一段变量!!尤其我们这里有一个input number元素需要实时显示value值,我们就可以直接利用这个value字段来存储唯一的内存空间。
var wall_size=3;//墙的宽度
var square_size=12;//方块的边长
var amount_x=50;//x的最大坐标
var amount_y=50;//y的最大坐标
2)存储型变量
用来存放大量数据,比较考验数据结构的理论。
var wall=new Array();//用来存放所有的墙
var shortest_path=new Array();//用来存放随机路径
var direction=new Array();//用来存放可走的方向
var already_path=new Array();//用来存放沿途走过的路径
3)状态变量
该字段也很重要,总共设置了两个:一个state变量用来显示此时迷宫是在play状态还是draw状态;还有一个get变量,后面会介绍用途。
4)标签对象
Very Important!索性把所有标签元素全给document.getElementById了,不然后面引用的时候累死宝宝。虽然我并没有全设:
var wall_color=document.getElementById("wall_color");
var path_color=document.getElementById("path_color");
var origin=document.getElementById("origin");
var destination=document.getElementById("destination");
var pen_x=document.getElementById("pen_x");
var pen_y=document.getElementById("pen_y");
var ori_x=document.getElementById("ori_x");
var ori_y=document.getElementById("ori_y");
var des_x=document.getElementById("des_x");
var des_y=document.getElementById("des_y");
var canvas=document.getElementById("canvas");
5)参数变量
这个指的是那些辅助变量,比如用于循环语句的i和j,以及用于JS方法的形式参数event和key等等。
第五步:初始化
1)独立区域
这里强烈建议window.onload中分配一块区域专门用来做初始化,而不要像核显一样集成在Html中,相信我,这样会便于后期管理!!这里我主要初始化的内容有:数值字段的缺省值、存储变量的内存空间、所有标签元素相关的属性。因本游戏不是大型软件,在独立区域只安排了部分初始化,其余都直接在html元素属性中完成:
for(var i=0;i<=100;i++){
wall[i]=new Array();
for(var j=0;j<=100;j++)
wall[i][j]=new Object();
}
for(var i=1;i<=100;i++){
already_path[i]=new Array();
}
clear_already();
draw_border_wall();
origin.style.top=canvas.offsetTop-(-wall_size)+"px";
origin.style.left=canvas.offsetLeft-(-wall_size)+"px";
第六步:思考数据结构
1)矩阵
这一步耗费了我好多精力,我们的矩阵框架想要合理的存储还不是那么容易。思来想去,最终决定用二维数组来存放,因为虚拟坐标的帮助,“方块”的坐标直接写成数组的下标,数组元素值暂定,比如可以0无1有。
2)2.5维数组
这个是用来存放墙的。因为墙的特殊性:横纵坐标中有且只有一个小数。如果将坐标值乘以2存入二维数组中会浪费内存空间,如果只在x方向上乘以2又难以管理整个数组。所以应该采用2.5维数组,这是我起的名字,其实就是个二维数组,只不过每个元素都有子对象,形成了“第三维”,但这“第三维”只有2层,遂我美其名曰“2.5维”。具体实现方式就是:存放坐标为方块,子对象是right和down,值是0或1 ,如图所示:
3)顺序表
之所以没有采用树形存储结构的原因是——忘了怎么定义树。。不过没关系,顺序表同样能满足需求:核心算法中将讲到他。顺序表是一个一维数组,最可贵的是他提供pop()和push()函数,这样就可以把它当做一个堆栈来使用。
第七步:编写常用的函数
1)优选API
浏览器提供的系统函数非常有用,现成的拿来直接用。比如Math对象的库函数、Array对象的库函数等等。这里就不列举了。
2)输入输出型
输入输出型函数就是一个数学函数,在SmartMaze中是一些辅助道具。这里面的direction数组用来存放制图时哪些方向可走,但我找了半天却没找到删除数组制定元素值的函数,无奈之下只能自定义:function del_array(array,data){}也许不久的将来,或许是你读到这篇文章的时候,JS已经支持这些新方法了,期待吧!
3)无参函数
行为函数(没有参数的函数)才是我们用到的主流函数,它的任务就是完成一系列动作,对系统做出一些改变。定义这种函数需要注意,一定要考虑周全,比如状态变量、存储表是否需要做出相应的更改。这里定义了好多好多:
function set_canvas_size();//重置画布尺寸
function set_origin_position();//重置起点位置
function set_destination_position();//重置终点位置
function clear_canvas();//清空画布
function draw_border_wall();//画边界墙
function clear_already();//清空already_path数组
function look_around();//环顾可走的方向
function random_draw_wall();//随机画墙
function forbid_wall();//设置不可随机画的墙
function run();//“弱智图”算法
function advanced_run();//“专业图”算法
4)事件监听
事件触发函数是自动执行的函数,系统会一直监听环境,当触发条件被打破,函数立即被执行。本游戏的三种事件:
document.getElementById(“canvas_color").onchange=function();
document.getElementById(“origin_color").onchange=function();
document.getElementById(“destination_color”).onchange=function();
document.getElementById(“wall_size").onchange=function();
document.getElementById(“square_size").onchange=function();
document.getElementById("amount_x").onchange=function amo_x_cha(){
document.getElementById("amount_y").onchange=function amo_y_cha(){
wall_color.onchange=function(){
path_color.onchange=function(){
ori_x.onchange=“ori_x_cha()”;
ori_y.onchange=“ori_y_cha()”;
des_x.onchange=“des_x_cha()”;
des_y.onchange=“des_y_cha()”;
canvas.onclick=function(event){}
document.body.onkeydown=function(key){}
第八步:确定核心算法
1)强大的递归
玩过的同学都看到,智能迷宫(SmartMaze)5.0版本以上都提供了两种迷宫生成算法:“弱智模式”和“专业竞速”(后来改成了简单模式和高级模式)。其实这两种模式的核心算法是类似的,确切说,后者是在前者的基础上改进而来的。既然要完全随机,那么给定一个起点和终点,之间唯一的路线的随机性必然也要囊括所有的可能性。具体思路如下:从起点或终点出发,每走一格的方向都是四个方向中随机的但同时既不能撞到墙也不能走回头路,就像这个样子:
这里要击破三个困难点:第一个是墙壁碰撞检测;第二个则是与“已走路”之间的碰撞检测。第三个困难点是:检测到碰撞后该朝哪走?要知道,碰撞拐弯之后很可能走入一个死胡同,这该怎么办,现在核心问题就是:如何判断环路?哈哈,方法就多了,但是我选择的办法是:不判断!我们用递归!发句感慨,递归真是算法中最宝贵的财富:当你想不出算法的时候就想它吧:)具体实现看下一段。但是记住,递归函数利于人类思考,代价是大量的机器计算。
2)随机之美
如图所示,每走一步之后,将这个新位置写入最短路径(shortest_path[])的最末端,然后环顾四周(确切是三周)有无墙(wall[]),临走前再次留恋四周看看有无已走过的路(already_path[]),之后在能走的方向上随机选一条,走之前顺便把目前的位置存入already_path。如果无路可走,嘿嘿,将刚才最末端的那个元素给剔除掉,然后返回到上一个位置。以上两条剧情线结束之后便重复所有的步骤,以此循环,哦对了还要一直监听是否到达终点。所以整个求shortest_path[]算法的逻辑逻辑可以写成这样:
fucntion 算法(){
if(shortest_path数组空了){
起点终点被墙分隔();
return null;
}
if(到达终点)return null;
环顾四周();
if(无路可走){
shortest_path弹掉末尾元素();
算法();
}
否则{
随机走一方向();
更新shortest_path();
更新already_path();
算法();
}
}
3)神奇的树
如此一来我们就得到了起点和终点之间的一条完全随机的路线:shortest_path,然后只要在不阻挡最短路径的基础上随机画入其余的墙壁,一个“弱智版”迷宫就大功告成了!:)不过说真,弱智图真的很傻逼,而且很不美观。所以我灵光一闪,高级算法诞生!
高级模式下整个迷宫地图其实是一棵树!!大家都知道,树是没有环路的,所有的叶子都可以作为根。SmartMaze7.0开始,高级算法改变成以终点为根发散,因为虽然树根都是对称的,但是如果从某一点生长出来的迷宫树,会形成一个很明显的“树干”,也就是主干路。如果从起点出发,玩家会发现只有“一条路”可走,减少了游戏的乐趣;相反如果从终点开始制图,迷宫的挑战性会大大增强。!!!
为什么是一棵树呢?其实很简单,“弱智”迷宫是先寻路,再画墙,而“专业”算法是一边寻路一边画墙。我的灵光这样来的:既然“弱智”迷宫的already_path[]肯定覆盖了画布上绝大多数面积,为何不把它利用起来呢?因为already_path除去shortest_path的部分,剩下的都是死路,不就可以画出这些死路来增加迷宫的难度吗?此外寻路时到达终点时不停止而选择继续寻路,直到already_path覆盖整个地图后shortest_path被弹空掉了才停止,这样就画满了整个限定区域,顺便设计一个get状态变量来记录整个过程中是否遇到了终点。搞定!然后逻辑算法这样的:
fucntion 算法(){
if(shortest_path数组空了){
return null;
}
环顾四周();
if(无路可走){
shortest_path弹掉末尾元素();
算法();
}
否则{
随机走一方向();
更新两边墙壁();
更新shortest_path();
更新already_path();
if(到达终点)get=“遇到”;
算法();
}
}
第九步:反向考虑兼容性问题
1)Get新技能!
随着游戏的更新换代,需要不断向其中融入新元素,新功能。目前玩家可以体验到的技能如瞬间移动的融入就要和alread_path[]有机结合才能正常运行。此外我还打算写入一个新技能叫“冲刺”,顾名思义,就是可以快速移动以节约时间。这里我们尽管大开脑洞吧,又比如“穿墙术”“漂移走位”“渐变残影”等华丽的新技能供我们开发。
2)分数系统
这又可以是一个庞大的成就系统,需要考虑种种兼容性问题。同上,虽然我还没写,但不妨幻想一下,比如:完成的地图数量、消耗时间、走过的步数、和最短路径的偏差量。甚至可以在地图上设置一些“分数球”,沿途吃到可加分等等。
3)跨平台
虽然Web独立于底层操作系统,当面对缺少合适的输入设备的手机和pad时候,一个虚拟键盘还是需要的,这将是一个新的固定元素(fixed),因为虚拟键盘需要依附于窗口。
第十步:优化
1)拒绝臃肿
程序最怕臃肿,不仅会严重影响软件性能,还给debugger(调试师)带来无尽的烦恼。要么按照这十个逻辑步骤一五一十地书写程序,将bug虐杀于幼虫之时,要么做完程序一定得复查一遍:取消重复的语句,终止多余的计算,删改无用的对象。只有这样才能让你的程序闪闪发光,得到用户的欣赏。
2)一起来化妆
大胆的化妆吧,这里不用考虑兼容性问题,因为只要通过CSS来美化周边,是完全不影响H5和JS的。背景、边框、内外边距都在你的*掌控之下。除此之外,你也可以添加新的元素来装饰你的作品,比如过场动画,背景音乐,特效等等,再次为游戏锦上添花。
3)更多的帮助
编程是程序员和上帝之间的一场竞赛,程序员努力写出更简单易懂,连*都会用的软件,而上帝则努力创造出更多更傻的*:就目前为止,上帝是赢的。———一个过客。。
当然了我肯定不是说玩家们是*,但是为了面向不同的群体,让其市场化而不得不提供此服务。
于是我在控制台中不同位置加入了许多<button>?</button>,其中onclick=“alert(‘…’)”。为了追求完美,我甚至准备加入中英文语言的选择功能,尽请期待SmartMaze12.0+。
最后一步:展望
结束了,everything is done,开源免费*完美的SmartMaze10.0终于告一段落了。但是我们的学习远没结束,至少这个智能迷宫还有许多可提升的方面。比如:
1)与服务器的交互
毕竟这是个Web应用,来电服务器相应和用户的回馈会大大增强游戏的可玩性,想象就刺激:多人竞赛,即时挑战,自制地图分享,限时追逐赛……
2)转型成3D画面
用JavaScript做3d并不难,好吧我不该发表评论的因为自己目前还不会做。不过SmartMaze3D版本迟早会出现,也许是12.0也许是20.0,或许你读到这段文字的时候我的智能3d版迷宫已经面世了:)
3)曲线迷宫
相比3d迷宫,个人认为这个最有挑战度,因为3d迷宫也可以做成90°,但这个曲线迷宫图还需要新的算法,至少目前没有头绪。Who Care?目前的作品已经够我装一阵子逼了。
OK,所有内容到此结束,如有疑问请留言新浪微博@IT让生活更美好
2017.3.25 19:09