Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   Алгоритм подсчета покерной руки (http://forum.oszone.net/showthread.php?t=48201)

Vadikan 18-04-2005 11:05 316741

Алгоритм подсчета покерной руки
 
Вложений: 3
Доброго времени суток!

Вот добрался я и до этого форума :) Сразу скажу, что программист я никакой, и весь мой опыт программирования сводится к нескольким заданиям в рамках курса VB.NET в универе. Курс предназначен для людей, не имеющих опыта программирования, и знакомит с элементарными понятиями. Поскольку курс преподается на англ. языке, я знаю только английскую терминологию. Предположу, что array - массив, a data structure - структура данных.

Финальный проект курса формулируется так. Надо сгенерировать колоду карт на 52 листа. Сначала нужно создать структуру данных, у которой два члена: face (номинал карты) и suit (масть). Затем нужно сформировать массив, который и будет наполнен 52 картами. Затем нужно сгенерировать 5 случайных уникальных номеров от 0 до 51 (или от 1 до 52) и на основе этих номеров сформировать покерную руку. Ну и самое главное - определить покерную комбиниацию, которая получится в рез-те. Покерные комбинации такие

Flush (все одной масти)
Четверка (четыре карды одного номинала)
Full house (3+2)
Тройка
Две пары
Пара
Ничего

Как видите, вариант упрощенный - straight (стрит) подсчитывать не нужно (курс-то для начинающих ;-) Ну и вывести надо 5 карт и название комбинации. Никаких картинок карточной колоды, все в текстовом виде.

Затрудненения у меня вызвали пять уникальных случайных кард и алгоритм подсчета покерной руки. С пятью уникальными картами я вроде разобрался относительно легко.

Прикрепленный файл

Если я что-то упустил, то буду признателен, если вы укажете на ошибки.

С подсчетом покерных комбинаций было сложнее. В конечном итоге, я его определил, но хотелось бы услышать и ваше мнение. Возможно, я что-то где-то упустил, или можно все сильно упростить.

Основная идея: в руке не может быть более двух различных совпадающих номиналов карт. Т.е. если есть два туза и два короля, то пятой карте пары уже точно не будет. Значит надо подсчитать сколько раз совпали два различных номинала.

Прикрепленный файл

Ну а дальше уже детали. По порядку проверяем следующие условия:
  1. Если все карты одной масти, то flush (масти берутся из массива на основе 5 уникальных чисел, сгенерированных ранее)
  2. Если первый счетчик равен 4, то у нас четверка.
  3. Если счетчики 3+2 или 2+3, то у нас full house.
  4. Если первый счетчик равен 3, то тройка (причем, второй счетчик может быть равен 3 только в случае 2+3).
  5. Если счетчики 2+2, то две пары.
  6. Если первый счетчик равен 2, то пара (причем второй счетчик может быть равен 2 только в случае 3+2 или 2+2)
  7. Ну а во всех остальных случаях нету даже пары.

Вот так я себе это представил, и вроде даже работает. Буду признателен, если вы проверите алгоритм на наличие ошибок или предложите более оптимальный вариант. Может где-то есть лишние шаги, которые можно убрать, упростив код.

Спасибо за внимание.

P.S. Полный код проекта прикреплен.

hasherfrog 18-04-2005 11:40 316755

Немного путанно или я старый уже, мозги усохли :] Можно уточнить условия?

>> Затем нужно сгенерировать 5 случайных уникальных номеров от 0 до 51 (или от 1 до 52) и на основе этих номеров сформировать покерную руку

Что такое "сформировать покерную руку"? Нужно просто рандомом (следя за неповторяющимися номерами) выбрать 5 чисел от 1 до 52? И всё? Так? Т.е. "сдать пять карт"?

>> Ну и самое главное - определить покерную комбиниацию, которая получится в рез-те.

Опять непонятно. Честно, я не очень понимаю. Нужно просто сказать, какая комбинация карт получилась из сданных пяти? Так? Т.е. никаких подсчётов вероятностей, никаких"взять ещё три" и "сколько сдать карт, чтобы получить флэш" и т.д.?

hasherfrog 18-04-2005 12:03 316766

Тасование колоды:
Цитата:

Алгоритм #4.
Код:

    1. ячейки вектора инициализируются значениями своих индексов
    2. R{ight} ← N {"текущая" длина вектора}
    3. генерируется случайное число K из интервала 1..R
    4. обмен содержимым между K-й и R-й ячейками
    5. R ← R-1
    6. if R = 1 then Exit
    7. переход к шагу 3

Приведенный механизм известен под именем алгоритма "тасования колоды". Его легко переписать, используя уже знакомые нам процедуры, приходится лишь заменить интервал 0..N-1 на 1..N.
Код:

type
  index = 1..N; {N>1}
  massive1 = array [index] of index;

procedure RandomN(var Mas: massive1);
var i, K: index;
begin
  for i := 1 to N do Mas[i] := i;
  Randomize;
  for i := N downto 2 do begin
    K := Random(i) + 1;
    Swap(Mas[K], Mas[i]);
  end;
end;


Соответственно, получение 5 карт можно дать так:
1. Инициализируем массив, от 1 до 52, Перемешиваем массив - RandomN()
2. Берём его первые 5 элементов.

hasherfrog 18-04-2005 12:10 316768

Масть - номер карты (0-51) mod 4 (остаток от деления) 0 - пики... 3 - бубны
Номинал - номер карты (0-51) / 4 (целочисленное деление) 0 - двойка ... 12 - туз

hasherfrog 18-04-2005 12:29 316773

Проверку на флеш делаем очень быструю, без массивов.
Флеш - это когда K[0]%4 == k[1]%4 == k[2]%4 == k[3]%4 == k[4]%4
% - остаток от деления
== - равно
Сорри за мой С :-Р

Поверки по номиналам даём с использованием промежуточного массива.
1. Делаем пустой масив номиналов S размером от 0(двойка) до 12 (туз), итого 13 элементов.
2. Инициализируем его нулями - это счётчики встреч
3. Для каждой карты K из массива k[0..4](пять карт) увеличиваем счётчик номинала.

для i от 0 до 4
счётчик[K[i]/4] ++
/ - целочисленое деление
++ - увеличение на 1.

Далее сортируем массив S (счётчиков номинала по возрастанию) и проверяем результат:
1. если первый элемент стал 4 - "каре".
2. если первый элемент стал 3:
2а. если второй элемент равен 2 - "полный дом".
2б. если второй элемент равен 0 или 1 - "тройка"
3. если первый елемент равен 2:
3а. если второй элемент равен 2 - "две пары".
3б. если второй элемент равен 0 или 1 - "пара"

Комбинации "туз" и "стрит-флеш" тоже легко вычисляются.

Осталось только расписать "сортируем массив счётчиков" - это простая пузырьковая сортировка, например. Или чего-нибудь продвинутое :]

hasherfrog 18-04-2005 12:37 316775

Сортировка (два-три метода)
http://www.relib.com/articles/article.asp?id=197

Vadikan 19-04-2005 00:46 316967

hasherfrog
LOL
Цитата:

Т.е. "сдать пять карт"?
Да, пять неповторяющихся карт.
Цитата:

Нужно просто сказать, какая комбинация карт получилась из сданных пяти?
Ок, можно и так сказать. Не вижу принципиальной разницы между этим и тем, что я сказал :) Наверное, твой вариант проще :) Спасибо за ответы.

Насчет тасования колоды я понял идею, но не слишком понял реализацию. Что делает следующий код?
Код:

for i := N downto 2 do begin
    K := Random(i) + 1;
    Swap(Mas[K], Mas[i]);

Конкретнее, downto2 do begin вообще непонятно, сорри.

А для подсчета совпадений номиналов твой вариант действительно проще. Общая идея, как я вижу, такая же: отталкиваемся от факта, что у нас не более двух различных совпадающих номиналов карт. Однако исполнение намного проще. Мне как-то в голову не пришло использовать целочисленое деление :confused:
Я сделал так. Допустим, у меня уже есть массив Hand(4), 5 элементов которого являются случайными числами от 0 до 51. Теперь создается массив HandFaces(12) для номиналов
Код:

  Dim HandFaces(12) As Integer
        For i = 0 To 12
            HandFaces(i) = 0
        Next

и все элементы наполняются нулями. Затем для каждого элемента из массива Hand выполняется целочисленное деление, получившееся число становится индексом массива HandFaces и значение данного элемента увеличивается на единицу.
Код:

        For i = 0 To 4
            HandFaces(Hand(i) \ 4) += 1
        Next

Я правильно понял? Затем массив HandFaces сортируется. Я, честно говоря, не знаю как отсортировать в обратном порядке. Поэтому мне нужно смотреть на последний и предпоследний элементы. Правильно?


hasherfrog 19-04-2005 10:00 317051

>> Правильно?
Ну да :]

Кстати... Я только сейчас увидел, что дал кусок про тасовку карт на паскале... :-P
LOL, даже ROTFLMAO

По-русски :] это будет так:
Код:

Sub shuffle()
'массив карт
Dim L(0 To 51) As Integer

'инициализация колоды
For I = 0 To 51 Step 1
L(I) = I
Next I

'печать для екселя - надо же что-то узреть :)
For I = 0 To 51 Step 1
Worksheets(1).Cells(I + 1, 1).Value = L(I)
Next I

'тасовка
For I = 51 To 0 Step -1
R = Int(52 * Rnd)
If R <> I Then
T = L(R): L(R) = L(I): L(I) = T
End If
Next I

'снова печать - результат тасования
For I = 0 To 51 Step 1
Worksheets(1).Cells(I + 1, 2).Value = L(I)
Next I

End Sub


Vadikan 19-04-2005 10:32 317069

hasherfrog
Да я уже разобрался как отсортировать массив в обратном порядке. На самом деле мне это не нужно, без разницы какой элемент использовать для подсчета сопвадений.
Кстати, хорошая идея с
Цитата:

Масть - номер карты (0-51) mod 4 (остаток от деления) 0 - пики... 3 - бубны
Номинал - номер карты (0-51) / 4 (целочисленное деление) 0 - двойка ... 12 - туз

Я "подсократил" колоду сразу. В задании сказано создать структуру, состоящую из мастей и номиналов. Вот я "заполнял" мастями все 52 карты. На самом деле достаточно 13 по числу номиналов, и четыре масти обозначить. А потом уже из них по твоему методу все брать.

Спасибо за пояснения по тасовке колоды. Я понял теперь :) Ты с VB на Си, a потом на паскаль прыгаешь, совсем меня запутал. Я же сказал, что я начинающий ;) На самом деле, я вижу преимущества такого метода вообще, т.к. если надо сдать две руки, то берутся просто следующие пять элементов массива. В моем же случае с одной единственной рукой я, пожалуй, оставлю свой код. Нет нужды в создании массива на 52 элемента, если нужно всего 5. Логично?

Признателен за ответы :up:


hasherfrog 19-04-2005 11:38 317090

У-ё.
Неправильно я расписал тасовку. Так тужился, вспоминая васик... :] Переупростил код и потерял смысл использования -1. Надо так:
Код:

'тасовка
For I = 51 To 1 Step -1
R = Int((I + 1) * Rnd)
If R <> I Then
T = L(R): L(R) = L(I): L(I) = T
End If
Next I

>> Я же сказал, что я начинающий
Стоит только начать... ;]
Удачи при сдаче.

Vadikan 21-04-2005 04:21 317634

hasherfrog
В общем, преподаватель сказал, что все очень хорошо и добавлять ничего не надо. Конечно, оговорился, что при финальной проверке может что-то и всплывет :)

Ладно, я тут подумал как подсчитывать straight на основе массива, который содержит счетчики номиналов. Пусть он будет массив S. Не сортируя его я могу задать поиск первого элемента, содержащего единицу
i=Array.IndexOf(S,1)
a затем рассматривать следующие варианты
i<0
элементов содержащих единицу нет, значит неповторяющихся номиналов нет. Это возможно только в случае 3+2 - значит Full House
i=0
Самый первый элемент массива имеет значение 1. Значит у нас в руке один Ace. Проверяем на наличие straight
Если А2345, что выражается как S(0)=S(1)=S(2)=S(3)=S(4) или 10JQKA, что выражается как S(0)=S(9)=S(10)=S(11)=S(12), то у нас straght, в противном случае сортируем массив и проверяем совпадение номиналов (на full house уже проверять не надо).
i<=8
Проверяем на наличие straight снова таким образом. Если S(i)=S(i+1)=S(i+2)=S(i+3)=S(i+4), то у нас straight. В противном случае сортируем массив и проверяем совпадение номиналов.
i>8
В этом случае straight уже быть не может, т.к. 9й элемент массива - десятка. Просто сортируем массив и проверяем совпадение номиналов.

Единственное, что три раза один и тот же код сортировки и сравнения номиналов вставлять не хочется, надо как-то это иначе оформить. Но идея такая.

hasherfrog 21-04-2005 09:45 317695

Vadikan
Эммм. Я бы исходил из того, что стрит (правильно я интерпретирую straight?) - это пять _разных(1) по номиналу, но _последовательных(2) карт.
1. Т.о. предварительную проверку на стрит можно делать уже после определения всех остальных комбинаций. Стрит получится, только если нет даже пары. Ведь все карты должны быть _разные по номиналу. Единственное, что нельзя учитывать - это флеш. Поэтому проверку на флеш стоит делать уже _после проверки на стрит (и до проверки на стрит-флеш aka флеш-рояль, емнип).
2. "Последовательность" можно определить двумя способами.
2а. Сданные карты должны дать в массиве счётчиков единицы в пяти последовательных ячейках (до сортировки по номиналу - то есть проверку на стрит надо делать самой-самой первой). Проверка такая: для i=0 to (12 - 5) если S[i]==S[i+1]==S[i+2]==S[i+3]==S[i+4] (и, кстати, == 1), то стрит.
2б. Можно опираться на номинал. Взять промежуточный массив из 5 элементов, заполнить его номиналами карт (K/4). Отсортировать по увеличению. И проверить, если будет T[0]==T[1]+1==T[2]+2==T[3]+3==T[4]+4, то стрит.

Единственное, что я не помню... Туз-король-дама-валет-десять - это стрит. А вот считается ли туз-двойка-тройка-4-5 за стрит??? Ентих покеров, афаик, штук десять разновидностей. В какой ваш перпод играет???

P.S. Ну а стрит-флеш, это когда есть стрит и есть флеш :]

OOOPS. Заметил несуразность. Надо делать проверку до сортировки. И тут же - надо делать проверку после сортировки :] Выходит так, что придётся делать не сильно оптимизированную программу. Либо немного запутанную по логике.

hasherfrog 21-04-2005 09:53 317697

Т.е. вариант в п. 2а. подходит без учёта п 1. - заполняем массив номиналов, прогоняем 2а, сортируем, дальше проверяем остальные комбинации.

Либо заполняем массив номиналов, сортируем, проверяем остальные комбинации, затем, если ничего нет, прогоняем 2б.

Vadikan 21-04-2005 09:57 317699

hasherfrog
Цитата:

Надо делать проверку до сортировки. И тут же - надо делать проверку после сортировки :]
Вот-вот :)
По пункту 2а. Да вроде я таки делаю.
Цитата:

(и, кстати, == 1)
ну я же сказал, что ищу первый элемент, который равен единице, с ним же и остальные сравниваю.
По пункту 2б. Еще один промежуточный массив не очень хочется делать. Это уже третий получается...
Цитата:

Туз-король-дама-валет-десять - это стрит. А вот считается ли туз-двойка-тройка-4-5 за стрит???
Я вроде учел оба варианта :) Для этого i=0 выделено в отдельный пункт.

hasherfrog 21-04-2005 15:28 317824

Vadikan
Спешил я, невнимательно прочёл :] У тебя же всё и расписано по полочкам, LOL.
Меня сбило с толку IndexOf, я сразу полез делать своё самостийное решение :]
Таки да, всё правильно, так и делай.

Vadikan 22-04-2005 03:47 318044

hasherfrog
Да, мне тоже показалось, что ты не столько мое решение проверял, сколько свое продвигал ;) Я уже сделал: алгоритм сравнения повторющихся номиналов запихал в subroutine, и все работает как надо. Со straight-flush я не стал заморачиваться, и так уже сделано больше, чем требовалось в проекте. Бонусных очков все равно не предусмотрено...

Vadikan 02-05-2005 12:00 320941

Зашел сейчас на страничку курса почитать письма сокурсников. В день сдачи проекта сразу несколько человек писали письма с вопросами о том, как им оценить покерную руку. Учитывая то, что как сгенерировать колоду фактически об'яснялось в лекциях, то непонятно чем люди занимались раньше :) Но речь не об этом. Преподаватель ответил сразу всем студентам, а не только тем, кто задавал вопрос. Он об'яснил, что надо считать совпадения номиналов, и сказал, что обычно студенты пишут очень болшие блоки IF/ELSE (бедные преподы потом в них разбираются ;-), но поскольку для вычисления "Пары" понадобится проверка десяти условий, то проблему подсчета совпадений номиналов он бы решил так:
Код:

For i = 0 to 4
 For j = 0 to 4
  If hand(i).face = hand(j).face AndAlso i <> j
    matches += 1
  End If
 Next j
Next i

hand(i).face - берется из структуры данных (колоды) созданной раннее. Весьма компактно, и мне такое в голову не пришло. Тем не менее решение с массивом позволяет также просчитать и straight. Но по условиям проекта straight считать не надо было...

aESThete 03-05-2005 10:15 321137

Vadikan
имхо этот кусок кода определит только то, что на руке есть что-то "не меньше пары", а вот что именно... тут подумать надо
а с преподом согласен, циклы всегда лучше/универсальнее (вдруг кому-то в голову придет сделать 6-карточный покер :))

Vadikan 03-05-2005 10:37 321140

aESThete
Мда... я вот сейчас посмотрел внимательнее... Не удивительно, что мне такое в голову не пришло :) Как ни крути, но придется работать с двумя числами, представляющимии совпадения номиналов. Я-то изначально два счетчика делал (второй прикрепленный файл), хотя там наверное можно было упростить как-то.

aESThete 03-05-2005 12:18 321175

Vadikan
счетчиков д.б. не два, а массив счетчиков (можно плавающий, можно по кол-ву номиналов...)
тогда достигаем нужной гибкости:
For i = 0 to 4
For j = 0 to 4
If hand(i).face = hand(j).face And i <> j then
matches(face) += 1
End If
Next j
Next i
соответственно затем:
For i = 0 to maxfaces
if matches(i) = 2
pairs += 1
elseif matches(i) = 3
threes += 1
elseif matches(i) = 4
kare += 1
endif
Next i
это и красиво, и будет работать при любом кол-ве карт в руке, т.е. покажет все пары, тройки и каре... :)

Vadikan 04-05-2005 04:08 321428

Цитата:

покажет все пары, тройки и каре...
A как насчет полного дома и двух пар? А, понял...

Спасибо за идею. Все равно, слишком много переменных вводится, тогда уж лучше массив...

aESThete 04-05-2005 10:37 321500

Цитата:

Все равно, слишком много переменных вводится, тогда уж лучше массив...
ну, для четырех мастей только три комбинации (пара, тройка, каре), если не считать покера (каре + джокер)
вводить массив не вижу смысла...
ок, рассмотрим гипотетический случай с MaxSuit мастями, MaxFaces количеством карт в каждой масти и MaxCardsOnHand картами в руках игрока, тогда
Цитата:

Цитата код
' упс!.. не заметил в первый раз:
' не стоит проверять по нескольку раз (дубляжи получаются)
For i = 0 to MaxCardsOnHand-1 ' не сравниваем последнюю (а с кем? :))
For j = i+1 to MaxCardsOnHand ' не сравниваем саму с собой и с проверенными ранее (вот они дубляжи!)
If hand(i).face = hand(j).face then ' And i <> j - это условие теперь лишнее
matches(face) += 1
End If
Next j
Next i
' время собирать камни
For i = 0 to MaxFaces
combinations(matches(i)) += 1 ' тут вроде понятно: combinations(2) - количество пар, combinations(3) - количество троек...
Next i
' показываем
For i = 2 to MaxSuits ' "комбинации" младше пары нас не интересуют, да их и нету
if combinations(i) > 0 then
? i;"-к = ";combinations(i)
endif
Next i
' дальше уже смотрим спецкомбинации:
if combinations(2) > 0 and combinations(3) > 0 then
? "фулхаус"
endif
' и т.д.

вроде ниче так получилось :)
но это только одинаковые значения карт.
опять же можно ввести джокеров и даже смотреть их старшинство (b.e. "пара дам с красным джокером" бьет "пару дам с синим")
стриты, флешрояли и иже с ними рассмотрим в следующей главе :)

Vadikan 17-05-2005 09:46 324843

aESThete
Сорри, я что-то не получил уведомления о последнем сообщении.
Код:

if combinations(i) > 0 then
      ? i;"-к = ";combinations(i)
  endif

Вот среднюю я строчку я не понял. Поясни, плиз ;-)

Между тем, я получил за проект 100% с комментарием "Очень хороший проект" и в целом за курс А+, что является максимально возможно оценкой. Спасибо всем, кто принял участие в обсуждении :)

aESThete 17-05-2005 14:23 324937

поздравляю!
Код:

if combinations(i) > 0 then ' уже писал в предыдущем посте: combinations(2) - количество пар, combinations(3) - количество троек
    ? i;"-к = ";combinations(i)
    ' просто печатает сколько каких комбинаций, например если при i=2 выведет: 2-к=2, то следует читать: двоек[пар] = две
    ' или при i=3 выведет: 3-к=1 (читай как: троек карт - одна)
endif
if combinations(2) > 0 and combinations(3) > 0 then ' по аналогии - количество пар и кол-во троек карт больше нуля
  ? "фулхаус"
endif

это что, мне тут на днях (домой в автобусе ехал, мысли витали в облаках :)) пришла мысля,что можно сданные карты (руку) засунуть в граф, где вершинами являются масти и значения карт, создать матрицу смежности графа (или просто создать массив вида H(масть,значение), что в сущности одно и тоже) а потом уж издеваться над этим как угодно: плясать от любой вершины по связям и т.д. (все согласно теории графов ;))
нуууу ооооочень красиво получается
но есть подозрение, что ваш препод тогда или поседеет раньше времени, или в дурку попадет :lol:

Avelyev 04-11-2011 05:02 1788383

Жалко что не на си))) было бы в сто раз понятнее, код хороший, я вот хочу сделать калькулятор шансов (подсчет аутов), велосипед (покерную руку) делать не очень хотелось.


Время: 23:00.

Время: 23:00.
© OSzone.net 2001-