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.