关于我帮领导的孩子写了一个小游戏参赛这种事

6,400 阅读6分钟

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

起因

这事起因是我们的领导突然给我们任务说自己家的小孩要去参加小学的编程比赛,叫我们开发一个简单有趣的小游戏项目!重点是:小学!小学! 现在程序员内卷化已经严重到编程要从哇哇抓起了吗?太恐怖了,于是我就想到干脆搞个连连看游戏得了,毕竟这游戏简单易上手,于是便有了第一版小游戏大概长这样:

image.png

第二天,信心满满的把作品拿过去,却被“游戏还是太复杂了,而且不符合他们活动的编程益学的主题”。那没办法嘛,执行领导的命令就是天职。啧但是东西写都写了,我也不想重新再从头写过了怎么办。中午吃饭的时候想了一想,突然想起来以前看到过一个关于加减乘除算式版的连连看。回头马上去找甲方商量一下,觉得方案可行,但是提出了一下要求
  1. 界面要简约
  2. 看起来要高端
  3. 因为是小学所以只要加减不要乘除
  4. 要有难度可以选择

嗯。。。合理且不过分的要求

构思

要求一:简约风格

还好我对设计有一些了解,所以大概明白一点简约风是怎么回事。首先界面的留白肯定得多,其次就是文字也得少一点,能用图片代替的绝对不用文字就像这样子(这些都是我用ps参考阿里巴巴文字图库做的)

image.png

要求二:高端大气

其实看起来高端的话和简约的外观也是一定程度挂钩的,毕竟前不久不是有日本主打“高端品牌”电动车结果看着还不如我外婆的电动自行车看起来功能多的感觉。当然简约归简约,UI面板的设计决定参考这种毛玻璃效果,看起来就更时尚高端!(个人感觉)

image.png

要求三:只要加减不要乘除

这种要求对我来说反倒是好事,毕竟做乘除那难度肯定又不一样的了嘛,只是做加减反倒轻松不少。当然具体算式如何相连我想了想,就和我小学时候做的那种左右相连的算式题一样就好了

image.png

要求四:难度选择

难度可以选择,这种要求我想了想也还好解决。首先这个游戏要在规定时间内完成正确的连接所有的算式,成功连接一个就能获得额外的时间,那么难度的增加也就无非是减少连接可获得的额外时间,并且增加算式个数罢了。

那么理论成立,那么接下来就是实操环节了

如何实现

毛玻璃UI样式

首先先把我们的页面风格实现出来,毛玻璃效果想要实现出来并不困难。

1.毛玻璃得有透明的嘛,所以背景颜色不能是完全实心的白色,我调来调去觉得0.25效果最好。

2.既然都说是毛玻璃了那肯定不能一眼看透过去的东西很清晰嘛,这时候就要用到backdrop-filter这个css属性了,设置属性值blur(4.5px)就没问题了,就有一点朦胧模糊的效果。

3.最后为了让他看起来更立体一点,那么就得用到边框boder和阴影box-shadow两个属性了。最后做出来效果代码如下:

background: rgba( 255, 255, 255, 0.25 );
box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 );
backdrop-filter: blur( 4.5px );
border-radius: 50%;
border: 1px solid rgba( 255, 255, 255, 0.18 );

那么最终用毛玻璃效果完成出来的页面就是这样的

连连看.gif

是不是和我说的一样有一种低调奢华的感觉~

算式的生成

这里本来写了前面关于界面切换和一些动态效果的讲解,但是觉得好像这种东西花太多篇幅有点本末倒置所以我选择了跳过,反正在线代码里都会有嘛~ 那么接下来要实现的就是算式要如何生成。先上代码后解释

// 两种不同的符号
let fuhao = ["+", "-"];

// 存储第一组算式
let formula = []

// 存储第二处算式
let formula1 = [];

setTimeout(() => {
    init();
}, 100);

// 随机数
function randomNum(){
    let posr = [];
    let pos = [~~(Math.random() * 20), ~~(Math.random() * 20)]
    pos[0] > pos[1] ? posr = pos : (posr = [pos[1],pos[0]]);
    return posr;
}

// 初始化
function init(){
    // 随机出前面一个数字最大的随机数组
    randomNums = [        randomNum(),        randomNum(),        randomNum(),        randomNum(),        randomNum()    ];
    // 第一组算式
    formula = [        `${randomNums[0][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[0][1]}`,
        `${randomNums[1][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[1][1]}`,
        `${randomNums[2][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[2][1]}`,
        `${randomNums[3][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[3][1]}`,
        `${randomNums[4][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[4][1]}`,
    ]
    // 获取第二组算式
    getFormula();

    // 创建出装算式的方块
    blockCreate();
    // 把算式随机放到方块里
    gridSpan();
    $('.remaining-blocks>span').html($('.blockStyle').length);
}


// 反向运算
function fuhaoF(str){
    if(str === "+"){
        return "-";
    }else if(str === "-"){
        return "+";
    }else if(str === "*"){
        return '/'
    }
}

// 总和
function formulaJ(num){
    return eval(formula[num]);
}

// 获得第二个算式
function getFormula(){
    for(let i=0;i<5;i++){
        let f = fuhao[~~(Math.random() * 2)];
        let num = ~~(Math.random() * formulaJ(i));
        formula1[i] = `${eval(`${formulaJ(i)} ${fuhaoF(f)} ${num}`)} ${f} ${num}`
    }
}

这里其实我已经用注释标注的差不多了,我把可能不理解的点给解释一下,首先多此一句的写了一个看似没用的randomNum()随机数字的函数,其实是因为小学嘛应该没有学负数的加减乘除,所以只能用大的减小来计算就不会出现负数的结果。其次是如何随机第二组算式:因为第二组的算式都要和第一组的算式有匹配的,所以我得用第二组算式的结果去随机一个符号然后反向计算得到一个新的算式

算式添加进页面

添加进页面就很简单了,只需要先创建好2边共10个格子,然后再把算式随机排列到节点里就行,代码如下

// 初始化
function init(){
    // 随机出前面一个数字最大的随机数组
    randomNums = [
        randomNum(),
        randomNum(),
        randomNum(),
        randomNum(),
        randomNum()
    ];
    // 第一组算式
    formula = [
        `${randomNums[0][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[0][1]}`,
        `${randomNums[1][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[1][1]}`,
        `${randomNums[2][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[2][1]}`,
        `${randomNums[3][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[3][1]}`,
        `${randomNums[4][0]} ${fuhao[~~(Math.random() * 2)]} ${randomNums[4][1]}`,
    ]
    // 获取第二组算式
    getFormula();

    // 创建出装算式的方块
    blockCreate();
    // 把算式随机放到方块里
    gridSpan();
    $('.remaining-blocks>span').html($('.blockStyle').length);
}

// 添加算式方格
function blockCreate() {
    for (let i = 0; i < 5; i++) {
        for(let j = 0; j < 2;j++){
            let block = $(`<div class='block blockStyle ${j === 1 ? 'br' : 'bl'}'></div>`).css({
                width: "250px",
                height: "85px",
                boxSizing: "border-box",
                position: 'absolute',
                right: j === 1 ? "0px" : "auto",
                top: 93 * i + 'px'
            })

            $('.game-map-in').append(block)
        }
    }
}

let num = [0,1,2,3,4];
let num1 = [0,1,2,3,4];

// 随机添加span算式
function gridSpan(){
    num = [0,1,2,3,4]
    num1 = [0,1,2,3,4]
    for(let l=0;l<5;l++){
        let spanNum = num.splice(~~(Math.random() * num.length), 1);

        let spanEle = $(`<span>${formula[spanNum]}</span>`).css({
            'color': '#fff',
            'font-size': '40px',
            'line-height': '85px'
        });
        $('.bl').eq(l).append(spanEle);
    }

    for(let l=0;l<5;l++){
        let spanNum = num1.splice(~~(Math.random() * num1.length), 1);

        let spanEle = $(`<span>${formula1[spanNum]}</span>`).css({
            'color': '#fff',
            'font-size': '40px',
            'line-height': '85px'
        });
        $('.br').eq(l).append(spanEle);
    }
}

算式连接到一起

这里直接看注释,没有什么特别的点了,里面其实有很多冗余代码可以优化将近百分之50,但是因为我要20天连肝十天所以优化的很多地方优化的事情只能晚点再说啦!

function clickBtn(){
    // 点击左边算式
    $('.bl').click(function (){
        $(this).toggleClass("blockClick").siblings().removeClass("blockClick");
        if(rightif){
            if(suansi === eval($(this).children().html())){
                // 画出连接的线
                connection();
                let pos1 = $(this).position();
                let pos2 = $(clickblock).position();
                strokeLine([pos1.left + 250, pos1.top + 45], [pos2.left, pos2.top + 45]);
                $(this).remove()
                $(clickblock).remove();
                rightif = false;
                leftif = false;
                clickblock = null;
                // 判断是否都连接完毕
                let gameover=true;
                if($('.blockStyle').length){
                    gameover = false;
                }
                $('.remaining-blocks>span').html($('.blockStyle').length);
                if(gameover && ageNum === 0){
                    $('.game-over').css({
                        display:'block'
                    })
                    setTimeout(() => {
                        $('.game-over').css({
                            display:'none'
                        })
                        $('.rank-btn').click();
                    }, 1000);
                }else if(gameover){
                    randomNums = []
                    formula = []
                    ageNum = ageNum - 1;
                    $('.animal-list>span').html(ageNum);
                    init();
                }
            }else{
                $(this).addClass('click-error');
                setTimeout(() => {
                    $(this).removeClass('click-error');
                }, 400)
                rightif = false;
                leftif = true;
                suansi = eval($(this).children().html());
                clickblock = this;
            }
        }else{
            // 没有点击过右边算式在此记录,首次点击为左边的算式
            leftif = true;
            // 保存第一次点击的盒子
            clickblock = this;
            suansi = eval($(this).children().html());
        }
    })

    // 左边算式
    $('.br').click(function (){
        $(this).toggleClass("blockClick").siblings().removeClass("blockClick");
        if(leftif){
            if(suansi === eval($(this).children().html())){
                connection();
                let pos1 = $(this).position();
                let pos2 = $(clickblock).position();
                // 画出连接的线
                strokeLine([pos1.left, pos1.top + 45], [pos2.left + 250, pos2.top + 45]);
                $(this).remove();
                $(clickblock).remove();
                rightif = false;
                leftif = false;
                clickblock = null;
                // 判断是否都连接完毕
                let gameover=true;
                if($('.blockStyle').length){
                    gameover = false;
                }
                $('.remaining-blocks>span').html($('.blockStyle').length);
                // 通过ageNum判断是否有没刷新完的波次
                if(gameover && ageNum === 0){
                    // 没有直接显示结束
                    $('.game-over').css({
                        display:'block'
                    })
                    setTimeout(() => {
                        $('.game-over').css({
                            display:'none'
                        })
                        $('.rank-btn').click();
                    }, 1000);
                }else if(gameover){
                    // 有的话再执行一遍
                    randomNums = [];
                    formula = [];
                    ageNum = ageNum - 1;
                    $('.animal-list>span').html(ageNum);
                    init();
                }
            }else{
                $(this).addClass('click-error');
                setTimeout(() => {
                    $(this).removeClass('click-error');
                }, 400)
                rightif = true;
                leftif = false;
                suansi = eval($(this).children().html());
                clickblock = this;
            }
        }else{
            rightif = true;
            clickblock = this;
            suansi = eval($(this).children().html());
        }
    })
}

// 连接画线
function strokeLine (pos1, pos2) {
    ctx.clearRect(0, 0, 745, 480);
    ctx.beginPath();
    ctx.lineWidth = '4'
    ctx.strokeStyle = "red";
    ctx.moveTo(pos1[0], pos1[1]);
    ctx.lineTo(pos2[0], pos2[1]);
    ctx.stroke();
    ctx.closePath();
    setTimeout(() => { ctx.clearRect(0, 0, 745, 480); }, 800)
}

在线代码

最主要的功能点已经放出来了,后面一些细节的功能点就直接看注释好的在线代码吧!

如果大家喜欢的话,请为我的文章点点关注点点赞,瑞斯败!jym

BE8A48E8484FDE85531B844375A2D08D.gif