今回は 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にためる - 成功が確定した瞬間に一括反映