Javascript|

双色球

年会娱乐项目

Posted by xzavier on February 14, 2018

今天春节前最后一天了,坚守。接着上一茬儿。

今儿聊聊双色球抽奖。

这个挺好玩儿的,一个一个的数字摇出来,中奖桌平分奖池奖金,显然这个很刺激。

手机端不说了,就是一个选号的的页面,为了让年会方便简洁,我们没有设计用户体系。种cookie判断的,所以其实换个浏览器或者清理掉cookie就可以一人扮演多个用户了。但年会嘛,为了玩的轻松,设计直接扫码玩游戏,在做时间限制,简直比有个登录快捷多了。

还是说台上的LED抽奖页面,毕竟是出奖的页面。设计师设计的精美漂亮,但我写demo就把设计师的所有元素的略去了,哈哈。

从设计说起吧,双色球摇奖。就让球在一个容器里摇晃,摇晃可以用css3设计出球,然后不断地改变它的边距,看着就像它在运动。但在canvas也占据一片天地的今天,显然canvas设计会更加合理。

准备画布

canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
context.globalAlpha = 1;

我们要让双色球摇滚起来,就需要不断的在下一个位置重绘。所以每个球的颜色和值需要固定。不然一个球一会儿是红色,一会儿是蓝色,就有点对不起观众了。

// 统一后续更新球的颜色值
for (var z = 0; z < ballLen; z++) {
    colors[(z + 1) + ''] = z % 2 === 0 ? 'rgb(239, 60, 18)' : 'rgb(37, 110, 220)';
}

每一次都新建一轮游戏,重置所有状态

初始化-状态重置

function newRound() {
    isCurrGameStart = false;
    prizeTens = [];
    backendBonus = [];
    ballWinners = [];
    currentIndex = 0;
    canNextPress = false;
    currRoundBonusAppended = false;

    $('.bonus-ball').remove();
    getBonusNumAsync();

    var newNums = [];
    for (var n = 0; n < ballLen; n++) {
        newNums.push(n + 1);
    }

    nBalls = newNums.splice(0);
    kBalls = [];
    createKBalls();

    clearInterval(dtimer);
    clearInterval(ktimer);
    clearTimeout(stimer);
    clearTimeout(endtimer);
    canStatic = false;

    context.clearRect(0, 0, width, height);
    dropBalls();
}

产生新的球球参数

function createKBalls() {
    for (var i = 0; i < nBalls.length; i++) {
        var color = colors['' + nBalls[i]];

        var ball = {
            x: Math.random() * (width - 2 * ballRadius) + ballRadius,
            y: Math.random() * (height - 2 * ballRadius) + ballRadius,
            vx: Math.pow(-1, Math.ceil(Math.random() * 2)) * Math.random() * 4 + 6,
            vy: Math.pow(-1, Math.ceil(Math.random() * 2)) * Math.random() * 2 + 4,
            radius: ballRadius,
            color: color,
            number: nBalls[i]
        }

        kBalls[i] = ball;
    }
}

画球

// 画个球
function draw(ball) {
    var grd = context.createRadialGradient(ball.x, ball.y, 0, ball.x, ball.y, ball.radius);
    grd.addColorStop(0, '#eee');
    grd.addColorStop(1, ball.color);
    context.fillStyle = grd;

    context.beginPath();
    context.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2, true);
    context.closePath();

    context.fill();

    context.fillStyle = '#fff';
    context.textAlign = 'center';
    context.font = 'bold 20px arial';
    context.fillText(ball.number, ball.x, ball.y + 8);
}

然后循环把球都用上面的方法画出来:

// 画球
function drawBall() {
    context.clearRect(0, 0, width, height);
    for (var i = 0; i < kBalls.length; i++) {
        draw(kBalls[i]);
    }
}

动画过程中,就是不断的画球,就需要不断的更新球的参数

不断更新球的位置

// 更新球的位置
function updateBall() {
    for (var i = 0; i < kBalls.length; i++) {
        var one = kBalls[i];
        one.x += one.vx;
        one.y += one.vy;
        if (one.x < one.radius || one.x > width - one.radius) {
            one.vx = -one.vx;
        }
        if (one.y < one.radius || one.y > height - one.radius) {
            one.vy = -one.vy;
        }
    }
}

随机生成10个中奖号码,添加到DOM,准备动画

出奖动画

function getBonusNumAsync() {
    var balls = []
    for (var i = 0; i < 30; i++) {
        balls.push(i + 1);
    }
    balls.sort(function() {
        return Math.random() - 0.5;
    });
    backendBonus = balls.slice(0, 10);
    appendBonusNum();
}

function appendBonusNum() {
    var tenBStr = '';
    for (var i = 0; i < backendBonus.length; i++) {
        var bColor = backendBonus[i] % 2 === 0 ? 'blue' : 'red';
        tenBStr += '<div class="bonus-ball ball-small' + (i + 1) + ' ' + bColor + '" style="display: none;">' + backendBonus[i] + '</div>';
    }
    $('.lotterry-box').append(tenBStr);
}

保障观感真实性

每摇出一个球,就把盒子的球把要出的球减去,不再渲染此球

function reduceBalls() {
    var currNum = backendBonus[currentIndex];
    prizeTens.push(currNum);
    downOneBall(currNum);

    currentIndex++;

    console.log('摇出第 ' + prizeTens.length + '个: ' + currNum);

    var newNums = []
    for (var n = 0; n < nBalls.length; n++) {
        if (nBalls[n] !== currNum) {
            newNums.push(nBalls[n]);
        }
    }

    nBalls = newNums.splice(0);
    kBalls = [];
    createKBalls();
}

摇出的这个球,用相应的动画展示中奖号:

function downOneBall(num) {
    var sColor = num % 2 === 0 ? 'small-blue' : 'small-red',
        bColor = num % 2 === 0 ? 'blue' : 'red';

    $('.ball-small').addClass(sColor).html(num).show().addClass('ball-ani').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
        $('.ball-small').removeClass('ball-ani small-blue small-red').html('').hide();
        $('.ball-small' + currentIndex).show();
        $('.ball-small' + currentIndex).addClass('ball-ani' + currentIndex);
    });
}

定时摇出每个号码

一开始我们设计是enter一下,摇一个球,后来觉得操作频繁,容易失误,就还是循序摇奖。

function autoDropLottery() {
    knockAnimate();
    autoTimer = setInterval(function() {
        if (prizeTens.length > 9) {
            isCurrGameStart = false;
            setNextPress();
            clearInterval(autoTimer);
            return;
        }

        knockAnimate();
    }, 3600);
}

球球摇滚

function knockAnimate() {
    clearInterval(dtimer);
    clearInterval(ktimer);
    clearTimeout(stimer);
    canStatic = false;

    context.clearRect(0, 0, width, height);

    if (isKnocking) {
        isKnocking = false;
        stop();
    } else {
        isKnocking = true;
        start();
    }
}


// 开始运动
function start() {
    ktimer = setInterval(function() {
        drawBall();

        if (isKnocking) {
            updateBall();
        }
    }, 7);
}

// 停止运动并往下降
function stop() {
    clearInterval(ktimer);
    context.clearRect(0, 0, width, height);
    reduceBalls();
    dropBalls();
}

摇一个全部下落

摇一个球后需要所有球重力下降,才有真实感啊

// 往下降
function dropAnimate() {
    context.clearRect(0, 0, width, height);
    var t = 16 / 1000;

    collision(dBalls);
    dBalls.forEach(function(b) {
        b.drop(t);
    });

    if (canStatic) {
        mkstatic();
    }
}

// setInterval 模拟动画往下降
function dropBalls() {
    // 清空之前的数据,重新画新球往下降
    dBalls = [];
    for (var i = 0; i < kBalls.length; i++) {
        var b = kBalls[i];
        var nb = new DBall(b.x, b.y, b.radius, b.vx, b.vy, b.color, b.number);
        dBalls.push(nb);
    }

    dtimer = setInterval(function() {
        dropAnimate();
    }, 8);

    stimer = setTimeout(function() {
        canStatic = true;
    }, 4000)
}

下落的球做碰撞检测

function collision(balls) {
    for (var i = 0; i < balls.length; i++) {
        for (var j = 0; j < balls.length; j++) {
            var b1 = balls[i],
                b2 = balls[j];
            if (b1 !== b2) {
                var rc = Math.sqrt(Math.pow(b1.x - b2.x, 2) + Math.pow(b1.y - b2.y, 2));
                if (Math.ceil(rc) < (b1.radius + b2.radius + 2)) {
                    //获得碰撞后速度的增量
                    var ax = ((b1.vx - b2.vx) * Math.pow((b1.x - b2.x), 2) + (b1.vy - b2.vy) * (b1.x - b2.x) * (b1.y - b2.y)) / Math.pow(rc, 2);
                    var ay = ((b1.vy - b2.vy) * Math.pow((b1.y - b2.y), 2) + (b1.vx - b2.vx) * (b1.x - b2.x) + (b1.y - b2.y)) / Math.pow(rc, 2);
                    //给与小球新的速度
                    b1.vx = (b1.vx - ax) * collarg;
                    b1.vy = (b1.vy - ay) * collarg;
                    b2.vx = (b2.vx + ax) * collarg;
                    b2.vy = (b2.vy + ay) * collarg;
                    //获取两球斜切位置并且强制旋转
                    var clength = ((b1.radius + b2.radius + 2) - rc) / 2;
                    var cx = clength * (b1.x - b2.x) / rc;
                    var cy = clength * (b1.y - b2.y) / rc;
                    b1.x = b1.x + cx;
                    b1.y = b1.y + cy;
                    b2.x = b2.x - cx;
                    b2.y = b2.y - cy;
                }
            }
        }
    }
}

球球静止

当球到达地面后就应该慢慢静止了:

// 下降过后一段时间不在重绘,使处于静态
function mkstatic() {
    clearInterval(dtimer);
    clearInterval(ktimer);
    context.clearRect(0, 0, width, height);
    for (var i = 0; i < dBalls.length; i++) {
        draw(dBalls[i]);
    }
}

之后就是不断的循环了。写了个demo,但只做了摇奖动画,其他元素都被窝略去了,对你有用的话,拿去随意增加。

拓展

可以更多拓展:

1、球球在摇滚过程中的碰撞检测,让球的运动轨迹发生改变。我这儿做的只是检测碰撞容器,球球之间状态独立。

2、现在容器只能是方形的,只能把容器隐藏,让用户感觉到是在一个椭圆形容器中。

3、优化。

Demo

摇奖demo,进入页面,Enter开始摇奖,下键新一轮(摇奖中无效)