patternMinor
TicTacToe in Haskell
Viewed 0 times
tictactoehaskellstackoverflow
Problem
Are there any ways which I could improve and simplify this code? Is my code idiomatic Haskell?
Is the use of a lens library to change an element of a two-dimensional array unnecessary? If so, how could I replace it with a more elegant and simpler solution?
Do I need to make use of more types in order to make my code more understandable?
As a Haskell beginner, I am still trying to get used to the functional programming way and would appreciate it if answerers would provide a detailed and easy-to-understand answer (as well as references to materials which I could look up to learn more).
```
-- Tic Tac Toe implementation in Haskell.
module TicTacToe (
newGame,
isWin,
move
) where
import Control.Lens
import Data.List
import Data.Maybe
-- Size of the grid
n :: Int
n = 3
data Player = X | O deriving (Show, Read, Eq)
type Marking = Maybe Player
type Position = (Int, Int)
type Grid = [[Marking]]
-- An empty grid of size n x n.
emptyGrid :: Grid
emptyGrid = replicate n $ replicate n Nothing
data Game = Game {
board :: Grid,
curTurn :: Player
} deriving Show
-- Initial game which board is of size n x n,
-- starting with O as the first player.
newGame :: Game
newGame = Game {
board = emptyGrid,
curTurn = X
}
getWinSeqs :: Grid -> [[Marking]]
getWinSeqs grid = horizontal ++ vertical ++ [fDiag, bDiag]
where horizontal = grid
vertical = transpose grid
fDiag = zipWith (\ i x -> i !! (n - x - 1)) grid [0..]
bDiag = zipWith (!!) grid [0..]
-- Check if a game has been won on a board.
isWin :: Game -> Maybe Player
isWin (Game grid _)
| isWin' X = Just X
| isWin' O = Just O
| otherwise = Nothing
where
isWin' :: Player -> Bool
isWin' player = any (all (== Just player)) $ getWinSeqs grid
-- Make the next move.
move :: Game -> Position -> Game
move (Game grid player) (i, j) = Game {
board = nextGrid,
curTurn = nextPlayer
} where
nextGrid =
if isNothing $ grid !! i !!
Is the use of a lens library to change an element of a two-dimensional array unnecessary? If so, how could I replace it with a more elegant and simpler solution?
Do I need to make use of more types in order to make my code more understandable?
As a Haskell beginner, I am still trying to get used to the functional programming way and would appreciate it if answerers would provide a detailed and easy-to-understand answer (as well as references to materials which I could look up to learn more).
```
-- Tic Tac Toe implementation in Haskell.
module TicTacToe (
newGame,
isWin,
move
) where
import Control.Lens
import Data.List
import Data.Maybe
-- Size of the grid
n :: Int
n = 3
data Player = X | O deriving (Show, Read, Eq)
type Marking = Maybe Player
type Position = (Int, Int)
type Grid = [[Marking]]
-- An empty grid of size n x n.
emptyGrid :: Grid
emptyGrid = replicate n $ replicate n Nothing
data Game = Game {
board :: Grid,
curTurn :: Player
} deriving Show
-- Initial game which board is of size n x n,
-- starting with O as the first player.
newGame :: Game
newGame = Game {
board = emptyGrid,
curTurn = X
}
getWinSeqs :: Grid -> [[Marking]]
getWinSeqs grid = horizontal ++ vertical ++ [fDiag, bDiag]
where horizontal = grid
vertical = transpose grid
fDiag = zipWith (\ i x -> i !! (n - x - 1)) grid [0..]
bDiag = zipWith (!!) grid [0..]
-- Check if a game has been won on a board.
isWin :: Game -> Maybe Player
isWin (Game grid _)
| isWin' X = Just X
| isWin' O = Just O
| otherwise = Nothing
where
isWin' :: Player -> Bool
isWin' player = any (all (== Just player)) $ getWinSeqs grid
-- Make the next move.
move :: Game -> Position -> Game
move (Game grid player) (i, j) = Game {
board = nextGrid,
curTurn = nextPlayer
} where
nextGrid =
if isNothing $ grid !! i !!
Solution
A haskell beginner using Control.Lens... Certainly better than my first program which happened to a tic tac toe solver, so I can compare. We all know Why functional programming matters, don't we?
Make your imports more specific:
You will note I replaced your
You only use
instead of
I recommend
Why force
I suggest you to write a program that uses this module, then you know better.
OK, now for
I'd write
Instead of throwing an error, return
and I put
because
I also made
Make your imports more specific:
import Control.Lens (set,ix)
import Data.List (transpose)
import Data.Maybe (catMaybes)You will note I replaced your
isNothing by pattern matching.You only use
type Position = (Int, Int) once. I would not declare this one.instead of
fDiag = zipWith (\ i x -> i !! (n - x - 1)) grid [0..]I recommend
fDiag = zipWith (!!) (reverse grid) [0..] -- or
fDiag = zipWith (!!) grid [n-1,n-2..]Why force
isWin to take Game? Grid would suffice.I suggest you to write a program that uses this module, then you know better.
OK, now for
move :: Game -> Position -> GameI'd write
move :: Int -> Int -> Game -> Maybe GameInstead of throwing an error, return
Nothing. Then you can compute all possible moves withmoves :: Game -> [Game]
moves game = catMaybes [move x y game | x<-[0..2], y<-[0..2]]and I put
Game in the last position so you can compose functions like move 2 0 . move 1 1 $ newGame. Actually, that won't work because move now encapsulates in Maybe. But now you can write:test = Just newGame >>=
move 1 1 >>= -- first move in center
move 2 1 >>=
move 2 0 >>=
move 0 2 >>= -- forced
move 0 0 -- setting up the trapbecause
Maybe is a monad, and >>= allows forward chaining instead of .! The modified move looks like this:move :: Int -> Int -> Game -> Maybe Game
move i j (Game grid player) = case grid !! i !! j of
Just _ -> Nothing
Nothing -> Just $ Game {
board = set (ix i . ix j) (Just player) grid,
curTurn = nextPlayer player
}
nextPlayer X = O
nextPlayer O = XI also made
nextPlayer a top level function, I'd export it, it looks useful.Code Snippets
import Control.Lens (set,ix)
import Data.List (transpose)
import Data.Maybe (catMaybes)fDiag = zipWith (\ i x -> i !! (n - x - 1)) grid [0..]fDiag = zipWith (!!) (reverse grid) [0..] -- or
fDiag = zipWith (!!) grid [n-1,n-2..]move :: Game -> Position -> Gamemove :: Int -> Int -> Game -> Maybe GameContext
StackExchange Code Review Q#68345, answer score: 5
Revisions (0)
No revisions yet.