4.3. Sakārtojām spēles kodu
Moduļu veidošana
Iepriekšējā sadaļā mēs pabeidzām spēles koda rakstīšanu, bet tas palika nesakārtots. Šoreiz mēs iemācīsimies veidot savus moduļus un importēt tos. Ir pieņemts veidot direktorijas moduļiem un likt moduļus tajās. Ir definēti noteikumi moduļu veidošanai: moduļu nosaukumam vajag sākties ar lielo burtu, kā arī, ar lielu burtu vajag sākties arī visām mapēm.
Izveidosim mapi Roulette un tajā izveidosim failu Helpers.hs. Tajā ieliksim mūsu palīgfunkcijas:
module Roulette.Helpers where
import Data.Char hiding (isNumber)
import Data.List
import Data.Tuple.Utils
-- Helpers
-- Check if all symbols in string are digits
isNumber n = n /= "" && all isDigit n
-- Check if string is numbers separated by comma
checkNumbers = all isNumber . splitBy ','
-- Split string by comma and convert to Int
toNumbers = nub . map (\x -> read x :: Int) . splitBy ','
-- Split string by delimiter
splitBy delimiter = foldr checkDelimiter [[]]
where
checkDelimiter c l@(x:xs)
| c == delimiter = [] : l
| otherwise = (c:x) : xs
-- Check if number count is within allowed lengths and all numbers are in [0..36]
checkList l lengths = (length parsedList) `elem` lengths && all (`elem` [0..36]) parsedList
where parsedList = toNumbers l
-- Check if argument is index in list of triples
inList :: Eq a => a -> [(a, b, c)] -> Bool
inList index = any (\ x -> index == fst3 x)
-- Find index in list of triples and execute function on that triple
tripleLookup _ [] _ def = def
tripleLookup index (l:ls) fn def
| fst3 l == index = fn l
| otherwise = tripleLookup index ls fn def
colorLine color line = "\x1b[" ++ color ++ "m" ++ line ++ "\x1b[0m"
[redLine, greenLine, pinkLine] = map colorLine ["31", "32", "35"]
Savukārt, galvenā failā vajag pievienot rindu:
import Roulette.Helpers
Vēl pārnesīsim funkcijas game, processInput, generateNumber, end, quit un visus datus modulī Roulette.Game:
module Roulette.Game where
import Data.List
import Data.Tuple.Utils
import System.Random
import Roulette.Helpers
-- Bets
reds = [1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36]
blacks = [1..36] \\ reds
bets = [
("odd", [1,3..35], 2),
("even", [2, 4..36], 2),
("red", reds, 2),
("black", blacks, 2),
("1..18", [1..18], 2),
("19..36", [19..36], 2),
("1..12", [1..12], 3),
("13..24", [13..24], 3),
("25..36", [25..36], 3)
]
numberBets = [1,3,6]
end s = putStrLn $ "Game ended: " ++ s
quit s = any (`isPrefixOf` s) [":q", "q", "exit"]
game betSize money
| money <= 0 = end $ redLine "You lost!"
| betSize > money = do
putStrLn $ pinkLine $ "Money too low, bet value changed to " ++ (show money) ++ "!"
game money money
| otherwise = do
putStrLn $ pinkLine $ "Money: " ++ (show money) ++ ". Bet value: " ++ (show betSize)
putStrLn "Enter your bet (to change amount of bet enter 'change <number>')"
input <- getLine
processInput betSize input money
processInput betSize input money
| quit input = end "quit."
| change = changeValue
| simpleBet || numberBet = generateNumber betSize money winners quotient multiplier
| otherwise = wrongInput
where
change = isPrefixOf "change" input
changeValue
| validValue = game newValNum money
| otherwise = wrongInput
where
newVal = dropWhile (== ' ') $ dropWhile (/= ' ') input
newValNum = read newVal :: Int
validValue = isNumber newVal && newValNum >= 1 && newValNum <= money
wrongInput = do
putStrLn $ redLine "Wrong input!"
game betSize money
simpleBet = inList input bets
numberBet = checkNumbers input && checkList input numberBets
numbers = toNumbers input
winners
| simpleBet = tripleLookup input bets snd3 []
| numberBet = numbers
quotient
| simpleBet = tripleLookup input bets thd3 0
| otherwise = 36
multiplier
| simpleBet = 1
| otherwise = length numbers
generateNumber betSize money winners quotient multiplier = do
gen <- fmap (`mod` 37) randomIO
putStr $ "Number won: " ++ (show gen) ++ " "
if gen == 0
then putStrLn ""
else do
if elem gen reds then putStr (redLine "red") else putStr "black"
if odd gen then putStrLn " odd" else putStrLn " even"
if elem gen winners
then do
let w = (quotient - multiplier) * betSize
putStrLn $ greenLine $ "You won " ++ (show w)
game betSize $ money + w
else do
let l = betSize * multiplier
putStrLn $ redLine "You lost"
game betSize $ money - l
Pievērsiet uzmanību, ka neskatoties uz to, ka abi moduļi atrodas vienā mapē, modulis Roulette.Game importē moduli Roulette.Helpers, izmantojot pilno ceļu. Tas ir saistīts ar to, ka par atskaites punktu tiek izmantota direktorija, kurā atrodas galvenais fails.
Mūsu galvenā faila saturs pēc visām izmaiņām izskatīsies šādi:
import Roulette.Helpers
import Roulette.Game
main = gameStart
-- Main cycle
gameStart = do
putStrLn "Enter amount"
m <- getLine
if quit m
then putStrLn "Bye!"
else do
if isNumber m
then game 1 $ read m
else do
putStrLn $ redLine "Wrong input!"
gameStart
Nedaudz par moduļu veidošanas iespējām - Jūs varat definēt funkcijas un datus, kas nav pieejami no ārpuses. Koncepts ir līdzīgs private īpašībām OOP valodās. Lai to īstenotu, mēs pēc moduļa nosaukuma (pirms atslēgvārda where), iekavās sarakstam funkcijas, kuras ir nepieciešams importēt:
module Foo.Mymodule
(foo, bar)
where
Dotajā piemērā, funkcijas foo un bar būs pieejamas no ārpuses, bet ja modulī ir kaut kādas citas funkcijas, tas ir pieejamas tikai no iekšienes.
Mēs esam pabeiguši rakstīt mūsu pirmo spēli Haskell valodā. Tagad Jūs varat papētīt šo kodu, paeksperimentēt ar to. Jūs varat novērtēt, ka veidot strādājošu kodu Haskell valodā ir interesanti un nav grūti. Šajā piemērā mēs izskatījām lietotāja ievadu, moduļu veidošanu un kā pareizi veidot funkcijas. Turpinājumā mēs pievērsīsim uzmanību tādām lietām, kā stāvoklis (ar State monādi), datubāzes, darbs ar tīklu, paralēla un konkurenta izpilde.