paiza Aランクレベルアップメニュー 陣取りゲーム「陣取りゲーム PHP編」final問題

何日もかかりました。本当に難しかった。
ですが、思いつく時は一瞬でした。
外出から帰ってきて手を水で洗っている最中に、急にひらめきました。ヘレンケラーか。
それとも流水にはひらめきのパワーがあるのか。
それはさておき。

<?php
    // 自分の得意な言語で
    // Let's チャレンジ!!


    $s = explode(" ",trim(fgets(STDIN)));
    
    $h = $s[0];
    $w = $s[1];
    
    $first = trim(fgets(STDIN));//先攻プレイヤー
    
    $con = 0;//reset カウント
    
    //map取得
    for ($i = 0; $i < $h; $i++) {
        $hoge = trim(fgets(STDIN));
        for ($j = 0; $j < $w; $j++) {
            $map[$i][$j] = mb_substr($hoge, $j, 1); 
            if($map[$i][$j]=="A"){
                $y_A = $i;
                $x_A = $j;
            }
            if($map[$i][$j]=="B"){
                $y_B = $i;
                $x_B = $j;
            }
        }
    }
    
    
    
    //初期位置に関して
    $newstar_a[]=array($y_A,$x_A, $con);
    $newstar_b[]=array($y_B,$x_B, $con);
    
    //print_r($map);
    
    while(count($newstar_a)>=1 || count($newstar_b)>=1){//移動可能な配列がある限り続ける
        
        
        if($first == "A"){
            //Aのターン
            //echo("Aのターンにはいりました 処理します"."\n");
            
            $newstar = $newstar_a;//仮の配列に入れる
            unset($newstar_a);//こっちは消しとく
            $newstar_a = array();//再セット(しないとエラーが出る)
            
            $put_text = "A";
            
            while(count($newstar)>=1){
                
                $y = $newstar[0][0];
                $x = $newstar[0][1];
                $con = ($newstar[0][2])+1;
        
                //共通操作
                if($y>=1 && $map[$y-1][$x]=="."){
                    $map[$y-1][$x] =$put_text;
                    $newstar_a[]=array($y-1,$x, $con);
                }
                if($y<$h-1 && $map[$y+1][$x]=="."){
                    $map[$y+1][$x] = $put_text;
                    $newstar_a[]=array($y+1,$x, $con);
                }
                if($x>=1 && $map[$y][$x-1]=="."){
                    $map[$y][$x-1] = $put_text;
                    $newstar_a[]=array($y,$x-1, $con);
                }
                if($x<$w-1 && $map[$y][$x+1]=="."){
                    $map[$y][$x+1] = $put_text;
                    $newstar_a[]=array($y,$x+1, $con);
                }
        
                array_shift($newstar);//いまつかった要素をひとつ消す
                
            
            }//end while level2

            
        }
        
        
        //Bのターン
        //echo("Bのターンにはいりました 処理します"."\n");
            
            $newstar = $newstar_b;//仮の配列に入れる
            unset($newstar_b);
            $newstar_b = array();
            
            $put_text = "B";
            
            while(count($newstar)>=1){
                
                $y = $newstar[0][0];
                $x = $newstar[0][1];
                $con = ($newstar[0][2])+1;
        
                //共通操作
                if($y>=1 && $map[$y-1][$x]=="."){
                    $map[$y-1][$x] =$put_text;
                    $newstar_b[]=array($y-1,$x, $con);
                }
                if($y<$h-1 && $map[$y+1][$x]=="."){
                    $map[$y+1][$x] = $put_text;
                    $newstar_b[]=array($y+1,$x, $con);
                }
                if($x>=1 && $map[$y][$x-1]=="."){
                    $map[$y][$x-1] = $put_text;
                    $newstar_b[]=array($y,$x-1, $con);
                }
                if($x<$w-1 && $map[$y][$x+1]=="."){
                    $map[$y][$x+1] = $put_text;
                    $newstar_b[]=array($y,$x+1, $con);
                }
        
                array_shift($newstar);//いまつかった要素を消す
            
            }//end while level2
            

   
        
        if($first == "B"){
            //Aのターン(後攻ぶん)
            //echo("Aのターン後攻にはいりました 処理します"."\n");
            
            $newstar = $newstar_a;//仮の配列に入れる
            unset($newstar_a);
            $newstar_a = array();
            
            $put_text = "A";
            
            while(count($newstar)>=1){
                
                $y = $newstar[0][0];
                $x = $newstar[0][1];
                $con = ($newstar[0][2])+1;
        
                //共通操作
                if($y>=1 && $map[$y-1][$x]=="."){
                    $map[$y-1][$x] =$put_text;
                    $newstar_a[]=array($y-1,$x, $con);
                }
                if($y<$h-1 && $map[$y+1][$x]=="."){
                    $map[$y+1][$x] = $put_text;
                    $newstar_a[]=array($y+1,$x, $con);
                }
                if($x>=1 && $map[$y][$x-1]=="."){
                    $map[$y][$x-1] = $put_text;
                    $newstar_a[]=array($y,$x-1, $con);
                }
                if($x<$w-1 && $map[$y][$x+1]=="."){
                    $map[$y][$x+1] = $put_text;
                    $newstar_a[]=array($y,$x+1, $con);
                }
        
                array_shift($newstar);//いまつかった要素を消す
                //print_r($newstar);
            
            }//end while level2
            
            
        }
        
    }//end while level1   
        
        

    
    //出力
    
    $count_a = 0;//reset
    $count_b = 0;
    
    for ($i = 0; $i < $h; $i++) {
        for ($j = 0; $j < $w; $j++) {
            if($map[$i][$j]=="A"){
                $count_a ++;
            }
            if($map[$i][$j]=="B"){
                $count_b ++;
            }
        }
    }
    
    echo($count_a." ".$count_b."\n");
        
    if($count_a > $count_b){
        echo("A"."\n");
    }else{
        echo("B"."\n");
    }


?>
kue

まず、ひとつ前のSTEPからの構造変更です。(なので、ひとつ前のSTEPが通っている前提の解説です)

AかBのどっちかが先攻になります。すると、構造としては「先攻がAならAの処理」「Bの処理(先攻にせよ後攻にせよ)」「先攻がBならAの処理」という、
(A)→B→(A) の処理になります。これで重複をすこし節約しました。

問題は配列です。前のSTEPで作ったコードをそのまま使いたい。
今回はAとBふたりのプレイヤーがいます。

$newstarというのは、前の前のSTEPあたりから引き継いだ配列で
こんな名前になっていますが、
いわゆる「新しく*に変更できた場所」、すなわちプレイヤーの新たな陣地を意味し、
その座標を格納していってます。

よって、$newstar_a(Aの分)と、 $newstar_b(Bの分)を、
それぞれwhile文がはじまったら$newstarへまるまるコピーすればいい。

$newstar = $newstar_a;//仮の配列に入れる

ひらめいたのはそれです。これならそれまでのコード(「共通操作」の部分)がそのまま使える。

しかし、実際動かしてみると、while文レベル2(2重whileの内側)の「共通操作」部分の格納先は、$newstar_aまたは$newstar_bである必要が出てきました。

「処理した結果、新しく陣地となって次のターンで処理をする分は$newstar_aまたはbに格納、今現在処理進行中のものは$newstar」という区別が必要になったのです。

これがないと、「1プレイヤーの1ターン」が理解されず、最終ターンまで先攻プレイヤーが全部取ってしまいます。ターンエンド(いまのターンで処理が必要な$newstarを使い切る)が必要なのでした。

また、
unset($newstar_a); //配列を削除
をしないと無限ループになり、(内容が残ってしまうためwhile文が延々に続く)

そのあと
$newstar_a = array(); //配列を宣言
をしないと「そんな配列ないんだけど」のエラーが出ます。

kue

前のSTEPからコードを引き継いでいるため、残っていますが、
おそらく今回、変数$con(カウンター)は何も役目をはたしていないと思います。
途中のSTEPで出てきたものなので、おそらくこのカウンターを利用して、paizaさんとしてはターンを区切ってほしかったんだろうと推察しますが(たぶん)
その方法が思いつけませんでした。

この問題に関してこちらの解説が大変参考になるかと思います。私の解説は途中からなので。

陣取りゲーム (paizaランク A 相当)をPHPで解いてみた

https://rnsr0371.boy.jp/2020/09/11/jintori_game/
kue

実行結果も3秒とだいぶ長いものがありますね。

追加をお待ちしております!

問題はこちら
paiza Aランクレベルアップメニュー(PHP編)

https://paiza.jp/works/mondai/a_rank_level_up_problems/problem_index?language_uid=php
3+