parent
e3dc99342a
commit
f435c644c2
13 changed files with 99566 additions and 0 deletions
@ -0,0 +1,8 @@ |
|||||||
|
# Chapter Exercises |
||||||
|
## Hangman game logic |
||||||
|
see hangman/src/Main.hs |
||||||
|
|
||||||
|
## Modifying code |
||||||
|
1. see src/ciphers.hs |
||||||
|
2. see src/palindrome.hs |
||||||
|
3. see src/person.hs |
@ -0,0 +1,8 @@ |
|||||||
|
# Intermission: Check your understanding |
||||||
|
1. `forever` and `when` |
||||||
|
2. `Data.Bits` and `Database.Blacktip.Types` |
||||||
|
3. The types used for the blacktip library |
||||||
|
4. |
||||||
|
1. `Control.Concurrent.MVar`, `Filesystem.Path.CurrentOS` and `Control.Concurrent` |
||||||
|
2. `import qualifed Filesystem as FS` |
||||||
|
3. `import Control.Monad (forever, when)` |
@ -0,0 +1,30 @@ |
|||||||
|
Copyright Author name here (c) 2017 |
||||||
|
|
||||||
|
All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above |
||||||
|
copyright notice, this list of conditions and the following |
||||||
|
disclaimer in the documentation and/or other materials provided |
||||||
|
with the distribution. |
||||||
|
|
||||||
|
* Neither the name of Author name here nor the names of other |
||||||
|
contributors may be used to endorse or promote products derived |
||||||
|
from this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1 @@ |
|||||||
|
# hangman |
@ -0,0 +1,2 @@ |
|||||||
|
import Distribution.Simple |
||||||
|
main = defaultMain |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@ |
|||||||
|
name: hangman |
||||||
|
version: 0.1.0.0 |
||||||
|
-- synopsis: |
||||||
|
-- description: |
||||||
|
homepage: https://github.com/githubuser/hangman#readme |
||||||
|
license: BSD3 |
||||||
|
license-file: LICENSE |
||||||
|
author: Gaël Depreeuw |
||||||
|
maintainer: example@example.com |
||||||
|
copyright: 2017 Gaël Depreeuw |
||||||
|
category: Game |
||||||
|
build-type: Simple |
||||||
|
cabal-version: >=1.10 |
||||||
|
extra-source-files: README.md, |
||||||
|
data/dict.txt |
||||||
|
|
||||||
|
executable hangman |
||||||
|
hs-source-dirs: src |
||||||
|
main-is: Main.hs |
||||||
|
default-language: Haskell2010 |
||||||
|
build-depends: base >= 4.7 && < 5, |
||||||
|
random, |
||||||
|
split |
@ -0,0 +1,123 @@ |
|||||||
|
module Main where |
||||||
|
|
||||||
|
import Control.Monad (forever) |
||||||
|
import Data.Char (toLower, isAlpha) |
||||||
|
import Data.Maybe (isJust) |
||||||
|
import Data.List (intersperse) |
||||||
|
import System.Exit (exitSuccess) |
||||||
|
import System.Random (randomRIO) |
||||||
|
|
||||||
|
newtype WordList = WordList [String] deriving (Eq, Show) |
||||||
|
|
||||||
|
allWords :: IO WordList |
||||||
|
allWords = do |
||||||
|
dict <- readFile "data/dict.txt" |
||||||
|
return (WordList $ lines dict) |
||||||
|
|
||||||
|
minWordLength :: Int |
||||||
|
minWordLength = 5 |
||||||
|
|
||||||
|
maxWordLength :: Int |
||||||
|
maxWordLength = 9 |
||||||
|
|
||||||
|
maxWrongGuesses :: Int |
||||||
|
maxWrongGuesses = 10 |
||||||
|
|
||||||
|
gameWords :: IO WordList |
||||||
|
gameWords = do |
||||||
|
WordList aw <- allWords |
||||||
|
return $ WordList (filter gameLength aw) |
||||||
|
where gameLength w = |
||||||
|
let l = length (w :: String) |
||||||
|
in l >= minWordLength && l < maxWordLength |
||||||
|
&& all isAlpha w |
||||||
|
|
||||||
|
randomWord :: WordList -> IO String |
||||||
|
randomWord (WordList wl) = do |
||||||
|
randomIndex <- randomRIO (0, length wl - 1) |
||||||
|
return $ wl !! randomIndex |
||||||
|
|
||||||
|
randomWord' :: IO String |
||||||
|
randomWord' = gameWords >>= randomWord |
||||||
|
|
||||||
|
data Puzzle = Puzzle String [Maybe Char] [Char] Int |
||||||
|
instance Show Puzzle where |
||||||
|
show (Puzzle _ discovered guessed i) = |
||||||
|
(intersperse ' ' $ fmap renderPuzzleChar discovered) |
||||||
|
++ " Guessed so far: " ++ guessed ++ "\n" |
||||||
|
++ "Wrong guesses so far: " ++ show i |
||||||
|
|
||||||
|
freshPuzzle :: String -> Puzzle |
||||||
|
freshPuzzle s = Puzzle s l [] 0 |
||||||
|
where l = fmap (const Nothing) s |
||||||
|
|
||||||
|
charInWord :: Puzzle -> Char -> Bool |
||||||
|
charInWord (Puzzle s _ _ _) c = elem c s |
||||||
|
|
||||||
|
alreadyGuessed :: Puzzle -> Char -> Bool |
||||||
|
alreadyGuessed (Puzzle _ _ s _) c = elem c s |
||||||
|
|
||||||
|
renderPuzzleChar :: Maybe Char -> Char |
||||||
|
renderPuzzleChar Nothing = '_' |
||||||
|
renderPuzzleChar (Just c) = c |
||||||
|
|
||||||
|
fillInCharacter :: Puzzle -> Char -> Puzzle |
||||||
|
fillInCharacter (Puzzle word filledInSoFar s i) c = |
||||||
|
Puzzle word newFilledInSoFar (c:s) i |
||||||
|
where newFilledInSoFar = zipWith (zipper c) word filledInSoFar |
||||||
|
zipper guessed wordChar guessChar = |
||||||
|
if wordChar == guessed |
||||||
|
then Just wordChar |
||||||
|
else guessChar |
||||||
|
|
||||||
|
handleWrongGuess :: Puzzle -> Puzzle |
||||||
|
handleWrongGuess (Puzzle w f g i) = |
||||||
|
Puzzle w f g (i+1) |
||||||
|
|
||||||
|
handleGuess :: Puzzle -> Char -> IO Puzzle |
||||||
|
handleGuess puzzle guess = do |
||||||
|
putStrLn $ "Your guess was: " ++ [guess] |
||||||
|
case (charInWord puzzle guess, alreadyGuessed puzzle guess) of |
||||||
|
(_, True) -> do |
||||||
|
putStrLn "You already guessed that character, pick something else!" |
||||||
|
return puzzle |
||||||
|
(True, _) -> do |
||||||
|
putStrLn "This character was in the word, filling in the word\ |
||||||
|
\ accordingly" |
||||||
|
return (fillInCharacter puzzle guess) |
||||||
|
(False, _) -> do |
||||||
|
putStrLn "This character wasn't in the word, try again." |
||||||
|
return $ handleWrongGuess (fillInCharacter puzzle guess) |
||||||
|
|
||||||
|
gameOver :: Puzzle -> Int -> IO () |
||||||
|
gameOver (Puzzle wordToGuess _ _ i) maxWrong = |
||||||
|
if i >= maxWrong then |
||||||
|
do putStrLn "You lose" |
||||||
|
putStrLn $ "The word was: " ++ wordToGuess |
||||||
|
exitSuccess |
||||||
|
else return () |
||||||
|
|
||||||
|
gameWin :: Puzzle -> IO () |
||||||
|
gameWin (Puzzle _ filledInSoFar _ _) = |
||||||
|
if all isJust filledInSoFar then |
||||||
|
do putStrLn "You win!" |
||||||
|
exitSuccess |
||||||
|
else return () |
||||||
|
|
||||||
|
runGame :: Puzzle -> IO () |
||||||
|
runGame puzzle = forever $ do |
||||||
|
gameOver puzzle maxWrongGuesses |
||||||
|
gameWin puzzle |
||||||
|
putStrLn $ "Current puzzle is: " ++ show puzzle |
||||||
|
putStr "Guess a letter: " |
||||||
|
guess <- getLine |
||||||
|
case guess of |
||||||
|
[c] -> handleGuess puzzle c >>= runGame |
||||||
|
_ -> putStrLn "Your guess must be a single character" |
||||||
|
|
||||||
|
|
||||||
|
main :: IO () |
||||||
|
main = do |
||||||
|
word <- randomWord' |
||||||
|
let puzzle = freshPuzzle (fmap toLower word) |
||||||
|
runGame puzzle |
@ -0,0 +1,66 @@ |
|||||||
|
# This file was automatically generated by 'stack init' |
||||||
|
# |
||||||
|
# Some commonly used options have been documented as comments in this file. |
||||||
|
# For advanced use and comprehensive documentation of the format, please see: |
||||||
|
# https://docs.haskellstack.org/en/stable/yaml_configuration/ |
||||||
|
|
||||||
|
# Resolver to choose a 'specific' stackage snapshot or a compiler version. |
||||||
|
# A snapshot resolver dictates the compiler version and the set of packages |
||||||
|
# to be used for project dependencies. For example: |
||||||
|
# |
||||||
|
# resolver: lts-3.5 |
||||||
|
# resolver: nightly-2015-09-21 |
||||||
|
# resolver: ghc-7.10.2 |
||||||
|
# resolver: ghcjs-0.1.0_ghc-7.10.2 |
||||||
|
# resolver: |
||||||
|
# name: custom-snapshot |
||||||
|
# location: "./custom-snapshot.yaml" |
||||||
|
resolver: lts-9.10 |
||||||
|
|
||||||
|
# User packages to be built. |
||||||
|
# Various formats can be used as shown in the example below. |
||||||
|
# |
||||||
|
# packages: |
||||||
|
# - some-directory |
||||||
|
# - https://example.com/foo/bar/baz-0.0.2.tar.gz |
||||||
|
# - location: |
||||||
|
# git: https://github.com/commercialhaskell/stack.git |
||||||
|
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||||
|
# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a |
||||||
|
# extra-dep: true |
||||||
|
# subdirs: |
||||||
|
# - auto-update |
||||||
|
# - wai |
||||||
|
# |
||||||
|
# A package marked 'extra-dep: true' will only be built if demanded by a |
||||||
|
# non-dependency (i.e. a user package), and its test suites and benchmarks |
||||||
|
# will not be run. This is useful for tweaking upstream packages. |
||||||
|
packages: |
||||||
|
- . |
||||||
|
# Dependency packages to be pulled from upstream that are not in the resolver |
||||||
|
# (e.g., acme-missiles-0.3) |
||||||
|
extra-deps: [] |
||||||
|
|
||||||
|
# Override default flag values for local packages and extra-deps |
||||||
|
flags: {} |
||||||
|
|
||||||
|
# Extra package databases containing global packages |
||||||
|
extra-package-dbs: [] |
||||||
|
|
||||||
|
# Control whether we use the GHC we find on the path |
||||||
|
# system-ghc: true |
||||||
|
# |
||||||
|
# Require a specific version of stack, using version ranges |
||||||
|
# require-stack-version: -any # Default |
||||||
|
# require-stack-version: ">=1.5" |
||||||
|
# |
||||||
|
# Override the architecture used by stack, especially useful on Windows |
||||||
|
# arch: i386 |
||||||
|
# arch: x86_64 |
||||||
|
# |
||||||
|
# Extra directories used by stack for building |
||||||
|
# extra-include-dirs: [/path/to/dir] |
||||||
|
# extra-lib-dirs: [/path/to/dir] |
||||||
|
# |
||||||
|
# Allow a newer minor version of GHC than the snapshot specifies |
||||||
|
# compiler-check: newer-minor |
@ -0,0 +1 @@ |
|||||||
|
Subproject commit dc97b6e0d15ae6d63c32f7b7fa35291270835552 |
@ -0,0 +1,82 @@ |
|||||||
|
module Cipher where |
||||||
|
|
||||||
|
import Data.Char |
||||||
|
|
||||||
|
-- Chapter 9.12 |
||||||
|
caesar :: String -> Int -> String |
||||||
|
caesar [] _ = [] |
||||||
|
caesar (x:xs) k |
||||||
|
| isAlpha x = go x k ++ caesar xs k |
||||||
|
| otherwise = caesar xs k |
||||||
|
where go c key = [enc c (+) key] |
||||||
|
|
||||||
|
unCaesar :: String -> Int -> String |
||||||
|
unCaesar [] _ = [] |
||||||
|
unCaesar (x:xs) k |
||||||
|
| isAlpha x = go x k ++ unCaesar xs k |
||||||
|
| otherwise = caesar xs k |
||||||
|
where go c key = [enc c (-) key] |
||||||
|
|
||||||
|
enc :: Char -> (Int -> Int -> Int) -> Int -> Char |
||||||
|
enc c f k |
||||||
|
| elem c ['a'..'z'] = chr (i 'a') |
||||||
|
| elem c ['A'..'Z'] = chr (i 'A') |
||||||
|
| otherwise = c |
||||||
|
where i b = (mod (ci b) r) + (ord b) |
||||||
|
ci b = f ((ord c) - (ord b)) k |
||||||
|
r = 26 |
||||||
|
|
||||||
|
alphaIndex :: Char -> Int |
||||||
|
alphaIndex c |
||||||
|
| elem c ['a'..'z'] = ord c - ord 'a' |
||||||
|
| elem c ['A'..'Z'] = ord c - ord 'A' |
||||||
|
| otherwise = 0 |
||||||
|
|
||||||
|
|
||||||
|
shift :: Char -> Char -> Char |
||||||
|
shift c k |
||||||
|
| elem c ['a'..'z'] = go c k 'a' |
||||||
|
| elem c ['A'..'Z'] = go c k 'A' |
||||||
|
| otherwise = c |
||||||
|
where go p key base = chr $ (mod rel r) + b |
||||||
|
where rel = alphaIndex p + alphaIndex key |
||||||
|
r = 26 |
||||||
|
b = ord base |
||||||
|
|
||||||
|
-- nice solution, but maps keyword to non-alpha characters |
||||||
|
-- e.g. MEET_AT_DAWN |
||||||
|
-- ALLYALLYALLY |
||||||
|
vigenere' :: [Char] -> [Char] -> [Char] |
||||||
|
vigenere' xs ys = zipWith shift xs ((concat . repeat) ys) |
||||||
|
|
||||||
|
-- wrote own zipWith variant which maps only when isAlpha |
||||||
|
vigenere :: [Char] -> [Char] -> [Char] |
||||||
|
vigenere xs [] = xs -- necessary to avoid bottom |
||||||
|
vigenere xs ys = myZipWith shift xs ys |
||||||
|
where myZipWith _ [] _ = [] |
||||||
|
myZipWith f s [] = myZipWith f s ys |
||||||
|
myZipWith f (a:as) k@(b:bs) = |
||||||
|
if isAlpha a |
||||||
|
then f a b : myZipWith f as bs |
||||||
|
else a : myZipWith f as k |
||||||
|
|
||||||
|
-- 1 |
||||||
|
-- Not a lot of error checking, but just testing |
||||||
|
-- reading the input |
||||||
|
callCaesar :: IO () |
||||||
|
callCaesar = do |
||||||
|
putStrLn "Enter the plain text:" |
||||||
|
s <- getLine |
||||||
|
putStr "Enter the key (Char): " |
||||||
|
c <- getChar |
||||||
|
putStr "\n" |
||||||
|
putStrLn $ caesar s $ alphaIndex c |
||||||
|
|
||||||
|
callVigenere :: IO () |
||||||
|
callVigenere = do |
||||||
|
putStrLn "Enter the plain text:" |
||||||
|
s <- getLine |
||||||
|
putStr "Enter the key (String): " |
||||||
|
k <- getLine |
||||||
|
putStr "\n" |
||||||
|
putStrLn $ vigenere s k |
@ -0,0 +1,15 @@ |
|||||||
|
module Palindrome where |
||||||
|
|
||||||
|
import Control.Monad |
||||||
|
import Data.Char |
||||||
|
|
||||||
|
palindrome :: IO () |
||||||
|
palindrome = forever $ do |
||||||
|
line1 <- getLine |
||||||
|
case (pal line1) of |
||||||
|
True -> putStrLn "It's a palindrome!" |
||||||
|
False -> putStrLn "Nope!" |
||||||
|
where pal s = |
||||||
|
let s' = map toLower $ filter isAlpha s |
||||||
|
in s' == reverse s' |
||||||
|
|
@ -0,0 +1,36 @@ |
|||||||
|
module Person where |
||||||
|
|
||||||
|
type Name = String |
||||||
|
type Age = Integer |
||||||
|
|
||||||
|
data Person = Person Name Age deriving Show |
||||||
|
|
||||||
|
data PersonInvalid = |
||||||
|
NameEmpty |
||||||
|
| AgeTooLow |
||||||
|
| PersonInvalidUnknown String |
||||||
|
deriving (Show, Eq) |
||||||
|
|
||||||
|
mkPerson :: Name -> Age -> Either PersonInvalid Person |
||||||
|
mkPerson name age |
||||||
|
| name /= "" && age > 0 = Right $ Person name age |
||||||
|
| name == "" = Left NameEmpty |
||||||
|
| not (age > 0) = Left AgeTooLow |
||||||
|
| otherwise = |
||||||
|
Left $ PersonInvalidUnknown $ |
||||||
|
"Name was: " ++ show name ++ |
||||||
|
" Age was: " ++ show age |
||||||
|
|
||||||
|
gimmePerson :: IO () |
||||||
|
gimmePerson = do |
||||||
|
putStr "Provide a name: " |
||||||
|
name <- getLine |
||||||
|
putStr "Provide an age: " |
||||||
|
age <- getLine |
||||||
|
let p = mkPerson name ((read age) :: Integer) |
||||||
|
case go p of |
||||||
|
True -> putStr "Yay! Succefully got a person: " |
||||||
|
False -> putStrLn "Error: " |
||||||
|
putStrLn $ show p |
||||||
|
where go (Right _) = True |
||||||
|
go (Left _) = False |
Loading…
Reference in new issue