連連看所屬算法分析與實(shí)現(xiàn)

需求

(1)隨機(jī)生成游戲界面集漾;
(2)選擇兩個(gè)相同圖案的圖片,并以不超過(guò)兩個(gè)轉(zhuǎn)彎的連線將它們連接起來(lái)砸脊,便可以消除這對(duì)圖片具篇,每一局用戶需要消除所有的圖片;
(3)當(dāng)沒(méi)有可以消除的圖案時(shí)凌埂,可以使用重置功能驱显;
(4)選擇提示功能時(shí),游戲自動(dòng)消除一對(duì)相同的圖片瞳抓;
(5)實(shí)現(xiàn)聯(lián)網(wǎng)對(duì)戰(zhàn)

可能存在的問(wèn)題

生成界面存在的問(wèn)題

  1. 界面中棋子必須成對(duì)出現(xiàn)
  2. 初始界面必須保證有解

連接時(shí)存在的問(wèn)題

  1. 折線次數(shù)的判斷
  2. 配對(duì)圖片的判斷
  3. 連接路徑上保證沒(méi)有阻礙

聯(lián)網(wǎng)同步問(wèn)題

  1. 同步何種數(shù)據(jù)來(lái)體現(xiàn)對(duì)戰(zhàn)

構(gòu)思

  1. 成對(duì)問(wèn)題: 可以準(zhǔn)備兩個(gè)數(shù)組分別存放匹配對(duì)埃疫,每次各取一個(gè)賦給sprite renderer。
  2. 初始界面保證有解問(wèn)題: 可以先隨機(jī)生成一個(gè)棋盤(pán)挨下,再用自動(dòng)匹配算法進(jìn)行匹配,如果無(wú)解則重新生成棋盤(pán)脐湾。
  3. 折線次數(shù)問(wèn)題: 可以用
  4. 判斷一對(duì)棋子是否可以消除時(shí)臭笆,即最小折線次數(shù)小于等于2,可以用廣度優(yōu)先搜索;在自動(dòng)求解時(shí)愁铺,可以用深度優(yōu)先搜索鹰霍。
  5. 圖片選項(xiàng)配對(duì)問(wèn)題:可以通過(guò)判斷名稱是否匹配。
  6. 連線路徑上無(wú)障礙:確保路徑上的sprite renderer為空即可茵乱。
  7. 簡(jiǎn)單實(shí)現(xiàn)對(duì)戰(zhàn)茂洒,只要實(shí)現(xiàn)一個(gè)進(jìn)度條,同步對(duì)方的完成度即可瓶竭。

棋盤(pán)生成算法分析

匹配算法分析

顯然這個(gè)類型的游戲督勺,判斷折線次數(shù)內(nèi)匹配的算法時(shí)最核心的,其他都問(wèn)題不大斤贰。下面我們來(lái)仔細(xì)分解一下這個(gè)問(wèn)題智哀。

$$方法一:分類判斷法

首先我們用分類的思想類分析這個(gè)問(wèn)題,這樣更有助于我們深入理解∮校現(xiàn)在要求是折線數(shù)少于3次完成連接瓷叫,那么我們很容易劃分出三種情況:0折連,1折連送巡,2折連摹菠。

0折連情況(即直連)
這種情況兩個(gè)選中目標(biāo)的橫坐標(biāo)或者縱坐標(biāo)是一樣的。只要再判斷連線上沒(méi)有阻礙即可骗爆。

0折連

1折連情況
此情況的兩個(gè)目標(biāo)橫縱坐標(biāo)都不能相等次氨,相當(dāng)于矩形上的對(duì)角點(diǎn)。所以要判斷兩個(gè)點(diǎn)是否滿足1折連就只要確定另外兩個(gè)對(duì)角點(diǎn)上的任一點(diǎn)同時(shí)對(duì)A和B滿足0折連的情況即可淮腾。

1折連

2折連情況
向四周查找空格區(qū)域糟需,直到“撞墻”,然后記錄點(diǎn)C谷朝。再看點(diǎn)C是否滿足與B 1折連情況洲押。

2折連

$$方法二:廣度優(yōu)先算法
把所有滿足情況的點(diǎn)都放在一個(gè)集合中,然后只要判斷B在不在集合中就行了圆凰。具體是這樣的:

  1. 準(zhǔn)備兩個(gè)數(shù)組杈帐,組A是存放滿足條件的點(diǎn);組B是存放暫時(shí)滿足情況的點(diǎn)专钉。
  2. 先把A放到組A挑童。
  3. 然后把所有與組A 0折連的點(diǎn)放到組B,然后把組B拷貝到組A再清空跃须。折數(shù)+1
  4. 重復(fù)3站叼,直到B在組A中或者折數(shù)超過(guò)2。

$$方法三:A*尋路算法
雖然A*是解決靜態(tài)網(wǎng)路尋路問(wèn)題中求解最短路徑的算法菇民,但是其實(shí)在這里也可以有很好的應(yīng)用尽楔。(不過(guò)我還沒(méi)實(shí)現(xiàn)過(guò)投储,大家可以試試,可以在評(píng)論區(qū)甩代碼鏈接分享下)

代碼參考:

1.棋盤(pán)初始化(主題實(shí)現(xiàn))


//初始化布局
    public void InitializeLayout()
    {
        float offset = sample.bounds.size.x;

        layout = new GameObject[Height, Width];

        //初始化空棋盤(pán)
        for (int i = 0; i < Height; i++)
            for (int j = 0; j < Width; j++)
            {
                GameObject tile = Instantiate(emptytile, new Vector3(parent.position.x + (offset * j), parent.position.y + (offset * i), 0), Quaternion.identity, parent);
                tile.name = (i * 10 + j).ToString();
                layout[i, j] = tile;
            }

        //矯正位置偏差
        if (SceneManager.GetActiveScene().name.Equals("Single"))
        {
            if (currentLevel <= 5)
                parent.position = parentPOS[currentLevel];
            else
                parent.position = parentPOS[5];
        }

        //給棋盤(pán)放上棋子
        GiveSprites();
    }
    void GiveSprites()
    {
        //實(shí)現(xiàn)圖片的成對(duì)放置:遍歷棋盤(pán)阔馋,當(dāng)前tile沒(méi)有精靈則貼上精靈并且成對(duì)的在棋盤(pán)隨機(jī)位置放上對(duì)應(yīng)精靈
        for (int i = 1; i < Height - 1; i++)
            for (int j = 1; j < Width - 1; j++)
            {
                if (layout[i, j].GetComponent<SpriteRenderer>().sprite == null)
                {
                    layout[i, j].GetComponent<SpriteRenderer>().sprite = GetRandomSprite();
                    GiveSpriteInRandomXY();
                }
            }
    }
//棋子圖片隨機(jī)付給沒(méi)貼圖的棋子玛荞,防止擺放太整齊
    void GiveSpriteInRandomXY()
    {
        int x = UnityEngine.Random.Range(100, (Height - 1) * 100);
        x /= 100;
        int y = UnityEngine.Random.Range(100, (Width - 1) * 100);
        y /= 100;

        //隨機(jī)定位一個(gè)位置
        Sprite thisSprite = layout[x, y].GetComponent<SpriteRenderer>().sprite;
        //檢查是否有空
        if (thisSprite == null)
            layout[x, y].GetComponent<SpriteRenderer>().sprite = GetRandomSprite();
        else if (thisSprite != null)
            GiveSpriteInRandomXY();
    }
//隨機(jī)選取棋子圖片
    Sprite GetRandomSprite()
    {
        Sprite s = null;
        int index = -1;
        //在s1中獲取一張精靈
        if (currentName == "")
        {
            int maxrange = s1.Length * 100;
            index = UnityEngine.Random.Range(0, maxrange);
            index /= 100;
            s = s1[index];
            currentName = s.name;
        }
        //在s2中獲取一張精靈
        else
        {
            string name1 = currentName;
            for (int i = 0; i < s1.Length; i++)
            {
                if (s1[i].name.Equals(name1))
                {
                    s = s1[i];
                    currentName = "";
                    break;
                }
            }
        }
        return s;
    }
//重新開(kāi)始
    public void Play()
    {
        if (CurrentLevel <= 5)
        { //根據(jù)當(dāng)前關(guān)卡顯示棋盤(pán)大小
            Width = ReadLevelRowCol()[0];
            Height = ReadLevelRowCol()[1];
        }

        if (CurrentLevel > 5)
        {
            Width = 7;
            Height = 8;
        }

        GetComponent<GameManager>().PlayAgain();
    }
//打亂棋盤(pán)
    public void ConfuseLayout()
    {
        /*當(dāng)棋盤(pán)無(wú)解時(shí)打亂棋盤(pán)*/
        remaining.Clear();
        //1.先把剩余sprite收走,存入數(shù)組
        for (int i = 1; i < Height - 1; i++)
            for (int j = 1; j < Width - 1; j++)
                if (layout[i, j].GetComponent<SpriteRenderer>().sprite != null)
                {

                    remaining.Add(layout[i, j].GetComponent<SpriteRenderer>().sprite);
                    layout[i, j].GetComponent<SpriteRenderer>().sprite = null;
                }
        //2.然后把它分發(fā)到棋盤(pán)上
        ReGive();
    }
    private void ReGive()
    {
        int x = UnityEngine.Random.Range(100, (Height - 1) * 100);
        x /= 100;
        int y = UnityEngine.Random.Range(100, (Width - 1) * 100);
        y /= 100;
        while (remaining.Count > 0)
        {
            if (layout[x, y].GetComponent<SpriteRenderer>().sprite == null)
            {
                layout[x, y].GetComponent<SpriteRenderer>().sprite = remaining[0];
                remaining.RemoveAt(0);
            }
            else
                ReGive();
        }
    }

2.判定(主體實(shí)現(xiàn))

    private void Update()
    {
        SelectDetact();
    }

    void SelectDetact()
    {
        if (Input.GetMouseButtonDown(0))
        {
            position = Input.mousePosition;
            Ray ray = Camera.main.ScreenPointToRay(position);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                SpriteRenderer hited = hit.transform.GetComponent<SpriteRenderer>();

                //如果第一次點(diǎn)擊則記錄名字
                if (firstSelect == null)
                {
                    firstSelect = hited;

                    if (firstSelect.sprite == null)
                        firstSelect = null;
                    else    //顯示年代
                    {
                        firstSelect.gameObject.GetComponent<Tile>().ShowFrame(true);
                        //記錄原先的圖呕寝,如果選擇失敗要回退
                        firstSprite = firstSelect.sprite;
                        firstSelect.sprite = layout.ShowDynasty(firstSelect.sprite.name);
                    }

                }
                else
                {
                    nextSelect = hited;
                    
                    //排除無(wú)效選擇
                    if (firstSelect.sprite == null || nextSelect.sprite == null || firstSelect.transform.position==nextSelect.transform.position )
                    {
                        //選擇置空
                        firstSelect.gameObject.GetComponent<Tile>().ShowFrame(false);

                        //圖片回退
                        firstSelect.sprite = firstSprite;
                        
                        //移除選擇記錄
                        firstSelect = null;
                        nextSelect = null;
                        return;
                    }
                    //記錄
                    nextSprite = nextSelect.sprite;
                    nextSelect.sprite = layout.ShowDynasty(nextSelect.sprite.name);

                    nextSelect.gameObject.GetComponent<Tile>().ShowFrame(true);
                    //判斷選擇對(duì)是否匹配
                    string name1 = firstSelect.sprite.name;
                    string name2 = nextSelect.sprite.name;

                    if (name1.Equals(name2) && JudgeConnection(firstSelect, nextSelect))
                        //消除
                        StartCoroutine(MatchSuccess());
                    else
                        StartCoroutine(MatchFail());
                }
            }
        }
    }

    bool JudgeConnection(SpriteRenderer a, SpriteRenderer b)
    {
        int count = 0;
        lst1.Add(a);
        while (!lst1.Contains(b) && count < 3)
        {
            foreach (SpriteRenderer s in lst1)
            {
                //0折連且路空則加入lst2
                lst2.AddRange(DirectConnect(s));
            }
            //lst2 -> lst1 并清空
            for (int i = 0; i < lst2.Count; i++)
                if (!lst1.Contains(lst2[i]))
                    lst1.Add(lst2[i]);

            lst2.Clear();
            count++;
        }

        return lst1.Contains(b) ? true : false;
    }

    List<SpriteRenderer> DirectConnect(SpriteRenderer a)
    {
        List<SpriteRenderer> temp = new List<SpriteRenderer>();
        temp.AddRange(FindDirect(Vector2.up, a));
        temp.AddRange(FindDirect(Vector2.left, a));
        temp.AddRange(FindDirect(Vector2.right, a));
        temp.AddRange(FindDirect(Vector2.down, a));

        return temp;
    }

    List<SpriteRenderer> FindDirect(Vector2 dirc, SpriteRenderer a)
    {
        SpriteRenderer start = a;
        List<SpriteRenderer> cast = new List<SpriteRenderer>();
        RaycastHit hit;
        SpriteRenderer sr;
        while (Physics.Raycast(start.transform.position, dirc, out hit))
        {
            sr = hit.transform.GetComponent<SpriteRenderer>();

            if (hit.transform.GetComponent<SpriteRenderer>().Equals(nextSelect))
            {
                cast.Add(sr);
                break;
            }
            else
            {
                if (JudgeRoad(hit.transform.GetComponent<SpriteRenderer>().sprite))
                {
                    cast.Add(sr);
                    start = sr;
                }
                else
                    break;
            }
        }

        ////把所有在A,B形成矩形范圍內(nèi)的點(diǎn)都加入waypoints以便尋路
        //foreach (SpriteRenderer s in cast)
        //{
        //    if (InArea(s, firstSelect, nextSelect))
        //        waypoints.Add(s.transform.position);
        //}

        return cast;
    }

    bool JudgeRoad(Sprite s)
    {
        if (s == null)
            return true;
        else
            return false;
    }

    /// <summary>
    /// 匹配成功動(dòng)動(dòng)作
    /// </summary>
    IEnumerator MatchSuccess()
    {
        //匹配成功音效
        SoundManager.instance.SetClip("success");
        yield return new WaitForSeconds(0.2f);
        if (remainingCount > 2)
        {
            remainingCount -= 2;
            if (SceneManager.GetActiveScene().name.Equals("Online"))
                owningSlider.GetComponent<Progress>().CmdMinuse();
        }
        //結(jié)算界面
        else
        {
            if (SceneManager.GetActiveScene().name.Equals("Online"))
                owningSlider.GetComponent<Progress>().CmdOver();
            else
            {
                OverPanel("win");
                if (SceneManager.GetActiveScene().name.Equals("Single"))
                    gameObject.GetComponent<LayoutInitailization>().CurrentLevel++;
            }
        }

        firstSelect.GetComponent<ParticleSystem>().Play();
        nextSelect.GetComponent<ParticleSystem>().Play();
        firstSelect.sprite = null;
        nextSelect.sprite = null;
        lst1.Clear();
        OverAction();
    }

    IEnumerator MatchFail()
    {
        //匹配失敗音效
        SoundManager.instance.SetClip("fail");

        yield return new WaitForSeconds(0.2f);

        //還原圖片
        firstSelect.sprite = firstSprite;
        nextSelect.sprite = nextSprite;

        lst1.Clear();
        OverAction();
    }

    void OverAction()
    {
        //選擇置空
        firstSelect.gameObject.GetComponent<Tile>().ShowFrame(false);
        nextSelect.gameObject.GetComponent<Tile>().ShowFrame(false);
        firstSelect = null;
        nextSelect = null;
    }

    public void OverPanel(string v)
    {
        restartPanel.SetActive(true);
        playingButton.SetActive(false);

        image.sprite = v.Equals("fail") ? fail : win;
        SoundManager.instance.SetClip(v.Equals("fail") ? "over" : "win");

        if (changebtn[0] != null)
        {
            changebtn[0].SetActive(v.Equals("fail"));
            changebtn[1].SetActive(v.Equals("win"));
            timenow = 10000;
        }
    }

    public void PlayAgain()
    {
        //更新行列值
        countRecord = (GetComponent<LayoutInitailization>().Height - 2) * (GetComponent<LayoutInitailization>().Width - 2);
        remainingCount = countRecord;
        //更新UI
        restartPanel.SetActive(false);
        playingButton.SetActive(true);
        timenow = 51;
        time.text = "51";

        //開(kāi)始倒計(jì)時(shí)
        StopCoroutine("TimeCount");
        StartCoroutine("TimeCount");

        pa.DestroyChildren();

        //布局
        layout.InitializeLayout();
    }
}

https://github.com/jewis123/AntiqueCoin/blob/master/LinkUpScriptExample

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勋眯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子下梢,更是在濱河造成了極大的恐慌客蹋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怔球,死亡現(xiàn)場(chǎng)離奇詭異嚼酝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竟坛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)闽巩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人担汤,你說(shuō)我怎么就攤上這事涎跨。” “怎么了崭歧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵隅很,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我率碾,道長(zhǎng)叔营,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任所宰,我火速辦了婚禮绒尊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仔粥。我一直安慰自己婴谱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布躯泰。 她就那樣靜靜地躺著谭羔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪麦向。 梳的紋絲不亂的頭發(fā)上瘟裸,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音诵竭,去河邊找鬼话告。 笑死十办,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的超棺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呵燕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棠绘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起再扭,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氧苍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后泛范,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體让虐,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年罢荡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赡突。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡区赵,死狀恐怖惭缰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笼才,我是刑警寧澤漱受,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骡送,受9級(jí)特大地震影響昂羡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摔踱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一虐先、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昌渤,春花似錦赴穗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至潜支,卻和暖如春甸赃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冗酿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工埠对, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留络断,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓项玛,卻偏偏與公主長(zhǎng)得像貌笨,于是被迫代替她去往敵國(guó)和親襟沮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锥惋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355