HT for Web 3D游戏设计设计--汉诺塔(Towers of Hanoi)

在这里我们将构造一个基于HT for Web的HTML5+JavaScript来实现汉诺塔游戏。

汉诺塔的游戏规则及递归算法分析请参考http://en.wikipedia.org/wiki/Tower_of_Hanoi

知道了汉诺塔的规则和算法,现在就开始创建元素。用HT for Web(http://www.hightopo.com)现有的3D模板创建底盘和3根柱子不是问题,问题是要创建若干个中空的圆盘。一开始的想法是:创建一个圆柱体,将圆柱体的上下两端隐藏,设置柱面的宽度来实现圆盘的效果,经过多次尝试并查阅相关api文档,发现柱面是没有厚度的,改方法不可行。

后来在HT for Web自定义3D模型的WebGL应用(http://www.hightopo.com/blog/381.html)受到启发,圆盘的形成就是在xy平面上的一个矩形,根据y轴旋转一周产生的,通过查阅相关文档,最总决定采用ht.Default.createRingModel方法来创建圆盘模型,然后在创建node的时候通过shape3d属性引用创建好的模型。

 技术分享

在逻辑实现上,采用了栈的先进后出的原理,对圆柱上的圆盘做顺序控制,确保每次移动的圆盘都是最小的圆盘。

在算法上,采用的是递归算法,通过递归算法,将搬迁过程一步一步记录下来,再采用堆的原理一步一步地执行搬迁过工作。

 技术分享

所有代码和运行效果如下:http://v.youku.com/v_show/id_XODcwMTk4MDI4.html 

  1 var barNum = 5, // 圆盘个数
  2     cylinderHeight = barNum * 20 + 40, // 圆柱高度
  3     barrelMinORadius  = 50, // 圆盘最大外半径
  4     barrelIRadius = 10, // 圆盘内半径
  5     poorRadius = 20, // 圆盘外半径差值
  6     barrelMaxORadius = barrelMinORadius + barNum * poorRadius,
  7     barrelHeight = 20, // 圆盘高
  8     barPadding = 20, // 柱体之间的间隙
  9     floorX = barrelMaxORadius * 6 + barPadding * 4, // 底盘长
 10     floorY = 20, // 底盘高
 11     floorZ = 2 * barrelMaxORadius + barPadding * 2, // 底盘宽
 12     // 柱体集
 13     positions = [
 14         {
 15             barrels: [],
 16             position: [-(2*barrelMaxORadius + barPadding), cylinderHeight / 2 + 1, 0]
 17         },{
 18             barrels: [],
 19             position: [0, cylinderHeight / 2 + 1, 0]
 20         },{
 21             barrels: [],
 22             position: [(2*barrelMaxORadius + barPadding), cylinderHeight / 2 + 1, 0]
 23         }
 24     ],
 25     runOrder = [], // 圆盘移动顺序集
 26     // 动画参数
 27     params = {
 28         delay: 10,
 29         duration: 500,
 30         easing: Easing[‘easeBoth‘]
 31     };
 32 
 33 /**
 34  * 初始化程序
 35  * */
 36 function init(){
 37     dataModel = new ht.DataModel();
 38     g3d = new ht.graph3d.Graph3dView(dataModel);
 39     view = g3d.getView();
 40     view.className = ‘main‘;
 41     document.body.appendChild(view);
 42     window.addEventListener(‘resize‘, function (e) {
 43         g3d.invalidate();
 44     }, false);
 45 
 46     g3d.setEye([0, cylinderHeight * 2, floorX * sin(2*PI/360*60)]);
 47 
 48     // 初始化节点
 49     initNodes();
 50 
 51     moveAnimation();
 52 }
 53 
 54 /**
 55  * 构造游戏移动队列
 56  * diskQuantity:圆盘个数
 57  * positionA:起点
 58  * positionB:中转点
 59  * positionC:终点
 60  * */
 61 function buildRunOrder(diskQuantity, positionA, positionB, positionC){
 62     if (diskQuantity == 1) {
 63         runOrder.push([positionA, positionC]);
 64     } else {
 65         buildRunOrder(diskQuantity - 1, positionA, positionC, positionB);
 66         buildRunOrder(1, positionA, positionB, positionC);
 67         buildRunOrder(diskQuantity - 1, positionB, positionA, positionC);
 68     }
 69 }
 70 
 71 /**
 72  * 移动动画
 73  * positionA:起点
 74  * positionC:终点
 75  * */
 76 function moveAnimation(positionA, positionC){
 77     if(!positionA){
 78         var poses = runOrder.shift();
 79         if(!poses){
 80             setTimeout(reset, 500);
 81         }else{
 82             moveAnimation(positions[poses[0]], positions[poses[1]]);
 83         }
 84     }else {
 85         var barrel = positionA.barrels.pop();
 86         var position = positionC.cylinder.p3(),
 87             barPos = barrel.getPosition3d();
 88         position[1] = position[1] + floorY + barrelHeight * positionC.barrels.length - cylinderHeight / 2;
 89         setPolylinePoints(polyline, barPos, position);
 90         params.action = function (v, t) {
 91             var length = g3d.getLineLength(polyline),
 92                 offset = g3d.getLineOffset(polyline, length * v),
 93                 point = offset.point,
 94                 px = point.x,
 95                 py = point.y,
 96                 pz = point.z;
 97             barrel.p3(px, py, pz);
 98         };
 99         params.finishFunc = function () {
100             positionC.barrels.push(barrel);
101             var poses = runOrder.shift();
102             if (!poses) {
103                 moveAnimation();
104             } else {
105                 moveAnimation(positions[poses[0]], positions[poses[1]]);
106             }
107         };
108         anim = ht.Default.startAnim(params);
109     }
110 }
111 
112 /**
113  * 重置游戏
114  * */
115 function reset(){
116     if(positions[0].barrels.length == 0){
117         positions[0].barrels = positions[2].barrels;
118     }
119     positions[2].barrels = [];
120     for(var i = 0, len = positions[0].barrels.length; i < len; i++){
121         var pos = positions[0].cylinder.p3();
122         pos[1] = pos[1] + floorY + i * barrelHeight - cylinderHeight / 2;
123         positions[0].barrels[i].p3(pos);
124     }
125     buildRunOrder(barNum, 0, 1, 2);
126     setTimeout(moveAnimation, 500);
127 }
128 
129 /**
130  * 初始化节点
131  * */
132 function initNodes(){
133     // 底盘
134     floor = createNode([0, floorY / 2, 0], [floorX, floorY, floorZ]).s({
135         ‘shape3d‘:  ‘box‘,
136         ‘3d.movable‘: false
137     });
138 
139     // 创建柱子
140     for(var i = 0, len = 3; i < len; i++){
141         positions[i].cylinder = createNode(positions[i].position, [20, cylinderHeight, 20], floor).s({
142             ‘shape3d‘:  ‘cylinder‘,
143             ‘shape3d.color‘: ‘#E5BB77‘,
144             ‘3d.movable‘: false
145         });
146     }
147 
148     // 创建圆盘
149     createBarrels(barNum, positions[0].cylinder);
150 
151     // 创建圆盘运行轨迹
152     polyline = new ht.Polyline();
153     polyline.setSegments([1, 2, 4, 2]);
154     polyline.s({
155         ‘shape.background‘: null,
156         ‘shape.border.color‘: ‘rgba(0,0,0,0)‘,
157         ‘shape.border.gradient.color‘: ‘rgba(0,0,0,0)‘,
158         ‘shape.border.pattern‘: [20, 10],
159         ‘shape3d.resolution‘: 50
160     });
161     dataModel.add(polyline);
162 }
163 
164 /**
165  * 设置路线节点
166  * */
167 function setPolylinePoints(polyline, from, to){
168     polyline.setPoints([
169         {x: from[0], y: from[2], e: from[1]},
170         {x: from[0], y: from[2], e: cylinderHeight},
171         {x: from[0], y: from[2], e: cylinderHeight + 60},
172         {x: to[0], y: to[2], e: cylinderHeight + 60},
173         {x: to[0], y: to[2], e: cylinderHeight},
174         {x: to[0], y: to[2], e: to[1]}
175     ]);
176     return polyline;
177 }
178 
179 /**
180  * 创建圆盘
181  * barNum:圆盘个数
182  * host:吸附节点
183  * */
184 function createBarrels(barNum, host){
185     // 圆盘初始x位置
186     var pos = host.p3();
187 
188     for(var i = barNum, j = 0; i > 0; i--, j++){
189         pos[1] = barrelHeight * j + floorY;
190         positions[0].barrels.push(createBarrel(pos, [1, barrelHeight, 1], barrelMinORadius + i*poorRadius, barrelIRadius, host).s({
191             ‘shape3d.color‘: randomColor(),
192             ‘3d.movable‘: false
193         }));
194     }
195 }
196 
197 /**
198  * 创建节点
199  * p3:节点位置
200  * s3:节点大小
201  * host:吸附节点
202  * */
203 function createNode(p3, s3, host){
204     var node = new ht.Node();
205     node.p3(p3);
206     node.s3(s3);
207     node.setHost(host);
208     node.s({
209         ‘wf.visible‘: ‘selected‘,
210         ‘wf.color‘: ‘#FF6B10‘,
211         ‘wf.width‘: 2,
212         ‘wf.short‘: true
213     });
214     dataModel.add(node);
215     return node;
216 }
217 
218 /**
219  * 创建空心圆柱
220  * p3:圆桶位置
221  * s3:圆桶大小
222  * oRadius:圆桶外径
223  * iRadius:圆桶内径
224  * host:吸附节点
225  * */
226 function createBarrel(p3, s3, oRadius, iRadius, host){
227     return createNode(p3, s3, host).s({
228         ‘shape3d‘:  ht.Default.createRingModel([
229             oRadius, 1,
230             oRadius, 0,
231             iRadius, 0,
232             iRadius, 1,
233             oRadius, 1
234         ], null, 20, false, false, 70)
235     });
236 }

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。