今回は paiza の「移動が可能かの判定・幅のある移動」の問題に挑戦!
これまでと違って、一マスだけの移動ではなく幅のある移動を扱う問題を解いていく!
🧩 問題概要
この問題は、マップ上で回転+直進を繰り返して移動するシミュレーション問題。
✔ マップ
- 文字
'.'は通れる -
'#'は障害物で通れない - 高さ
H、幅W
✔ 初期状態
- 位置 (
sy,sx) - 必ず 「北」向き からスタート
✔ 与えられる移動
N 回の移動が与えられる
-
d_i(L = 左回転、R = 右回転) -
l_i(進むマス数)
移動手順は
① L or R で向きを変える
② その向きへ 1 マスずつ l_i 回進む
✔ 移動成功条件
- マップ外に出ない
- 進む経路に障害物が無い
上記がすべて満たされれば 移動後の座標を出力。
✔ 移動が失敗する場合
1マスずつ進む途中で
- マップ外
- 障害物
#
に当たったらその瞬間の移動は中断し、
① 進めるところまで移動した位置を出力
② "Stop" を出力
以降の移動はすべて無視して終了
入力:
H W sy sx N
S_0
...
S_(H-1)
d_1 l_1
...
d_N l_N
入力例:
10 10 6 4 3
..#.....#.
..........
##.#......
#.##....#.
.##.#.....
........#.
.#......#.
.#........
...#......
#.#.......
L 2
R 1
L 4
出力例:
6 2
5 2
5 0
Stop
✅ OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', input => lines.push(input));
rl.on('close', () => {
const [rawH, rawW, sy, sx, rawN] = lines[0].split(' ');
const H = Number(rawH);
const W = Number(rawW);
const N = Number(rawN);
const gridS = lines.slice(1, H+1).map(line => line.split(''));
const dl = lines.slice(H+1).map(line => line.split(' '));
let y = Number(sy);
let x = Number(sx);
let dir = 'N';
for (let i = 0; i < N; i++) {
const d = dl[i][0];
const l = Number(dl[i][1]);
if (dir === 'N') {
if (d === 'L') {
dir = 'W';
} else { // === 'R'
dir = 'E';
}
} else if (dir === 'S') {
if (d === 'L') {
dir = 'E';
} else {
dir = 'W';
}
} else if (dir === 'E') {
if (d === 'L') {
dir = 'N';
} else {
dir = 'S';
}
} else { // === 'W'
if (d === 'L') {
dir = 'S';
} else {
dir = 'N';
}
}
for (let j = 1; j <= l; j++) {
if (dir === 'N') y--;
else if (dir === 'S') y++;
else if (dir === 'E') x++;
else x--;
if (y < 0 || y >= H || x < 0 || x >= W || gridS[y][x] === '#') {
if (dir === 'N') console.log(y+1, x);
else if (dir === 'S') console.log(y-1, x);
else if (dir === 'E') console.log(y, x-1);
else console.log(y, x+1);
console.log('Stop');
return;
}
}
console.log(y, x);
}
});
🧭 コードの流れ
① 入力処理
- 1行目から
-
H(高さ) -
W(幅) -
sy(初期y) -
sx(初期x) -
N(移動回数)
を読み取る
-
- 次の
H行を読み取り、
gridSとして「マップ」を 2 次元配列で保持する
(.= 通れる、#= 障害物) - 残りの
N行を
d_i(向き L/R)とl_i(進むマス数)として保存
② 初期状態のセット
- 現在位置を
y = sy,x = sxに設定 - 最初は必ず北向き →
dir = 'N'
③ N 回の移動を順番に処理
-
d_i(L/R)が与えられたら、
現在のdir(N/S/E/W)に応じて新しい向きに変更
(左旋回 or 右旋回)
④ 1 マスずつ進む(最大 l_i 回)
- その方向へ 1 マス移動する
- N →
y-- - S →
y++ - E →
x++ - W →
x--
- N →
- 移動後のマスが以下のどちらかなら「移動失敗」
- マップ外に出た
-
gridS[y][x]が#(障害物)
⑤ 失敗したときの処理
- 直前の正常位置に戻して座標を出力
-
"Stop"を出力して 全処理を終了
⑥ 成功したときの処理
- 最後まで移動できた場合、移動後の (
y, x) を出力 - 次の移動へ進む
⭐(最終的な動作)
- 全移動を正しく終えたら
N行分の座標 が出力される - 途中で障害物や外に出たら
- 1 回だけ停止座標 +
“Stop”が出力され、残りの移動は無視される
✨ 辞書化(オブジェクト)で短縮コード
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', l => lines.push(l));
rl.on('close', () => {
const [H, W, sy, sx, N] = lines[0].split(' ').map(Number);
const grid = lines.slice(1, 1 + H).map(r => r.split(''));
const moves = lines.slice(1 + H).map(l => l.split(' '));
// 向きの回転辞書
const turn = {
N: { L: 'W', R: 'E' },
S: { L: 'E', R: 'W' },
E: { L: 'N', R: 'S' },
W: { L: 'S', R: 'N' },
};
// 各方向ごとの進み量
const dy = { N: -1, S: 1, E: 0, W: 0 };
const dx = { N: 0, S: 0, E: 1, W: -1 };
let y = sy, x = sx, dir = 'N';
for (let i = 0; i < N; i++) {
let [d, l] = moves[i];
l = Number(l);
// 左右回転
dir = turn[dir][d];
// l マス進む
for (let step = 0; step < l; step++) {
const ny = y + dy[dir];
const nx = x + dx[dir];
// 範囲外 or 障害物 → STOP
if (ny < 0 || ny >= H || nx < 0 || nx >= W || grid[ny][nx] === '#') {
console.log(y, x);
console.log("Stop");
return;
}
y = ny;
x = nx;
}
console.log(y, x);
}
});
🔥 特徴
- 方向の回転を辞書(オブジェクト)で一発変換
- 進行方向も辞書化
-
if文ほぼゼロ(1つだけ) - 20 行以上の削減
✔ 方向回転は辞書で一発
turn[dir][d]
- ブラケット記法によって動的に値を取得可能。
-
dirとd(L/R) の組み合わせで次の方向が取れる。
✔ 移動も辞書で一発
y += dy[dir];
x += dx[dir];
(if 文が大幅に消える)
✔ 「前の位置に戻す」処理
y, x と 移動先を意味する ny, nx で区別することによって、移動不可かどうかの判定で不可だった場合でも、移動できるところまでの位置を残しておくことができる。
🗒️ まとめ
🔍 1. 方向の回転は辞書(オブジェクト)で一発変換できる
dir = turn[dir][d];
N → L → W
E → R → S
などを辞書化すると if 文が消える。
🔍 2. 進行方向の移動量も辞書化できる
y += dy[dir];
x += dx[dir];
これで方向ごとの処理を統合できる(if 不要)。
🔍 3. 「進む前に next(ny, nx)を計算」するのが重要
実際に y と x を進める前に
→ 先のマスが範囲外/障害物かチェック
ダメなら現在位置 y,x を出力して Stop
これで「前の位置に戻す」コードが不要になる。
🔍 4. 移動は 1 マスずつ処理する必要がある
- まとめて
l回進めてはダメ - 各ステップごとに障害物チェックが必須
🔍 5. if だらけのコードより辞書(オブジェクト)の方が正確で短い
- 方向の組合せの取り違えが防げる
- 可読性・保守性が非常に高まる
- 記述量が 20 行以上減る