diff --git a/.obelisk/impl/.attr-cache/command.out b/.obelisk/impl/.attr-cache/command.out new file mode 120000 index 0000000..55c029c --- /dev/null +++ b/.obelisk/impl/.attr-cache/command.out @@ -0,0 +1 @@ +/nix/store/q7dazghyvyk3qwbgs253w4r297dirzyj-obelisk-command-0.9.0.1 \ No newline at end of file diff --git a/src/Display.hs b/src/Display.hs new file mode 100644 index 0000000..3923384 --- /dev/null +++ b/src/Display.hs @@ -0,0 +1,33 @@ +module Display (printBoard, displayHelp) where + +import GameLogic (Board, Player(..)) + +printBoard :: Board -> IO () +printBoard board = putStrLn $ unlines $ map (unwords . map cellToChar) board + where + cellToChar Nothing = "." + cellToChar (Just X) = "X" + cellToChar (Just O) = "O" + +displayHelp :: IO () +displayHelp = do + putStrLn "Here are the instructions to play the game:" + putStrLn "" + putStrLn "1. How to start the game:" + putStrLn " - Select start" + putStrLn "2. How to make a move:" + putStrLn " - The board is represented as a 3x3 grid with numbered positions:" + putStrLn " 1 | 2 | 3" + putStrLn " ---+---+---" + putStrLn " 4 | 5 | 6" + putStrLn " ---+---+---" + putStrLn " 7 | 8 | 9" + putStrLn " - Enter the number corresponding to the position where you want to place your mark (X or O)." + putStrLn " - For instance when Player X provides 1 1 the first cell will be occupied as follows:" + putStrLn " X | | " + putStrLn " ---+---+---" + putStrLn " | | " + putStrLn " ---+---+---" + putStrLn " | | " + putStrLn "" + putStrLn "Enjoy the game! May the best player win!" diff --git a/src/GameLogic.hs b/src/GameLogic.hs new file mode 100644 index 0000000..c710724 --- /dev/null +++ b/src/GameLogic.hs @@ -0,0 +1,69 @@ +module GameLogic where + +data Player = X | O deriving (Eq, Show) +type Position = (Int, Int) +type Board = [[Maybe Player]] + +data GameState = InProgress | Draw | Won Player deriving (Show) +data Move = Move Player Position deriving (Show) + +data Game = Game { + board :: Board, + currentPlayer :: Player, + state :: GameState +} deriving (Show) + +emptyBoard :: Board +emptyBoard = replicate 3 (replicate 3 Nothing) + +newGame :: Game +newGame = Game { board = emptyBoard, currentPlayer = X, state = InProgress } + +makeMove :: Game -> Move -> Either String Game +makeMove game (Move player (row, col)) + | row < 0 || row >= 3 || col < 0 || col >= 3 = Left "Move out of bounds. Try again." + | board game !! row !! col /= Nothing = Left "Cell already occupied. Try again." + | otherwise = Right $ game { + board = updatedBoard, + currentPlayer = nextPlayer (currentPlayer game), + state = newState + } + where + updatedBoard = take row (board game) + ++ [take col (board game !! row) ++ [Just player] ++ drop (col + 1) (board game !! row)] + ++ drop (row + 1) (board game) + newState = if checkWin updatedBoard player then Won player + else if isDraw updatedBoard then Draw + else InProgress + +checkWin :: Board -> Player -> Bool +checkWin board player = any (all (== Just player)) (board ++ transpose board ++ diagonals board) + +isDraw :: Board -> Bool +isDraw board = all (/= Nothing) (concat board) + +nextPlayer :: Player -> Player +nextPlayer X = O +nextPlayer O = X + +diagonals :: Board -> [[Maybe Player]] +diagonals board = [mainDiagonal board, antiDiagonal board] + +mainDiagonal :: Board -> [Maybe Player] +mainDiagonal board = [board !! i !! i | i <- [0..n-1]] + where n = length board + +antiDiagonal :: Board -> [Maybe Player] +antiDiagonal board = [board !! i !! (n - i - 1) | i <- [0..n-1]] + where n = length board + +transpose :: [[a]] -> [[a]] +transpose [] = [] +transpose x + | all null x = [] + | otherwise = map safeHead x : transpose (map safeTail x) + where + safeHead [] = error "Unexpected empty list in safeHead" + safeHead (y:_) = y + safeTail [] = [] + safeTail (_:ys) = ys diff --git a/src/Main.hs b/src/Main.hs index 366f6bb..3515cf6 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,15 +1,21 @@ module Main where import System.IO (hFlush, stdout) +import GameLogic +import Display +import Data.Char (isDigit) main :: IO () main = do - putStrLn "Welcome to Tic Tac Toe!" + putStrLn "========================================" + putStrLn " Welcome to Tic Tac Toe game! " + putStrLn " Enjoy to the fullest!!!!! " + putStrLn "========================================" loop -loop :: IO () +loop :: IO() loop = do - putStr "Enter command: " + putStr "Enter the command (start, help, exit): " hFlush stdout input <- getLine isLooping <- handleInput input @@ -19,8 +25,53 @@ loop = do handleInput :: String -> IO Bool handleInput "exit" = do - putStrLn "Goodbye!" + putStrLn "Goodbye see you next time!" pure False -handleInput input = do - putStrLn $ "You entered: " ++ input +handleInput "help" = do + Display.displayHelp pure True +handleInput "start" = do + gameLoop newGame + pure True +handleInput _ = do + putStrLn "Invalid command! Please enter 'start', 'help' or 'exit'." + pure True +gameLoop :: Game -> IO () +gameLoop game = do + printBoard (board game) + case state game of + InProgress -> do + putStrLn $ "Player " ++ show (currentPlayer game) ++ ", make your move (row and column): " + hFlush stdout + input <- getLine + case parseMove input of + Just (r, c) -> + case makeMove game (Move (currentPlayer game) (r, c)) of + Left err -> putStrLn err >> gameLoop game + Right newGameState -> gameLoop newGameState + Nothing -> putStrLn "Invalid input! Enter a valid input(row and column between 1 to 3)." >> gameLoop game + Draw -> putStrLn "It's a draw!" >> restartGame + Won player -> do + putStrLn $ "Player " ++ show player ++ " wins!" + restartGame + + +restartGame :: IO () +restartGame = do + putStrLn "Do you want to play again? (yes/no)" + hFlush stdout + response <- getLine + if response == "yes" + then gameLoop newGame + else if response == "no" + then putStrLn "Thanks for playing, always enjoy to play anytime!" + else do + putStrLn "Invalid choice. Please type 'yes' or 'no'." + restartGame + +parseMove :: String -> Maybe Position +parseMove input = case words input of + [r, c] -> if all isDigit r && all isDigit c + then Just (read r - 1, read c - 1) + else Nothing + _ -> Nothing \ No newline at end of file diff --git a/tic-tac-toe.cabal b/tic-tac-toe.cabal index 55513e5..206c599 100644 --- a/tic-tac-toe.cabal +++ b/tic-tac-toe.cabal @@ -9,5 +9,7 @@ cabal-version: >=1.10 executable tic-tac-toe main-is: Main.hs hs-source-dirs: src + other-modules: Display + GameLogic default-language: Haskell2010 build-depends: base >=4.7 && <5