Tìm đường tiêu chuẩn là đủ tốt - trạng thái của bạn là vị trí hiện tại của bạn + hàng tồn kho hiện tại của bạn. "Di chuyển" là thay đổi phòng hoặc thay đổi hàng tồn kho. Không nằm trong câu trả lời này, nhưng không có quá nhiều nỗ lực bổ sung, đang viết một heuristic tốt cho A * - nó thực sự có thể tăng tốc tìm kiếm bằng cách thích nhặt những thứ hơn di chuyển ra khỏi nó, thích mở khóa một cánh cửa gần mục tiêu tìm kiếm một chặng đường dài, v.v.
Câu trả lời này đã nhận được rất nhiều sự ủng hộ kể từ khi nó xuất hiện lần đầu tiên và có một bản demo, nhưng để có một giải pháp chuyên biệt và tối ưu hơn nhiều, bạn cũng nên đọc câu trả lời "Làm ngược lại nhanh hơn nhiều" /gamedev/ / a / 150155/2624
Javascript hoạt động đầy đủ của khái niệm dưới đây. Xin lỗi vì câu trả lời như một bãi chứa mã - tôi đã thực sự thực hiện điều này trước khi tôi tin rằng đó là một câu trả lời tốt, nhưng nó có vẻ khá linh hoạt đối với tôi.
Để bắt đầu khi nghĩ về tìm đường, hãy nhớ rằng sự thừa kế của các thuật toán tìm đường đơn giản là:
- Breadth First Search là đơn giản như bạn có thể nhận được.
- Thuật toán của Djikstra giống như Tìm kiếm đầu tiên của Breadth nhưng với "khoảng cách" khác nhau giữa các quốc gia
- A * là Djikstras nơi bạn có 'ý thức chung về hướng đúng' có sẵn như là một heuristic.
Trong trường hợp của chúng tôi, chỉ cần mã hóa "trạng thái" là "vị trí + khoảng không quảng cáo" và "khoảng cách" là "sử dụng vật phẩm hoặc chuyển động" cho phép chúng tôi sử dụng Djikstra hoặc A * để giải quyết vấn đề của mình.
Đây là một số mã thực tế chứng minh mức độ ví dụ của bạn. Đoạn mã đầu tiên chỉ để so sánh - chuyển sang phần thứ hai nếu bạn muốn xem giải pháp cuối cùng. Chúng tôi bắt đầu với việc triển khai của Djikstra tìm ra đường dẫn chính xác, nhưng chúng tôi đã bỏ qua tất cả các chướng ngại vật và chìa khóa. (Hãy dùng thử, bạn có thể thấy nó chỉ là những con ong cho kết thúc, từ phòng 0 -> 2 -> 3-> 4-> 6-> 5)
function Transition(cost, state) { this.cost = cost, this.state = state; }
// given a current room, return a room of next rooms we can go to. it costs
// 1 action to move to another room.
function next(n) {
var moves = []
// simulate moving to a room
var move = room => new Transition(1, room)
if (n == 0) moves.push(move(2))
else if ( n == 1) moves.push(move(2))
else if ( n == 2) moves.push(move(0), move(1), move(3))
else if ( n == 3) moves.push(move(2), move(4), move(6))
else if ( n == 4) moves.push(move(3))
else if ( n == 5) moves.push(move(6))
else if ( n == 6) moves.push(move(5), move(3))
return moves
}
// Standard Djikstra's algorithm. keep a list of visited and unvisited nodes
// and iteratively find the "cheapest" next node to visit.
function calc_Djikstra(cost, goal, history, nextStates, visited) {
if (!nextStates.length) return ['did not find goal', history]
var action = nextStates.pop()
cost += action.cost
var cur = action.state
if (cur == goal) return ['found!', history.concat([cur])]
if (history.length > 15) return ['we got lost', history]
var notVisited = (visit) => {
return visited.filter(v => JSON.stringify(v) == JSON.stringify(visit.state)).length === 0;
};
nextStates = nextStates.concat(next(cur).filter(notVisited))
nextStates.sort()
visited.push(cur)
return calc_Djikstra(cost, goal, history.concat([cur]), nextStates, visited)
}
console.log(calc_Djikstra(0, 5, [], [new Transition(0, 0)], []))
Vì vậy, làm thế nào để chúng ta thêm các mục và khóa vào mã này? Đơn giản! thay vì mỗi "trạng thái" chỉ bắt đầu số phòng, giờ đây là một bộ phòng và trạng thái tồn kho của chúng tôi:
// Now, each state is a [room, haskey, hasfeather, killedboss] tuple
function State(room, k, f, b) { this.room = room; this.k = k; this.f = f; this.b = b }
Chuyển đổi bây giờ thay đổi từ một tuple (chi phí, phòng) sang một tuple (chi phí, trạng thái), do đó có thể mã hóa cả "chuyển sang phòng khác" và "nhặt một vật phẩm"
// move(3) keeps inventory but sets the room to 3
var move = room => new Transition(1, new State(room, cur.k, cur.f, cur.b))
// pickup("k") keeps room number but increments the key count
var pickup = (cost, item) => {
var n = Object.assign({}, cur)
n[item]++;
return new Transition(cost, new State(cur.room, n.k, n.f, n.b));
};
cuối cùng, chúng tôi thực hiện một số thay đổi nhỏ liên quan đến loại đối với chức năng Djikstra (ví dụ: nó vẫn chỉ khớp với số phòng mục tiêu thay vì trạng thái đầy đủ) và chúng tôi nhận được câu trả lời đầy đủ! Lưu ý kết quả được in trước tiên vào phòng 4 để lấy chìa khóa, sau đó đến phòng 1 để nhặt lông, sau đó đến phòng 6, giết sếp, sau đó đến phòng 5)
// Now, each state is a [room, haskey, hasfeather, killedboss] tuple
function State(room, k, f, b) { this.room = room; this.k = k; this.f = f; this.b = b }
function Transition(cost, state, msg) { this.cost = cost, this.state = state; this.msg = msg; }
function next(cur) {
var moves = []
// simulate moving to a room
var n = cur.room
var move = room => new Transition(1, new State(room, cur.k, cur.f, cur.b), "move to " + room)
var pickup = (cost, item) => {
var n = Object.assign({}, cur)
n[item]++;
return new Transition(cost, new State(cur.room, n.k, n.f, n.b), {
"k": "pick up key",
"f": "pick up feather",
"b": "SLAY BOSS!!!!"}[item]);
};
if (n == 0) moves.push(move(2))
else if ( n == 1) { }
else if ( n == 2) moves.push(move(0), move(3))
else if ( n == 3) moves.push(move(2), move(4))
else if ( n == 4) moves.push(move(3))
else if ( n == 5) { }
else if ( n == 6) { }
// if we have a key, then we can move between rooms 1 and 2
if (cur.k && n == 1) moves.push(move(2));
if (cur.k && n == 2) moves.push(move(1));
// if we have a feather, then we can move between rooms 3 and 6
if (cur.f && n == 3) moves.push(move(6));
if (cur.f && n == 6) moves.push(move(3));
// if killed the boss, then we can move between rooms 5 and 6
if (cur.b && n == 5) moves.push(move(6));
if (cur.b && n == 6) moves.push(move(5));
if (n == 4 && !cur.k) moves.push(pickup(0, 'k'))
if (n == 1 && !cur.f) moves.push(pickup(0, 'f'))
if (n == 6 && !cur.b) moves.push(pickup(100, 'b'))
return moves
}
var notVisited = (visitedList) => (visit) => {
return visitedList.filter(v => JSON.stringify(v) == JSON.stringify(visit.state)).length === 0;
};
// Standard Djikstra's algorithm. keep a list of visited and unvisited nodes
// and iteratively find the "cheapest" next node to visit.
function calc_Djikstra(cost, goal, history, nextStates, visited) {
if (!nextStates.length) return ['No path exists', history]
var action = nextStates.pop()
cost += action.cost
var cur = action.state
if (cur.room == goal) return history.concat([action.msg])
if (history.length > 15) return ['we got lost', history]
nextStates = nextStates.concat(next(cur).filter(notVisited(visited)))
nextStates.sort()
visited.push(cur)
return calc_Djikstra(cost, goal, history.concat([action.msg]), nextStates, visited)
o}
console.log(calc_Djikstra(0, 5, [], [new Transition(0, new State(0, 0, 0, 0), 'start')], []))
Về lý thuyết, điều này hoạt động ngay cả với BFS và chúng tôi không cần chức năng chi phí cho Djikstra, nhưng có chi phí cho phép chúng tôi nói "nhặt chìa khóa là dễ dàng, nhưng chiến đấu với một ông chủ thực sự khó khăn, và chúng tôi muốn quay lại 100 bước thay vì chiến đấu với ông chủ, nếu chúng ta có sự lựa chọn ":
if (n == 4 && !cur.k) moves.push(pickup(0, 'k'))
if (n == 1 && !cur.f) moves.push(pickup(0, 'f'))
if (n == 6 && !cur.b) moves.push(pickup(100, 'b'))