LoginSignup
0
1

More than 1 year has passed since last update.

横・縦・斜め の値が揃っているか

Last updated at Posted at 2021-09-20

はじめに

paizaの問題集にある「五目並べ」の問題で、 斜めの値が揃っているかを調べる際、主に斜めの判定でかなり悩んだので備忘録として残します。

追記

2021/9/22 : コメントでより簡潔なコードをご教示頂いたので、最後の項目(追記)に追加しました。

問題の内容

問題にも書いていますが、ざっくり言うとこんな感じです。

・盤面の各マスには、OX. が書かれている。
・同じ記号が斜め連続で5つ並んでいれば、その記号のプレイヤーが勝者となる。
・その勝者の記号を表示させる。勝負がついていなければDを表示。

もくじ

1. 値の取得
2. 縦の判定
3. 斜めの判定

1. 値の取得

いつものように標準入力を取得。
取得したXXOXOX X O X Oというように1文字ずつ分解していく。

  // 標準入力を1行ずつ取得
  while($line = fgets(STDIN)) {
    $tmp[] = trim($line);
    }

// $tmpの中身(イメージ)
// $tmp = {
// [0] => "XXOXO"
// [1] => "OXOXX"
// [2] => "OOOOO"
// [3] => "OXOX."
// [4] => "XOXXO"
// }

  // 1行の文字列をさらに分解
  foreach ($tmp as $value) {
    $str_split[] = mb_str_split($value);
  }

// $str_splitの中身(イメージ)
// $str_split = {
// [0] => {
//   [0] => "X"
//   [1] => "X"
//   [2] => "O"
//   [3] => "X"
//   [4] => "O"
//  }
// [1] => {
//   [0] => "O"
//   [1] => "X"
//   [2] => "O"
//   [3] => "X"
//   [4] => "X"
//  }
// 以下略

スクリーンショット 2021-09-19 16.21.59.png

2. 縦の判定

縦の判定は、以下のように実装。

    // 縦の判定
    for($i = 0; $i < 5; $i++) {
      foreach ($str_split as $str) {
          // ① 配列の行と列を入れ替える
          $column[$i][] = $str[$i];
      }

      // ② "X"と"O"の数を数える
      $columnCount[] = array_count_values($column[$i]);
      if($columnCount[$i]["X"] === 5) {
          echo "X";
          return;
      } elseif($columnCount[$i]["O"] === 5) {
          echo "O";
          return;
      }
    };

① では以下のように新しい配列$columnに同じ添字同士の値を格納していきました。
スクリーンショット 2021-09-19 15.35.14.png
最終的に以下のように縦の列同士だった値が配列に格納される形となります。
スクリーンショット 2021-09-19 22.47.46.png

② では上記で作成した配列の中に、XOがそれぞれいくつあるのか判定しています。
この部分については、以下の左上から右下を判定のところで触れています。

      $columnCount[] = array_count_values($column[$i]);
      if($columnCount[$i]["X"] === 5) {
          echo "X";
          return;
      } elseif($columnCount[$i]["O"] === 5) {
          echo "O";
          return;
      }

3. 斜めの判定

斜めの判定は、左上から右下右上から左下の2パターンを判定しています。

    // 左上から右下
    for($i = 0; $i < 5; $i++) {
      // ① 左上から右下の列を配列に格納
      $aboveLeft[] = $str_split[$i][$i];
    };
    // ② "X"と"O"の数を数える
    $aboveLeftCount = array_count_values($aboveLeft);
      if($aboveLeftCount["X"] === 5) {
         echo  "X";
         return;
      } elseif($aboveLeftCount["O"] === 5) {
          echo "O";
          return;
      }


    // 右上から左下
    for($i = 0, $j = 4; $i < 5; $i++, $j--) {
      // ① 右上から左下の列を配列に格納
      $aboveRight[] = $str_split[$i][$j];
    };
    // ② "X"と"O"の数を数える
    $aboveRightCount = array_count_values($aboveRight);
      if($aboveRightCount["X"] === 5) {
          echo  "X";
          return;
      } elseif($aboveRightCount["O"] === 5) {
          echo "O";
          return;
      }

左上から右下

左上から右下へは、規則的に添字が揃っているため、以下のようにコードを書くことができます。

    for($i = 0; $i < 5; $i++) {
      $aboveLeft[] = $str_split[$i][$i];
    };

スクリーンショット 2021-09-19 15.41.07.png

左上から右下の列を配列に格納した後は、配列の中にXOがそれぞれいくつあるかを調べます。
同じ文字が5つ揃っている = 勝者 となるため条件分岐で、5つ文字が揃っていればその記号を表示させるというようにしています。

      if($aboveLeftCount["X"] === 5) {
         echo  "X";
         return;
      } elseif($aboveLeftCount["O"] === 5) {
          echo "O";
          return;
      }

配列の中の要素を数える

上記のように配列の中にある要素がいくつあるかを調べるにあたり、array_count_values()を用いました。

スクリーンショット 2021-09-19 15.41.47.png

右上から左下

右上から左下へは、片方の添字は0から4へ増加、もう片方の添字は4から0へ減少するという法則があります。
スクリーンショット 2021-09-19 15.41.23.png

この法則を利用して以下のようにコードを書くことができます。

    for($i = 0, $j = 4; $i < 5; $i++, $j--) {
      $aboveRight[] = $str_split[$i][$j];
    };

この後は、先ほどのようにXOが5つあるのか条件分岐を行います。

    $aboveRightCount = array_count_values($aboveRight);
    if($aboveRightCount["X"] === 5) {
        echo  "X";
        return;
    } elseif($aboveRightCount["O"] === 5) {
        echo "O";
        return;
    }

おわり

解答コード例を見てみると、上記とは違う解法であったためとても勉強になりました。
繰り返し処理をうまく使えるようにしていこう!

追記

上記のコードより簡潔な書き方を教わったため以下に記載致します。

<?php
function judge($arr) {
  $check = function($str) {
    $str = implode(array_unique(str_split($str)));
    return (strlen($str) === 1 && $str !== '.') ? $str : false;
  };

  $s1 = $s2 = '';
  $size = count($arr);

  for($i = 0; $i < $size; $i++) {
    // 横判定
    $str = $check($arr[$i]);
    if($str) return $str;

    // 縦判定
    $str = implode(array_map(function($m) use($i) { return $m[$i];}, $arr));
    $str = $check($str);
    if($str) return $str;

    // 斜め判定用
    $s1 .= $arr[$i][$i];
    $s2 .= $arr[$i][$size - $i - 1];
  }

  // 斜め判定
  $str = $check($s1);
  if($str) return $str;

  $str = $check($s2);
  if($str) return $str;

  return 'D';
}


echo judge([
  'XO.X.',
  'XOXXO',
  'OOOOX',
  'XOXX.',
  'OOXOO',
]);          // O

echo judge([
  'O..XX',
  'OOXXO',
  'OOXXX',
  'XX.OO',
  'XOOXO',
]);          // X

こちら、コメントで@3267fさんより頂いたコードとなります。
本当にありがとうございます。

縦・斜めの値を取得

私が記述したやり方では、 斜めの値を一度配列に置き直し、繰り返しを用いて文字のカウントをしていました。
が、以下のコードで簡潔に書くことができます。

各列の値の取得
  $s1 = $s2 = '';
  $size = count($arr);

  for($i = 0; $i < $size; $i++) {
    // 横判定
    $str = $check($arr[$i]);
    if($str) return $str;

    // 縦判定
    $str = implode(array_map(function($m) use($i) { return $m[$i];}, $arr));
    $str = $check($str);
    if($str) return $str;

    // 斜め判定用
    $s1 .= $arr[$i][$i];
    $s2 .= $arr[$i][$size - $i - 1];
  }

配列に格納せずとも上記のように配列から特定の値を直接指定して、文字数をカウントすることができます。

文字のカウント

文字のカウントは以下のようにarray_uniqueで文字を絞り、残った文字を判定しています。

文字のカウント
  $check = function($str) {
    $str = implode(array_unique(str_split($str)));
    return (strlen($str) === 1 && $str !== '.') ? $str : false;
  };

簡潔なコードになり、非常に勉強になりました!
改めてお礼申し上げます。

参考

0
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1