# Console Based Snake Game

This is a simple program where the goal is to visualize a virtual game to be played by an AI. For the purposes of this post, the playGameManually() function was added as a way of testing the rest of the code. The program was built around an open ended framework that allows each element to be modified independently outside of regular gameplay while also making it simple when using manually.

import Foundation

// -- Constants -- //

let MAPWIDTH  = 50
let MAPHEIGHT = 20

// -- End Constants -- //

struct Coord {
var x: Int
var y: Int

init(x: Int, y: Int) {
self.x = x
self.y = y
}
}

enum Direction: Int {
case up    = 1
case down  = 2
case left  = 3
case right = 4
}

class Game {

var snake = Array<Coord>(arrayLiteral: Coord(x: 0, y: 2), Coord(x: 0, y: 1), Coord(x: 0, y: 0)) // A queue would be far better here but since swift dosen't have one build in we use an array for simplicity.
var food = Coord(x: 10, y: 10)
var direction = Direction.down

func move() {
func calculateNextLocation() -> Coord {
// This function calculates where the snake should move and makes sure that the next movement is not off the screen.  It does NOT check to see if a part has crossed or if it is touching food.

if direction == .down && snake.y < MAPHEIGHT - 1 {
return Coord(x: snake.x, y: snake.y + 1)
}
else if direction == .up && snake.y > 0 {
return Coord(x: snake.x, y: snake.y - 1)
}
else if direction == .left && snake.x > 0 {
return Coord(x: snake.x - 1, y: snake.y)
}
else if direction == .right && snake.x < MAPWIDTH - 1 {
return Coord(x: snake.x + 1, y: snake.y)
}
else {
print("Death: You went off the map!")
death()
return Coord(x: 0, y: 0)//compiler doesn't know that death() will self destruct
}
}

func isCrossed(coord: Coord) -> Bool {
for node in snake {
if node.x == coord.x && node.y == coord.y {
return true
}
}
return false
}

let nextCoord = calculateNextLocation()

if !isCrossed(coord: nextCoord) {
if nextCoord.x == food.x && nextCoord.y == food.y {//delete last node if not touching food
food = generateNewFoodCoords()
}
else {
snake.removeLast()
}
snake.insert(nextCoord, at: 0)
}
else {
death()
}

}

func generateNewFoodCoords() -> Coord {
return Coord(x: Int(arc4random_uniform(UInt32(MAPWIDTH))), y: Int(arc4random_uniform(UInt32(MAPHEIGHT))))
}

func moveUp() {
if direction != .down {
direction = .up
move();
}
else {
print("Death: You looped back over your tail!")
death()
}
}

func moveDown() {
if direction != .up {
direction = .down
move();
}
else {
print("Death: You looped back over your tail!")
death()
}
}

func moveLeft() {
if direction != .right {
direction = .left
move();
}
else {
print("Death: You looped back over your tail!")
death()
}
}

func moveRight() {
if direction != .left {
direction = .right
move();
}
else {
print("Death: You looped back over your tail!")
death()
}
}

func death() {
exit(1)
}

func drawMap() {
print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
print("+--------------------------------------------------+")
for y in 0 ..< MAPHEIGHT {
print("|", terminator:"")
for x in 0 ..< MAPWIDTH {
var hasPrinted = false
for node in snake {
if x == node.x && y == node.y {
print("+", terminator:"")
hasPrinted = true
break
}
}
if !hasPrinted {
if x == food.x && y == food.y {
print("X", terminator:"")
}
else {
print(" ", terminator:"")
}
}
}
print("|\n", terminator:"")
}
print("+--------------------------------------------------+")
}
}

func playGameManually() {
let g = Game()
g.drawMap()
var l = ""
while l != "q" {
if l == "w" {
g.moveUp()
}
else if l == "a" {
g.moveLeft()
}
else if l == "s" {
g.moveDown()
}
else if l == "d" {
g.moveRight()
}

g.drawMap()
}
}


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()
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

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


method of struct Coord.

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

the moveUp / moveDown etc can be more generic

func move(Direction nextDirection, Direction deathDirection){
if direction != deathDirection {
direction = nextDirection
move();
}
else {
print("Death: You looped back over your tail!")
death()
}
}


and then instead of moveUp() and others you give the direction as parameter:

move(.up,.down)