Skip to content

Commit

Permalink
Part18
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed May 25, 2020
1 parent ad697f4 commit 43ec2af
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 39 deletions.
138 changes: 138 additions & 0 deletions Source/Engine/MapGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// MapGenerator.swift
// Engine
//
// Created by Nick Lockwood on 09/05/2020.
// Copyright © 2020 Nick Lockwood. All rights reserved.
//

public struct MapGenerator {
public private(set) var map: Tilemap
private var rng: RNG
private var elevatorPosition: Vector!
private var playerPosition: Vector!
private var emptyTiles: [Vector] = []
private var wallTiles: [Vector] = []

public init(mapData: MapData, index: Int) {
self.map = Tilemap(mapData, index: index)
self.rng = RNG(seed: mapData.seed ?? .random(in: 0 ... .max))

// Find empty tiles
for y in 0 ..< map.height {
for x in 0 ..< map.width {
let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
if map[x, y].isWall {
if map[x, y] == .elevatorBackWall {
map[thing: x, y] = .switch
}
wallTiles.append(position)
} else {
if map[x, y] == .elevatorFloor {
elevatorPosition = position
}
switch map[thing: x, y] {
case .nothing:
emptyTiles.append(position)
case .player:
playerPosition = position
default:
break
}
}
}
}

// Add doors
for position in emptyTiles {
let x = Int(position.x), y = Int(position.y)
let left = map[x - 1, y], right = map[x + 1, y],
up = map[x, y - 1], down = map[x, y + 1]
if (left.isWall && right.isWall && !up.isWall && !down.isWall)
|| (!left.isWall && !right.isWall && up.isWall && down.isWall) {
add(.door, at: position)
}
}

// Add push-walls
for _ in 0 ..< (mapData.pushwalls ?? 0) {
add(.pushwall, at: wallTiles.filter { position in
let x = Int(position.x), y = Int(position.y)
guard x > 0, x < map.width - 1, y > 0, y < map.height - 1 else {
return false // Outer wall
}
let left = map[x - 1, y], right = map[x + 1, y],
up = map[x, y - 1], down = map[x, y + 1]
if left.isWall, right.isWall, !up.isWall, !down.isWall,
!map[x, y - 2].isWall, !map[x, y + 2].isWall {
return true
}
if !left.isWall, !right.isWall, up.isWall, down.isWall,
!map[x - 2, y].isWall, !map[x + 2, y].isWall {
return true
}
return false
}.randomElement(using: &rng))
}

// Add player
if playerPosition == nil {
playerPosition = emptyTiles.filter {
findPath(from: $0, to: elevatorPosition, maxDistance: 1000).isEmpty == false
}.randomElement(using: &rng)
add(.player, at: playerPosition)
}

// Add monsters
for _ in 0 ..< (mapData.monsters ?? 0) {
add(.monster, at: emptyTiles.filter {
(playerPosition - $0).length > 2.5
}.randomElement(using: &rng))
}

// Add medkits
for _ in 0 ..< (mapData.medkits ?? 0) {
add(.medkit, at: emptyTiles.randomElement(using: &rng))
}

// Add shotguns
for _ in 0 ..< (mapData.shotguns ?? 0) {
add(.shotgun, at: emptyTiles.randomElement(using: &rng))
}
}
}

private extension MapGenerator {
mutating func add(_ thing: Thing, at position: Vector?) {
if let position = position {
map[thing: Int(position.x), Int(position.y)] = thing
if let index = emptyTiles.lastIndex(of: position) {
emptyTiles.remove(at: index)
}
}
}
}

extension MapGenerator: Graph {
public typealias Node = Vector

public func nodesConnectedTo(_ node: Node) -> [Node] {
return [
Node(x: node.x - 1, y: node.y),
Node(x: node.x + 1, y: node.y),
Node(x: node.x, y: node.y - 1),
Node(x: node.x, y: node.y + 1),
].filter { node in
let x = Int(node.x), y = Int(node.y)
return map[x, y].isWall == false
}
}

public func estimatedDistance(from a: Node, to b: Node) -> Double {
return abs(b.x - a.x) + abs(b.y - a.y)
}

public func stepDistance(from a: Node, to b: Node) -> Double {
return 1
}
}
32 changes: 32 additions & 0 deletions Source/Engine/RNG.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// RNG.swift
// Engine
//
// Created by Nick Lockwood on 17/05/2020.
// Copyright © 2020 Nick Lockwood. All rights reserved.
//

let multiplier: UInt64 = 6364136223846793005
let increment: UInt64 = 1442695040888963407

public struct RNG {
private var seed: UInt64 = 0

public init(seed: UInt64) {
self.seed = seed
}

public mutating func next() -> UInt64 {
seed = seed &* multiplier &+ increment
return seed
}
}

public extension Collection where Index == Int {
func randomElement(using generator: inout RNG) -> Element? {
if isEmpty {
return nil
}
return self[startIndex + Index(generator.next() % UInt64(count))]
}
}
16 changes: 13 additions & 3 deletions Source/Engine/Tilemap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@

public struct MapData: Decodable {
fileprivate let tiles: [Tile]
fileprivate let things: [Thing]
fileprivate let things: [Thing]?
fileprivate let width: Int
public let seed: UInt64?
public let monsters: Int?
public let medkits: Int?
public let shotguns: Int?
public let pushwalls: Int?
}

public struct Tilemap {
private(set) var tiles: [Tile]
public let things: [Thing]
private var things: [Thing]
public let width: Int
public let index: Int

public init(_ map: MapData, index: Int) {
self.tiles = map.tiles
self.things = map.things
self.things = map.things ?? Array(repeating: .nothing, count: map.tiles.count)
self.width = map.width
self.index = index
}
Expand All @@ -40,6 +45,11 @@ public extension Tilemap {
set { tiles[y * width + x] = newValue }
}

subscript(thing x: Int, y: Int) -> Thing {
get { return things[y * width + x] }
set { things[y * width + x] = newValue }
}

func tileCoords(at position: Vector, from direction: Vector) -> (x: Int, y: Int) {
var offsetX = 0, offsetY = 0
if position.x.rounded(.down) == position.x {
Expand Down
6 changes: 3 additions & 3 deletions Source/Engine/World.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public extension World {
for y in 0 ..< map.height {
for x in 0 ..< map.width {
let position = Vector(x: Double(x) + 0.5, y: Double(y) + 0.5)
let thing = map.things[y * map.width + x]
let thing = map[thing: x, y]
switch thing {
case .nothing:
break
Expand Down Expand Up @@ -330,7 +330,7 @@ public extension World {
}

func isDoor(at x: Int, _ y: Int) -> Bool {
return map.things[y * map.width + x] == .door
return map[thing: x, y] == .door
}

func door(at x: Int, _ y: Int) -> Door? {
Expand All @@ -349,7 +349,7 @@ public extension World {
}

func `switch`(at x: Int, _ y: Int) -> Switch? {
guard map.things[y * map.width + x] == .switch else {
guard map[thing: x, y] == .switch else {
return nil
}
return switches.first(where: {
Expand Down
8 changes: 8 additions & 0 deletions Source/Rampage.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
0108A66723F4D9370075E1AF /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D09AF022A481AB0052745A /* Color.swift */; };
0108A66923F4D9B70075E1AF /* Textures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0108A66823F4D9B70075E1AF /* Textures.swift */; };
0108A66F23F543750075E1AF /* Pathfinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0108A66E23F543740075E1AF /* Pathfinder.swift */; };
010978C124694477002CC646 /* MapGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010978C024694477002CC646 /* MapGenerator.swift */; };
0128F26223EEE7AE00439050 /* shotgunFire.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 0128F26123EEE7AE00439050 /* shotgunFire.mp3 */; };
0128F26423EEEB0A00439050 /* shotgunPickup.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 0128F26323EEEB0A00439050 /* shotgunPickup.mp3 */; };
012A0C4D22C96E150068E8EF /* Tile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0C4C22C96E150068E8EF /* Tile.swift */; };
Expand All @@ -23,6 +24,7 @@
012A0C9E22D47C220068E8EF /* Actor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0C9D22D47C220068E8EF /* Actor.swift */; };
012A0CA222D7AD0A0068E8EF /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A0CA122D7AD0A0068E8EF /* Animation.swift */; };
012DF10822E251CF00D52706 /* Effect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012DF10722E251CF00D52706 /* Effect.swift */; };
013BBC122470B3F200F14BBC /* RNG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013BBC112470B3F100F14BBC /* RNG.swift */; };
013D492523ED607D00763FCA /* medkit.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 013D492423ED607D00763FCA /* medkit.mp3 */; };
013D492723EE17C000763FCA /* Weapon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 013D492623EE17C000763FCA /* Weapon.swift */; };
01467C3E22E6F54600B5607D /* Easing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01467C3D22E6F54600B5607D /* Easing.swift */; };
Expand Down Expand Up @@ -121,6 +123,7 @@
0108A65A23F4D84C0075E1AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0108A66823F4D9B70075E1AF /* Textures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Textures.swift; sourceTree = "<group>"; };
0108A66E23F543740075E1AF /* Pathfinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pathfinder.swift; sourceTree = "<group>"; };
010978C024694477002CC646 /* MapGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapGenerator.swift; sourceTree = "<group>"; };
0128F26123EEE7AE00439050 /* shotgunFire.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = shotgunFire.mp3; sourceTree = "<group>"; };
0128F26323EEEB0A00439050 /* shotgunPickup.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = shotgunPickup.mp3; sourceTree = "<group>"; };
012A0C4C22C96E150068E8EF /* Tile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tile.swift; sourceTree = "<group>"; };
Expand All @@ -130,6 +133,7 @@
012A0C9D22D47C220068E8EF /* Actor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actor.swift; sourceTree = "<group>"; };
012A0CA122D7AD0A0068E8EF /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = "<group>"; };
012DF10722E251CF00D52706 /* Effect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Effect.swift; sourceTree = "<group>"; };
013BBC112470B3F100F14BBC /* RNG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNG.swift; sourceTree = "<group>"; };
013D492423ED607D00763FCA /* medkit.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = medkit.mp3; sourceTree = "<group>"; };
013D492623EE17C000763FCA /* Weapon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weapon.swift; sourceTree = "<group>"; };
01467C3D22E6F54600B5607D /* Easing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Easing.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -291,6 +295,7 @@
01EDA5DA2444DC2B00FC1795 /* Game.swift */,
01557ACF245109E600FF8FF0 /* HUD.swift */,
01D09B0222A4958E0052745A /* Input.swift */,
010978C024694477002CC646 /* MapGenerator.swift */,
012A0C6122CC200D0068E8EF /* Monster.swift */,
0108A66E23F543740075E1AF /* Pathfinder.swift */,
0159A3F423DEF636001EEB81 /* Pickup.swift */,
Expand All @@ -299,6 +304,7 @@
01D09B0422A5C9DB0052745A /* Ray.swift */,
01D09AFC22A4873B0052745A /* Rect.swift */,
01D09B0622A6E09A0052745A /* Rotation.swift */,
013BBC112470B3F100F14BBC /* RNG.swift */,
015A23C8230586E3004CBB78 /* Switch.swift */,
0199F56F23E1AFEA003E3F08 /* Sounds.swift */,
01D09B0A22A7F7570052745A /* Texture.swift */,
Expand Down Expand Up @@ -587,6 +593,7 @@
01557AD0245109E600FF8FF0 /* HUD.swift in Sources */,
012A0C6222CC200E0068E8EF /* Billboard.swift in Sources */,
013D492723EE17C000763FCA /* Weapon.swift in Sources */,
010978C124694477002CC646 /* MapGenerator.swift in Sources */,
01ADC64022B9846B00DC8AAD /* World.swift in Sources */,
0108A66723F4D9370075E1AF /* Color.swift in Sources */,
012A0C4D22C96E150068E8EF /* Tile.swift in Sources */,
Expand All @@ -597,6 +604,7 @@
012A0CA222D7AD0A0068E8EF /* Animation.swift in Sources */,
01D09B0522A5C9DB0052745A /* Ray.swift in Sources */,
01E3963A2342758D00D02236 /* Pushwall.swift in Sources */,
013BBC122470B3F200F14BBC /* RNG.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
34 changes: 5 additions & 29 deletions Source/Rampage/Levels.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[
{
"seed": 1,
"width": 8,
"monsters": 5,
"medkits": 2,
"shotguns": 1,
"pushwalls": 1,
"tiles": [
1, 3, 1, 1, 3, 1, 1, 1,
1, 0, 0, 2, 0, 0, 0, 1,
Expand All @@ -10,35 +15,6 @@
1, 0, 1, 2, 0, 0, 0, 1,
6, 5, 6, 1, 0, 4, 4, 1,
1, 7, 3, 1, 1, 3, 1, 1
],
"things": [
0, 0, 0, 0, 0, 0, 0, 0,
0, 2, 0, 0, 0, 0, 7, 0,
0, 0, 0, 0, 2, 0, 0, 0,
0, 6, 0, 3, 0, 0, 0, 0,
0, 0, 2, 0, 4, 0, 3, 0,
0, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 0, 2, 0,
0, 5, 0, 0, 0, 0, 0, 0
]
},
{
"width": 5,
"tiles": [
2, 1, 1, 6, 1,
1, 0, 4, 5, 7,
1, 1, 1, 6, 1,
2, 0, 0, 1, 3,
1, 0, 3, 1, 3,
1, 1, 1, 1, 1
],
"things": [
0, 0, 0, 0, 0,
0, 1, 3, 0, 5,
0, 4, 0, 0, 0,
0, 0, 2, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
]
}
]
4 changes: 3 additions & 1 deletion Source/Rampage/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public func loadLevels() -> [Tilemap] {
let jsonURL = Bundle.main.url(forResource: "Levels", withExtension: "json")!
let jsonData = try! Data(contentsOf: jsonURL)
let levels = try! JSONDecoder().decode([MapData].self, from: jsonData)
return levels.enumerated().map { Tilemap($0.element, index: $0.offset) }
return levels.enumerated().map { index, mapData in
MapGenerator(mapData: mapData, index: index).map
}
}

public func loadFont() -> Font {
Expand Down
11 changes: 8 additions & 3 deletions Source/Renderer/Renderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ public extension Renderer {
let tile = world.map[tileX, tileY]
if end.x.rounded(.down) == end.x {
let neighborX = tileX + (ray.direction.x > 0 ? -1 : 1)
let isDoor = world.isDoor(at: neighborX, tileY)
wallTexture = textures[isDoor ? .doorjamb : tile.textures[0]]
wallX = end.y - end.y.rounded(.down)
if world.map[neighborX, tileY].isWall {
wallTexture = textures[tile.textures[1]]
wallX = end.x - end.x.rounded(.down)
} else {
let isDoor = world.isDoor(at: neighborX, tileY)
wallTexture = textures[isDoor ? .doorjamb : tile.textures[0]]
wallX = end.y - end.y.rounded(.down)
}
} else {
let neighborY = tileY + (ray.direction.y > 0 ? -1 : 1)
let isDoor = world.isDoor(at: tileX, neighborY)
Expand Down

0 comments on commit 43ec2af

Please sign in to comment.