HT for Web 3D遊戲設計設計--漢諾塔(Towers of Hanoi)
阿新 • • 發佈:2019-02-14
在這裡我們將構造一個基於HT for Web的HTML5+JavaScript來實現漢諾塔遊戲。
知道了漢諾塔的規則和演算法,現在就開始建立元素。用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屬性引用建立好的模型。
在邏輯實現上,採用了棧的先進後出的原理,對圓柱上的圓盤做順序控制,確保每次移動的圓盤都是最小的圓盤。
在演算法上,採用的是遞迴演算法,通過遞迴演算法,將搬遷過程一步一步記錄下來,再採用堆的原理一步一步地執行搬遷過工作。
var barNum = 5, // 圓盤個數 cylinderHeight = barNum * 20 + 40, // 圓柱高度 barrelMinORadius = 50, // 圓盤最大外半徑 barrelIRadius = 10, // 圓盤內半徑 poorRadius = 20, // 圓盤外半徑差值 barrelMaxORadius = barrelMinORadius + barNum * poorRadius, barrelHeight = 20, // 圓盤高 barPadding = 20, // 柱體之間的間隙 floorX = barrelMaxORadius * 6 + barPadding * 4, // 底盤長 floorY = 20, // 底盤高 floorZ = 2 * barrelMaxORadius + barPadding * 2, // 底盤寬 // 柱體集 positions = [ { barrels: [], position: [-(2*barrelMaxORadius + barPadding), cylinderHeight / 2 + 1, 0] },{ barrels: [], position: [0, cylinderHeight / 2 + 1, 0] },{ barrels: [], position: [(2*barrelMaxORadius + barPadding), cylinderHeight / 2 + 1, 0] } ], runOrder = [], // 圓盤移動順序集 // 動畫引數 params = { delay: 10, duration: 500, easing: Easing['easeBoth'] }; /** * 初始化程式 * */ function init(){ dataModel = new ht.DataModel(); g3d = new ht.graph3d.Graph3dView(dataModel); view = g3d.getView(); view.className = 'main'; document.body.appendChild(view); window.addEventListener('resize', function (e) { g3d.invalidate(); }, false); g3d.setEye([0, cylinderHeight * 2, floorX * sin(2*PI/360*60)]); // 初始化節點 initNodes(); moveAnimation(); } /** * 構造遊戲移動佇列 * diskQuantity:圓盤個數 * positionA:起點 * positionB:中轉點 * positionC:終點 * */ function buildRunOrder(diskQuantity, positionA, positionB, positionC){ if (diskQuantity == 1) { runOrder.push([positionA, positionC]); } else { buildRunOrder(diskQuantity - 1, positionA, positionC, positionB); buildRunOrder(1, positionA, positionB, positionC); buildRunOrder(diskQuantity - 1, positionB, positionA, positionC); } } /** * 移動動畫 * positionA:起點 * positionC:終點 * */ function moveAnimation(positionA, positionC){ if(!positionA){ var poses = runOrder.shift(); if(!poses){ setTimeout(reset, 500); }else{ moveAnimation(positions[poses[0]], positions[poses[1]]); } }else { var barrel = positionA.barrels.pop(); var position = positionC.cylinder.p3(), barPos = barrel.getPosition3d(); position[1] = position[1] + floorY + barrelHeight * positionC.barrels.length - cylinderHeight / 2; setPolylinePoints(polyline, barPos, position); params.action = function (v, t) { var length = g3d.getLineLength(polyline), offset = g3d.getLineOffset(polyline, length * v), point = offset.point, px = point.x, py = point.y, pz = point.z; barrel.p3(px, py, pz); }; params.finishFunc = function () { positionC.barrels.push(barrel); var poses = runOrder.shift(); if (!poses) { moveAnimation(); } else { moveAnimation(positions[poses[0]], positions[poses[1]]); } }; anim = ht.Default.startAnim(params); } } /** * 重置遊戲 * */ function reset(){ if(positions[0].barrels.length == 0){ positions[0].barrels = positions[2].barrels; } positions[2].barrels = []; for(var i = 0, len = positions[0].barrels.length; i < len; i++){ var pos = positions[0].cylinder.p3(); pos[1] = pos[1] + floorY + i * barrelHeight - cylinderHeight / 2; positions[0].barrels[i].p3(pos); } buildRunOrder(barNum, 0, 1, 2); setTimeout(moveAnimation, 500); } /** * 初始化節點 * */ function initNodes(){ // 底盤 floor = createNode([0, floorY / 2, 0], [floorX, floorY, floorZ]).s({ 'shape3d': 'box', '3d.movable': false }); // 建立柱子 for(var i = 0, len = 3; i < len; i++){ positions[i].cylinder = createNode(positions[i].position, [20, cylinderHeight, 20], floor).s({ 'shape3d': 'cylinder', 'shape3d.color': '#E5BB77', '3d.movable': false }); } // 建立圓盤 createBarrels(barNum, positions[0].cylinder); // 建立圓盤執行軌跡 polyline = new ht.Polyline(); polyline.setSegments([1, 2, 4, 2]); polyline.s({ 'shape.background': null, 'shape.border.color': 'rgba(0,0,0,0)', 'shape.border.gradient.color': 'rgba(0,0,0,0)', 'shape.border.pattern': [20, 10], 'shape3d.resolution': 50 }); dataModel.add(polyline); } /** * 設定路線節點 * */ function setPolylinePoints(polyline, from, to){ polyline.setPoints([ {x: from[0], y: from[2], e: from[1]}, {x: from[0], y: from[2], e: cylinderHeight}, {x: from[0], y: from[2], e: cylinderHeight + 60}, {x: to[0], y: to[2], e: cylinderHeight + 60}, {x: to[0], y: to[2], e: cylinderHeight}, {x: to[0], y: to[2], e: to[1]} ]); return polyline; } /** * 建立圓盤 * barNum:圓盤個數 * host:吸附節點 * */ function createBarrels(barNum, host){ // 圓盤初始x位置 var pos = host.p3(); for(var i = barNum, j = 0; i > 0; i--, j++){ pos[1] = barrelHeight * j + floorY; positions[0].barrels.push(createBarrel(pos, [1, barrelHeight, 1], barrelMinORadius + i*poorRadius, barrelIRadius, host).s({ 'shape3d.color': randomColor(), '3d.movable': false })); } } /** * 建立節點 * p3:節點位置 * s3:節點大小 * host:吸附節點 * */ function createNode(p3, s3, host){ var node = new ht.Node(); node.p3(p3); node.s3(s3); node.setHost(host); node.s({ 'wf.visible': 'selected', 'wf.color': '#FF6B10', 'wf.width': 2, 'wf.short': true }); dataModel.add(node); return node; } /** * 建立空心圓柱 * p3:圓桶位置 * s3:圓桶大小 * oRadius:圓桶外徑 * iRadius:圓桶內徑 * host:吸附節點 * */ function createBarrel(p3, s3, oRadius, iRadius, host){ return createNode(p3, s3, host).s({ 'shape3d': ht.Default.createRingModel([ oRadius, 1, oRadius, 0, iRadius, 0, iRadius, 1, oRadius, 1 ], null, 20, false, false, 70) }); }