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