翻牌小游戏(NG版)

2018年第一篇,今天大年初三,回想起来,2017年给自己每个月必须产出一篇日志的目标,在最后一个月失败了,唯一欣慰的是,这一年里写的日志不低于12篇,虽然博客基本没人看,也就自己写着玩,不过记录一下还是好的。

因为十月份刚换了工作,新的工作比起之前的打鱼晒网来说,确实忙了不少,以往都是闲的发慌,赶紧找点东西学学,看点源代码,现在刚忙完一个需求,还没喘息过来,组长就给了我一个新的需求,所以比起之前的学习时间,现在就变得少了很多,有时候都没空优化之前的代码,更别说优化一下构建工具脚本了。

牢骚发完,继续这次的主题。这个小游戏是我四年前春节做的了,当时因为玩着一个类似的游戏,所以想着自己实现一个,当时也因为产品经理给我看一个网站的效果问我能否实现,于是有了启发。

这个游戏主要是通过点击翻转匹配消除的方式来得分通关。点我来玩啦~

实现

css

翻转的效果,是通过backface-visibility: hidden;transfrom来实现的。前者是实现元素背面不可见,后者则是用于样式翻转。

布局

每张牌,通过设置两个div(一个代表前面,一个代表后面)。 前者设置backface-visibility来设置背面隐藏,通过设置点击翻转rotate,同时翻转前后div,实现翻牌效果。

每张牌用一个唯一字符来表示,这样的话那么多个字符,其实能代表的牌数就很多了,每一个关初始化时,从字符池里随机出几个字符,再复制一倍,就有了n对字符,每次点击翻牌两张时,判断两者是否相同字符,是则消除,否则重新盖住。

对于牌子后面的图案,则采用font-face属性来做设置,一方面,这样的话,每次游戏生成的字符就可以用简单的字母来做随机生成即可,将固定的字符固定对应不同的团并生成对应的font-face文件即可。

逻辑实现

mvvm框架来说,重点是将展示的内容数据化,通过动态修改数据,从而触发界面重新渲染。在这里的话,我将界面展示的字符对存放到一个数组内,同时用一个变量来标识已经翻出一张牌所对应的数组的索引。

 items: Item[]
 first: number
 level: number

由于牌子在展示上需要摆满一行,因此,我设定了一个级别,第一级每行只有两张牌,第二级则为三张牌,于是一行偶数张牌时(牌数n),则需要的牌数是n*n,为奇数张牌时,需要的牌数为n*(n+1),这样就能保证不会凑不齐整行的牌了。

let column = this.level+1
let num;
if(column%2){
    num = column*(column+1);
}else{
    num = column*column;
}

至于牌的字符集组合,直接用lodash的方法来打乱顺序,其次,代表牌的数组元素,用属性表示是否被翻开,是否已经被匹配,是否翻开过。

this.items = _.shuffle(srcTmp.split(''))
.map(function(item){
    return {
        content: ''+item,
        showed: false,
        isShow: false,
        isCheck: false 
    }
});

这样子做的情况下,在view层面就可以直接根据这几个属性,来处理牌的翻转、隐藏交互。

组件的模板可以这么做:

<div class="map" [ngStyle]="{'width': (80*(level+1))+'px'}">
    <div *ngFor="let item of items; let i = index;" class="item" 
    [ngClass]="{'un':!item.showed, 'match':item.isCheck, 'show':item.isShow}"
    (click)="clickItem(item, i);">
        <div class="back"><span></span></div>
        <div class="front"></div>
    </div>
</div>

剩下的交互判断逻辑就很明了了。

import { Component, OnInit, Injectable } from '@angular/core';
import _ from 'lodash';

interface Item {
    content: string;
    showed: boolean;
    isShow: boolean;
    isCheck: boolean;
}

interface Result {
    level: number;
    step: number;
    time: number;
}

@Component({
    selector: 'game-matches',
    templateUrl: './matches.component.html',
    styleUrls: ['./matches.component.css']
})

export class MatchesComponent implements OnInit {
    items: Item[]
    first: number
    level: number
    source: string
    resultStart: Date
    resultStep: number
    record: Result[]
    constructor() {
        this.first = null;
        this.level = 1;
        this.source = 'ABCDEFGHI☆J❤KLMNOPQRSTUVWXYZ123456789♂♀♠♣★△▽囧你我她';
        this.record = [];
    }

    ngOnInit() {
        this.initItems();
    }

    // 初始化牌字符集
    initItems() {
        let column = this.level+1
        let num;
        if(column%2){
            num = column*(column+1);
        }else{
            num = column*column;
        }
        console.log(num);
        num = num/2;
        let src = this.source.split('');
        let srcTmp = _.sampleSize(src, num).join('');
        srcTmp = srcTmp+srcTmp;

        this.items = _.shuffle(srcTmp.split(''))
        .map(function(item){
            return {
                content: ''+item,
                showed: false,
                isShow: false,
                isCheck: false 
            }
        });

        this.initRecord();
        
    }
	// 初始化记录
    initRecord(){
        this.resultStart = new Date();
        this.resultStep = 0;
    }
	// 进入下一关
    nextLevel(){
        this.level++;
        this.initItems();
    }
	// 延时判断是否全部完成
    checkItems(indexs){
        setTimeout(()=>{
            indexs.forEach((index)=>{
                this.items[index].isCheck = true;
            });

            if(this.items.every((item)=>{
                return item.isCheck
            })){
                this.recordResult();
                if(this.level==8){
                    alert('过关');
                }else{
                   this.nextLevel();
                }
            }
        }, 500);
    }
	// 记录通关信息
    recordResult(){
        let now = new Date();
        this.record.push({
            level: this.level,
            step: this.resultStep,
            time: now.getTime() - this.resultStart.getTime()
        });
    }
	// 延时隐藏已成功配对的牌
    hideItems(indexs){
        setTimeout(()=>{         
            indexs.forEach((index)=>{
                this.items[index].isShow = false;
            });
        }, 500);
    }
	// 点击翻牌交互
    clickItem(item: Item, index: number){
        item.showed = true;
        if(item.isShow||item.isCheck){
            return;
        }
        this.resultStep++;
        item.isShow = true;
        if(this.first!==null){
            if(this.items[this.first].content===item.content){
                this.checkItems([this.first, index]);
            }else{
                this.hideItems([this.first, index]);
            }
            this.first = null;
        }else{
            this.first = index;
        }

    }  
}

至此,本篇结束,很奇怪,每次实现完一个东西,总不能很好的写出其内容来,有时候写文档确实比写代码要累得多,累的是整理。