HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

TicTacToe game in Haskell

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
tictactoegamehaskell

Problem

As a beginner, I tried to implement TicTacToe in Haskell. How can I improve my code?

I have refactored my code: General TicTacToe in Haskell

Utils

module Data.List.Utils where 

import Data.List (intersperse)

surround :: a -> [a] -> [a]
surround x ys = x : intersperse x ys ++ [x]

nth :: Int -> (a -> a) -> [a] -> [a]
nth _ _ [] = []
nth 0 f (x:xs) = f x : xs
nth n f (x:xs) = x : nth (n - 1) f xs


TicTacToe

```
module TicTacToe where

import Data.List (transpose)
import Data.Foldable (asum)
import Data.List.Utils (nth, surround)

data Tile = Empty | O | X deriving (Show, Eq)

showTile :: Tile -> String
showTile Empty = " "
showTile O = "O"
showTile X = "X"

type Board = [[ Tile ]]

showBoard :: Board -> String
showBoard = unlines
. surround vert
. map (concat . surround horiz . map showTile)

where vert = "+-+-+-+"
horiz = "|"

whoWon :: Board -> Maybe Tile
whoWon xs = asum . map winner $ diag : anti : rows ++ cols

where rows = xs
cols = transpose xs
diag = zipWith (!!) xs [0..]
anti = zipWith (!!) xs [2,1..]

winner [O, O, O] = Just O
winner [X, X, X] = Just X
winner _ = Nothing

fillTile :: Int -> Int -> Tile -> Board -> Board
fillTile row col = nth row . nth col . const

isOver :: Board -> Bool
isOver = all (all (/= Empty))

overMsg :: Maybe Tile -> String
overMsg (Just x) = "Winner: " ++ (showTile x)
overMsg Nothing = "Game Over! No winner."

inRange :: Int -> Bool
inRange x = x >= 0 && x Board -> IO ()
gameLoop player board
| isOver board || winner /= Nothing = putStrLn $ overMsg winner
| otherwise = do

putStrLn $ showBoard board
putStrLn $ "Now player " ++ showTile player

putStr "Row: "
row <- fmap read getLine

putStr "Col: "
col <- fmap read getLine

if inRange row && inRange col && (board !! row !! col == Empty)

then gameLoop (if player == O then X

Solution

Some thoughts about your Tic-Tac-Toe version.

You made a confusion between Tile and Player. You assume a Player is the same as a Tile but it is not. A Tile can be Empty but a Player cannot be empty. You should separate these two notions. It will make your declarations clearer on what you expect. What if I call "gameLoop Empty startBoard" ?

The putStr function does not flush immediately its output in a compiled program. Your program asks the user for a row and a column and displays these labels after the user inputs.

Is it natural to ask for the row before the column ?

You have intertwined the game logic with the user interface in the gameLoop function. What will you do if you want to implement a computer player ? Or have a GUI to play the game ? You will have to patch your gameLoop function though the game logic has not changed.

Your solution is locked to the fact that your board is a 3×3 grid with a 3 tiles win. For example, your inRange functions works for row indices AND column indices, which are not the same. You use the constants 2 and 3 in your code (inRange, startBoard and whoWon), but nothing implies a relation between them. The 3 rows and 3 columns are characteristics of the Board, not of your functions.

Messages could be more specific. When you tell the user "Invalid position", there’s no way to know if it’s because the coordinates are out of bounds or because the tile has already been played.

Note: there are many Tic-Tac-Toe game related questions on CodeReview, you should take a look at:

  • Tic Tac Toe game in Haskell (a brute first attempt)



  • Tic Tac Toe game in haskell follow up (its follow up)



  • TicTacToe in Haskell (a version using Lens)



  • m,n,k-game (i.e. generalized tic-tac-toe) (its generalization)

Context

StackExchange Code Review Q#98725, answer score: 2

Revisions (0)

No revisions yet.