• FruitFrolic


        这是一个连连看小游戏,以 Unity2D 开发。因用了数种水果图片来做头像,所以游戏取名 FruitFrolic。同样,它也只是我闲时的练手。

        少时曾玩过掌上游戏机里的俄罗斯方块及打飞机,及手机上的推箱子等,也在 Dos 上玩过几乎人人皆知的超级玛丽。我很想在闲暇的时候自己来实现它们,但为兴趣和乐趣而已。所以有前文所述的 PetGenie,以及本文,和之后可能的自实现版俄罗斯方块。不过限于美术素材及个人精力等之因,它们应会实现得比较简陋,虽然游戏核心逻辑几都具备。

        而我所使用的所有美术素材及音频等都来源于网络,本着开放的原则,我的(所有)自实现小游戏也都开源,且没有任何版权等限制。

        连连看的核心显然在洗牌及连线分析算法。洗牌控制了游戏的难易,变化很多。但我这里只是简单地平均生成了头像并随机打乱,而在连线分析算法里使用了广度优先搜索。

        洗牌代码如下。

    void RandomGenies() {
            int idx;
            // 共 6 * 8 个格子、12 种水果 --> 每种水果生成 4 次
            int[] geniesCounter = new int[cSpriteTypeCount] {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
            for (int i = 0; i < cGridRows; i++) {
                for (int j = 0; j < cGridCols; j++) {
                    genies[i, j].x = i;
                    genies[i, j].y = j;
                    while (true) {
                        idx = (int)(Random.value * cSpriteTypeCount);
                        if (geniesCounter[idx] > 0) {
                            break;
                        }
                    }
                    geniesCounter[idx]--;
                    genies[i, j].index = idx;
                    genies[i, j].spriteRenderer.sprite = fruitSprites[idx];
                }
            }
        }

        而连线分析实现代码如下。

    void DetectLink() {
            if (!ValidPrecondition()) {
                return;
            }
    
            List<TGenie> contnr0 = new List<TGenie>();
            FindCells((int)(touchCoords.pos1.x), (int)(touchCoords.pos1.y), contnr0);
            if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr0)) {
                genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
                return;
            }
    
            List<TGenie> contnr1 = new List<TGenie>();
            PrepareContnr(contnr0, contnr1);
            ShrinkContnr(contnr0, contnr1);
            if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr1)) {
                genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
                return;
            }
    
            List<TGenie> contnr2 = new List<TGenie>();
            PrepareContnr(contnr1, contnr2);
            ShrinkContnr(contnr0, contnr2);
            ShrinkContnr(contnr1, contnr2);
            if (CellExists(genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)], contnr2)) {
                genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
                return;
            }
    
            DetectSpecialLink();
        }
    
    bool ValidPrecondition() {
            if ((touchCoords.pos1.x == cInvalidCoordValue) || (touchCoords.pos1.y == cInvalidCoordValue) || (touchCoords.pos2.x == cInvalidCoordValue) || (touchCoords.pos2.y == cInvalidCoordValue)) {
                return false;
            }
            
            if ((touchCoords.pos1.x == touchCoords.pos2.x) && (touchCoords.pos1.y == touchCoords.pos2.y)) {
                return false;
            }
            
            if (genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index != genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index) {
                return false;
            }
    
            return true;
        }
    
    void FindCells(int x, int y, List<TGenie> contnr) {
            int n = 0;
            n = y - 1;
            if (n >= 0) {
                if (!(contnr.Contains(genies[n, x]))) {            
                    contnr.Add(genies[n, x]);
                }
            }
            while ((n >= 0) && (genies[n, x].index == cInvalidCoordValue)) {
                n--;
                if ((n >= 0) && (!(contnr.Contains(genies[n, x])))) {
                    contnr.Add(genies[n, x]);
                }
            }
    
            n = y + 1;
            if (n < cGridRows) {
                if (!(contnr.Contains(genies[n, x]))) {            
                    contnr.Add(genies[n, x]);
                }
            }
            while ((n < cGridRows) && (genies[n, x].index == cInvalidCoordValue)) {
                n++;
                if ((n < cGridRows) && (!(contnr.Contains(genies[n, x])))) {
                    contnr.Add(genies[n, x]);
                }
            }
    
            n = x - 1;
            if (n >= 0) {
                if (!(contnr.Contains(genies[y, n]))) {            
                    contnr.Add(genies[y, n]);
                }
            }
            while ((n >= 0) && (genies[y, n].index == cInvalidCoordValue)) {
                n--;
                if ((n >= 0) && (!(contnr.Contains(genies[y, n])))) {
                    contnr.Add(genies[y, n]);
                }
            }
    
            n = x + 1;
            if (n < cGridCols) {
                if (!(contnr.Contains(genies[y, n]))) {            
                    contnr.Add(genies[y, n]);
                }
            }
            while ((n < cGridCols) && (genies[y, n].index == cInvalidCoordValue)) {
                n++;
                if ((n < cGridCols) && (!(contnr.Contains(genies[y, n])))) {
                    contnr.Add(genies[y, n]);
                }
            }
        }
    
        bool CellExists(TGenie genie, List<TGenie> contnr) {
            foreach (TGenie g in contnr) {
                if (g.Equals(genie)) {
                    return true;
                }
            }
    
            return false;
        }
    
        void PrepareContnr(List<TGenie> contnrSrc, List<TGenie> contnrDest) {
            foreach (TGenie g in contnrSrc) {
                if (g.index == cInvalidCoordValue) {
                    FindCells(g.y, g.x, contnrDest);
                }
            }
        }
    
        void ShrinkContnr(List<TGenie> contnrSrc, List<TGenie> contnrDest) {
            foreach (TGenie g in contnrSrc) {
                if (contnrDest.Contains(g)) {
                    contnrDest.Remove(g);
                }
            }
        }
    
    void DetectSpecialLink() {
            // 若在第一或最末列
            if (touchCoords.pos1.x == touchCoords.pos2.x) {
                if ((touchCoords.pos1.x == 0) || (touchCoords.pos1.x == cGridCols - 1)) {
                    genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                    genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;                
                    return;
                }
            }
    
            // 若在第一或最末行
            if (touchCoords.pos1.y == touchCoords.pos2.y) {
                if ((touchCoords.pos1.y == 0) || (touchCoords.pos1.y == cGridRows - 1)) {
                    genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                    genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;                
                    return;
                }
            }
        }

        判断是否已连线完毕(已全部消除或已死锁)的代码如下。

    bool HasMatches() {
            // 检测上下左右第一行/列是否有可消除的格子(特殊处理)
            for (int i = 0; i < cGridCols - 1; i++) {
                if (genies[0, i].index != cInvalidCoordValue) {
                    for (int j = i + 1; j < cGridCols; j++) {
                        if (genies[0, i].index == genies[0, j].index) {
                            return true;
                        }
                    }
                }
    
                if (genies[cGridRows - 1, i].index != cInvalidCoordValue) {
                    for (int j = i + 1; j < cGridCols; j++) {
                        if (genies[cGridRows - 1, i].index == genies[cGridRows - 1, j].index) {
                            return true;
                        }
                    }
                }
            }
    
            for (int i = 0; i < cGridRows - 1; i++) {
                if (genies[i, 0].index != cInvalidCoordValue) {
                    for (int j = i + 1; j < cGridRows; j++) {
                        if (genies[i, 0].index == genies[j, 0].index) {
                            return true;
                        }
                    }
                }
                
                if (genies[i, cGridCols - 1].index != cInvalidCoordValue) {
                    for (int j = i + 1; j < cGridRows; j++) {
                        if (genies[i, cGridCols - 1].index == genies[j, cGridCols - 1].index) {
                            return true;
                        }
                    }
                }
            }
    
            for (int i = 0; i < cGridRows; i++) {
                for (int j = 0; j < cGridCols; j++) {
                    if (genies[i, j].index != cInvalidCoordValue) {
                        // 0 转弯
                        List<TGenie> contnr0 = new List<TGenie>();
                        FindCells(j, i, contnr0);
                        if (HasMatchableGenie(genies[i, j], contnr0)) {                        
                            return true;
                        }
    
                        // 1 转弯
                        List<TGenie> contnr1 = new List<TGenie>();
                        PrepareContnr(contnr0, contnr1);
                        ShrinkContnr(contnr0, contnr1);
                        if (HasMatchableGenie(genies[i, j], contnr1)) {                        
                            return true;
                        }
    
                        // 2 转弯
                        List<TGenie> contnr2 = new List<TGenie>();
                        PrepareContnr(contnr1, contnr2);
                        ShrinkContnr(contnr0, contnr2);
                        ShrinkContnr(contnr1, contnr2);
                        RemoveNullGenies(contnr2);
                        if (HasMatchableGenie(genies[i, j], contnr2)) {                        
                            return true;
                        }
                    }
                }
            }
    
            return false;
        }
    
    bool HasMatchableGenie(TGenie genie, List<TGenie> contnr) {
            foreach (TGenie g in contnr) {
                if ((!g.Equals(genie)) && (g.index == genie.index)) {
                    return true;
                }
            }
            
            return false;
        }
    
        void RemoveNullGenies(List<TGenie> contnr) {
            List<TGenie> tmp = new List<TGenie>();
            foreach (TGenie g in contnr) {
                if (g.index == cInvalidCoordValue) {
                    tmp.Add(g);
                }
            }
    
            foreach (TGenie g in tmp) {
                contnr.Remove(g);
            }
        }

        其实我本想分析每一种可能的连线情况(0---2 个转弯),但在写完 0 和 1 个转弯分析之后不想再写 2 个转弯分析代码了,因它们确实不好理解(也不好维护)。

    // 0 个转角连通
        bool CheckLink0() {
            // 若在同一列格子
            if (touchCoords.pos1.x == touchCoords.pos2.x) {
                if ((touchCoords.pos1.x != 0) && (touchCoords.pos1.x != cGridCols - 1)) {
                    if (touchCoords.pos1.y < touchCoords.pos2.y) {
                        for (int i = (int)(touchCoords.pos1.y) + 1; i < ((int)(touchCoords.pos2.y)); i++) {
                            if (genies[i, (int)(touchCoords.pos1.x)].index != cInvalidCoordValue) {
                                return false;
                            }
                        }
                    } else {
                        for (int i = (int)(touchCoords.pos2.y) + 1; i < ((int)(touchCoords.pos1.y)); i++) {
                            if (genies[i, (int)(touchCoords.pos1.x)].index != cInvalidCoordValue) {
                                return false;
                            }
                        }
                    }
                }
    
                genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
                
                return true;
            }
            
            // 若在同一行格子
            if (touchCoords.pos1.y == touchCoords.pos2.y) {
                if ((touchCoords.pos1.y != 0) && (touchCoords.pos1.y != cGridRows - 1)) {
                    if (touchCoords.pos1.x < touchCoords.pos2.x) {
                        for (int i = (int)(touchCoords.pos1.x) + 1; i < ((int)(touchCoords.pos2.x)); i++) {
                            if (genies[(int)(touchCoords.pos1.y), i].index != cInvalidCoordValue) {
                                return false;
                            }
                        }
                    } else {
                        for (int i = (int)(touchCoords.pos2.x) + 1; i < ((int)(touchCoords.pos1.x)); i++) {
                            if (genies[(int)(touchCoords.pos1.y), i].index != cInvalidCoordValue) {
                                return false;
                            }
                        }
                    }
                }
    
                genies[(int)(touchCoords.pos1.y), (int)(touchCoords.pos1.x)].index = cInvalidCoordValue;
                genies[(int)(touchCoords.pos2.y), (int)(touchCoords.pos2.x)].index = cInvalidCoordValue;
                
                return true;
            }
    
            return false;
        }
    
        // 1 个转角连通 --> 相当于两个格子划出一个矩形, 这两个格子是一对对角顶点, 另两个顶点如果可以同时和这两个格子直连, 那就说明可以连通
        bool CheckLink1() {
            int l = cInvalidCoordValue, t = cInvalidCoordValue, r = cInvalidCoordValue, b = cInvalidCoordValue;
            if (touchCoords.pos1.y < touchCoords.pos2.y) {
                t = (int)(touchCoords.pos1.y);
                b = (int)(touchCoords.pos2.y);
            } else {
                t = (int)(touchCoords.pos2.y);
                b = (int)(touchCoords.pos1.y);
            }
            if (touchCoords.pos1.x < touchCoords.pos2.x) {
                l = (int)(touchCoords.pos1.x);
                r = (int)(touchCoords.pos2.x);
            } else {
                l = (int)(touchCoords.pos2.x);
                r = (int)(touchCoords.pos1.x);
            }
            
            if (genies[t, l].index == cInvalidCoordValue) {  // 若选取的两个格子在 右上、左下
                for (int i = t + 1; i < b; i++) {
                    if (genies[i, l].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                for (int j = l + 1; j < r; j++) {
                    if (genies[t, j].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                genies[t, r].index = cInvalidCoordValue;
                genies[b, l].index = cInvalidCoordValue;
                
                return true;
            } else if (genies[t, r].index == cInvalidCoordValue) {  // 若选取的两个格子在 左上、右下
                for (int i = t + 1; i < b; i++) {
                    if (genies[i, r].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                for (int j = l + 1; j < r; j++) {
                    if (genies[t, j].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                genies[t, l].index = cInvalidCoordValue;
                genies[b, r].index = cInvalidCoordValue;
                
                return true;
            } else if (genies[b, l].index == cInvalidCoordValue) {  // 若选取的两个格子在 左上、右下
                for (int i = t + 1; i < b; i++) {
                    if (genies[i, r].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                for (int j = l + 1; j < r; j++) {
                    if (genies[b, j].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                genies[t, l].index = cInvalidCoordValue;
                genies[b, r].index = cInvalidCoordValue;
                
                return true;            
            } else if (genies[b, r].index == cInvalidCoordValue) {  // 若选取的两个格子在 右上、左下
                for (int i = t + 1; i < b; i++) {
                    if (genies[i, l].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                for (int j = l + 1; j < r; j++) {
                    if (genies[b, j].index != cInvalidCoordValue) {
                        return false;
                    }
                }
                
                genies[t, r].index = cInvalidCoordValue;
                genies[b, l].index = cInvalidCoordValue;
                
                return true;
            }
    
            return false;
        }

        游戏真机运行截图如下。

        代码下载链接在这里

  • 相关阅读:
    数组中只出现一次的数字
    平衡二叉树
    二叉树的深度
    数字在排序数组中出现的次数
    数组中的逆序对
    第一个只出现一次的字符位置
    丑数
    把数组排成最小的数/1038. Recover the Smallest Number
    python系统编程(十一)
    python系统编程(十)
  • 原文地址:https://www.cnblogs.com/ecofast/p/4137766.html
Copyright © 2020-2023  润新知