基于HTML5的WebGL设计汉诺塔3D游戏

在这里我们将构造一个基于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

时间: 2024-10-12 12:32:50

基于HTML5的WebGL设计汉诺塔3D游戏的相关文章

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根柱子不是问题,问题是要创建若干个中空的圆盘.一开始的想法是:创建一个圆柱体,将圆柱体的上下两端隐藏,设置柱面的宽度来实现圆盘的效果

【Python实践-3】汉诺塔问题递归求解

1 # -*- coding: utf-8 -*- 2 #汉诺塔移动问题 3 # 定义move(n,a,b,c)函数,接受参数n,表示3个柱子A.B.C中第1个柱子A的盘子数量 4 # 然后打印出把所有盘子从A借助B移动到C的方法 5 def move(n,a,b,c): 6 if n==1: 7 print('move', a, '-->', c) 8 else: 9 move(n-1,a,c,b) 10 move(1,a,b,c) 11 move(n-1,b,a,c) 12 move(5,'

bzoj1019: [SHOI2008]汉诺塔(dp)

1019: [SHOI2008]汉诺塔 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 1739  Solved: 1062[Submit][Status][Discuss] Description 汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成.一开始n个盘子都摞在柱子A上,大的在下面,小的在上面,形成了一个塔状的锥形体. 对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移动的

1019: [SHOI2008]汉诺塔

1019: [SHOI2008]汉诺塔 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 1495  Solved: 916[Submit][Status][Discuss] Description 汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成.一开始n个盘子都摞在柱子A上,大的在下面,小的在上面,形成了一个塔状的锥形体. 对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移动的盘

【BZOJ 1019】 1019: [SHOI2008]汉诺塔 (DP?)

1019: [SHOI2008]汉诺塔 Description 汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成.一开始n个盘子都摞在柱子A上,大的在下面,小的在上面,形成了一个塔状的锥形体. 对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移动的盘子一定放在比它更大的盘子上面(如果移动到空柱子上就不需要满足这个要求).我们可以用两个字母来描述一次操作:第一个字母代表起始柱子,第二个字母代表目标柱子.例如,AB就是把柱子A最上

BZOJ_1019_[SCOI2008]_汉诺塔_(DP)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1019 汉诺塔游戏,但是有移动优先级,在不违反原有规则的情况下,给定优先移动目标.求完成游戏所需的步数. 分析 我们用\(f[j][i]\)表示把第\(j\)个柱子上的最上面\(i\)个盘子移走所需的步数,用\(g[j][i]\)表示会移动到哪个柱子上. 那么对于\(f[j][i]\),首先把前\(i-1\)个移出去,步数为\(f[j][i-1]\),记移到了柱子\(y\),这时候再把第\(i

bzoj1019 [SHOI2008]汉诺塔

汉诺塔由三根柱子(分别用A B C表示)和n个大小互不相同的空心盘子组成.一开始n个盘子都摞在柱子A上,大的在下面,小的在上面,形成了一个塔状的锥形体. 对汉诺塔的一次合法的操作是指:从一根柱子的最上层拿一个盘子放到另一根柱子的最上层,同时要保证被移动的盘子一定放在比它更大的盘子上面(如果移动到空柱子上就不需要满足这个要求).我们可以用两个字母来描述一次操作:第一个字母代表起始柱子,第二个字母代表目标柱子.例如,AB就是把柱子A最上面的那个盘子移到柱子B.汉诺塔的游戏目标是将所有的盘子从柱子A移

BZOJ 1019: [SHOI2008]汉诺塔( dp )

dp(x, y)表示第x根柱子上y个盘子移开后到哪根柱子以及花费步数..然后根据汉诺塔原理去转移... ----------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define X(o) o.pos #define Y(o) o.w t

汉诺塔问题(递归、栈)

修改一下汉诺塔的游戏规则,现在不能直接从左边走到右边,也不能直接右边走到左边. 方法一:递归实现 现在分析一下,比如左边有1~n,那么移动最后一个的情况,就是: 1.1-n-1从左边移动到右边 2.n从左边移动到中间 3.1-n-1从右边移动到左边 4.n从中间移动到右边 5.1-n-1从左边移动到右边 那么,假如我有这样一个f(range,from,to)那么我需要求解的就是f(n,lrft,right),原子情况就是从只有一个数的时候,直接左边中间右边即可. 1 public static