DEV Community

Discussion on: Daily Challenge #79 - Connect Four

Collapse
 
earlware profile image
EarlWare

A Swift solution:

import Foundation

//  possible winning moves in the form of [ [deltaRow, deltaCol] ]
let acrossWinCords = [[0, 0], [0, 1], [0, 2], [0, 3]]
let vertWinCords = [[0, 0], [-1, 0], [-2, 0], [-3, 0]]
let diagUpWinCords = [[0, 0], [-1, 1], [-2, 2], [-3, 3]]
let diagDownWinCords = [[0, 0], [1, 1], [2, 2], [3, 3]]

let red    = Character("R")
let yellow = Character("Y")
let blank  = Character(" ")

// helper function to initalize a gameboard with all blanks.
func initBoard() -> [[Character]] {
    let newRow = [Character](repeating: blank, count: 7)
    let newBoard = [[Character]](repeating: newRow, count: 6)

    return newBoard
}

// helper function to neatly print out the board
func printBoard(_ board:[[Character]]) {

    print("Row   A    B    C    D    E    F    G")
    for index in 0..<board.count {
        print("", index, "",board[index])
    }
}

// helper function, checks to make sure its within bounds of the game board.
func validMove(_ row:Int, _ col:Int) -> Bool {
    if (row >= 0 && col >= 0) &&
       (row <= 5 && col <= 6) {
        return true
    }
    return false
}


func testForWin(_ board:[[Character]]) -> Character {

    // tests for a winning streak from the bottom left and continues left, then steps up
    for row in (0..<board.count).reversed() {
        for col in 0..<board[row].count {
            var winner = testWinType(row, col, board, acrossWinCords)
            if winner != blank {
                return winner
            }

            winner = testWinType(row, col, board, vertWinCords)
            if winner != blank {
                return winner
            }

            winner = testWinType(row, col, board, diagUpWinCords)
            if winner != blank {
                return winner
            }

            winner = testWinType(row, col, board, diagDownWinCords)
            if winner != blank {
                return winner
            }
        }
    }

    return blank
}

func testWinType(_ row:Int, _ col:Int, _ board:[[Character]], _ winCords:[[Int]]) -> Character {
    var countYellow = 0
    var countRed = 0

    // tests the moves from current location along the path defined in given winCords
    for move in winCords {
        let x = row+move[0]
        let y = col+move[1]

        if validMove(x, y) {
            let slot = board[x][y]
            if slot == blank {
                break  // if there is a blank, theres no possibility of a win
            } else if slot == red {
                countRed += 1
            } else if slot == yellow {
                countYellow += 1
            }
        } else {
            break // if there is an invalid move, theres no possiblity of a win
        }
    }

    if countRed == 4 {
        return red
    } else if countYellow == 4 {
        return yellow
    }
    return blank
}

func insert(_ aMove:String, _ board:[[Character]]) -> [[Character]] {

    // split the move into a column letter and player color.  Invalid inputs WILL break this.
    let move = aMove.split(separator: Character("_"), maxSplits: 2, omittingEmptySubsequences: true)
    let letter = move[0]
    let player = move[1]

    var col = 0

    // explictly decipher Letters into column index value.
    switch letter {
    case "A":
        col = 0
    case "B":
        col = 1
    case "C":
        col = 2
    case "D":
        col = 3
    case "E":
        col = 4
    case "F":
        col = 5
    case "G":
        col = 6
    default:
        // unexpected values shouldnt change the state of the board at all.
        return board
    }

    var newBoard = board

    // iterate through the board rows starting at the bottom in the given column
    for row in (0..<board.count).reversed() {
        if board[row][col] == blank {
            if player == "Red" {
                newBoard[row][col] = red
                return newBoard
            } else if player == "Yellow" {
                newBoard[row][col] = yellow
                return newBoard
            }
        }
    }

    // unexpected values shouldnt change the state of the board at all.
    return board
}


/*
 ConnectFour challenge function.

 @param pieceList:[String] an ordered list of moves in the format of ColLetter_Color(ie "D_Red" or "F_Yellow")

 @return String containing the winner, or Draw

 */
func connectFour(_ pieceList:[String]) -> String {
    // create a blank playfield
    var connect4Grid:[[Character]] = initBoard()

    //  add pieces to the board.
    for move in pieceList {
        connect4Grid = insert(move, connect4Grid)
    }

    printBoard(connect4Grid)

    let winner = testForWin(connect4Grid)

    if winner == red {
        return "Red"
    } else if winner == yellow {
        return "Yellow"
    }

    return "Draw"
}


let example1 = ["A_Red",
                "B_Yellow",
                "A_Red",
                "B_Yellow",
                "A_Red",
                "B_Yellow",
                "G_Red",
                "B_Yellow"]

let example2 = ["A_Red",
                "A_Yellow",
                "B_Red",
                "B_Yellow",
                "C_Red",
                "C_Yellow",
                "D_Red",
                "D_Yellow"]

let example3 = ["A_Red",
                "G_Yellow",
                "B_Red",
                "F_Yellow",
                "C_Red",
                "E_Yellow"]


print("Example1 results:   ", connectFour(example1))
print("\n")
print("Example2 results:   ", connectFour(example2))
print("\n")
print("Example3 results:   ", connectFour(example3))
print("\n")

Output:

Row   A    B    C    D    E    F    G
 0  [" ", " ", " ", " ", " ", " ", " "]
 1  [" ", " ", " ", " ", " ", " ", " "]
 2  [" ", "Y", " ", " ", " ", " ", " "]
 3  ["R", "Y", " ", " ", " ", " ", " "]
 4  ["R", "Y", " ", " ", " ", " ", " "]
 5  ["R", "Y", " ", " ", " ", " ", "R"]
Example1 results:    Yellow


Row   A    B    C    D    E    F    G
 0  [" ", " ", " ", " ", " ", " ", " "]
 1  [" ", " ", " ", " ", " ", " ", " "]
 2  [" ", " ", " ", " ", " ", " ", " "]
 3  [" ", " ", " ", " ", " ", " ", " "]
 4  ["Y", "Y", "Y", "Y", " ", " ", " "]
 5  ["R", "R", "R", "R", " ", " ", " "]
Example2 results:    Red


Row   A    B    C    D    E    F    G
 0  [" ", " ", " ", " ", " ", " ", " "]
 1  [" ", " ", " ", " ", " ", " ", " "]
 2  [" ", " ", " ", " ", " ", " ", " "]
 3  [" ", " ", " ", " ", " ", " ", " "]
 4  [" ", " ", " ", " ", " ", " ", " "]
 5  ["R", "R", "R", " ", "Y", "Y", "Y"]
Example3 results:    Draw


Program ended with exit code: 0

I feel like its a bit of a mess, but it works! I didn't do any input validation, and I also assumed that there would only be one winner per move list given for the input, so invalid move list structure and move lists with multiple winners possible to be found might be unreliable. I just spent too much time on this already so I'm cutting my losses! As always, any constructive criticism is always very much appreciated.