用PHP實現開心消消樂演算法

owen發表於2019-02-16

一、需求描述:
1、在一個8*8的矩陣方格中隨機出現5種顏色的色塊。
2、當有三個或以上色塊在橫向或縱向上相連,則消除這些色塊。
3、色塊消除後,上方色塊往下平移,並掉下顏色隨機的色塊填充矩陣空缺。
4、重複2、3步驟。
5、消除3個相同色塊加10分,4個加15分,5個加20分,6個加30分,7個加40分,8個加70分,9個加100分,10個加150分,再往後每增加一個就比上一個多加50分。

二、上程式碼

<?php
//所有圖形初始化資料,key代表位置,value代表顏色
$xxl = array(
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
    array(``, ``, ``, ``, ``, ``, ``, ``),
);
$point = play($xxl, $point);//開始遊戲
echo "
共獲得積分數量:{$point}";

/*開始消除
 *$xxl        array    所有圖形集合
 *$point    int        獲得積分數量
*/
$bu = 0;
function play($xxl, $point){
    global $bu;
    $bu ++;
    echo `=================================開始第`.$bu.`步==================================`;
    $color = array(1 => `red`,2 => `green`,3 => `yellow`,4 => `blue`,5 => `black`);//代表5種顏色
    $samCol = array();//列上相連色塊集合
    $nowCol = array();//列上相連色塊指標
    $samArr = array();//相連色塊總集合
    $group = 1;//組指標

    //隨機填充顏色,並獲得行上相連色塊start
    foreach($xxl as $k1 => $v1){
        $sam = array();//行上相連色塊集合
        $now = 1;//行上相連色塊指標
        foreach($v1 as $k2 => $v2){
            if(empty($v2) || $v2 == ` `){
                $v2 = $xxl[$k1][$k2] = array_rand($color);//隨機填充顏色
            }
            if(!isset($nowCol[$k2])){
                $nowCol[$k2] = 1;
            }
            if($k1 === 0){
                $samCol[$k2][$nowCol[$k2]][$k1 .`-`. $k2] = array($k1, $k2, $v2, $k1 .`-`. $k2 .`-`. $v2);
            }else{
                if($v2 != $xxl[$k1-1][$k2]){//同一列上和前一個顏色不一樣
                    $nowCol[$k2] ++;
                }
                $samCol[$k2][$nowCol[$k2]][$k1 .`-`. $k2] = array($k1, $k2, $v2, $k1 .`-`. $k2 .`-`. $v2);
            }


            if($k2 === 0){
                $sam[$now][$k1 .`-`. $k2] = array($k1, $k2, $v2, $k1 .`-`. $k2 .`-`. $v2);
            }else{
                if($v2 != $xxl[$k1][$k2-1]){//同一行上和前一個顏色不一樣
                    $now++;
                }
                $sam[$now][$k1 .`-`. $k2] = array($k1, $k2, $v2, $k1 .`-`. $k2 .`-`. $v2);
            }
        }
        //獲得行上相連色塊start
        foreach($sam as $x => $y){
            if(count($y) > 2){
                $key = `R-`.$group;
                foreach($y as $x2 => $y2){
                    $y[$x2][`group`][`r`] = $key;
                }
                $samArr += $y;
                $group ++;
            }
        }
        //獲得行上相連色塊end
    }
    //隨機填充顏色,並獲得行上相連色塊end

    //獲得列上相連色塊start
    $group = 1;
    foreach($samCol as $k => $v){
        foreach($v as $x => $y){
            if(count($y) > 2){
                $key = `L-`.$group;
                foreach($y as $x2 => $y2){
                    $y[$x2][`group`][`l`] = $key;
                    if(isset($samArr[$x2][`group`][`r`])){//判斷本點是否已出現在橫向組裡
                        $samArr[$x2][`group`][`l`] = $key;
                    }
                }
                $samArr += $y;
                $group ++;
            }
        }
    }
    //獲得列上相連色塊end

    //查詢相連色塊start
    $res = array();//相連色塊集合
    $hasRes = array();
    foreach($samArr as $k => $v){
        if(isset($hasRes[$k])){
            continue;
        }
        $arr = array();
        seek($samArr, $v, $arr);
        $res[] = array_keys($arr);
        $hasRes += $arr;
    }
    //查詢相連色塊end
    show($xxl);//列印消除前的圖形
    if(empty($res)){//如果沒有相連色塊則退出遞迴
        echo `=================================消除完畢!==================================`;
        return $point;
    }
    $thisPoint = countPoint($res);//計算本次消除獲得積分
    $point += $thisPoint;//累計到總積分

    //消除相連色塊start
    $next = $xxl;
    foreach($res as $k => $v){
        foreach($v as $k2 => $v2){
            $y = $samArr[$v2][0];
            $x = $samArr[$v2][1];
            $xxl[$y][$x] = `*`;
            unset($next[$y][$x]);
        }
    }
    //消除相連色塊end

    show($xxl);//列印消除時的圖形
    $next = step($next);
    show($next);//列印消除後的圖形
    echo "本次消除獲得積分數量:{$thisPoint}
";
    return play($next, $point);
}

/*計算獲得積分數量
 *$xxl        array    相連色塊集合
 */
function countPoint($xxl){
    //初始化積分配置start
    $config = array(3 => 10, 4 => 15, 5 => 20, 6 => 30, 7 => 40, 8 => 70, 9 => 100);
    for($i = 10; $i <= 64; $i++){
        $config[$i] = 100 + ($i - 9) * 50;
    }
    //初始化積分配置end
    $point = 0;
    foreach($xxl as $v){
        $key = count($v);
        $point += $config[$key];
    }
    return $point;
}

/*消掉並左移
 *$xxl        array    所有圖形集合
 */
function step($xxl){
    foreach($xxl as $k => $v){
        $temp = array_merge($v);
        $count = count($temp);
        if($count == 8){
            continue;
        }
        for($i = $count; $i <= 7; $i++){
            $temp[$i] = ` `;
        }
        $xxl[$k] = $temp;
    }
    return $xxl;
}

/*找相鄰點
 *$xxl        array    相連圖形集合
 *$one            array    某一個點
 *$arr            array    圖形集合裡的相鄰的點
*/
function seek($xxl, $one, &$arr){
//    global $i;
    $near = array();
    $near[`up`] = ($one[0] - 1).`-`.$one[1];//上面的點
    $near[`down`] = ($one[0] + 1).`-`.$one[1];//下面的點
    $near[`left`] = $one[0].`-`.($one[1] - 1);//左面的點
    $near[`right`] = $one[0].`-`.($one[1] + 1);//右面的點
    foreach($near as $v){
        if(isset($xxl[$v]) && $xxl[$v][2] == $one[2]){//找到相鄰點
            $xj = array_intersect($one[`group`], $xxl[$v][`group`]);
            if(empty($xj)){//如果相鄰的點不是本組的就跳過
                continue;
            }
            if(isset($arr[$v])){//如果該點已被遍歷過則跳過
                continue;
            }
            $arr[$v] = $xxl[$v];
            seek($xxl, $xxl[$v], $arr);//繼續找相鄰的點
        }
    }
}

/*列印圖形
 *$xxl        array    所有圖形集合
 */
function show($xxl){
    //順時針旋轉矩陣start
    $arr = array();
    foreach($xxl as $k => $v){
        foreach($v as $k2 => $v2){
            $arr[7-$k2][$k] = $v2;
        }
    }
    ksort($arr);
    //順時針旋轉矩陣end
    $str = ``;
    foreach($arr as $v){
        foreach($v as $v2){
            $str .= ` `.$v2;
        }
        $str .= "
";
    }
    echo "
".$str;
}

執行結果如下:
12345分別代表5種顏色。

=================================開始第1步==================================
 3 3 2 2 1 1 1 4
 4 3 4 3 4 1 1 3
 3 1 4 1 1 4 1 2
 2 3 4 3 1 2 4 4
 4 2 4 2 2 2 1 4
 3 3 2 1 2 3 1 1
 5 2 1 3 2 1 4 5
 3 4 5 1 3 2 3 3

 3 3 2 2 * * * 4
 4 3 * 3 4 1 * 3
 3 1 * 1 1 4 * 2
 2 3 * 3 1 2 4 4
 4 2 * * * * 1 4
 3 3 2 1 * 3 1 1
 5 2 1 3 * 1 4 5
 3 4 5 1 3 2 3 3

 3 3           4
 4 3   2       3
 3 1   3   1   2
 2 3   1   4 4 4
 4 2 2 3 4 2 1 4
 3 3 2 1 1 3 1 1
 5 2 1 3 1 1 4 5
 3 4 5 1 3 2 3 3
本次消除獲得積分數量:55
=================================開始第2步==================================
 3 3 2 2 3 3 2 4
 4 3 3 2 1 3 3 3
 3 1 3 3 4 1 4 2
 2 3 5 1 2 4 4 4
 4 2 2 3 4 2 1 4
 3 3 2 1 1 3 1 1
 5 2 1 3 1 1 4 5
 3 4 5 1 3 2 3 3

 3 3 2 2 3 3 2 4
 4 3 3 2 1 * * *
 3 1 3 3 4 1 4 2
 2 3 5 1 2 * * *
 4 2 2 3 4 2 1 4
 3 3 2 1 1 3 1 1
 5 2 1 3 1 1 4 5
 3 4 5 1 3 2 3 3

 3 3 2 2 3      
 4 3 3 2 1      
 3 1 3 3 4 3 2 4
 2 3 5 1 2 1 4 2
 4 2 2 3 4 2 1 4
 3 3 2 1 1 3 1 1
 5 2 1 3 1 1 4 5
 3 4 5 1 3 2 3 3
本次消除獲得積分數量:20
=================================開始第3步==================================
 3 3 2 2 3 4 1 3
 4 3 3 2 1 4 2 5
 3 1 3 3 4 3 2 4
 2 3 5 1 2 1 4 2
 4 2 2 3 4 2 1 4
 3 3 2 1 1 3 1 1
 5 2 1 3 1 1 4 5
 3 4 5 1 3 2 3 3
=================================消除完畢!==================================
共獲得積分數量:75

相關文章