Skip to main content
added 13 characters in body
Source Link
Martin R
  • 24.2k
  • 2
  • 38
  • 96
  • func calculateNextLocation() operates on a coordinate, not on the game. This could be made a

      mutating func movenext(dirdirection: Direction) -> Coord { ... }
    

method of struct Coord.

  • func calculateNextLocation() operates on a coordinate, not on the game. This could be made a

      mutating func move(dir: Direction) { ... }
    

of struct Coord.

  • func calculateNextLocation() operates on a coordinate, not on the game. This could be made a

      func next(direction: Direction) -> Coord { ... }
    

method of struct Coord.

Source Link
Martin R
  • 24.2k
  • 2
  • 38
  • 96

The raw values of enum Direction are not used at all, you can simplify the definition to

enum Direction {
    case up
    case down
    case left
    case right
}

The x/y values of struct Coord are never mutated, so you can declare them as constants with let. The init method is not needed because there is a default member-wise initializer:

struct Coord {
    let x: Int
    let y: Int
}

Your code can be simplified at many places if you make the Coord type Equatable:

struct Coord: Equatable {
    let x: Int
    let y: Int

    static func ==(lhs: Coord, rhs: Coord) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

For example,

if nextCoord.x == food.x && nextCoord.y == food.y

becomes

if nextCoord == food

and

for node in snake {
    if node.x == coord.x && node.y == coord.y {
        return true
    }
}
return false

becomes

return snake.contains(node)

so that func isCrossed() is not really needed anymore:

let nextCoord = calculateNextLocation()
if !snake.contains(nextCoord) {
    if nextCoord == food {
        food = generateNewFoodCoords()
    } else {
        snake.removeLast()
    }
    snake.insert(nextCoord, at: 0)
} else  ...

This also simplifies the code in func drawMap(). In addition, String(repeating:count:) can be used here to draw the horizontal lines (so that it works for other map widths as well):

func drawMap() {
    print(String(repeating: "\n", count: 22))
    print("+" + String(repeating: "-", count: MAPWIDTH) + "+")
    for y in 0 ..< MAPHEIGHT {
        print("|", terminator:"")
        for x in 0 ..< MAPWIDTH {
            let coord = Coord(x: x, y: y)
            if snake.contains(coord) {
                print("+", terminator:"")
            } else if coord == food {
                print("X", terminator:"")
            } else {
                print(" ", terminator:"")
            }
        }
        print("|\n", terminator:"")
    }
    print("+" + String(repeating: "-", count: MAPWIDTH) + "+")
}

Next,

var snake = Array<Coord>(arrayLiteral: Coord(x: 0, y: 2), Coord(x: 0, y: 1), Coord(x: 0, y: 0))

can be written shorter as

var snake = [ Coord(x: 0, y: 2), Coord(x: 0, y: 1), Coord(x: 0, y: 0) ]

The dummy return in

print("Death: You went off the map!")
death()
return Coord(x: 0, y: 0)//compiler doesn't know that death() will self destruct

is not needed because you can tell the compiler that death() will never return:

func death() -> Never {
    exit(1)
}

I would pass the reason as a parameter:

func death(_ reason: String) -> Never {
    print("Death: \(reason)")
    exit(1)
}

so that you can call

death("You went off the map!")

In func playGameManually() I would use a switch statement to handle the possible inputs, and a boolean flag for the "running" state:

func playGameManually() {
    let g = Game()
    var running = true
    while running {
        g.drawMap()
        switch readLine() ?? "" {
        case "w":
            g.moveUp()
        case "a":
            g.moveLeft()
        case "s":
            g.moveDown()
        case "d":
            g.moveRight()
        case "q":
            running = false
        default:
            break // ignore all other input
        }
    }
}

Further suggestions:

  • func calculateNextLocation() operates on a coordinate, not on the game. This could be made a

      mutating func move(dir: Direction) { ... }
    

of struct Coord.

  • Make MAPWIDTH / MAPHEIGHT properties of the game instead of global variables.