<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/css" href="http://wiki2.linuxformat.ru/skins/common/feed.css?97"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki2.linuxformat.ru/index.php?action=history&amp;feed=atom&amp;title=LXF72%3APHP</id>
		<title>LXF72:PHP - История изменений</title>
		<link rel="self" type="application/atom+xml" href="http://wiki2.linuxformat.ru/index.php?action=history&amp;feed=atom&amp;title=LXF72%3APHP"/>
		<link rel="alternate" type="text/html" href="http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;action=history"/>
		<updated>2026-05-13T20:58:19Z</updated>
		<subtitle>История изменений этой страницы в вики</subtitle>
		<generator>MediaWiki 1.11.1</generator>

	<entry>
		<id>http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;diff=5971&amp;oldid=prev</id>
		<title>Yaleks: викификация</title>
		<link rel="alternate" type="text/html" href="http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;diff=5971&amp;oldid=prev"/>
				<updated>2008-12-13T18:28:58Z</updated>
		
		<summary type="html">&lt;p&gt;викификация&lt;/p&gt;
&lt;a href=&quot;http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;amp;diff=5971&amp;amp;oldid=5970&quot;&gt;(Различия между версиями)&lt;/a&gt;</summary>
		<author><name>Yaleks</name></author>	</entry>

	<entry>
		<id>http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;diff=5970&amp;oldid=prev</id>
		<title>Yaleks: Новая: {{Цикл/PHP}} ==PHP A* ПоисК ПУти== '''''Пол Хадсон''' (Paul Hudson) не может придумать, как выбраться из бумажного пакет...</title>
		<link rel="alternate" type="text/html" href="http://wiki2.linuxformat.ru/index.php?title=LXF72:PHP&amp;diff=5970&amp;oldid=prev"/>
				<updated>2008-12-13T18:24:47Z</updated>
		
		<summary type="html">&lt;p&gt;Новая: {{Цикл/PHP}} ==PHP A* ПоисК ПУти== '''''Пол Хадсон''' (Paul Hudson) не может придумать, как выбраться из бумажного пакет...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая статья&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Цикл/PHP}}&lt;br /&gt;
==PHP A* ПоисК ПУти==&lt;br /&gt;
'''''Пол Хадсон''' (Paul Hudson) не может придумать, как выбраться из бумажного пакета, но он может написать программу поиска пути. если вы любитель создавать игры, можете за ним последовать. Исходный код – на прилагаемом диске.''&lt;br /&gt;
&lt;br /&gt;
Первая реализация игры Elite для BBC Micro занимала всего&lt;br /&gt;
лишь 22 килобайта, но в ней вы могли до бесконечности&lt;br /&gt;
летать от звезды к звезде и исследовать новые планеты.&lt;br /&gt;
Конечно, большая часть этих звёзд была сгенерирована с помощью&lt;br /&gt;
случайных чисел, и именно поэтому вы могли путешествовать очень&lt;br /&gt;
долго без необходимости в тысячах дискет. В Elite выполнялось главное правило создания игр – не надо хранить то, что можно очень быстро сгенерировать. Для карт это значит, что нет смысла хранить все&lt;br /&gt;
возможные маршруты ваших искусственных существ, вместо этого&lt;br /&gt;
лучше использовать алгоритм поиска пути, благодаря которому они&lt;br /&gt;
смогут перемещаться в изменяющемся окружении, демонстрируя некоторый интеллект.&lt;br /&gt;
&lt;br /&gt;
=== всё, что есть на свете ===&lt;br /&gt;
Существует два популярных алгоритма поиска пути, и они распространены по разным причинам. Первый называется алгоритмом Дейкстры&lt;br /&gt;
(он так же известен как алгоритм заливки), и он популярен так как его&lt;br /&gt;
просто программировать. Второй называется A* (читается как «А-звёздочка»), и он популярен потому, что быстр. В этой статье мы рассмотрим алгоритм А*, широко используемый в игровой индустрии. Суть в&lt;br /&gt;
том, что алгоритм Дейкстры на самом деле лучше, так как он всегда&lt;br /&gt;
находит кратчайший путь от точки А в точку б, тогда как А* очень&lt;br /&gt;
эффективен при поиске очень хороших маршрутов, но они не обязательно оказываются оптимальными.&lt;br /&gt;
&lt;br /&gt;
Алгоритм заливки так называется потому, что он делает поиск в&lt;br /&gt;
ширину – он ищет все возможные пути вокруг точки А перед тем, как&lt;br /&gt;
увеличить радиус поиска. Если мы оценим в одну единицу перемещение из квадрата в любой из восьми соседних, то мы сможем оценивать сложность пути – перемещение на два квадрата будет стоить&lt;br /&gt;
две единицы, перемещение на три – три и так далее. Выполняя&lt;br /&gt;
«заливку», мы оцениваем длину пути для каждого квадратика на&lt;br /&gt;
игровой доске, даже после того, как алгоритм добрался до целевой&lt;br /&gt;
точки. Это необходимо, так как идеальный путь может требовать&lt;br /&gt;
обойти целевую точку и затем вернуться к ней с противоположной&lt;br /&gt;
стороны.&lt;br /&gt;
&lt;br /&gt;
А* вместо этого использует эвристическую оценку предполагаемого расстояния между текущей точкой и целью маршрута. здесь нам&lt;br /&gt;
придётся изменить терминологию – мы будем называть квадратики&lt;br /&gt;
«узлами», так как алгоритм А* одинаково хорошо работает для квадратиков, шестиугольников и додекаэдров. Эвристикой будем называть&lt;br /&gt;
функцию, которая помогает найти расстояние до следующего узла –&lt;br /&gt;
оно может быть определено неправильно, но мы надеемся что оно окажется близко к истине. Эвристика, которой мы будем пользоваться,&lt;br /&gt;
полагается на Манхэттенское расстояние между точками, иначе называемое «геометрия таксиста». Этот показатель называется так потому,&lt;br /&gt;
что остров Манхэттен в нью йорке имеет прямоугольную планировку, и&lt;br /&gt;
если вы начали путь в точке А и закончили в точке б, то не имеет значения, какой маршрут вы избрали, поскольку длина у них у всех&lt;br /&gt;
одинаковая.&lt;br /&gt;
&lt;br /&gt;
Для ясности давайте введём персонажа, который ищет дорогу, а&lt;br /&gt;
вместо точек А и б будем говорить Старт и Финиш.&lt;br /&gt;
&lt;br /&gt;
=== вслед за магией ===&lt;br /&gt;
[[Изображение:img 72 95 1.png|thumb|Манхэттенское расстояние обозначает, что не важно, по какому маршруту вы доберётесь из точки А в точку B – пройденное расстояние получится одинаковым.]]&lt;br /&gt;
Алгоритм А* включает в себя три важных компонента – два массива&lt;br /&gt;
узлов под названиями «открытый список» и «закрытый список» и&lt;br /&gt;
наша эвристическая функция.&lt;br /&gt;
&lt;br /&gt;
Узел Старта (или точка А) добавляется к открытому списку, значит,&lt;br /&gt;
мы должны рассматривать её как часть пути. затем мы используем&lt;br /&gt;
эвристику, чтобы оценить путь от старта до финиша. Поскольку мы&lt;br /&gt;
используем манхэттенское расстояние, и поскольку первая версия&lt;br /&gt;
нашего алгоритма оценивает движение по диагонали как эквивалентное движению вдоль стороны квадрата, эвристике достаточно вычесть&lt;br /&gt;
координату X финиша из координаты X старта, затем так же вычесть&lt;br /&gt;
координаты Y и взять максимальную из полученных разностей. однако&lt;br /&gt;
это ещё не всё, мы знаем расстояние до финиша, но нам так же нужно&lt;br /&gt;
и расстояние до старта, а так же их суммарное значение (которое&lt;br /&gt;
будем называть суммарным расстоянием).&lt;br /&gt;
&lt;br /&gt;
хранить расстояние до старта важно, так как оно поможет нашему&lt;br /&gt;
персонажу найти самый короткий путь. При подсчёте эвристики для&lt;br /&gt;
данного узла мы должны передать ей расстояние до старта от предыдущего узла маршрута (того, который добавил данный узел в открытый список), для того чтобы правильно подсчитать пройденный путь.&lt;br /&gt;
После подсчёта эвристики для стартового узла мы входим в цикл.&lt;br /&gt;
В цикле мы будем искать возможность добавить новый узел в открытый список до тех пор, пока он не опустеет или пока мы не найдём путь&lt;br /&gt;
до финиша. Если мы пока не добрались до финиша и в открытом списке ещё есть элементы, возьмём из него элемент с минимальным суммарным расстоянием. В начале в открытом списке всего один элемент – старт – так что мы выберем именно его. затем мы должны&lt;br /&gt;
добавить все узлы вокруг старта в открытый список, подсчитав для&lt;br /&gt;
каждого из них эвристику. В финале переместим выбранный узел&lt;br /&gt;
(Старт на первом шаге) из открытого списка в закрытый, так как мы&lt;br /&gt;
знаем, что уже обработали его.&lt;br /&gt;
&lt;br /&gt;
на втором шаге цикла в открытом списке уже больше элементов,&lt;br /&gt;
так как там находятся все узлы, соседствующие со стартом. найдём&lt;br /&gt;
среди них элемент с минимальным суммарным расстоянием – и добавим всех его соседей в открытый список (кроме тех, которые уже находятся в закрытом списке, так что Старт в открытый список повторно не&lt;br /&gt;
попадёт).&lt;br /&gt;
&lt;br /&gt;
цикл будет продолжаться до тех пор, пока открытый список не&lt;br /&gt;
опустеет (что значит что нужного пути не существует) или пока Финиш&lt;br /&gt;
не попадёт в закрытый список – что значит мы нашли путь к нему.&lt;br /&gt;
&lt;br /&gt;
===Правила ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=нА зАМетКУ&lt;br /&gt;
|Содержание=&lt;br /&gt;
* При отладке ncurses оператор print не работает. используйте вместо него file_put_contents().&lt;br /&gt;
* Вы можете научить вашу реализацию А* работать по алгоритму Дейкстры, для этого достаточно задать эвристическую оценку пути от старта до финиша равную нулю. Это приведёт к тому, что поиск будет происходить во всех направлениях, пока не доберётся до финиша. Такой вариант хорош, когда вы не знаете, где находится финиш, или когда конечных узлов несколько.&lt;br /&gt;
* Если вы хотите больше узнать об алгоритмах поиска пути, посмотрите на апплет JsearchDemo, расположенный по адресу http://www.cs.kuleuven.ac.be/~remko/jsearchdemo. он реализует несколько различных методов (включая А*) на языке Java и позволяет разработать свой собственный метод размещения узлов.&lt;br /&gt;
|Ширина=250px}}&lt;br /&gt;
нет причин скрывать – этот код скорее всего будет самым сложным из&lt;br /&gt;
всего, что вы писали раньше. Проблема в том, что алгоритм А*&lt;br /&gt;
настолько сложен, что скорее всего потребует использования PHP-отладчика, иначе вы можете провести очень много времени, гуляя кругами и удивляясь, почему ваша программа падает, или почему персонаж забредает в никуда.&lt;br /&gt;
&lt;br /&gt;
Это ещё ничего. однако, вдобавок ко всем нашим проблемам мы&lt;br /&gt;
собираемся использовать ncurses, библиотеку для текстовой графики&lt;br /&gt;
из LXF69. использование ncurses требует компиляции вашей собственной версии php, с включённой поддержкой этой библиотеки. А это значит, что вы не сможете воспользоваться нестандартным отладчиком,&lt;br /&gt;
например входящим в Zend Studio, поскольку он может работать только с вполне конкретной версией PHP – именно с той, с которой отладчик был скомпилирован.&lt;br /&gt;
&lt;br /&gt;
Кроме того, это значит что вы не сможете отлаживать программу&lt;br /&gt;
при помощи большого числа операторов print в коде, так как их просто&lt;br /&gt;
не будет видно, поскольку ncurses занимает весь экран целиком. и&lt;br /&gt;
хотя вы увидите ошибку, из-за которой работа сценария прекращается,&lt;br /&gt;
понять её без помощи KSnapshot будет довольно затруднительно.&lt;br /&gt;
&lt;br /&gt;
итак, сочетание из А* и ncurses обозначает тяжёлую работу. но&lt;br /&gt;
чтобы оправдать заработанные тяжким трудом деньги, которые вы&lt;br /&gt;
заплатили за LXF, мы проделали эту работу сами! Так что вы обнаружите на прилагаемом к журналу диске код, готовый к использованию.&lt;br /&gt;
Поэтому давайте просто посмотрим, как он работает.&lt;br /&gt;
&lt;br /&gt;
Весь сценарий, кроме последней пары страницы кода состоит из&lt;br /&gt;
функций – пропустим их пока. Внизу вы увидите обычный код ncurses&lt;br /&gt;
для инициализации экрана и получения его размеров, но тут есть&lt;br /&gt;
несколько важных моментов.&lt;br /&gt;
&lt;br /&gt;
Глобальные переменные – а тут их несколько – записываются буквами в верхнем регистре. $LETTER_RANGE – это массив символов от&lt;br /&gt;
A до Z; он сделан глобальным для того, чтобы не пересоздавать его&lt;br /&gt;
каждый раз, когда он понадобится. $CURRENT_POS – это координаты точки, в которой находится персонаж. заметьте, что мы используем&lt;br /&gt;
метод «через коридор вверх по лестнице», т.е. номер колонки стоит&lt;br /&gt;
перед номером строки. Колонки нумеруются от A до Z, а строки от 1&lt;br /&gt;
до максимально доступного числа.&lt;br /&gt;
&lt;br /&gt;
Продолжаем. $prompt содержит текст, который мы покажем на&lt;br /&gt;
экране как инструкцию для пользователя. Мы хотим, чтобы люди могли ввести тут координаты ячейки, так что у этой же области экрана&lt;br /&gt;
должна быть возможность получить и интерпретировать ввод пользователя. Это делается при помощи глобальной переменной $LIVE_PROMPT, которая изначально принимает значение $prompt. Когда&lt;br /&gt;
пользователь вводит текст, он добавляется к $LIVE_PROMPT, так что&lt;br /&gt;
вы можете видеть то, что набираете. После нажатия на Enter переменная $prompt используется, чтобы вернуть $LIVE_PROMPT изначальное значение.&lt;br /&gt;
&lt;br /&gt;
После вызова функции drawgrid(), которую я вскоре опишу, код&lt;br /&gt;
входит в бесконечный цикл с помощью оператора while(1). Вызов&lt;br /&gt;
ncurses_wgetch() выполняет ожидание, пока не будет нажата какая-либо клавиша, а по нажатию мы входим в оператор switch для выбора&lt;br /&gt;
нужного действия. Если код нажатой клавиши равен 13 (то есть если&lt;br /&gt;
это Enter), то мы обрабатываем ввод пользователя, иначе просто&lt;br /&gt;
добавляем введённую букву к $LIVE_PROMPT и перерисовываем&lt;br /&gt;
экран.&lt;br /&gt;
&lt;br /&gt;
По нажатию Enter выполняется простейшая проверку ошибок.&lt;br /&gt;
было ли введено точно два символа? является ли первый символ буквой? является ли второй цифрой? Если все эти условия были выполнены, вызывается функция makemove() для поиска пути от старта до&lt;br /&gt;
финиша, $prompt сбрасывается и сетка рисуется заново.&lt;br /&gt;
&lt;br /&gt;
итак, это мы рассмотрели основную часть кода. Выше вы найдёте&lt;br /&gt;
функции resetgrid() для очистки данных, drawgrid() для рисования&lt;br /&gt;
сетки, функция handlenode() принимает узел и рассматривает, которых из его соседей можно использовать. Функция calc_distance()&lt;br /&gt;
рассчитывает эвристику, а makemove() настраивает открытый и&lt;br /&gt;
закрытый списки и вызывает handlenode() до тех пор, пока путь не&lt;br /&gt;
будет найден, а затем обновляет сетку, чтобы показать маршрут.&lt;br /&gt;
&lt;br /&gt;
==== resetgrid() ====&lt;br /&gt;
Функция resetgrid() создаёт и заполняет двумерный массив $DATA,&lt;br /&gt;
который содержит все ячейки сетки и их значения. Первое измерение&lt;br /&gt;
массива – это номер строки, второе – номер столбца. В каждом элементе массива содержится строка, которая отображается в нужной&lt;br /&gt;
ячейке. По умолчанию в каждом элементе «» (пустая строка). Когда&lt;br /&gt;
путь найден, в ячейках избранного маршрута содержится буква «z», а&lt;br /&gt;
в финальной точке - буква «c».&lt;br /&gt;
&lt;br /&gt;
Массив настраивается циклом от нуля до $MAX_ROW делённого&lt;br /&gt;
на два, это сделано потому, что в сетке нужно пространство для отображения содержимого. Вложенный цикл добавляет массиву второе&lt;br /&gt;
измерение и одновременно заполняет его пустыми значениями.&lt;br /&gt;
&lt;br /&gt;
==== calc_distance() ====&lt;br /&gt;
[[Изображение:img 72 96 1.png|thumb|А* так быстр потому, что он проверяет только близкие к маршруту узлы. Серым цветом помечены проверенные узлы, а красным – избранные узлы маршрута. Число в центре обозначает суммарное расстояние – сумму пути до этого узла от старта (точка А) и от узла до финиша (точка B).]]&lt;br /&gt;
calc_distance() – это наш эвристический калькулятор. Он вызывается каждый раз, когда надо оценить длину пути от одного узла до другого. О на состоит всего из четырёх строчек, но это самые важные&lt;br /&gt;
строчки в программе, поскольку именно они определяют, которая из&lt;br /&gt;
ячеек будет выбрана как следующая на пути к финишу. Первые две&lt;br /&gt;
строки очень просты, это наша реализация подсчёта манхэттенского&lt;br /&gt;
алгоритма. Помните, в сетке одинаково быстро можно как пройти сначала вверх на три блока, а потом вправо на три блока, так и вверх-вправо-вверх-вправо-вверх-вправо. Б олее того, поскольку мы трактуем диагональный переход равным переходу вдоль стенки квадрата, манхэттенское расстояние от старта до финиша всегда будет максимальным&lt;br /&gt;
из расстояний вдоль оси X или Y. Н апример, если финиш четырьмя&lt;br /&gt;
узлами правее старта и двумя узлами выше, то потребуется пройти&lt;br /&gt;
четыре узла, чтобы попасть куда нужно.&lt;br /&gt;
&lt;br /&gt;
Вернёмся к коду. Первая строка получает номер колонки из его&lt;br /&gt;
ASCII-эквивалента при помощи функции ord(). Н апример, 65 – это&lt;br /&gt;
ASCII-код латинской буквы A. Когда мы вычитаем два полученных числа друг из друга, то получаем расстояние между двумя колонками.&lt;br /&gt;
Если финиш левее, чем старт, то это даст нам отрицательное число,&lt;br /&gt;
поэтому воспользуемся функцией abs(), чтобы сделать его положительным. В третьей строке при помощи функции max() мы получим&lt;br /&gt;
большее число из двух, позволяющее нам эвристически оценить расстояние от старта до финиша.&lt;br /&gt;
&lt;br /&gt;
Важно заметить, что эвристическая оценка чаще всего оказывается&lt;br /&gt;
заниженной. В простейшем примере, который мы имеем, манхэттенское расстояние – это действительно минимальное расстояние от старта&lt;br /&gt;
до финиша, то есть если манхэттенское расстояние равно четырём&lt;br /&gt;
узлам, то персонажу придётся пройти именно четыре узла.&lt;br /&gt;
&lt;br /&gt;
Это не всегда оказывается верным. Если на пути есть препятствия,&lt;br /&gt;
или если у вас есть различные типы поверхности, которые могут&lt;br /&gt;
замедлять путь персонажа, то манхеттенское расстояние окажется&lt;br /&gt;
заниженным. Поэтому правильным будет сообщать расстояние от старта до финиша для «птичьего полёта», тогда как реальный путь окажется значительно медленнее. О днако, если наша эвристика завысит путь&lt;br /&gt;
от старта до финиша, то полученный путь окажется неоптимальным,&lt;br /&gt;
так что этот алгоритм попадёт в число неприемлемых (неспособных&lt;br /&gt;
найти оптимальный путь).&lt;br /&gt;
&lt;br /&gt;
Последняя строчка calc_distance() просто определяет возвращаемое значение, которое является массивом значений, содержащим&lt;br /&gt;
суммарное расстояние (насколько хорош путь через этот узел), расстояние до старта и эвристику (как далеко мы от финиша по геометрии&lt;br /&gt;
таксиста).&lt;br /&gt;
&lt;br /&gt;
==== makemove() ====&lt;br /&gt;
Функция makemove() вызывается, когда пользователь ввёл правильные координаты и нажал ввод. Как уже упоминалось, алгоритму А*&lt;br /&gt;
требуются открытый и закрытый списки узлов, в которых нужно искать&lt;br /&gt;
и в которых поиск будет производиться в определённом порядке. В&lt;br /&gt;
данной функции и создаются эти списки. В обоих массивах координаты узла используются в качестве ключа, а возвращённые из calc_distance() данные как значения (это массив, как говорилось чуть&lt;br /&gt;
раньше).&lt;br /&gt;
&lt;br /&gt;
Вся работа происходит в цикле while. В условиях цикла проверяется, что финиш не входит в закрытый список и что открытый список&lt;br /&gt;
не пуст. Если это верно, то вызывается функция handlenode() и ей&lt;br /&gt;
передаются открытый и закрытый списки. Когда цикл закончен, то&lt;br /&gt;
есть мы нашли финиш, мы вносим в соответствующую ячейку символ «с» и перерисовываем экран. З атем мы вызываем sleep(), из-за чего ничего не происходит целую секунду. Это нужно исключительно для красоты, чтобы вы успели полюбоваться изменениями на&lt;br /&gt;
экране.&lt;br /&gt;
&lt;br /&gt;
В конце концов, мы пробегаем по закрытому списку и задаём каждому его элементу значение «z», отмечая, каким образом персонаж&lt;br /&gt;
добрался до нужного места. И в конце концов обновляется значение&lt;br /&gt;
переменной $CURRENT_POS, чтобы запомнить новое положение&lt;br /&gt;
персонажа.&lt;br /&gt;
&lt;br /&gt;
==== handlenode() ====&lt;br /&gt;
Функция handlenode() самая длинная, но вовсе не самая сложная.&lt;br /&gt;
Больше всего места занимает поиск соседей нужной ячейки, которые&lt;br /&gt;
нуждаются в проверке. Если диагональное перемещение стоит больше, чем перемещение вдоль стороны квадрата, то мы легко можем&lt;br /&gt;
переработать этот код при помощи простого цикла – пройти строки от&lt;br /&gt;
row-1 до row+1 и столбцы от col-1 до col+1, пропустив текущий узел.&lt;br /&gt;
но по причине эквивалентности диагонали и простого смещения&lt;br /&gt;
использование такого кода будет поощрять диагональные перемещения – вместо движения по прямой, персонаж начнёт чертить зигзаги&lt;br /&gt;
на местности.&lt;br /&gt;
&lt;br /&gt;
В первой части handlenode() происходит поиск лучшего узла для&lt;br /&gt;
обработки. Это делается при помощи записи в $shortest_distance&lt;br /&gt;
большого числа, и записи в $best_node нуля. затем происходит цикл&lt;br /&gt;
по всем элементам открытого списка, сравнение каждого значения с&lt;br /&gt;
$shortest_distance до тех пор, пока не обнаружится узел с самым&lt;br /&gt;
коротким суммарным расстоянием. затем этот узел удаляется из&lt;br /&gt;
открытого списка, происходит поиск соседей и они добавляются в&lt;br /&gt;
открытый список. Узлы группируются в «колонку слева», «текущую&lt;br /&gt;
колонку» и «колонку справа», но нам достаточно обсудить только первую группу, чтобы понять их все.&lt;br /&gt;
&lt;br /&gt;
итак, чтобы найти узлы в колонке слева от текущего узла, для&lt;br /&gt;
начала нам надо проверить, что мы не находимся в колонке А, поскольку она уже является самой левой. Как мы видели в calc_distance(), функция ord() превращает букву в её ASCII-код. Если мы&lt;br /&gt;
вычтем 1 из этого кода и преобразуем его обратно в символ, то B превратится в A, T в S и так далее. Поскольку теперь мы знаем имя колонки слева, нам надо проверить, входят ли верхний, средний и нижний&lt;br /&gt;
квадратики из этой колонки в закрытый список, и если не входят –&lt;br /&gt;
внести их в открытый список.&lt;br /&gt;
&lt;br /&gt;
Как и в makemove(), тут мы используем координаты ячейки как&lt;br /&gt;
ключ, а массив, полученный из calc_distance() – как значение.&lt;br /&gt;
обратите внимание: в качестве первого параметра calc_distance()&lt;br /&gt;
выступает путь до старта от текущего узла (то есть число перемещений,&lt;br /&gt;
которое потребовалось, чтобы сюда попасть) плюс 1, так как до нового&lt;br /&gt;
узла надо сделать ещё один шаг.&lt;br /&gt;
&lt;br /&gt;
==== drawgrid() ====&lt;br /&gt;
Функция, которая делает рисование нашей таблички в ncurses ужасным по двум причинам. Во-первых, она использует ncurses, что само&lt;br /&gt;
по себе превращает процесс отладки в ад. Во-вторых, в ней используются на первый взгляд произвольные числа для добавления или вычитания здесь и там, а я это ненавижу. что ж, это мой шанс записать их&lt;br /&gt;
все на бумаге, чтобы никогда больше не забывать.&lt;br /&gt;
&lt;br /&gt;
После использования ncurses_werase() для очистки экрана,&lt;br /&gt;
переменная $gridrows устанавливается равной $MAX_ROW-4, что-бы оставить внизу место для подсказки пользователю. затем мы&lt;br /&gt;
видим два цикла for, которые рисуют сетку. Первый из них рисует&lt;br /&gt;
строки, начиная с 0 и до $gridrows, добавляя 2 каждый раз, чтобы&lt;br /&gt;
оставить место для данных между линиями сетки. Каждая итерация&lt;br /&gt;
цикла использует ncurses_wmove() для перемещения курсора к нужной строке, а затем ncurses_whline() для рисования горизонтальной&lt;br /&gt;
линии. она принимает в качестве параметров экран для рисования,&lt;br /&gt;
символ, которым будет рисовать, и длину линии. Функция ord() снова&lt;br /&gt;
используется для того, чтобы превратить символ в его числовой эквивалент для функции.&lt;br /&gt;
&lt;br /&gt;
Если $i больше 2, то есть если это третья или последующая строка цикла – наступило время выводить данные. Это не настолько случайно, как кажется: первая итерация цикла нарисовала внешнюю&lt;br /&gt;
линию сетки, а вторая – заголовок (тот, в котором находится координатные обозначения), только в третьей строчке мы можем приступить&lt;br /&gt;
к отображению данных. итак, вызовем ncurses_mvwaddstr() для&lt;br /&gt;
вывода номера строки в самом левом столбике, а затем сбросим указатель массива $LETTER_RANGE, чтобы мы могли использовать его&lt;br /&gt;
для выборки элементов из массива $DATA. Вот что делает внутренний цикл for – он перебирает элементы от 8 до $MAX_COL по 4 за&lt;br /&gt;
раз. По сути, это то же самое, что мы делали со строками – мы начинаем с конца второго столбика, чтобы не писать поверх номеров&lt;br /&gt;
строк.&lt;br /&gt;
&lt;br /&gt;
Вызов ncurses_mvwaddstr() кажется очень сложным, но он разбивается на очень простые кусочки: нарисовать в нашей строке и&lt;br /&gt;
колонке, используя половину $i как ключ массива $DATA и текущее&lt;br /&gt;
значение $LETTER_RANGE как ключ ко второму измерению массива.&lt;br /&gt;
Помните, $i увеличивается дважды на каждую строку данных, поэтому&lt;br /&gt;
нам приходится делить её на два. Функция next() перемещает указатель текущего элемента в массиве $LETTER_RANGE на один элемент&lt;br /&gt;
вперёд при каждом вызове, так что в результате мы печатаем&lt;br /&gt;
$DATA[1][“a”], $DATA[1][“b”] и так далее.&lt;br /&gt;
&lt;br /&gt;
После вывода всех строк reset() возвращает указатель&lt;br /&gt;
$LETTER_RANGE обратно к «A», так что мы можем отобразить вертикальные линии. В этот раз счётчик пробегает от 0 до $MAX_COL,&lt;br /&gt;
так что мы можем заодно нарисовать края сетки и линию справа от&lt;br /&gt;
номеров строк. Поскольку в этот раз мы рисуем столбики, функция&lt;br /&gt;
ncurses_wvline() вызывается, чтобы напечатать символ «|» сверху&lt;br /&gt;
вниз. Как и со строками, если значение больше 4 (мы используем 4&lt;br /&gt;
вместо 2, чтобы сделать сетку квадратной), то надо заодно отобразить заголовки столбцов.&lt;br /&gt;
&lt;br /&gt;
В конце концов мы выводим подсказку внизу экрана и вызываем ncurses_wrefresh(), чтобы обновить это всё.&lt;br /&gt;
&lt;br /&gt;
=== ну теперь всё? ===&lt;br /&gt;
я думаю, вы согласитесь, что одолеть всё это было довольно&lt;br /&gt;
сложно. использование ncurses уже непросто, а с применением сложных алгоритмов программа делается совсем мудрёной. но теперь тяжёлая работа закончена, вы может запустить&lt;br /&gt;
программу и получать удовольствие: у вас есть полноценная&lt;br /&gt;
реализация алгоритма А* на php, а я думаю что это ужасно круто.&lt;br /&gt;
Конечно, она не совершенна – посмотрите на врезку «Домашнее&lt;br /&gt;
задание», и у вас найдётся занятие на весь месяц ожидания следую-&lt;br /&gt;
&lt;br /&gt;
{{Врезка|center|&lt;br /&gt;
|Заголовок=ДоМАШнее зАДАние&lt;br /&gt;
|Содержание=Мы с вами только начали разбираться с алгоритмами поиска пути, и вы кое-что можете сделать, чтобы узнать больше. Вот несколько идей, с которых можно начать:&lt;br /&gt;
* В наш код явно требует указания двухсимвольных координат, таких как e8. Если вы введёте что-то вроде e12, то это число не будет воспринято. хороший способ исправить это - использование координат «с двоеточием», таких как e:12, и разделение их на части при помощи функции explode().&lt;br /&gt;
* Для экономии времени в скрипте делается очень небольшая проверка ошибок. Если вы укажете неправильные значения, программа скорее всего просто «упадёт». Попробуйте сделать её устойчивой к таким ошибкам.&lt;br /&gt;
* Содержимое таблицы никогда не сбрасывается, и поэтому после нескольких запусков она замусоривается. В идеале хорошо было бы использовать ещё один оператор sleep() после отображения найденного пути, а затем вызвать resetgrid(). Посмотрите, в конце функции makemove() вы найдёте комментарий, с которого можно начинать.&lt;br /&gt;
* В нашей реализации персонаж не пытается избежать препятствий, и вообще концепция поверхности отсутствует. Вы можете подумать, что её очень сложно добавить, но на самом деле это можно устроить запросто. Достаточно изменить вызов функции calc_distance() в том месте, где мы передаём ей Initial +1, чтобы добавить один шаг перемещения к новому узлу. именно это делает перемещение по всем узлам одинаковым. чтобы учесть рельеф, вам достаточно добавлять тут 2 или 3, если узел содержит холмы или горы и что-нибудь ужасно большое, вроде 9999, этот узел непроходим. После этой небольшой корректировки алгоритм начинает работать очень впечатляюще. например, если вы нарисуете линию посреди сетки и назовёте её сплошной стеной, вы увидите как персонаж начнёт обходить её.&lt;br /&gt;
* Как только вы добавите рельеф, вам может пригодиться следующая оптимизация. Если вы собираетесь добавить узел в открытый список и обнаруживаете, что он там уже есть, то вы можете запустить calc_distance() и сравнить полученные значения. Путь до этого узла, для которого суммарное расстояние окажется меньше, выбирается как более подходящий.&lt;br /&gt;
* После появления препятствий вы обнаружите, что А* начинает забредать в тупики – вы больше не сможете рассматривать закрытый список как окончательный путь от старта до финиша потому, что он будет включать в себя все проверенные варианты. решить эту проблему можно при помощи поля Parent, в котором хранится узел, вводивший этот узел в расчёт. По началу для всех узлов в этом поле должен храниться старт. В этом случае добравшись до финиша вам нужно будет в качестве предыдущего узла взять его Parent, затем – Parent от Parent и так далее, пока не вернётесь обратно к старту – и это и будет тот путь, по которому нужно следовать.&lt;br /&gt;
* Сам по себе код написан так, чтобы его было проще читать, а не для того чтобы он быстрее выполнялся. на самом деле он достаточно медленный. например, стоит ли перерисовывать весь экран каждый раз, когда пользователь нажал на кнопку?&lt;br /&gt;
|Ширина=}}&lt;/div&gt;</summary>
		<author><name>Yaleks</name></author>	</entry>

	</feed>