JavaScrtip Lab1
A Robot
Date: 2019/10/27
Lecturer: 土豆
大綱
- A Robot of What?
- 在程式中建立村莊地圖
- 用Class代表目前狀態
- Robots
A Robot of What?
設計一個Robot,幫我們拿取包裹,並送至指定地點
這是村莊的地圖
如果用OOP的概念該怎麼設計這個程式
1. Robot
這個程式中的物件有哪些?
2. Village
3. Parcel
4. Place
......
全部寫成Class!
如果你是這樣想,那就錯了
物件導向這種東西,剛剛好就好
不是你(X
在程式中建立村莊地圖
把地圖轉換成程式看得懂的樣子
?
Step1: Array & String
const roads = [
"Alice's House-Bob's House", "Alice's House-Cabin",
"Alice's House-Post Office", "Bob's House-Town Hall",
"Daria's House-Ernie's House", "Daria's House-Town Hall",
"Ernie's House-Grete's House", "Grete's House-Farm",
"Grete's House-Shop", "Marketplace-Farm",
"Marketplace-Post Office", "Marketplace-Shop",
"Marketplace-Town Hall", "Shop-Town Hall"
];
Step2: Graph
一種資料結構
{
'Alice\'s House': [ 'Bob\'s House', 'Cabin', 'Post Office' ],
'Bob\'s House': [ 'Alice\'s House', 'Town Hall' ],
Cabin: [ 'Alice\'s House' ],
'Post Office': [ 'Alice\'s House', 'Marketplace' ],
'Town Hall': [ 'Bob\'s House', 'Daria\'s House', 'Marketplace', 'Shop' ],
'Daria\'s House': [ 'Ernie\'s House', 'Town Hall' ],
'Ernie\'s House': [ 'Daria\'s House', 'Grete\'s House' ],
'Grete\'s House': [ 'Ernie\'s House', 'Farm', 'Shop' ],
Farm: [ 'Grete\'s House', 'Marketplace' ],
Shop: [ 'Grete\'s House', 'Marketplace', 'Town Hall' ],
Marketplace: [ 'Farm', 'Post Office', 'Shop', 'Town Hall' ]
}
Array to Graph轉換程式碼
function buildGraph(edges) {
let graph = Object.create(null);
function addEdge(from, to) {
if (graph[from] == null) {
graph[from] = [to];
} else {
graph[from].push(to);
}
}
for (let [from, to] of edges.map(r => r.split("-"))) {
addEdge(from, to);
addEdge(to, from);
}
return graph;
}
用Class代表目前狀態
先看看完整程式
class VillageState {
constructor(place, parcels) {
this.place = place;
this.parcels = parcels;
}
move(destination) {
if (!roadGraph[this.place].includes(destination)) {
return this;
} else {
let parcels = this.parcels.map(p => {
if (p.place != this.place) return p;
return {place: destination, address: p.address};
}).filter(p => p.place != p.address);
return new VillageState(destination, parcels);
}
}
}
Attribute
Place: 目前robot所在地
Parcels: 待送包裹
parcels:
[ { place: 'Marketplace', address: 'Bob\'s House' },
{ place: 'Alice\'s House', address: 'Marketplace' },
{ place: 'Bob\'s House', address: 'Daria\'s House' },
{ place: 'Town Hall', address: 'Post Office' },
{ place: 'Post Office', address: 'Ernie\'s House' } ]
- place: 包裹目前位置
- address: 包裹的目的地
Methods
如果目的地不在graph上,則不動
否則
1. 確認所有parcel
2. 過濾掉已經到達地點的包裹,亦即place = address
如果目前不在包裹的位置,則包裹狀態不變
如果在包裹目前位置(原本初始位置 or 正被你帶在身上),則更新包裹位置至目標地點
Methods
move(destination) {
if (!roadGraph[this.place].includes(destination)) {
return this;
} else {
let parcels = this.parcels.map(p => {
if (p.place != this.place) return p;
return {place: destination, address: p.address};
}).filter(p => p.place != p.address);
return new VillageState(destination, parcels);
}
}
測試一下
// Class VillageState
let first = new VillageState(
"Post Office",
[{place: "Post Office", address: "Alice's House"}]
);
let next = first.move("Alice's House");
console.log(first.place);
// → Post Office
console.log(next.place);
// → Alice's House
console.log(next.parcels);
// → []
隨機初始化
function randomPick(array) {
let choice = Math.floor(Math.random() * array.length);
return array[choice];
}
VillageState.random = function(parcelCount = 5) {
let parcels = [];
for (let i = 0; i < parcelCount; i++) {
let address = randomPick(Object.keys(roadGraph));
let place;
do {
place = randomPick(Object.keys(roadGraph));
} while (place == address);
parcels.push({place, address});
}
return new VillageState("Post Office", parcels);
};
定義成static
隨機初始化(這也也可以)
function randomPick(array) {
let choice = Math.floor(Math.random() * array.length);
return array[choice];
}
class VillageState {
// constructor
// move
static random(parcelCount = 5) {
let parcels = [];
for (let i = 0; i < parcelCount; i++) {
let address = randomPick(Object.keys(roadGraph));
let place;
do {
place = randomPick(Object.keys(roadGraph));
} while (place == address);
parcels.push({place, address});
}
return new VillageState("Post Office", parcels);
}
}
Robots
RunRobot
function runRobot(state, robot, memory) {
for (let turn = 0;; turn++) {
if (state.parcels.length == 0) {
console.log(`Done in ${turn} turns`);
break;
}
let action = robot(state, memory);
state = state.move(action.direction);
memory = action.memory;
console.log(`Moved to ${action.direction}`);
}
}
- state: VillageState
- robot: 根據state決定下一步怎麼走
- memory: 記錄下某些資訊
有很多方式決定下一步怎麼走
最簡單的是Random,每一步都是隨機選擇
// buildGraph
// Class Village
// runRobot
function randomPick(array) {
let choice = Math.floor(Math.random() * array.length);
return array[choice];
}
function randomRobot(state) {
return {direction: randomPick(roadGraph[state.place])};
}
它不需要memory
Robot1: randomRobot
怎麼跑
// ...
runRobot(VillageState.random(), randomRobot, []);
// Your code
runRobotAnimation(VillageState.random(), randomRobot, []);
固定路徑
先找出可走訪全部地點的路徑,然後直接跑一遍
最多兩遍可送完
// buildGraph
// Class Village
// runRobot
const mailRoute = [
"Alice's House", "Cabin", "Alice's House", "Bob's House",
"Town Hall", "Daria's House", "Ernie's House",
"Grete's House", "Shop", "Grete's House", "Farm",
"Marketplace", "Post Office"
];
function routeRobot(state, memory) {
if (memory.length == 0) {
memory = mailRoute;
}
return {direction: memory[0], memory: memory.slice(1)};
}
Robot2: routeRobot
比較有技巧性地找路徑
前面兩種方式,雖然完成的了任務,但有點沒效率
我們需要想想怎麼走會比較好
讀取某節點連通的所有節點
如果讀取到目標節點,則結束
如果都不是目標節點,則將這些節點放入待搜尋名單
廣度優先搜尋
BFS(Breadth-First Search)
重複以上動作,直到到達目的地為止
比較有技巧性地找路徑
From: Alice's House
To: Shop
OK!
找路徑程式碼
function findRoute(graph, from, to) {
let work = [{at: from, route: []}];
for (let i = 0; i < work.length; i++) {
let {at, route} = work[i];
for (let place of graph[at]) {
if (place == to) return route.concat(place);
if (!work.some(w => w.at == place)) {
work.push({at: place, route: route.concat(place)});
}
}
}
}
來跑跑看
function buildGraph(edges) {
let graph = Object.create(null);
function addEdge(from, to) {
if (graph[from] == null) {
graph[from] = [to];
} else {
graph[from].push(to);
}
}
for (let [from, to] of edges.map(r => r.split("-"))) {
addEdge(from, to);
addEdge(to, from);
}
return graph;
}
const roads = [
"Alice's House-Bob's House", "Alice's House-Cabin",
"Alice's House-Post Office", "Bob's House-Town Hall",
"Daria's House-Ernie's House", "Daria's House-Town Hall",
"Ernie's House-Grete's House", "Grete's House-Farm",
"Grete's House-Shop", "Marketplace-Farm",
"Marketplace-Post Office", "Marketplace-Shop",
"Marketplace-Town Hall", "Shop-Town Hall"
];
const roadGraph = buildGraph(roads);
function findRoute(graph, from, to) {
let work = [{at: from, route: []}];
for (let i = 0; i < work.length; i++) {
console.log("------------------------------------");
console.log(work[i]);
console.log(work);
let {at, route} = work[i];
for (let place of graph[at]) {
console.log(place);
if (place == to) return route.concat(place);
if (!work.some(w => w.at == place)) {
work.push({at: place, route: route.concat(place)});
}
}
}
}
console.log(findRoute(roadGraph, "Alice's House", "Shop"));
goalOrientedRobot
function goalOrientedRobot(state, route) {
if (route.length == 0) {
let parcel = state.parcels[0];
if (parcel.place != state.place) {
route = findRoute(roadGraph, state.place, parcel.place);
} else {
route = findRoute(roadGraph, state.place, parcel.address);
}
}
return {direction: route[0], memory: route.slice(1)};
}
如果沒有路徑存在,就以目前的目的地,新增一條路徑
如果已經有路徑,就把路徑走完
如果還沒拿到包裹,就先去拿
如果已經有包裹,就往包裹目的地走
Robot3: goalOrientedRobot
完整程式碼
謝謝聆聽
JavaScript Lab1: A Robot
By Sam Yang
JavaScript Lab1: A Robot
- 630