Реализация Yahtzee в SML - PullRequest
0 голосов
/ 03 мая 2019

Я пишу программу SML Yahtzee / Poker, которая получает список в виде входных данных в формате

yahtzee(1,3,3,4,3)

, в котором каждое число является результатом броска костей, а затем печатает соответствующий результат, который в этом случае будет

(three-of-a-kind, 14)

последнее число является суммой всех 5 кубиков, полученных в качестве входных данных в этом случае.

В настоящее время я беру строку и подсчитываю вхождения каждого числа с помощью переменной count, поскольку в броске костей имеется 6 возможных результатов - 1, 2, 3, 4, 5 или 6.

Я сортирую счет в порядке возрастания, так что последний номер моего нового списка дает мне некоторую информацию относительно того, какой «рукой» это может быть, из следующего:

yahtzee - это 5 в своем роде и "стоит" 50 баллов, поэтому возврат будет (yahtzee, 50) большая прямая - последовательность из 5 чисел, стоит 40 баллов, поэтому возвращается (large-straight, 40) маленькая прямая - последовательность из 4 чисел, 30 баллов, так что (small-straight, 30) фулл-хаус - 3 вида и два другого вида, 25 очков, так что (full-house, 25) четыре вида - стоит столько же очков, сколько сумма всех 5 кубиков, поэтому (four-of-a-kind, sum) три вида - стоит столько же очков, сколько сумма всех 5 кубиков, так что (three-of-a-kind, sum) шанс - (chance, sum)

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

А для фулл-хауса, если я проверяю число перед последним, и оно равно 2, я знаю, что это фулл-хаус, потому что последним оно будет 3. Важно отметить, что если сумма фулл-хауса больше 25, но вместо этого эта рука должна быть тройкой, потому что она приносит большее количество очков.

Я еще не реализовал все случаи, и поэтому приведенный ниже код соответствует тому, что у меня есть в данный момент, и последняя функция - это попытка проверить, движусь ли я в правильном направлении, но она будет переименована в yahtzee(L) позже, чтобы он мог получить ввод.

fun counter (nil, count1:int, count2:int, count3:int, count4:int, count5:int, count6:int) = ListMergeSort.sort (fn (x,y) => x > y) [count1, count2, count3, count4, count5, count6]
    | counter (6::t, count1, count2, count3, count4, count5, count6) = counter (t, count1, count2, count3, count4, count5, count6+1)
    | counter (5::t, count1, count2, count3, count4, count5, count6) = counter (t, count1, count2, count3, count4, count5+1, count6)
    | counter (4::t, count1, count2, count3, count4, count5, count6) = counter (t, count1, count2, count3, count4+1, count5, count6)
    | counter (3::t, count1, count2, count3, count4, count5, count6) = counter (t, count1, count2, count3+1, count4, count5, count6)
    | counter (2::t, count1, count2, count3, count4, count5, count6) = counter (t, count1, count2+1, count3, count4, count5, count6)
    | counter (1::t, count1, count2, count3, count4, count5, count6) = counter (t, count1+1, count2, count3, count4, count5, count6)
    | counter (h::t, count1, count2, count3, count4, count5, count6) = ListMergeSort.sort (fn (x,y) => x > y) [count1, count2, count3, count4, count5, count6];

fun sum (nil) = 0
    | sum (h::t) = h + sum(t)

fun anyOfAKind (nil) = 0
    | anyOfAKind (L) = List.last(L)

fun fullHouse (nil) = 0
    | fullHouse (L) = List.nth(L, 3)

fun testdice (nil) = []
    | testdice (L) = 
        let 
            val listSum = sum(L)
            val count = counter(L,0,0,0,0,0,0)
        in
            if fullHouse(count) = 2 then
                if listSum <= 25 then print "(fullhouse, 25)"
                else print "(threeofakind, " ^ Int.toString (sum) ^ ")"
        end;

Если я удаляю последнюю функцию, проблем с созданием файла не возникает, однако при наличии последней функции появляется сообщение об ошибке

Error: syntax error found at END

Мой первый вопрос: почему я получаю это сообщение об ошибке?

Я думаю, что мне нужно что-то вернуть, но это не так?

И еще один вопрос, который у меня возникает, если мой мыслительный процесс выглядит хорошо, а если нет, то любой совет будет высоко оценен.

Спасибо!

Ответы [ 2 ]

2 голосов
/ 06 мая 2019

Есть несколько основных проблем:

  1. У вас есть if, который не соответствует else.Это ваша проблема с синтаксисом.
  2. После того, как вы отсортировали, testdice хочет либо создать список, либо напечатать что-нибудь и выдать unit.Предложения функций не могут иметь разные типы.
  3. nil - это просто имя, а в шаблоне оно соответствует всему.Это не конструктор данных - конструктор «пустого списка» - это [].

Я собираюсь предположить, что на самом деле функция должна генерировать пару, а не печатать строку с определеннымформат.
Я также предполагаю, что ввод, как вы говорите, является списком, а не пятикратным, как в примере.
(переписать из одной формы в другую несложно).

Сначала определите подходящий тип данных:

datatype Hand = Yahtzee
              | LargeStraight
              | SmallStraight
              | FullHouse
              | Four
              | Three
              | Chance;

Функция скоринга создаст пару Hand * int.

Существует много способов продолжить;Я предпочитаю писать классификационные функции, основанные на структуре, а не на подсчете.

(* Utility function; 'all xs' is true if and only if none of its elements are false. *)
fun all [] = true
  | all (false :: ts) = false
  | all (_ :: ts) = all ts

(* These check the conditions for each type of roll. They all assume that the input is sorted. *)
fun is_yahtzee [a,b,c,d,e] = all [a = b, b = c, c = d, d = e]
  | is_yahtzee _ = false;

fun is_large_straight [a,b,c,d,e] = all [b = a + 1, c = b + 1, d = c + 1, e = d + 1]
  | is_large_straight _ = false;

fun is_small_straight [a,b,c,d,e] = all [b = a + 1, c = b + 1, d = c + 1, e <> d + 1]
                             orelse all [b <> a + 1, c = b + 1, d = c + 1, e = c + 1]
  | is_small_straight _ = false;

fun is_full_house [a,b,c,d,e] = all [a = b, b = c, c <> d, d = e]
                         orelse all [a = b, b <> c, c = d, d = e]
  | is_full_house _ = false;

fun is_four [a,b,c,d,e] = all [a = b, b = c, c = d, d <> e]
                   orelse all [a <> b, b = c, c = d, d = e]
  | is_four _ = false;

fun is_three [a,b,c,d,e] = all [a = b, b = c, c <> d, d <> e]
                    orelse all [a <> b, b = c, c = d, d <> e]
                    orelse all [a <> b, b <> c, c = d, d = e]
  | is_three _ = false;

При этом вы можете записать свои правила скоринга как функцию довольно простым способом:

(* Assumes that the input is sorted and has five elements. *)
fun score roll =
    let val value = sum roll in
        if is_yahtzee roll
        then (Yahtzee, 50)
        else if is_large_straight roll
        then (LargeStraight, 40)
        else if is_small_straight roll
        then (SmallStraight, 30)
        else if is_full_house roll
        then (if value > 25 then (Three, value) else (FullHouse, 25))
        else if is_four roll
        then (Four, value)
        else if is_three roll
        then (Three, value)
        else (Chance, value)
    end
1 голос
/ 06 мая 2019

Вот как я мог бы выполнить рефакторинг этого.

Чтобы лучше определить, какой у меня бросок, я бы хотел построить гистограмму кости:

fun histogram [] = []
  | histogram (d::ds) =
    let
      fun insert x [] = [(x, 1)]
        | insert x ((y,n) :: hist) =
            if x = y
            then (y,n+1) :: hist
            else (y,n) :: insert x hist
    in
      insert d (histogram ds)
    end

fun frequencyOf (x, []) = 0
  | frequencyOf (x, (y,n)::hist) =
      if x = y
      then n
      else frequencyOf (x, hist)

fun highestFrequency [] = 0
  | highestFrequency ((x,i)::hist) =
      Int.max (i, highestFrequency hist)

Таким образом, я могу определить yahtzee, если есть только один элемент в гистограмме, прямой, если есть пять, четыре в своем роде, если самая высокая частота равна 4, и т. Д.

Тогда я быкак пользовательский тип данных для представления вида результата,

Редактировать: После molbdnilo я также полностью отделил счет от типа roll.

datatype roll
  = Yahtzee
  | LargeStraight
  | SmallStraight
  | FullHouse
  | FourOfAKind
  | ThreeOfAKind
  | Chance

И чтобы определить результат,

fun determineDice (d1, d2, d3, d4, d5) =
    let
      val dice = ListMergeSort.sort (op >) [d1, d2, d3, d4, d5]
      val diceHistogram = histogram dice
    in 
      case length diceHistogram of
           1 => (Yahtzee, 50)
         | 2 => if highestFrequency diceHistogram = 4
                then (FourOfAKind, sum dice)
                else (FullHouse, 25)
         | 3 => if highestFrequency diceHistogram = 3
                then (ThreeOfAKind, sum dice)
                else (Chance, sum dice)
         | 4 => (Chance, sum dice)
         | 5 => if frequencyOf (6, dice) = 0
                then (SmallStraight, 30)
                else (LargeStraight, 40)
    end

Для печати результата рулона я бы также написал вспомогательные функции:

fun showRoll Yahtzee = "Yahtzee"
  | showRoll LargeStraight = "Large straight"
  | ...
  | showRoll (Chance _) = "Chance"

fun showRollWithPoints (roll, points) =
    showRoll roll ^ " of " ^ Int.toString points ^ " points"

Некоторые другие улучшения, которые вы могли бы сделать:

...