2012-06-18 8 views
7

अभ्यास के रूप में, मैं हास्केल में कैसीनो गेम "युद्ध" के लिए सिमुलेशन लिखने की कोशिश कर रहा हूं।हास्केल कोड का यह टुकड़ा अधिक संक्षिप्त कैसे बनाते हैं?

http://en.wikipedia.org/wiki/Casino_war

यह कुछ नियमों के साथ एक बहुत ही सरल खेल है। मुझे पता है कि किसी भी अनिवार्य भाषा में लिखना अन्यथा बहुत ही सरल समस्या होगी, हालांकि मैं इसे हास्केल में लिखने के लिए संघर्ष कर रहा हूं।

कोड मैं अब तक है:

-- Simulation for the Casino War 

import System.Random 
import Data.Map 

------------------------------------------------------------------------------- 
-- stolen from the internet 

fisherYatesStep :: RandomGen g => (Map Int a, g) -> (Int, a) -> (Map Int a, g) 
fisherYatesStep (m, gen) (i, x) = ((insert j x . insert i (m ! j)) m, gen') 
    where 
     (j, gen') = randomR (0, i) gen 

fisherYates :: RandomGen g => g -> [a] -> ([a], g) 
fisherYates gen [] = ([], gen) 
fisherYates gen l = toElems $ Prelude.foldl 
     fisherYatesStep (initial (head l) gen) (numerate (tail l)) 
    where 
     toElems (x, y) = (elems x, y) 
     numerate = zip [1..] 
     initial x gen = (singleton 0 x, gen) 

------------------------------------------------------------------------------- 

data State = Deal | Tie deriving Show 

-- state: game state 
-- # cards to deal 
-- # cards to burn 
-- cards on the table 
-- indices for tied players 
-- # players 
-- players winning 
-- dealer's winning 
type GameState = (State, Int, Int, [Int], [Int], Int, [Int], Int) 

gameRound :: GameState -> Int -> GameState 
gameRound (Deal, toDeal, toBurn, inPlay, tied, numPlayers, pWins, dWins) card 
    | toDeal > 0 = 
     -- not enough card, deal a card 
     (Deal, toDeal - 1, 0, card:inPlay, tied, numPlayers, pWins, dWins) 
    | toDeal == 0 = 
     -- enough cards in play now 
     -- here should detemine whether or not there is any ties on the table, 
     -- and go to the tie state 
     let 
      dealerCard = head inPlay 
      p = zipWith (+) pWins $ (tail inPlay) >>= 
       (\x -> if x < dealerCard then return (-1) else return 1) 
      d = if dealerCard == (maximum inPlay) then dWins + 1 else dWins - 1 
     in 
      (Deal, numPlayers + 1, 0, [], tied, numPlayers, p, d) 
gameRound (Tie, toDeal, toBurn, inPlay, tied, numPlayers, pWins, dWins) card 
    -- i have no idea how to write the logic for the tie state AKA the "war" state 
    | otherwise = (Tie, toDeal, toBurn, inPlay, tied, numPlayers, pWins, dWins) 

------------------------------------------------------------------------------- 

main = do 
    rand <- newStdGen 
    -- create the shuffled deck 
    (deck, _) <- return $ fisherYates rand $ [2 .. 14] >>= (replicate 6) 
    -- fold the state updating function over the deck 
    putStrLn $ show $ Prelude.foldl gameRound 
     (Deal, 7, 0, [], [], 6, [0 ..], 0) deck 

------------------------------------------------------------------------------- 

मुझे समझ में क्यों अतिरिक्त काम यादृच्छिक संख्या बनाने की दिशा में जाना पड़ता है, लेकिन मैं यकीन है कि मैं कुछ बुनियादी निर्माण या अवधारणा याद आ रही हूँ। राज्यों का संग्रह रखने के लिए यह अजीब नहीं होना चाहिए, और इनपुट की सूची में शाखाबद्ध तर्क चलाएं। मैं उस मामले के लिए तर्क लिखने का एक अच्छा तरीका भी नहीं समझ पाया जहां टेबल पर संबंध हैं।

मैं पूर्ण समाधान मांग नहीं रहा हूं। यह वास्तव में अच्छा होगा अगर कोई यह बता सके कि मैं क्या गलत कर रहा हूं, या कुछ अच्छी रीडिंग सामग्री प्रासंगिक हैं।

अग्रिम धन्यवाद।

+1

आपको ['स्टेटटी'] (http://hackage.haskell.org/packages/archive/mtl/latest/doc/html/Control-Monad-State-Lazy.html#v:StateT) में देखना चाहिए और ['RandT'] (http://hackage.haskell.org/packages/archive/MonadRandom/0.1.6/doc/html/Control-Monad-Random.html#t:RandT) मोनड ट्रांसफार्मर। –

उत्तर

6

आवेदन स्थिति को बनाए रखने के लिए एक उपयोगी डिजाइन पैटर्न तथाकथित राज्य इकाई है। आप एक विवरण और कुछ प्रारंभिक उदाहरण here पा सकते हैं। इसके अलावा, आप, GameState के लिए एक टपल के बजाय नामित क्षेत्रों के साथ एक डेटा प्रकार का उपयोग पर विचार करना उदाहरण के लिए चाहते हो सकता है:

data GameState = GameState { state :: State, 
          toDeal :: Int 
          -- and so on 
          } 

इससे तक पहुँचने के लिए कर देगा/व्यक्ति record syntax का उपयोग कर खेतों अद्यतन।

+1

रिकॉर्ड सिंटैक्स आपके कोड को समझने में भी आसान बना सकता है, क्योंकि फ़ील्ड में टुपल्स के बजाय वर्णनात्मक नाम हो सकते हैं जो कि केवल प्रकार हैं, और '(Int, Int, Int)' का एक टुपल बहुत उपयोगी नहीं है यदि आप नहीं कर सकते याद रखें कि किस चीज के लिए 'Int' है। राज्य मोनड के लिए +1 भी, यह मैनुअल पाइपलाइन के बहुत से बचाता है। –

2

यह मेरे लिए हुआ है कि सिफारिश के इस्तेमाल StateT 'एक छोटे से अपारदर्शी इसलिए मैं उस शब्दजाल में एक सा अनुवाद, उम्मीद है आप देख सकते हैं कि कैसे वहां से जाने के लिए हो सकता है। खेल राज्य में डेक की स्थिति को शामिल करना सबसे अच्छा हो सकता है। नीचे gameround बस स्टेट फ़िंगो में आपके फ़ंक्शन को पुनरारंभ करता है। पिछली परिभाषा, game खेल स्थिति के deck फ़ील्ड का उपयोग करता है, लगातार कम हो जाता है, और इसमें पूरा गेम होता है। मैं आईओ कार्यों को पेश करता हूं, यह दिखाने के लिए कि यह कैसे किया जाता है, और यदि आप ghci में मुख्य कॉल करते हैं तो आप राज्यों के उत्तराधिकार को देख सकते हैं। आप आईओ कार्यों को राज्य टी मशीनरी में उठाते हैं, ताकि उन्हें प्राप्त करने और रख-रखाव के स्तर पर रखा जा सके। ध्यान दें कि मोस उपखंड में, हम नया राज्य डालते हैं और फिर कार्रवाई को दोहराने के लिए कहते हैं, ताकि डॉक ब्लॉक में पूर्ण रिकर्सिव ऑपरेशन हो। इस आत्म अपडेट करते समय एक समारोह GameState उपज game पर main हम runStateT की अंतिम पंक्ति में (टाई और एक खाली डेक तुरंत खेल खत्म।) इसके बाद -> आईओ (GameState,()); तो हम आईओ एक्शन प्राप्त करने के लिए यादृच्छिक रूप से निर्धारित डेक समेत एक निश्चित प्रारंभिक राज्य के साथ इसे खिलाते हैं जो मुख्य व्यवसाय है। (मैं का पालन नहीं करते कैसे खेल काम करने के लिए माना जाता है, लेकिन यंत्रवत् भर में विचार प्राप्त करने के चारों ओर बातें आगे बढ़ रहा था।)

import Control.Monad.Trans.State 
import Control.Monad.Trans 
import System.Random 
import Data.Map 

data Stage = Deal | Tie deriving Show 
data GameState = 
    GameState { stage  :: Stage 
       , toDeal  :: Int 
       , toBurn  :: Int 
       , inPlay  :: [Int] 
       , tied  :: [Int] 
       , numPlayers :: Int 
       , pWins  :: [Int] 
       , dWins  :: Int 
       , deck  :: [Int]} deriving Show 
       -- deck field is added for the `game` example 
type GameRound m a = StateT GameState m a 

main = do 
    rand <- newStdGen 
    let deck = fst $ fisherYates rand $ concatMap (replicate 6) [2 .. 14] 
    let startState = GameState Deal 7 0 [] [] 6 [0 ..100] 0 deck 
    runStateT game startState 

game :: GameRound IO() 
game = do 
    st <- get 
    lift $ putStrLn "Playing: " >> print st 
    case deck st of 
    []   -> lift $ print "no cards" 
    (card:cards) -> 
     case (toDeal st, stage st) of 
     (0, Deal) -> do put (first_case_update st card cards) 
         game -- <-- recursive call with smaller deck 
     (_, Deal) -> do put (second_case_update st card cards) 
         game 
     (_, Tie) -> do lift $ putStrLn "This is a tie" 
         lift $ print st 

where -- state updates: 
      -- I separate these out hoping this will make the needed sort 
      -- of 'logic' above clearer. 
    first_case_update s card cards= 
    s { numPlayers = numPlayers s + 1 
     , pWins = [if x < dealerCard then -1 else 1 | 
        x <- zipWith (+) (pWins s) (tail (inPlay s)) ] 
     , dWins = if dealerCard == maximum (inPlay s) 
        then dWins s + 1 
        else dWins s - 1 
     , deck = cards } 
      where dealerCard = head (inPlay s) 

    second_case_update s card cards = 
    s { toDeal = toDeal s - 1 
     , toBurn = 0 
     , inPlay = card : inPlay s 
     , deck = cards} 

-- a StateTified formulation of your gameRound 
gameround :: Monad m => Int -> GameRound m() 
gameround card = do 
    s <- get 
    case (toDeal s, stage s) of 
    (0, Deal) -> 
     put $ s { toDeal = numPlayers s + 1 
       , pWins = [if x < dealerCard then -1 else 1 | 
          x <- zipWith (+) (pWins s) (tail (inPlay s)) ] 
       , dWins = if dealerCard == maximum (inPlay s) 
           then dWins s + 1 
           else dWins s - 1} 
        where dealerCard = head (inPlay s) 
    (_, Deal) -> 
     put $ s { toDeal = toDeal s - 1 
       , toBurn = 0 
       , inPlay = card : inPlay s} 
    (_, Tie) -> return() 


fisherYatesStep :: RandomGen g => (Map Int a, g) -> (Int, a) -> (Map Int a, g) 
fisherYatesStep (m, gen) (i, x) = ((insert j x . insert i (m ! j)) m, gen') 
    where 
     (j, gen') = randomR (0, i) gen 

fisherYates :: RandomGen g => g -> [a] -> ([a], g) 
fisherYates gen [] = ([], gen) 
fisherYates gen l = toElems $ Prelude.foldl 
     fisherYatesStep (initial (head l) gen) (numerate (tail l)) 
    where 
     toElems (x, y) = (elems x, y) 
     numerate = zip [1..] 
     initial x gen = (singleton 0 x, gen)  
3

कोड अधिक पठनीय बनाने के लिए, आप खेल की संरचना को तोड़ने चाहिए सार्थक घटकों में, और तदनुसार अपने कोड को पुनर्गठित करना। आपने जो किया है वह सभी गेम के राज्य को एक डेटा संरचना में रखना है। नतीजा यह है कि आपको हर समय सभी गेम विवरणों से निपटना होगा।

खेल प्रत्येक खिलाड़ी और डीलर के लिए स्कोर का ट्रैक रखता है। कभी-कभी यह स्कोर से 1 या घटाता है 1। किसी अन्य चीज़ के लिए स्कोर का उपयोग नहीं किया जाता है।अन्य कोड से स्कोर प्रबंधन को अलग करें:

-- Scores for each player and the dealer 
data Score = Score [Int] Int 

-- Outcome for each player and the dealer. 'True' means a round was won. 
data Outcome = Outcome [Bool] Bool 

startingScore :: Int -> Score 
startingScore n = Score (replicate n 0) 0 

updateScore :: Outcome -> Score -> Score 
updateScore (Outcome ps d) (Score pss ds) = Score (zipWith upd pss pos) (update ds d) 
    where upd s True = s+1 
     upd s False = s-1 

कार्ड का निपटारा खिलाड़ियों और डीलर से भी जुड़ा हुआ है। एक दौर जीतना या खोना केवल कार्ड मूल्यों पर आधारित है। अन्य कोड से स्कोर गणना को अलग:

type Card = Int 
data Dealt = Dealt [Card] Card 

scoreRound :: Dealt -> Outcome 
scoreRound (Dealt ps dealerCard) = Outcome (map scorePlayer ps) (dealerCard == maximumCard) 
    where 
    maximumCard = maximum (dealerCard : ps) 
    scorePlayer p = p >= dealerCard 

मैं कहूंगा कि एक खेल दौर एक भी Outcome उत्पादन के लिए आवश्यक सभी कदम होते हैं। तदनुसार कोड को पुनर्संगठित करें:

type Deck = [Card] 

deal :: Int -> Deck -> (Dealt, Deck) 
deal n d = (Dealt (take n d) (head $ drop n d), drop (n+1) d) -- Should check whether deck has enough cards 

-- The 'input-only' parts of GameState 
type GameConfig = 
    GameConfig {nPlayers :: Int} 

gameRound :: GameConfig -> Deck -> (Deck, Outcome) 
gameRound config deck = let 
    (dealt, deck') = deal (nPlayers config) deck 
    outcome  = scoreRound dealt 
    in (deck', outcome) 

यह वही मूल कोड में किया गया था के सबसे शामिल हैं। आप बाकी के समान तरीके से संपर्क कर सकते हैं।


मुख्य विचार आप मिलना चाहिए कि हास्केल यह छोटे टुकड़ों है कि उनके अपने पर सार्थक हैं में घुलना कार्यक्रमों के लिए आसान बनाता है। यही कारण है कि कोड के साथ काम करना आसान बनाता है।

इसके बजाय GameState में सब कुछ डालने की

, मैं Score, Outcome, Dealt, और Deck बनाया। इनमें से कुछ डेटा प्रकार मूल GameState से आए थे। अन्य मूल कोड में बिल्कुल नहीं थे; जटिल जटिल लूप आयोजित किए जाने के तरीके में वे अंतर्निहित थे। पूरे गेम को gameRound में डालने के बजाय, मैंने updateScore, scoreRound, deal और अन्य फ़ंक्शंस बनाए। इनमें से प्रत्येक डेटा के केवल कुछ टुकड़ों के साथ बातचीत करता है।