Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

The purpose of this assignment is to understand the concepts of functional progr

ID: 3745124 • Letter: T

Question

 The purpose of this assignment is to understand the concepts of functional programming and have a taste of it in Haskell [1].  You are to to write a Haskell program for playing Connect Four games. The Connect Four game is a two-player connection game in which the players take turns dropping their discs from the top into a seven-column, six-row vertically suspended grid [Wikipedia]. The pieces fall straight down, occupying the next available space within the column. The aim of the game is to connect four of one's own discs next to each other vertically, horizontally, or diagonally before the opponent.  As in the AspectJ project, you will use the model-view-control (MVC) pattern. Your Haskell program must consist of two modules, one for the game model (M) and the other for a console-based UI (VC). To write the second module, you will need to learn about the basic I/O operations in Haskell (refer to Haskell tutorials such as https://www.haskell.org/tutorial/io.html). In Haskell, an I/O function may call non-I/O functions, but a non-I/O function may not call I/O functions. You shouldn't include any I/O function in the first module (M).  Do not use any library function other than the standard Prelude functions that are automatically imported into every Haskell module (see Part II below for an exception).  Part I. (65 points) Develop a Haskell module named Board to model a Connect Four board and two players. As said earlier, the idea is to separate the model part of your program from the UI part to be developed in Part II below. Thus, no UI (especially, I/O) function should be defined in this module. The following functions are suggested to be written in this module. They will be useful in writing the UI module in Part II.  1. (8 points) Creating a board and players.     mkBoard m n        Return an empty mxn board, where m and n are positive numbers      denoting the numbers of columns and rows, respectively. A 1-based      index will be used to denote and access a specific column and row      of a board. But, it is up to you to come up with a concrete      representation of a board, e.g., a nested list.        mkPlayer = 1      Return the first player. You may choose your own representation,      e.g., 1 as done above.       mkOpponent = 2      Return the second player (the opponent). You may choose your own      representation, e.g., 2 as done above.  2. (20 points) Checking a board and dropping a disc     dropInSlot bd i p      Drop a player p's disc in a slot (column) i of a board bd. The      specified slot is assumed to have an empty place to hold the      dropped disc (see isSlotOpen below).     isSlotOpen bd i      Is a slot (column) i of a board bd open in that it can hold an      additional disc?     numSlot bd      Return the number of columns of a board bd.     isFull bd      Is the given board bd full in that there is no empty place?  3. (25 points) Determining the outcome     isWonBy bd p      Is the game played on a board bd won by a player p?  4. (12 points) Converting a board to a string for printing     boardToStr playerToChar bd       Return a string representation of a board bd. It is a      higher-order function. The first argument (playerToChar) is a      function to convert a player to a character representation, e.g.,      'O' or 'X'. A formatted sample return value is shown below      (assuming that one player is mapped to 'O' and the other to 'X'      by the playerToChar function) .       ". . . . . . .        . . . . . . .        . . . . . . .        O . . . . . .        O X . . . . .        O X O X O X ."  Part II. (35 points) Develop a Haskell module named Main that provides a console-based UI for playing a Connect Four game. Define the following functions.  1. (15 points) Reading user inputs and printing outputs.     playerToChar p       Return a character representation of a player p. It returns a      Char value. This function is used to print the current state of a      board (see the boardToStr function above).     readSlot bd p       Read a 1-based index of an open slot of a board bd for a player p      to drop her disc. The function reads inputs from the standard      input (stdin) and returns an IO value such as IO(Int) or      IO(Integer).     The following IO functions may be useful.     putStr, putStrLn - print a string to the standard out    getLine - read a line from the standard in    reads:: [(Integer, String)] - parse an Integer from a string     For example, the following IO function reads lines from stdin    until a positive Integer is read.     getX = do      putStrLn "Enter a positive value?"      line <- getLine      let parsed = reads line :: [(Integer, String)] in        if length parsed == 0        then getX'        else let (x, _) = head parsed in          if x > 0           then return x          else getX'      where        getX' = do          putStrLn "Invalid input!"          getX  2. (20 points) Playing a game     main      Main function to play a Connect Four game by two players.      It returns an IO() value. The dimension of the board is 7x6, and      user inputs are read from the standard input (see the readSlot      function below) and outputs like the board state and the game      outcome are printed on the standard output. For Haskell I/O, you      will need to import the System.IO module.   Part III. (20+ bonus points) You may earn bonus points by implementing the following features. However, your bonus points count only when you complete all the previous functions for regular points.      1. (5 points) Let a player be able to quit a game by entering a    special value, say -1, for the slot index in the readSlot function    above.  2. (15+ points) Support a strategy game to play a game against a    computer by implementing a computer move strategy. An easiest way    to add this feature is to define a strategy function, say    genRandomSlot, and use it in place of the readSlot function when it    is the opponent's turn. Use the System.Random module to generate a    random value, e.g., x <- randomRIO(1,7) to generate a random number    between 1 and 7, inclusive.

Explanation / Answer


module Main (main) where

import Data.List
import System.IO
import Board
import System.Random

-- ---------- 1 ----------

main :: IO()
-- main function to player Connect Four game on a 7x6 board
main = do
    strategy <- askForStrategy
    let bd = mkBoard 7 6
    gameState <- gameLoop bd mkPlayer strategy    -- Start playing the game, store who won in gameState (refer to gameLoop return values)
    if gameState == 0
        then putStrLn("Game ends in a tie! There's no losers! Or really there are no winners. You both should be ashamed of yourselves.")
        else do
           if gameState == (-1)
                then putStrLn("Player quit the game. What a quitter.")
                else do
                    if gameState == mkPlayer
                        then putStrLn("Player " ++ (show mkPlayer) ++ " won! Congratulations!")
                        else do
                            if gameState == mkOpponent
                                then putStrLn("Player " ++ (show mkOpponent) ++ " won! Congratulations!")
                                else putStrLn("Game ended in an error. This should not be possible.")

askForStrategy :: IO (Int)
-- Asks user for a type of strategy, asks again if input is invalid
askForStrategy = do
    putStrLn "Do you want to human vs. human (1) or human vs. computer (2)?"
    userInput <- getLine
    let parsed = reads userInput :: [(Int, String)]    -- parse input as int
    if length parsed == 0
        then askForStrategy'
        else do
            let (x, _) = head parsed    -- only take first value if multiple int digits passed in
            if x == 1
                then return 1    -- Human vs. human
                else do
                    if x == 2    -- Human vs. computer
                        then return 2
                        else askForStrategy'

askForStrategy' :: IO (Int)
-- helper function to handle invalid inputs for askForStrategy
askForStrategy' = do
    putStrLn "Invalid input!"
    askForStrategy

gameLoop :: [Int] -> Int -> Int -> IO (Int)
-- bd: board, p: player
-- Loops through the game, alternating players, and dropping their token in the slot
-- returns 0 for tie, 1 for player1 win, 2 for player2 win
gameLoop bd p strategy = do
    putStrLn("Player " ++ (show p) ++ "'s turn!")
    if isFull bd
        then return 0    -- tie
        else do
            if strategy == 2 && p == mkOpponent    -- random strategy replaces mkOpponent's readSlot function with genRandomSlot function
                then do
                    slot <- (genRandomSlot bd p)
                    gameLoopHelper bd slot p strategy
                else do
                    slot <- (readSlot bd p)
                    gameLoopHelper bd slot p strategy

gameLoopHelper :: [Int] -> Int -> Int -> Int -> IO (Int)
-- bd: board, slot: column to drop token into, p: player
-- refactored out this code so that I don't have to write it twice for readSlot and genRandomSlot in gameLoop function
gameLoopHelper bd slot p strategy = do
    if slot == (-1)    -- player quit game
        then return (-1)
        else do
            let bdMod = dropInSlot bd slot p
            if isWonBy bdMod mkPlayer
                then do
                    putStrLn(boardToStr playerToChar bdMod)
                    return mkPlayer    -- return 1 for player1 win
                else do
                    if isWonBy bdMod mkOpponent
                        then do
                            putStrLn(boardToStr playerToChar bdMod)
                            return mkOpponent    -- return 2 for player2 win
                        else do
                            putStrLn(boardToStr playerToChar bdMod)
                            if p == mkPlayer    -- alternates players
                                then gameLoop bdMod mkOpponent strategy
                                else gameLoop bdMod mkPlayer strategy


readSlot :: [Int] -> Int -> IO (Int)
-- bd: board, p: player
-- Asks user for a slot input, "loops" through and asks again if input is invalid
-- assumes board is not full
readSlot bd p = do
    putStrLn "Enter a slot position (1-7), -1 to quit: "
    userInput <- getLine
    let parsed = reads userInput :: [(Int, String)]    -- parse input as int
    if length parsed == 0
        then readSlot' bd p
        else do
            let (x, _) = head parsed    -- only take first value if multiple int digits passed in
            if x > 0 && x < ((numSlot bd) + 1) && isSlotOpen bd x   -- 0 < x < 8
                then return x
                else do
                    if x == (-1)
                        then return (-1)
                        else readSlot' bd p

readSlot' :: [Int] -> Int -> IO (Int)
-- bd: board, p: player
-- helper function to handle invalid inputs for readSlot
readSlot' bd p = do
    putStrLn "Invalid input!"
    readSlot bd p

genRandomSlot :: [Int] -> Int -> IO (Int)
-- bd: board, p: player
-- Generates a random slot for the AI to drop a token into
-- assumes board is not full
genRandomSlot bd p = do
    x <- randomRIO(1,7)    -- Generate random number between 1-7 inclusive
    if isSlotOpen bd x
        then return x
        else genRandomSlot bd p

playerToChar :: Int -> Char
-- p: player
-- turns a value of a position in the board to it's respective character
playerToChar p
    | p == mkPlayer = 'X'
    | p == mkOpponent = 'O'
    | otherwise = '.'