FORTH-программирование?
Ну и всюду, даже на PHP, стараюсь пользоваться Thinking Forth
/Balancer/
а можно и "выше", когда мы уже пишем "проблемно-ориентированный язык", наиболее выразительно описывающий задачу
/Я/
Итак, "Игра в 15".
Кажется, куда уж тупее (см. Дракон -
http://fforum.winglion.ru/viewtopic.php?p=39567#p39567), но и здесь кУульНЫй фоРТеР может получить удовольствие.
Начнем с блок-схемы.
Для подобных игр она завсегда одна -
http://www.gudleifr.h1.ru/52.html.
И очень похожа на наш Цикл Управления: основной цикл - чтение ПОТОКА, возврат по ПРОВЕРКЕ ввода - BADWORD, возврат по АВАРИИ - там же, где "нормальный" FORTH проверяет исчерпание стека.
Значит, мы строим FORTH-машину для игры в "15", надеясь получить выгоду от унификации ее с "обычной".
Пробежимся по типам FORTH-данных, чтобы потом больше к ним не возвращаться. Этакое определение размеров бедствия:
ПОТОК команд игрока. Возможно, видимо, только три варианта (тыкаем ли мы мышкой или бьем по клавишам): "координаты клетки", "стрелки" и "номер шашки".
ЗНАЧЕНИЕ - ходящая шашка (и координаты, и номер). Здесь же имеет смысл хранить текущее положение дырки. (И все, что нам еще может понадобиться для описания игровой ситуации).
СТЕК - казалось бы, в таких играх он нафиг не нужен... Но... Как раз в "15" он оказывается полезен. Ведь я могу подвинуть разом не одну шашку, а целый ряд. Вот и загоним все шашки, которые надо подвинуть в СТЕК. (Конечно, это не будет стек в прямом смысле слова. Нам достаточно всего трех "ячеек" и обязательно нужен счетчик).
СЛОВАРЬ - на первый взгляд он кажется фиксированным. Максимум, по команде для 15 шашек, для 4 стрелок и 16 клеток.
Однако, можно заметить, что допустимость этих команд меняется в зависимости от игровой позиции, точнее, от положения дырки. Значит, перемещая дырку, придется "перекомпилировать".
Теперь, пройдемся по Блок-Схеме.
ВВОД - понятно, самая "машинно-зависимая" часть программы (ОК + СИМВОЛ.EXPECT).
ПРОВЕРКА - проверка команды на допустимость (СИМВОЛ.FIND).
РАСЧЕТ - основная часть работы (ВЫПОЛНИТЬ).
АВАРИЯ - единственное, что имеет смысл проверять, не выиграна уже ли игра.
НАЧАЛО - тоже очевидно. Тут надо перемешать шашки случайным образом, не забывая, что должна быть соблюдена четность перестановок, иначе задача станет неразрешимой проще всего выполнить достаточно длинную случайную комбинацию команд-стрелок.
Теперь будем думать о реализации.
ОК должна выдавать три вида сообщений: "ситуация на доске", "неправильный ход" и "непонятно".
Но, т.к. я пока не знаю, на чем буду писать, придется оставить.
В первом случае необходим массив, реализующий функцию клетка-шашка.
СИМВОЛ - три словаря - для клеток, шашек и стрелок (в зависимости от реализации, наверняка останется один. Например, при мышином клике очевидно будет получена клетка).
Задача СИМВОЛ - проверить на допустимость и вернуть наиболее полный вариант (клетка, шашка, стрелка, кол-во стрелок), потом обрежем, пока не будем заморачиваться.
Очевидно наличие двух массивов, реализующих функции шашка-клетка и клетка-шашка (которые, тоже надо будет постоянно "перекомпилировать").
Кроме того надо до трех матриц допустимости.
Пусть дырка расположена в клетке bbbb (их же всего 16).
Тогда матрица допустимости стрелок будет представлять массив четырех бит, соответствующих истиности попадания bbbb под шаблоны 00bb, 11bb, bb00, bb11.
А матрица допустимости шашек - единицы в соответствующем столбце и строке.
Матрицу допустимости шашек строить муторно, проще уже при вводе для шашки определить клетку и проверить последнюю.
Перевести и стрелки в клетки? Удобно, но матрицу стрелок все равно иметь надо, чтобы не выйти за пределы доски.
Итак, можно подвести итоги. Процедура СИМВОЛ должна уметь:
проверять на допустимость стрелки и клетки;
преобразовывать номер шашки и стрелки в координаты клетки.
Возвращать должна клетку.
Рассмотрим процедуру ВЫПОЛНИТЬ.
Она должна выяснить что и куда подвинуть, перекомпилировать массивы и проверить, не случилась ли победа.
В случае сдвига одной шашки все просто:
КЛЕТКА(ПУСТО) := ШАШКА(КЛЕТКА)
КЛЕТКА := ПУСТО
Однако, у нас может быть сдвиг до 3-х шашек:
КЛЕТКА(ДЫРКА) := ШАШКА(КЛЕТКА1)
КЛЕТКА(КЛЕТКА1) := ШАШКА(КЛЕТКА2)
КЛЕТКА(КЛЕТКА2) := ШАШКА(КЛЕТКА3)
КЛЕТКА3 := ДЫРКА
Т.е. имеет смысл разделить ХОД на ПОЛУХОД (перемещение шашки) и ПОСЛЕХОД (перемещение дырки).
Как получить КЛЕТКИ1-3 из просто клетки?
Очевидно, хорошо бы иметь "обратную стрелку", единичное приращение координат от дырки к клетке (видимо, надо добавить ее расчет в СИМВОЛ - как значение, возвращаемое словом-клеткой - или тупо считать здесь разности).
ПОЛУХОД очевидно автоматически компилирует таблицу клетка-шашка, но надо добавить компиляцию и шашка-клетка (если она используется в СИМВОЛ), и установку бита соответствия клетки и шашки для проверки победы (не гонять же на каждом ходу цикл проверки).
ПОСЛЕХОД аналогично должен сбросить бит победы для клетки, перекомпилировать таблицы допустимости стрелок и клеток, а так же - "обратных стрелок".
Очевидно, до этого места все имело смысл делать в уме.
Теперь же можно начать писать.
Кстати, можно писать и не на FORTH. Наш F-язык свою функцию уже выполнил.
При написании этого всего на "обычном FORTH", вы кстати обратите внимание, что такое сложное ЗНАЧЕНИЕ породит большое количество переменных, а обилие таблиц, тем более битовых - большое количество стековых перетасовок. Кодовая оптимизация нашей P-машине не повредит.