haskell - Best Practices for Where clauses -
i wrote simple tic-tac-toe program in haskell. runs on command line, has 1 , 2 player mode, , implements minimax algorithm when play against it.
i'm used writing proper code in oo languages, haskell new me. code works reasonably well, seems hard read (even me!). suggestions on how make code more...haskellian?
import data.list import data.char import data.maybe import control.monad data square = | b | c | d | e | f | g | h | | x | o deriving (read, eq, ord) instance show square show = "a" show b = "b" show c = "c" show d = "d" show e = "e" show f = "f" show g = "g" show h = "h" show = "i" show x = "x" show o = "o" type row = [square] type board = [row] data player = px | po deriving (read, eq) instance show player show px = "player x" show po = "player o" data result = xwin | tie | owin deriving (read, show, eq, ord) main :: io () main = putstrln "let's play tic tac toe!!!" putstrln "yeeeaaaaaahh!!!" gameselect gameselect :: io () gameselect = putstrln "who gonna play, 1 playa or two??? (enter 1 or 2)" gamemode <- getline case gamemode of "1" -> oneplayermode "2" -> twoplayermode gamemode -> gameselect oneplayermode = putstrln "one playa" putstrln "cool! ready play...against invincible tic tac toe ai!!!!! hahahaha!!!" gameloop 1 emptyboard px twoplayermode = putstrln "two players" gameloop 2 emptyboard px emptyboard = [[a,b,c],[d,e,f],[g,h,i]] gameloop :: int -> board -> player -> io () gameloop noofplayers board player = case detectwin board of xwin -> endgame board xwin owin -> endgame board owin tie -> endgame board tie nothing -> if noofplayers == 1 if player == px entermove 1 board player else enterbestmove board po else entermove 2 board player entermove :: int -> board -> player -> io () entermove noofplayers board player = displayboard board if noofplayers == 1 putstrln ("make move. (a-i)") else putstrln (show player ++ ", it's turn. (a-i)") move <- getline print move if not $ move `elem` ["a","b","c","d","e","f","g","h","i"] putstrln $ move ++ " not move, doofus" gameloop noofplayers board player else if (read (map toupper move) :: square) `elem` [ sq | sq <- concat board] gameloop noofplayers (newboard (read (map toupper move) :: square) player board) (if player == px po else px) else putstrln "that square occupied" gameloop noofplayers board player enterbestmove :: board -> player -> io () enterbestmove board player = gameloop 1 (newboard bestmove player board) px bestmove = fst $ findbestmove po board findbestmove :: player -> board -> (square, result) findbestmove player board | player == po = findmax results | player == px = findmin results findmin = foldl1 (\ acc x -> if snd x < snd acc x else acc) findmax = foldl1 (\ acc x -> if snd x > snd acc x else acc) results = [ (sq, getresult b) | (sq, b) <- boards player board ] getresult b = if detectwin b == nothing snd (findbestmove (if player == px po else px) b) else fromjust $ detectwin b boards :: player -> board -> [(square, board)] boards player board = [(sq, newboard sq player board) | sq <- concat board, sq /= x, sq /=o] displayboard :: board -> io () displayboard board = mapm_ print board newboard :: square -> player -> board -> board newboard move player board = [ [if sq == move mark else sq | sq <- row] | row <- board] mark = if player == px x else o detectwin :: board -> (maybe result) detectwin board | [x,x,x] `elem` board ++ transpose board = xwin | [x,x,x] `elem` [diagonal1 board, diagonal2 board] = xwin | [o,o,o] `elem` board ++ transpose board = owin | [o,o,o] `elem` [diagonal1 board, diagonal2 board] = owin | [x,x,x,x,x,o,o,o,o] == (sort $ concat board) = tie | otherwise = nothing diagonal1 :: board -> [square] diagonal1 bs = bs!!0!!0 : bs!!1!!1 : bs!!2!!2 : [] diagonal2 :: board -> [square] diagonal2 bs = bs!!0!!2 : bs!!1!!1 : bs!!2!!0 : [] endgame :: board -> result -> io () endgame board result = displayboard board if result `elem` [xwin, owin] let player = if result == xwin px else po in putstrln ("the game over, , " ++ show player ++ " wins!") putstrln ((if player == px show po else show px) ++ " loser lol") else putstrln "the game tie" putstrln "you both losers! ugh!" putstrln "want play again? (y/n)" again <- getline if again `elem` ["y", "y", "yes", "yes", "yes"] gameselect else putstrln "goodbye"
edit: special @chi , @caridorc, i've made following changes. further suggestions considered , updated well
import data.list import data.char import data.maybe import control.monad data square = | b | c | d | e | f | g | h | | x | o deriving (read, eq, ord) instance show square show = "a" show b = "b" show c = "c" show d = "d" show e = "e" show f = "f" show g = "g" show h = "h" show = "i" show x = "x" show o = "o" type row = [square] type board = [row] data player = px | po deriving (read, eq) instance show player show px = "player x" show po = "player o" data result = xwin | tie | owin deriving (read, show, eq, ord) main :: io () main = putstrln "let's play tic tac toe!!!" putstrln "yeeeaaaaaahh!!!" gameselect gameselect :: io () gameselect = putstrln "who gonna play, 1 playa or two??? (enter 1 or 2)" gamemode <- getline case gamemode of "1" -> oneplayermode "2" -> twoplayermode _ -> gameselect oneplayermode = putstrln "one playa" putstrln "cool! ready play...against invincible tic tac toe ai!!!!! hahahaha!!!" gameloop 1 emptyboard px twoplayermode = putstrln "two players" gameloop 2 emptyboard px emptyboard = [[a,b,c],[d,e,f],[g,h,i]] displayboard :: board -> io () displayboard board = mapm_ print board otherplayer :: player -> player otherplayer px = po otherplayer po = px gameloop :: int -> board -> player -> io () gameloop noofplayers board player = case detectwin board of res -> endgame board res nothing -> case noofplayers of 1 -> case player of px -> entermove 1 board player po -> enterbestmove board po 2 -> entermove 2 board player entermove :: int -> board -> player -> io () entermove noofplayers board player = displayboard board case noofplayers of 1 -> putstrln ("make move. (a-i)") 2 -> putstrln (show player ++ ", it's turn. (a-i)") move <- getline print move if not $ move `elem` ["a","b","c","d","e","f","g","h","i"] putstrln $ move ++ " not move, doofus" gameloop noofplayers board player else if (read (map toupper move) :: square) `elem` (concat board) gameloop noofplayers (newboard (read (map toupper move) :: square) player board) (otherplayer player) else putstrln "that square occupied" gameloop noofplayers board player enterbestmove :: board -> player -> io () enterbestmove board player = gameloop 1 (newboard bestmove player board) px bestmove = fst $ findbestmove po board findbestmove :: player -> board -> (square, result) -- minimax algorithm findbestmove player board | player == po = findmax results | player == px = findmin results findmin = foldl1 (\ acc x -> if snd x < snd acc x else acc) findmax = foldl1 (\ acc x -> if snd x > snd acc x else acc) results = [ (sq, getresult b) | (sq, b) <- boards player board ] getresult b = case detectwin b of nothing -> snd (findbestmove (otherplayer player) b) x -> x boards :: player -> board -> [(square, board)] boards player board = [(sq, newboard sq player board) | sq <- concat board, sq /= x, sq /=o] newboard :: square -> player -> board -> board newboard move player board = [ [if sq == move mark else sq | sq <- row] | row <- board] mark = if player == px x else o detectwin :: board -> (maybe result) detectwin board | [x,x,x] `elem` (triplets board) = xwin | [o,o,o] `elem` (triplets board) = owin | [x,x,x,x,x,o,o,o,o] == (sort $ concat board) = tie | otherwise = nothing triplets :: board -> [[square]] triplets board = board ++ transpose board ++ [diagonal1] ++ [diagonal2] flat = concat board diagonal1 = [flat !! 0, flat !! 4, flat !! 8] diagonal2 = [flat !! 2, flat !! 4, flat !! 6] endgame :: board -> result -> io () endgame board result = displayboard board putstrln $ endgamemessage result putstrln "want play again? (y/n)" again <- getline if again `elem` ["y", "y", "yes", "yes", "yes"] gameselect else putstrln "goodbye" endgamemessage :: result -> string endgamemessage result | result `elem` [xwin, owin] = winnernotice ++ losernotice | otherwise = "the game tie\n" ++ "you both losers! ugh!" winner = case result of xwin -> px owin -> po winnernotice = "the game over, , " ++ show winner ++ " wins!\n" losernotice = (show $ otherplayer winner) ++ " loser lol"
code style matter of personal preference, in haskell arguably more in other languages "standard" style guide. still, here's few random suggestions.
don't over-indent case
s: use line
case gamemode of "1" -> oneplayermode "2" -> twoplayermode gamemode -> gameselect
vs
case gamemode of "1" -> oneplayermode "2" -> twoplayermode gamemode -> gameselect
or even
case gamemode of "1" -> oneplayermode "2" -> twoplayermode _ -> gameselect
case
preferred if .. == constructor
:
if player == px entermove 1 board player else enterbestmove board po
vs
case player of px -> entermove 1 board player py -> enterbestmove board po
i'd recommend against using partial functions fromjust
, since can crash program if forget check nothing
beforehand. safer alternatives exist, never cause such crashes -- less burden on programmer.
if detectwin b == nothing snd (findbestmove (if player == px po else px) b) else fromjust $ detectwin b
vs
case detectwin b of nothing -> snd $ findbestmove (if player == px po else px) b x -> x
or
frommaybe (snd $ findbestmove (if player == px po else px) b) $ detectwin b
try factorize commonly used functions. instance
nextplayer px = po nextplayer po = px
can replace uses of
if player == px po else px
no do
needed when there's 1 statement:
if noofplayers == 1 putstrln ("make move. (a-i)") -- no need parentheses here else putstrln (show player ++ ", it's turn. (a-i)")
since mention where
in title, let me state have mixed feelings where
, in general. know tend avoid where
in favor of let
, feeling not shared many other haskellers, take care.
personally, tend limit where
uses one-liners:
foo = f x y x = ... y = ...
especially in do
blocks, might span several lines, prefer let
s:
foo = line line using x -- x ??!? line ... line x = ... -- ah, here
vs
foo = line let x = ... line using x line ... line
however, feel free adopt style find more readable.
also don't forget add few comments, @mawalker points out. definitions obvious , don't need explanation. others benefit few lines explaining purpose.
Comments
Post a Comment