基於HTML5的WebGL設計漢諾塔3D遊戲

hightopo發表於2015-11-14

在這裡我們將構造一個基於HT for Web的HTML5+JavaScript來實現漢諾塔遊戲。

http://hightopo.com/demo/hanoi_20151106/index.html

漢諾塔的遊戲規則及遞迴演算法分析請參考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

http://hightopo.com/demo/hanoi_20151106/index.html

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)
    });
}

 http://hightopo.com/demo/hanoi_20151106/index.html

相關文章