Под расчётом руки понимаем определение фанов в руке.
Вышеприведённых значений достаточно для однозначного определения всех фанов в руке (в т.ч. если возможны несколько вариантов раскладки руки по комбинациям и фанам).
- Процедура "парсинга" руки, т.е. синтаксического разбора закрытых тайлов в руке и представление всей руки в виде набора комбинаций.
Рука в MCR может быть представлена как в виде "регулярной" руки, т.е. состоящей из 4 сетов (чоу, панг, конг) и одной пары, так и в виде специальных рук. К специальным рукам относятся:
- рука из семи пар;
- тринадцать сирот;
- большие или малые благородные и переплетённые кости.
Сама процедура построена по принципу рекурсии, т.е. если на входе процедуры выясняется, что все закрытые кости в руке уже распределены по комбинациям, то объявляем получившиеся комбинации одним из вариантов устройства руки. Если присутствуют закрытые кости, то берём первую закрытую кость и пытаемся пристроить её в чоу, панг или пару (естественно, что конгов в закрытой части руки быть не может). Если получается, то рекурсивно продолжаем разбор получившейся руки, если не получается - откатываемся назад. В случае, если количество закрытых костей равно 14 (т.е. вся рука закрыта, кроме возможно выигрышной кости), то дополнительно проверяем на специальную руку, а если количество закрытых костей больше или равно 9 проверяем на наличие в руке переплетённого ряда (который описывается, как некие специальные типы комбинаций).
На выходе процедуры получаем в случае валидной руки массив вариантов представления руки в виде набора комбинаций, включая специальные (в большистве случаев это будет единственный вариант), либо ответ о невозможности такого представления.
Нетривиальные моменты процедуры парсинга:
- выигрышная кость, даже пришедшая с дискарда тоже участвует в списке костей для распределения по комбинациям (очевидно, т.к. мы при взятии с дискарда в чоу или панг не объявляем эту комбинацию, а сразу же говорим "маджонг")
- при дальнейшем анализе руки необходимо различать открытые и закрытые панги и чоу
- необходимо учитывать, в какую именно комбинацию пошла выигрышная кость и в зависимости от способа взятия (со стены или дискарда) ставить для этой комбинации соответствующий признак (закрытый или открытый).
- при парсинге закрытых костей на сеты сперва делается попытка сделать чоу, потом пары и панги. При попадании ранга выигрышной кости в комбинацию, его тип выставляется по типу выигрышной кости (т.е. если кость со стены, то сет закрытый, а если с дискарда - то открытый). Таким образом, если выигрыщная кость с дискарда и попадает как в чоу, так и в панг, то первый вариант разбора руки будет открытое чоу-закрытый панг, а уж второй вариант закрытое чоу-открытый панг. Т.к. закрытый панг всегда даёт больше очков, чем открытый, то первый вариант предпочтительней по очкам, и именно он и будет разбираться по фанам, а второй просто не попадёт на разбор, т.к. по алгоритму сравнения вариантов сравниваются только ранги сетов, а не их закрытость.
- ещё одно нетривиальное место связано с фаном "открытая рука". Мы определяем этот фан по признаку "все сеты открыты" и выигрышная кость с дискарда не входит в фаны. Поскольку первым проверяется чоу, то не может быть ситуации, когда кость подходит и в чоу и в пару, но выйдет вариант с выходом в пару (первым будет вариант с чоу), а попасть кость в пару и панг не может, т.к. одинаковых костей всего четыре.
- Определение количества ожиданий.
Для этого используется вышеприведённая процедура парсинга. Просто изымаем выигрышную кость и, добавляя в цикле все возможные ранги костей в качестве выигрышной проверяем, получилась ли валидная рука, т.е. рука, состоящая из комбинаций, в т.ч. специальных.
Замечание. Количество ожиданий считается без учёта выхода костей, т.е. перебираем именно ВСЕ ранги, даже если все кости данного ранга уже вышли в дискард или находятся в руке (пятые кости).
На выходе процедуры получаем количество ожиданий.
- Определение фанов.
Процедура строится с учётом "совместимости" фанов. Для определения совместимости использовалась таблица значений логического типа (т.е. совместим - не совместим). Размерность таблицы - половина от 80*80 (т.е. мы не проверяем совместимость фанов самих с собой и таблица симметрична относительно диагонали, т.ч. можем использовать только половину значений).
Для каждого фана написана специальная функция, которая для заданной руки (уже представленной в виде набора комбинаций) выдаёт на выходе значение, присутствует ли данный фан в руке (в общем случае - количество таких фанов).
Сам алгоритм определения фанов в руке построен так:
- перебираем фаны от 1 до 81 (т.е. начиная от старшего).
- если данные фан может быть в руке, вызываем соответствующую функцию проверки.
- если данный фан нашёлся, записываем его наличие в руке и для всех нижестоящих фанов вычёркиваем несовместимые с ним (чтобы в дальнейшем не проверять).
На выходе процедуры получаем массив количества фанов в руке, из которого путём суммирования легко вычислить количество очков в руке (а также количество очков не включая цветы/сезоны, что нужно для определения выигрыша)
Некоторые замечания по алгоритму:
- некоторые фаны применимы только для регулярных рук, т.е. рук со структурой "четыре сета + пара". Для того, чтобы не гонять процедуры вхолостую используется дополнительный признак фана "применим только для регулярной руки". В случае, если рука нерегулярная, на данные фаны проверять не будем априори.
- особый случай проверки на чоу-3 фаны (№№ 23, 28, 30, 39, 41, 51) и чоу-2 фаны (№№ 69, 70, 71, 72). Эти фаны так связаны друг с другом, что проще сразу провести проверку руки на все. При наличии чоу-3 фана и четырёх чоу в руке нужно пометить номер свободного чоу - если в руке присутствует чоу-2 фан, он может быть построен только на этом чоу.
- фаны ожиданий (№№ 77, 78, 79) определяются сначала независимо друг от друга и независимо от того, куда попала выигрышная кость, т.е. просто по рангу выигрышной кости. Затем происходит проверка - в случае наличия нескольких фанов ожиданий, они все аннулируются.
- фан "цыплячья рука" проверяется вне очереди после того, как все основные фаны уже проверены.