Стандартне накладання маршрутів є достатньо хорошим - ваші штати - це ваше поточне місцезнаходження + ваш поточний інвентар. "переміщення" - це або роздягальня, або зміна інвентарю. Не висвітлений у цій відповіді, але не надто багато додаткових зусиль - це написання хорошої евристики для A * - це може дійсно пришвидшити пошук, віддаючи перевагу забрати речі, віддаляючись від нього, вважаючи за краще відмикати двері біля цілі над пошуком довгого шляху тощо.
Ця відповідь отримала багато відгуків, оскільки вона з’явилася вперше та має демонстраційну версію, але для набагато більш оптимізованого та спеціалізованого рішення слід також прочитати відповідь «Робити це набагато швидше назад» /gamedev/ / а / 150155/2624
Повністю функціональне підтвердження Javascript концепту нижче. Вибачте за відповідь як дамп коду - я фактично реалізував це ще до того, як переконався, що це хороша відповідь, але мені це здається досить гнучким.
Щоб почати, думаючи про пошук маршруту, пам’ятайте, що спадковість простих алгоритмів проходження маршруту:
- Перший пошук ширини приблизно такий же простий, як ви можете отримати.
- Алгоритм Джикстри - це як пошук першої ширини, але з різними "відстанями" між державами
- A * - Джикстрас, де у вас є «загальне відчуття правильного напрямку», яке є евристичним.
У нашому випадку просто кодування "стану" як "розташування + інвентар" та "відстані" як "руху або використання предмета" дозволяє нам використовувати Djikstra або A * для вирішення нашої проблеми.
Ось фактичний код, що демонструє ваш приклад рівня. Перший фрагмент лише для порівняння - перейдіть до другої частини, якщо ви хочете побачити остаточне рішення. Ми починаємо з реалізації Djikstra, яка знаходить правильний шлях, але ми проігнорували всі перешкоди та ключі. (Спробуйте, ви можете побачити його лише на фініші, з кімнати 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)], []))
Отже, як ми додаємо елементи та ключі до цього коду? Просто! замість кожного "штату" починати саме номер кімнати, тепер це картка кімнати та наш інвентарний стан:
// 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 }
Тепер переходи змінюються від (вартість, номер) кортежу до (вартості, штату) кортежу, тому тоді можна кодувати як "переїзд до іншої кімнати", так і "підбір предмета"
// 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));
};
нарешті, ми робимо деякі незначні зміни, пов'язані з типом функції Djikstra (наприклад, вона все ще просто відповідає номеру кімнати цілі замість повного стану), і ми отримуємо повну відповідь! Зауважте, що надрукований результат спочатку переходить до кімнати 4, щоб забрати ключ, потім переходить до кімнати 1, щоб забрати перо, потім переходить до кімнати 6, вбиває начальника, потім переходить до кімнати 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')], []))
Теоретично це працює навіть з BFS, і нам не потрібна функція витрат для Djikstra, але наявність вартості дозволяє нам сказати: "забрати ключ - це нелегко, але боротися з начальником - це дуже важко, і ми б краще відмовитися 100 кроків, а не боротьба з начальником, якби у нас був вибір ":
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'))