Данное число показывает минимальное число замен, необходимое, чтобы рука стала темпай (один тайл до победы).
Так как в риичи-маджонге есть три кардинально отличающихся типа руки (обычный, все пары и кокуши), то и алгоритмов подсчёта надо реализовывать три.
читать дальшеДля начала можно рассмотреть вторые два как более простые. Шантен до читойцу считается просто. В темпае в руке 6 пар и один левый тайл. Соответственно, надо посчитать число пар и вычесть это из шести. Повторяющиеся пары не считаются.
ShantenChiitoi = 6 - (Paircount);
Шантен до Кокуши считается аналогично, только считаются уже отдельные тайлы. Все терминалы, все ветра, все драконы.
Максимальный шантен: 13. За каждый нужный неповторяющийся тайл отнимаем единицу. И один раз отнимаем за повтор нужного тайла (нужна одна пара). Все остальные повторы не уменьшают число шантен.
Самый сложный алгоритм, конечно, относится к обычной руке, так как там могут быть различные и пересекающиеся формы. Суть алгоритма всё же не такая и мудрёная: вырезать все пары, сеты и таацу (незаконченные формы) всеми возможными способами, — и тем самым найти минимальное значение числа шантен. Максимальное значение шантен для обычной руки: 8. То есть как бы имеем зайчатки для 4 сетов и одной пары, но только по одному тайлу от каждого (итого 13 - 5 = 8). Соответственно, пара будет уменьшать число шантен на единицу (как пара или как незаконченный ankou в дальнейшем), два (изолированных от остальных) соседних тайла будут уменьшать число шантен на единицу, образуя таацу, законченный сет же (3 одинаковых или 3 последовательных тайла) уменьшает число шантен на 2, так как к изолированному тайлу пришло два подходящих тайла. Итого, число шантен для текущего разложения считается: Temp = 8 - Mentsu * 2 - Kouho - Toitsu;
Mentsu в данном случае обозначает число сетов (чи, пон).
Toitsu — голова, пара тайлов, или 0 или 1.
Kouhou — все таацу и остальные пары (кантян, пентян, рянмен, пара).
Выделяя из руки форму, убираем тайлы в неё входящие, чтобы можно было посмотреть, что же осталось.
Например: имеем часть руки 





.
Проходим первый раз без выделения пары, начиная с 2х:
1. Убирается сет 

;
2. Убирается сет 

;
Остался в руке лишний тайл:
. Посчитали Mentsu = 2. Если остальные сеты открыты (Mentsu += 2), то это темпай. (8 - 2 * 4) = 0.
Проходим в другой раз, но сначала убирая пару:
1. Убирается пара 
.
2. Убирается сет 

.
3. Убирается кантян: 
.
В руке ничего не осталось. Mentsu = 1. Toitsu = 1. Kouho = 1. Это тоже темпай: (8 - 3*2 - 1 - 1) = 0.
Ещё раз проходим, начиная с 4х:
1. Убирается сет 

;
2. Убирается пара: 
;
3. Убирается рянмен: 
;
В руке ничего не осталось. Mentsu = 1. Toitsu = 1. Kouho = 1. Расчёт такой же: (8 - 3*2 - 1 - 1) = 0.
И так проходится по всем возможным вариантам! Правда, этот пример не очень удачный, тут нет формы с другим числом шантен, но суть должна быть понятна.
Для удобства расчётов руку удобно представлять в виде массива, где индекс обозначает конкретный тайл, а содержимое — количество оных тайлов в руке.
uint8_t Tehai[38];
Всего тайлов 34, но для удобства хорошо бы отделить все неблагородные тайлы разных мастей друг от друга и выровнять относительно какого-либо числа, например:
Для примера выше массив будет инициализирован так:
Теперь остаток от деления на 10 будет давать номер тайла в данной масти. И алгоритмы нахождения последовательностей будут проще из-за того, что все масти в массиве расположены симметрично и разделены нулями.
Чтобы полностью обойти все возможные варианты разбиения руки надо использовать рекурсивный алгоритм. Перед запуском назначаем руке шантен 8, число всех сетов, пар и форм 0. Когда вырезаем форму, убираем используемые тайлы и увеличиваем счётчик форм, вызываем ту же функцию, пусть она ищет дальше. Когда ничего полезного не останется, считаем по формуле шантен и сравниваем с записанным где-то. Если получился меньше, то обновим его. По выходе из функции возвращаем тайлы на место, уменьшаем счётчик форм (чтоб потом попробовать тайлы скомбинировать по-иному) и идём дальше.
Так же, не стоит забывать, что сетов нам надо всего четыре, потому лишние формы нам с шантеном не помогут, их надо пропускать.
Очерёдность вырезания тоже понятна: сначала сеты, потом из того, что осталось, вычленяем незавершённые формы. Единственное исключение: голову вырезать пробуем первой (она всё же отличается от сетов).
Что здорово, нет ограничения на число тайлов. Если есть открытые сеты, то просто запишем в Mentsu их количество.
Чтобы получить просто число шантен, надо посчитать его по каждому из алгоритмов и выбрать наименьшее.
Вот кусок кода на C#, который считает шантен по данному алгоритму:
А имея алгоритм расчёта шантен, можно уже посчитать уке-ире и иные параметры руки. То есть круто.
Данный алгоритм подсмотрен на упоминавшемся мной ранее сайте: 麻雀C言語プログラム集 (Collection of C language mahjong program).
@темы:
мии,
программизмы,
маджонг
Это не очень внятное описание алгоритма программы, не более того. Человекам от этого пользы немного =D
Это не для пользования в уме, это можно использовать в связанных программах: анализаторе и просмотрщике прошедших игр (на том же tenhou.net), ботах для маджонга (определять, что сбрасывать и можно ли объявлять риичи или победу), в своём серверочке для онлайн-игр, в каких-нибудь упражнениях и задачках и т.д. Всего лишь один из нужных алгоритмов =)
Человек шантен определяет примерно так же, но он сразу видит лишние тайлы (особенно если их мало), программе же это неочевидно и приходится перебирать всё подряд =)
Поток — это когда заходит? Чаще, чем это должно быть. Если это так, то это можно понаблюдать...