今回は、paizaのクラスの最終でAランクの「ロボットの暴走」問題に挑戦!
⚙️問題概要
- 無限大のマス目状の工場で、N 台のロボットがそれぞれ決まった方角へ複数回移動する。
- ロボットの移動後の最終 座標とレベル を求める。
⚙️ ロボットの移動仕様
-
ロボットには Lv1〜Lv4 があり、レベルごとに移動距離が決まっている。
- Lv1 → 1 マス
- Lv2 → 2 マス
- Lv3 → 5 マス
- Lv4 → 10 マス
-
移動は 北(N) / 南(S) / 東(E) / 西(W) の 4 方向。
⚙️ 工具箱によるレベルアップ
- 工場内には工具箱が 10個 置かれている。
- ロボットが 移動後に工具箱のマスにぴったり止まるとレベルが1上がる(最大 Lv4)。
- 初期位置が工具箱にあっても最初からレベルアップはしない。
⚙️ 入力で与えられる情報
- 工場の大きさの目安 H W(マスの範囲の上限)、ロボット台数 N、移動回数 K
- 工具箱の 10 箇所の座標
- ロボットの初期位置(x, y)とレベル
- ロボットがどの順番でどの方向に動くかの移動指令(ロボット番号 + 方角)
⚙️ 出力
- 全ての移動が終わった後の
- 各ロボットの最終的な座標 (x, y) とレベル をロボット番号順に出力する。
⚙️ ポイント
- 座標系は、北に進むと y が減り、南に進むと y が増える。
- 移動のたびに工具箱と位置一致を確認して、レベルアップ処理をする。
- 移動順序・距離・方向を間違えずにシミュレーションする。
入力例:
5 5 3 3
0 0
0 1
0 2
0 3
0 4
1 0
1 1
1 2
1 3
1 4
2 1 1
2 2 1
2 3 1
1 W
1 E
3 S
出力例:
3 1 2
2 2 1
2 4 1
✨ OKコード例:
class Robot {
constructor(x, y, level){
this.x = x;
this.y = y;
this.level = level;
this.moveDir = {
N : (num) => { this.y -= num },
S : (num) => { this.y += num },
E : (num) => { this.x += num },
W : (num) => { this.x -= num }
};
}
getResult(){
return `${this.x} ${this.y} ${this.level}`
}
levelUp(){
if(this.level < 4) this.level++;
}
move(dir){
const distance = [0, 1, 2, 5, 10][this.level];
this.moveDir[dir](distance);
}
getCurrent(){
return `${this.x} ${this.y}`
}
}
const rl = require('readline').createInterface({ input:process.stdin });
const lines = [];
rl.on('line', (input) => {
lines.push(input);
});
rl.on('close', () => {
const [H, W, N, K] = lines[0].split(' ').map(Number);
const toolBoxes = new Set(lines.slice(1, 11).map(line => line.trim()));
const robots = lines.slice(11, 11 + N).map(r => {
return new Robot(...r.split(' ').map(Number));
});
const moves = lines.slice(11 + N);
for(const m of moves){
const tokens = m.split(' ');
const robot = robots[Number(tokens[0]) - 1];
const dir = tokens[1];
// 移動
robot.move(dir);
// 移動先の座標と工具箱の座標を比較し、レベルアップをチェック
const current = robot.getCurrent();
if(toolBoxes.has(`${current}`)){
robot.levelUp();
}
}
robots.forEach(r => console.log(r.getResult()));
});
✅ ① this.moveDir の役割と注意点
this.moveDir = {
N: (num) => { this.y -= num }, // 北は y を減少
S: (num) => { this.y += num }, // 南は y を増加
E: (num) => { this.x += num }, // 東は x を増加
W: (num) => { this.x -= num } // 西は x を減少
};
🔍ポイント:
-
方向と座標軸の対応をミスしやすいので要注意。
-
N(北)・S(南)は y 座標を増減、E(東)・W(西)は x 座標を増減。
-
この問題では、y座標は北へ行くと減り、南へ行くと増える という座標系を守るのが大事。
-
図を見て、どちらが増える/減るか必ず確認!
🔍実装ポイント:
this.moveDir[dir](距離)
の形で、方向文字列から直接正しい移動ロジックを呼び出せる。
※ちなみに direction の dir
✅ ② レベルアップ処理の上限チェック
if (this.level < 4) this.level++;
🔍ポイント:
-
ロボットのレベルは最大 4 に制限されているので、5 以上に上がらないようにする。
-
.levelUp()
メソッド内でチェックすることで、どこから呼んでも安心。 -
バグ防止の安全策。
✅ ③ 移動距離の計算を配列で簡潔化
move(dir) {
const distance = [0, 1, 2, 5, 10][this.level];
this.moveDir[dir](distance);
}
🔍ポイント:
-
レベルごとの移動距離を
if
文で分岐する代わりに、配列[0, 1, 2, 5, 10]
を使ってシンプル化。 -
level
は 1〜4 なので、インデックスをそのまま使えばズレなし。 -
distance
(距離の意) を変数にして可読性も◎。
🔍比較:
// 分岐なし → スッキリ
const distance = [0, 1, 2, 5, 10][this.level];
vs
// 以前の形
if (this.level === 1) …
if (this.level === 2) …
if (this.level === 3) …
...
→ 配列のほうが圧倒的に可読性・メンテ性が良い!
✅ ④ 工具箱との一致判定と trim() の重要性
const toolBoxes = new Set(lines.slice(1, 11).map(line => line.trim()));
...
const current = robot.getCurrent();
if (toolBoxes.has(current)) {
robot.levelUp();
}
🔍ポイント:
-
ロボットが移動後に工具箱のマスに止まっていたらレベルアップする仕様を実現。
-
工具箱の座標を
Set
にすることで、位置一致判定を高速化。 -
.getCurrent()
は“x y”
の文字列なので、工具箱も同じ形式で保存。 -
trim()
は必須!
→ 標準入力は改行や余分な空白を含む可能性があるので、trim()
しないと“1 2”
と“1 2\n”
が一致しなくなり、判定ミスが起こる。
🗝️ めも & まとめ
-
方向の増減と距離をデータで管理 → シンプルで正確
-
条件分岐を配列で置き換え → コード量削減 & 可読性UP
-
Set
による高速検索 &trim()
で入力の曖昧さ解消 -
ロジックを
Robot
クラスに閉じ込めて責任分担が明確