Pathfinding standar adalah Cukup Baik - negara Anda adalah lokasi Anda saat ini + inventaris Anda saat ini. "Memindahkan" adalah mengubah kamar atau mengubah inventaris. Tidak tercakup dalam jawaban ini, tetapi tidak terlalu banyak upaya tambahan, menulis heuristik yang baik untuk A * - itu benar-benar dapat mempercepat pencarian dengan lebih memilih untuk mengambil barang-barang daripada bergerak menjauh dari itu, lebih memilih untuk membuka kunci pintu di dekat target mencari lebih jauh, dll.
Jawaban ini telah mendapatkan banyak peningkatan sejak datang pertama dan memiliki demo, tetapi untuk solusi yang jauh lebih dioptimalkan dan khusus, Anda juga harus membaca jawaban "Melakukannya mundur jauh lebih cepat" /gamedev/ / a / 150155/2624
Bukti Javascript yang sepenuhnya operasional konsep di bawah ini. Maaf untuk jawabannya sebagai dump kode - Saya telah benar-benar menerapkan ini sebelum saya yakin itu adalah jawaban yang baik, tetapi tampaknya cukup fleksibel bagi saya.
Untuk memulai ketika memikirkan pathfinding, ingatlah bahwa hirarki algoritma pathfinding sederhana adalah:
- Breadth First Search adalah sesederhana mungkin.
- Algoritma Djikstra seperti Breadth First Search tetapi dengan "jarak" yang bervariasi antar negara bagian
- A * adalah Djikstras di mana Anda memiliki 'pengertian umum tentang arah yang benar' yang tersedia sebagai heuristik.
Dalam kasus kami, hanya penyandian "keadaan" sebagai "lokasi + inventaris" dan "jarak" sebagai "perpindahan atau penggunaan item" memungkinkan kami untuk menggunakan Djikstra atau A * untuk menyelesaikan masalah kami.
Berikut adalah beberapa kode aktual yang menunjukkan level contoh Anda. Cuplikan pertama hanya untuk perbandingan - lompat ke bagian kedua jika Anda ingin melihat solusi akhir. Kami memulai dengan implementasi Djikstra yang menemukan jalan yang benar, tetapi kami mengabaikan semua hambatan dan kunci. (Cobalah, Anda bisa melihatnya hanya untuk garis finish, dari kamar 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)], []))
Jadi, bagaimana kita menambahkan item dan kunci ke kode ini? Sederhana! alih-alih setiap "keadaan" memulai hanya nomor kamar, sekarang menjadi tupel ruangan dan status inventaris kami:
// 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 }
Transisi sekarang berubah dari tuple (biaya, kamar) menjadi tupel (biaya, status), sehingga dapat menyandikan "pindah ke kamar lain" dan "mengambil item"
// 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));
};
akhirnya, kami membuat beberapa perubahan kecil yang berhubungan dengan tipe pada fungsi Djikstra (misalnya, masih cocok dengan nomor ruang tujuan alih-alih keadaan penuh), dan kami mendapatkan jawaban lengkap kami! Perhatikan hasil cetakan pertama pergi ke kamar 4 untuk mengambil kunci, kemudian pergi ke kamar 1 untuk mengambil bulu, kemudian pergi ke kamar 6, membunuh bos, lalu pergi ke kamar 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')], []))
Secara teori, ini bekerja bahkan dengan BFS dan kami tidak memerlukan fungsi biaya untuk Djikstra, tetapi memiliki biaya memungkinkan kami untuk mengatakan "mengambil kunci itu mudah, tetapi melawan bos sangat sulit, dan kami lebih memilih mundur 100 langkah daripada melawan bos, jika kita punya pilihan ":
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'))