1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今回は paiza の「いびつなリバーシ対戦(2人)」の問題に挑戦!

🧩 問題概要

〇 全体像

  • A さん・B さんの 2 人で行うリバーシ風ゲーム
  • 盤面には
    • .:空マス
    • #:穴(通過・反転不可)
  • A → B → A → B … の順で交互に行動
  • 各プレイヤーは N 回ずつ 行動する(合計 2N 手)

〇 1 回の操作でやること
各手番(A または B)で、次を 必ずこの順で 行う:

① 指定されたマスに石を置く

  • A:'A'、B:'B'
  • そのマスに相手の石があっても 上書きする

② 8方向に「はさみ」をチェック

  • 縦・横・斜めの 8 方向 を調べる
  • 次の条件を満たした方向のみ有効:
    • '#' を含まない
    • 直線上の先に 自分の石 がある
  • 有効な場合:
    • 間にあるマスを すべて自分の石に置き換える
    • 相手の石でも . でも上書きする

③ 連鎖はしない

  • ひっくり返した結果 さらに別方向で挟めそうになっても、その手番はそこで終了

〇 最終目標

全 2N 回の操作が終わったあと、盤面を:

  • A の石 → 'A'
  • B の石 → 'B'

としてそのまま出力



入力例:

3 3 2
...
...
.#.
0 0
2 0
0 2
2 2

出力例:

AAA
...
B#B






✅OK例:

const rl = require('readline').createInterface({ input: process.stdin });

const lines = [];

// 入力を1行ずつ配列に格納
rl.on('line', (line) => lines.push(line));

rl.on('close', () => {
    // 盤面サイズ H×W と 各プレイヤーのターン数 N
    const [H, W, N] = lines[0].split(' ').map(Number);

    // 初期盤面を2次元配列に変換
    const grid = lines.slice(1, H+1).map(row => row.split(''));

    // A・Bが石を置く座標一覧
    const stones = lines.slice(H+1).map(row => row.split(' ').map(Number));
    
    // 8方向(縦・横・斜め)
    const directionList = [
        [-1, 0],  // 上
        [-1, 1],  // 右上
        [0, 1],   // 右
        [1, 1],   // 右下
        [1, 0],   // 下 
        [1, -1],  // 左下
        [0, -1],  // 左
        [-1, -1]  // 左上
    ];
    
    let playerNum = 0; // 0: A, 1: B
    
    // 全ての操作を順番に処理
    for (const stone of stones) {
        const [Y, X] = stone;
        
        // 現在のプレイヤーを判定
        const player = playerNum === 0 ? 'A' : 'B';

        // 指定マスに石を置く(上書きOK)
        grid[Y][X] = player;
        
        // 8方向それぞれをチェック
        for (const direction of directionList) {
            const [dy, dx] = direction;
            let ny = Y + dy;
            let nx = X + dx;
            
            const path = []; // 挟まれる候補のマス
            
            // 盤面内を直線的に探索
            while (0 <= ny && ny < H && 0 <= nx && nx < W) {
                if (grid[ny][nx] === '#') break; // 穴があれば失敗
                if (grid[ny][nx] === player) {
                    // 自分の石で挟めたら、間を全部ひっくり返す
                    for (const [py, px] of path) {
                        grid[py][px] = player;
                    }
                    break;
                }
                path.push([ny, nx]);
                ny += dy;
                nx += dx;
            }
        }

        // 次のプレイヤーへ
        playerNum = (playerNum + 1) % 2;
    }
    
    // 最終盤面を出力
    grid.forEach(g => console.log(g.join('')));
});

🔍 このコードの流れ

  • 入力を全部 lines に読み込む
  • 初期盤面を 2次元配列 grid に変換
  • A → B → A → B … の順で石を置く
  • 石を置いたら
    → 8方向に一直線で探索
    → 穴 # が出たら失敗
    → 自分の石にぶつかったら、その間を全部ひっくり返す
  • ひっくり返しは 1回の操作で終了(連鎖しない)
  • 全操作終了後、盤面をそのまま出力






📝まとめ

🔹 1. 「ロジックは同じ、状態だけが違う」

  • 挟み判定の本質は一切変わっていない
  • 変わったのは:
    • 石の種類
    • プレイヤーの切り替え
    • 入力形式

👉 「差分」にだけ対応すればいい

🔹 2. プレイヤー管理の最小構成

let playerNum = 0; // 0: A, 1: B
const player = playerNum === 0 ? 'A' : 'B';
playerNum = (playerNum + 1) % 2;
  • 状態管理は 最小限で十分
  • if を増やさず切り替える

🔹 3. 上書きOK

  • 相手の石かどうかを細かく判定しない
  • 成功したら、grid[y][x] = player

🔹 4. path 配列

  • すぐに盤面を書き換えない
  • 一旦 path にためる
  • 成功が確定した瞬間に一括反映
1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?