因为第三方跨域限制,图片统统没上传...
第五步:初始化
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)更多的帮助
编程是程序员和上帝之间的一场竞赛,程序员努力写出更简单易懂,连傻子都会用的软件,而上帝则努力创造出更多更傻的傻子:就目前为止,上帝是赢的。———一个过客。。
当然了我肯定不是说玩家们是傻子,但是为了面向不同的群体,让其市场化而不得不提供此服务。
于是我在控制台中不同位置加入了许多,其中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,所有内容到此结束,如有疑问可以关注我的https://github.com/JinHengyu
2017.3.25 19:09