前回(Part 1)はこちら:
https://qiita.com/qtenky/items/a9923a434456f2194704
今回は、「人が乗りたいと言ったとき(外ボタンが押されたとき)」はどのイベントで拾えるのか? を起点に、Challenge #2: Transport 20 people in 60 seconds or less を解くための考え方と実装に進みます。
今回やること
前回は idle(エレベーターが暇になった瞬間)のイベントを扱いました。
じゃあ逆に、外で人が待っていて「上/下」ボタンが押された瞬間はどのイベント?
答えはこれです。
- フロア側:
up_button_pressed/down_button_pressed - エレベーター側:
floor_button_pressed(乗った人が行き先を押す)
まずは外ボタン(フロア側)から拾っていきます。
elevators と floors は “配列” で渡ってくる
init の引数を見てください。
init: function(elevators, floors) {
ここにある通り、elevators と floors は 配列 です。
ドキュメントにもこうあります:
Do stuff with the elevators and floors, which are both arrays of objects
つまり中身のイメージはこう。
elevators = [elevator, elevator, elevator, ...];
floors = [floor, floor, ...];
そしてサンプルではこうして1台目を取っています。
var elevator = elevators[0];
複数台あるとき「全員が同じ動き」だと事故る
ここでちょっと想像。
エレベーターが複数台あるのに、全員が同じロジックで同じ階へ向かったら…?
→ 無駄移動だらけで、効率が死にます。
解決策の一例としては、
-
エレベーターごとに担当範囲を分ける
- 低層担当・高層担当みたいに分担する
例えばこんな感じ(今回はChallenge #2なので「考え方の紹介」だけ):
var mid = Math.floor(floors.length / 2);
-
Math.floor()は切り下げ(Math.floor(3.9) === 3) - 四捨五入は
Math.round()
2.5階みたいな謎フロアを作らないためにも、整数にするのは大事です。
ただし今回は Challenge #2(エレベーター1台)に戻る
今回は前提がシンプル。
-
エレベーター:1台
-
目的:外で待っている人を素早く拾って運び切る
-
制限:60秒
- 無駄移動・重複処理は即アウト
つまり肝はこれ:
- 外ボタンが押された階を「呼び出し」として記録する
- 重複登録を防ぐ
-
idleになったら、今ある呼び出しの中で 一番近い階へ行く(絶対値で距離計算) - 行くと決めた呼び出しは配列から消す(←次回やる)
外ボタンを記録する requests を作る
呼び出し階を溜める配列 requests を用意します。
requests = [0, 1, 4];
これは「0階、1階、4階から呼ばれている」状態。
重複を防ぐには includes を使うのが定番です。
if (!requests.includes(floorNum)) {
requests.push(floorNum);
}
-
includes:入ってたらtrue、なければfalse -
push:末尾に追加
注意:floors.on は書けない(floors は配列)
ここ、つまずきポイントです。
floors は配列なのでこうはできません:
floors.on("up_button_pressed", ...)
代わりに、各 floor に対してイベントを登録します。
外ボタンイベントを requests に追加する
{
init: function(elevators, floors) {
var elevator = elevators[0];
var requests = [];
floors.forEach(function(floor) {
floor.on("up_button_pressed", function() {
var floorNum = floor.floorNum();
if (!requests.includes(floorNum)) {
requests.push(floorNum);
}
});
floor.on("down_button_pressed", function() {
var floorNum = floor.floorNum();
if (!requests.includes(floorNum)) {
requests.push(floorNum);
}
});
});
},
update: function(dt, elevators, floors) {}
}
「これだめ?」:floor.floorNum() 直書きでも動くけど、冗長になりやすい
たとえばこういう書き方:
floor.on("down_button_pressed", function() {
if (!requests.includes(floor.floorNum())) {
requests.push(floor.floorNum());
}
});
これ自体が間違いではないです。動きます。
でも floor.floorNum() を2回呼んでいて読みづらいし、up と down で同じ処理をコピペし始めると、すぐコードが太ります。
なので、一度変数に入れて使い回すのが読みやすいです。
次は「どの階に行くべきか」を絶対値で決める
idle(暇)になったら、requests の中から 今いる階に一番近い階へ向かいます。
距離は絶対値:
Math.abs(target - current)
ここでは
-
bestFloor(今選ぶべき階) -
bestDist(その距離)
を使います。
var current = elevator.currentFloor();
var bestFloor = null;
var bestDist = Infinity;
for (var i = 0; i < requests.length; i++) {
var dist = Math.abs(requests[i] - current);
if (dist < bestDist) {
bestDist = dist;
bestFloor = requests[i];
}
}
ここまで統合:idle で最寄りの呼び出しへ向かう
{
init: function(elevators, floors) {
var elevator = elevators[0];
var requests = [];
floors.forEach(function(floor) {
var register = function() {
var floorNum = floor.floorNum();
if (!requests.includes(floorNum)) {
requests.push(floorNum);
}
};
floor.on("up_button_pressed", register);
floor.on("down_button_pressed", register);
});
elevator.on("idle", function() {
if (requests.length === 0) return;
var current = elevator.currentFloor();
var bestFloor = null;
var bestDist = Infinity;
for (var i = 0; i < requests.length; i++) {
var dist = Math.abs(requests[i] - current);
if (dist < bestDist) {
bestDist = dist;
bestFloor = requests[i];
}
}
elevator.goToFloor(bestFloor);
});
},
update: function(dt, elevators, floors) {}
}
次回予告:選んだ階は requests から消す
ここまでで、
- 外ボタン押下 →
requestsに登録 -
idle→ 最寄り階を選んで向かう
まではできました。
次はここです:
3) 選んだ階は requests から消す
行くと決めたら、それはもう「呼び出し」じゃないので削除する。
ここまでで Part 2 は一旦終了です。