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

  • 612