Storing values ​​in a Haskell data structure

I'm trying to store randomly generated bone values ​​in some data structure, but don't know exactly how to do it in Haskell. I could generate random ints so far, but I want to be able to compare them with the corresponding color values ​​and instead preserve the colors (can't imagine what this function would look like). Here is the code I have -

module Main where

import System.IO
import System.Random
import Data.List

diceColor = [("Black",1),("Green",2),("Purple",3),("Red",4),("White",5),("Yellow",6)]
diceRoll = []

rand :: Int -> [Int] -> IO ()
rand n rlst = do
       num <- randomRIO (1::Int, 6)
       if n == 0
        then printList rlst       -- here is where I need to do something to store the values
        else rand (n-1) (num:rlst)

printList x = putStrLn (show (sort x))

--matchColor x = doSomething()

main :: IO ()
main = do
    --hSetBuffering stdin LineBuffering
    putStrLn "roll, keep, score?"
    cmd <- getLine
    doYahtzee cmd
    --rand (read cmd) []

doYahtzee :: String -> IO ()
doYahtzee cmd = do
if cmd == "roll" 
    then do rand 5 []
        else putStrLn "Whatever"

      

After that, I want to give the user the ability to keep the same dice (as in the case of accumulating points), and give them the ability to re-roll the dice from the left - I think this can be done by moving the data structure (with the values ​​in the dice) and counting the duplicate dice as points and store them in yet another data structure. If the user chooses to re-roll, he should be able to trigger random again and replace the values ​​in the original data structure.

I come from OOP and Haskell is new territory for me. Help is greatly appreciated.

+3


source to share


3 answers


So, a few questions, let's take them one by one:

First . How to generate something other than integers with functions from System.Random (this is a slow generator, but performance is not vital for your application). There are several approaches, with your list, you need to write an intToColor function:

intToColor :: Int -> String
intToColor n = head . filter (\p -> snd p == n) $ [("Black",1),("Green",2),("Purple",3),("Red",4),("White",5),("Yellow",6)]

      

Not very nice. Although you could do better if you wrote the pair in order (key, value), as there is little support for a "list of associations" in Data.List with a lookup function:

intToColor n = fromJust . lookup n $ [(1,"Black"),(2,"Green"),(3,"Purple"),(4,"Red"),(5,"White"),(6,"Yellow")]

      

Or, of course, you could just forget this operation with the Int key from 1 to 6 in the list, since the lists are already indexed by Int:

intToColor n = ["Black","Green","Purple","Red","White","Yellow"] !! n

      

(note that this function is slightly different as intToColor 0 is now "black" and not intToColor 1, but this is not very important given your purpose, if this really shocks you you can write "!! (n- 1) "instead of)

But since your colors are not actually strings and look like characters, you should probably create a color type:

data Color = Black | Green | Purple | Red | White | Yellow deriving (Eq, Ord, Show, Read, Enum)

      

So now Black is a Color value, you can use it anywhere in your program (and GHC will protest if you write Blak), and thanks to the magic of automatic derivation, you can compare color values ​​or show them, or use toEnum to convert Int in colour!

So now you can write:

randColorIO :: IO Color
randColorIO = do
   n <- randomRIO (0,5)
   return (toEnum n)

      



Second , you want to store the values ​​(colors) in the dice in a data structure and be able to keep the same rolls. Therefore, you must first store the results of multiple throws, given the maximum number of simultaneous throws (5) and the complexity of your data, a simple list is sufficient and given the number of functions for processing lists in Haskell, this is a good choice.

So, you want to roll some dice:

nThrows :: Int -> IO [Color]
nThrows 0 = return []
nThrows n = do
   c <- randColorIO
   rest <- nThrows (n-1)
   return (c : rest)

      

This is a good first approach to what you are doing, more or less, except what you use, if instead of pattern matching and you have an explicit accumulator argument (have you been for tail recursion?), It is not better if do not count strict (Int, not lists).

Of course, Haskell promotes higher-order functions rather than forward recursion, so let's take a look at combinators looking for "Int -> IO a -> IO [a]" with Hoogle:

replicateM :: Monad m => Int -> m a -> m [a]

      

What exactly do you want:

nThrows n = replicateM n randColorIO

      

(I'm not sure if I would even write this as a function, since I found the explicit expression clearer and almost as short)

Once you have the results of the throws you have to check which ones are identical, I suggest you look at the sort, group, map and length to achieve this (converting a list of results to a list of a list of identical results is not the most efficient data structure, but in this scale is the most appropriate choice). Then saving the colors you've received multiple times is just a matter of using a filter.

Then you have to write a few more functions to handle interaction and scoring:

type Score = Int
yahtzee :: IO Score
yahtzeeStep :: Int -> [[Color]] -> IO [[Color]] -- recursive
scoring :: [[Color]] -> Score

      

Therefore, I recommend storing and transmitting [[Color]] to keep track of what has been pending. This should be enough for your needs.

+6


source


Basically you are asking two different questions here. The first question can be answered with a type function getColor n = fst . head $ filter (\x -> snd x == n) diceColor

.

The second question is, however, much more interesting. You cannot replace items. You need a function that can call itself recursively, and that function will control your game. It must accept the current score and the list of saved cubes as parameters. Upon entry, the score will be zero, and the list of those remaining in the dice will remain empty. It will then roll as many cubes as needed to fill the list (I'm not familiar with Yahtzee's rules), presents it to the user, and prompts for a choice. If the user decides to end the game, the function will return the score. If he decides to keep some dice, the function calls itself with the current score and a list of dice remaining. So, to sum up playGame :: Score -> [Dice] -> IO Score

.



Disclaimer: I am a very beginner in Haskell too.

+3


source


first thought:

rand :: Int -> IO [Int]
rand n = mapM id (take n (repeat (randomRIO (1::Int, 6))))

      

although adekers can remove parens

+1


source







All Articles