SOURCE

console 命令行工具 X clear

                    
>
console
<template>
    <div v-if="step === 0" class="intro">
        <div>
            横向卡片最大平铺排数
            <input
                v-model="option.x" min="2"
                max="10" type="range"
            > {{ option.x }}
        </div>
        <div>
            纵向卡片最大平铺排数
            <input
                v-model="option.y" min="2"
                max="10" type="range"
            > {{ option.y }}
        </div>
        <div>
            卡片最大堆叠层数
            <input
                v-model="option.z" min="2"
                max="10" type="range"
            > {{ option.z }}
        </div>
        <div>
            卡片密度
            <input
                v-model="option.cardRandom" min="0"
                max="1" step="0.1"
                type="range"
            >
            {{ option.cardRandom }}
        </div>
        <div>
            最大卡片种类
            <input
                v-model="option.maxCardType" min="3"
                max="14" step="1"
                type="range"
            >
            {{ option.maxCardType }}
        </div>
        <br>
        <button @click="startGame">开始游戏</button>
    </div>
    <div v-else-if="step === 2" class="intro">
        <h1>{{ result ? "You Win!��" : "You Lose!��" }}</h1>
        <button @click="rePlay">再来一轮</button>
        <button @click="setGame">难度调节</button>
    </div>
    <div v-else class="box">
        <div class="card-wrap" :style="cardWrapStyle">
            <div
                v-for="item in cardItemList"
                :key="item.key"
                :class="{'item-cover': item.cover}"
                class="card-item"
                :style="item.style"
                @click="clickCard(item)"
            >
                {{ item.content }}
            </div>
            <div
                v-for="item in penddingList"
                :key="item.key"
                class="card-item"
                :style="item.style"
            >
                {{ item.content }}
            </div>
            <div
                v-for="item in clearList"
                :key="item.key"
                class="card-item clear-item"
                :style="item.style"
            >
                {{ item.content }}
            </div>
            <div
                v-for="item in saveList"
                :key="item.key"
                class="card-item"
                :style="item.style"
                @click="clickSaveCard(item)"
            >
                {{ item.content }}
            </div>
            <p class="card-tips">
                剩余空位:{{ 7 - penddingList.length }}/7;已消除:{{ clearList.length }}/{{
                    cardItemList.length + penddingList.length + saveList.length + clearList.length
                }}
            </p>
        </div>
        <div class="tools">
            道具:
            <button :disabled="!tools.save" @click="saveCard">取出3个卡片</button>
            <button :disabled="!tools.rand" @click="randCard">随机</button>
            <button @click="rePlay">再来一轮</button>
        </div>
    </div>
</template>

<script>
import Vue from 'vue';

class CardItem {
    static x = 20;
    static y = 21;
    static colorType = {
        1: {background: '#FFB7DD'},
        2: {background: '#FFCCCC'},
        3: {background: '#FFC8B4'},
        4: {background: '#FFDDAA'},
        5: {background: '#FFEE99'},
        6: {background: '#FFFFBB'},
        7: {background: '#EEFFBB'},
        8: {background: '#CCFF99'},
        9: {background: '#99FF99'},
        10: {background: '#BBFFEE'},
        11: {background: '#AAFFEE'},
        12: {background: '#99FFFF'},
        13: {background: '#CCEEFF'},
        14: {background: '#CCDDFF'}
    };
    static contentType = {
        1: '��',
        2: '✂️',
        3: '��',
        4: '��',
        5: '��',
        6: '��',
        7: '��',
        8: '��',
        9: '��',
        10: '��',
        11: '��',
        12: '��',
        13: '��',
        14: '��'
    };
    constructor({x, y, z, key}) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.key = key;
        const offset = z * 0;
        this.val = key;
        this.style = {
            top: y * CardItem.y + offset + 'px',
            left: x * CardItem.x + offset + 'px',
            width: CardItem.x * 2 - 2 + 'px',
            height: CardItem.y * 2 - 8 + 'px'
        };
    }

    setValue(val) {
        this.val = val;
        this.content = CardItem.contentType[val];
        Object.assign(this.style, CardItem.colorType[val]);
    }
}

export default {
    data() {
        return {
            option: {
                x: 6,
                y: 4,
                z: 8,
                cardRandom: 0.2,
                maxCardType: 11
            },
            step: 0,
            win: false,
            cardMap: [],
            cardItemList: [],
            penddingList: [],
            clearList: [],
            saveList: [],
            calcValueList: [],
            maxWidth: 0,
            maxHeight: 0,
            tools: {
                save: true,
                rand: true
            },
            timer: 0
        };
    },
    computed: {
        cardWrapStyle() {
            return {
                width: (this.maxWidth + 2) * CardItem.x + 'px',
                height: (this.maxHeight + 1) * CardItem.y + 'px'
            };
        },
        leftOffset() {
            const wrapWidth = (this.maxWidth + 2) * CardItem.x;
            return (wrapWidth - 7 * CardItem.x * 2) / 2;
        }
    },
    methods: {
        randCard() {
            if (!this.tools.rand) {
                return;
            }
            this.tools.rand = false;
            const length = this.cardItemList.length;
            this.cardItemList.forEach(item => {
                const randNum = Math.floor(length * Math.random());
                const newItem = this.cardItemList[randNum];
                let temp;
                temp = item.style.left;
                item.style.left = newItem.style.left;
                newItem.style.left = temp;
                temp = item.style.top;
                item.style.top = newItem.style.top;
                newItem.style.top = temp;
                temp = item.x;
                item.x = newItem.x;
                newItem.x = temp;
                temp = item.y;
                item.y = newItem.y;
                newItem.y = temp;
                temp = item.z;
                item.z = newItem.z;
                newItem.z = temp;
            });

            this.cardItemList.sort((a, b) => a.z - b.z);
            this.calcCover();
        },
        saveCard() {
            if (!this.tools.save) {
                return false;
            }
            this.tools.save = false;
            this.saveList = this.penddingList.slice(0, 3);
            setTimeout(() => {
                this.saveList.forEach((item, index) => {
                    item.style.top = '110%';
                    item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
                    this.calcValueList[item.val]--;
                });
            }, 0);
            this.penddingList = this.penddingList.slice(3);
            this.penddingList.forEach((item, index) => {
                item.style.top = '160%';
                item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
            });
        },
        initGame() {
            this.step = 1;
            this.getMap(this.option);
            this.penddingList = [];
            this.clearList = [];
            this.saveList = [];
            this.tools.save = true;
            this.tools.rand = true;
            this.setCardValue({maxCardType: Number(this.option.maxCardType)});
            this.calcCover();
        },
        // 表示地图最大为 x * y 张牌,最多有 z 层
        getMap({x, y, z, cardRandom} = {}) {
            this.maxWidth = (x - 1) * 2;
            this.maxHeight = (y - 1) * 2 + 1;
            const cardMap = new Array(z);
            const cardItemList = [];
            let key = 0;
            // 地图初始化
            for (let k = 0; k < z; k++) {
                cardMap[k] = new Array(this.maxHeight);
                for (let i = 0; i <= this.maxHeight; i++) {
                    cardMap[k][i] = new Array(this.maxWidth).fill(0);
                }
            }
            for (let k = 0; k < z; k++) {
                const shrink = Math.floor((z - k) / 3);
                // 行
                for (let i = shrink; i < this.maxHeight - shrink; i++) {
                    // 列,对称设置
                    const mid = Math.ceil((this.maxWidth - shrink) / 2);
                    for (let j = shrink; j <= mid; j++) {
                        let canSetCard = true;
                        if (j > 0 && cardMap[k][i][j - 1]) {
                            // 左边不能有牌
                            canSetCard = false;
                        }
                        else if (i > 0 && cardMap[k][i - 1][j]) {
                            // 上边不能有牌
                            canSetCard = false;
                        }
                        else if (i > 0 && j > 0 && cardMap[k][i - 1][j - 1]) {
                            // 左上不能有牌
                            canSetCard = false;
                        }
                        else if (i > 0 && cardMap[k][i - 1][j + 1]) {
                            // 右上不能有牌
                            canSetCard = false;
                        }
                        else if (k > 0 && cardMap[k - 1][i][j]) {
                            // 正底不能有牌
                            canSetCard = false;
                        }
                        else if (Math.random() >= cardRandom) {
                            canSetCard = false;
                        }
                        if (canSetCard) {
                            key++;
                            const cardItem = new CardItem({x: j, y: i, z: k, key});
                            cardMap[k][i][j] = cardItem;
                            cardItemList.push(cardItem);
                            // 对称放置
                            if (j < mid) {
                                key++;
                                const cardItem = new CardItem({
                                    x: this.maxWidth - j,
                                    y: i,
                                    z: k,
                                    key
                                });
                                cardMap[k][i][j] = cardItem;
                                cardItemList.push(cardItem);
                            }
                        }
                    }
                }
            }
            cardItemList.reverse();
            for (let i = 1; i <= key % 3; i++) {
                const clearItem = cardItemList.pop();
                cardMap[clearItem.z][clearItem.y][clearItem.x] = 0;
            }
            cardItemList.reverse();
            this.cardMap = cardMap;
            this.cardItemList = cardItemList;
        },
        setCardValue({maxCardType} = {}) {
            // 卡片种类
            const valStack = new Array(maxCardType);
            this.calcValueList = new Array(maxCardType + 1).fill(0);
            // 给卡片设置值
            this.cardItemList.forEach(item => {
                const value = Math.ceil(Math.random() * maxCardType);
                if (valStack[value]) {
                    valStack[value].push(item);
                    if (valStack[value].length === 3) {
                        valStack[value].forEach(item => {
                            item.setValue(value);
                        });
                        valStack[value] = null;
                    }
                }
                else {
                    valStack[value] = [item];
                }
            });

            let count = 2;
            // console.log(valStack)
            valStack.forEach(list => {
                list
                    && list.forEach(item => {
                        count++;
                        item.setValue(Math.floor(count / 3));
                    });
            });
        },
        // 计算遮挡关系
        calcCover() {
            // 构建一个遮挡 map
            const coverMap = new Array(this.maxHeight);
            for (let i = 0; i <= this.maxHeight; i++) {
                coverMap[i] = new Array(this.maxWidth).fill(false);
            }

            // 从后往前,后面的层数高
            for (let i = this.cardItemList.length - 1; i >= 0; i--) {
                const item = this.cardItemList[i];
                const {x, y, z} = item;
                if (coverMap[y][x]) {
                    item.cover = true;
                }
                else if (coverMap[y][x + 1]) {
                    item.cover = true;
                }
                else if (coverMap[y + 1][x]) {
                    item.cover = true;
                }
                else if (coverMap[y + 1][x + 1]) {
                    item.cover = true;
                }
                else {
                    item.cover = false;
                }
                coverMap[y][x] = true;
                coverMap[y + 1][x] = true;
                coverMap[y][x + 1] = true;
                coverMap[y + 1][x + 1] = true;
            }
        },
        clickSaveCard(item) {
            this.cardItemList.push(item);
            const index = this.saveList.indexOf(item);
            this.saveList = this.saveList
                .slice(0, index)
                .concat(this.saveList.slice(index + 1));
            this.clickCard(item);
        },
        removeThree() {
            this.penddingList.some(item => {
                if (this.calcValueList[item.val] === 3) {
                    this.penddingList.forEach(newItem => {
                        if (newItem.val === item.val) {
                            this.clearList.push(newItem);
                        }
                    });
                    setTimeout(() => {
                        this.clearList.forEach((item, index) => {
                            item.style.left = this.leftOffset - 60 + 'px';
                        });
                    }, 300);

                    this.penddingList = this.penddingList.filter(newItem => {
                        return newItem.val !== item.val;
                    });
                    this.penddingList.forEach((item, index) => {
                        item.style.top = '160%';
                        item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
                    });
                    this.calcValueList[item.val] = 0;
                    if (this.cardItemList.length === 0) {
                        this.step = 2;
                        this.result = true;
                    }
                }
            });

            if (this.penddingList.length >= 7) {
                this.step = 2;
                this.result = false;
            }
        },
        // 点击卡片
        clickCard(item) {
            clearTimeout(this.timer);
            this.removeThree();
            this.penddingList.push(item);
            const index = this.cardItemList.indexOf(item);
            this.cardItemList = this.cardItemList
                .slice(0, index)
                .concat(this.cardItemList.slice(index + 1));
            this.calcCover();
            this.calcValueList[item.val]++;
            setTimeout(() => {
                item.style.top = '160%';
                item.style.left
                    = this.leftOffset + (this.penddingList.length - 1) * CardItem.x * 2 + 'px';
            }, 0);

            this.timer = setTimeout(() => {
                this.removeThree();
            }, 500);
        },
        // 开始
        startGame() {
            this.initGame();
        },
        // 设置
        setGame() {
            this.step = 0;
        },
        // 重来
        rePlay() {
            this.initGame();
        }
    }
};
</script>

<style>
.box {
  position: relative;
}
.intro {
  margin: 10% auto 0 auto;
  text-align: center;
}

.card-wrap {
  position: relative;
  margin: 10% auto 0 auto;
}

.card-item {
  font-size: 28px;
  text-align: center;
  position: absolute;
  border-radius: 2px;
  box-sizing: border-box;
  background: #ddd;
  opacity: 1;
  cursor: pointer;
  transition: all 0.3s;
  box-shadow: 0px 3px 0 0 #fff, 0 8px 0 0 #ddd, 0 8px 0 2px #333, 0 0 0 2px #333;
}

.card-item:hover {
  transform: scale3d(1.1, 1.1, 1.1);
  z-index: 1;
}

.item-cover {
  pointer-events: none;
  box-shadow: 0px 3px 0 0 #999, 0 8px 0 0 #666, 0 8px 0 2px #000, 0 0 0 2px #000;
}

.item-cover:after {
  border-radius: 2px;
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  background: #000;
  opacity: 0.55;
}

.card-tips {
  white-space: nowrap;
  position: absolute;
  left: 50%;
  top: 130%;
  transform: translate(-50%, 0);
  pointer-events: none;
}

.tools {
  position: absolute;
  top: 200%;
  width: 100%;
  left: 0;
  text-align: center;
}
.clear-item {
  pointer-events: none;
}
</style>

本项目引用的自定义外部资源