3
\$\begingroup\$

I want to learn Haskell and figured the best way to do it is to write a simple TicTacToe implementation. However the result feels very inelegant and I think it might be the best playground for your rants and give me an opportunity to learn some lessons.

{-|
TODO: -filter symmetric states from game tree
|-}

module Tictactoe  where

import Data.List

initialBoardStr = "         "    

data Tree a = Node a [Tree a]  deriving (Show)

type Board = String
type Points = Int
type Pos = Int

expandGameTree :: Board -> Tree Board
expandGameTree state = Node state $ succesors state
        where succesors cur_state = [Node s (succesors s) 
                | s <- indicateAllowedMoves cur_state, s /= []]

evalBoard :: Board -> Points
evalBoard board
        | win && nextSymb board == "X" = 100
        | win && nextSymb board == "O" = (-100)
        | otherwise = 0
        where win = winingBoard board

evalState :: Tree Board -> Points
evalState (Node a []) = evalBoard a
evalState (Node a (x:xs)) = (minimax a) [evalState b | b <- (x:xs)]

minimax board
    | nextSymb board == "X" = minimum
    | nextSymb board == "O" = maximum

nextMove = (makeNextMove . expandGameTree)

numerate :: Board -> Board
numerate board
    | length (findIndices (==' ') board) == 0 = board
    | otherwise = numerate $ replace board (idx board) (show (idx board)) 
        where idx board = (findIndices (==' ') board) !! 0

makeNextMove (Node a (x:xs)) = getBoardFromTree ((x:xs) !! (maxIndex (rates (x:xs))))
    where maxIndex xs = head $ filter ((== maximum xs) . (xs !!)) [0..]
          rates (x:xs) = [evalState c | c <- (x:xs)]

getBoardFromTree (Node a (x:xs)) = a
getBoardFromTree (Node a []) = a

showBoard :: Board -> String
showBoard board =  "|" ++ (row 0) ++ "|\n|" ++ (row 1) ++ "|\n|" ++ (row 2) ++ "|\n"
           where numeratedBoard = numerate board
                 row i = takeRange numeratedBoard [3*i..(3*i)+2]

indicateAllowedMoves :: Board -> [Board]
indicateAllowedMoves board
        | winingBoard board = [] 
        | otherwise = [replace board i symb | i <- [0..8], board !! i == ' '] 
        where symb = nextSymb board

replace :: String -> Pos -> String -> String
replace str idx char = take idx str ++ char ++ drop (idx+1) str

isAllowedMove :: Board -> Pos -> Bool 
isAllowedMove board pos = elem next allowed
        where next = insertInBoardStr board pos
              allowed = indicateAllowedMoves board

winingRow :: String -> Bool
winingRow row 
    | row == "XXX" || row == "OOO" = True
    | otherwise = False

rows :: Board -> [String]
rows board = row ++ cols ++ diag
    where row = [takeRange board [i..(i+2)] | i <- [0,3,6]]
          cols = [takeRange board $ map (+i) [0,3,6] | i <- [0..2]]
          diag = [takeRange board [0,4,8]] ++ [takeRange board [2,4,6]]

winingBoard :: Board -> Bool
winingBoard board = any winingRow $ rows board

--takeRange :: String -> [Int] -> String
takeRange str range = [str !! i | i <- range]

insertInBoardStr :: Board -> Pos -> Board
insertInBoardStr board pos = replace board pos symb
        where symb = nextSymb board

nextSymb :: Board -> String
nextSymb board 
    | countLetters board 'X' ==  countLetters board 'O' = "X"
    | otherwise = "O"
    where countLetters str c = length $ filter (== c) str

curSymb :: Board -> String
curSymb board
    | nextSymb board == "X" = "O"
    | otherwise = "X"


isEquivivalent :: String -> String -> Bool
isEquivivalent row1 row2
        | row1 == row2 = True
        | row1 == reverse row2 = True
        | otherwise = False

play board = do
    putStr "\ESC[2J"
    putStrLn $ showBoard board
    if (winingBoard board) == True then
        putStrLn ("Player: " ++  (curSymb board) ++ " wins")
     else
        putStrLn "Make your move: "
    input <- getLine
    let pos = (read input :: Int)
    if (isAllowedMove board pos) == True then
        play (nextMove (insertInBoardStr board pos))
     else
        play board

main = play initialBoardStr
\$\endgroup\$
3
  • 3
    \$\begingroup\$ a different implementation of the same "problem": codereview.stackexchange.com/questions/68345/… \$\endgroup\$
    – Vogel612
    Nov 5, 2014 at 8:50
  • \$\begingroup\$ @Vogel612 The question which you linked to is mine, and I implemented a Tic Tac Toe game. On the other hand, OP implemented a Tic Tac Toe AI. Both are related but isn't really the same. \$\endgroup\$
    – wei2912
    Nov 5, 2014 at 14:15
  • \$\begingroup\$ @wei2912 ohh carp.. I don't speak haskell... but the title and text didn't make it apparent your and his "problem" are different :( That's why I prefer to add a "What does my code do?" to questions I ask ;) Thank you for clarifying \$\endgroup\$
    – Vogel612
    Nov 5, 2014 at 14:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.