diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..835cea8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: swift
+osx_image: xcode11.4
+
+script:
+ - cd Source
+ - xcodebuild clean build -scheme Rampage -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max,OS=13.4'
+ - xcodebuild clean test -scheme Rampage -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max,OS=13.4'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..36f3877
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,227 @@
+## Change Log
+
+Occasionally bugs happen, and given the episodic nature of this tutorial, it is difficult to address these retrospectively without changing the Git commit history.
+
+This file is a record of bugs that have been found and fixed since the tutorial started. The dates next to each bug indicate when the fix was merged. If you completed the relevant tutorial(s) after the date listed for a given bug, you can safely ignore it.
+
+### Disable Status Bar (2020/04/25)
+
+The original code for [Part 16](Tutorial/Part16.md) disabled the status bar in the `Info.plist`, but this isn't actually sufficient to disable it on iPad. To do so properly you also need to add the following code to `ViewController`:
+
+```swift
+override var prefersStatusBarHidden: Bool {
+ return true
+}
+```
+
+### Out of Bounds Crash (2020/03/09)
+
+The original `drawColumn()` method added in [Part 4](Tutorial/Part4.md) had an unsafe upper bound that could potentially try to read a negative index in the wall texture, resulting in a crash.
+
+The fix was to replace the following line in the `drawColumn()` method in `Bitmap.swift`:
+
+```swift
+let sourceY = (Double(y) - point.y) * stepY
+```
+
+with:
+
+```swift
+let sourceY = max(0, Double(y) - point.y) * stepY
+```
+
+Note that this line appears twice in the latest version of `drawColumn()` due to the `isOpaque` optimization added in [Part 9](Tutorial/Part9.md). You should replace both occurences.
+
+### Ceiling Texture Gap (2020/03/09)
+
+The original ceiling texture code we added in [Part 4](Tutorial/Part4.md) resulted in a one-pixel gap at the top of the ceiling texture.
+
+The fix for this was to replace the following line in the `// Draw wall` section of `Renderer.draw()`:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 + 0.001)
+```
+
+with:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 - 0.001)
+```
+
+Then to replace the following line in the `// Draw floor and ceiling` section:
+
+```swift
+bitmap[x, bitmap.height - y] = ceilingTexture[normalized: textureX, textureY]
+```
+
+with:
+
+```swift
+bitmap[x, bitmap.height - 1 - y] = ceilingTexture[normalized: textureX, textureY]
+```
+
+### Weapon Switch Interrupted (2020/03/08)
+
+When we added the shotgun in [Part 14](Tutorial/Part14.md) there was a bug in the weapon switching logic which meant that if you fired the last round in the shotgun as you exited the level you'd begin the next level still with the shotgun, but no ammo and no way to switch back to the pistol.
+
+The fix was to replace the following lines near the bottom of the `Player.update()` method:
+
+```swift
+switch state {
+case .idle:
+ break
+case .firing:
+ if animation.isCompleted {
+ state = .idle
+ animation = weapon.attributes.idleAnimation
+ if ammo == 0 {
+ setWeapon(.pistol)
+ }
+ }
+}
+```
+
+with:
+
+```swift
+switch state {
+case .idle:
+ if ammo == 0 {
+ setWeapon(.pistol)
+ }
+case .firing:
+ if animation.isCompleted {
+ state = .idle
+ animation = weapon.attributes.idleAnimation
+ }
+}
+```
+
+### Unused Property in Player struct (2020/02/05)
+
+When we originally wrote the Player weapon code in [Part 8](Tutorial/Part8.md) we added a `lastAttackTime` property which was not actually used in the implementation.
+
+This has now been removed.
+
+### Monsters Can See Through Push-walls (2020/01/28)
+
+When push-walls were introduced in [Part 11](Tutorial/Part11.md), the `World.hitTest()` method was not updated to detect ray intersections with the `Pushwall` billboards, with the result that the monster in the second room in the first level can see (and be shot by) the player through the push-wall.
+
+The fix was to replace the following lines in the `World.hitTest()` method:
+
+```swift
+for door in doors {
+ guard let hit = door.billboard.hitTest(ray) else {
+```
+
+with:
+
+```swift
+let billboards = doors.map { $0.billboard } +
+ pushwalls.flatMap { $0.billboards(facing: ray.origin) }
+for billboard in billboards {
+ guard let hit = billboard.hitTest(ray) else {
+```
+
+### Bitmap Bounds Error (2019/10/11)
+
+The original `drawColumn()` method introduced in [Part 4](Tutorial/Part4.md) had an unsafe upper bound that could potentially cause a crash by trying to read beyond the end of the source bitmap.
+
+The fix was to replace the following line in the `drawColumn()` method in `Bitmap.swift`:
+
+```swift
+let start = Int(point.y), end = Int(point.y + height) + 1
+```
+
+with:
+
+```swift
+let start = Int(point.y), end = Int((point.y + height).rounded(.up))
+```
+
+### Inverted Bitmap Width and Height (2019/10/11)
+
+The original logic in [Part 9](Tutorial/Part9.md) that switched to column-first pixel order had a bug where the width and height were swapped on output, causing the result to be corrupted for non-square images. Since the game used square textures for all the walls and sprites, the bug wasn't immediately apparent.
+
+The fix was to change the last line in the `Bitmap.init()` function in `UIImage+Bitmap.swift` from:
+
+```swift
+self.init(height: cgImage.width, pixels: pixels)
+```
+
+to:
+
+```swift
+self.init(height: cgImage.height, pixels: pixels)
+```
+
+### Flipped Floor and Ceiling (2019/09/27)
+
+The original logic in [Part 9](Tutorial/Part9.md) for rotating the textures to compensate for switching to column-first pixel order had the side-effect of flipping the Z-axis. This resulted in the floor texture being drawn on the ceiling, and vice-versa (thanks to [Adam McNight](https://twitter.com/adamcnight/status/1174323711710781442?s=20) for reporting).
+
+The fix for this was to change two lines in `UIImage+Bitmap.swift`. First, in `UIImage.init()` change:
+
+```swift
+self.init(cgImage: cgImage, scale: 1, orientation: .left)
+```
+
+to:
+
+```swift
+self.init(cgImage: cgImage, scale: 1, orientation: .leftMirrored)
+```
+
+Then in `Bitmap.init()` change:
+
+```swift
+UIImage(cgImage: cgImage, scale: 1, orientation: .rightMirrored).draw(at: .zero)
+```
+
+to:
+
+```swift
+UIImage(cgImage: cgImage, scale: 1, orientation: .left).draw(at: .zero)
+```
+
+### Wall Collisions (2019/08/19)
+
+The original wall collision detection code described in [Part 2](Tutorial/Part2.md) had a bug that could cause the player to stick when sliding along a wall (thanks to [José Ibañez](https://twitter.com/jose_ibanez/status/1163225777401401344?s=20) for reporting).
+
+The fix for this was to return the largest intersection detected between any wall segment, rather than just the first intersection detected. The necessary code changes are in `Actor.intersection(with map:)`, which should now look like this:
+
+```swift
+func intersection(with map: Tilemap) -> Vector? {
+ let minX = Int(rect.min.x), maxX = Int(rect.max.x)
+ let minY = Int(rect.min.y), maxY = Int(rect.max.y)
+ var largestIntersection: Vector?
+ for y in minY ... maxY {
+ for x in minX ... maxX where map[x, y].isWall {
+ let wallRect = Rect(
+ min: Vector(x: Double(x), y: Double(y)),
+ max: Vector(x: Double(x + 1), y: Double(y + 1))
+ )
+ if let intersection = rect.intersection(with: wallRect),
+ intersection.length > largestIntersection?.length ?? 0 {
+ largestIntersection = intersection
+ }
+ }
+ }
+ return largestIntersection
+}
+```
+
+### Sprite Rendering (2019/08/02)
+
+In the original version of [Part 5](Tutorial/Part5.md) there were a couple of bugs in the sprite texture coordinate calculation. In your own project, check if the `// Draw sprites` section in `Renderer.swift` contains the following two lines:
+
+```swift
+let textureX = Int(spriteX * Double(wallTexture.width))
+let spriteTexture = textures[sprite.texture]
+```
+
+If so, replace them with:
+
+```swift
+let spriteTexture = textures[sprite.texture]
+let textureX = min(Int(spriteX * Double(spriteTexture.width)), spriteTexture.width - 1)
+```
diff --git a/LICENSE.md b/LICENSE.md
index 67b2c7d..0967029 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -19,3 +19,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
+---------------------------------------------------------------------------
+
+Sound effects obtained from https://www.zapsplat.com. Attribution required.
\ No newline at end of file
diff --git a/README.md b/README.md
index 236374a..c75d253 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,160 @@
## Retro Rampage
-This repository contains a code snapshot for the [Retro Rampage tutorial series](https://github.com/nicklockwood/RetroRampage) by Nick Lockwood.
+[![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CR6YX6DLRNJTY&source=url)
+[![Travis](https://travis-ci.org/nicklockwood/RetroRampage.svg)](https://travis-ci.org/nicklockwood/RetroRampage)
+[![Swift 5](https://img.shields.io/badge/swift-5-red.svg?style=flat)](https://developer.apple.com/swift)
+[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)
+[![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood)
+
+![Screenshot](Tutorial/Images/YellowAlert.png)
+
+### New!
+
+The first chapters of Retro Rampage are now available as a [video tutorial series](https://talk.objc.io/collections/retro-rampage), produced by the great folks at [objc.io](https://objc.io) (with Yours Truly as guest presenter).
+
+### About
+
+Retro Rampage is a tutorial series in which you will learn how to build a Wolfenstein-like game from scratch, in Swift. Initially the game will be targeting iPhone and iPad, but the engine should work on any platform that can run Swift code.
+
+Modern shooters have moved on a bit from Wolfenstein's grid-based 2.5D world, but we're going to stick with that template for a few reasons:
+
+* It's feasible to build Wolfenstein's 3D engine from scratch, without a lot of complicated math and without needing to know anything about GPUs or shaders.
+
+* It's simple to create and visualize maps that are constructed on a 2D grid, avoiding the complexities of 3D modeling and animation tools.
+
+* Tile grids are an excellent way to prototype techniques such as procedural map generation, pathfinding and line-of-sight calculations, which can then be applied to more complex worlds.
+
+### Background
+
+Ever since I first played Wolfenstein 3D on a friend's battered old 386 back in 1993, I was hooked on the *First-Person Shooter*.
+
+As an aspiring programmer, I wanted to recreate what I had seen. But armed only with 7th grade math and a rudimentary knowledge of BASIC, recreating the state-of-the-art in modern PC 3D graphics was hopelessly beyond my reach.
+
+More than two decades later, a few things have changed:
+
+We have the iPhone - a mobile computer many hundreds of times more powerful than a DOS-era desktop PC; We have Swift - a simple, powerful programming language with which to write apps and games; Finally - and most importantly - we have the Wolfenstein source code, and the wizardry behind it has been thoroughly demystified.
+
+I guess now is as good a time as any to scratch that quarter-century itch and build an FPS!
+
+### Tutorials
+
+The tutorials below are designed to be completed in order, and each step builds on the code from the previous one. If you decide to skip ahead, project snapshots for each step are available [here](https://github.com/nicklockwood/RetroRampage/releases).
+
+The tutorials are written with the assumption that you are already familiar with Xcode and are comfortable setting up an iOS project and adding new files to it. No knowledge of advanced Swift features is required, so it's fine if you've only used Objective-C or other C-like languages.
+
+[Part 1 - Separation of Concerns](Tutorial/Part1.md)
+
+Unlike most apps, games are typically designed to be independent of any given device or OS. Swift has already been ported to many platforms outside of the Apple ecosystem, including Android, Ubuntu, Windows and even Raspberry Pi. In this first part, we'll set up our project to minimize dependencies with iOS and provide a solid foundation for writing a fully portable game engine.
+
+[Part 2 - Mazes and Motion](Tutorial/Part2.md)
+
+Wolfenstein 3D is really a 2D game projected into the third dimension. The game mechanics work exactly the same as for a top-down 2D shooter, and to prove that we'll begin by building the game from a top-down 2D perspective before we make the shift to first-person 3D.
+
+[Part 3 - Ray Casting](Tutorial/Part3.md)
+
+Long before hardware accelerated 3D graphics, some of the greatest game programmers of our generation were creating incredible 3D worlds armed only with a 16-bit processor. We'll follow in their footsteps and bring our game into the third dimension with an old-school graphics hack called *ray casting*.
+
+[Part 4 - Texture Mapping](Tutorial/Part4.md)
+
+In this chapter we'll spruce up the bare walls and floor with *texture mapping*. Texture mapping is the process of painting or *wall-papering* a 3D object with a 2D image, helping to provide the appearance of intricate detail in an otherwise featureless surface.
+
+[Part 5 - Sprites](Tutorial/Part5.md)
+
+It's time to introduce some monsters to keep our player company. We'll display these using *sprites* - a popular technique used to add engaging content to 3D games in the days before it was possible to render textured polygonal models in real-time with sufficient detail.
+
+[Part 6 - Enemy Action](Tutorial/Part6.md)
+
+Right now the monsters in the maze are little more than gruesome scenery. We'll bring those passive monsters to life with collision detection, animations, and artificial intelligence so they can hunt and attack the player.
+
+[Part 7 - Death and Pixels](Tutorial/Part7.md)
+
+In this part we'll implement player damage, giving the monsters the ability to hurt and eventually kill the game's protagonist. We'll explore a variety of damage effects and techniques, including a cool Wolfenstein transition called *fizzlefade*.
+
+[Part 8 - Target Practice](Tutorial/Part8.md)
+
+We'll now give the player a weapon so they can fight back against the ravenous monsters. This chapter will demonstrate how to extend our drawing logic to handle screen-space sprites, add a bunch of new animations, and figure out how to implement reliable collision detection for fast-moving projectiles.
+
+[Part 9 - Performance Tuning](Tutorial/Part9.md)
+
+The new effects we've added are starting to take a toll on the game's frame rate, especially on older devices. Let's take a break from adding new features and spend some time on improving the rendering speed. In this chapter we'll find out how to diagnose and fix performance bottlenecks, while avoiding the kind of micro-optimizations that will make it harder to add new features later on.
+
+[Part 10 - Sliding Doors](Tutorial/Part10.md)
+
+In this chapter we add another iconic feature from Wolfenstein - the sliding metal doors between rooms. These add some interesting challenges as the first non-static, non-grid-aligned scenery in the game.
+
+[Part 11 - Secrets](Tutorial/Part11.md)
+
+Time to add bit of intrigue with the introduction of a secret passage, hidden behind a sliding push-wall that doubles as a zombie-squishing booby trap. Moving walls pose some interesting problems, both for the rendering engine and collision detection - we get to find out what a wall looks like from the inside *and* what the world looks like from the outside!
+
+[Part 12 - Another Level](Tutorial/Part12.md)
+
+In this chapter we add a second level and an end-of-level elevator that the player can use to reach it. We'll demonstrate a number of new techniques including animated wall decorations, and how the command pattern can be a handy substitute for delegation when using structs.
+
+[Part 13 - Sound Effects](Tutorial/Part13.md)
+
+It's time to end the silence! In this chapter we add sound effects to the game, demonstrating how to stream MP3 files on iOS with minimal latency, as well as techniques such as 3D positional audio.
+
+[Part 14 - Power-ups and Inventory](Tutorial/Part14.md)
+
+Long before games made the leap into 3D, power-ups were a staple feature of action-oriented games. In this chapter we add a medkit and a new weapon for the player to collect on their travels around the maze.
+
+[Part 15 - Pathfinding](Tutorial/Part15.md)
+
+Right now the game's zombie inhabitants only follow the player if they have line-of-sight. In this chapter we'll enhance the monster intelligence by adding *pathfinding* using the A* algorithm, so they can chase you through doors and around corners.
+
+[Part 16 - Heads-Up Display](Tutorial/Part16.md)
+
+In Part 16 we add a heads-up display showing the player important information such as how many bullets they've got left, and whether they're about to die. This tutorial covers a bunch of new techniques such as text rendering, sprite sheets, dynamic image tinting, and more!
+
+[Part 17 - Title Screen](Tutorial/Part17.md)
+
+In this part we add more polish to the game in the form of a title screen. Adding a single, static screen seems simple enough, but it provides an opportunity for some long-overdue refactoring, as well as enhancements to the nascent text rendering system we introduced in Part 16.
+
+### Reader Exercises
+
+Each tutorial includes a "Reader Exercises" section at the end. These exercises are intended as an optional challenge for readers to check that they've followed and understood the material so far - completing the exercises is not a prerequisite for starting the next tutorial.
+
+The questions are arranged in ascending order of difficulty:
+
+* The first is usually a trivial modification to the existing code.
+* The second requires a bit more thought.
+* The third may require significant changes and enhancements to the game engine.
+
+Some of the more advanced questions will eventually be answered, either in a later tutorial or in an Experiments PR (see below). If you are stuck on one of the exercises (or if you've completed an exercise and want to show off your solution) feel free to open a PR or Github issue.
+
+### Bugs
+
+I've occasionally made retrospective fixes after a tutorial chapter was published. This will be called out in a later tutorial if it directly impacts any new code, but it's a good idea to periodically check the [CHANGELOG](CHANGELOG.md) for fixes.
+
+### Experiments
+
+If you're up-to-date with the tutorials, and can't wait for the next chapter, you might like to check out some of the [Experiments PRs](https://github.com/nicklockwood/RetroRampage/pulls) on Github.
+
+These experiments demonstrate advanced features that we aren't quite ready to explore in the tutorials yet.
+
+### Further Reading
+
+If you've exhausted the tutorials and experiments and are still eager to learn more, here are some resources you might find useful:
+
+* [Swift Talks](https://talk.objc.io/collections/retro-rampage) - The first few chapters of Retro Rampage are available in video form, hosted by the great folks at [objc.io](https://objc.io) (with Yours Truly as guest presenter).
+* [Lode's Raycasting Tutorial](https://lodev.org/cgtutor/raycasting.html#Introduction) - A great tutorial on ray casting, implemented in C++.
+* [Game Engine Black Book: Wolfenstein 3D](https://www.amazon.co.uk/gp/product/1727646703/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1727646703&linkCode=as2&tag=charcoaldesig-21&linkId=aab5d43499c96f7417b7aa0a7b3e587d) - Fabien Sanglard's excellent book about the Wolfenstein 3D game engine.
+* [Swiftenstein](https://github.com/nicklockwood/Swiftenstein) - A more complete but less polished implementation of the ideas covered in this tutorial.
+* [Handmade Hero](https://handmadehero.org) - A video series in which games industry veteran [Casey Muratori](https://github.com/cmuratori) builds a game from scratch in C.
+
+### Acknowledgments
+
+I'd like to thank [Nat Brown](https://github.com/natbro) and [PJ Cook](https://github.com/pjcook) for their invaluable feedback on the first drafts of these tutorials.
+
+Thanks also to [Lode Vandevenne](https://github.com/lvandeve) and [Fabien Sanglard](https://github.com/fabiensanglard/), whom I've never actually spoken to, but whose brilliant explanations of ray casting and the Wolfenstein engine formed both the basis and inspiration for this tutorial series.
+
+All sound effects used in the project were obtained from [zapsplat.com](https://www.zapsplat.com). These may be used for free with attribution. See https://www.zapsplat.com/license-type/standard-license/ for details.
+
+All graphics were drawn (badly) by me, in [Aseprite](https://www.aseprite.org).
+
+### Tip Jar
+
+I started this tutorial series thinking it would take just a few days. Many months later, with no end in sight, I realize I may have been a bit naive. If you've found it interesting, please consider donating to my caffeine fund.
+
+[![Donate via PayPal](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CR6YX6DLRNJTY&source=url)
+
diff --git a/Tutorial/Images/99Bullets.png b/Tutorial/Images/99Bullets.png
new file mode 100644
index 0000000..e2f5bb8
Binary files /dev/null and b/Tutorial/Images/99Bullets.png differ
diff --git a/Tutorial/Images/AccidentalDischarge.png b/Tutorial/Images/AccidentalDischarge.png
new file mode 100644
index 0000000..6233adc
Binary files /dev/null and b/Tutorial/Images/AccidentalDischarge.png differ
diff --git a/Tutorial/Images/AddUnitTestingBundle.png b/Tutorial/Images/AddUnitTestingBundle.png
new file mode 100644
index 0000000..af3480b
Binary files /dev/null and b/Tutorial/Images/AddUnitTestingBundle.png differ
diff --git a/Tutorial/Images/Architecture.png b/Tutorial/Images/Architecture.png
new file mode 100644
index 0000000..e0f0324
Binary files /dev/null and b/Tutorial/Images/Architecture.png differ
diff --git a/Tutorial/Images/AttackAnimation.png b/Tutorial/Images/AttackAnimation.png
new file mode 100644
index 0000000..c3a83e8
Binary files /dev/null and b/Tutorial/Images/AttackAnimation.png differ
diff --git a/Tutorial/Images/BackFaceCulling.png b/Tutorial/Images/BackFaceCulling.png
new file mode 100644
index 0000000..ef6bbe1
Binary files /dev/null and b/Tutorial/Images/BackFaceCulling.png differ
diff --git a/Tutorial/Images/BigCrosshair.png b/Tutorial/Images/BigCrosshair.png
new file mode 100644
index 0000000..e9e14dd
Binary files /dev/null and b/Tutorial/Images/BigCrosshair.png differ
diff --git a/Tutorial/Images/BigPixelFizzle.png b/Tutorial/Images/BigPixelFizzle.png
new file mode 100644
index 0000000..79c511d
Binary files /dev/null and b/Tutorial/Images/BigPixelFizzle.png differ
diff --git a/Tutorial/Images/BitmapOpacityTest.png b/Tutorial/Images/BitmapOpacityTest.png
new file mode 100644
index 0000000..0f563ae
Binary files /dev/null and b/Tutorial/Images/BitmapOpacityTest.png differ
diff --git a/Tutorial/Images/BlackSpriteBackground.png b/Tutorial/Images/BlackSpriteBackground.png
new file mode 100644
index 0000000..ee4e68e
Binary files /dev/null and b/Tutorial/Images/BlackSpriteBackground.png differ
diff --git a/Tutorial/Images/BlendColorCrash.png b/Tutorial/Images/BlendColorCrash.png
new file mode 100644
index 0000000..f34618b
Binary files /dev/null and b/Tutorial/Images/BlendColorCrash.png differ
diff --git a/Tutorial/Images/BlurryBluePixel.png b/Tutorial/Images/BlurryBluePixel.png
new file mode 100644
index 0000000..4f952d4
Binary files /dev/null and b/Tutorial/Images/BlurryBluePixel.png differ
diff --git a/Tutorial/Images/BranchingPaths.png b/Tutorial/Images/BranchingPaths.png
new file mode 100644
index 0000000..882fbdd
Binary files /dev/null and b/Tutorial/Images/BranchingPaths.png differ
diff --git a/Tutorial/Images/BulletSpread.png b/Tutorial/Images/BulletSpread.png
new file mode 100644
index 0000000..998b1ec
Binary files /dev/null and b/Tutorial/Images/BulletSpread.png differ
diff --git a/Tutorial/Images/ClearSpriteBackground.png b/Tutorial/Images/ClearSpriteBackground.png
new file mode 100644
index 0000000..e14821b
Binary files /dev/null and b/Tutorial/Images/ClearSpriteBackground.png differ
diff --git a/Tutorial/Images/CollisionLoop.png b/Tutorial/Images/CollisionLoop.png
new file mode 100644
index 0000000..47e9044
Binary files /dev/null and b/Tutorial/Images/CollisionLoop.png differ
diff --git a/Tutorial/Images/CollisionResponse.png b/Tutorial/Images/CollisionResponse.png
new file mode 100644
index 0000000..7d707fb
Binary files /dev/null and b/Tutorial/Images/CollisionResponse.png differ
diff --git a/Tutorial/Images/ColumnOrderTest.png b/Tutorial/Images/ColumnOrderTest.png
new file mode 100644
index 0000000..288f964
Binary files /dev/null and b/Tutorial/Images/ColumnOrderTest.png differ
diff --git a/Tutorial/Images/Connectivity.png b/Tutorial/Images/Connectivity.png
new file mode 100644
index 0000000..e2e4c10
Binary files /dev/null and b/Tutorial/Images/Connectivity.png differ
diff --git a/Tutorial/Images/CorrectlyCroppedText.png b/Tutorial/Images/CorrectlyCroppedText.png
new file mode 100644
index 0000000..4c99c5e
Binary files /dev/null and b/Tutorial/Images/CorrectlyCroppedText.png differ
diff --git a/Tutorial/Images/Crosshair.png b/Tutorial/Images/Crosshair.png
new file mode 100644
index 0000000..8ce691d
Binary files /dev/null and b/Tutorial/Images/Crosshair.png differ
diff --git a/Tutorial/Images/CurvedWalls.png b/Tutorial/Images/CurvedWalls.png
new file mode 100644
index 0000000..4b4b7b6
Binary files /dev/null and b/Tutorial/Images/CurvedWalls.png differ
diff --git a/Tutorial/Images/DeathEffectTimeline.png b/Tutorial/Images/DeathEffectTimeline.png
new file mode 100644
index 0000000..e5d8d4a
Binary files /dev/null and b/Tutorial/Images/DeathEffectTimeline.png differ
diff --git a/Tutorial/Images/DebugMode.png b/Tutorial/Images/DebugMode.png
new file mode 100644
index 0000000..9c210de
Binary files /dev/null and b/Tutorial/Images/DebugMode.png differ
diff --git a/Tutorial/Images/DepthSortingBug.png b/Tutorial/Images/DepthSortingBug.png
new file mode 100644
index 0000000..f41ca6a
Binary files /dev/null and b/Tutorial/Images/DepthSortingBug.png differ
diff --git a/Tutorial/Images/DepthSortingBug2.png b/Tutorial/Images/DepthSortingBug2.png
new file mode 100644
index 0000000..9dbb470
Binary files /dev/null and b/Tutorial/Images/DepthSortingBug2.png differ
diff --git a/Tutorial/Images/DisableSafetyChecks.png b/Tutorial/Images/DisableSafetyChecks.png
new file mode 100644
index 0000000..fb813f8
Binary files /dev/null and b/Tutorial/Images/DisableSafetyChecks.png differ
diff --git a/Tutorial/Images/DisablingPortraitMode.png b/Tutorial/Images/DisablingPortraitMode.png
new file mode 100644
index 0000000..64909e9
Binary files /dev/null and b/Tutorial/Images/DisablingPortraitMode.png differ
diff --git a/Tutorial/Images/DistortedShotgun.png b/Tutorial/Images/DistortedShotgun.png
new file mode 100644
index 0000000..7633e9e
Binary files /dev/null and b/Tutorial/Images/DistortedShotgun.png differ
diff --git a/Tutorial/Images/DoorInCorner.png b/Tutorial/Images/DoorInCorner.png
new file mode 100644
index 0000000..d59284c
Binary files /dev/null and b/Tutorial/Images/DoorInCorner.png differ
diff --git a/Tutorial/Images/DoorRays.png b/Tutorial/Images/DoorRays.png
new file mode 100644
index 0000000..9dfe8ae
Binary files /dev/null and b/Tutorial/Images/DoorRays.png differ
diff --git a/Tutorial/Images/DoorTextures.png b/Tutorial/Images/DoorTextures.png
new file mode 100644
index 0000000..f837f97
Binary files /dev/null and b/Tutorial/Images/DoorTextures.png differ
diff --git a/Tutorial/Images/Doorjamb.png b/Tutorial/Images/Doorjamb.png
new file mode 100644
index 0000000..346934d
Binary files /dev/null and b/Tutorial/Images/Doorjamb.png differ
diff --git a/Tutorial/Images/DoorjambTextures.png b/Tutorial/Images/DoorjambTextures.png
new file mode 100644
index 0000000..68d4e2c
Binary files /dev/null and b/Tutorial/Images/DoorjambTextures.png differ
diff --git a/Tutorial/Images/DraggableJoystick.png b/Tutorial/Images/DraggableJoystick.png
new file mode 100644
index 0000000..fb5b530
Binary files /dev/null and b/Tutorial/Images/DraggableJoystick.png differ
diff --git a/Tutorial/Images/DrawingOrderFixed.png b/Tutorial/Images/DrawingOrderFixed.png
new file mode 100644
index 0000000..89ae25a
Binary files /dev/null and b/Tutorial/Images/DrawingOrderFixed.png differ
diff --git a/Tutorial/Images/DrawingOrderGlitch.png b/Tutorial/Images/DrawingOrderGlitch.png
new file mode 100644
index 0000000..cccce80
Binary files /dev/null and b/Tutorial/Images/DrawingOrderGlitch.png differ
diff --git a/Tutorial/Images/EasingCurves.png b/Tutorial/Images/EasingCurves.png
new file mode 100644
index 0000000..4d02ee3
Binary files /dev/null and b/Tutorial/Images/EasingCurves.png differ
diff --git a/Tutorial/Images/Elevator.png b/Tutorial/Images/Elevator.png
new file mode 100644
index 0000000..bf562ef
Binary files /dev/null and b/Tutorial/Images/Elevator.png differ
diff --git a/Tutorial/Images/ElevatorTextures.png b/Tutorial/Images/ElevatorTextures.png
new file mode 100644
index 0000000..8790eb4
Binary files /dev/null and b/Tutorial/Images/ElevatorTextures.png differ
diff --git a/Tutorial/Images/FieldOfView.png b/Tutorial/Images/FieldOfView.png
new file mode 100644
index 0000000..7d2a793
Binary files /dev/null and b/Tutorial/Images/FieldOfView.png differ
diff --git a/Tutorial/Images/FizzleFade.png b/Tutorial/Images/FizzleFade.png
new file mode 100644
index 0000000..0146ff1
Binary files /dev/null and b/Tutorial/Images/FizzleFade.png differ
diff --git a/Tutorial/Images/FloatingJoystick.png b/Tutorial/Images/FloatingJoystick.png
new file mode 100644
index 0000000..4261fb0
Binary files /dev/null and b/Tutorial/Images/FloatingJoystick.png differ
diff --git a/Tutorial/Images/FrameworkTarget.png b/Tutorial/Images/FrameworkTarget.png
new file mode 100644
index 0000000..b205a51
Binary files /dev/null and b/Tutorial/Images/FrameworkTarget.png differ
diff --git a/Tutorial/Images/FullHealth.png b/Tutorial/Images/FullHealth.png
new file mode 100644
index 0000000..11e1c0c
Binary files /dev/null and b/Tutorial/Images/FullHealth.png differ
diff --git a/Tutorial/Images/FunctioningDoor.png b/Tutorial/Images/FunctioningDoor.png
new file mode 100644
index 0000000..b770561
Binary files /dev/null and b/Tutorial/Images/FunctioningDoor.png differ
diff --git a/Tutorial/Images/GreenHealth.png b/Tutorial/Images/GreenHealth.png
new file mode 100644
index 0000000..506ab6a
Binary files /dev/null and b/Tutorial/Images/GreenHealth.png differ
diff --git a/Tutorial/Images/HurtThroughDoor.png b/Tutorial/Images/HurtThroughDoor.png
new file mode 100644
index 0000000..4a4f130
Binary files /dev/null and b/Tutorial/Images/HurtThroughDoor.png differ
diff --git a/Tutorial/Images/ImprovedTextureTrace.png b/Tutorial/Images/ImprovedTextureTrace.png
new file mode 100644
index 0000000..0e903f6
Binary files /dev/null and b/Tutorial/Images/ImprovedTextureTrace.png differ
diff --git a/Tutorial/Images/InfiniteAmmo.png b/Tutorial/Images/InfiniteAmmo.png
new file mode 100644
index 0000000..97acff7
Binary files /dev/null and b/Tutorial/Images/InfiniteAmmo.png differ
diff --git a/Tutorial/Images/InlineSetterTest.png b/Tutorial/Images/InlineSetterTest.png
new file mode 100644
index 0000000..808de3a
Binary files /dev/null and b/Tutorial/Images/InlineSetterTest.png differ
diff --git a/Tutorial/Images/InsetCorners.png b/Tutorial/Images/InsetCorners.png
new file mode 100644
index 0000000..b01993b
Binary files /dev/null and b/Tutorial/Images/InsetCorners.png differ
diff --git a/Tutorial/Images/JoggingOnTheSpot.png b/Tutorial/Images/JoggingOnTheSpot.png
new file mode 100644
index 0000000..2a7e105
Binary files /dev/null and b/Tutorial/Images/JoggingOnTheSpot.png differ
diff --git a/Tutorial/Images/Lighting.png b/Tutorial/Images/Lighting.png
new file mode 100644
index 0000000..a07a152
Binary files /dev/null and b/Tutorial/Images/Lighting.png differ
diff --git a/Tutorial/Images/LineDrawing.png b/Tutorial/Images/LineDrawing.png
new file mode 100644
index 0000000..cc50337
Binary files /dev/null and b/Tutorial/Images/LineDrawing.png differ
diff --git a/Tutorial/Images/LineOfSight.png b/Tutorial/Images/LineOfSight.png
new file mode 100644
index 0000000..bf6f667
Binary files /dev/null and b/Tutorial/Images/LineOfSight.png differ
diff --git a/Tutorial/Images/LinearAccessTest.png b/Tutorial/Images/LinearAccessTest.png
new file mode 100644
index 0000000..c23e2f8
Binary files /dev/null and b/Tutorial/Images/LinearAccessTest.png differ
diff --git a/Tutorial/Images/Medkit.png b/Tutorial/Images/Medkit.png
new file mode 100644
index 0000000..886357c
Binary files /dev/null and b/Tutorial/Images/Medkit.png differ
diff --git a/Tutorial/Images/MedkitSprite.png b/Tutorial/Images/MedkitSprite.png
new file mode 100644
index 0000000..57d82e6
Binary files /dev/null and b/Tutorial/Images/MedkitSprite.png differ
diff --git a/Tutorial/Images/MemoryHierarchy.png b/Tutorial/Images/MemoryHierarchy.png
new file mode 100644
index 0000000..1b78c50
Binary files /dev/null and b/Tutorial/Images/MemoryHierarchy.png differ
diff --git a/Tutorial/Images/MisalignedPushwall.png b/Tutorial/Images/MisalignedPushwall.png
new file mode 100644
index 0000000..7e5b8f3
Binary files /dev/null and b/Tutorial/Images/MisalignedPushwall.png differ
diff --git a/Tutorial/Images/MonsterBlockage.png b/Tutorial/Images/MonsterBlockage.png
new file mode 100644
index 0000000..b3fe5e9
Binary files /dev/null and b/Tutorial/Images/MonsterBlockage.png differ
diff --git a/Tutorial/Images/MonsterCrush.png b/Tutorial/Images/MonsterCrush.png
new file mode 100644
index 0000000..d89e063
Binary files /dev/null and b/Tutorial/Images/MonsterCrush.png differ
diff --git a/Tutorial/Images/MonsterDeath.png b/Tutorial/Images/MonsterDeath.png
new file mode 100644
index 0000000..0e9f219
Binary files /dev/null and b/Tutorial/Images/MonsterDeath.png differ
diff --git a/Tutorial/Images/MonsterInWall.png b/Tutorial/Images/MonsterInWall.png
new file mode 100644
index 0000000..b97773c
Binary files /dev/null and b/Tutorial/Images/MonsterInWall.png differ
diff --git a/Tutorial/Images/MonsterInWall2.png b/Tutorial/Images/MonsterInWall2.png
new file mode 100644
index 0000000..5f8a9d2
Binary files /dev/null and b/Tutorial/Images/MonsterInWall2.png differ
diff --git a/Tutorial/Images/MonsterMob.png b/Tutorial/Images/MonsterMob.png
new file mode 100644
index 0000000..ec5ab3b
Binary files /dev/null and b/Tutorial/Images/MonsterMob.png differ
diff --git a/Tutorial/Images/MonsterOpensDoor.png b/Tutorial/Images/MonsterOpensDoor.png
new file mode 100644
index 0000000..db2937e
Binary files /dev/null and b/Tutorial/Images/MonsterOpensDoor.png differ
diff --git a/Tutorial/Images/MonsterSprite.png b/Tutorial/Images/MonsterSprite.png
new file mode 100644
index 0000000..0f2a385
Binary files /dev/null and b/Tutorial/Images/MonsterSprite.png differ
diff --git a/Tutorial/Images/MonsterSurprise.png b/Tutorial/Images/MonsterSurprise.png
new file mode 100644
index 0000000..b0fb5fd
Binary files /dev/null and b/Tutorial/Images/MonsterSurprise.png differ
diff --git a/Tutorial/Images/MonsterVision.png b/Tutorial/Images/MonsterVision.png
new file mode 100644
index 0000000..57b8582
Binary files /dev/null and b/Tutorial/Images/MonsterVision.png differ
diff --git a/Tutorial/Images/MonstersAttacking.png b/Tutorial/Images/MonstersAttacking.png
new file mode 100644
index 0000000..98ae383
Binary files /dev/null and b/Tutorial/Images/MonstersAttacking.png differ
diff --git a/Tutorial/Images/MuzzleFlash.png b/Tutorial/Images/MuzzleFlash.png
new file mode 100644
index 0000000..6d86da9
Binary files /dev/null and b/Tutorial/Images/MuzzleFlash.png differ
diff --git a/Tutorial/Images/NoInliningSubscript.png b/Tutorial/Images/NoInliningSubscript.png
new file mode 100644
index 0000000..e8e5a67
Binary files /dev/null and b/Tutorial/Images/NoInliningSubscript.png differ
diff --git a/Tutorial/Images/NotSoSecretPassage.png b/Tutorial/Images/NotSoSecretPassage.png
new file mode 100644
index 0000000..d94e52d
Binary files /dev/null and b/Tutorial/Images/NotSoSecretPassage.png differ
diff --git a/Tutorial/Images/NumberFont.png b/Tutorial/Images/NumberFont.png
new file mode 100644
index 0000000..113c426
Binary files /dev/null and b/Tutorial/Images/NumberFont.png differ
diff --git a/Tutorial/Images/ObscuredCorners.png b/Tutorial/Images/ObscuredCorners.png
new file mode 100644
index 0000000..48342cc
Binary files /dev/null and b/Tutorial/Images/ObscuredCorners.png differ
diff --git a/Tutorial/Images/OptimizedBlendTrace.png b/Tutorial/Images/OptimizedBlendTrace.png
new file mode 100644
index 0000000..a834738
Binary files /dev/null and b/Tutorial/Images/OptimizedBlendTrace.png differ
diff --git a/Tutorial/Images/OptimizedOrderTrace.png b/Tutorial/Images/OptimizedOrderTrace.png
new file mode 100644
index 0000000..5ba39cb
Binary files /dev/null and b/Tutorial/Images/OptimizedOrderTrace.png differ
diff --git a/Tutorial/Images/OverlappingAccess.png b/Tutorial/Images/OverlappingAccess.png
new file mode 100644
index 0000000..6f87c8d
Binary files /dev/null and b/Tutorial/Images/OverlappingAccess.png differ
diff --git a/Tutorial/Images/PanValueFromCosine.png b/Tutorial/Images/PanValueFromCosine.png
new file mode 100644
index 0000000..95d70b9
Binary files /dev/null and b/Tutorial/Images/PanValueFromCosine.png differ
diff --git a/Tutorial/Images/PerspectiveView.png b/Tutorial/Images/PerspectiveView.png
new file mode 100644
index 0000000..d17925b
Binary files /dev/null and b/Tutorial/Images/PerspectiveView.png differ
diff --git a/Tutorial/Images/PistolFiring.png b/Tutorial/Images/PistolFiring.png
new file mode 100644
index 0000000..b7b8852
Binary files /dev/null and b/Tutorial/Images/PistolFiring.png differ
diff --git a/Tutorial/Images/PistolOverlay.png b/Tutorial/Images/PistolOverlay.png
new file mode 100644
index 0000000..6ce7229
Binary files /dev/null and b/Tutorial/Images/PistolOverlay.png differ
diff --git a/Tutorial/Images/PistolPosition.png b/Tutorial/Images/PistolPosition.png
new file mode 100644
index 0000000..b097eb3
Binary files /dev/null and b/Tutorial/Images/PistolPosition.png differ
diff --git a/Tutorial/Images/PistolSprite.png b/Tutorial/Images/PistolSprite.png
new file mode 100644
index 0000000..9cc606a
Binary files /dev/null and b/Tutorial/Images/PistolSprite.png differ
diff --git a/Tutorial/Images/PixelOpacityTest.png b/Tutorial/Images/PixelOpacityTest.png
new file mode 100644
index 0000000..307bb00
Binary files /dev/null and b/Tutorial/Images/PixelOpacityTest.png differ
diff --git a/Tutorial/Images/PortraitHUD.png b/Tutorial/Images/PortraitHUD.png
new file mode 100644
index 0000000..0991ef4
Binary files /dev/null and b/Tutorial/Images/PortraitHUD.png differ
diff --git a/Tutorial/Images/ProjectSounds.png b/Tutorial/Images/ProjectSounds.png
new file mode 100644
index 0000000..f90934b
Binary files /dev/null and b/Tutorial/Images/ProjectSounds.png differ
diff --git a/Tutorial/Images/ProjectStructure.png b/Tutorial/Images/ProjectStructure.png
new file mode 100644
index 0000000..0b43ce0
Binary files /dev/null and b/Tutorial/Images/ProjectStructure.png differ
diff --git a/Tutorial/Images/PushwallGlitch.png b/Tutorial/Images/PushwallGlitch.png
new file mode 100644
index 0000000..9a2a03a
Binary files /dev/null and b/Tutorial/Images/PushwallGlitch.png differ
diff --git a/Tutorial/Images/Pythagoras.png b/Tutorial/Images/Pythagoras.png
new file mode 100644
index 0000000..73626e6
Binary files /dev/null and b/Tutorial/Images/Pythagoras.png differ
diff --git a/Tutorial/Images/RayFan.png b/Tutorial/Images/RayFan.png
new file mode 100644
index 0000000..b5456f0
Binary files /dev/null and b/Tutorial/Images/RayFan.png differ
diff --git a/Tutorial/Images/RayLengths.png b/Tutorial/Images/RayLengths.png
new file mode 100644
index 0000000..1369b89
Binary files /dev/null and b/Tutorial/Images/RayLengths.png differ
diff --git a/Tutorial/Images/RayTileIntersection.png b/Tutorial/Images/RayTileIntersection.png
new file mode 100644
index 0000000..e30de83
Binary files /dev/null and b/Tutorial/Images/RayTileIntersection.png differ
diff --git a/Tutorial/Images/RedFlashEffect.png b/Tutorial/Images/RedFlashEffect.png
new file mode 100644
index 0000000..0376e11
Binary files /dev/null and b/Tutorial/Images/RedFlashEffect.png differ
diff --git a/Tutorial/Images/RedSpriteBackground.png b/Tutorial/Images/RedSpriteBackground.png
new file mode 100644
index 0000000..658bc05
Binary files /dev/null and b/Tutorial/Images/RedSpriteBackground.png differ
diff --git a/Tutorial/Images/ReleaseMode.png b/Tutorial/Images/ReleaseMode.png
new file mode 100644
index 0000000..17ed91e
Binary files /dev/null and b/Tutorial/Images/ReleaseMode.png differ
diff --git a/Tutorial/Images/ReleaseModeTests.png b/Tutorial/Images/ReleaseModeTests.png
new file mode 100644
index 0000000..56c361f
Binary files /dev/null and b/Tutorial/Images/ReleaseModeTests.png differ
diff --git a/Tutorial/Images/RemoveMultiplicationTest.png b/Tutorial/Images/RemoveMultiplicationTest.png
new file mode 100644
index 0000000..1355f97
Binary files /dev/null and b/Tutorial/Images/RemoveMultiplicationTest.png differ
diff --git a/Tutorial/Images/RendererBuildSettings.png b/Tutorial/Images/RendererBuildSettings.png
new file mode 100644
index 0000000..daa5b19
Binary files /dev/null and b/Tutorial/Images/RendererBuildSettings.png differ
diff --git a/Tutorial/Images/RotatedOutput.png b/Tutorial/Images/RotatedOutput.png
new file mode 100644
index 0000000..192a04c
Binary files /dev/null and b/Tutorial/Images/RotatedOutput.png differ
diff --git a/Tutorial/Images/RotatedSprites.png b/Tutorial/Images/RotatedSprites.png
new file mode 100644
index 0000000..f877626
Binary files /dev/null and b/Tutorial/Images/RotatedSprites.png differ
diff --git a/Tutorial/Images/RoundingErrors.png b/Tutorial/Images/RoundingErrors.png
new file mode 100644
index 0000000..e7e5f17
Binary files /dev/null and b/Tutorial/Images/RoundingErrors.png differ
diff --git a/Tutorial/Images/SafetyChecksOffTest.png b/Tutorial/Images/SafetyChecksOffTest.png
new file mode 100644
index 0000000..7ed6046
Binary files /dev/null and b/Tutorial/Images/SafetyChecksOffTest.png differ
diff --git a/Tutorial/Images/ScaledHealthIndicator.png b/Tutorial/Images/ScaledHealthIndicator.png
new file mode 100644
index 0000000..157d41b
Binary files /dev/null and b/Tutorial/Images/ScaledHealthIndicator.png differ
diff --git a/Tutorial/Images/ScrambledOutput.png b/Tutorial/Images/ScrambledOutput.png
new file mode 100644
index 0000000..cb0382f
Binary files /dev/null and b/Tutorial/Images/ScrambledOutput.png differ
diff --git a/Tutorial/Images/SecretPassageWall.png b/Tutorial/Images/SecretPassageWall.png
new file mode 100644
index 0000000..e24c474
Binary files /dev/null and b/Tutorial/Images/SecretPassageWall.png differ
diff --git a/Tutorial/Images/SensibleCrosshair.png b/Tutorial/Images/SensibleCrosshair.png
new file mode 100644
index 0000000..0811d89
Binary files /dev/null and b/Tutorial/Images/SensibleCrosshair.png differ
diff --git a/Tutorial/Images/SetBaseline.png b/Tutorial/Images/SetBaseline.png
new file mode 100644
index 0000000..00ea000
Binary files /dev/null and b/Tutorial/Images/SetBaseline.png differ
diff --git a/Tutorial/Images/SharpBluePixel.png b/Tutorial/Images/SharpBluePixel.png
new file mode 100644
index 0000000..1396e9b
Binary files /dev/null and b/Tutorial/Images/SharpBluePixel.png differ
diff --git a/Tutorial/Images/ShotgunSprites.png b/Tutorial/Images/ShotgunSprites.png
new file mode 100644
index 0000000..bcd7dfb
Binary files /dev/null and b/Tutorial/Images/ShotgunSprites.png differ
diff --git a/Tutorial/Images/SlidingWall.png b/Tutorial/Images/SlidingWall.png
new file mode 100644
index 0000000..e89d9cc
Binary files /dev/null and b/Tutorial/Images/SlidingWall.png differ
diff --git a/Tutorial/Images/SlimePushwall.png b/Tutorial/Images/SlimePushwall.png
new file mode 100644
index 0000000..2a13b01
Binary files /dev/null and b/Tutorial/Images/SlimePushwall.png differ
diff --git a/Tutorial/Images/SlopeIntercept.png b/Tutorial/Images/SlopeIntercept.png
new file mode 100644
index 0000000..54ba242
Binary files /dev/null and b/Tutorial/Images/SlopeIntercept.png differ
diff --git a/Tutorial/Images/SmoothedDeathEffect.png b/Tutorial/Images/SmoothedDeathEffect.png
new file mode 100644
index 0000000..1e5f681
Binary files /dev/null and b/Tutorial/Images/SmoothedDeathEffect.png differ
diff --git a/Tutorial/Images/SolidFloorColor.png b/Tutorial/Images/SolidFloorColor.png
new file mode 100644
index 0000000..4aaa09d
Binary files /dev/null and b/Tutorial/Images/SolidFloorColor.png differ
diff --git a/Tutorial/Images/SortedSprites.png b/Tutorial/Images/SortedSprites.png
new file mode 100644
index 0000000..32d7a7b
Binary files /dev/null and b/Tutorial/Images/SortedSprites.png differ
diff --git a/Tutorial/Images/SpriteBehindPlayer.png b/Tutorial/Images/SpriteBehindPlayer.png
new file mode 100644
index 0000000..d902e0c
Binary files /dev/null and b/Tutorial/Images/SpriteBehindPlayer.png differ
diff --git a/Tutorial/Images/SpriteLines.png b/Tutorial/Images/SpriteLines.png
new file mode 100644
index 0000000..a2d2bbb
Binary files /dev/null and b/Tutorial/Images/SpriteLines.png differ
diff --git a/Tutorial/Images/SpriteOrderBug.png b/Tutorial/Images/SpriteOrderBug.png
new file mode 100644
index 0000000..b941b27
Binary files /dev/null and b/Tutorial/Images/SpriteOrderBug.png differ
diff --git a/Tutorial/Images/SpriteRadius.png b/Tutorial/Images/SpriteRadius.png
new file mode 100644
index 0000000..f897e81
Binary files /dev/null and b/Tutorial/Images/SpriteRadius.png differ
diff --git a/Tutorial/Images/SpriteRayBug.png b/Tutorial/Images/SpriteRayBug.png
new file mode 100644
index 0000000..bb9b4ca
Binary files /dev/null and b/Tutorial/Images/SpriteRayBug.png differ
diff --git a/Tutorial/Images/SpriteRayIntersection.png b/Tutorial/Images/SpriteRayIntersection.png
new file mode 100644
index 0000000..d7efec5
Binary files /dev/null and b/Tutorial/Images/SpriteRayIntersection.png differ
diff --git a/Tutorial/Images/SquashedText.png b/Tutorial/Images/SquashedText.png
new file mode 100644
index 0000000..65d9176
Binary files /dev/null and b/Tutorial/Images/SquashedText.png differ
diff --git a/Tutorial/Images/StagedResponse.png b/Tutorial/Images/StagedResponse.png
new file mode 100644
index 0000000..8d60eee
Binary files /dev/null and b/Tutorial/Images/StagedResponse.png differ
diff --git a/Tutorial/Images/StateMachine.png b/Tutorial/Images/StateMachine.png
new file mode 100644
index 0000000..1d7016b
Binary files /dev/null and b/Tutorial/Images/StateMachine.png differ
diff --git a/Tutorial/Images/StoredWidthTest.png b/Tutorial/Images/StoredWidthTest.png
new file mode 100644
index 0000000..ed29c3b
Binary files /dev/null and b/Tutorial/Images/StoredWidthTest.png differ
diff --git a/Tutorial/Images/StraightWalls.png b/Tutorial/Images/StraightWalls.png
new file mode 100644
index 0000000..bf52d82
Binary files /dev/null and b/Tutorial/Images/StraightWalls.png differ
diff --git a/Tutorial/Images/StretchedWalls.png b/Tutorial/Images/StretchedWalls.png
new file mode 100644
index 0000000..b1214ed
Binary files /dev/null and b/Tutorial/Images/StretchedWalls.png differ
diff --git a/Tutorial/Images/SwitchFlipped.png b/Tutorial/Images/SwitchFlipped.png
new file mode 100644
index 0000000..2ff472b
Binary files /dev/null and b/Tutorial/Images/SwitchFlipped.png differ
diff --git a/Tutorial/Images/SwitchOnWall.png b/Tutorial/Images/SwitchOnWall.png
new file mode 100644
index 0000000..dca84bc
Binary files /dev/null and b/Tutorial/Images/SwitchOnWall.png differ
diff --git a/Tutorial/Images/SwitchTextures.png b/Tutorial/Images/SwitchTextures.png
new file mode 100644
index 0000000..82e4072
Binary files /dev/null and b/Tutorial/Images/SwitchTextures.png differ
diff --git a/Tutorial/Images/TargetMembership.png b/Tutorial/Images/TargetMembership.png
new file mode 100644
index 0000000..63f94cd
Binary files /dev/null and b/Tutorial/Images/TargetMembership.png differ
diff --git a/Tutorial/Images/TextFont.png b/Tutorial/Images/TextFont.png
new file mode 100644
index 0000000..2dfdc70
Binary files /dev/null and b/Tutorial/Images/TextFont.png differ
diff --git a/Tutorial/Images/TextureLookupTrace.png b/Tutorial/Images/TextureLookupTrace.png
new file mode 100644
index 0000000..9be5cb9
Binary files /dev/null and b/Tutorial/Images/TextureLookupTrace.png differ
diff --git a/Tutorial/Images/TextureMapping.png b/Tutorial/Images/TextureMapping.png
new file mode 100644
index 0000000..a25ada6
Binary files /dev/null and b/Tutorial/Images/TextureMapping.png differ
diff --git a/Tutorial/Images/TextureSmearing.png b/Tutorial/Images/TextureSmearing.png
new file mode 100644
index 0000000..eb08db5
Binary files /dev/null and b/Tutorial/Images/TextureSmearing.png differ
diff --git a/Tutorial/Images/TextureVariants.png b/Tutorial/Images/TextureVariants.png
new file mode 100644
index 0000000..9f934a1
Binary files /dev/null and b/Tutorial/Images/TextureVariants.png differ
diff --git a/Tutorial/Images/TexturedCeiling.png b/Tutorial/Images/TexturedCeiling.png
new file mode 100644
index 0000000..55f464d
Binary files /dev/null and b/Tutorial/Images/TexturedCeiling.png differ
diff --git a/Tutorial/Images/TexturedFloor.png b/Tutorial/Images/TexturedFloor.png
new file mode 100644
index 0000000..cc60a06
Binary files /dev/null and b/Tutorial/Images/TexturedFloor.png differ
diff --git a/Tutorial/Images/TexturesAndLighting.png b/Tutorial/Images/TexturesAndLighting.png
new file mode 100644
index 0000000..b53a537
Binary files /dev/null and b/Tutorial/Images/TexturesAndLighting.png differ
diff --git a/Tutorial/Images/TheUpsideDown.png b/Tutorial/Images/TheUpsideDown.png
new file mode 100644
index 0000000..fbfe89e
Binary files /dev/null and b/Tutorial/Images/TheUpsideDown.png differ
diff --git a/Tutorial/Images/TileEdgeHit.png b/Tutorial/Images/TileEdgeHit.png
new file mode 100644
index 0000000..8b607aa
Binary files /dev/null and b/Tutorial/Images/TileEdgeHit.png differ
diff --git a/Tutorial/Images/TileSteps.png b/Tutorial/Images/TileSteps.png
new file mode 100644
index 0000000..d51380d
Binary files /dev/null and b/Tutorial/Images/TileSteps.png differ
diff --git a/Tutorial/Images/Tilemap.png b/Tutorial/Images/Tilemap.png
new file mode 100644
index 0000000..06b3f02
Binary files /dev/null and b/Tutorial/Images/Tilemap.png differ
diff --git a/Tutorial/Images/TinyHealthIndicator.png b/Tutorial/Images/TinyHealthIndicator.png
new file mode 100644
index 0000000..0206c59
Binary files /dev/null and b/Tutorial/Images/TinyHealthIndicator.png differ
diff --git a/Tutorial/Images/TitleLogo.png b/Tutorial/Images/TitleLogo.png
new file mode 100644
index 0000000..d7fc90d
Binary files /dev/null and b/Tutorial/Images/TitleLogo.png differ
diff --git a/Tutorial/Images/TitleScreenBackground.png b/Tutorial/Images/TitleScreenBackground.png
new file mode 100644
index 0000000..1ccc1c4
Binary files /dev/null and b/Tutorial/Images/TitleScreenBackground.png differ
diff --git a/Tutorial/Images/TitleScreenWithLogo.png b/Tutorial/Images/TitleScreenWithLogo.png
new file mode 100644
index 0000000..f8011e3
Binary files /dev/null and b/Tutorial/Images/TitleScreenWithLogo.png differ
diff --git a/Tutorial/Images/TitleScreenWithText.png b/Tutorial/Images/TitleScreenWithText.png
new file mode 100644
index 0000000..275f1f3
Binary files /dev/null and b/Tutorial/Images/TitleScreenWithText.png differ
diff --git a/Tutorial/Images/UndistortedShotgun.png b/Tutorial/Images/UndistortedShotgun.png
new file mode 100644
index 0000000..d2f8aaa
Binary files /dev/null and b/Tutorial/Images/UndistortedShotgun.png differ
diff --git a/Tutorial/Images/UnoptimizedTest.png b/Tutorial/Images/UnoptimizedTest.png
new file mode 100644
index 0000000..1924d1c
Binary files /dev/null and b/Tutorial/Images/UnoptimizedTest.png differ
diff --git a/Tutorial/Images/UnoptimizedTrace.png b/Tutorial/Images/UnoptimizedTrace.png
new file mode 100644
index 0000000..6b0c139
Binary files /dev/null and b/Tutorial/Images/UnoptimizedTrace.png differ
diff --git a/Tutorial/Images/VariedTextures.png b/Tutorial/Images/VariedTextures.png
new file mode 100644
index 0000000..8891eae
Binary files /dev/null and b/Tutorial/Images/VariedTextures.png differ
diff --git a/Tutorial/Images/ViewFrustum.png b/Tutorial/Images/ViewFrustum.png
new file mode 100644
index 0000000..af5e4f0
Binary files /dev/null and b/Tutorial/Images/ViewFrustum.png differ
diff --git a/Tutorial/Images/ViewPlane.png b/Tutorial/Images/ViewPlane.png
new file mode 100644
index 0000000..48f4750
Binary files /dev/null and b/Tutorial/Images/ViewPlane.png differ
diff --git a/Tutorial/Images/WalkingFrames.png b/Tutorial/Images/WalkingFrames.png
new file mode 100644
index 0000000..c82b99f
Binary files /dev/null and b/Tutorial/Images/WalkingFrames.png differ
diff --git a/Tutorial/Images/WallCollisions.png b/Tutorial/Images/WallCollisions.png
new file mode 100644
index 0000000..791f548
Binary files /dev/null and b/Tutorial/Images/WallCollisions.png differ
diff --git a/Tutorial/Images/WallDistance.png b/Tutorial/Images/WallDistance.png
new file mode 100644
index 0000000..7f6911b
Binary files /dev/null and b/Tutorial/Images/WallDistance.png differ
diff --git a/Tutorial/Images/WallHit.png b/Tutorial/Images/WallHit.png
new file mode 100644
index 0000000..afb897c
Binary files /dev/null and b/Tutorial/Images/WallHit.png differ
diff --git a/Tutorial/Images/WallTexture.png b/Tutorial/Images/WallTexture.png
new file mode 100644
index 0000000..40fefd7
Binary files /dev/null and b/Tutorial/Images/WallTexture.png differ
diff --git a/Tutorial/Images/WeaponIcon.png b/Tutorial/Images/WeaponIcon.png
new file mode 100644
index 0000000..8517425
Binary files /dev/null and b/Tutorial/Images/WeaponIcon.png differ
diff --git a/Tutorial/Images/WeaponIcons.png b/Tutorial/Images/WeaponIcons.png
new file mode 100644
index 0000000..ead2ead
Binary files /dev/null and b/Tutorial/Images/WeaponIcons.png differ
diff --git a/Tutorial/Images/WolfensteinSprites.png b/Tutorial/Images/WolfensteinSprites.png
new file mode 100644
index 0000000..ec1ae04
Binary files /dev/null and b/Tutorial/Images/WolfensteinSprites.png differ
diff --git a/Tutorial/Images/YellowAlert.png b/Tutorial/Images/YellowAlert.png
new file mode 100644
index 0000000..0943566
Binary files /dev/null and b/Tutorial/Images/YellowAlert.png differ
diff --git a/Tutorial/Part1.md b/Tutorial/Part1.md
new file mode 100644
index 0000000..6efbb43
--- /dev/null
+++ b/Tutorial/Part1.md
@@ -0,0 +1,754 @@
+## Part 1: Separation of Concerns
+
+In a traditional app, it is common practice to split the logic into three layers - the Model, View and Controller[[1]](#footnote1). Games can also be divided into three similar layers:
+
+* Platform Layer - Platform-specific code to integrate with the host operating system.
+* Engine - Platform-independent game functions like asset loaders, rendering, physics, pathfinding AI, etc.
+* Game Logic - Gameplay and graphics specific to this game. Tightly coupled to a particular engine.
+
+
+
+Apple would ideally like you to build your game or engine using frameworks like GameKit, SpriteKit and SceneKit. These are powerful and easy-to-use, but doing so shackles your game completely to Apple's platforms.
+
+In practice, few game developers do this because Apple alone doesn't represent a large enough share of the games market. A popular alternative is to use a 3rd party framework like Unity or Unreal that provide a cross-platform engine, and have platform layers for many different systems. This frees you from Apple exclusivity, but ties you into a 3rd party ecosystem instead.
+
+In this tutorial we are choosing a third path: We'll create a very minimal platform layer for iOS using UIKit, then build both the game and engine from scratch using pure Swift, with no external dependencies.
+
+### Project Setup
+
+Modern games require a fairly complex interface with their host operating system in order to implement hardware accelerated graphics, sound and user input. But fortunately, *we aren't building a modern game*.
+
+In the era of Wolfenstein 3D, computers were mainly intended as business machines, and did almost nothing for you as a game developer. We're going to build this game using a similar approach to what they used in the MSDOS days (while taking advantage of modern advancements such as fast floating-point math and 32-bit color).
+
+Start by creating a new *Single View iOS App* project in Xcode. To keep ourselves honest, we're going to put the engine code in a separate module, creating an "air gap" between the game code and the code that interacts with iOS.
+
+To create the Engine module, go to the Targets tab and add a new target of type *Framework* called "Engine". Xcode will automatically link this in to your main app. It will also create a C header file in the Engine folder called `Engine.h` which imports UIKit. We aren't going to be using UIKit in the engine, so you can go ahead and delete `Engine.h`.
+
+
+
+If you aren't confident creating a project with a nested framework (or if you just don't feel like setting up a project from scratch) you can download the base project from [here](https://github.com/nicklockwood/RetroRampage/archive/Start.zip).
+
+Here is how the project structure should look:
+
+
+
+### View Setup
+
+Open `ViewController.swift` and replace its contents with the following:
+
+```swift
+import UIKit
+
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpImageView()
+ }
+
+ func setUpImageView() {
+ view.addSubview(imageView)
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+ imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+ imageView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
+ imageView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
+ imageView.contentMode = .scaleAspectFit
+ imageView.backgroundColor = .black
+ }
+}
+```
+
+Now run the app. If all has gone well you should see the black background of the `UIImageView` filling the whole screen.
+
+### Pixel Pushing
+
+Apps display views. Games typically deal with sprites or polygons. We are going to be working directly with *pixels*. Everything you see on screen is made up of pixels, and on a modern system, pixels are usually stored as 4 bytes[[2]](#footnote2) - one byte for each of the red, green, blue and alpha channels.
+
+UIKit provides the `UIColor` class, which treats those channels as floating-point values in the range 0.0 to 1.0. This is a convenient abstraction, but it's an expensive way to store colors and it couples us to UIKit, so we'll create our own `Color` type instead. Create a new file called `Color.swift` in the Engine module with the following contents[[3]](#footnote3):
+
+```swift
+public struct Color {
+ public var r, g, b, a: UInt8
+
+ public init(r: UInt8, g: UInt8, b: UInt8, a: UInt8 = 255) {
+ self.r = r
+ self.g = g
+ self.b = b
+ self.a = a
+ }
+}
+```
+
+Since we're mostly going to be dealing with opaque colors, we've defaulted the alpha value to 255 (no transparency) in the initializer. In the same file, add some common color constants, which will be useful later[[4]](#footnote4):
+
+```swift
+public extension Color {
+ static let clear = Color(r: 0, g: 0, b: 0, a: 0)
+ static let black = Color(r: 0, g: 0, b: 0)
+ static let white = Color(r: 255, g: 255, b: 255)
+ static let gray = Color(r: 192, g: 192, b: 192)
+ static let red = Color(r: 255, g: 0, b: 0)
+ static let green = Color(r: 0, g: 255, b: 0)
+ static let blue = Color(r: 0, g: 0, b: 255)
+}
+```
+
+Now that the engine has a way to represent a pixel color, we need a type to represent an entire image. We'll call this `Bitmap` to avoid confusion with `UIImage`. Create a new file called `Bitmap.swift` in the Engine module with the following contents:
+
+```swift
+public struct Bitmap {
+ public private(set) var pixels: [Color]
+ public let width: Int
+
+ public init(width: Int, pixels: [Color]) {
+ self.width = width
+ self.pixels = pixels
+ }
+}
+```
+
+You'll notice that `Bitmap` stores its pixels in a flat array instead of a 2D matrix as you might expect. This is mainly for efficiency - allocating a flat array like this means that the pixels will be stored contiguously in a single block of memory instead of each row or column being allocated separately on the heap.
+
+In performance-critical code, consideration of memory layout is important to avoid cache-misses, which occur when the program is forced to page in new data from main memory instead of the much-faster [processor cache](https://en.wikipedia.org/wiki/CPU_cache).
+
+Accessing a 2D image using a one-dimensional index (and having to compute the row offsets each time) isn't very ergonomic though, so lets add a 2D subscript. While we're at it, let's also add a computed property for the image height, and a convenience initializer for creating an empty image:
+
+```swift
+public extension Bitmap {
+ var height: Int {
+ return pixels.count / width
+ }
+
+ subscript(x: Int, y: Int) -> Color {
+ get { return pixels[y * width + x] }
+ set { pixels[y * width + x] = newValue }
+ }
+
+ init(width: Int, height: Int, color: Color) {
+ self.pixels = Array(repeating: color, count: width * height)
+ self.width = width
+ }
+}
+```
+
+The subscript takes an X and Y coordinate within the image and does some math to compute the offset into the flat `pixels` array. The code assumes that the pixels are stored in row order rather than column order (which is the convention on most platforms, including iOS), but this assumption is not exposed in the public interface, so we can change it later if necessary.
+
+Add a new file to the main app target called `UIImage+Bitmap.swift` with the following contents:
+
+```swift
+import UIKit
+import Engine
+
+extension UIImage {
+ convenience init?(bitmap: Bitmap) {
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = bitmap.width * bytesPerPixel
+
+ guard let providerRef = CGDataProvider(data: Data(
+ bytes: bitmap.pixels, count: bitmap.height * bytesPerRow
+ ) as CFData) else {
+ return nil
+ }
+
+ guard let cgImage = CGImage(
+ width: bitmap.width,
+ height: bitmap.height,
+ bitsPerComponent: 8,
+ bitsPerPixel: bytesPerPixel * 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: CGBitmapInfo(rawValue: alphaInfo.rawValue),
+ provider: providerRef,
+ decode: nil,
+ shouldInterpolate: true,
+ intent: .defaultIntent
+ ) else {
+ return nil
+ }
+
+ self.init(cgImage: cgImage)
+ }
+}
+```
+
+Notice that the file imports both UIKit and the Engine module - that's a good clue as to its purpose, which is to act as a bridge between UIKit and the game engine. Specifically, it provides a way to take a `Bitmap` and turn it into a `UIImage` for display on screen.
+
+We want this conversion process to be as cheap as possible since we're repeating it every frame. The `pixels` array already has the correct memory layout to be consumed directly by `CGImage`, so it's a simple copy without any need for per-pixel manipulation.
+
+Pay particular attention to this line:
+
+```swift
+let alphaInfo = CGImageAlphaInfo.premultipliedLast
+```
+
+Usually in Swift, the order in which properties are declared in a struct doesn't matter very much. In this case though, we are relying on the order and alignment of the `r`, `g`, `b` and `a` properties of `Color` to match the format we specified for `alphaInfo`.
+
+The constant `premultipliedLast` specifies that the alpha value is stored *after* the RGB channels. It is common practice on iOS to use `premultipliedFirst` instead, and if we used that we would have to change the property order in our `Color` struct to `a, r, g, b` or our colors would be messed up.
+
+Now we have all the pieces we need to display a `Bitmap` on the screen. Add the following code to `viewDidLoad()` in `ViewController.swift` (note that you need to add `import Engine` at the top of the file as well since the `Bitmap` type is defined in the Engine module):
+
+```swift
+import Engine
+
+...
+
+override func viewDidLoad() {
+ super.viewDidLoad()
+ setUpImageView()
+
+ var bitmap = Bitmap(width: 8, height: 8, color: .white)
+ bitmap[0, 0] = .blue
+
+ imageView.image = UIImage(bitmap: bitmap)
+}
+```
+
+This code creates a new 8x8 `Bitmap` filled with white, and then sets the pixel at 0,0 (the top-left corner) to blue. The result looks like this:
+
+![A blurry blue pixel](Images/BlurryBluePixel.png)
+
+Now I know what you're thinking: *Why is it all blurry?*
+
+What you are seeing here is the modern miracle of [bilinear filtering](https://en.wikipedia.org/wiki/Bilinear_filtering). In the good old days, when computers were slow, when we expanded images on the screen we saw big square pixels[[5]](#footnote5) *and we liked it*. But in the mid-to-late '90s, as 3D graphics cards became popular and math became cheaper, bilinear filtering replaced the so-called [nearest-neighbor](https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation) algorithm used previously.
+
+Bilinear filtering combines nearby pixel values, creating results that are interpolated rather than just sampled directly from the original image. When downscaling images this produces dramatically better results, because instead of pixels just disappearing as the output shrinks, their color still contributes to the final image. Unfortunately, when *upscaling* images - especially chunky pixel-art - the results tend to look like blurry garbage.
+
+To solve this, add the following line to the end of `ViewController.setUpImageView()`:
+
+```swift
+imageView.layer.magnificationFilter = .nearest
+```
+
+That tells the `UIImageView` to use the nearest-neighbor algorithm when magnifying an image instead of bilinear filtering (which is the default for both upscaling and downscaling). Run the app again and the blurriness should be gone.
+
+![A sharp blue pixel](Images/SharpBluePixel.png)
+
+### Inversion of Control
+
+Right now our drawing logic lives in the platform layer, which is kind of backwards. The game engine should be responsible for the drawing, not the platform. Create a new file called `Renderer.swift` in the Engine module with the following contents:
+
+```swift
+public struct Renderer {
+ public private(set) var bitmap: Bitmap
+
+ public init(width: Int, height: Int) {
+ self.bitmap = Bitmap(width: width, height: height, color: .white)
+ }
+}
+
+public extension Renderer {
+ mutating func draw() {
+ bitmap[0, 0] = .blue
+ }
+}
+
+```
+
+Then in `ViewController.viewDidLoad()` replace the lines:
+
+```swift
+var bitmap = Bitmap(width: 8, height: 8, color: .white)
+bitmap[0, 0] = .blue
+
+imageView.image = UIImage(bitmap: bitmap)
+```
+
+with:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+renderer.draw()
+
+imageView.image = UIImage(bitmap: renderer.bitmap)
+```
+
+Run it again, and the result should look the same as before.
+
+### Loop-the-Loop
+
+That's all very well, but one static pixel is hardly a game. In a game, things change over time, even when the player isn't doing anything. This is different from a typical GUI application where the app spends most of its time sitting idle, waiting for user input.
+
+Right now we just draw one screen and then stop. In a real game, everything happens in a loop called the *game loop*. Game loops are usually driven in sync with the display's [refresh rate](https://en.wikipedia.org/wiki/Refresh_rate).
+
+In the early days of home computing, computer monitors used a scanning electron beam to stimulate phosphorescent paint on the inside of the glass screen. The rate at which that beam could scan across the entire screen was called the *refresh rate* - typically 50 Hz (PAL) or 60 Hz (NTSC).
+
+Early graphics cards did little more than expose a pixel buffer into which apps and games could draw their interface. If you timed your drawing at the wrong point in the beam cycle, the image would flicker, which is why it was important for game loops to be synchronized to the refresh rate.
+
+Modern LCD or OLED screens no longer have a scanning beam, but they still have a finite rate at which data can be streamed into and out of video memory. Thanks to [double buffering](https://en.wikipedia.org/wiki/Multiple_buffering#Double_buffering_in_computer_graphics) we no longer have to worry about flickering and tearing, but we still need to synchronize our drawing to the refresh rate in order to achieve smooth animations.
+
+If we redraw the interface too frequently then we'll waste cycles on drawing that will never be seen. If we redraw too slowly then the screen will appear frozen. If we redraw slightly out of step with the refresh, objects will appear to stutter as they move across the screen. Most iOS developers have experienced the *jerky scrolling* that arises when a `UITableView` takes too long to redraw its cells.
+
+In single-threaded, single-process operating systems like DOS, the game loop was an actual `while` loop, repeatedly scanning for user input, running the game logic, then redrawing the screen. Modern operating systems like iOS have a lot of things going on in parallel, and blocking the main thread is a big no-no. So instead of a loop, we use a *timer*, which calls our update logic at a specified frequency, handing back control to the OS while it's waiting.
+
+In iOS, the recommended way to synchronize code execution to the screen refresh is to use `CADisplayLink`. `CADisplayLink` is a special type of timer whose period is always an exact multiple of the refresh rate (typically 60 Hz, although some modern iOS devices can manage 120 Hz).
+
+Unlike `Timer`, `CADisplayLink` doesn't have a Swift-friendly, closure-based API, so we need to use the target/selector pattern. Add the following method to `ViewController` (Note the `@objc` annotation, which is needed for the selector binding to work):
+
+```swift
+@objc func update(_ displayLink: CADisplayLink) {
+ var renderer = Renderer(width: 8, height: 8)
+ renderer.draw()
+
+ imageView.image = UIImage(bitmap: renderer.bitmap)
+}
+```
+
+Then in `viewDidLoad()`, replace the lines:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+renderer.draw()
+
+imageView.image = UIImage(bitmap: renderer.bitmap)
+```
+
+with:
+
+```swift
+let displayLink = CADisplayLink(target: self, selector: #selector(update))
+displayLink.add(to: .main, forMode: .common)
+```
+
+This code creates a `CADisplayLink` with the default frequency (60 Hz) and adds it to the main `RunLoop`. A `RunLoop` is essentially the same concept as a game loop - it's a loop that runs continuously on a given thread, monitoring for user-triggered events like touches or key presses, and firing scheduled events like timers. By adding our display link to the `RunLoop` we are telling the system to keep track of it and call our `update()` method as needed until further notice.
+
+Note the `forMode:` argument. The `Runloop.Mode` is a sort of priority system that iOS uses to determine which events need to be updated under which circumstances. The documentation for `CADisplayLink` suggests using `default` for the mode, but events bound to the `default` mode won't fire during *tracking* (e.g. when the user is scrolling, or manipulating a UI control). By using the `common` mode we ensure that our `CADisplayLink` will be given top priority.
+
+A word of caution: `CADisplayLink` is retained by the `RunLoop`, and also retains a strong reference to its `target` (the view controller in this case) while running. We are not keeping a reference to the display link anywhere, so we have no means to stop it, which means it will run forever until the app terminates, and will never release the `ViewController`, even if it moves off-screen.
+
+In this case that doesn't matter because `ViewController` is the root view controller and will never be dismissed, but if you were to build a game that used multiple view controllers for different screens it would be something to watch out for. A simple solution would be to store a reference to the `CADisplayLink` and call its `invalidate()` method when the view controller is dismissed.
+
+### Setting Things in Motion
+
+If everything worked correctly, we should now be redrawing our screen at 60 FPS (Frames Per Second). We're still always drawing the same thing though, so lets add some animation.
+
+Motion is a change of position over time. Position in a 2D world is typically defined as a vector with X (for horizontal position) and Y (for vertical position). We already touched on the idea of X and Y coordinates in the context of accessing pixels in a `Bitmap`, and world coordinates are similar. But unlike pixels, entities in the world won't necessarily be aligned exactly on a grid, so we need to represent them using floating point values.
+
+Let's create a new type to represent a position in the world. Add a new file to the Engine module called `Vector.swift` with the following contents:
+
+```swift
+public struct Vector {
+ public var x, y: Double
+
+ public init(x: Double, y: Double) {
+ self.x = x
+ self.y = y
+ }
+}
+```
+
+Vectors are a very flexible type - they can represent position, velocity, distance, direction and more. We will be making a lot of use of this type throughout the game engine. Our vector type could easily be extended into the 3rd dimension by adding a Z component, but because our game mechanics are primarily 2D, we'll stick with just X and Y as it makes the math much simpler.
+
+While we're here, let's add a few operator overloads[[6]](#footnote6) to make `Vector` easier to work with:
+
+```swift
+public extension Vector {
+ static func + (lhs: Vector, rhs: Vector) -> Vector {
+ return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
+ }
+
+ static func - (lhs: Vector, rhs: Vector) -> Vector {
+ return Vector(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
+ }
+
+ static func * (lhs: Vector, rhs: Double) -> Vector {
+ return Vector(x: lhs.x * rhs, y: lhs.y * rhs)
+ }
+
+ static func / (lhs: Vector, rhs: Double) -> Vector {
+ return Vector(x: lhs.x / rhs, y: lhs.y / rhs)
+ }
+
+ static func * (lhs: Double, rhs: Vector) -> Vector {
+ return Vector(x: lhs * rhs.x, y: lhs * rhs.y)
+ }
+
+ static func / (lhs: Double, rhs: Vector) -> Vector {
+ return Vector(x: lhs / rhs.x, y: lhs / rhs.y)
+ }
+
+ static func += (lhs: inout Vector, rhs: Vector) {
+ lhs.x += rhs.x
+ lhs.y += rhs.y
+ }
+
+ static func -= (lhs: inout Vector, rhs: Vector) {
+ lhs.x -= rhs.x
+ lhs.y -= rhs.y
+ }
+
+ static func *= (lhs: inout Vector, rhs: Double) {
+ lhs.x *= rhs
+ lhs.y *= rhs
+ }
+
+ static func /= (lhs: inout Vector, rhs: Double) {
+ lhs.x /= rhs
+ lhs.y /= rhs
+ }
+
+ static prefix func - (rhs: Vector) -> Vector {
+ return Vector(x: -rhs.x, y: -rhs.y)
+ }
+}
+```
+
+### Ready Player One
+
+We'll need a *player* object to act as our avatar in the game. Create a new file called `Player.swift` in the Engine module, with the following contents:
+
+```swift
+public struct Player {
+ public var position: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ }
+}
+```
+
+The player's position should persist between frames, so it can't be a local variable. Add a `player` property to the `ViewController` class:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private var player = Player(position: Vector(x: 4, y: 4))
+
+ ...
+}
+```
+
+The player is positioned at 4,4 by default which would be the center of a world that had a size of 8x8 units square. We haven't really thought about what those units are or what any of that means yet, but for now let's assume that the world's dimensions match the bitmap that we've been drawing into - i.e. that the units are in pixels.
+
+Now we have a player instance, the renderer will need access to it in order to draw it. In `Renderer.swift` change the `draw()` method to:
+
+```swift
+mutating func draw(_ player: Player) {
+ bitmap[Int(player.position.x), Int(player.position.y)] = .blue
+}
+```
+
+Then in `ViewController.swift` update the line:
+
+```swift
+renderer.draw()
+```
+
+to:
+
+```swift
+renderer.draw(player)
+```
+
+Run the app and and you'll see that we now draw a pixel at the player's position - the middle of the bitmap - instead of the top corner. Next, we'll make that position change over time.
+
+### Need for Speed
+
+A change of position over time is called [velocity](https://en.wikipedia.org/wiki/Velocity). Velocity is a vector that combines both the [speed](https://en.wikipedia.org/wiki/Speed) at which an object is moving, and the direction of movement. Let's add a velocity property to `Player`:
+
+```swift
+public struct Player {
+ public var position: Vector
+ public var velocity: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 1, y: 1)
+ }
+}
+```
+
+Now that the player has a velocity, we can implement movement by adding the player `velocity` to the player `position` every frame. We don't really want to add movement logic inside the `draw()` method because that violates the [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) principle, so let's add a new method to `Player` called `update()`:
+
+```swift
+public extension Player {
+ mutating func update() {
+ position += velocity
+ }
+}
+```
+
+In `ViewController`'s `update()` method, call `player.update()` before drawing:
+
+```swift
+@objc func update(_ displayLink: CADisplayLink) {
+ player.update()
+
+ var renderer = Renderer(width: 8, height: 8)
+ ...
+}
+```
+
+If we run the app now it crashes almost instantly. The problem is that the player quickly moves outside of visible area, and when we try to draw outside the bitmap we get an out-of-bounds error for the `pixels` array.
+
+This is likely to be a common problem. Rather than having to add tedious bounds checking to every drawing function we write, let's just add a guard inside the subscript to prevent us from accidentally drawing outside the array. In `Bitmap.swift`, replace the line:
+
+```swift
+set { pixels[y * width + x] = newValue }
+```
+
+with:
+
+```swift
+set {
+ guard x >= 0, y >= 0, x < width, y < height else { return }
+ pixels[y * width + x] = newValue
+}
+```
+
+With that protection in place the app won't crash anymore, but the player avatar still vanishes from view almost immediately. To solve that, we can wrap the player position within the bitmap, using the `formTruncatingRemainder()` function from the Swift standard library:
+
+```swift
+mutating func update() {
+ position += velocity
+ position.x.formTruncatingRemainder(dividingBy: 8)
+ position.y.formTruncatingRemainder(dividingBy: 8)
+}
+```
+
+Run the app now and you should see the blue player pixel streaking diagonally across the screen, wrapping back around to the top-left corner whenever it moves off the screen. It seems to be moving much too fast - but how fast is it exactly?
+
+For the player velocity we used `Vector(x: 1, y: 1)`, which means that the player will move one pixel to the right and down every frame. Since our frame rate is 60 FPS, that means it's moving 60 pixels per second, and since the world is only 8 pixels wide that means it flashes across the entire screen 7.5 times per second!
+
+If we want it to move at one unit per *second* instead of one unit per frame, we need to divide the velocity by the frame-rate:
+
+```swift
+func update() {
+ position += velocity / 60
+ ...
+}
+```
+
+That magic number 60 is pretty nasty though because it ties us to 60 FPS, when we might decide that we actually want to run at 30 FPS on older devices (or 120 FPS on newer ones) without it affecting the speed at which objects move.
+
+Instead of hard-coding the time factor as 1/60, let's pass it as a parameter to the update function:
+
+```swift
+mutating func update(timeStep: Double) {
+ position += velocity * timeStep
+ ...
+}
+```
+The value for `timeStep` will need to be provided by the platform layer. Although `CADisplayLink` has a number of time-related properties, it doesn't have exactly the value we need, so we'll need to compute it. To do that we'll add a `lastFrameTime` property to `ViewController`:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private var player = Player(position: Vector(x: 4, y: 4))
+ private var lastFrameTime = CACurrentMediaTime()
+
+ ...
+}
+```
+
+Then, in the `update()` method, replace the line:
+
+```swift
+player.update()
+```
+
+with:
+
+```swift
+let timeStep = displayLink.timestamp - lastFrameTime
+player.update(timeStep: timeStep)
+lastFrameTime = displayLink.timestamp
+```
+
+Run the game again and you'll see that the player avatar now moves much more slowly. The frame rate appears much lower too, but that's an illusion - the player avatar *really is* being moved and redrawn ~60 times per second, but its position is rounded down to the nearest whole pixel in the bitmap (which only has 8x8 resolution), and the rounded value only changes once per second.
+
+We can make the movement smoother by increasing the resolution of the bitmap from 8x8 to something much higher, but that will also make the player rectangle smaller and slower because both its size and speed are proportional to the bitmap resolution. Besides, we've hard-coded those 8x8 pixel dimensions in a couple of places already and all these magic numbers are starting to get a bit unwieldy. It's time for a refactor.
+
+### Brave New World
+
+We don't want the dimensions of the world to be coupled to the dimensions of the bitmap that we are drawing into, so let's make a new type to represent the world itself. Create a new file in the Engine module called `World.swift` with the following contents:
+
+```swift
+public struct World {
+ public let size: Vector
+ public var player: Player
+
+ public init() {
+ self.size = Vector(x: 8, y: 8)
+ self.player = Player(position: Vector(x: 4, y: 4))
+ }
+}
+
+public extension World {
+ mutating func update(timeStep: Double) {
+ player.position += player.velocity * timeStep
+ player.position.x.formTruncatingRemainder(dividingBy: size.x)
+ player.position.y.formTruncatingRemainder(dividingBy: size.y)
+ }
+}
+```
+
+And delete the `update()` method from `Player.swift`, as we won't be needing it anymore.
+
+In `ViewController.swift` replace the line:
+
+```swift
+private var player = Player(position: Vector(x: 4, y: 4))
+```
+
+with:
+
+```swift
+private var world = World()
+```
+
+Then in `ViewController.update()` replace:
+
+```swift
+player.update(timeStep: timeStep)
+```
+
+with:
+
+```swift
+world.update(timeStep: timeStep)
+```
+
+And replace:
+
+```swift
+renderer.draw(player)
+```
+
+with:
+
+```swift
+renderer.draw(world.player)
+```
+
+Currently, the world is still the same size as the bitmap (8x8 units), but because the world now has its own coordinate system independent of the bitmap, we are free to make the bitmap resolution higher without affecting the player speed.
+
+If we want to be able to draw the player avatar at different scales, we can no longer just represent it as a single pixel. Let's introduce a rectangle type to represent the player's size on screen. Create a new file in the Engine module called `Rect.swift` with the following contents:
+
+```swift
+public struct Rect {
+ var min, max: Vector
+
+ public init(min: Vector, max: Vector) {
+ self.min = min
+ self.max = max
+ }
+}
+```
+
+We'll also add a convenience method to `Bitmap` to draw a `Rect`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func fill(rect: Rect, color: Color) {
+ for y in Int(rect.min.y) ..< Int(rect.max.y) {
+ for x in Int(rect.min.x) ..< Int(rect.max.x) {
+ self[x, y] = color
+ }
+ }
+ }
+}
+```
+
+Now that the player avatar is a rectangle rather than a single pixel, we should make the size configurable. Add a `radius` property to `Player`:
+
+```swift
+public struct Player {
+ public let radius: Double = 0.5
+ ...
+}
+```
+
+Radius might seem an odd way to specify the size of a square, and you may be wondering why we don't use a width and height, but a radius value will be easier to work with (as we'll see in just a second). Add a computed property to `Player` to get the bounding `Rect` (in world units):
+
+```swift
+public extension Player {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+}
+```
+
+Here is why the radius is useful. We want the player rectangle to be *centered* on their position, which means that it needs to extend by half their width/height in every direction. We can use the `radius` value for this directly instead of dividing by two every time.
+
+Finally, we can update `Renderer.draw()` to display the player as a filled rectangle instead of just a single pixel. Since the method now needs to know the world size in order to compute the scale at which to draw, we'll update the method signature to accept the whole world rather than just the player:
+
+```swift
+mutating func draw(_ world: World) {
+ let scale = Double(bitmap.height) / world.size.y
+
+ // Draw player
+ var rect = world.player.rect
+ rect.min *= scale
+ rect.max *= scale
+ bitmap.fill(rect: rect, color: .blue)
+}
+```
+
+Then, in `ViewController`, update the line:
+
+```swift
+renderer.draw(world.player)
+```
+
+to just:
+
+```swift
+renderer.draw(world)
+```
+
+By dividing the bitmap height by the world height[[7]](#footnote7) we get the relative scale between world units and pixels. That value is then used to scale the player rectangle so that its size on screen is independent of the pixel resolution of the bitmap.
+
+The bitmap size is set inside the platform layer, in `ViewController`. This makes sense because the resolution at which we draw the output should be chosen to suit the device we are displaying it on. But instead of matching the world size, let's derive it from the screen size. In `ViewController.swift` replace the line:
+
+```swift
+var renderer = Renderer(width: 8, height: 8)
+```
+
+with:
+
+```swift
+let size = Int(min(imageView.bounds.width, imageView.bounds.height))
+var renderer = Renderer(width: size, height: size)
+```
+
+Note that the view size is in *points*, which on a Retina display is only a half or a third of the actual pixel resolution. This isn't a mistake - drawing a full-screen image with the CPU is expensive, and I don't recommend trying to do it at full pixel resolution. Besides, chunky pixels are good for the authentic retro look!
+
+Run the app again and you should now at last see the blue player rectangle moving smoothly across the screen (don't worry if it's a bit jerky on the simulator - it will run silky smooth in release mode on a real device).
+
+And that's a wrap for Part 1. To recap, in this part we...
+
+* Created a platform-independent software renderer
+* Created a simple game engine with a moving player avatar
+* Created an iOS platform layer to display the output of the engine
+
+In [Part 2](Part2.md) we'll make the world a bit more interesting, and implement user input so the player can travel in any direction, not just diagonally.
+
+### Reader Exercises
+
+1. We've set the bitmap size to match the screen points (rather than physical pixels) to keep the total size down. But if we opened the game on an iPad it would potentially still be a lot of pixels. Can you modify the logic to cap the bitmap size at a maximum of 512x512?
+
+2. If we set the player velocity to -1, -1 (up and left instead of down and right) then the wrapping doesn't work. Can you fix it?
+
+3. We'll tackle this in the next chapter, but try moving the player by touch instead of a hard-coded velocity. How would you pass touch gestures from the platform layer to the engine?
+
+
+
+[[1]](#reference1) MVC is not the only architecture used for apps, but patterns such as MVP, MVVM, Clean, VIPER, etc. still share the same basic concepts of *view*, *model* and *everything else*, even if they give them different names or divide their responsibilities across additional layers.
+
+[[2]](#reference2) On older systems, when RAM was at much more of a premium and memory bandwidth was lower, it was common practice to use an [indexed color palette](https://en.wikipedia.org/wiki/Indexed_color). Since the number of colors in the palette was smaller than the total number of possible colors, the space required to store a bitmap made up of color indexes was much smaller than the 3 or 4 bytes needed to store each color directly.
+
+[[3]](#reference3) Note that there is no `import Foundation`. Nothing in the Engine module relies on any code outside the Swift standard library, even Foundation.
+
+[[4]](#reference4) Note that the constants are added via a public extension rather than directly in the `Color` struct. Members added this way will be `public` by default instead of `internal`, which saves some typing.
+
+[[5]](#reference5) Or possibly *rectangles*, because in the popular [Mode 13h](https://en.wikipedia.org/wiki/Mode_13h) VGA display resolution used by games like Wolfenstein and Doom, the pixels weren't actually square.
+
+[[6]](#reference6) I'm not a big fan of operator overloading as a rule, but extending regular math operators to support vector operands like this can make code a lot more concise and easier to follow.
+
+[[7]](#reference7) We could compute X scale and Y scale separately, but since both our world and bitmap are square we know they'll be the same. Later that will change, but we'll be replacing this code before that happens anyway.
diff --git a/Tutorial/Part10.md b/Tutorial/Part10.md
new file mode 100644
index 0000000..6db80a9
--- /dev/null
+++ b/Tutorial/Part10.md
@@ -0,0 +1,773 @@
+## Part 10: Sliding Doors
+
+In [Part 9](Part9.md) we optimized the drawing logic to improve frame rate on older devices and provide some headroom for adding new features. The code for Part 9 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part9.zip).
+
+The game world so far consists of a simple maze containing monsters. It's time to expand that environment a bit with some interactive elements.
+
+### Room Service
+
+Wolfenstein 3D breaks up its endless corridors by splitting areas into separate rooms, separated by doors. These aren't just a graphical flourish, they also affect gameplay - rooms can be set up with surprise ambushes that only trigger when the player opens the door, and doors can also be locked with a key that must be located before the door will open.
+
+But how exactly *did* Wolfenstein's doors work? After all, the ray casting system is specifically designed to work with a grid, and the doors don't actually conform to this grid. Like all other level geometry in Wolfenstein, doors are grid-aligned, but they have zero thickness and don't line up with the other wall surfaces.
+
+Viewed from above, doors are a line segment bridging the gap between two wall sections. The Wolfenstein engine handled these as a special case in the ray caster[[1]](#footnote1). If the ray encountered a door tile it would know that it needed to make a half-step to reach the door, and then, depending on how open the door was, it would either stop at the door or continue through the gap.
+
+
+
+It wouldn't be too hard to extend the ray caster to handle doors in this way, but we aren't going to do that, because we already have another mechanism we can use that is more flexible.
+
+### Write Once, Use Twice
+
+I have a confession to make, dear reader. In [Part 4](Part4.md), when we added the logic for drawing sprites, I made you work a bit harder than you really needed to.
+
+If you just want to draw scalable sprites that always face the screen, there's really no need to compute the intersection point between arbitrary lines. The `Billboard` type we added is significantly over-specced for its ostensible purpose of drawing sprites.
+
+And that's because it has *another purpose*. We can create a door by placing a `Billboard` in the gap between two walls. But unlike sprites, this billboard will be aligned with the map grid rather than with the view plane.
+
+We'll need a texture for the door. Doors have two sides, but we'll just let the reverse side be a mirror of the front (this falls naturally out of the way billboard texturing works, so we don't need to do anything extra). We'll want the lighting of the doors to match the environment though, so we'll actually add *two* textures - a light and dark variant - for the vertical and horizontal door orientations.
+
+Here are the textures I've used. You will find them in the [project assets folder](https://github.com/nicklockwood/RetroRampage/tree/Part10/Source/Rampage/Assets.xcassets/), but feel free to make your own. The door graphic can be anything you like - it can even include transparency.
+
+
+
+Add the door textures to XCAssets, then add the new cases to `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case door, door2
+ ...
+}
+```
+
+Next up, we need to add a type to represent the door. Create a new file called `Door.swift` in the Engine module, with the following contents:
+
+```swift
+public struct Door {
+ public let position: Vector
+ public let direction: Vector
+ public let texture: Texture
+
+ public init(position: Vector, direction: Vector, texture: Texture) {
+ self.position = position
+ self.direction = direction
+ self.texture = texture
+ }
+}
+```
+
+The structure of the door is pretty similar to `Billboard` - not a coincidence since that's how we intend to present the door to the renderer. Let's go ahead and add a computed property for the door's billboard representation. Add the following code to the bottom of the file:
+
+```swift
+public extension Door {
+ var billboard: Billboard {
+ return Billboard(
+ start: position - direction * 0.5,
+ direction: direction,
+ length: 1,
+ texture: texture
+ )
+ }
+}
+```
+
+We'll need to place the doors in the map. The `Tilemap` has two categories of data in it, *tiles* and *things*, and it's not immediately obvious which of these a door would fall under.
+
+On the one hand, a door is part of the level geometry. It's not sentient, it can't move freely around the level, it can't occupy the same space as a wall. So it makes sense for it to be a type of tile, right?
+
+But on the other hand, doors are not static. At any given time a door can be open or closed (or somewhere in between), so it requires runtime state. It's animated, it can interact with the player, it can't occupy the same starting tile as a monster or other *thing*.
+
+I don't think there's an obvious right answer here, but in the end I opted to make doors a *thing*, for the following reasons:
+
+* It means we can specify the floor/ceiling tile for the doorway independently of the door itself.
+* It requires fewer code changes to the `Renderer` and `World` initialization code.
+
+In `Thing.swift`, go ahead and add a `door` case to the enum:
+
+```swift
+public enum Thing: Int, Decodable {
+ case nothing
+ case player
+ case monster
+ case door
+}
+```
+
+Then in `Map.json`, in the main project, add two doors to the `things` array (doors are represented by a `3`, matching their index in the `Thing` enum):
+
+```swift
+{
+ "width": 8,
+ "tiles": [
+ ...
+ ],
+ "things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+}
+```
+
+Back in Engine, in `World.swift` update the `World` struct to include a new `doors` property as follows:
+
+```swift
+public struct World {
+ public let map: Tilemap
+ public private(set) var doors: [Door]
+ public private(set) var monsters: [Monster]
+ public private(set) var player: Player!
+ public private(set) var effects: [Effect]
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.doors = []
+ self.monsters = []
+ self.effects = []
+ reset()
+ }
+}
+```
+
+Still in `World.swift`, add the following to the top of the `reset()` method, just below the `self.monsters = []` line:
+
+```swift
+self.doors = []
+```
+
+We'll also need to add a case to the switch statement to handle the new `door` type:
+
+```swift
+case .door:
+ doors.append(Door(
+ position: position,
+ direction: Vector(x: 0, y: 1),
+ texture: .door
+ ))
+```
+
+This adds a vertical door, but what do we do about horizontal doors? We could add a second door type to `Thing` but that seems rather inelegant. It's also not great that `World` needs to know quite so much about the door internals here.
+
+In `Door.swift`, replace the `init` method with the following:
+
+```swift
+public init(position: Vector, isVertical: Bool) {
+ self.position = position
+ if isVertical {
+ self.direction = Vector(x: 0, y: 1)
+ self.texture = .door
+ } else {
+ self.direction = Vector(x: 1, y: 0)
+ self.texture = .door2
+ }
+}
+```
+
+That's better - the `Door` initializer now only allows valid door configurations, and the caller can easily specify if a vertical or horizontal door is needed. So how do we work out which type we need?
+
+The design of the doors means that they have to be straddled on either side by two wall tiles. They can't have a wall segment in front and behind them, otherwise it wouldn't be possible to walk through.
+
+On that basis, we can infer that if there is a wall tile above and below a door then it must be vertical, and if there isn't, it must be horizontal. Back in `World.reset()`, replace the `case .door:` block with:
+
+```swift
+case .door:
+ let isVertical = map[x, y - 1].isWall && map[x, y + 1].isWall
+ doors.append(Door(position: position, isVertical: isVertical))
+```
+
+You may have noticed that this code will crash if the door is placed on a border tile - we could add bounds checks, but the renderer will freeze up anyway if the border of the map isn't a wall, so we can consider this a programmer error.
+
+But since we disabled safety checks in [Part 9](Part9.md), it's possible an out-of-bounds error *won't* actually crash here but will return a garbage value and probably crash at some later point. It would be good code hygiene to add a `precondition()` call to ensure we crash early with a meaningful error.
+
+Add the following line just before the `let isVertical ...`:
+
+```swift
+precondition(y > 0 && y < map.height, "Door cannot be placed on map edge")
+```
+
+### Drawing Room
+
+That's all the code we need in order to place the doors in the world. Now we just need to actually draw them, and turn our corridors into rooms.
+
+This part is actually fairly trivial, since we already did all the setup work. In the `World.sprites` computed property, replace the line:
+
+```swift
+return monsters.map { $0.billboard(for: ray) }
+```
+
+with:
+
+```swift
+return monsters.map { $0.billboard(for: ray) } + doors.map { $0.billboard }
+```
+
+And that's it! Run the game and there's the door in the corner[[2]](#footnote2).
+
+![The door, visible in the corner of the room](Images/DoorInCorner.png)
+
+If you walk over to admire the door up close, you might be surprised by this fellow making a sudden appearance!
+
+![Monster walking through the door](Images/MonsterSurprise.png)
+
+The problem is, although we've placed the door in the level and drawn it, it has no physical presence. None of the collision detection mechanisms are aware of it, so monsters can walk right through it - as can the player.
+
+### You Shall Not Pass
+
+To prevent the monster and player from passing through the door, we need to extend the collision logic. All of our collisions so far are based on rectangle intersections, and fortunately that will work for doors as well. In `Door.swift`, add a computed `rect` property, as follows:
+
+```swift
+public extension Door {
+ var rect: Rect {
+ let position = self.position - direction * 0.5
+ return Rect(min: position, max: position + direction)
+ }
+
+ ...
+}
+```
+
+This `Rect` actually has zero area - it's really just a line - but it will work perfectly well for collision calculations. The original Wolfenstein 3D actually modeled the collision area for doors as a full tile-sized square, so you couldn't step into the alcove in front of them, but on a mobile device that makes it tricky to line yourself up with the door, as John Carmack described in a [blog post about the original Wolfenstein 3D iPhone port](https://www.eurogamer.net/articles/id-releases-open-source-wolf-3d-iphone):
+
+> In watching the early testers, the biggest issue I saw was people sliding off doors before they opened, and having to maneuver back around to go through. In Wolf, as far as collision detection was concerned, everything was just a 64x64 tile map that was either solid or passable.
+
+> It turned out to be pretty easy to make the door tiles only have a solid central core against the player, so players would slide into the "notch" with the door until it opened. This made a huge improvement in playability.
+
+Collisions between a monster or player and the walls are handled by the `Actor` protocol. In `Actor.swift`, we already have an `intersection(with map:)` method, but we can't simply extend that to handle doors as well because the `Tilemap` only contains the initial state of the doors, and we'll need to vary the collision behavior when the doors are opened or closed.
+
+Instead, add two new methods just below `intersection(with map:)`:
+
+```swift
+func intersection(with door: Door) -> Vector? {
+ return rect.intersection(with: door.rect)
+}
+
+func intersection(with world: World) -> Vector? {
+ if let intersection = intersection(with: world.map) {
+ return intersection
+ }
+ for door in world.doors {
+ if let intersection = intersection(with: door) {
+ return intersection
+ }
+ }
+ return nil
+}
+```
+
+The `intersection(with door:)` method detects the intersection with a single door, and the `intersection(with world:)` checks for intersections with any wall or door in the world.
+
+In `World.update`, in the `// Handle collisions` section, replace the line:
+
+```
+while let intersection = monster.intersection(with: map) {
+```
+
+with:
+
+```
+while let intersection = monster.intersection(with: self) {
+```
+
+Then replace:
+
+```
+while let intersection = player.intersection(with: map) {
+```
+
+with:
+
+```
+while let intersection = player.intersection(with: self) {
+```
+
+Try running the game again and you should find that the monster can no longer walk through the door to get you. If you walk right up to the door though, you'll find that the screen starts flashing red, and you eventually die. What's up with that?
+
+![Being hurt through the door](Images/HurtThroughDoor.png)
+
+### X-Ray Vision
+
+Even though the door is closed, the monster behind it can still see you because we didn't update their AI to take doors into account. As soon as you walk in front of the door they'll walk towards you. They can't walk through the door, but since the door has zero thickness, if you stand right up against it then you're inside their attack radius and they can hurt you.
+
+Your pistol also "sees" the monster using a ray cast, so right now there's nothing to stop you shooting them through the door. Both of these bugs have the same root cause:
+
+The monster detects the player via the `Monster.canSeePlayer()` method, which projects a ray towards the player and checks if it hits a wall before it reaches them. To detect wall collisions, it uses the `Tilemap.hitTest()` method internally, which isn't aware of doors (hence the "ability" to see through them).
+
+The player's pistol finds its target by calling the `World.hitTest()` method, which projects a ray and returns the index of the first monster it hits. This method also calls `Tilemap.hitTest()`, and so also ignores doors.
+
+In retrospect, `World.hitTest()` was poorly named - partly because it deviates from the pattern of other `hitTest()` methods (which return a coordinate rather than an object index), and partly because it implies generality, whereas actually this method is specifically checking for *monster* hits - we can't use it to detect if the ray hits a wall, or a door, or the player.
+
+In `World.swift`, rename the `hitTest()` method to `pickMonster()`, as follows:
+
+```swift
+func pickMonster(_ ray: Ray) -> Int? {
+```
+
+Then, in `Player.update()`, change the line:
+
+```swift
+if let index = world.hitTest(ray) {
+```
+
+to:
+
+```swift
+if let index = world.pickMonster(ray) {
+```
+
+That frees up the name `World.hitTest()` to use for its more natural purpose. Back in `World.swift`, add a new `hitTest()` method above the renamed `pickMonster()`:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector {
+ var wallHit = map.hitTest(ray)
+
+ return wallHit
+}
+```
+
+Currently, this just wraps the `Tilemap.hitTest()` method, and will only detect wall hits, but let's expand it to handle doors as well. In `Door.swift` add the following method:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector? {
+ return billboard.hitTest(ray)
+}
+```
+
+This may seem a little redundant, since the `Door.billboard` property is public and we could `hitTest()` it directly, but it gives us the flexibility to change the behavior. For example, we might later want doors with holes that can be seen and fired through, or we could add the ability to destroy doors, etc.
+
+Back in `World.hitTest()`, add the following lines before the return statement:
+
+```swift
+var distance = (wallHit - ray.origin).length
+for door in doors {
+ guard let hit = door.hitTest(ray) else {
+ continue
+ }
+ let hitDistance = (hit - ray.origin).length
+ guard hitDistance < distance else {
+ continue
+ }
+ wallHit = hit
+ distance = hitDistance
+}
+```
+
+Here we loop through the doors in the level and check if the ray hits them. If so, the distance of the hit is compared to the original wall hit distance, and the shortest distance is taken as the new threshold.
+
+Now we have a method to detect ray collisions with both doors and walls, we can refactor the methods that use `Tilemap.hitTest()` to use `World.hitTest()` instead. In `World.pickMonster()`, replace the line:
+
+```swift
+let wallHit = map.hitTest(ray)
+```
+
+with just:
+
+```swift
+let wallHit = hitTest(ray)
+```
+
+Then, in `Monster.canSeePlayer()`, replace:
+
+```swift
+let wallHit = world.map.hitTest(ray)
+```
+
+with just:
+
+```swift
+let wallHit = world.hitTest(ray)
+```
+
+With that done, we finally have a door that is impervious both to movement and vision. We have created *a wall*. Um... I guess we should probably work out how to make a door that actually opens?
+
+### Open Sesame
+
+To allow doors to be opened and closed, we will need to give them some internal state. By now you should be familiar with the concept of creating a state machine by switching over an enum - we'll use the same technique again here.
+
+In `Door.swift`, add the following enum to the top of the file:
+
+```swift
+public enum DoorState {
+ case closed
+ case opening
+ case open
+ case closing
+}
+```
+
+The door will start out in the closed state, and like all animated objects in the game it will need to keep track of time, so go ahead and add the following properties to the `Door` struct:
+
+```swift
+public var state: DoorState = .closed
+public var time: Double = 0
+```
+
+Since doors are interactive, they will need their own `update()` method. Add the following method to the extension block:
+
+```swift
+mutating func update(in world: inout World) {
+ switch state {
+ case .closed:
+
+ case .opening:
+
+ case .open:
+
+ case .closing:
+
+ }
+}
+```
+
+This is just a shell for now - we'll work out the implementation in a second. But first, go to `World.update` and add this block of code after the `// Update monsters` section:
+
+```swift
+// Update doors
+for i in 0 ..< doors.count {
+ var door = doors[i]
+ door.time += timeStep
+ door.update(in: &self)
+ doors[i] = door
+}
+```
+
+This should be a fairly familiar pattern - for each door we make a copy, increment the time, and then call `update()`, before replacing the original door instance in the array with the modified one.
+
+Door opening will be triggered by the player, but having an explicit "open door" button isn't great UX for a mobile game - as Carmack explains in the [aforementioned blog post](https://www.eurogamer.net/articles/id-releases-open-source-wolf-3d-iphone):
+
+> I started out with an explicit "open door" button like the original game, but I quickly decided to just make that automatic. Wolf and Doom had explicit "use" buttons, but we did away with them on Quake with contact or proximity activation on everything. Modern games have generally brought explicit activation back by situationally overriding attack, but hunting for push walls in Wolf by shooting every tile wouldn't work out. There were some combat tactics involving explicitly shutting doors that are gone with automatic-use, and some secret push walls are trivially found when you pick up an item in front of them now, but this was definitely the right decision.
+
+If and when we port the game to other platforms, we might want to consider adding an explicit door control, but for now we'll make it automatic. That means we don't need to extend the `Input` or `Player` types at all - we can implement the door opening logic entirely within `Door.update()`. Replace the empty `case .closed:` with the following:
+
+```swift
+case .closed:
+ if world.player.intersection(with: self) != nil {
+ state = .opening
+ time = 0
+ }
+```
+
+So now if the player is touching the door when it's in the `closed` state, it will transition to the `opening` state automatically. What's next?
+
+The transition from `opening` to `open` is time-based. We'll need to add a `duration` constant to control how long that process should take, so go ahead and add the following property at the top of the `Door` struct:
+
+```swift
+public let duration: Double = 0.5
+```
+
+Then in `update()`, replace the empty `case .opening:` with:
+
+```swift
+case .opening:
+ if time >= duration {
+ state = .open
+ time = 0
+ }
+```
+
+We'll use the same duration for the closing animation too, so replace `case .closing:` with:
+
+```swift
+case .closing:
+ if time >= duration {
+ state = .closed
+ time = 0
+ }
+```
+
+That just leaves the `open` case. We need some logic to transition from the open state to the closing sequence. The opening sequence is triggered when the player touches the door, but there's no equivalent action for closing the door again - so how should this work?
+
+We could do something based on distance - if the player moves outside a certain radius then the door closes automatically - but a simpler option is to have the door close after a fixed delay. Add a second constant called `closeDelay` to `Door`, just below the `duration` property we added earlier:
+
+```swift
+public let closeDelay: Double = 3
+```
+
+Then in `update()`, replace `case .open:` with:
+
+```swift
+case .open:
+ if time >= closeDelay {
+ state = .closing
+ time = 0
+ }
+```
+
+That takes care of the door's state machine - now we just need to implement the animation.
+
+In keeping with the original Wolfenstein 3D, our doors will slide open sideways[[3]](#footnote3). In earlier chapters we implemented animations based on image sequences and opacity - this time we are going to animate a position, but really the principle is the same.
+
+The door already has a `position` property. We could manipulate this directly, but that would make it difficult to track where we are at in the animation, and could also lead to a gradual buildup of floating point rounding errors over time. Instead, we'll add a separate `offset` property which we can combine with the `position` to get the current door placement.
+
+Add the following computed property to the extension block in `Door.swift`, just above the `billboard` property:
+
+```swift
+var offset: Double {
+ let t = min(1, time / duration)
+ switch state {
+ case .closed:
+ return 0
+ case .opening:
+ return Easing.easeInEaseOut(t)
+ case .open:
+ return 1
+ case .closing:
+ return 1 - Easing.easeInEaseOut(t)
+ }
+}
+```
+
+This `offset` property works in a very similar way to the `progress` property we added to the `Effect` type in [Part 7](Part7.md). Like the `fizzleOut` effect, we've used the `easeInEaseOut()` easing function so that the door will gently accelerate and decelerate as it opens and closes.
+
+In the `billboard` property code, replace the line:
+
+```swift
+start: position - direction * 0.5,
+```
+
+with:
+
+```swift
+start: position + direction * (offset - 0.5),
+```
+
+So now the `Billboard` for the door will slide along its axis in proportion to the current offset. That will take care of rendering, but we also need to handle player and monster collisions, which are based on the door's `rect` property.
+
+There are a couple of ways we could handle this - for example, we could update `Player.intersection(with door:)` to return nil if the door is in the `open` or `opening state`. That's actually how the original Wolfenstein engine worked.
+
+But the way we have constructed the door means it's really not much trouble to implement collisions properly. In the computed `Door.rect` property, replace the following line:
+
+```swift
+let position = self.position - direction * 0.5
+```
+
+with:
+
+```swift
+let position = self.position + direction * (offset - 0.5)
+```
+
+With this in place, the position of the collision rect for the door will match up exactly with the billboard position, allowing the player to pass through the gap when the door is open. Try running the game now and you should find that the door is fully functional.
+
+![Functioning door](Images/FunctioningDoor.png)
+
+### Door Jam
+
+The decision to implement the door collisions faithfully does introduce a slight complication. After opening the door (and dispatching any prowling monsters), try standing in the doorway and waiting for it to close on you.
+
+Assuming you've lined yourself up correctly, you'll find that the game freezes when the door hits you. Not good!
+
+If you recall, in `World.update()` we wrote some collision logic for the player that looked like this:
+
+```swift
+while let intersection = player.intersection(with: map) {
+ player.position -= intersection
+}
+```
+
+This well-intentioned loop was supposed to ensure that the player never got stuck inside walls, but we failed to account for the scenario of a wall (or in this case, a door) getting stuck *inside the player*. As the door closes on the player, the collision handling tries to move the player out of the way, but there is nowhere to move to, as the gap between the door and wall is now too narrow for the player to fit.
+
+The solution is to make the collision handling a bit more tolerant. But since this code is duplicated between the player and monsters, we'll also take this opportunity to extract it. In `Actor.swift`, add the following method to the extension block:
+
+```swift
+mutating func avoidWalls(in world: World) {
+ var attempts = 10
+ while attempts > 0, let intersection = intersection(with: world) {
+ position -= intersection
+ attempts -= 1
+ }
+}
+```
+
+Like the existing collision loops, this repeatedly tries to move the actor away from any scenery they are intersecting. Unlike those loops, however, it attempts this only 10 times before giving up, on the basis that it's better to allow some object interpenetration than to freeze the game.
+
+Back in `World.update()`, in the `// Handle collisions` block, replace the lines:
+
+```swift
+while let intersection = player.intersection(with: map) {
+ player.position -= intersection
+}
+```
+
+with:
+
+```swift
+player.avoidWalls(in: self)
+```
+
+Then also replace:
+
+```swift
+while let intersection = monster.intersection(with: map) {
+ monster.position -= intersection
+}
+```
+
+with:
+
+```swift
+monster.avoidWalls(in: self)
+```
+
+Try running the game again and you should find that it's no longer possible to get stuck in the door. If you position yourself so it closes on you, you just end up getting nudged out again as the `avoidWalls()` method gives up and the door keeps moving.
+
+### Doorjamb
+
+It looks a little odd having the doors slide out of a featureless wall. Wolfenstein had special [doorjamb](https://en.wikipedia.org/wiki/Jamb) textures that were applied to the neighboring wall tiles.
+
+Go ahead and add a pair of doorjamb textures to XCAssets.
+
+
+
+Then add the new cases to `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case doorjamb, doorjamb2
+ ...
+}
+```
+
+We could implement the doorjamb as a special kind of wall tile, but our texturing scheme currently only allows us to vary the texture for vertical or horizontal walls, which would lead to an awkward limitation on how doors can be placed to avoid the jamb texture appearing on walls we didn't intend it to.
+
+Instead of trying to force this into our existing wall model, let's extend the renderer to support doorjambs as a special case. Whenever a wall is adjacent to a door, we'll use the jamb texture instead of whatever texture is specified by the tile.
+
+To check if a given tile contains a door, we could either use the `things` array in the `Tilemap` or the `doors` array in `World`. The `things` array is already ordered for lookup by tile coordinate, whereas `doors` would require a linear search to find the element with the matching position, so `things` seems like the better choice.
+
+In `World.swift`, add the following method:
+
+```swift
+func isDoor(at x: Int, _ y: Int) -> Bool {
+ return map.things[y * map.width + x] == .door
+}
+```
+
+Now let's take a look at the wall-rendering code. In `Renderer.draw()`, in the `// Draw wall` section, we look up the current tile using:
+
+```swift
+let tile = world.map.tile(at: end, from: ray.direction)
+```
+
+The `TileMap.tile(at:from:)` method uses the ray intersection point and direction to compute the tile that's been hit. This method returns the `Tile` directly, but in order to determine if the neighboring tile contains a door, it would be more helpful if we had the tile *coordinates* instead.
+
+In `Tilemap.swift`, find the following method:
+
+```swift
+func tile(at position: Vector, from direction: Vector) -> Tile {
+```
+
+and change its signature to:
+
+```swift
+func tileCoords(at position: Vector, from direction: Vector) -> (x: Int, y: Int) {
+```
+
+Then, in the same method, replace the last line:
+
+```swift
+return self[Int(position.x) + offsetX, Int(position.y) + offsetY]
+```
+
+with:
+
+```swift
+return (x: Int(position.x) + offsetX, y: Int(position.y) + offsetY)
+```
+
+Next, to avoid breaking all the call sites that were using `tile(at:from:)`, add a new method with the same signature just below the `tileCoords()` method:
+
+```swift
+func tile(at position: Vector, from direction: Vector) -> Tile {
+ let (x, y) = tileCoords(at: position, from: direction)
+ return self[x, y]
+}
+```
+
+Back in the `// Draw wall` section of `Renderer.draw()`, replace the line:
+
+```swift
+let tile = world.map.tile(at: end, from: ray.direction)
+```
+
+with:
+
+```swift
+let (tileX, tileY) = world.map.tileCoords(at: end, from: ray.direction)
+let tile = world.map[tileX, tileY]
+```
+
+The new code is functionally equivalent, but now that we have the actual tile coordinates available, we can derive the neighboring tile and determine if it contains a door.
+
+The code immediately after those lines looks like this:
+
+```swift
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[tile.textures[0]]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[tile.textures[1]]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+This code checks if the wall is horizontal or vertical, then applies the appropriate wall texture. It is here that we need to add the check for a neighboring door and (if applicable) override the texture.
+
+For a vertical wall, the tile we need to check will be either to the left or right of `tileX`, depending on the direction of the ray. Replace the line:
+
+```swift
+wallTexture = textures[tile.textures[0]]
+```
+
+with:
+
+```swift
+let neighborX = tileX + (ray.direction.x > 0 ? -1 : 1)
+let isDoor = world.isDoor(at: neighborX, tileY)
+wallTexture = textures[isDoor ? .doorjamb : tile.textures[0]]
+```
+
+That code uses the ray direction to determine which neighbor to check, then if the neighbor contains a door, swaps the wall texture for the doorjamb. Now we'll do the same for the `else` (horizontal) branch. Replace:
+
+```swift
+wallTexture = textures[tile.textures[1]]
+```
+
+with:
+
+```swift
+let neighborY = tileY + (ray.direction.y > 0 ? -1 : 1)
+let isDoor = world.isDoor(at: tileX, neighborY)
+wallTexture = textures[isDoor ? .doorjamb2 : tile.textures[1]]
+```
+
+Run the game again and you should see the doorjamb in position by the door.
+
+![Doorjamb texture on wall next to door](Images/Doorjamb.png)
+
+And that's it for Part 10! In this part we:
+
+* Added sliding doors by repurposing the billboard renderer we used for sprites
+* Implemented doorjamb textures without awkward map layout restrictions
+
+In [Part 11](Part11.md) we'll add another iconic gameplay feature from Wolfenstein 3D.
+
+### Reader Exercises
+
+1. Try increasing the thickness of the door collision rect so that you don't have to actually touch it to open it.
+
+2. The door closing automatically when you are trying to walk through can be a nuisance. Could you modify the door logic so that it will only close if the doorway tile is unoccupied?
+
+3. Create a new type of door that swings open from one corner instead of sliding (you can cheat and include Foundation if you need the trigonometry functions, but there's bonus points if you can figure out how to do it without those).
+
+
+
+[[1]](#reference1) Sliding doors are a rare example of something the primitive Wolfenstein engine could do that later, more sophisticated engines like Doom could not replicate. In Doom's sector-based world, sectors can only move up and down, not sideways. Doors in Doom were effectively mini elevators - with a small section of floor moving up to meet the ceiling, or vice-versa.
+
+[[2]](#reference2) Before examining the door, you might want to kill the zombie first so you can appreciate it properly without being mauled.
+
+[[3]](#reference3) Note that we don't *have* to implement them this way. Although vertical offsets aren't current supported for billboards, we could create a door opening animation using a sequence of image frames and then animate it using the same technique we use for sprites.
diff --git a/Tutorial/Part11.md b/Tutorial/Part11.md
new file mode 100644
index 0000000..b789887
--- /dev/null
+++ b/Tutorial/Part11.md
@@ -0,0 +1,790 @@
+## Part 11: Secrets
+
+In [Part 10](Part10.md) we added sliding doors. The code for Part 10 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part10.zip).
+
+**Note:** There was a bug introduced in [Part 9](Part9.md) that caused the floor and ceiling to be swapped. While it doesn't directly affect any of the code in this chapter, if you have been coding along with the tutorials as they were released then you might want to fix it in your own project before proceeding. For details, refer to the [CHANGELOG](../CHANGELOG.md).
+
+### Pushing the Boundaries
+
+Beyond the innovative[[1]](#footnote1) run-and-gun gameplay, one of the most iconic features of the original Wolfenstein 3D were the secret rooms, filled with treasure and power-ups, hidden behind sliding push-walls[[2]](#footnote2).
+
+The mechanism for push-walls is similar to the sliding doors. Although they *look* like ordinary walls, push-walls aren't aligned to the map grid, and can't be drawn using the normal tile-based wall-renderer. Instead, we'll construct the push wall using four individual billboards, arranged in a square.
+
+Add a new file called `Pushwall.swift` to the Engine module, with the following contents:
+
+```swift
+public struct Pushwall {
+ public var position: Vector
+ public let tile: Tile
+
+ public init(position: Vector, tile: Tile) {
+ self.position = position
+ self.tile = tile
+ }
+}
+
+public extension Pushwall {
+ var rect: Rect {
+ return Rect(
+ min: position - Vector(x: 0.5, y: 0.5),
+ max: position + Vector(x: 0.5, y: 0.5)
+ )
+ }
+
+ var billboards: [Billboard] {
+ let topLeft = rect.min, bottomRight = rect.max
+ let topRight = Vector(x: bottomRight.x, y: topLeft.y)
+ let bottomLeft = Vector(x: topLeft.x, y: bottomRight.y)
+ let textures = tile.textures
+ return [
+ Billboard(start: topLeft, direction: Vector(x: 0, y: 1), length: 1, texture: textures[0]),
+ Billboard(start: topRight, direction: Vector(x: -1, y: 0), length: 1, texture: textures[1]),
+ Billboard(start: bottomRight, direction: Vector(x: 0, y: -1), length: 1, texture: textures[0]),
+ Billboard(start: bottomLeft, direction: Vector(x: 1, y: 0), length: 1, texture: textures[1])
+ ]
+ }
+}
+```
+
+This is a basic model for the push-wall that has a `position`, `tile` type, `rect` (for collisions) and a `billboards` property for the faces of the wall.
+
+Like doors, push-walls will be `Thing`s, allowing them to move around within the map without needing to alter the static `tiles` layout at runtime. Open `Thing.swift` and add a `pushwall` case to the end of the enum:
+
+```swift
+public enum Thing: Int, Decodable {
+ ...
+ case pushwall
+}
+```
+
+In `World.swift`, add a `pushwalls` property to the `World` struct:
+
+```swift
+public struct World {
+ public let map: Tilemap
+ public private(set) var doors: [Door]
+ public private(set) var pushwalls: [Pushwall]
+ ...
+}
+```
+
+Then update `World.init()` to set `pushwalls` to an empty array:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.doors = []
+ self.pushwalls = []
+ ...
+}
+```
+
+Update `World.reset()` to reset the `pushwalls` array:
+
+```swift
+mutating func reset() {
+ self.monsters = []
+ self.doors = []
+ self.pushwalls = []
+ ...
+}
+```
+
+And, still in the `reset()` method, add a case to the switch statement:
+
+```swift
+case .pushwall:
+ precondition(!map[x, y].isWall, "Pushwall must be placed on a floor tile")
+ pushwalls.append(Pushwall(position: position, tile: .wall))
+```
+
+Note the precondition - because `Pushwall` acts like a wall, it can't actually occupy the same space as a wall tile in the map. In `Map.json` go ahead and remove an inner wall from the `tiles` array and replace it with a push-wall (index `4`) in the `things` array:
+
+```swift
+"tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 0, 1, 0, 1,
+ 1, 0, 4, 2, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 4, 4, 1,
+ 1, 3, 3, 1, 1, 3, 1, 1
+],
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+]
+```
+
+This creates a secret passage immediately to the left of the player, which we can use to surprise the monster in the second room. Try running the game and looking to your left (you might want to dispatch the monster in front of you first).
+
+![The not-so-secret passage](Images/NotSoSecretPassage.png)
+
+Hmm. The secret passage isn't all that secret really - I guess we forgot to actually draw the push-wall!
+
+Still in `World.swift` find the `sprites` computed property and update it to include the push-wall billboards:
+
+```swift
+var sprites: [Billboard] {
+ let ray = Ray(origin: player.position, direction: player.direction)
+ return monsters.map { $0.billboard(for: ray) } + doors.map { $0.billboard }
+ + pushwalls.flatMap { $0.billboards }
+}
+```
+
+That takes care of the wall drawing, but the monster on the other side of the passage can potentially still see us.
+
+The monster's vision isn't based on the same rendering logic used for the game view - it "sees" by casting a single ray towards the player and checking if it finds anything solid in the way. To ensure that rays cast by the monster will be stopped by push-walls as well as doors, find the following lines in `World.hitTest()`:
+
+```swift
+for door in doors {
+ guard let hit = door.billboard.hitTest(ray) else {
+```
+
+And replace them with:
+
+```swift
+let billboards = doors.map { $0.billboard } + pushwalls.flatMap { $0.billboards }
+for billboard in billboards {
+ guard let hit = billboard.hitTest(ray) else {
+```
+
+Okay, so now let's try running the game again.
+
+![Secret passage covered by wall](Images/SecretPassageWall.png)
+
+Not much to look at - it's just an ordinary wall. But if you walk forward you'll find you can pass right through it. It's a secret passage alright, but not quite what we intended. Although it now *looks* like a wall, the push-wall has no physical presence.
+
+We want the player (and monsters too), to bounce off the push-wall just as they would any other wall. Collisions between player and scenery are handled by the `Actor.avoidWalls()` method, which in turn calls `Actor.intersection(with world:)`. That method currently checks for collisions with walls and doors, so we'll extend it to detect push-walls as well.
+
+In `Actor.swift`, add the following method, just after `intersection(with door:)`:
+
+```swift
+func intersection(with pushwall: Pushwall) -> Vector? {
+ return rect.intersection(with: pushwall.rect)
+}
+```
+
+Then add the following code to the `intersection(with world:)` method, just before the `return nil` line:
+
+```swift
+for pushwall in world.pushwalls {
+ if let intersection = intersection(with: pushwall) {
+ return intersection
+ }
+}
+```
+
+If you run the game again you should find you can no longer walk through the wall. We have successfully invented a new (and considerably less efficient) way to draw walls. Good job everyone!
+
+### Right of Passage
+
+To convert our stationary wall back into a passageway, we need to give it some interactivity. You should be pretty familiar with this process by now - Things that move need a `velocity`; Things that interact need an `update()` method.
+
+In `Pushwall.swift`, add a `speed` constant and `velocity` property:
+
+```swift
+public struct Pushwall {
+ public let speed: Double = 0.25
+ public var position: Vector
+ public var velocity: Vector
+ public let tile: Tile
+
+ public init(position: Vector, tile: Tile) {
+ self.position = position
+ self.velocity = Vector(x: 0, y: 0)
+ self.tile = tile
+ }
+}
+```
+
+Then, in the extension block, add the following code:
+
+```swift
+public extension Pushwall {
+ ...
+
+ var isMoving: Bool {
+ return velocity.x != 0 || velocity.y != 0
+ }
+
+ mutating func update(in world: inout World) {
+ if isMoving == false, let intersection = world.player.intersection(with: self) {
+ if abs(intersection.x) > abs(intersection.y) {
+ velocity = Vector(x: intersection.x > 0 ? speed : -speed, y: 0)
+ } else {
+ velocity = Vector(x: 0, y: intersection.y > 0 ? speed : -speed)
+ }
+ }
+ }
+}
+```
+
+This logic detects when the player touches the push-wall, and will start it moving at `speed` in whatever direction it was pushed. The reason for the `isMoving` check is that we don't really want the player to be able to interfere with the push-wall again once activated.
+
+Next, go to `World.update()` and add the following code just after the `// Update doors` section:
+
+```swift
+// Update pushwalls
+for i in 0 ..< pushwalls.count {
+ var pushwall = pushwalls[i]
+ pushwall.update(in: &self)
+ pushwall.position += pushwall.velocity * timeStep
+ pushwalls[i] = pushwall
+}
+```
+
+Run the game again and bump up against the wall. You should see it start to slide slowly away.
+
+![Sliding wall](Images/SlidingWall.png)
+
+The problem now is that it never actually stops sliding - it just keeps going until it passes through the far wall and vanishes. We handled collisions between player and push-wall, but forgot about collisions between the push-wall and the rest of the map.
+
+Currently, the logic for detecting collisions with the map is tied to the `Actor` protocol, but `Pushwall` isn't an `Actor`. Could it be, though? It has a `position` and a `rect` and it moves around and bumps into things like other actors. Let's try it!
+
+In `Pushwall.swift`, conform `Pushwall` to the `Actor` protocol, and add a `radius` property:
+
+```swift
+public struct Pushwall: Actor {
+ public let radius: Double = 0.5
+ ...
+}
+```
+
+In the extension below, delete the computed `rect` property (this is provided by `Actor` already) and replace it with the following:
+
+```swift
+var isDead: Bool { return false }
+```
+
+The `Actor` protocol requires an `isDead` property, but walls can't be killed[[3]](#footnote3), so rather than storing a redundant property, we use a computed property that always returns `false`.
+
+Now that `Pushwall` is an `Actor` we can use the existing intersection methods defined on the `Actor` protocol extension to detect collisions. Add the following code to the end of the `Pushwall.update()` method:
+
+```swift
+if let intersection = self.intersection(with: world.map),
+ abs(intersection.x) > 0.001 || abs(intersection.y) > 0.001 {
+ velocity = Vector(x: 0, y: 0)
+ position.x = position.x.rounded(.down) + 0.5
+ position.y = position.y.rounded(.down) + 0.5
+}
+```
+
+This code will stop the push-wall when it hits another wall in the map. The `abs(intersection.x) > 0.001` adds a *fudge factor* to prevent the wall getting stuck when brushing past other walls.
+
+After zeroing the velocity, we also update the position to ensure the push-wall is exactly centered in the tile where it came to rest. That prevents bugs like this, where the player bumps the push-wall at an angle, causing it to get stuck in a neighboring tile (note the single-pixel vertical strip where the push-wall has become misaligned from the tile grid):
+
+![Push-wall misaligned from the tile grid](Images/MisalignedPushwall.png)
+
+There's another potential bug here though - `self.intersection(with: world.map)` only checks for collisions with the wall tiles in the map itself, not with doors or other push-walls.
+
+What happens if we replace `self.intersection(with: world.map)` with `self.intersection(with: world)`? Try it, then run the game again.
+
+If you push the wall now, it doesn't move - so what's the problem?
+
+Well, `Actor.intersection(with world:)` checks for intersections between the current actor's `Rect` and all other scenery including all push-walls. Since in this case the actor *is* a push-wall, it's detecting a collision with itself.
+
+To fix that, first open `Vector.swift` and add `Equatable` conformance to the `Vector` type, as follows:
+
+```swift
+public struct Vector: Equatable {
+```
+
+Then open `Actor.swift` and replace the following line in the `intersection(with world:)` method:
+
+```swift
+for pushwall in world.pushwalls {
+```
+
+with:
+
+```swift
+for pushwall in world.pushwalls where pushwall.position != position {
+```
+
+With that change, the push-wall will no longer detect collisions with itself, and can once again be set in motion.
+
+### Out of Sorts
+
+You may have noticed that at certain angles, we can see a glitch in the rendering of the push-wall where the billboards are displayed in the wrong order, with farther wall faces appearing in front of nearer ones.
+
+![Push-wall rendering glitch](Images/PushwallGlitch.png)
+
+This bug is caused by an optimization we made in `Renderer.draw()` back in [Part 5](Part5.md) when we first added the sprite rendering logic - specifically these lines:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ let spriteDistance = (sprite.start - world.player.position).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+This code gets the list of sprite billboards and sorts them according to the distance from one end of the billboard to the player's position.
+
+This isn't quite the same thing as the distance from the camera plane (which would be the *perpendicular* distance), but it's a good-enough approximation as long as the billboards are all facing the camera - which they were when we wrote this code.
+
+But the billboards we added for the doors and push-walls are aligned relative to the *map*, not the view plane, which means that the distance from the camera varies along the length of billboard, and sorting by the distance to one end of each billboard will not always produce the correct rendering order at every point on the screen.
+
+The push-wall is a cube constructed from four square billboards[[4]](#footnote4). Due to the problem with rendering order, at certain angles the distance from the player to the end of a rearward-facing billboard may be less than the distance to the end of one that's in front of it, resulting in the rear billboard being drawn on top.
+
+
+
+To fix this properly, we should modify the renderer to sort the billboards by the intersection distance for each ray. But that's a complex change, with potential performance drawbacks. There's a simpler solution we can apply to solve the immediate problem *and* improve performance as a bonus, namely [back-face culling](https://en.wikipedia.org/wiki/Back-face_culling).
+
+When you construct a 3D shape from polygons (such as triangles or quads), each polygon on the surface of the object has a face that points outwards from the shape, and a face that points inwards. Assuming the shape is solid and has no holes, the inward-pointing face can never be seen, since it will always be obscured by an outward-pointing face. These inward-pointing faces are known as *back-faces*.
+
+The principle of back-face culling is that since we can't see those faces, we don't need to draw them. Any polygon with it's back-face towards the camera can be discarded. This is primarily a performance optimization, but it also helps to reduce drawing-order glitches in engines like ours that lack a [Z-buffer](https://en.wikipedia.org/wiki/Z-buffering) and rely on the [Painter's Algorithm](https://en.wikipedia.org/wiki/Painter%27s_algorithm).
+
+So how do we work out which are the backward-facing billboards? We can do this by casting a ray from the player to each billboard (as we do already to measure the distance), and comparing it with the [face normal](https://en.wikipedia.org/wiki/Normal_(geometry)) using the [vector dot product](https://en.wikipedia.org/wiki/Dot_product#Geometric_definition).
+
+The dot product of two vectors is proportional to the cosine of the angle between them. If the angle is concave (less than 90 degrees) the cosine will be positive, if the angle is convex (greater than 90 degrees) it will be negative. Using the dot product, we can determine if a given side of the push-wall is facing towards or away from the player's viewpoint.
+
+
+
+In `Vector.swift`, add the following method to the extension block, just below the computed `length` property[[5]](#footnote5):
+
+```swift
+func dot(_ rhs: Vector) -> Double {
+ return x * rhs.x + y * rhs.y
+}
+```
+
+In `Pushwall.swift` replace the line:
+
+```swift
+var billboards: [Billboard] {
+```
+
+with:
+
+```swift
+func billboards(facing viewpoint: Vector) -> [Billboard] {
+```
+
+Then update the return statement of the function as follows:
+
+```swift
+return [
+ Billboard(start: topLeft, direction: Vector(x: 0, y: 1), length: 1, texture: textures[0]),
+ Billboard(start: topRight, direction: Vector(x: -1, y: 0), length: 1, texture: textures[1]),
+ Billboard(start: bottomRight, direction: Vector(x: 0, y: -1), length: 1, texture: textures[0]),
+ Billboard(start: bottomLeft, direction: Vector(x: 1, y: 0), length: 1, texture: textures[1])
+].filter { billboard in
+ let ray = billboard.start - viewpoint
+ let faceNormal = billboard.direction.orthogonal
+ return ray.dot(faceNormal) < 0
+}
+```
+
+This computes the dot product between the face normal of each billboard and a ray from the viewpoint, then filters out the back-facing billboards. All that's left is to pass in the player's position as the viewpoint.
+
+In `World.swift`, in the computed `sprites` property, replace the line:
+
+```swift
++ pushwalls.flatMap { $0.billboards }
+```
+
+with:
+
+```swift
++ pushwalls.flatMap { $0.billboards(facing: player.position) }
+```
+
+Then in `hitTest()`, replace:
+
+```swift
+let billboards = doors.map { $0.billboard } + pushwalls.flatMap { $0.billboards }
+```
+
+with:
+
+```swift
+let billboards = doors.map { $0.billboard } +
+ pushwalls.flatMap { $0.billboards(facing: ray.origin) }
+```
+
+Run the game again and you should see that the rendering glitches on the push-wall have disappeared. There's just one slightly entertaining bug left to resolve.
+
+### Your First Crush
+
+If you push the wall and then very quickly run through the door you can get ahead of the sliding wall and put yourself in a position where it will crush you against the outer wall.
+
+The fact that you can do this isn't in itself a bug - the problem is that there isn't actually a crushing mechanic in the game. The player can't be compressed, and the sliding wall isn't affected by collisions with the player when it's already moving, so it will simply go on pushing the player until something breaks.
+
+What breaks first depends on your precise positioning - you may end up inside the push-wall, or the push-wall may bounce off you and head in another direction, etc. But if you try to walk towards the outer wall while being crushed, things can get a bit... trippy[[6]](#footnote6).
+
+![The Upside Down](Images/TheUpsideDown.png)
+
+This is what the world looks like from outside the wall. An infinite plane of inside-out geometry I like to call *[The Upside Down](https://strangerthings.fandom.com/wiki/The_Upside_Down)*.
+
+This bug is almost too fun to fix, but fix it we must.
+
+The solution is pretty simple. If the player somehow manages to get outside the map, *we'll kill them*. Monsters can also get trapped behind a push-wall and end up outside the map, so let's solve this for any `Actor`, not just the player.
+
+In `Actor.swift` add the following method to the extension block:
+
+```swift
+func isStuck(in world: World) -> Bool {
+
+}
+```
+
+There are various ways in which an actor can potentially get stuck. The one that we've just encountered is when they end up outside the map, so let's tackle that first. Add the following code to the `isStuck()` method:
+
+```swift
+// If outside map
+if position.x < 1 || position.x > world.map.size.x - 1 ||
+ position.y < 1 || position.y > world.map.size.y - 1 {
+ return true
+}
+```
+
+Although we haven't seen it happen yet, another possible scenario is that an actor ends up inside a wall tile. While we're here, let's handle that as well. Add the following code to the method:
+
+```swift
+// If stuck in a wall
+if world.map[Int(position.x), Int(position.y)].isWall {
+ return true
+}
+```
+
+Finally, an actor could end up inside a push-wall. We'd better check for that too. Add the following code to complete the method:
+
+```swift
+// If stuck in a pushwall
+return world.pushwalls.contains(where: {
+ abs(position.x - $0.position.x) < 0.6 && abs(position.y - $0.position.y) < 0.6
+})
+```
+
+Now we have the means to *detect* a stuck actor, we need to respond to it. In `World.swift`, add the following code at the end of the `update()` method, just after the `// Handle collisions` section:
+
+```swift
+// Check for stuck actors
+if player.isStuck(in: self) {
+ hurtPlayer(1)
+}
+for i in 0 ..< monsters.count where monsters[i].isStuck(in: self) {
+ hurtMonster(at: i, damage: 1)
+}
+```
+
+This logic detects if a player or monster is stuck and quickly kills them.
+
+You might wonder why we only do one unit of damage instead of 100 or 1000? This just adds a bit of fault tolerance. If the player is stuck for just one frame due to a transient glitch then this probably won't be fatal, but if they are trapped permanently then their health will drain to zero within seconds.
+
+We added this death-trap feature to fix a bug, but now that we have it, we might as well have some fun with it. Open `Map.json` and edit the `things` array to move the monster in the second room directly behind the push-wall:
+
+```swift
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+]
+```
+
+Now, when you push the wall, it will crush the monster!
+
+![Monster crushed by push-wall](Images/MonsterCrush.png)
+
+Unfortunately, this reveals another glitch. Once dead, the monster's corpse pops out from behind the push-wall due to the `avoidWalls()` logic. That's a bit weird, but I think we can live with it. The bigger problem is this:
+
+![Push-wall drawn in front of monster corpse](Images/DrawingOrderGlitch.png)
+
+This is the drawing order glitch raising its ugly head again. The push-wall billboard is being drawn in front of the monster sprite. Unfortunately this time our back-face culling trick doesn't help because both the wall and sprite are facing forwards - it's just that the relative angle between them breaks the sorting logic.
+
+
+
+It looks like we're going to have to fix this properly after all.
+
+### Disorderly Conduct
+
+As a reminder, the bug occurs because we are sorting all the billboards up-front according to their distance from the player, when really we should be sorting each vertical slice according to its perpendicular distance from the view plane.
+
+In `Renderer.swift`, cut this block of code:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ let spriteDistance = (sprite.start - world.player.position).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+Then paste it again *inside* the for loop, just before the `// Draw sprites` section. Now that it's inside the loop, the sorting logic can use the exact perpendicular distance for that column of pixels, instead of an approximation. Replace the line:
+
+```swift
+let spriteDistance = (sprite.start - world.player.position).length
+```
+
+with:
+
+```swift
+guard let hit = sprite.hitTest(ray) else {
+ continue
+}
+let spriteDistance = (hit - ray.origin).length
+if spriteDistance > wallDistance {
+ continue
+}
+```
+
+Run the game again and you should find that the problem is fixed.
+
+![Push-wall correctly drawn behind monster corpse](Images/DrawingOrderFixed.png)
+
+That wasn't so complicated after all. But we're now repeating the ray intersection calculation twice for each visible sprite, so let's clean that up.
+
+Delete the following lines from the `// Draw sprites` loop, since we're already computing this information in the `// Sort sprites by distance` loop anyway:
+
+```swift
+guard let hit = sprite.hitTest(ray) else {
+ continue
+}
+let spriteDistance = (hit - ray.origin).length
+if spriteDistance > wallDistance {
+ continue
+}
+```
+
+Then replace the line:
+
+```swift
+for (_, sprite) in spritesByDistance {
+```
+
+with:
+
+```swift
+for (hit, spriteDistance, sprite) in spritesByDistance {
+```
+
+The drawing loop requires the hit position as well as the distance, but we aren't currently including that in the `spritesByDistance` tuples, so in the `// Sort sprites by distance` section, change the line:
+
+```swift
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+```
+
+to:
+
+```swift
+var spritesByDistance: [(hit: Vector, distance: Double, sprite: Billboard)] = []
+```
+
+And then, inside the loop, replace:
+
+```swift
+spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+)
+```
+
+with:
+
+```swift
+spritesByDistance.append(
+ (hit: hit, distance: spriteDistance, sprite: sprite)
+)
+```
+
+### Variety Show
+
+In Wolfenstein 3D, the push-walls were typically disguised as lavish paintings, making them more discoverable than just a blank wall.
+
+When we added the logic in `World.reset()` to insert a push-wall into the map, we hard-coded the push-wall's tile-type. This isn't very satisfactory because it means push-walls will all have the same appearance. Ideally, we'd like to be able to use *any* wall texture for our push-wall.
+
+`Thing`s are represented by a single integer index value in the `Map.json` file, so there isn't really an obvious way to attach additional metadata such as the tile texture. One solution would be to create a distinct `Thing` type for every push-wall texture, but that's pretty inelegant, and would result in a lot of duplication in various switch statements.
+
+It would be neat if we could combine the wall-type information from the `tiles` array with the `Thing` type. But if we allow a push-wall to occupy the same tile as a wall, what happens to the wall tile when the push-wall starts moving? We'd need to add some way to modify the tiles so we could remove the original wall and replace it with a push-wall.
+
+So let's do that, I guess?
+
+Currently we access the tile information via a read-only subscript method on `Tilemap`. In order to modify the tiles, we'll need to make that read-write instead. Open `Tilemap.swift` and in the extension block replace the following code:
+
+```swift
+subscript(x: Int, y: Int) -> Tile {
+ return tiles[y * width + x]
+}
+```
+
+with:
+
+```swift
+subscript(x: Int, y: Int) -> Tile {
+ get { return tiles[y * width + x] }
+ set { tiles[y * width + x] = newValue }
+}
+```
+
+This will produce an error because the underlying `tiles` property is immutable, so in the `Tilemap` struct declaration, change the line:
+
+```swift
+let tiles: [Tile]
+```
+
+to:
+
+```swift
+private(set) var tiles: [Tile]
+```
+
+The `Tilemap` is now editable in principle, but the `map` property in `World` is still immutable. So next, in `World.swift`, change the line:
+
+```swift
+public let map: Tilemap
+```
+
+to:
+
+```swift
+public private(set) var map: Tilemap
+```
+
+We can now modify the map at runtime. In `World.reset()`, update the `pushwall` case as follows:
+
+```swift
+case .pushwall:
+ var tile = map[x, y]
+ if tile.isWall {
+ map[x, y] = .floor
+ } else {
+ tile = .wall
+ }
+ pushwalls.append(Pushwall(position: position, tile: tile))
+```
+
+Previously we had to place a push-wall on top of an existing floor tile. With the new logic, if the push-wall is placed on an existing wall tile it will use that wall texture for the push-wall, and replace the wall itself with a floor tile.
+
+Let's try it out. In `Map.json` replace the floor tile under the `push-wall` with a slime wall (tile index `3`):
+
+```swift
+"tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 3, 1, 0, 1,
+ 1, 0, 4, 2, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 4, 4, 1,
+ 1, 3, 3, 1, 1, 3, 1, 1
+],
+```
+
+Run the game again and... it works!
+
+![Slime push-wall](Images/SlimePushwall.png)
+
+### Hard Reset
+
+If the player dies, everything in the world is reset to the initial state defined in the `Tilemap`. The problem is that we've now overwritten some of that state by changing the push-wall tiles to floor tiles.
+
+If we reset the game now, all the push-walls will be converted into regular wall tiles. We need to somehow preserve the original tile textures. We could make a second copy of the tiles that we *don't* modify, but I have a sneakier solution:
+
+We still have the original wall texture information stored in the push-walls, so instead of resetting the `pushwalls` array, what if we keep it? In `World.reset()`, replace the line:
+
+```swift
+self.pushwalls = []
+```
+
+with:
+
+```swift
+var pushwallCount = 0
+```
+
+Then add the following code to the top of `case .pushwall:`:
+
+```swift
+pushwallCount += 1
+if pushwalls.count >= pushwallCount {
+ let tile = pushwalls[pushwallCount - 1].tile
+ pushwalls[pushwallCount - 1] = Pushwall(position: position, tile: tile)
+ break
+}
+```
+
+### Floor Plan
+
+There's just one last little niggle with the way we specify push-walls. Now that we are using the `tiles` array to define the push-wall texture, we've lost the ability to define the floor underneath it. Not a huge problem, but still unsatisfactory.
+
+We can improve on the current solution of hard-coding the floor tile by finding the nearest floor tile and using that instead. It's not a perfect solution, but at least it means the floor beneath the push-wall should be consistent with the area around it.
+
+For a push-wall to be able to move, at least two of the four adjacent tiles must be floor tiles, so we won't have to search very far. Open `Tilemap.swift` and add the following method just before `hitTest()`:
+
+```swift
+func closestFloorTile(to x: Int, _ y: Int) -> Tile? {
+ for y in max(0, y - 1) ... min(height - 1, y + 1) {
+ for x in max(0, x - 1) ... min(width - 1, x + 1) {
+ let tile = self[x, y]
+ if tile.isWall == false {
+ return tile
+ }
+ }
+ }
+ return nil
+}
+```
+
+Then, back in `World.reset()`, in `case .pushwall:`, replace the line:
+
+```swift
+tile = .wall
+```
+
+with:
+
+```swift
+tile = map.closestFloorTile(to: x, y) ?? .wall
+```
+
+And that's it for Part 11! In this part we:
+
+* Created a secret passageway between rooms
+* Learned about back-face culling
+* Pushed a wall into a zombie
+* Found a way to combine `Tile` and `Thing` data
+
+In [Part 12](Part12.md) we'll add an actual goal for the player (besides *kill or be killed*), in the form of an end-of-level elevator.
+
+### Reader Exercises
+
+1. Currently, if you walk around the push-wall you can push it from the other side. Can you restrict it to move in only one direction? How would that be specified in the map JSON?
+
+2. Can you implement another way to activate the push-wall besides pushing it? What about a floor panel that opens a secret wall when you step on it?
+
+3. We've established that the `Billboard` type can basically just be used as a wall that isn't bound to the tile grid. Could you use this to create a *diagonal* wall? And how would you add support for diagonal wall tiles in the JSON? Would there be performance implications to using a lot of these?
+
+
+
+[[1]](#reference1) Back in 1992.
+
+[[2]](#reference2) Apparently John Carmack was originally reluctant to implement this feature as he felt it would spoil the elegance of his 3D engine implementation. Luckily, he eventually relented.
+
+[[3]](#reference3) What Is Dead May Never Die.
+
+[[4]](#reference4) Yes, I know a cube has six sides. Don't be pedantic.
+
+[[5]](#reference5) We've used a method rather than the `*` operator because the dot product is only one of several types of vector multiplication. We could use a custom operator such as `•` instead, but I prefer the clarity of an ordinary method name.
+
+[[6]](#reference6) That's assuming you disabled safety checks, as suggested in [Part 9](Part9.md#safeties-off). If you didn't, you'll find things get a bit *crashy* instead.
diff --git a/Tutorial/Part12.md b/Tutorial/Part12.md
new file mode 100644
index 0000000..a6983cf
--- /dev/null
+++ b/Tutorial/Part12.md
@@ -0,0 +1,718 @@
+## Part 12: Another Level
+
+In [Part 11](Part11.md) we added a secret passage, hidden behind a zombie-squishing push-wall. The code for Part 11 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part11.zip).
+
+The player has a few things to do now, but there's still no actual *goal*. If you die then the level restarts, but if you manage to slay all the monsters then you are left alone with nothing to do except force-quit the app.
+
+### Elevated Status
+
+In Wolfenstein 3D, though the levels are flat, we imagine ourselves inside a towering castle. We begin in the dungeon, and work our way up by finding the elevator on each floor to move up to the next level.
+
+This is a fairly elegant solution to the design problem of creating a multi-story building in an engine that supports neither stairs nor overlapping floors[[1]](#footnote1). So how do we make an elevator?
+
+Well, the Wolfenstein engine doesn't really support elevators either - at least not in the sense that later games like Doom did. The "elevator" is simply a room with metallic walls and a switch. When you throw the switch, the player is teleported instantly to the next level.
+
+So the real question is not *how do we make an elevator*, but *how do we make a switch*?
+
+### Bait and Switch
+
+We'll start by adding some texture images for the switch itself. Wolfenstein only had on and off frames for the switch, but the infrastructure we already built for animations in Rampage means it's no trouble to add several frames for a smooth transition.
+
+
+
+If you wish to flex your creative muscles and draw your own switch textures, by all means do so, but you can find the ones from the tutorial [here](https://github.com/nicklockwood/RetroRampage/tree/Part12/Source/Rampage/Assets.xcassets/).
+
+You may have noticed that these textures have a transparent background. This is a departure from Wolfenstein 3D, which combined the switch with the elevator wall behind it.
+
+Add the switch textures to XCAssets, then update the `Texture` enum in `Textures.swift` with the new cases:
+
+```swift
+case switch1, switch2, switch3, switch4
+```
+
+As with the doors in [Part 10](Part10.md), it may not be immediately obvious whether the switch should be a *tile* or a *thing*. Switches are bound to a particular wall, and don't move around, but they are animated and have state information.
+
+In [Part 11](Part11.md) we added the capability of mutating the map at runtime, which was used to replace pushable wall tiles with empty floor tiles. In principle we could do something similar with switches - perhaps using two different tiles for the on and off states. But for a four-frame animation we'd need four different tiles, plus we'd have to create new infrastructure for animations rather than re-using what we've already built. Using a `Thing` for the switch will be much more straightforward.
+
+In `Thing.swift`, add the following case to the end of the `Thing` enum:
+
+```swift
+case `switch`
+```
+
+Note the back-ticks around "switch". Because `switch` is actually a keyword in Swift, these are needed to escape the case name. This is a bit ugly, but other than at the point of declaration they mostly aren't needed.
+
+We'll eventually want to place the switch in an "elevator" room, but for now we'll just stick it on an arbitrary wall. The switch has an index of `5` in the `Thing` enum, so add a number `5` to the bottom-right outer edge of the `things` in `Map.json`, just behind the first monster's position:
+
+```swift
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 0, 3, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 0, 3, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 5,
+ 0, 0, 0, 0, 0, 0, 0, 0
+]
+```
+
+Now that we have placed a switch in the map, we'll need to actually provide the implementation. Create a new file in the Engine module called `Switch.swift` with the following contents:
+
+```swift
+public enum SwitchState {
+ case off
+ case on
+}
+
+public struct Switch {
+ public let position: Vector
+ public var state: SwitchState = .off
+ public var animation: Animation = .switchOff
+
+ public init(position: Vector) {
+ self.position = position
+ }
+}
+
+public extension Animation {
+ static let switchOff = Animation(frames: [
+ .switch1
+ ], duration: 0)
+ static let switchFlip = Animation(frames: [
+ .switch1,
+ .switch2,
+ .switch3,
+ .switch4
+ ], duration: 0.4)
+ static let switchOn = Animation(frames: [
+ .switch4
+ ], duration: 0)
+}
+```
+
+There's a lot going on here, but hopefully it should all be quite familiar by now - The `Switch` has a `position`, a `state` and an `animation` property. Although there are only two states, three animation phases are needed, because we need separate single-frame on and off animations to play when the switch has finished its transition.
+
+### Turned On
+
+We've defined the states - now to write the logic for transitioning between them. Add the following block of code between the `Switch` struct declaration and the `Animation` extension:
+
+```swift
+public extension Switch {
+ var rect: Rect {
+ return Rect(
+ min: position - Vector(x: 0.5, y: 0.5),
+ max: position + Vector(x: 0.5, y: 0.5)
+ )
+ }
+
+ mutating func update(in world: inout World) {
+ switch state {
+ case .off:
+ if world.player.rect.intersection(with: self.rect) != nil {
+ state = .on
+ animation = .switchFlip
+ }
+ case .on:
+ if animation.isCompleted {
+ animation = .switchOn
+ }
+ }
+ }
+}
+```
+
+This code defines a collision `Rect` for the switch (equivalent to the tile it sits on), and an `update()` method with a simple state machine similar to the ones we've used previously.
+
+In the `off` state the switch waits for the player to come along and bump into it, at which point it transitions to the `on` state and starts the `switchFlip` animation. In the `on` state, it waits for the animation to complete and then transitions to the single-frame `switchOn` animation so the switch doesn't keep cycling from off to on repeatedly.
+
+In `World.swift`, add a `switches` property to the `World` struct, just below `pushwalls`:
+
+```swift
+public struct World {
+ public let map: Tilemap
+ public private(set) var doors: [Door]
+ public private(set) var pushwalls: [Pushwall]
+ public private(set) var switches: [Switch]
+ ...
+}
+```
+
+Then in `World.init()` initialize the property:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.doors = []
+ self.pushwalls = []
+ self.switches = []
+ ...
+}
+```
+
+A bit further down, in `World.update()`, just below the `// Update pushwalls` section, add the following:
+
+```swift
+// Update switches
+for i in 0 ..< switches.count {
+ var s = switches[i]
+ s.animation.time += timeStep
+ s.update(in: &self)
+ switches[i] = s
+}
+```
+
+Further down still, in `World.reset()`, add a line to reset the switches when the level reloads:
+
+```swift
+mutating func reset() {
+ self.monsters = []
+ self.doors = []
+ self.switches = []
+ ...
+}
+```
+
+And below that, in the same function, add a case to the switch statement to handle the, uh, switch:
+
+```swift
+case .switch:
+ precondition(map[x, y].isWall, "Switch must be placed on a wall tile")
+ switches.append(Switch(position: position))
+```
+
+Since it only makes sense to place switches on wall tiles, we've added a `precondition` to ensure the switch is placed on an existing wall tile.
+
+### Writing on the Wall
+
+Switches in Wolfenstein behave a lot like ordinary wall textures (except that they're interactive). But because in our game we've implemented the switch as a *thing* rather than a tile, the switch doesn't exactly replace a wall tile - there's still an ordinary tile occupying the same space.
+
+There is already a precedent in the game for `Thing`s overriding the textures of nearby tiles - in [Part 10](Part10.md) when we added the doorjamb feature, it worked by replacing the walls next to each door with a different texture. The code for doing that looks like this:
+
+```swift
+let isDoor = world.isDoor(at: neighborX, tileY)
+wallTexture = textures[isDoor ? .doorjamb : tile.textures[0]]
+```
+
+If we had opted to make our switch texture opaque, and included the elevator wall texture in the same frame, we would probably use that exact same technique again. But instead we made it partially transparent, giving us the flexibility to place a switch on any wall.
+
+Technically we don't *need* this functionality right now, as we will only be using the switch for elevators, but it's very little extra work to support this, and it means we can potentially use switches for other gameplay features later.
+
+Combining the wall and switch requires a slightly different approach. Instead of replacing the wall texture with the switch texture, we need to draw the switch on top of the wall in a second pass.
+
+For drawing the doors, we created an `World.isDoor()` function to determine if a given tile contained a door. We'll need something similar for drawing switches. Still in `World.swift`, find the `isDoor()` function, and just below it add the following:
+
+```swift
+func `switch`(at x: Int, _ y: Int) -> Switch? {
+ guard map.things[y * map.width + x] == .switch else {
+ return nil
+ }
+ return switches.first(where: {
+ Int($0.position.x) == x && Int($0.position.y) == y
+ })
+}
+```
+
+Like `isDoor(at:)`, the `switch(at:)` method first checks the `map.things` array to see if the specified tile contains a switch. This lookup can be done in constant time, so for the majority of tiles which don't contain a switch, it's inexpensive.
+
+But because the switch texture changes over time, it's not enough to know *if* a tile contains a switch - we need to actually access the switch object. For that, we perform a linear search of the switches array to find the one with matching coordinates.
+
+In `Renderer.swift`, insert the following code just before the `// Draw floor and ceiling` section:
+
+```swift
+// Draw switch
+if let s = world.switch(at: tileX, tileY) {
+ let switchTexture = textures[s.animation.texture]
+ bitmap.drawColumn(textureX, of: switchTexture, at: wallStart, height: height)
+}
+```
+
+This code looks for a switch at the current tile coordinate, and if found, draws a vertical strip of the current switch animation frame over the top of the wall texture.
+
+Run the app now and you should see the switch on the wall behind the first monster.
+
+![Switch texture on wall](Images/SwitchOnWall.png)
+
+### Flip the Switch
+
+Dispatch the monster before it kills you, then walk over and flip the switch. It should smoothly animate from off to on.
+
+![Switch in on state](Images/SwitchFlipped.png)
+
+That's all well and good, but nothing actually happens when the switch is flipped. What we *want* to happen is for the level to end.
+
+Since we don't yet have multiple levels, for now we'll just reset the current level to the beginning. In [Part 7](Part7.md) we implemented the logic to reset the level when the player is killed. The process for ending the level will be similar.
+
+In `World.swift`, add the following property to the `World` struct:
+
+```swift
+public private(set) var isLevelEnded: Bool
+```
+
+Then in `World.init()`, set it to `false` by default:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.doors = []
+ self.switches = []
+ self.monsters = []
+ self.effects = []
+ self.isLevelEnded = false
+ reset()
+}
+```
+
+Further down, in `World.reset()`, set `isLevelEnded` to `false` again to avoid a reset loop:
+
+```swift
+ mutating func reset() {
+ self.monsters = []
+ self.doors = []
+ self.switches = []
+ self.isLevelEnded = false
+ ...
+}
+```
+
+We've made the `isLevelEnded` setter private because rather than having the switch just set the property directly, we want to have some control over the level-end sequence. Still in `World.swift`, just above the `reset()` method, add the following:
+
+```swift
+mutating func endLevel() {
+ isLevelEnded = true
+ effects.append(Effect(type: .fadeOut, color: .black, duration: 2))
+}
+```
+
+This method sets `isLevelEnded` to `true` and also triggers a slow fade to black that will nicely cover the transition[[2]](#footnote2). Next, in `World.update()`, just below the `// Update effects` section, add the following:
+
+```swift
+// Check for level end
+if isLevelEnded {
+ if effects.isEmpty {
+ reset()
+ effects.append(Effect(type: .fadeIn, color: .black, duration: 0.5))
+ }
+ return
+}
+```
+
+This is pretty similar to the logic we used for the player death scenario. If the level has ended, we exit early from the update method and wait for the fade effect to finish before resetting the level and fading back in.
+
+The early exit ensures that the player can't be killed between flipping the switch and the level ending, which would lead to an awkward race condition, and a potentially frustrating gameplay experience.
+
+Finally, in `Switch.update()`, update the `on` case as follows:
+
+```swift
+case .on:
+ if animation.time >= animation.duration {
+ animation = .switchOn
+ world.endLevel()
+ }
+```
+
+You might wonder why we're calling `endLevel()` here when the animation completes instead of in the `off` case when the transition to `on` happens? The reason is that triggering the level-end pauses all animation updates (apart from `Effect`s), so if we did it earlier we would never actually see the switch animation.
+
+Run the game again and go press the switch. You should see the level fade to black and reset back to the beginning.
+
+### Make Room
+
+It was handy for testing purposes to place the switch at the start of the level, but it doesn't make much sense from a gameplay perspective. It's time to construct a proper elevator room.
+
+Elevators aren't generally made out of stone. The elevator will need new metallic textures for the walls, floor and ceiling. When we factor in the two copies of each texture needed for lighting angles, this adds up to quite a lot of images for something that only appears once in each map.
+
+Wolfenstein cheated here by only drawing the elevator and switch textures in one shade - the elevator textures are always arranged in the same way, so as long as they are shaded correctly relative to each other, there's nothing to break the lighting illusion.
+
+We'll take a similar approach. Here is the full set of textures for the elevator.
+
+
+
+As before, add the textures to XCAssets, then update the `Texture` enum in `Textures.swift` with the new cases:
+
+```swift
+case elevatorFloor, elevatorCeiling, elevatorSideWall, elevatorBackWall
+```
+
+In `Tile.swift`, add new cases for the elevator tiles:
+
+```swift
+public enum Tile: Int, Decodable {
+ ...
+ case elevatorFloor
+ case elevatorSideWall
+ case elevatorBackWall
+}
+```
+
+Update the `Tile.isWall` computed property as follows:
+
+```swift
+var isWall: Bool {
+ switch self {
+ case .wall, .crackWall, .slimeWall, .elevatorSideWall, .elevatorBackWall:
+ return true
+ case .floor, .crackFloor, .elevatorFloor:
+ return false
+ }
+}
+```
+
+Then extend the `Tile.textures` property logic to handle the new cases:
+
+```swift
+var textures: [Texture] {
+ switch self {
+ ...
+ case .elevatorSideWall:
+ return [.elevatorSideWall, .elevatorSideWall]
+ case .elevatorBackWall:
+ return [.elevatorBackWall, .elevatorBackWall]
+ case .elevatorFloor:
+ return [.elevatorFloor, .elevatorCeiling]
+ }
+}
+```
+
+That should be all the changes needed to support the elevator textures. Finally, we just need to craft the elevator itself in the `Map.json` file. Update the JSON as follows:
+
+```swift
+"tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 3, 1, 0, 1,
+ 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, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 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
+]
+```
+
+Run the game and fight your way through the zombies. At the end you'll be rewarded with the exit elevator.
+
+![The exit elevator](Images/Elevator.png)
+
+### Level Up
+
+It's nice that we can now actually complete the level, but a single level won't keep the player entertained for long. It's time to add a second map.
+
+The first level map is currently defined inside `Map.json`. We could add a second `Map2.json` file, but using separate files for each map would introduce a problem with ordering. We'd then need to add an additional manifest file for the game to know the total number of levels, which level to start with, and which to load next.
+
+Since our map data is relatively tiny, for now we'll sidestep this complexity by just putting all of the level maps in a single file. Rename `Map.json` to `Levels.json`, and replace its contents with the following:
+
+```swift
+[
+ {
+ "width": 8,
+ "tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 3, 1, 0, 1,
+ 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, 0, 0,
+ 0, 0, 0, 0, 2, 0, 0, 0,
+ 0, 0, 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
+ ]
+ }
+]
+```
+
+The top-level container of the JSON is now an array. The first element is the original map, and we've added a second map element for the second level.
+
+You may have noticed that the new map is smaller, and rectangular rather than square. Since each map specifies its own width, there's no requirement for the levels to all be the same size[[3]](#footnote3).
+
+In `ViewController.swift` (in the main app project, not the Engine module), find the `loadMap()` method and replace it with the following:
+
+```swift
+public func loadLevels() -> [Tilemap] {
+ let jsonURL = Bundle.main.url(forResource: "Levels", withExtension: "json")!
+ let jsonData = try! Data(contentsOf: jsonURL)
+ return try! JSONDecoder().decode([Tilemap].self, from: jsonData)
+}
+```
+
+The loading logic is similar to what we had before, but it now returns an array of `Tilemap`s instead of a single map. The initializer for `World` only takes a single map, so further down in the same file replace the line:
+
+```swift
+private var world = World(map: loadMap())
+```
+
+with:
+
+```swift
+private let levels = loadLevels()
+private lazy var world = World(map: levels[0])
+```
+
+The `World` instance is now initialized with the first map in the `levels` array, but to move between levels we're going to need a way for the world to keep track of which level we are currently on.
+
+### By the Numbers
+
+Since the levels have an inherent order (defined by their position in the levels array), it would make sense to refer to them by index. We could add a property to the `World` or `ViewController` to track the current level index, but in general it's better to keep related data together.
+
+Open `Tilemap.swift` and add a `index` property:
+
+```swift
+public struct Tilemap: Decodable {
+ ...
+ public let index: Int
+}
+```
+
+Now how do we set this property? Maps are currently created by deserializing them from JSON. We could just add an `index` property to the JSON, but it's rather inelegant to specify the index twice (implicitly, with the order of the level in the `levels` array, and then again explicitly in each level).
+
+This may seem like a pedantic thing to worry about, but these kinds of data duplication can lead to real bugs - what would happen for example if we rearranged the levels but forgot to update their index values, so that the level order and level indexes no longer matched up?
+
+Ideally, we'd like to set the index programmatically after we've loaded the levels JSON, but Swift's strictness can make deferred initialization difficult. We could make the `index` property optional by using `?` or `!` but then we'd have to deal with partially initialized objects, and either write code to handle maps with a `nil` index in places where that should never actually happen, or risk a runtime crash by force-unwrapping.
+
+If we take a step back, it's clear that what we are really talking about here are two distinct types: Maps that have an index, and maps that don't. The elegant solution is therefore to fork the `Tilemap` type.
+
+In `Tilemap.swift`, add the following code just above the `Tilemap` struct definition:
+
+```swift
+public struct MapData: Decodable {
+ fileprivate let tiles: [Tile]
+ fileprivate let things: [Thing]
+ fileprivate let width: Int
+}
+```
+
+This `MapData` struct is a new, opaque type that represents the map data as it appears in the JSON file (without an index). `Tilemap` itself will no longer be directly decoded from the JSON, so we can remove the `Decodable` conformance. Replace the line:
+
+```swift
+public struct Tilemap: Decodable {
+```
+
+with:
+
+```swift
+public struct Tilemap {
+```
+
+Next, add a public initializer to `Tilemap` so that we can construct it from a deserialized `MapData` object:
+
+```swift
+public init(_ map: MapData, index: Int) {
+ self.tiles = map.tiles
+ self.things = map.things
+ self.width = map.width
+ self.index = index
+}
+```
+
+Finally, back in `ViewController.swift`, replace the following line in `loadLevels()`:
+
+```swift
+return try! JSONDecoder().decode([Tilemap].self, from: jsonData)
+```
+
+with:
+
+```swift
+let levels = try! JSONDecoder().decode([MapData].self, from: jsonData)
+return levels.enumerated().map { Tilemap($0.element, index: $0.offset) }
+```
+
+### At Your Command
+
+The `World` now has a way to track which level it is running, but it still lacks a way to load the next level.
+
+This brings us to an interesting design problem. The `ViewController` owns the world and the levels, but the `World` decides when the level has ended. Communication between `ViewController` and the `World` is currently one-way, so how can the world tell the platform that it needs to load the next level?
+
+If you're familiar with Cocoa design patterns, you'll have seen this situation before. The relationship between our view controller and the game world is similar to the relationship between an ordinary `UIViewController` and its view, and you might be thinking right now that the [Delegation Pattern](https://en.wikipedia.org/wiki/Delegation_pattern) might be a good solution.
+
+Delegation works really well in UIKit, where everything is class-based, but it won't work well here due to the *overlapping access* issue we encountered back in [Part 8](Part8.md#health-hazard)[[4]](#footnote4).
+
+Because the `World` is a struct (a [value type](https://en.wikipedia.org/wiki/Value_type_and_reference_type)), we are effectively replacing it every time we modify any of its properties. If the world calls the `ViewController` from inside the mutating `update()` method in order to load a new map, and the `ViewController` then synchronously modifies its own copy of the world, there will be two different copies of the world being modified at once, and one set of changes will be lost. If we try to solve that by passing the current world instance via `inout` to the delegate, we'll trigger the overlapping access error.
+
+This problem is a little hard to demonstrate without writing a lot of code that we'll ultimately throw away, so let's just skip to the better solution, which is to use the [Command Pattern](https://en.wikipedia.org/wiki/Command_pattern).
+
+In the command pattern, a *command* (also known as an *action*) is an object that encapsulates a function call. With delegation, messages must be sent synchronously, but with the command pattern, a message can be stored, passed around between functions, and evaluated at your convenience.
+
+Everything that takes place in the game happens inside the `World.update()` method. Right now, when the level ends we call the `World.reset()` method and then return early from the function. Since `World.update()` is called from `ViewController.update()`, this is the perfect setup for two-way communication. The `ViewController` communicates with `World` via arguments to the `World.update()` function, and if `World` needs to send a command back to the `ViewController`, it can just return it from that function.
+
+At the top of `World.swift` add the following:
+
+```swift
+public enum WorldAction {
+ case loadLevel(Int)
+}
+```
+
+The `WorldAction` enum represents the commands that the `World` can send to `ViewController`. For now there is only one type of action that `World` needs to send - an instruction to load a new level - but there will undoubtedly be other such messages as the logic evolves.
+
+Now go ahead and change the method signature for `World.update()` from:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+```
+
+to:
+
+```swift
+mutating func update(timeStep: Double, input: Input) -> WorldAction? {
+```
+
+There are a couple of places in `update()` where the method returns early. The first is in the `// Check for level end` section:
+
+```swift
+ // Check for level end
+if isLevelEnded {
+ if effects.isEmpty {
+ reset()
+ effects.append(Effect(type: .fadeIn, color: .black, duration: 0.5))
+ }
+ return
+}
+```
+
+Replace that code block with the following:
+
+```swift
+// Check for level end
+if isLevelEnded {
+ if effects.isEmpty {
+ effects.append(Effect(type: .fadeIn, color: .black, duration: 0.5))
+ return .loadLevel(map.index + 1)
+ }
+ return nil
+}
+```
+
+Next, in the `// Update player` section, replace the `return` in the `else` block with:
+
+```swift
+return nil
+```
+
+Then add another `return nil` to the very end of the `World.update()` method, just after the `// Check for stuck actors` block:
+
+```swift
+mutating func update(timeStep: Double, input: Input) -> WorldEvent? {
+ ...
+
+ // Check for stuck actors
+ if player.isStuck(in: self) {
+ hurtPlayer(1)
+ }
+ for i in 0 ..< monsters.count where monsters[i].isStuck(in: self) {
+ hurtMonster(at: i, damage: 1)
+ }
+
+ return nil
+}
+```
+
+Back in `ViewController.update()`, replace the line:
+
+```swift
+world.update(timeStep: timeStep / worldSteps, input: input)
+```
+
+with:
+
+```swift
+if let action = world.update(timeStep: timeStep / worldSteps, input: input) {
+ switch action {
+ case .loadLevel(let index):
+ let index = index % levels.count
+ world = World(map: levels[index])
+ }
+}
+```
+
+### Jump Start
+
+Run the game again, fight your way to the exit elevator[[5]](#footnote5), and you should find that flipping the switch loads the next level.
+
+The only slight problem is that we've lost the nice fade-in when the level starts. Because we're just replacing the whole world, in the `loadLevel` action, the in-flight fade `Effect` is not being preserved, and so the next level starts with a jump.
+
+We can't (and shouldn't) manipulate `World.effects` from inside `ViewController`, but we can solve this with a dedicated setter method. In `World.swift`, add the following method just after `endLevel`:
+
+```swift
+mutating func setLevel(_ map: Tilemap) {
+ let effects = self.effects
+ self = World(map: map)
+ self.effects = effects
+}
+```
+
+Then in `ViewController.update()`, replace the line:
+
+```swift
+world = World(map: levels[index])
+```
+
+with:
+
+```swift
+world.setLevel(levels[index])
+```
+
+That's it for Part 12! In this part we:
+
+* Added a wall switch to end the level
+* Fashioned an elevator room to house the switch
+* Added a second level and the means to load it
+
+In [Part 13](Part13.md) we'll bring the world to life with sound effects.
+
+### Reader Exercises
+
+1. Try adding a third level.
+
+2. Right now the levels must be played through in order, but do they have to be? Could you add a second switch (perhaps hidden behind a push-wall door) that takes you to a secret level?
+
+3. What if a wall switch could do something other than end the level? What about adding a switch that opens a locked door, or activates a push-wall automatically? For a real challenge, how about a switch that turns the lights on and off in the level (this will be easier if you completed the dynamic lighting exercise in [Part 4](Part4.md#reader-exercises)).
+
+
+
+[[1]](#reference1) Setting aside the rather implausible architecture of a building where each pair of floors is joined by a separate elevator shaft in a different place on the floor plan.
+
+[[2]](#reference2) You might wonder why we don't just use the `didSet` handler on the `isLevelEnded` property rather than adding a separate method? In general it's risky to use `didSet` to trigger side-effects. There are other places in the code where we set the `isLevelEnded` property without wanting to trigger the side-effect (e.g. inside `reset()`), so doing it within the setter may lead to unexpected behavior.
+
+[[3]](#reference3) In Wolfenstein 3D the level-size was hard-coded to 64x64 tiles - it wasn't possible to make a level larger than that, but it was possible to make *smaller* levels by simply not using all the space.
+
+[[4]](#reference4) There's actually a second reason not to use delegation, which is that it will make it harder in the future to implement a save game system, because adding a delegate property to `World` will interfere with automatic Codable synthesis.
+
+[[5]](#reference5) If you find this too challenging - especially when trying to play with a mouse on the simulator - try swapping the first and second levels in `Levels.json`. The second level is considerably easier!
diff --git a/Tutorial/Part13.md b/Tutorial/Part13.md
new file mode 100644
index 0000000..e4efa5f
--- /dev/null
+++ b/Tutorial/Part13.md
@@ -0,0 +1,978 @@
+## Part 13: Sound Effects
+
+In [Part 12](Part12.md) we added multiple levels, and the means to move between them. You can find the complete source code for Part 12 [here](https://github.com/nicklockwood/RetroRampage/archive/Part12.zip).
+
+We've covered several different aspects of graphics and gameplay so far, but in this chapter we're going to explore an area that we've so-far neglected - sound.
+
+### The Sound of Silence
+
+We have an almost fully-playable game, but it's a bit... *quiet*. Many gamers, especially on mobile devices tend to play with the sound turned off, but even so it's expected that a game should provide sound and/or music for those that want it, and audio design often plays a key role in building the atmosphere in a game.
+
+The digitized voices of the prison guards[[1]](#footnote1) shouting "Halten sie!" or "Mein lieben!" were an iconic feature of Wolfenstein 3D, but in the early DOS era, playing sound was a significant challenge.
+
+### Beep Boop
+
+DOS PCs did not come with the means to play complex digital audio as standard. If you were lucky, you had a [Sound Blaster](https://en.wikipedia.org/wiki/Sound_Blaster) or [Gravis Utrasound](https://en.wikipedia.org/wiki/Gravis_Ultrasound) card, but in many cases the only sound output was the built-in [PC speaker](https://en.wikipedia.org/wiki/PC_speaker), a coin-sized device that could beep in various tones to tell you that the computer was (or wasn't) working.
+
+Fortunately, polyphonic stereo sound is now standard on pretty much any device we might want to support. Well, *sort of* standard. While the platforms that Swift targets have similar audio capabilities, Swift's standard library has no built-in support for sound, and for that reason we will have to implement the actual audio output in the platform layer rather than the cross-platform part of the engine.
+
+A key theme of this tutorial series has been to demonstrate the low-level graphics programming techniques used in retro games, before the advent of modern high-level frameworks and drivers. It's tempting to try to take the same approach with sound, and implement playback by directly managing a buffer of quantized [PCM](https://en.wikipedia.org/wiki/Pulse-code_modulation) sound samples inside the engine (the audio equivalent of the pixel array we use to represent bitmaps). But while that would allow for complex audio manipulation, it's overkill for what we need in the game, which is really just to play back prerecorded sound effects.
+
+Since the engine itself won't need to play or manipulate audio, there's no need for it to handle raw data. But it will need some way to refer to specific sounds.
+
+In the Engine module, add a new file called `Sounds.swift`, with the following contents:
+
+```swift
+public enum SoundName: String, CaseIterable {
+ case pistolFire
+ case ricochet
+ case monsterHit
+ case monsterGroan
+ case monsterDeath
+ case monsterSwipe
+ case doorSlide
+ case wallSlide
+ case wallThud
+ case switchFlip
+ case playerDeath
+ case playerWalk
+ case squelch
+}
+
+public struct Sound {
+ public let name: SoundName
+}
+```
+
+`SoundName` is a enum of identifiers that we can use to refer to sound resources within the engine. Like the `Texture` enum, the `SoundName` cases will map to actual files in the platform layer, but the engine doesn't need to know the details of that mapping.
+
+### Bang Bang
+
+We'll start with the pistol sound. Firing is handled by the `Player` object, but the player doesn't have a direct channel of communication with the platform layer - all of its interactions are mediated via the `World` object. For that reason, let's add a `playSound()` method to `World` that can be called by its inhabitants.
+
+In `World.swift`, add the following method just before the `endLevel()` function declaration:
+
+```swift
+func playSound(_ name: SoundName) {
+
+}
+```
+
+Next, in `Player.update()`, add the following line just after `animation = .pistolFire`:
+
+```
+world.playSound(.pistolFire)
+```
+
+Now how will the world actually play the sound? Most communication between the platform and the world has been one-way so far, but in [Part 12](Part12.md) we introduced the `WorldAction` enum, allowing the world to send commands back to the platform layer at the end of every frame update. Go ahead now and extend `WorldAction` (in `World.swift`) with a `playSounds()` case:
+
+```switch
+public enum WorldAction {
+ case loadLevel(Int)
+ case playSounds([Sound])
+}
+```
+
+Actions are returned from the `World.update()` method, but the `World.playSound()` method doesn't have access to the local scope of `update()`, so we'll need to add a variable to the `World` struct that can be used to store sounds temporarily until the frame is complete.
+
+For now the only sound source is the pistol firing, but later it's possible that more that one sound may be triggered within a single frame, so we'll use an array to store them. Add the following property to `World`, just below the `effects` property:
+
+```swift
+private var sounds: [Sound] = []
+```
+
+Next, update the `playSound()` method as follows:
+
+```swift
+mutating func playSound(_ name: SoundName) {
+ sounds.append(Sound(name: name))
+}
+```
+
+The `update()` method returns an optional `WorldAction`. Most of the time it returns `nil`, unless a new level needs to be loaded. At the end of the method, replace the line `return nil` with the following:
+
+```swift
+// Play sounds
+defer { sounds.removeAll() }
+return .playSounds(sounds)
+```
+
+This means that any sounds initiated during the frame will be returned to the caller in a `playSounds()` action. The `defer { ... }` statement ensures that the sounds are removed from the array after returning them, so they won't be played again on the next frame.
+
+That takes care of the model side. Now we need to update the platform layer.
+
+### Playtime
+
+The first step is to choose an API for sound playback. On iOS we're [spoilt for choice](https://www.objc.io/issues/24-audio/audio-api-overview/) when it comes to audio APIs. For the purposes of this tutorial we're going to use `AVAudioPlayer`, a high-level framework that abstracts over the finer details of working with audio hardware and sound data.
+
+The `AVAudioPlayer` class supports a variety of sound file formats. Before we implement the player itself we'll need some files to play, so let's go ahead and add those now - one file for each case in the `SoundNames` enum. The sound files used in the tutorial can be found [here](https://github.com/nicklockwood/RetroRampage/tree/Part13/Source/Rampage/Sounds).
+
+The sounds are in [MP3](https://en.wikipedia.org/wiki/MP3) format, and were obtained from https://www.zapsplat.com. Download the files and import the entire `Sounds` directory into the main project (see the screenshot below).
+
+**Note:** If you wish to redistribute these files you must abide by the terms of the [Zapsplat license](https://www.zapsplat.com/license-type/standard-license/].
+
+
+
+Each `AVAudioPlayer` instance is responsible for a single sound. To manage multiple sounds we'll need to build some infrastructure around it. Add a new file to the main app target called `SoundManager.swift` with the following contents:
+
+```swift
+import AVFoundation
+
+public class SoundManager {
+ private var playing = Set()
+
+ public static let shared = SoundManager()
+
+ private init() {}
+}
+
+public extension SoundManager {
+ func activate() throws {
+ try AVAudioSession.sharedInstance().setActive(true)
+ }
+
+ func play(_ url: URL) throws {
+ let player = try AVAudioPlayer(contentsOf: url)
+ playing.insert(player)
+ player.play()
+ }
+}
+```
+
+The `SoundManager` class is a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern). The `activate()` method sets up the audio session, which notifies iOS that the app will be using sound. The `play()` method is somewhat self-explanatory - it accepts a URL pointing to an audio file, creates an `AVAudioPlayer` instance from it, and then plays it.
+
+You might be wondering about the `playing.insert(sound)` line. The `AVAudioPlayer` does not retain itself while playing, so if we simply returned without storing the player anywhere then it would immediately be released, and sound playback would stop. For that reason we add it to an internal `Set` so that it can live long enough for playback to complete.
+
+This does leave us with the problem of how to clean up completed sounds. One option would be to scan the `playing` Set for finished sounds every time we call `play()`, but there's a better solution, which is to use the `AVAudioPlayerDelegate`.
+
+Change the class declaration for `SoundManager` as follows:
+
+```swift
+public class SoundManager: NSObject, AVAudioPlayerDelegate {
+```
+
+Then replace the line:
+
+```swift
+private init() {}
+```
+
+with:
+
+```swift
+private override init() {}
+
+public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
+ playing.remove(player)
+}
+```
+
+Next, in the `SoundManager.play()` method, add the following line between `playing.insert(player)` and `player.play()`:
+
+```swift
+player.delegate = self
+```
+
+Now, when a player has finished playing the `audioPlayerDidFinishPlaying()` method will be called and we'll remove it from the set so the object can be released.
+
+In `ViewController.swift`, at the top of the file, just below the `loadTextures()` method, add the following code:
+
+```swift
+public extension SoundName {
+ var url: URL? {
+ return Bundle.main.url(forResource: rawValue, withExtension: "mp3")
+ }
+}
+
+func setUpAudio() {
+ try? SoundManager.shared.activate()
+}
+```
+
+This convenience extension on `SoundName` makes it easy for us to map the case used by the game engine to the equivalent MP3 file in the app bundle. Further down, in `viewDidLoad(), add a call to `setUpAudio()` just before `setUpImageView()`:
+
+```swift
+override func viewDidLoad() {
+ ...
+
+ setUpAudio()
+ setUpImageView()
+
+ ...
+}
+```
+
+Still in `ViewController.swift`, find the `update()` function and add a new case to the switch statement, below `case .loadLevel`:
+
+```swift
+case .playSounds(let sounds):
+ for sound in sounds {
+ guard let url = sound.name.url else {
+ continue
+ }
+ try? SoundManager.shared.play(url)
+ }
+```
+
+### Sound Check
+
+We used a guard here to prevent a crash if the URL is nil, but this can really only happen due to a programming error (e.g. if the file is misnamed or missing from the bundle), so don't really want to fail silently. Force-unwrapping here also wouldn't be ideal because if we did mess up then we'd prefer to find out immediately, rather than at some later point in the game when a given sound is played for the first time.
+
+A good solution for this would be to run through all the `SoundName` cases when the app first launches and assert that the equivalent file exists. Since `SoundName` already conforms to the `CaseIterable` protocol, that's quite straightforward. Insert the following code at the start of the `setUpAudio()` method we added earlier:
+
+```swift
+for name in SoundName.allCases {
+ precondition(name.url != nil, "Missing mp3 file for \(name.rawValue)")
+}
+```
+
+With this assertion in place, the game will crash immediately on launch if any sound file is missing from the app bundle[[2]](#footnote2).
+
+Run the game and you should find that the pistol now fires with a satisfying "BANG!" (or whatever sound you decided to use for it). But on some devices (and it's especially noticeable in the iOS Simulator) you may see the game freeze momentarily when you first try to fire.
+
+While I've not been able to find any official Apple documentation for this, it seems that the first time an `AVAudioPlayer` instance is created, the thread is blocked briefly as it brings the audio system online. A simple fix for this is to *preload* a sound file when the app first launches, thereby priming `AVAudioPlayer` to play future sounds without a delay.
+
+To facilitate that, in `SoundManager.swift` add the following new method just below the `activate()` declaration:
+
+```swift
+func preload(_ url: URL) throws -> AVAudioPlayer {
+ return try AVAudioPlayer(contentsOf: url)
+}
+```
+
+Then in `ViewController.swift`, add the this line to the bottom of the `setUpAudio()` method:
+
+```swift
+_ = try? SoundManager.shared.preload(SoundName.allCases[0].url!)
+```
+
+This will load the first sound in the `SoundName` enum when the app first starts up (without actually playing it). This happens to be the pistol sound that we are currently using, but it is not necessary to preload the specific sound that you wish to play - loading *any* sound is sufficient to warm up the system so that all subsequent sounds will load and play without latency.
+
+Now that we have one sound effect working, it's time to add some more. When the pistol is fired, it will either hit a monster, or miss and hit the wall behind. We have two sound effects for these scenarios, `monsterHit` and `ricochet`.
+
+In `Player.update()`, replace the following block of code:
+
+```swift
+if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: 10)
+}
+```
+
+with:
+
+```swift
+if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: 10)
+ world.playSound(.monsterHit)
+} else {
+ world.playSound(.ricochet)
+}
+```
+
+If you run the game again you should now find that bullets hit the monster with a satisfying "SPLAT!", and misses have a "ZING!" as they bounce around the room.
+
+### Going the Distance
+
+The bullet collision system is based on a ray-test rather than a moving projectile, so the collision effectively happens instantly the moment the weapon is fired. Visually this is not a problem, as the level is quite small and bullets travel at supersonic speeds. But from an audio perspective we'd expect a delay between the sound of the gunshot and the sound of the bullet ricocheting off a distant wall due to the finite speed of sound.
+
+Another (related) issue is that the *loudness* does not diminish with distance, so a ricochet sounds the same if it hits a wall on the far side of the room or right in front of your face.
+
+Fixing these limitations will go a long way towards making the game's audio more immersive. To do that we'll need to adjust the sound volume and starting delay based on the distance of the sound source from the player.
+
+In `Sounds.swift`, add `volume` and `delay` properties to the `Sound` struct:
+
+```swift
+public struct Sound {
+ public let name: SoundName
+ public let volume: Double
+ public let delay: Double
+}
+```
+
+Rather than computing these values at the call site, we'll pass the sound's source location to `World.playSound()` and let it figure out the rest. In `Player.update()`, replace the line:
+
+```swift
+world.playSound(.pistolFire)
+```
+
+with:
+
+```swift
+world.playSound(.pistolFire, at: position)
+```
+
+The `position` value we are using in this case is just the player's position, since the pistol is in the player's hand. Next, replace the following code:
+
+```swift
+if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: 10)
+ world.playSound(.monsterHit)
+} else {
+ world.playSound(.ricochet)
+}
+```
+
+with:
+
+```swift
+if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: 10)
+ world.playSound(.monsterHit, at: world.monsters[index].position)
+} else {
+ let hitPosition = world.hitTest(ray)
+ world.playSound(.ricochet, at: hitPosition)
+}
+```
+
+Even though they are triggered by the same event, the positions for the hit and ricochet sounds are different from the gunshot. For a hit we use the monster's position, and for a miss we use `hitTest()` to determine where the bullet intersects the wall.
+
+In `World.swift`, update the signature of the `playSound()` method as follows:
+
+```swift
+mutating func playSound(_ name: SoundName, at position: Vector) {
+```
+
+To compute the sound distance, we need to subtract the player's position from the sound's position and get the length of the resultant vector. Add the following code to the start of the `playSound()` method:
+
+```swift
+let delta = position - player.position
+let distance = delta.length
+```
+
+Sound propagation follows the [inverse square law](https://en.wikipedia.org/wiki/Inverse-square_law), meaning that the apparent sound volume is proportional to one divided by the square of the distance of the listener from the source. Add the following line to `playSound()`:
+
+```swift
+let volume = 1 / (distance * distance + 1)
+```
+
+Note the `+ 1` in the equation. This ensures that at zero distance from the player, the volume will be 1.0 and not infinity.
+
+What about the time delay? Well, the speed of sound in air is [around 343 meters per second](https://en.wikipedia.org/wiki/Speed_of_sound). If we assume that the map tiles are about two meters square, that means the delay will be 2 / 343 which amounts to 0.0058 seconds per world unit of distance.
+
+It may seem like such a trivial delay won't be noticeable, but at 60fps a single frame has a duration of only 0.0167 seconds, so a sound will be delayed by about one frame for every three world units' distance from the player. Still in `playSound()`, replace the line:
+
+```swift
+sounds.append(Sound(name: name))
+```
+
+with:
+
+```swift
+let delay = distance * 2 / 343
+sounds.append(Sound(name: name, volume: volume, delay: delay))
+```
+
+That's it for the engine updates, but we still need to handle these new sound parameters in the platform layer. In `SoundManager.swift`, change the signature of the `play()` method to:
+
+```swift
+func play(_ url: URL, volume: Double) throws {
+```
+
+Then add the following line just before `player.play()`:
+
+```swift
+player.volume = Float(volume)
+```
+
+Next, in `ViewController.update()` replace the lines:
+
+```swift
+guard let url = sound.name.url else {
+ continue
+}
+try? SoundManager.shared.play(url)
+```
+
+with:
+
+```swift
+DispatchQueue.main.asyncAfter(deadline: .now() + sound.delay) {
+ guard let url = sound.name.url else {
+ return
+ }
+ try? SoundManager.shared.play(url, volume: sound.volume)
+}
+```
+
+Try running the game again and you should be able to hear the difference, with distant ricochets sounding both quieter and more delayed. It's possible the sound volume now drops a little *too* quickly. We can fix that by adding a multiplier to the volume equation.
+
+In `World.playSound()`, replace the line:
+
+```swift
+let volume = 1 / (distance * distance + 1)
+```
+
+with:
+
+```swift
+let dropOff = 0.5
+let volume = 1 / (distance * distance * dropOff + 1)
+```
+
+The `dropOff` constant controls the rate at which the sound volume drops with distance. A value of 0.5 means the volume will drop at half the rate it did before.
+
+### Pump Up the Stereo
+
+In the same way that different perspectives from our two eyes grant us stereo vision, the separate inputs from our two ears give us stereo *hearing*, allowing us to work out the approximate direction of a sound source.
+
+The sound system in the game is now taking the sound's distance into account, but not its *direction*. `AVAudioPlayer` has a `pan` property for controlling the balance of sound between the left and right speakers. Go ahead and add `pan` to the `Sound` type in the Engine module:
+
+```swift
+public struct Sound {
+ public let name: SoundName
+ public let volume: Double
+ public let pan: Double
+ public let delay: Double
+}
+```
+
+To calculate the pan value, we need to compute the angle between the player's direction and the sound source, then convert that to a value in the range -1 to 1.
+
+Taking the sine of the angle would give us the result we want, but we don't readily have the means to compute that in the Engine module, as Swift's standard library doesn't include trigonometric functions (we don't have a way to compute the angle either, for that matter).
+
+Fortunately there's a simpler way. The sine of an angle is equivalent to the cosine of the orthogonal angle (see diagram).
+
+
+
+How does that help us? Well, the cosine of the angle between two normalized vectors is equal to the dot product[[3]](#footnote3) of those vectors. So if we take the dot product of the direction of the sound and the orthogonal of the player direction, that should be exactly what we need for the pan value.
+
+In `World.playSound()`, replace the line:
+
+```swift
+sounds.append(Sound(name: name, volume: volume, delay: delay))
+```
+
+with:
+
+```swift
+let direction = distance > 0 ? delta / distance : player.direction
+let pan = player.direction.orthogonal.dot(direction)
+sounds.append(Sound(name: name, volume: volume, pan: pan, delay: delay))
+```
+
+Note the `distance > 0` check - this is needed to prevent a divide-by-zero when the sound source is directly on top of the player.
+
+The final step is to add support for `pan` in the platform layer. In `SoundManager.swift`, update the `play()` method as follows:
+
+```swift
+func play(_ url: URL, volume: Double, pan: Double) throws {
+ let player = try AVAudioPlayer(contentsOf: url)
+ playing.insert(player)
+ player.delegate = self
+ player.volume = Float(volume)
+ player.pan = Float(pan)
+ player.play()
+}
+```
+
+Then, in `ViewController.update()`, replace the line:
+
+```swift
+try? SoundManager.shared.play(url, volume: sound.volume)
+```
+
+with:
+
+```swift
+try? SoundManager.shared.play(
+ url,
+ volume: sound.volume,
+ pan: sound.pan
+)
+```
+
+Run the game again and see if you can hear the difference. If you are having trouble detecting it, try bumping up against a door from the side-on, then turn around to face the other way before it closes. The effect is especially pronounced when wearing headphones.
+
+### Quiet Room
+
+It's time to bring some sound to the environment itself, starting with the sliding doors. If you haven't already done so, add all the remaining sound files now. Then, in `Door.swift` add the following code to the `update()` method, just below the `state = .opening` line:
+
+```swift
+world.playSound(.doorSlide, at: position)
+```
+
+Then add the same line again below `state = .closing`.
+
+Next, in `Switch.swift`, in the `update()` method, insert the following code just below the line `animation = .switchFlip`:
+
+```swift
+world.playSound(.switchFlip, at: position)
+```
+
+In `Pushwall.swift`, add this line at the top of the `update()` method:
+
+```swift
+let wasMoving = isMoving
+```
+
+Then, at the end of the method add the following code:
+
+```swift
+if isMoving, !wasMoving {
+ world.playSound(.wallSlide, at: position)
+} else if !isMoving, wasMoving {
+ world.playSound(.wallThud, at: position)
+}
+```
+
+Run the game again. The doors should now open and close with a swiping sound, the end-of-level switch will click, and the push-wall will start moving with a scraping of stone on stone before finally coming to a halt with a satisfying thud.
+
+The scraping sound when the push-wall first starts moving sounds pretty good, but it doesn't persist for the whole time that the wall is moving, making the last leg of its journey seem oddly silent. We'll come back to this problem later, but there's actually a much bigger issue to solve first.
+
+### Unsound Engineering
+
+Try walking up to the push-wall and bumping into it again after it has stopped moving. You may be surprised to hear a sudden chorus of grinding noises start emanating from the block as soon as you touch it. What's causing that?
+
+The way that the push-wall handles collisions means that even if it is wedged against another wall, touching it sets it in motion for a single frame before it comes to rest again. This brief movement is undetectable to the eye, but it's enough to trigger the scraping sound effect, which then plays in its entirety even though the motion has ended.
+
+To fix this we need to alter the movement logic. In `Pushwall.update()`, replace the following code:
+
+```swift
+if isMoving == false, let intersection = world.player.intersection(with: self) {
+ if abs(intersection.x) > abs(intersection.y) {
+ velocity = Vector(x: intersection.x > 0 ? speed : -speed, y: 0)
+ } else {
+ velocity = Vector(x: 0, y: intersection.y > 0 ? speed : -speed)
+ }
+}
+```
+
+with:
+
+```swift
+if isMoving == false, let intersection = world.player.intersection(with: self) {
+ let direction: Vector
+ if abs(intersection.x) > abs(intersection.y) {
+ direction = Vector(x: intersection.x > 0 ? 1 : -1, y: 0)
+ } else {
+ direction = Vector(x: 0, y: intersection.y > 0 ? 1 : -1)
+ }
+ let x = Int(position.x + direction.x), y = Int(position.y + direction.y)
+ if world.map[x, y].isWall == false {
+ velocity = direction * speed
+ }
+}
+```
+
+The new code checks if the push-wall is up against another wall *before* setting it in motion, so the sliding sound is never triggered.
+
+### Silent Protagonist
+
+If you let a zombie get too close it will make short work of you with its claws, but your player avatar dies in silence. The zombie too is oddly stoical, either killing or being killed without a peep. Maybe it's time to add some character sounds?
+
+We'll start with the zombie's death grunt. In `World.swift`, add the following line to the `hurtMonster()` method, just inside the `if monster.isDead {` block:
+
+```swift
+playSound(.monsterDeath, at: monster.position)
+```
+
+In addition to death by gunfire, the monster can also be crushed by the push-wall. We'll add a comedic "SPLAT!" effect for that scenario. Insert the following code below the line you just added:
+
+```swift
+if monster.isStuck(in: self) {
+ playSound(.squelch, at: monster.position)
+}
+```
+
+Now we'll do the same for the player. In `hurtPlayer()`, add the following inside the `if player.isDead {` clause:
+
+```swift
+playSound(.playerDeath, at: player.position)
+if player.isStuck(in: self) {
+ playSound(.squelch, at: player.position)
+}
+```
+
+We have a sound for the player's weapon firing, but not for the monster's attack. In `Monster.swift`, find the `update()` method and add the following code just below the line `world.hurtPlayer(10)`:
+
+```swift
+world.playSound(.monsterSwipe, at: position)
+```
+
+Finally, let's add a growl of surprise when the monster first sets eyes on the player. Still in `Monster.update()`, add the following line inside the `if canSeePlayer(in: world) {` clause in `case .idle:`:
+
+```swift
+world.playSound(.monsterGroan, at: position)
+```
+
+If you run the game now, you will find that the monster growls every time you shoot it. This wasn't intentional - the problem is that whenever the monster is hurt, it resets to the `idle` state and then immediately "sees" the player again.
+
+We can solve this by modifying the monster's state machine slightly so that it remains in `chasing` mode after being shot, and only returns to `idle` when it loses sight of the player. In `case .hurt:` replace the following two lines:
+
+```swift
+state = .idle
+animation = .monsterIdle
+```
+
+with:
+
+```swift
+state = .chasing
+animation = .monsterWalk
+```
+
+If you try the game again you should now find that the zombie only growls once when it first sees you.
+
+**Note:** if the monster's growl has a strange echo, it is probably due to a bug in the original code for [Part 11](Part11.md) that allowed the monster in the neighboring room to see you through the secret push-wall. For details of how to fix this, refer to the [CHANGELOG](../CHANGELOG.md).
+
+### Walk the Walk
+
+We have one last sound effect to add - The player's footsteps.
+
+The footsteps sound we are using is several seconds long. If we play that sound every time the player position is updated (120 times per second) it will sound less like footsteps and more like a hammer drill!
+
+A potential solution would be to take the sound duration into account, and not attempt to play it again until the first playback has completed. To do that then we'd need to store the sound duration on the engine side, which means reading in that information up-front in the platform layer and then passing it to the engine (or just hard-coding it, which is really ugly).
+
+Another option would be for the platform to inform the engine when a given sound has finished playing. That's a bit more elegant, since we already track that event inside the `SoundManager` but there's a third, simpler option:
+
+Instead of thinking of the footsteps as a one-shot effect like the rest of the sounds we've added so far, we can treat them as a continuous, looping audio track that plays from when we start moving until we stop.
+
+With that approach, we just need to make `AVAudioPlayer` play the sound in a loop until we tell it to finish. That's much easier to coordinate than a series of individual play instructions that must be sent with precise timing. It also means we can interrupt the sound part-way through, so it pauses instantly when the player does, instead of playing to completion.
+
+We'll need a way to refer back to an already-playing sound in order to pause it. Because `Sound` is a struct rather than a class, it has no inherent identity, so we can't track a particular sound instance between frames. Even if we were to use a class instead, the player would need to store a reference to the playing `Sound` object indefinitely, which would cause problems later when it comes to serializing the model[[4]](#footnote4).
+
+We could add some kind of unique identifier to each `Sound`, but that is also rather complicated. We don't have any equivalent of Foundation's [NSUUID](https://developer.apple.com/documentation/foundation/nsuuid) in the Swift stdlib, so we'd need an alternative way to allocate unique IDs on-the-fly.
+
+### Change the Channel
+
+In the '90s, sound cards typically had a very limited number of sound [channels](https://en.wikipedia.org/wiki/Sound_card#Sound_channels_and_polyphony), and games would have to carefully juggle effects between the available channels. Modern sound APIs like AVFoundation abstract away the hardware and can play an effectively unlimited number of sounds concurrently, but the concept of channels could still be useful to us.
+
+Instead of trying to keep track of which sound is playing, we can specify that all sounds of a given type should be played in a specific "channel", and when we're done we just clear that channel. These would be *virtual* channels, with no relationship to the underlying hardware - they're just a convenient way to refer a particular sound source without needing to create and track IDs for each sound instance.
+
+In `Sounds.swift`, update the `Sound` struct as follows:
+
+```swift
+public struct Sound {
+ public let name: SoundName?
+ public let channel: Int?
+ public let volume: Double
+ public let pan: Double
+ public let delay: Double
+}
+```
+
+We don't need *every* sound to play in a channel - only looping sounds like the footsteps - so we've made the `channel` property optional. Note that we've made the `name` property optional too, so we can pass `nil` to clear the channel.
+
+In `World.swift`, change the signature of the `playSound()` method as follows:
+
+```swift
+mutating func playSound(_ name: SoundName?, at position: Vector, in channel: Int? = nil) {
+```
+
+Then replace the line:
+
+```swift
+sounds.append(Sound(name: name, volume: volume, pan: pan, delay: delay))
+```
+
+with:
+
+```swift
+sounds.append(Sound(
+ name: name,
+ channel: channel,
+ volume: volume,
+ pan: pan,
+ delay: delay
+))
+```
+
+In `Player.swift`, add the following constant to the `Player` struct:
+
+```swift
+public let soundChannel: Int = 0
+```
+
+We need to be able to track when the player starts and stops moving. The push-wall has an `isMoving` property that would be helpful here. Copy the following code from `Pushwall.swift`:
+
+```swift
+var isMoving: Bool {
+ return velocity.x != 0 || velocity.y != 0
+}
+```
+
+And paste it inside the `Player` extension block, just below the `isDead` property. Next, add the following line at the start of the `Player.update()` method:
+
+```swift
+let wasMoving = isMoving
+```
+
+Then add the following code to the bottom of the method:
+
+```swift
+if isMoving, !wasMoving {
+ world.playSound(.playerWalk, at: position, in: soundChannel)
+} else if !isMoving {
+ world.playSound(nil, at: position, in: soundChannel)
+}
+```
+
+That should be it for the model changes. Now, in the main app target, add this property to the top of the `SoundManager` class:
+
+```swift
+private var channels = [Int: (url: URL, player: AVAudioPlayer)]()
+```
+
+Next, update the signature of the `play()` method as follows:
+
+```swift
+func play(_ url: URL, channel: Int?, volume: Double, pan: Double) throws {
+```
+
+Then add the following block of code to the `play()` method, just below the `let player = try AVAudioPlayer(contentsOf: url)` line:
+
+```swift
+if let channel = channel {
+ channels[channel] = (url, player)
+ player.numberOfLoops = -1
+}
+```
+
+This means that when called with a non-nil channel, the sound will be added to the `channels` dictionary. Since channels will only be used for looping sounds, we also set the `numberOfLoops` to -1, which tells `AVAudioPlayer` to keep playing the sound indefinitely.
+
+Below the `play()` method declaration, add the following two new methods:
+
+```swift
+func clearChannel(_ channel: Int) {
+ channels[channel]?.player.stop()
+ channels[channel] = nil
+}
+
+func clearAll() {
+ channels.keys.forEach(clearChannel)
+}
+```
+
+Finally, in `ViewController.update()`, insert the following line inside `case .loadLevel(let index):`:
+
+```swift
+SoundManager.shared.clearAll()
+```
+
+Then replace the lines:
+
+```swift
+guard let url = sound.name.url else {
+ return
+}
+try? SoundManager.shared.play(
+ url,
+ volume: sound.volume,
+ pan: sound.pan
+)
+```
+
+with:
+
+```swift
+guard let url = sound.name?.url else {
+ if let channel = sound.channel {
+ SoundManager.shared.clearChannel(channel)
+ }
+ return
+}
+try? SoundManager.shared.play(
+ url,
+ channel: sound.channel,
+ volume: sound.volume,
+ pan: sound.pan
+)
+```
+
+Run the game again and you should now hear the player's footsteps as you walk around. Having implemented the channels system, we can now solve another little problem we saw earlier...
+
+### Scraping the Bottom
+
+If you recall, we mentioned that the scraping sound played by the push-wall as it drags along the ground doesn't last for the whole time that it's moving. We now have the means to solve that.
+
+We hard-coded a value for the `soundChannel` in `Player.swift`. We could hard code a different value for `PushWall`, but in principle a level could have more than one push-wall in motion at once, in which case they would need to use different channels.
+
+Instead, we'll make the `World` responsible for allocating the channels at setup time. In `Player.swift` replace the line:
+
+```swift
+public let soundChannel: Int = 0
+```
+
+with:
+
+```swift
+public let soundChannel: Int
+```
+
+Then update `Player.init` to accept the `soundChannel` as a parameter:
+
+```swift
+public init(position: Vector, soundChannel: Int) {
+ ...
+ self.health = 100
+ self.soundChannel = soundChannel
+}
+```
+
+Make the equivalent changes in `Pushwall.swift`:
+
+```swift
+public struct Pushwall: Actor {
+ ...
+ public let tile: Tile
+ public let soundChannel: Int
+
+ public init(position: Vector, tile: Tile, soundChannel: Int) {
+ ...
+ self.tile = tile
+ self.soundChannel = soundChannel
+ }
+}
+```
+
+Then in `Pushwall.update()` replace the lines:
+
+```swift
+if isMoving, !wasMoving {
+ world.playSound(.wallSlide, at: position)
+} else if !isMoving, wasMoving {
+ world.playSound(.wallThud, at: position)
+}
+```
+
+with:
+
+```swift
+if isMoving, !wasMoving {
+ world.playSound(.wallSlide, at: position, in: soundChannel)
+} else if !isMoving {
+ world.playSound(nil, at: position, in: soundChannel)
+ if wasMoving {
+ world.playSound(.wallThud, at: position)
+ }
+}
+```
+
+In `World.swift`, in the `reset()` method, add the following line just below `pushwallCount = 0`:
+
+```swift
+var soundChannel = 0
+```
+
+Replace the line:
+
+```swift
+self.player = Player(position: position)
+```
+
+with:
+
+```swift
+self.player = Player(position: position, soundChannel: soundChannel)
+soundChannel += 1
+```
+
+Then replace:
+
+```swift
+pushwalls[pushwallCount - 1] = Pushwall(position: position, tile: tile)
+```
+
+with:
+
+```swift
+pushwalls[pushwallCount - 1] = Pushwall(
+ position: position,
+ tile: tile,
+ soundChannel: soundChannel
+)
+soundChannel += 1
+```
+
+And finally, replace:
+
+```swift
+pushwalls.append(Pushwall(position: position, tile: tile))
+```
+
+with:
+
+```swift
+pushwalls.append(Pushwall(
+ position: position,
+ tile: tile,
+ soundChannel: soundChannel
+))
+soundChannel += 1
+```
+
+If you try running the game again now and giving the push-wall a shove, you should hopefully hear the scraping noise continue right up until it hits the far wall. You may notice a couple of bugs though, one rather subtle and one not-so subtle.
+
+The subtle bug is that the volume of the wall scraping sound doesn't decrease as the wall gets further away. That's because we only call `playSound()` once with its initial starting position, so the volume is never updated.
+
+The less-subtle bug (which will be apparent if you play the game for a while) is that the footsteps and push-wall sounds sometimes fail to start or stop correctly. It turns out that trying to detect the starting and stopping of motion in the `update()` methods is a bit fragile because object velocities can be modified in multiple places in the code due to collisions, etc.
+
+We can solve both of these bugs in the same way. In `Pushwall.update()`, replace this line:
+
+```swift
+if isMoving, !wasMoving {
+```
+
+with just:
+
+```swift
+if isMoving {
+```
+
+That means that instead of just sending a single play and stop action, the push-wall will send play actions continuously to update volume and pan as it moves.
+
+With the current `SoundManager` implementation that will cause hundreds of copies of the sound to play at once, so we'll need to make some changes to the implementation. In `SoundManager.swift`, update the `preload()` method as follows:
+
+```swift
+func preload(_ url: URL, channel: Int? = nil) throws -> AVAudioPlayer {
+ if let channel = channel, let (oldURL, oldSound) = channels[channel] {
+ if oldURL == url {
+ return oldSound
+ }
+ oldSound.stop()
+ }
+ return try AVAudioPlayer(contentsOf: url)
+}
+```
+
+Then in `SoundManager.play()` replace the line:
+
+```swift
+let player = try AVAudioPlayer(contentsOf: url)
+```
+
+with:
+
+```swift
+let player = try preload(url, channel: channel)
+```
+
+These changes means that the `play()` method will now reuse the existing `AVAudioPlayer` instance for a given channel if the requested sound is already playing, instead of starting a new instance of the same sound.
+
+That should do it. Run the game again and you'll find that the push-wall sound fades as expected, and the glitches are gone.
+
+And that's a wrap for Part 13! In this part we:
+
+* Added sound effects to the game
+* Introduced sound channels to manage long-running sounds
+* Implemented 3D positional audio
+
+In [Part 14](Part14.md) we'll add support for power-ups and multiple weapons.
+
+### Reader Exercises
+
+1. Add another sound effect yourself. What about a "bump" noise when the player runs into a wall?
+
+2. What about background music? Should music be a channel, or a one-shot sound effect? Where is a logical place in the code to play the music?
+
+3. Currently the sound volume is affected only by distance, not obstacles. How would you implement a system where sound emanating from behind a door or wall would sound quieter?
+
+
+
+[[1]](#reference1) The voices were actually members of the Id Software team faking German accents (badly).
+
+[[2]](#reference2) If you are sourcing your own sound effects instead of using the ones from the tutorial, you might wish to comment out all but the first three `SoundName` enum cases for now so you can get on with the tutorial without having to provide every single sound effect up-front.
+
+[[3]](#reference3) If you recall, the vector dot product is a useful method that we added in [Part 11](Part11.md) to help us detect back-facing billboards.
+
+[[4]](#reference4) Suppose we were to pause the game while a sound was playing, then serialize the game state to disk and quit. When we deserialized the state again later, deserialized instances of the same object wouldn't share the same pointer anymore. For this reason it's generally best to avoid relying on pointer-based identity in your data model.
\ No newline at end of file
diff --git a/Tutorial/Part14.md b/Tutorial/Part14.md
new file mode 100644
index 0000000..8d76492
--- /dev/null
+++ b/Tutorial/Part14.md
@@ -0,0 +1,882 @@
+## Part 14: Power-ups and Inventory
+
+In [Part 13](Part13.md) we added sound effects. You can find the complete source code for Part 13 [here](https://github.com/nicklockwood/RetroRampage/archive/Part13.zip).
+
+We'll now return to working on the actual game mechanics, and implement another important gameplay feature...
+
+### Power to The People
+
+As a lone soldier battling a horde of the undead, the odds are against you. You might have the upper hand at first, but eventually the monsters will wear you down until your health runs out, and you finally fall.
+
+But it doesn't have to be this way. What if you could recover your health? Or maybe even become *more* powerful? All of this is possible with pickups or - as they are sometimes known - *power*-ups.
+
+We're going to add a new kind of `Thing` to the level, and this time it is neither an enemy nor an obstacle, but an *ally*. In `Thing.swift` add the following new case to the enum:
+
+```swift
+public enum Thing: Int, Decodable {
+ ...
+ case medkit
+}
+```
+
+Yes, the first type of pickup we'll add is a *medkit*. This will bump the player's health by a few points so they can keep on fighting.
+
+The medkit should have an index of `6` in the `Thing` enum. Open `Levels.json` and add a `6` to the `things` array for the first level:
+
+```swift
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 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
+]
+```
+
+Like most entities in the level, pickups will be a type of `Actor`. Create a new file in the Engine module called `Pickup.swift` with the following contents:
+
+```swift
+public enum PickupType {
+ case medkit
+}
+
+public struct Pickup: Actor {
+ public let type: PickupType
+ public let radius: Double = 0.4
+ public let position: Vector
+
+ public init(type: PickupType, position: Vector) {
+ self.type = type
+ self.position = position
+ }
+}
+
+public extension Pickup {
+ var isDead: Bool { return false }
+}
+```
+
+We'll assume that pickups cannot move or change size, so their position and radius are constant. It's also reasonable to assume all pickups will have similar properties and behavior, so we've added a `type` field so that we can re-use the `Pickup` struct for multiple pickup types.
+
+With the `Monster` type it was important that the radius was small enough to accurately reflect collisions, but large enough that the sprite wouldn't clip into the walls. In this case the pickup will always be centered in its tile, so clipping is not much of a concern. We've chosen a large-ish radius mainly because it will make it easier for the player to, well... *pick it up*.
+
+The medkit pickup looks like this[[1]](#footnote1):
+
+
+
+As always you can draw your own art assets, but if you want to use the one from the tutorial you will find it [here](https://github.com/nicklockwood/RetroRampage/tree/Part14/Source/Rampage/Assets.xcassets/medkit.imageset).
+
+Add the medkit image to XCAssets, then in `Textures.swift` add a new entry to the `Texture` enum:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case medkit
+}
+```
+
+Pickups will be represented as sprites, which means that they will be rendered using a `Billboard` that always faces the player. We already worked out this logic for the `Monster` sprite in [Part 5](Part5.md) so we'll re-use the same implementation.
+
+Add the following code to the `Pickup` extension:
+
+```swift
+public extension Pickup {
+ ...
+
+ var texture: Texture {
+ switch type {
+ case .medkit:
+ return .medkit
+ }
+ }
+
+ func billboard(for ray: Ray) -> Billboard {
+ let plane = ray.direction.orthogonal
+ return Billboard(
+ start: position - plane / 2,
+ direction: plane,
+ length: 1,
+ texture: texture
+ )
+ }
+}
+```
+
+Next, we need to update `World.swift`. Start by adding a `pickups` array to the `World` struct, along with the initialization code:
+
+```swift
+public struct World {
+ public private(set) var map: Tilemap
+ public private(set) var doors: [Door]
+ public private(set) var pushwalls: [Pushwall]
+ public private(set) var switches: [Switch]
+ public private(set) var pickups: [Pickup]
+ public private(set) var monsters: [Monster]
+ public private(set) var player: Player!
+ public private(set) var effects: [Effect]
+ public private(set) var isLevelEnded: Bool
+ private var sounds: [Sound] = []
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.doors = []
+ self.pushwalls = []
+ self.switches = []
+ self.pickups = []
+ self.monsters = []
+ self.effects = []
+ self.isLevelEnded = false
+ reset()
+ }
+}
+```
+
+In order to draw the medkit, we'll need to add pickups to the `sprites` array:
+
+```swift
+var sprites: [Billboard] {
+ let ray = Ray(origin: player.position, direction: player.direction)
+ return monsters.map { $0.billboard(for: ray) } + doors.map { $0.billboard }
+ + pushwalls.flatMap { $0.billboards(facing: player.position) }
+ + pickups.map { $0.billboard(for: ray) }
+}
+```
+
+We'll also need to clear the `pickups` array inside the `reset()` method:
+
+```swift
+mutating func reset() {
+ self.monsters = []
+ self.doors = []
+ self.switches = []
+ self.pickups = []
+ ...
+}
+```
+
+Then, still in `reset()`, add the following case to the switch statement:
+
+```swift
+case .medkit:
+ pickups.append(Pickup(type: .medkit, position: position))
+```
+
+That should be everything needed to display the medkit. Run the game and battle your way through the zombie hoards to the final door. Ah, sweet relief...
+
+![Medkit on the floor](Images/Medkit.png)
+
+Of course, the medkit doesn't actually *do* anything. It doesn't even get in our way, as the collision detection system ignores it. It's nothing more than a ghostly apparition, sitting intangibly on the floor, taunting us with the promise of health restored.
+
+To fix that, we'll need to add some logic to `World.update()`. But what should that logic *do*?
+
+Add the following loop just below the `// Handle collisions` block:
+
+```swift
+// Handle pickups
+for i in (0 ..< pickups.count).reversed() {
+ let pickup = pickups[i]
+ if player.intersection(with: pickup) != nil {
+ pickups.remove(at: i)
+ }
+}
+```
+
+This code will loop through every pickup, and if any of them are intersecting the player then they'll be removed.
+
+The logic is pretty similar to the other update loops, but for one detail: Notice the `.reversed()` in the first line. We're looping through the pickups *backwards*. Can you tell why?
+
+Up until now, all objects in the map have have been permanent. Monsters can die, but they aren't removed when that happens - they just change state. In this case we are *actually removing* the `Pickup` actor from the world when it has been collected.
+
+Mutating an array while you're iterating through it can be dangerous. Once an element has been removed, the subsequent indices no longer match-up with their original elements, and the last index will point past the end of the array.
+
+The simple trick of iterating backwards ensures that the indices affected will always be the ones we have *already visited*, so we avoid a crash.
+
+Merely removing the pickup from the world is not enough - we need the pickup to actually alter the state of the player. Add the following code inside the if statement in the `// Handle pickups` section, just after `pickups.remove(at: i)`:
+
+```swift
+switch pickup.type {
+case .medkit:
+ player.health += 25
+ effects.append(Effect(type: .fadeIn, color: .green, duration: 0.5))
+}
+```
+
+This code will bump the player's health by 25 points. Since we don't currently have a health meter on screen (and it's difficult from a first-person perspective to see what's going on at your feet) we also trigger a fullscreen green flash effect to indicate that the medkit was collected.
+
+Now that the game has audio, we should really add a sound effect for the medkit pickup too. Add the following new case to the `SoundName` enum in `Sounds.swift`, and add the accompanying MP3 file to (which you can find [here](https://github.com/nicklockwood/RetroRampage/tree/Part14/Source/Rampage/Sounds/medkit.mp3)) to the `Sounds` directory in the main project:
+
+```swift
+public enum SoundName: String, CaseIterable {
+ ...
+ case medkit
+}
+```
+
+Back in `World.update()`, in the `// Handle pickups` section, add the following line just below `player.health += 25`:
+
+```swift
+playSound(.medkit, at: pickup.position)
+```
+
+### Arms Race
+
+It's time to add a new weapon to our arsenal. In Wolfenstein 3D the next weapon upgrade from the pistol was an SMG, but in this case we're going to take our inspiration from Doom and add a shotgun.
+
+Besides the pickup sprite, we'll also need new graphics for the weapon in-hand:
+
+
+
+Like the pistol, the shotgun has one idle frame and four firing frames. You can find all the new graphics [here](https://github.com/nicklockwood/RetroRampage/tree/Part14/Source/Rampage/Assets.xcassets/) (or feel free to create your own).
+
+Add the images for the shotgun pickup and firing animation to XCAssets, then in `Textures.swift` extend the `Texture` enum with the additional cases, matching the names of the image assets:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case shotgun
+ case shotgunFire1, shotgunFire2, shotgunFire3, shotgunFire4
+ case shotgunPickup
+}
+```
+
+We'll also need new sound effects. The shotgun has a louder gunshot than the pistol, and we'll need a sound for when we collect the shotgun pickup too. Add these new cases to the `SoundName` enum in `Sounds.swift`. You'll find the MP3 files for the new sounds [here](https://github.com/nicklockwood/RetroRampage/tree/Part14/Source/Rampage/Sounds).
+
+```swift
+public enum SoundName: String, CaseIterable {
+ ...
+ case shotgunFire
+ case shotgunPickup
+}
+```
+
+Currently the pistol behavior is hard-coded in the `Player` type. To add a new weapon we'll need to extract some of that logic. Create a new file in the Engine module called `Weapon.swift` with the following contents:
+
+```swift
+public enum Weapon {
+ case pistol
+ case shotgun
+}
+```
+
+At the bottom of `Player.swift` is an extension on `Animation` that adds constants for the pistol animation sequences:
+
+```swift
+public extension Animation {
+ static let pistolIdle = Animation(frames: [
+ .pistol
+ ], duration: 0)
+ ...
+}
+```
+
+Delete this extension from `Player.swift` and then add a new one at the bottom of `Weapon.swift`:
+
+```swift
+public extension Animation {
+ static let pistolIdle = Animation(frames: [
+ .pistol
+ ], duration: 0)
+ static let pistolFire = Animation(frames: [
+ .pistolFire1,
+ .pistolFire2,
+ .pistolFire3,
+ .pistolFire4,
+ .pistol
+ ], duration: 0.5)
+ static let shotgunIdle = Animation(frames: [
+ .shotgun
+ ], duration: 0)
+ static let shotgunFire = Animation(frames: [
+ .shotgunFire1,
+ .shotgunFire2,
+ .shotgunFire3,
+ .shotgunFire4,
+ .shotgun
+ ], duration: 0.5)
+}
+```
+
+We've now got separate animations and sound effects for both the pistol and shotgun, but how do we associate these with their respective weapons?
+
+Since `Weapon` is an enum, it's awkward to add properties to it directly. We could add computed properties, but multiple computed properties are also a bit of a headache to deal with, as we'd need a switch statement inside each property.
+
+Instead, we'll create a new type to contain the attributes (animations, sounds and other properties) for each weapon type. Add the following extension to `Weapon.swift`, just below the `Weapon` enum definition:
+
+```swift
+public extension Weapon {
+ struct Attributes {
+ let idleAnimation: Animation
+ let fireAnimation: Animation
+ let fireSound: SoundName
+ }
+
+ var attributes: Attributes {
+ switch self {
+ case .pistol:
+ return Attributes(
+ idleAnimation: .pistolIdle,
+ fireAnimation: .pistolFire,
+ fireSound: .pistolFire
+ )
+ case .shotgun:
+ return Attributes(
+ idleAnimation: .shotgunIdle,
+ fireAnimation: .shotgunFire,
+ fireSound: .shotgunFire
+ )
+ }
+ }
+}
+```
+
+Back in `Player.swift`, replace the line:
+
+```swift
+public var animation: Animation = .pistolIdle
+```
+
+with:
+
+```swift
+public private(set) var weapon: Weapon = .shotgun
+public var animation: Animation
+```
+
+Then add the following code to the bottom of `Player.init()`:
+
+```swift
+self.animation = weapon.attributes.idleAnimation
+```
+
+We've made the `weapon` setter private because changing the player's weapon will have other side-effects, such as changing the current animation. We could use a `didSet` handler, but (as mentioned in [Part 12](Part12.md#reference2)) this can lead to hard-to-diagnose bugs when seemingly trivial code has unexpected side-effects.
+
+Instead, we'll use an explicit setter method. Add the following code just below the `canFire` computed var:
+
+```swift
+mutating func setWeapon(_ weapon: Weapon) {
+ self.weapon = weapon
+ self.animation = weapon.attributes.idleAnimation
+}
+```
+
+We'll need to make some changes in the `Player.update()` method too. Replace the lines:
+
+```swift
+animation = .pistolFire
+world.playSound(.pistolFire, at: position)
+```
+
+with:
+
+```swift
+animation = weapon.attributes.fireAnimation
+world.playSound(weapon.attributes.fireSound, at: position)
+```
+
+Then, further down, replace:
+
+```swift
+animation = .pistolIdle
+```
+
+with:
+
+```swift
+animation = weapon.attributes.idleAnimation
+```
+
+Run the game now and you should see something like the screenshot on the left below.
+
+![Distorted shotgun](Images/DistortedShotgun.png)
+
+If you see the screen on the *right* instead, it's due to a bug that was introduced in the original draft of [Part 9](Tutorial/Part9.md) which caused the width and height of bitmaps to be inverted. To fix it, you need to replace the following line in `UIImage+Bitmap.swift`:
+
+```swift
+self.init(height: cgImage.width, pixels: pixels)
+```
+
+with:
+
+```swift
+self.init(height: cgImage.height, pixels: pixels)
+```
+
+Assuming you aren't seeing the corrupted version, you may still be thinking that the shotgun looks a bit *squished* - and you'd be right. Unlike all the other textures we have used up until this point, the shotgun textures are rectangular rather than square.
+
+The logic we originally added in the renderer assumes equal dimensions for the weapon image, hence the distortion. Let's fix that now. In `Renderer.swift`, replace the following lines:
+
+```swift
+// Player weapon
+let screenHeight = Double(bitmap.height)
+bitmap.drawImage(
+ textures[world.player.animation.texture],
+ at: Vector(x: Double(bitmap.width) / 2 - screenHeight / 2, y: 0),
+ size: Vector(x: screenHeight, y: screenHeight)
+)
+```
+
+with:
+
+```swift
+// Player weapon
+let weaponTexture = textures[world.player.animation.texture]
+let aspectRatio = Double(weaponTexture.width) / Double(weaponTexture.height)
+let screenHeight = Double(bitmap.height)
+let weaponWidth = screenHeight * aspectRatio
+bitmap.drawImage(
+ weaponTexture,
+ at: Vector(x: Double(bitmap.width) / 2 - weaponWidth / 2, y: 0),
+ size: Vector(x: weaponWidth, y: screenHeight)
+)
+```
+
+This new code calculates the actual aspect ratio of the weapon graphic instead of assuming a square. The rest of the rendering logic is unchanged. Run the game again and you should see the un-squashed shotgun.
+
+![Un-distorted shotgun](Images/UndistortedShotgun.png)
+
+The shotgun looks and sounds suitably fearsome, but it doesn't really *feel* any more powerful than the pistol. And that's because it *isn't*. We've changed the appearance, but the behavior is exactly the same.
+
+We need to extract some more of the weapon behavior from `Player`. In `Weapon.swift`, add `damage` and `cooldown` properties to the `Weapon.Attributes`:
+
+```swift
+public extension Weapon {
+ struct Attributes {
+ let idleAnimation: Animation
+ let fireAnimation: Animation
+ let fireSound: SoundName
+ let damage: Double
+ let cooldown: Double
+ }
+
+ var attributes: Attributes {
+ switch self {
+ case .pistol:
+ return Attributes(
+ ...
+ fireSound: .pistolFire,
+ damage: 10,
+ cooldown: 0.25
+ )
+ case .shotgun:
+ return Attributes(
+ ...
+ fireSound: .shotgunFire,
+ damage: 50,
+ cooldown: 0.5
+ )
+ }
+ }
+}
+```
+
+In `Player.swift` you can delete the `attackCooldown` property (since it's now defined in the weapon itself). Then in the `Player.canFire` computed var, replace the line:
+
+```swift
+return animation.time >= attackCooldown
+```
+
+with:
+
+```swift
+return animation.time >= weapon.attributes.cooldown
+```
+
+Next, in `Player.update()`, replace the line:
+
+```swift
+world.hurtMonster(at: index, damage: 10)
+```
+
+with:
+
+```swift
+world.hurtMonster(at: index, damage: weapon.attributes.damage)
+```
+
+That's more like it! The shotgun now does enough damage to kill a zombie with one shot. We've also increased the cooldown period to half a second, because you don't really expect a double-barrelled shotgun to have the same firing rate as a semi-automatic pistol.
+
+### Spray and Pray
+
+Although it's more powerful now, the shotgun still doesn't behave quite convincingly. A real shotgun typically[[2]](#footnote2) fires a spray of pellets with each shot, rather than a single bullet. These pellets spread out, doing more damage at closer range and potentially hitting multiple targets at a distance.
+
+We can't really emulate this with a single ray - we need to cast a *spread*. Add two more parameters to `Weapon.Attributes`:
+
+```swift
+public extension Weapon {
+ struct Attributes {
+ ...
+ let projectiles: Int
+ let spread: Double
+ }
+
+ var attributes: Attributes {
+ switch self {
+ case .pistol:
+ return Attributes(
+ ...
+ cooldown: 0.25,
+ projectiles: 1,
+ spread: 0
+ )
+ case .shotgun:
+ return Attributes(
+ ...
+ cooldown: 0.5,
+ projectiles: 5,
+ spread: 0.4
+ )
+ }
+ }
+}
+```
+
+The `projectiles` parameter will be the number of individual bullets or pellets cast by the weapon each time it fires. The `spread` determines the arc across which the bullets will be dispersed.
+
+In `Player.swift`, locate the following block of code in the `update()` method:
+
+```swift
+let ray = Ray(origin: position, direction: direction)
+if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: weapon.attributes.damage)
+ world.playSound(.monsterHit, at: world.monsters[index].position)
+} else {
+ let position = world.hitTest(ray)
+ world.playSound(.ricochet, at: position)
+}
+```
+
+And wrap it in a loop, as follows:
+
+```swift
+let projectiles = weapon.attributes.projectiles
+for _ in 0 ..< projectiles {
+ let ray = Ray(origin: position, direction: direction)
+ if let index = world.pickMonster(ray) {
+ world.hurtMonster(at: index, damage: weapon.attributes.damage)
+ world.playSound(.monsterHit, at: world.monsters[index].position)
+ } else {
+ let position = world.hitTest(ray)
+ world.playSound(.ricochet, at: position)
+ }
+}
+```
+
+This isn't very useful by itself. The shotgun now fires five bullets instead of one, but they'll all be going in the same direction. To spread the bullets out we'll need to apply a random offset to each.
+
+The way that we calculate the offset is similar to the calculation we used in [Part 13](Part13.md#pump-up-the-stereo) to determine the sound pan, but in reverse. Instead of taking a direction vector and converting it to a sine or cosine value, we're going to take the sine value and convert it to a direction vector.
+
+
+
+We can't use the dot product like we used to calculate the sound pan, because the dot product isn't a reversible operation. However we can use Pythagoras's Theorem[[3]](#footnote3) to get the cosine of the angle from the sine:
+
+```swift
+cosine = sqrt(1 - sine * sine)
+```
+
+And with the sine and cosine we can generate a rotation matrix to calculate the bullet direction. Insert the following code just inside the for loop:
+
+```swift
+let spread = weapon.attributes.spread
+let sine = Double.random(in: -spread ... spread)
+let cosine = (1 - sine * sine).squareRoot()
+let rotation = Rotation(sine: sine, cosine: cosine)
+let direction = self.direction.rotated(by: rotation)
+```
+
+We'll also need to divide the total damage by the number of projectiles, otherwise each of pellet of the shotgun will be delivering a fatal blow, making it excessively powerful. Replace the line:
+
+```swift
+world.hurtMonster(at: index, damage: weapon.attributes.damage)
+```
+
+with:
+
+```swift
+let damage = weapon.attributes.damage / Double(projectiles)
+world.hurtMonster(at: index, damage: damage)
+```
+
+If you run the game now you should find that the shotgun works more like a shotgun. Close up, it will usually kill a zombie outright, but at a distance there is a good chance that some of the individual pellets will miss.
+
+Something is wrong with the sound though. The ricochet is louder and more echoey than expected. The problem is that we are now playing the impact sound repeatedly for every projectile in the loop.
+
+We can defer it until after the loop, but which sound do we play? It could be that some of the bullets hit and others miss, so what we really need to do is keep track of collisions inside the loop and then play one or both of the possible impact sounds afterwards.
+
+Just before the for loop, add the following line:
+
+```swift
+var hitPosition, missPosition: Vector?
+```
+
+Now, inside the loop, replace the line:
+
+```swift
+world.playSound(.monsterHit, at: world.monsters[index].position)
+```
+
+with:
+
+```swift
+hitPosition = world.monsters[index].position
+```
+
+Then replace the lines:
+
+```swift
+let position = world.hitTest(ray)
+world.playSound(.ricochet, at: position)
+```
+
+with:
+
+```swift
+missPosition = world.hitTest(ray)
+```
+
+Finally, add the following code after the for loop, but still inside the `if input.isFiring, canFire {` block:
+
+```swift
+if let hitPosition = hitPosition {
+ world.playSound(.monsterHit, at: hitPosition)
+}
+if let missPosition = missPosition {
+ world.playSound(.ricochet, at: missPosition)
+}
+```
+
+Now the impact sounds will only play at most once each, regardless of the number of projectiles.
+
+### Time to Say Goodbye
+
+Now that we've perfected the shotgun, we have to take it away. The point of this exercise was to make the shotgun a *power-up* that the player has to find - it doesn't do much good to just give it to them from the outset.
+
+In `Player.swift`, replace the line:
+
+```swift
+public private(set) var weapon: Weapon = .shotgun
+```
+
+with:
+
+```swift
+public private(set) var weapon: Weapon = .pistol
+```
+
+Now we'll put the shotgun back, but this time in `Pickup` form. We'll start by adding a `shotgun` case to the `Thing` enum in `Things.swift`:
+
+```swift
+public enum Thing: Int, Decodable {
+ ...
+ case shotgun
+}
+```
+
+Then do the same for the `PickupType` enum in `Pickup.swift`:
+
+```swift
+public enum PickupType {
+ case medkit
+ case shotgun
+}
+```
+
+And, further down in the same file, add an extra case to the `Pickup.texture` property:
+
+```swift
+var texture: Texture {
+ switch type {
+ case .medkit:
+ return .medkit
+ case .shotgun:
+ return .shotgunPickup
+ }
+}
+```
+
+In `Levels.json` update the `things` array for the first level by adding a shotgun pickup in the top-right of the map, which corresponds to the second room (the shotgun `Thing` has an index of `7`):
+
+```swift
+"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
+]
+```
+
+We'll also need to update the world logic. In `World.update()`, in the `// Handle pickups` section, add the following case to the switch statement:
+
+```swift
+case .shotgun:
+ player.setWeapon(.shotgun)
+ playSound(.shotgunPickup, at: pickup.position)
+ effects.append(Effect(type: .fadeIn, color: .white, duration: 0.5))
+```
+
+On collision between the player and the pickup, this logic will set the player's weapon to the shotgun, play the `shotgunPickup` sound effect and also flash the screen white (to distinguish from the green flash when picking up a medkit).
+
+Finally, in `World.reset()`, add the following after `case medkit:`:
+
+```swift
+case .shotgun:
+ pickups.append(Pickup(type: .shotgun, position: position))
+```
+
+### Easy Come, Easy Go
+
+Battle through the level to the end and hit the elevator switch. You appear in a new level, but your shotgun doesn't.
+
+The problem is that the player (and their inventory) does not actually persist between levels. When the level is loaded, everything (including the player object) is created afresh from the map JSON.
+
+We don't actually want to preserve the player instance[[4]](#footnote4) between levels, because there are more properties that we need to reset than preserve (position, direction, velocity, sound channel, animation) but we do want the ability to carry over the player's health and inventory between levels.
+
+In `Player.swift`, add the following method just after `setWeapon()`:
+
+```swift
+mutating func inherit(from player: Player) {
+ health = player.health
+ setWeapon(player.weapon)
+}
+```
+
+Then in `World.swift`, update the `setLevel()` method as follows:
+
+```swift
+mutating func setLevel(_ map: Tilemap) {
+ let effects = self.effects
+ let player = self.player!
+ self = World(map: map)
+ self.effects = effects
+ self.player.inherit(from: player)
+}
+```
+
+This logic makes a copy of the current player prior to replacing the world contents, then calls `inherit()` to copy over their health and weapon properties to the new player.
+
+### Artificial Scarcity
+
+Carrying weapons and heath over to the next level seems fairer, but is also makes things a bit too easy. Once you've acquired the shotgun, you'll never lose it again, and you'd have no reason to drop back to the pistol (even if the game offered the option to do so).
+
+In Wolfenstein 3D (as in the real world), weapons eventually run out of bullets. If the shotgun can run out of ammunition then it will restore some of the balance we've lost by keeping the weapon between levels.
+
+In `Weapon.swift` add a new `defaultAmmo` property to `Weapon.Attributes`:
+
+```swift
+public extension Weapon {
+ struct Attributes {
+ ...
+ let spread: Double
+ let defaultAmmo: Double
+ }
+
+ var attributes: Attributes {
+ switch self {
+ case .pistol:
+ return Attributes(
+ ...
+ spread: 0,
+ defaultAmmo: .infinity
+ )
+ case .shotgun:
+ return Attributes(
+ ...
+ spread: 0.4,
+ defaultAmmo: 5
+ )
+ }
+ }
+}
+```
+
+We've given the pistol infinite ammo (since there's nothing to drop down to if it runs out), but the shotgun is now limited to five shots.
+
+In `Player.swift`, just below the line:
+
+```swift
+public private(set) var weapon: Weapon = .pistol
+```
+
+Add:
+
+```swift
+public private(set) var ammo: Double
+```
+
+Then add the following line to the bottom of `Player.init()`:
+
+```swift
+self.ammo = weapon.attributes.defaultAmmo
+```
+
+We don't want the gun to fire if we've run out of ammo, so add a guard statement at the start of the `canFire` computed property:
+
+```swift
+var canFire: Bool {
+ guard ammo > 0 else {
+ return false
+ }
+ ...
+}
+```
+
+Now add the following line to the end of the `Player.setWeapon()` method:
+
+```swift
+ammo = weapon.attributes.defaultAmmo
+```
+
+Changing weapons will now reset the ammo to the default for that weapon. Now add the following line to the end of `Player.inherit()`:
+
+```swift
+ammo = player.ammo
+```
+
+That means the player's ammo will be kept between levels. All that's left is to actually consume ammo when firing. In `Player.update()` just after the line `state = .firing`, add the following:
+
+```swift
+ammo -= 1
+```
+
+Then a few lines below that, inside the switch statement, replace the `break` in `case .idle:` with:
+
+```swift
+if ammo == 0 {
+ setWeapon(.pistol)
+}
+```
+
+Try playing the game now and you should find that after 5 shots, the shotgun is replaced by the pistol again.
+
+And that brings us to the end of Part 14. In this part we:
+
+* Added a medkit pickup to restore the player's health
+* Implemented support for new weapon types, and added a collectable shotgun
+* Added persistent player inventory that carries over between levels
+* Implemented a simple ammunition mechanic, so weapons eventually run out of bullets
+
+In [Part 15](Part15.md) we'll improve the monster IQ by adding *pathfinding*.
+
+### Reader Exercises
+
+1. Picking up a medkit when you're already on more than 75% health currently *overcharges* you to beyond 100%. Can you modify the logic so that your health is capped at 100%? For bonus points, make it so that if you're already at 100% the game won't let you pick up the medkit at all.
+
+2. If you manage to preserve your ammo for long enough to pick up another shotgun, the current behavior is that the ammo is reset to the default for that weapon. Can you modify the logic so that whatever ammo you have left over is kept and added to the total?
+
+3. Try adding a third weapon type to the game (a laser gun would be an easy choice, as it doesn't require any new mechanics). If you were you pick up the shotgun and then pick up the third weapon, it would replace the shotgun in your inventory. Can you modify the code to allow the player to carry multiple weapons at once, each with its own ammo type? Weapons should have an implicit hierarchy so that picking up a more powerful weapon switches to it automatically. When you run out of ammo, the next most powerful weapon in your arsenal should be selected (not necessarily the pistol).
+
+
+
+[[1]](#reference1) You may be wondering why the cross on the medkit is green instead of the more-traditional red? Surprisingly, the red cross on a white background is not a generic medical symbol, but the emblem of the International Committee of the Red Cross, whose lawyers have apparently made the case that its use in videogames [constitutes a violation of the Geneva Conventions](https://www.pcgamer.com/how-the-prison-architect-developers-broke-the-geneva-conventions/).
+
+[[2]](#reference2) Shotguns can actually fire [a wide variety of shell types](https://en.wikipedia.org/wiki/Shotgun_shell) including solid slugs, non-lethal beanbag rounds and incendiary chemicals.
+
+[[3]](#reference3) We've used this at least three times already - I'm not explaining it again!
+
+[[4]](#reference4) This isn't exactly meaningful anyway given that `Player` is a [value type](https://en.wikipedia.org/wiki/Value_type_and_reference_type), but Swift's semantics do distinguish between copying a whole struct vs some of its properties.
diff --git a/Tutorial/Part15.md b/Tutorial/Part15.md
new file mode 100644
index 0000000..8b3c046
--- /dev/null
+++ b/Tutorial/Part15.md
@@ -0,0 +1,900 @@
+## Part 15: Pathfinding
+
+In [Part 14](Part14.md) we added power-ups, including a medkit and a kickass shotgun! You can find the complete source code for Part 14 [here](https://github.com/nicklockwood/RetroRampage/archive/Part14.zip).
+
+In this part we're going to revisit the rather primitive monster AI and see if we can't bump its IQ by a few points...
+
+### A Blunt Instrument
+
+At the start of [Part 4](Part4.md) we switched the project to run in Release mode so that it would actually be playable while testing. In Release mode, Xcode switches on a bunch of compiler optimizations that make the generated code a lot faster, but this comes at a cost of making it much harder to debug because the optimized code structure bears little resemblance to the original source. Variables and functions that you are trying to inspect may be inlined or optimized away completely.
+
+Until now, the algorithms we've been writing have been relatively simple, and it has been possible to debug the app mostly by visually inspecting the game as it runs. But in this chapter we are going to need to implement some rather more complex logic, and doing that without the benefit of the debugger will be painful.
+
+So what can we do? The game is completely unplayable in Debug mode, but is un-debuggable in Release mode. Well, it turns out that switching the *entire app* to Release mode is a rather blunt instrument, because it's mainly the *renderer* that is performance-critical.
+
+Unfortunately, in order to apply optimizations only to the renderer, we will need to extract it into its own module. We already created a separate module for the game engine - now we'll need to do the same again for the rendering logic.
+
+### Parting Ways
+
+**Note:** Extracting a module is not terribly complicated, but it's error-prone and difficult to troubleshoot. If you get stuck, it's fine to skip this section as it's not critical to the features we'll be implementing right now. Just remember to grab the completed project from [here](https://github.com/nicklockwood/RetroRampage/archive/Part15.zip) when you're done.
+
+We'll start by creating a new target. Go to the Targets tab in Xcode and add a new target of type *Framework* called "Renderer". Xcode will automatically link this in to your main app. It will also create a C header file in the Renderer folder called `Renderer.h` which imports Foundation. We aren't going to be using Foundation in the renderer, so you can go ahead and delete `Renderer.h`.
+
+
+
+With the Renderer target selected, go to the Build Phases tab and add Engine as a dependency of the Renderer module. Next, in the Build Settings tab, in the `Swift Compiler - Code Generation` section set `Disable Safety Checks` to `Yes`, and set `Optimization Level` to `Optimize for Speed [-O]`.
+
+
+
+These changes will ensure that any code in the Renderer module will be optimized even if the module is built in Debug mode. Next move the files `Bitmap.swift`, `Textures.swift` and `Renderer.swift` from the Engine module into the Renderer module. Make sure that Target Membership settings for these files (in the right-hand sidebar in Xcode) are all updated correctly.
+
+
+
+Add the following line to the top of each of the files:
+
+```swift
+import Engine
+```
+
+Then, in the `Textures.swift` file, cut (i.e. copy and delete) the `Texture` enum definition:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case crackWall, crackWall2
+ case slimeWall, slimeWall2
+ case door, door2
+ case doorjamb, doorjamb2
+ case floor
+ case crackFloor
+ case ceiling
+ case monster
+ case monsterWalk1, monsterWalk2
+ case monsterScratch1, monsterScratch2, monsterScratch3, monsterScratch4
+ case monsterScratch5, monsterScratch6, monsterScratch7, monsterScratch8
+ case monsterHurt, monsterDeath1, monsterDeath2, monsterDead
+ case pistol
+ case pistolFire1, pistolFire2, pistolFire3, pistolFire4
+ case shotgun
+ case shotgunFire1, shotgunFire2, shotgunFire3, shotgunFire4
+ case shotgunPickup
+ case switch1, switch2, switch3, switch4
+ case elevatorFloor, elevatorCeiling, elevatorSideWall, elevatorBackWall
+ case medkit
+}
+```
+
+Add a new file called `Texture.swift` to the Engine module, and paste the `Texture` enum into it. Still in the Engine module, in `Rect.swift` replace the following line:
+
+```swift
+var min, max: Vector
+```
+
+with:
+
+```swift
+public var min, max: Vector
+```
+
+Next, in the main game module add the following line to `ViewController.swift` and `UIImage+Bitmap.swift`:
+
+```swift
+import Renderer
+```
+
+Add the same import to `RampageTests.swift` in the RampageTests module.
+
+Finally, go to Edit Scheme > Run > Build Configuration and revert the Run mode back to Debug.
+
+
+
+And that *should* be everything. Try running the game again now and it should play as smoothly as before.
+
+**Note:** If the app fails to compile, try a clean build. If it still fails, it's a good idea to try building each module separately in turn to narrow down the problem.
+
+### Out of Sight, Out of Mind
+
+Right now the monster's state is tied closely to whether they can see the player. When the monster sees the player they will start to chase them, then they'll stop again when they lose visual contact.
+
+This isn't very realistic. If you're being chased by a monster and you go round a corner, you'd expect them to follow, not immediately forget that you exist. Let's change it so that once "activated", a monster will chase you wherever you go.
+
+In `Monster.swift`, in the `update()` method, delete the following lines:
+
+```swift
+guard canSeePlayer(in: world) else {
+ state = .idle
+ animation = .monsterIdle
+ velocity = Vector(x: 0, y: 0)
+ break
+}
+```
+
+Then try running the game again.
+
+You may not immediately notice much difference in the monster behavior. The problem is that the ability of the monsters to follow you is significantly hampered by their inability to open doors.
+
+Amusingly though, if you go and stand near the door (but not near enough to open it), the monster will swipe at you *through* the door. The monster's attack logic is purely range-based, and so at close proximity it can attack you even if it can't see you.
+
+We'll fix that problem first. In `Monster.update()`, find the following code inside `case chasing:` :
+
+```swift
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+ lastAttackTime = -attackCooldown
+ velocity = Vector(x: 0, y: 0)
+ break
+}
+```
+
+and wrap it in a `canSeePlayer()` check, as follows:
+
+```swift
+if canSeePlayer(in: world) {
+ if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+ lastAttackTime = -attackCooldown
+ velocity = Vector(x: 0, y: 0)
+ break
+ }
+}
+```
+
+Next, in `Door.swift` find the following line:
+
+```swift
+if world.player.intersection(with: self) != nil {
+```
+
+and replace it with:
+
+```swift
+if world.player.intersection(with: self) != nil ||
+ world.monsters.contains(where: { monster in
+ monster.isDead == false &&
+ monster.intersection(with: self) != nil
+ }) {
+```
+
+The doors will now open if a (living) monster touches them. We'll also make it a little easier for our zombie friends by increasing the thickness of the door collision rectangle. In the computed `Rect.rect` property, replace the line:
+
+```swift
+return Rect(min: position, max: position + direction)
+```
+
+with:
+
+```swift
+let depth = direction.orthogonal * 0.1
+return Rect(min: position + depth, max: position + direction - depth)
+```
+
+This doesn't affect the appearance of the door (which is still rendered as a paper-thin surface), but it increases the collision area by a distance of 0.1 world units in each direction (for a total of thickness 0.2 units).
+
+### Heeeeere's Johnny!
+
+Try running the game again and you should find that the monster follows you through the door.
+
+![Monster can now open the door](Images/MonsterOpensDoor.png)
+
+This still isn't very satisfactory though - if you round a corner after passing through the door then the monster will not follow you through, but will instead try to take the shortest path towards you (through the wall) and get stuck.
+
+The monster's supernatural awareness of the player location is actually a hinderance to its ability to chase you. The monster always tries to walk directly towards the player, wherever they are, even if the way is obstructed.
+
+A more realistic model would be for the monster to retain a memory of where it last saw you, and to head towards that. Because it will always be walking to somewhere that it has (or had) a clear line of sight to, it should ensure that the destination is reachable. Once it gets there, it can attempt to reacquire sight of you and resume the chase.
+
+Add the following property to the `Monster` struct:
+
+```swift
+public private(set) var lastKnownPlayerPosition: Vector?
+```
+
+Then in `Monster.update()`, in `case .chasing:`, just inside `if canSeePlayer(in: world) {`, insert this line:
+
+```swift
+lastKnownPlayerPosition = world.player.position
+```
+
+This ensures the `lastKnownPlayerPosition` vector will always be updated when the monster has eyes on the player.
+
+A few lines further down, replace the line:
+
+```swift
+let direction = world.player.position - position
+```
+
+with:
+
+```swift
+guard let destination = lastKnownPlayerPosition else {
+ break
+}
+let direction = destination - position
+```
+
+The monster will now always walk towards the last place that it saw the player - which is not necessarily the player's current location. Here it will remain until it catches sight of the player once more.
+
+### Crowd Control
+
+Run the game again and you should find the zombies chase you rather more effectively. When you reach the last room, however the logic breaks down. If you pop your head into the room and then back out again, both monsters will start chasing you... and promptly collide inside the door, each blocking the other's path.
+
+![Monsters getting stuck in the door](Images/MonsterBlockage.png)
+
+This kind of traffic jam is an inevitable consequence of the monsters all independently following the same chase routine. So how can we get them to cooperate?
+
+In `Monster.swift`, add a new `blocked` state to the `MonsterState` enum:
+
+```swift
+public enum MonsterState {
+ case idle
+ case chasing
+ case blocked
+ ...
+}
+```
+
+Then in the `Animation` extension in the same file, add a new case after `monsterIdle`:
+
+```swift
+public extension Animation {
+ static let monsterIdle = Animation(frames: [
+ .monster
+ ], duration: 0)
+ static let monsterBlocked = Animation(frames: [
+ .monster
+ ], duration: 1)
+ ...
+}
+```
+
+The blocked animation is basically the same as the idle animation, except for its duration. When it enters the `blocked` state we want the monster to wait for a period before resuming the chase. We don't currently have any way to set a standalone timer in the monster logic, so we'll use the animation's duration as a way to control how long the monster waits in the blocked state.
+
+Add the following case to the switch statement inside `update()`, just after `case .chasing:`:
+
+```swift
+case .blocked:
+ if animation.isCompleted {
+ state = .chasing
+ animation = .monsterWalk
+ }
+```
+
+Then add the following new method just below the `update()` method:
+
+```swift
+func isBlocked(by other: Monster) -> Bool {
+
+}
+```
+
+This will be used to detect if monsters are blocking each other. We'll start by discarding monsters that are dead or inactive, as the former won't get in the way, and the latter can be pushed out of the way if needed. Add the following code to the method body:
+
+```swift
+// Ignore dead or inactive monsters
+if other.isDead || other.state != .chasing {
+ return false
+}
+```
+
+Next we'll check if the other monster is close enough to be a problem. We'll use the sum of the monster radii plus a reasonable buffer distance as the threshold. Add this code:
+
+```swift
+// Ignore if too far away
+let direction = other.position - position
+let distance = direction.length
+if distance > radius + other.radius + 0.5 {
+ return false
+}
+```
+
+If two monsters are in close proximity, one needs to give way to the other. So how do we decide which gets priority?
+
+We'll base it on the direction of movement. If the other monster lies in the same direction we are trying to move then it's blocking us. Add this final line to complete the method:
+
+```swift
+// Is standing in the direction we're moving
+return (direction / distance).dot(velocity / velocity.length) > 0.5
+```
+
+We've used the dot product here to get the cosine of the angle between the normalized direction and velocity. The threshold of 0.5 equates to a 120 degree arc in front of the monster. If `other` is in that arc then we consider it to be blocking.
+
+Now we just need to call this method for every monster in the level. If any monster is blocking us, we'll halt for one second and allow it to pass. In `Monster.update()` find the line:
+
+```swift
+velocity = direction * (speed / direction.length)
+```
+
+and add the following code just after it:
+
+```swift
+if world.monsters.contains(where: isBlocked(by:)) {
+ state = .blocked
+ animation = .monsterBlocked
+ velocity = Vector(x: 0, y: 0)
+}
+```
+
+Try running the game again. The monsters in the last room should no longer block each other.
+
+### Seeing Double
+
+It's quite noticeable (particularly when entering the last room) that the monsters aren't very good at spotting the player. Even when you can clearly see a monster's eye poking out from behind a wall, it doesn't always react to you.
+
+The monsters "see" by projecting a single ray from their center towards the player. If it hits a wall or door before it reaches the player, they are considered to be obscured. This extreme *tunnel vision* means the monster can't see the player at all if the midpoint of either party is obscured.
+
+This is a very low-fidelity view of the world compared to the player's own, where we cast hundreds of rays. It would be too expensive to do this for every monster, but we can compromise by using *two* rays to give the monster binocular vision[[1]](#footnote1).
+
+
+
+To implement this, open `Monster.swift` and update the `canSeePlayer()` method as follows:
+
+```swift
+func canSeePlayer(in world: World) -> Bool {
+ var direction = world.player.position - position
+ let playerDistance = direction.length
+ direction /= playerDistance
+ let orthogonal = direction.orthogonal
+ for offset in [-0.2, 0.2] {
+ let origin = position + orthogonal * offset
+ let ray = Ray(origin: origin, direction: direction)
+ let wallHit = world.hitTest(ray)
+ let wallDistance = (wallHit - position).length
+ if wallDistance > playerDistance {
+ return true
+ }
+ }
+ return false
+}
+```
+
+The ray direction is the same as before, but we now create two rays, each offset by 0.2 world units from the monster's center. This coincides with the position of the eyes in the monster sprite, which means that if you can see either of the monster's eyes, it can probably see you.
+
+### Chart a Course
+
+We've significantly improved the monster's chasing logic, but they still tend to get stuck in door frames. The problem is that the monsters always want to walk to their destination in a straight line, without regard for obstructions.
+
+So what if the monsters could chart the shortest route from their current location to their destination, following the layout of the map, avoiding walls, and approaching doorways head-on instead of at an arbitrary angle?
+
+This process is called [Pathfinding](https://en.wikipedia.org/wiki/Pathfinding), and the best-known of the pathfinding algorithms is the [A* algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm), which we will now implement[[2]](#footnote2).
+
+A* is designed to quickly find the shortest path between two nodes in a [graph](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)). This is a relatively simple problem to solve with brute force (comparing all possible paths and taking the shortest), but solving it this way is incredibly slow, and the time increases exponentially with the number of nodes[[3]](#footnote3).
+
+The algorithm hones in on the optimal solution by using a [best-first search](https://en.wikipedia.org/wiki/Best-first_search), discarding longer paths without having to fully trace them. It is able to do this by using a [heuristic](https://en.wikipedia.org/wiki/Heuristic_(computer_science)) to quickly estimate the approximate distance between nodes.
+
+The exact nature of this heuristic depend on the type of graph being traversed. In our case, the graph is a rectangular grid of tiles, and so we will use the [Manhattan Distance](https://en.wiktionary.org/wiki/Manhattan_distance) for estimating distance. The Manhattan Distance (named in reference to the grid-like nature of New York's streets) is the distance required to travel between two nodes in a grid if you are only permitted to move horizontally or vertically, and assuming that there are no obstacles (of course in reality there *will* be obstacles - that's why it's only an estimate).
+
+Since the pathfinder is a nontrivial[[4]](#footnote4) piece of code, we're going to try to write it in a generalized way so that we can re-use it for other purposes later. In particular, we don't want to make too many assumptions about the nature of the map itself. To that end, we'll use a protocol to decouple the pathfinding logic from the world.
+
+Create a new file in the Engine module called `Pathfinder.swift`, with the following contents:
+
+```swift
+public protocol Graph {
+ associatedtype Node: Hashable
+
+ func nodesConnectedTo(_ node: Node) -> [Node]
+ func estimatedDistance(from a: Node, to b: Node) -> Double
+ func stepDistance(from a: Node, to b: Node) -> Double
+}
+```
+
+This protocol defines the functions that a graph must implement for the pathfinding to work. The `nodesConnectedTo()` method tells the algorithm which nodes (or tiles, in our case) can be reached from a given position. The `estimatedDistance()` function is used to return the approximate distance between nodes. Finally, the `stepDistance()` will be used to return the exact distance[[5]](#footnote5) between two neighboring nodes.
+
+Notice that the protocol has an associated type called `Node`. `Node` is an abstract representation of a point along the path. A nice quality of the A* algorithm is that it doesn't need to know anything much about these nodes at all - they could be a grid coordinate, a 3D vector, or even a postal address. The only requirement is that nodes must conform to the `Hashable` protocol, so they can be stored in a `Set`.
+
+The A* algorithm works by maintaining a list of explored paths and comparing their lengths and the estimated distance remaining to decide which is the best candidate to reach the goal. The paths form an inverted tree structure, where the front (or *head*) of each path is a leaf in the tree, and its tail forms a branch all the way back to the root (the starting point).
+
+
+
+Add the following class to `Pathfinding.swift`:
+
+```swift
+private class Path {
+ let head: Node
+ let tail: Path?
+ let distanceTravelled: Double
+ let totalDistance: Double
+
+ init(head: Node, tail: Path?, stepDistance: Double, remaining: Double) {
+ self.head = head
+ self.tail = tail
+ self.distanceTravelled = (tail?.distanceTravelled ?? 0) + stepDistance
+ self.totalDistance = distanceTravelled + remaining
+ }
+}
+```
+
+The `Path` class represents a path. It has the same structure as a [linked list](https://en.wikipedia.org/wiki/Linked_list), with a head containing its value (a graph node) and an optional tail that's represented by another `Path`. Because `Path` is a class (a reference type), multiple paths can share the same tail instance, which is where the tree structure comes in.
+
+In addition to the `Node`, paths also store `distanceTravelled`, which is the sum of all the steps taken to get from the start to the current head position, and `totalDistance`, which is the sum of the `distanceTravelled` plus the estimated distance remaining.
+
+Add the following computed property to `Path`:
+
+```swift
+var nodes: [Node] {
+ var nodes = [head]
+ var tail = self.tail
+ while let path = tail {
+ nodes.insert(path.head, at: 0)
+ tail = path.tail
+ }
+ return nodes
+}
+```
+
+The `nodes` property iteratively[[6]](#footnote6) gathers the nodes making up the path into an array (for more convenient traversal later).
+
+Next, we'll write the path-finding function itself. Add the following extension to `Pathfinder.swift`:
+
+```swift
+public extension Graph {
+ func findPath(from start: Node, to end: Node) -> [Node] {
+ var visited = Set([start])
+ var paths = [Path(
+ head: start,
+ tail: nil,
+ stepDistance: 0,
+ remaining: estimatedDistance(from: start, to: end)
+ )]
+
+ }
+}
+```
+
+We begin with a single node (the starting point), which we add to the `visited` set. From this node we also create our first `Path` object, which we store in the `paths` array.
+
+Next, we'll set up the main search loop. Add the following code to the `findPath()` method:
+
+```swift
+while let path = paths.popLast() {
+
+}
+
+// Unreachable
+return []
+```
+
+This loop removes the last path from the `paths` array. If the array is empty, it means that we've failed to find a path so we just return an empty array.
+
+The first thing to do inside the loop is check if the goal (the `end` node) has been reached. If it has, we'll return the successful path. Add the following code inside the while loop:
+
+```swift
+// Finish if goal reached
+if path.head == end {
+ return path.nodes
+}
+```
+
+Now for the meat of the algorithm. Add the following code just after the `// Finish if goal reached` block (still inside the while loop):
+
+```swift
+// Get connected nodes
+for node in nodesConnectedTo(path.head) where !visited.contains(node) {
+
+}
+```
+
+This for loop iterates through all nodes connected to the current path head, excluding ones that we've already visited. Add the following code to the loop:
+
+```swift
+visited.insert(node)
+let next = Path(
+ head: node,
+ tail: path,
+ stepDistance: stepDistance(from: path.head, to: node),
+ remaining: estimatedDistance(from: node, to: end)
+)
+```
+
+The first line adds the connected node to the `visited` set so we don't explore it again. We then create a new path called `next` with the current path as its tail. Now we just need to add the `next` path to the `paths` array.
+
+Instead of just appending new paths to the array, we insert them in reverse order of the `totalDistance`, so that the last element is always the shortest candidate. Add the following code to the for loop:
+
+```swift
+// Insert shortest path last
+if let index = paths.firstIndex(where: {
+ $0.totalDistance <= next.totalDistance
+}) {
+ paths.insert(next, at: index)
+} else {
+ paths.append(next)
+}
+```
+
+This is where the *best-first* part of the algorithm comes in. For each step of the while loop, we take the last path in the `paths` array, which is also the one we have estimated to be the shortest.
+
+Now we just need to actually implement the `Graph` protocol. First we must decide on a type to use for `Graph.Node`. Since we are only dealing in grid-aligned positions, we could use the integer tile index, or perhaps create a new type with integer `x` and `y` properties representing the map coordinate, but since our player and monster positions are already specified as vectors it will save a lot of conversions if we use `Vector` for the path nodes too.
+
+All we need to do to use `Vector` as a `Graph.Node` is add `Hashable` conformance. In `Vector.swift` replace the line:
+
+```swift
+public struct Vector: Equatable {
+```
+
+with:
+
+```swift
+public struct Vector: Hashable {
+```
+
+The `Hashable` protocol is a superset of `Equatable`, so there's no need to include both. Also, Swift is able to synthesize `Hashable` conformance automatically for structs whose properties all conform already, so we don't need to do anything else.
+
+Next, open `World.swift` and add the following code to the bottom of the file:
+
+```swift
+extension World: Graph {
+ public typealias Node = Vector
+
+ public func nodesConnectedTo(_ node: Node) -> [Node] {
+
+ }
+
+ public func estimatedDistance(from a: Node, to b: Node) -> Double {
+
+ }
+
+ public func stepDistance(from a: Node, to b: Node) -> Double {
+
+ }
+}
+```
+
+We'll start with the `nodesConnectedTo()` method, which is actually the most complex to implement. If you recall, the purpose of this method is to tell the pathfinding algorithm which nodes are reachable from a given point.
+
+Although monsters can move in any direction in the open, for the purposes of the algorithm we will limit their movement to the horizontal and vertical. Given this, a given node will have a maximum of four possible connections:
+
+
+
+Add the following code to the `nodesConnectedTo()` method:
+
+```swift
+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),
+]
+```
+
+In practice, these nodes may not all be reachable. If one of the neighbors of the tile represented by the current node is a wall, we can't walk to it. In the `nodesConnectedTo()` method, replace the closing `]` with:
+
+```swift
+].filter { node in
+ let x = Int(node.x), y = Int(node.y)
+ return map[x, y].isWall == false
+}
+```
+
+This will filter out nodes that lie on a wall tile. Besides regular walls, monsters also can't walk through push-walls, so we better add a check for those too. Add the following code just below the `isDoor(at:)` method:
+
+```swift
+func pushwall(at x: Int, _ y: Int) -> Pushwall? {
+ return pushwalls.first(where: {
+ Int($0.position.x) == x && Int($0.position.y) == y
+ })
+}
+```
+
+Note that unlike the `isDoor(at:)` and `switch(at:)` implementations, we can't use the `map.things` array for a fast push-wall check because the wall can move from its original position, so we must resort to a linear search using `first(where:)`. It's unlikely that a typical level will contain a very large number of push-walls though, so this shouldn't be too expensive.
+
+Back in the `nodesConnectedTo()` method, replace the line:
+
+```swift
+return map[x, y].isWall == false
+```
+
+with:
+
+```swift
+return map[x, y].isWall == false && pushwall(at: x, y) == nil
+```
+
+That's the connectivity sorted - now we need to implement the other two `Graph` methods.
+
+When moving on a rectangular grid, the distance between any two points (assuming there are no obstacles) is the sum of the horizontal distance between the points plus the vertical distance (the *Manhattan Distance* we mentioned earlier). Add the following code to the `estimatedDistance()` method:
+
+```swift
+return abs(b.x - a.x) + abs(b.y - a.y)
+```
+
+Finally, the `stepDistance()` is the *actual* (not-estimated) distance between the neighboring nodes `a` and `b`. Since our map is a uniform grid of square tiles, the distance between any two points is just one world unit, so the implementation for the `stepDistance()` method is just:
+
+```swift
+return 1
+```
+
+With the pathfinding logic implemented, we need to modify the monster AI to actually use it.
+
+In `Monster.swift`, replace the line:
+
+```swift
+public private(set) var lastKnownPlayerPosition: Vector?
+```
+
+with:
+
+```swift
+public private(set) var path: [Vector] = []
+```
+
+Then, in the `update()` method, replace:
+
+```swift
+lastKnownPlayerPosition = world.player.position
+```
+
+with:
+
+```swift
+path = world.findPath(from: position, to: world.player.position)
+```
+
+and replace:
+
+```swift
+guard let destination = lastKnownPlayerPosition else {
+```
+
+with:
+
+```swift
+guard let destination = path.first else {
+```
+
+The monster will now walk towards the first node in the path instead of directly towards the player's last known location. But what happens when they get there? We need to add some code to detect when the monster has completed the first step in the path, and move on to the next.
+
+Replace the line:
+
+```swift
+velocity = direction * (speed / direction.length)
+```
+
+with:
+
+```swift
+let distance = direction.length
+if distance < 0.1 {
+ path.removeFirst()
+ break
+}
+velocity = direction * (speed / distance)
+```
+
+So now, when the monster is close to the first node in the path, we'll remove that node and break so that on the next call to `update()` they will start to move towards the next node in the path instead.
+
+That should be it. Try running the game and see what happens.
+
+![Monster jogging on the spot](Images/JoggingOnTheSpot.png)
+
+That's weird. The monster seems to be stuck in place, jogging on the spot instead of chasing us. What's going on?
+
+If we add a breakpoint after the path is created and inspect the value, we see that the path has three nodes. The first is the monster's own position, the last is the player position. This is as expected, but there's a logical error in our code:
+
+Since the first node matches the monster position, the `if distance < 0.1` test will pass every time. We remove the first node and then break, but the next time `update()` is called it will recreate the path from the same starting position, so no progress is ever made.
+
+In `Pathfinder.swift`, in the `Path.nodes` computed property, add the following line just before `return nodes`:
+
+```swift
+nodes.removeFirst()
+```
+
+The path returned by `findPath()` will now no longer include the starting point.
+
+Try running the game again and... oh. The monster's still stuck.
+
+### Type Mismatch
+
+The reason this time is a little more subtle - the monster now makes it past the `if distance < 0.1` check, but when the path is recomputed on the next iteration, it's empty.
+
+It turns out we made a mistake by using `Vector` for the `Graph.Node` type. While it does meet the requirement that each node can be represented as a unique value, it's not strict enough because there are multiple distinct `Vector` values that map to a single node.
+
+When the game starts, the player and monster are both centered in their respective tiles, so their positions align with the pathfinding nodes and we are able to plot a path between them. But as soon as the monster moves it is no longer exactly in the center of a tile, which means that its position coordinates are no longer an integer multiple of the player's coordinates, so the `if path.head == end` check in `findPath()` will never pass.
+
+We could calculate tile-centered values for the start and end positions before calling `findPath()`, but that makes the API very fragile. Instead, let's create a dedicated type for use as the `Graph.Node`.
+
+In `World.swift` replace the line:
+
+```swift
+public typealias Node = Vector
+```
+
+with:
+
+```swift
+public struct Node: Hashable {
+ public let x, y: Double
+
+ public init(x: Double, y: Double) {
+ self.x = x.rounded(.down) + 0.5
+ self.y = y.rounded(.down) + 0.5
+ }
+}
+```
+
+This new `Node` type is structurally identical to `Vector`, but its initializer enforces grid alignment, so it's impossible to have a situation when the `start` or `end` parameters are not valid node positions.
+
+For convenience, we'll overload the `World.findPath()` method with a `Vector`-based version:
+
+```swift
+public func findPath(from start: Vector, to end: Vector) -> [Vector] {
+ return findPath(
+ from: Node(x: start.x, y: start.y),
+ to: Node(x: end.x, y: end.y)
+ ).map { node in
+ Vector(x: node.x, y: node.y)
+ }
+}
+```
+
+That saves us having to update any of the code in `Monster.swift`.
+
+Run the game again and you should find that the zombies chase you much more effectively.
+
+### Hear No Evil
+
+Because pathfinding is not limited to line-of-sight, it introduces some interesting new gameplay possibilities. Instead of just being able to see the player, what if monsters could *hear* the player too?
+
+In [Part 13](Part13.md) we introduced a crude form of sound propagation, where volume drops with distance according to the inverse square law. This works well inside a room, but doesn't account for sounds being muffled by walls.
+
+Add the following method to `Monster.swift`, just below the `canSeePlayer()` definition:
+
+```swift
+func canHearPlayer(in world: World) -> Bool {
+ guard world.player.state == .firing else {
+ return false
+ }
+ let path = world.findPath(from: position, to: world.player.position)
+ return path.count < 8
+}
+```
+
+Instead of using the straight-line distance as we did for the volume calculation, `canHearPlayer()` uses pathfinding to simulate sound propagation through the maze. This is still only a crude approximation[[7]](#footnote7), but it allows for greater realism than a distance-based approach (where monsters would be able to hear you just as clearly through a solid wall as through thin air).
+
+In `Monster.update()` replace *both* of the instances of:
+
+```swift
+if canSeePlayer(in: world) {
+```
+
+with:
+
+```swift
+if canSeePlayer(in: world) || canHearPlayer(in: world) {
+```
+
+Try playing the game and you should find that shooting the monster in the first room now attracts the monster in the second room. The problem is that it works a little *too* well, as the monsters in the third room soon follow. We'd expect that the closed door would also muffle the sound a bit, but currently it offers no more resistance to sound than any other tile.
+
+In `World.swift` we currently return `1` as the distance between any pair of nodes. We should ideally return a greater step distance for a closed door than for an open floor tile to reflect the fact that it takes longer to traverse. There is already an `World.isDoor()` method, but this doesn't tell us whether the door in question is closed or not. Add the following new method just below `isDoor()`:
+
+```swift
+func door(at x: Int, _ y: Int) -> Door? {
+ guard isDoor(at: x, y) else {
+ return nil
+ }
+ return doors.first(where: {
+ Int($0.position.x) == x && Int($0.position.y) == y
+ })
+}
+```
+
+Then, in the `stepDistance()` method, add the following code before the return statement:
+
+```swift
+let x = Int(b.x), y = Int(b.y)
+if door(at: x, y)?.state == .closed {
+ return 5
+}
+```
+
+The pathfinding algorithm will now consider a closed door to be equivalent to five floor tiles in terms of the effort needed to cross it. The current implementation of `canHearPlayer()` doesn't take that into account yet though because it's only based on the path *count*.
+
+Computing the total path length is possible, but expensive. The thing is, we already calculated the path length inside the `findPath()` method, but we don't expose it. Trying to make that value public might lead to a rather awkward API, but we don't actually need the value, we just need to know if it exceeds a given threshold.
+
+Open `Pathfinder.swift` and replace the line:
+
+```swift
+func findPath(from start: Node, to end: Node) -> [Node] {
+```
+
+with:
+
+```swift
+func findPath(from start: Node, to end: Node, maxDistance: Double) -> [Node] {
+```
+
+Then insert the following code just before the `// Insert shortest path last` comment:
+
+```swift
+// Skip this node if max distance exceeded
+if next.totalDistance > maxDistance {
+ break
+}
+```
+
+We also need to do the same for the overloaded `findPath()` method in `World.swift`. Replace:
+
+```swift
+public func findPath(from start: Vector, to end: Vector) -> [Vector] {
+ return findPath(
+ from: Node(x: start.x, y: start.y),
+ to: Node(x: end.x, y: end.y)
+ ).map { node in
+ Vector(x: node.x, y: node.y)
+ }
+}
+```
+
+with:
+
+```swift
+public func findPath(
+ from start: Vector,
+ to end: Vector,
+ maxDistance: Double = 50
+) -> [Vector] {
+ return findPath(
+ from: Node(x: start.x, y: start.y),
+ to: Node(x: end.x, y: end.y),
+ maxDistance: maxDistance
+ ).map { node in
+ Vector(x: node.x, y: node.y)
+ }
+}
+```
+
+Notice the default value of 50. Since we've added the `maxDistance` parameter anyway, this seems like a good opportunity to ensure that the monsters don't spend an unbounded time on hunting the player.
+
+Finally, in `Monster.swift`, replace the following lines in `canHearPlayer()`:
+
+```swift
+let path = world.findPath(from: position, to: world.player.position)
+return path.count < 8
+```
+
+with:
+
+```swift
+return world.findPath(
+ from: position,
+ to: world.player.position,
+ maxDistance: 12
+).isEmpty == false
+```
+
+And that brings us to the end of Part 15. In this part we:
+
+* Split the Renderer out into its own module to aid debugging
+* Added monster memory, so it can continue to hunt for the player even after losing sight of them
+* Allowed monsters to open doors, so they can chase you between rooms
+* Added blockage resolution, so monsters don't get in each other's way
+* Gave the monsters binocular vision, so they can peek at you around corners
+* Implemented A* pathfinding, so that monsters don't get stuck against walls and in doorways
+* Made the monsters able to hear and respond to gunfire in the next room
+
+In [Part 16](Part16.md) we'll add a heads-up display (HUD) so the player can get a better sense of what's going on.
+
+### Reader Exercises
+
+Finished the tutorial and hungry for more? Here are some ideas you can try out for yourself:
+
+1. The monster movement can look a bit weird up-close as they zig-zag towards the player along exact horizontal or vertical axes. Can you modify the chasing logic so that when the monsters get close enough they'll just head directly towards the player instead?
+
+2. In Wolfenstein 3D, certain enemies were set to *ambush mode* where they wouldn't come running if they heard the player, but would instead stay in hiding until they made eye contact. These would typically be hidden in alcoves along a corridor, or behind pillars so they could jump out and surprise the player. Can you implement something similar in Retro Rampage?
+
+3. Although the `maxDistance` parameter helps to cap the performance cost of pathfinding, in a worst-case scenario (a large, closed room with no access to the player) even with a maximum path distance of 50 tiles the algorithm could potentially be forced to run thousands of total iterations - and that's *per-monster*, *every frame*. Can you think of a way to modify the pathfinder to work incrementally, so it can be paused and resumed if a monster fails to find a complete path within a given time budget? For bonus points, can you share explored paths between monsters so they don't have to duplicate work?
+
+
+
+[[1]](#reference1) Note that this is "binocular vision" only in the literal sense that the monster has *two eyes*. It has nothing to do with depth perception.
+
+[[2]](#reference2) FYI, Apple includes a nice pathfinding implementation in [GameplayKit](https://developer.apple.com/documentation/gameplaykit).
+
+[[3]](#reference3) In the general case, this problem of finding the shortest path is known as the [Travelling Salesman Problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem) and is a commonly-cited example of an [NP-hard](https://en.wikipedia.org/wiki/NP-hardness) problem, for which there is no fast solution.
+
+[[4]](#reference4) FYI, "nontrivial" is programmer-speak for "makes my head hurt".
+
+[[5]](#reference5) Some implementations use the term "cost" instead of "distance". That's because the *cost* of moving between nodes may depend on more than just their physical separation. For example, moving to a given node might require going uphill, or wading through mud, and so the cost encapsulates qualities that might make a path more *costly* even if it's technically shorter.
+
+[[6]](#reference6) You may be thinking that there is a more elegant way to write this using recursion - and you'd be right - but a simple recursive solution is actually a lot more expensive due to the additional array allocations needed.
+
+[[7]](#reference7) Wolfenstein used an even cruder (if ingenious) system for sound propagation. Each distinct room in a level was assigned a number, and the tiles in that room were tagged with that number in the map data. The connectivity (via doorways) between the numbered rooms was then pre-calculated and stored in a lookup table. The sound of player gunfire could then be propagated very quickly between rooms with open doors.
diff --git a/Tutorial/Part16.md b/Tutorial/Part16.md
new file mode 100644
index 0000000..94e3d6c
--- /dev/null
+++ b/Tutorial/Part16.md
@@ -0,0 +1,730 @@
+## Part 16: Heads-Up Display
+
+In [Part 15](Part15.md) we improved the AI of our monsters, adding pathfinding logic so that they can follow the player around corners and avoid getting stuck in doorways. You can find the complete source code for Part 15 [here](https://github.com/nicklockwood/RetroRampage/archive/Part15.zip).
+
+Now that we have the basic gameplay up and running, it's time to add some in-game UI.
+
+**Note:** A number of bugs have been found and fixed since Part 15 was published. You should make sure that you've applied those fixed before proceeeding. For details, refer to the [CHANGELOG](../CHANGELOG.md).
+
+### Heads Up
+
+Most first-person games feature some kind of [heads-up display](https://en.wikipedia.org/wiki/Head-up_display) (HUD) to assist the player.
+
+Although we're building a retro game, we don't want to ignore *all* the innovations of modern shooters. One of the common visual affordances introduced since the days of Wolfenstein 3D is the [targeting reticle](https://en.wikipedia.org/wiki/Reticle), or *crosshair*, which makes it easier for players to aim.
+
+Go ahead and add a crosshair image to XCAssets. [Here's](https://github.com/nicklockwood/RetroRampage/tree/Part16/Source/Rampage/Assets.xcassets/crosshair.imageset) one I prepared earlier.
+
+Add a new case for the crosshair texture to the `Texture` enum in `Texture.swift` in the Engine module:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case crosshair
+}
+```
+
+Unlike most images in the game, the crosshair will not be textured onto a 3D surface, but actually just drawn flat in the center of the screen on top of everything else. We do have a precedent for this in the code - the player weapon is drawn in a similar way.
+
+Open up `Renderer.swift` and take a look at the `// Player weapon` block near the bottom of the `draw()` method:
+
+```swift
+// Player weapon
+let weaponTexture = textures[world.player.animation.texture]
+let aspectRatio = Double(weaponTexture.width) / Double(weaponTexture.height)
+let screenHeight = Double(bitmap.height)
+let weaponWidth = screenHeight * aspectRatio
+bitmap.drawImage(
+ weaponTexture,
+ at: Vector(x: Double(bitmap.width) / 2 - weaponWidth / 2, y: 0),
+ size: Vector(x: weaponWidth, y: screenHeight)
+)
+```
+
+This logic computes the size and position at which to draw the player weapon so that it fills the center of the display. The code is kind of gross though. Let's add a small affordance to `Bitmap` so we can simplify this down to something we might want to reuse.
+
+In `Bitmap.swift`, add the following computed property at the top of the extension block:
+
+```swift
+public extension {
+ var size: Vector {
+ return Vector(x: Double(width), y: Double(height))
+ }
+
+ ...
+}
+```
+
+Thanks to the operators we already created for doing `Vector` math, exposing `Bitmap` size as a vector means we can simplify the weapon drawing code down to this:
+
+```swift
+// Player weapon
+let weaponTexture = textures[world.player.animation.texture]
+let weaponScale = bitmap.size.y / weaponTexture.size.y
+let weaponSize = weaponTexture.size * weaponScale
+bitmap.drawImage(weaponTexture, at: (bitmap.size - weaponSize) / 2, size: weaponSize)
+```
+
+This is pretty close to what we want for drawing the crosshair, so let's add the code for that now, just below the `// Player weapon` block:
+
+```swift
+// Crosshair
+let crosshair = textures[.crosshair]
+let crosshairScale = bitmap.size.y / crosshair.size.y
+let crosshairSize = crosshair.size * crosshairScale
+bitmap.drawImage(crosshair, at: (bitmap.size - crosshairSize) / 2, size: crosshairSize)
+```
+
+Try running the game and... whoa!
+
+![Excessively-large crosshair](Images/BigCrosshair.png)
+
+OK, so maybe we don't want the *exact* same logic as for the player weapon. The weapon sits at the bottom of a 32x32 image that is scaled to fit the height of the screen. But for the crosshair image we cropped the contents exactly, so stretching it to fill the screen makes it *huge*.
+
+We could just go back and draw the crosshair in the middle of a larger image, but that would be an awful waste of pixels. Instead let's figure out a more appropriate scale at which to draw it.
+
+Game HUDs are typically drawn at 1:1 screen resolution[[1]](#footnote1), but the crosshair is only 10 pixels high - if we draw it at 1:1 size it will be tiny. Since we're going for a low-res pixel-art aesthetic in this game, we'll need to up-scale the HUD graphics a bit.
+
+With pixel art, it's important to have some consistency with the resolution. Although the pixel sizes chosen are often arbitrary (not dictated by the hardware, as they once were), it looks best if things at the same conceptual distance from the player are displayed with the same pixel density.
+
+For the wall graphics and sprites we used a vertical resolution of 16 pixels. For the player weapon we increased this to 32 because (as explained in [Part 8](Part8.md#pistols-at-dawn), even though the weapon is nearer to player than other objects in the world, it better matches up with the *perceived* resolution of the more distant textures.
+
+The HUD is not supposed to represent a physical entity in the world at all, so we can justify a resolution switch so long as the different elements of the HUD are consistent with each other. 32 vertical pixels is rather limiting, as the HUD graphics will need to either be so low-res that they are hard to identify, or so large that they obscure most of the screen.
+
+Instead we'll adopt a virtual resolution of 64 vertical pixels for all HUD graphics. Replace the lines:
+
+```swift
+let crosshairScale = bitmap.size.y / crosshair.size.y
+let crosshairSize = crosshair.size * crosshairScale
+```
+
+with:
+
+```swift
+let hudScale = bitmap.size.y / 64
+let crosshairSize = crosshair.size * hudScale
+```
+
+Now try running the game again and you should see that the crosshair is more reasonably-sized.
+
+![Sensibly-sized crosshair](Images/SensibleCrosshair.png)
+
+### Vital Statistics
+
+The original Wolfenstein 3D displayed the player's health and ammo readouts in an opaque bar along the bottom of the screen, centered by a picture of the player character's grimacing face. While this approach was certainly iconic (it was replicated for both Doom and Quake), it occupied a lot of valuable screen real estate[[2]](#footnote2).
+
+We'll adopt a more modern approach, and place our indicators discretely in the corners of the screen so as to obscure as little of the action as possible.
+
+Let's start with the health indicator, which we'll display as a sequence of hearts. Add a suitable icon to XCAssets (you can use [this one](https://github.com/nicklockwood/RetroRampage/tree/Part16/Source/Rampage/Assets.xcassets/healthIcon.imageset), or draw your own) and then add an entry for it in `Texture.swift`:
+
+```swift
+case healthIcon
+```
+
+Next, in `Renderer.draw()`, add the following code just after `// Crosshair` block:
+
+```swift
+// Health
+let healthIcon = textures[.healthIcon]
+let health = Int(max(0, world.player.health) / 100 * 5)
+for i in 0 ..< health {
+ let offset = Vector(x: healthIcon.size.x * Double(i), y: 0)
+ bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size)
+}
+```
+
+This takes the player health, which is a floating point value in the range 0 - 100 (although it can actually exceed 100 since we didn't add an upper limit), and converts it to an integer value in the range 0 - 5. It then draws that number of heart icons in a loop.
+
+Run the game and let's see how it looks.
+
+![Tiny health indicator](Images/TinyHealthIndicator.png)
+
+Hmm. Well I guess if you hold your nose against the screen and squint you can *sort* of tell it's a health indicator. We'll need to scale it up to match the HUD resolution.
+
+Replace the lines:
+
+```swift
+let offset = Vector(x: healthIcon.size.x * Double(i), y: 0)
+bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size)
+```
+
+with:
+
+```swift
+let offset = Vector(x: healthIcon.size.x * Double(i) * hudScale, y: 0)
+bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size * hudScale)
+```
+
+While we're at it, let's also add one (HUD-scale) pixel of margin around the edge. Modify the line:
+
+```swift
+let offset = Vector(x: healthIcon.size.x * Double(i) * hudScale, y: 0)
+```
+
+so that it reads:
+
+```swift
+let offset = Vector(x: healthIcon.size.x * Double(i) + 1, y: 1) * hudScale
+```
+
+Now run the game again.
+
+![Scaled health indicator](Images/ScaledHealthIndicator.png)
+
+### A Numbers Game
+
+A row of hearts gives a rough idea of your proximity to death, but it's unclear how the same approach will work for the ammo display. What we really want to be able to do is show a *numeric* readout, and that means *text rendering*.
+
+Unicode-compliant, scalable, vector text rendering is an immensely complex field that took modern operating systems decades to figure out. But games in the DOS era still managed to display text - so how did they do it?
+
+The answer is [bitmap fonts](https://en.wikipedia.org/wiki/Computer_font#BITMAP).
+
+In the early '90s, even though Unicode [was already](https://www.unicode.org/history/versionone.html) on the horizon, [ASCII](https://en.wikipedia.org/wiki/ASCII) still dominated[[3]](#footnote3). And since ASCII only runs to a measly 127 characters (fewer, if you ignore the weird stuff like printer control characters), it's quite practical to just store a bitmap of every character glyph at every size you wish to use it.
+
+Since file compatibility wasn't really an issue, many games actually used a custom character set with just alphanumerics and basic punctuation. In some cases they'd also include a few special pictographic characters - an early form of *emoji*. Since we are only interested in numbers for now, we can limit our bitmap font to just the digits 0 - 9.
+
+We could store these ten characters as separate images, but what we'd ideally like to do is bundle them all together in a *sprite sheet* (also known as a [texture atlas](https://en.wikipedia.org/wiki/Texture_atlas)).
+
+A sprite sheet is a collection of distinct images stored together in a single large bitmap. Sprite sheets are a venerable technique that dates back to the early days of videogames, but which are often employed in non-gaming applications too, such as web pages.
+
+The original benefits of sprite sheets were that they avoided the overhead of storing duplicate metadata for each file, as well as being easier to work with and faster to load[[4]](#footnote4).
+
+Later on, with the advent of 3D graphics hardware that required power-of-two sizes for textures, sprite sheets also became a way to avoid wasted graphics memory by packing multiple odd-sized sprites into a single power-of-two square, as well as helping to avoid GPU pipeline stalls caused when switching between different texture targets.
+
+Even with a purely CPU-based renderer like ours, sprite sheets also offer benefits in terms of [memory locality](https://en.wikipedia.org/wiki/Locality_of_reference). By storing all our character glyphs in a single bitmap, we ensure that they'll be close to each other in RAM, so drawing a string composed of multiple characters is less likely to cause an expensive [cache miss](https://en.wikipedia.org/wiki/CPU_cache#Cache_miss).
+
+### Font of Life
+
+Add a new image to XCAssets containing the digits 0 - 9 laid out horizontally in ascending order, with a pixel of space between them, and a transparent background (You can also just use the font image from the tutorial if you like, which can be found [here](https://github.com/nicklockwood/RetroRampage/tree/Part16/Source/Rampage/Assets.xcassets/font.imageset)).
+
+
+
+Add a new `font` case to the enum in `Texture.swift`:
+
+```swift
+case font
+```
+
+Now let's update the health display. Instead of a row of hearts, we'll draw just one heart as an icon to indicate what the display is showing. Replace the lines:
+
+```swift
+// Health
+let healthIcon = textures[.healthIcon]
+let health = Int(max(0, world.player.health) / 100 * 5)
+for i in 0 ..< health {
+ let offset = Vector(x: healthIcon.size.x * Double(i) + 1, y: 1) * hudScale
+ bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size * hudScale)
+}
+```
+
+with:
+
+```swift
+// Health icon
+let healthIcon = textures[.healthIcon]
+var offset = Vector(x: 1, y: 1) * hudScale
+bitmap.drawImage(healthIcon, at: offset, size: healthIcon.size * hudScale)
+offset.x += healthIcon.size.x * hudScale
+```
+
+Next, we need to render the numeric display. We'll start by just drawing just a single digit to test the principle. Add the following code below the lines we just added:
+
+```swift
+// Health
+let font = textures[.font]
+let charSize = Vector(x: font.size.x / 10, y: font.size.y)
+bitmap.drawImage(font, at: offset, size: charSize * hudScale)
+```
+
+We know that the font image contains 10 characters, so the width of each character should be `font.size.x / 10`. Run the game and see how it looks.
+
+![Squashed health text](Images/SquashedText.png)
+
+Ah. So the problem is that now that we're trying to draw the entire font bitmap into the space of a single character. What we need to do is specify a range within the source bitmap that we want to draw.
+
+Normally in a sprite sheet you would specify a source *rectangle*, but since the characters in our font are only spread out horizontally, we can just specify a *range*. In `Bitmap.swift`, replace the `drawImage()` method signature:
+
+```swift
+mutating func drawImage(_ source: Bitmap, at point: Vector, size: Vector) {
+```
+
+with:
+
+```swift
+mutating func drawImage(
+ _ source: Bitmap,
+ xRange: Range? = nil,
+ at point: Vector,
+ size: Vector
+) {
+ let xRange = xRange ?? 0 ..< source.width
+```
+
+This adds a new, optional `xRange` parameter that can be used to select a slice of the source bitmap to display. Next, replace the following lines in the body of the `drawImage()` method:
+
+```swift
+ let stepX = Double(source.width) / size.x
+ for x in max(0, start) ..< min(width, end) {
+ let sourceX = (Double(x) - point.x) * stepX
+ let outputPosition = Vector(x: Double(x), y: point.y)
+ drawColumn(Int(sourceX), of: source, at: outputPosition, height: size.y)
+ }
+```
+
+with:
+
+```swift
+ let stepX = Double(xRange.count) / size.x
+ for x in max(0, start) ..< max(0, start, min(width, end)) {
+ let sourceX = Int(max(0, Double(x) - point.x) * stepX) + xRange.lowerBound
+ let outputPosition = Vector(x: Double(x), y: point.y)
+ drawColumn(sourceX, of: source, at: outputPosition, height: size.y)
+ }
+```
+
+Back in `Renderer.draw()`, in the `// Health` block, replace the line:
+
+```swift
+bitmap.drawImage(font, at: offset, size: charSize * hudScale)
+```
+
+with:
+
+```swift
+let xRange = 0 ..< Int(charSize.x)
+bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale)
+```
+
+Then run the game again.
+
+![Correctly cropped health text](Images/CorrectlyCroppedText.png)
+
+OK, so that works. Now we just need to draw the other digits. Replace the lines:
+
+```swift
+let xRange = 0 ..< Int(charSize.x)
+bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale)
+```
+
+with:
+
+```swift
+let health = Int(max(0, world.player.health))
+for char in String(health) {
+
+}
+```
+
+This code first converts the player's health to a positive integer, then string-ifies the integer and loops over each character. To convert the character to a pixel range within the font bitmap, add the following lines inside the loop:
+
+```swift
+let index = Int(char.asciiValue!) - 48
+let step = Int(charSize.x)
+let xRange = index * step ..< (index + 1) * step
+```
+
+The `index` variable here is the index of the individual character image within the font bitmap (from 0 - 9). We compute it by first taking the ASCII value of the character, then subtracting 48 (the ascii value of the character "0"). We multiply the index by the character's pixel size to get the range.
+
+Add the following lines to complete the loop:
+
+```swift
+bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale)
+offset.x += charSize.x * hudScale
+```
+
+Run the game again and you should see the following.
+
+![Health indicator at full health](Images/FullHealth.png)
+
+### Guns n' Ammo
+
+Since we've put the health indicator in the top-left corner, we'll display the ammo indicator in the top-right. This introduces a bit of a layout challenge though - since we're drawing text at the right-hand edge of the display, we'll need to draw it from right-to left to avoid having to pre-compute the width.
+
+Add the following code beneath the `// Health` block:
+
+```swift
+// Ammunition
+offset.x = bitmap.size.x
+```
+
+That sets the starting offset to the right-hand edge of the screen[[5]](#footnote5). Now add this code:
+
+```swift
+let ammo = Int(max(0, world.player.ammo))
+for char in String(ammo).reversed() {
+ let index = Int(char.asciiValue!) - 48
+ let step = Int(charSize.x)
+ let xRange = index * step ..< (index + 1) * step
+
+}
+```
+
+This is pretty much the same as what we used for drawing the health digits. Note that we're using `String.reversed()` for the loop though, so that we step through the characters backwards.
+
+Add the following to complete the loop:
+
+```swift
+offset.x -= charSize.x * hudScale
+bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale)
+```
+
+This time we're *subtracting* the character width from offset instead of adding it (so that we step backwards), and we're doing so *before* we draw the character glyph, instead of after it.
+
+Try running the game again.
+
+![A whole lot of ammo](Images/InfiniteAmmo.png)
+
+Huh. What happened there?
+
+Since there' no way to pick up new pistol ammo in the game, we gave it infinite ammo by default. Trying to cast infinity to an Int would normally crash in Swift, but because we turned off safety checks in the Renderer module, it's just truncating the value to `2^63` (the largest positive value for a 64 bit signed integer).
+
+That's not a very helpful way to indicate limitless ammo, so let's truncate to a slightly smaller value instead. Replace the line:
+
+```swift
+let ammo = Int(max(0, world.player.ammo))
+```
+
+with:
+
+```swift
+let ammo = Int(max(0, min(99, world.player.ammo)))
+```
+
+Run the game again, and you should see a rather more reasonable result.
+
+![Ammo count truncated to 99](Images/99Bullets.png)
+
+Finally, let's add a weapon icon to make it clear what the ammo indicator is showing.
+
+We currently have two weapons in the game, so let's go ahead and add suitable icons for both of those. You can download the icons used in the project [here](https://github.com/nicklockwood/RetroRampage/tree/Part16/Source/Rampage/Assets.xcassets/pistolIcon.imageset) and [here](https://github.com/nicklockwood/RetroRampage/tree/Part16/Source/Rampage/Assets.xcassets/shotgunIcon.imageset).
+
+
+
+Add cases for both of these new icons to the enum in `Texture.swift`:
+
+```swift
+case pistolIcon, shotgunIcon
+```
+
+Rather than hard-coding the weapon icons in the Renderer, it would make more sense to add the icon to the existing weapon metadata in the Engine. In `Weapon.swift`, add a `hudIcon` property to the `Weapon.Attributes` struct:
+
+```swift
+public extension Weapon {
+ struct Attributes {
+ let idleAnimation: Animation
+ let fireAnimation: Animation
+ ...
+ let defaultAmmo: Double
+ public let hudIcon: Texture
+ }
+
+ var attributes: Attributes {
+ switch self {
+ case .pistol:
+ return Attributes(
+ ...
+ defaultAmmo: .infinity,
+ hudIcon: .pistolIcon
+ )
+ case .shotgun:
+ return Attributes(
+ ...
+ defaultAmmo: 5,
+ hudIcon: .shotgunIcon
+ )
+ }
+ }
+}
+```
+
+Then, back in `Renderer.draw()`, add the following code below the `// Ammunition` block to complete the HUD:
+
+```swift
+// Weapon icon
+let weaponIcon = textures[world.player.weapon.attributes.hudIcon]
+offset.x -= weaponIcon.size.x * hudScale
+bitmap.drawImage(weaponIcon, at: offset, size: weaponIcon.size * hudScale)
+```
+
+Run the game again and you should see your current weapon displayed just to the left of the ammo indicator.
+
+![Weapon icon displayed next to ammo indicator](Images/WeaponIcon.png)
+
+### Playing it Safe
+
+The HUD looks pretty good in these screenshots, but on a real device it's a slightly different story.
+
+![HUD corners obscured by iPhone bezel](Images/ObscuredCorners.png)
+
+The HUD corners are slightly obscured by the rounded corners of the iPhone. We could just inset the indicators by a few pixels, but then the HUD wouldn't look right on devices without a bezel, such as older iPhone or iPads.
+
+What we should really do here is take the iPhone's [safe area](https://en.wikipedia.org/wiki/Safe_area_%28television%29) into account. Open `Renderer.swift` and add the following property to the `Renderer` struct:
+
+```swift
+public var safeArea: Rect
+```
+
+Then in `Renderer.init()`, add this line:
+
+```swift
+self.safeArea = Rect(min: Vector(x: 0, y: 0), max: bitmap.size)
+```
+
+This initializes the safe area to match the bitmap size by default. Renderer doesn't know what the safe area actually is since it has no awareness of the host operating system or hardware. We'll need to pass this information in from the platform layer.
+
+In `ViewController.swift` find the following line inside the `update()` method:
+
+```swift
+var renderer = Renderer(width: width, height: height, textures: textures)
+```
+
+Just below this line, add the following code:
+
+```swift
+let insets = self.view.safeAreaInsets
+renderer.safeArea = Rect(
+ min: Vector(x: Double(insets.left), y: Double(insets.top)),
+ max: renderer.bitmap.size - Vector(x: Double(insets.left), y: Double(insets.bottom))
+)
+```
+
+Now that we have access to the safe area in `Renderer`, we need to take it into account in our HUD layout. In `Renderer.draw()`, replace the following line in the `// Health icon` block:
+
+```swift
+var offset = Vector(x: 1, y: 1) * hudScale
+```
+
+with:
+
+```swift
+var offset = safeArea.min + Vector(x: 1, y: 1) * hudScale
+```
+
+Next, replace this line in the `// Ammunition` block:
+
+```swift
+offset.x = bitmap.size.x
+```
+
+with:
+
+```swift
+offset.x = safeArea.max.x
+```
+
+If we run the game again, we see that the HUD is no longer obscured.
+
+![HUD corners no longer obscured](Images/InsetCorners.png)
+
+### Portrait of a Killer
+
+Things were so easy in the VGA days, when everything ran full-screen, and screens were all 4:3 with a fixed resolution. But in this modern era we have to cope with all kinds of screen resolutions and aspect ratios, and on a phone we can't even rely on the screen having landscape orientation.
+
+The liquid layout of our HUD drawing code means it can cope with a lot of variation in screen dimensions, but it's not *infinitely* flexible, and portrait looks... well.
+
+
+
+Let's disable portrait layout in the app target settings.
+
+![Disabling portrait mode and status bar](Images/DisablingPortraitMode.png)
+
+While we're at it, we may as well hide the status bar as well[[6]](#footnote6). Add the following code to `ViewController` to ensure that it never appears:
+
+```swift
+override var prefersStatusBarHidden: Bool {
+ return true
+}
+```
+
+### Colorful Writing
+
+We can improve the legibility of the indicator text with a splash of color. But since the text is drawn using images, how can we color it?
+
+A simple solution would be to add multiple bitmap fonts - one for each color. But a more interesting option is to tint the text by blending the image pixels with another color at runtime.
+
+Images are drawn using the `Bitmap.drawImage()` method, which itself calls `Bitmap.drawColumn()` to actually draw the individual pixels. We'll need to update both methods, so let's start with the innermost one.
+
+Open `Bitmap.swift` and replace the `drawColumn()` method declaration:
+
+```swift
+mutating func drawColumn(_ sourceX: Int, of source: Bitmap, at point: Vector, height: Double) {
+```
+
+with:
+
+```swift
+mutating func drawColumn(
+ _ sourceX: Int,
+ of source: Bitmap,
+ at point: Vector,
+ height: Double,
+ tint: Color? = nil
+) {
+```
+
+So how do we combine the tint color with the bitmap? The tinting is applied per-pixel, or in fact per-*component*. We'll multiply each component (red, green, blue and alpha) of the source color by the equivalent component of the tint color.
+
+Since color components are stored as a `UInt8`, the maximum value of any color component is 255, however the result of multiplying two components together will be a value in the range 0 - 65,025, so we'll need to cast the values to UInt16 to avoid an overflow.
+
+We'll then divide the result by 255 again to get a suitable value to store back in the color. Using the red component as an example, the resulting code looks like this:
+
+```swift
+sourceColor.r = UInt8(UInt16(sourceColor.r) * UInt16(tint.r) / 255)
+```
+
+We made the `tint` parameter optional and defaulted it to `nil`. We could technically just default to white, since the result of tinting a color with white is always the original color, but actually performing the multiplications is expensive, so we'd prefer to skip them when it isn't needed.
+
+The `drawColumn()` method contains two instances of the line:
+
+```swift
+let sourceColor = source[sourceX, Int(sourceY)]
+```
+
+Replace them both with:
+
+```swift
+var sourceColor = source[sourceX, Int(sourceY)]
+if let tint = tint {
+ sourceColor.r = UInt8(UInt16(sourceColor.r) * UInt16(tint.r) / 255)
+ sourceColor.g = UInt8(UInt16(sourceColor.g) * UInt16(tint.g) / 255)
+ sourceColor.b = UInt8(UInt16(sourceColor.b) * UInt16(tint.b) / 255)
+ sourceColor.a = UInt8(UInt16(sourceColor.a) * UInt16(tint.a) / 255)
+}
+```
+
+Next, we need to add the `tint` parameter to the `drawImage()` method as well. The resultant method signature should look like this:
+
+```swift
+mutating func drawImage(
+ _ source: Bitmap,
+ xRange: Range? = nil,
+ at point: Vector,
+ size: Vector,
+ tint: Color? = nil
+) {
+```
+
+Since all the heavy lifting is actually done by the `drawColumn()` method, the only change we need to make to the body of `drawImage()` is to replace the line:
+
+```swift
+drawColumn(sourceX, of: source, at: outputPosition, height: size.y)
+```
+
+with:
+
+```swift
+drawColumn(sourceX, of: source, at: outputPosition, height: size.y, tint: tint)
+```
+
+### A Green Bill of Health
+
+Now we have the ability to tint images, let's test it out. In `Renderer.draw()`, in the `// Health` block, replace the line:
+
+```swift
+bitmap.drawImage(font, xRange: xRange, at: offset, size: charSize * hudScale)
+```
+
+with:
+
+```swift
+bitmap.drawImage(
+ font,
+ xRange: xRange,
+ at: offset,
+ size: charSize * hudScale,
+ tint: .green
+)
+```
+
+Run the game.
+
+![Green health indicator text](Images/GreenHealth.png)
+
+OK, it's kind of ugly, but the principle works[[7]](#footnote7). Let's swap the hard-coded static colors in the Engine module with ones from the palette we've been using for the game graphics. In `Color.swift` replace the lines:
+
+```swift
+static let gray = Color(r: 192, g: 192, b: 192)
+static let red = Color(r: 255, g: 0, b: 0)
+static let green = Color(r: 0, g: 255, b: 0)
+static let blue = Color(r: 0, g: 0, b: 255)
+```
+
+with:
+
+```swift
+static let red = Color(r: 217, g: 87, b: 99)
+static let green = Color(r: 153, g: 229, b: 80)
+static let yellow = Color(r: 251, g: 242, b: 54)
+```
+
+We'll use green for when the player's health is full, yellow when it hits 40%, and red for 20% and below. Back in `Renderer.draw()`, find the line:
+
+```swift
+let health = Int(max(0, world.player.health))
+```
+
+and insert the following code beneath it:
+
+```swift
+let healthTint: Color
+switch health {
+case ...10:
+ healthTint = .red
+case 10 ... 30:
+ healthTint = .yellow
+default:
+ healthTint = .green
+}
+```
+
+Then in the call to `bitmap.drawImage()` below, replace the line:
+
+```swift
+tint: .green
+```
+
+with:
+
+```swift
+tint: healthTint
+```
+
+Run the game again and you should see the health indicator in a (less-garish) green, which changes to yellow and red as you take damage.
+
+![Health bar turning yellow after significant damage](Images/YellowAlert.png)
+
+And that brings us to the end of Part 16. To recap, in this this part we:
+
+* Added a crosshair so the player can see what they're shooting
+* Added a HUD with health and ammunition indicators
+* Created a very simple text-drawing system and used it to display numbers
+* Handled safe area clipping and disable portrait mode
+* Added dynamic image tinting (I wonder if that will have other uses later...)
+
+In [Part 17](Part17.md) we'll bring our game a bit closer to a polished, shippable product by adding a title screen.
+
+### Reader Exercises
+
+Finished the tutorial and hungry for more? Here are some ideas you can try out for yourself:
+
+1. Can you modify the game to use a different crosshair graphic for each weapon type? Use the logic for `hudIcon` as inspiration.
+
+2. Try implementing a horizontal health bar instead of a numeric health indicator. The drawing functions in `Bitmap` should provide everything you need to implement this using either vector or bitmap graphics.
+
+3. Right now the HUD is non-interactive. How would you go about adding a pause button in the top-right corner? This is a surprisingly difficult problem to solve in a non-hacky way, because the code that handles user input knows nothing about where the button will be drawn on the screen in the Renderer.
+
+
+
+[[1]](#reference1) The chunky, pixelated look of '90s game HUDs was a technical limitation, not a deliberate aesthetic choice.
+
+[[2]](#reference2) One can't help feeling that the main reason for the huge HUDs in early 3D games was to reduce the area of the screen that had to be filled by expensive rendering.
+
+[[3]](#reference3) In the Western world, at any rate.
+
+[[4]](#reference4) Just like with RAM, data on disk is divided into fixed-size blocks or pages, so it's often more efficient to load one large file than multiple tiny ones. It was common in the '90s to bundle all games assets (not just images) into a single archive in order to minimize access time. For similar reasons, care was taken with the [placement of files on media such as CD-ROM or DVD disks](https://docs.microsoft.com/en-us/windows/win32/dxtecharts/optimizing-dvd-performance-for-windows-games).
+
+[[5]](#reference5) If you're wondering why we don't reduce that by one hud pixel so the text doesn't touch the edge of the screen, it's because our bitmap font already has one pixel of trailing space included in the width of each character, so there's no need to apply the spacing twice.
+
+[[6]](#reference6) The status bar isn't visible anyway in landscape mode on iPhone, but on some devices (such as iPads, or iPhones running older iOS versions) it would be.
+
+[[7]](#reference7) If you're wondering why the black shadow of the text isn't also tinted, it's because the RGB components of black are all zero, and zero multiplied by anything is zero. So tinting a pixel that's already black just results in black.
+
diff --git a/Tutorial/Part17.md b/Tutorial/Part17.md
new file mode 100644
index 0000000..5916640
--- /dev/null
+++ b/Tutorial/Part17.md
@@ -0,0 +1,871 @@
+## Part 17: Title Screen
+
+In [Part 16](Part16.md) we added a heads-up display showing the user health and ammo. You can find the complete source code for Part 16 [here](https://github.com/nicklockwood/RetroRampage/archive/Part16.zip).
+
+Now that we have some in-game UI, what about *pre*-game UI? It's time for us to turn our attention to the title screen.
+
+### Working Title
+
+So far, all of the logic in the game has been managed by the game's `World` object. The world is a model of everything that happens inside the environment of a level. We've even extended the world with some objects that arguably fall slightly outside of that scope - such as the `effects` array that models transitions between levels. But incorporating the title screen into the `World` object seems like a step too far.
+
+Let's introduce a new object called `Game`. The `Game` object will manage the higher-level interactions with the game that exist outside of the game world, such as the title screen and menus, leaving `World` to focus on managing what happens in the virtual universe of the game itself.
+
+Add a new file to the Engine module called `Game.swift`, with the following contents:
+
+```swift
+public struct Game {
+ public let levels: [Tilemap]
+ public private(set) var world: World
+
+ public init(levels: [Tilemap]) {
+ self.levels = levels
+ self.world = World(map: levels[0])
+ }
+}
+```
+
+We've made a lot of use of state machines in the game so far, for managing things like monster AI or animations. But we can think of the *entire* game as a sort of state machine, which is either playing (when the game is running, and the player is interacting with the world) or not (such as when we are looking at the title screen, and the world doesn't really exist yet).
+
+Add the following enum to the top of the `Game.swift` file:
+
+```swift
+public enum GameState {
+ case title
+ case playing
+}
+```
+
+Then add a `state` property to the game object itself:
+
+```swift
+public struct Game {
+ public let levels: [Tilemap]
+ public private(set) var world: World
+ public private(set) var state: GameState = .title
+}
+```
+
+Since `World` is now wrapped inside `Game`, we will need to proxy the communications between `ViewController` and `World`. This is an opportunity to bring some of the increasingly complex logic inside `ViewController.update()` out of the platform layer and into the game engine itself.
+
+In [Part 12](Part12.md#at-your-command), we added the `WorldAction` enum as a way to pass messages back from `World` to the platform layer. If you recall, we couldn't use the traditional delegation pattern because of the *overlapping access* problem, but that issue doesn't apply to communications between `ViewController` and `Game`, as it doesn't have the same mutation constraints.
+
+Add the following protocol declaration to the top of the `Game.swift` file:
+
+```swift
+public protocol GameDelegate: AnyObject {
+ func playSound(_ sound: Sound)
+ func clearSounds()
+}
+```
+
+Next, add a new property to the top of the `Game` struct:
+
+```swift
+public weak var delegate: GameDelegate?
+```
+
+Then add the following code at the bottom of the `Game.swift` file:
+
+```swift
+public extension Game {
+ mutating func update(timeStep: Double, input: Input) {
+ guard let delegate = delegate else {
+ return
+ }
+
+ // Update state
+ switch state {
+ case .title:
+
+ case .playing:
+
+ }
+ }
+}
+```
+
+To keep things simple, when `Game` is in the `title` state we'll just wait until the player presses fire to begin. Add the following code inside `case .title:`:
+
+```swift
+if input.isFiring {
+ state = .playing
+}
+```
+
+Once the game enters the `playing` state, the `World` object will take over. But since `World` is now wrapped inside `Game`, we will need to proxy its communications with `ViewController`. This is an opportunity to bring some of the increasingly complex logic inside `ViewController.update()` out of the platform layer and into the game engine itself.
+
+Add the following lines inside `case .playing:`:
+
+```swift
+if let action = world.update(timeStep: timeStep, input: input) {
+ switch action {
+ case .loadLevel(let index):
+ let index = index % levels.count
+ world.setLevel(levels[index])
+ delegate.clearSounds()
+ case .playSounds(let sounds):
+ sounds.forEach(delegate.playSound)
+ }
+}
+```
+
+Now we'll need to make some changes in `ViewController.swift` to actually make use of the `Game` object. Start by adding the following code to the bottom of the file:
+
+```swift
+extension ViewController: GameDelegate {
+ func playSound(_ sound: Sound) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + sound.delay) {
+ guard let url = sound.name?.url else {
+ if let channel = sound.channel {
+ SoundManager.shared.clearChannel(channel)
+ }
+ return
+ }
+ try? SoundManager.shared.play(
+ url,
+ channel: sound.channel,
+ volume: sound.volume,
+ pan: sound.pan
+ )
+ }
+ }
+
+ func clearSounds() {
+ SoundManager.shared.clearAll()
+ }
+}
+```
+
+This conforms `ViewController` to the `GameDelegate` protocol. Next, replace the following line in `ViewController`:
+
+```swift
+private let levels = loadLevels()
+private lazy var world = World(map: levels[0])
+```
+
+with:
+
+```swift
+private var game = Game(levels: loadLevels())
+```
+
+We'll need to set `self` as the game delegate, but we can't do it here because `self` isn't available yet. Instead, add the following line to the end of the `viewDidLoad()` method:
+
+```swift
+game.delegate = self
+```
+
+We've moved the sound playback logic into the delegate methods, and the level management into `Game.update()`, so now we can replace the following, complicated block of code in `ViewController.update()`:
+
+```swift
+if let action = world.update(timeStep: timeStep / worldSteps, input: input) {
+ switch action {
+ case .loadLevel(let index):
+ SoundManager.shared.clearAll()
+ let index = index % levels.count
+ world.setLevel(levels[index])
+ case .playSounds(let sounds):
+ for sound in sounds {
+ DispatchQueue.main.asyncAfter(deadline: .now() + sound.delay) {
+ guard let url = sound.name?.url else {
+ if let channel = sound.channel {
+ SoundManager.shared.clearChannel(channel)
+ }
+ return
+ }
+ try? SoundManager.shared.play(
+ url,
+ channel: sound.channel,
+ volume: sound.volume,
+ pan: sound.pan
+ )
+ }
+ }
+ }
+}
+```
+
+with just:
+
+```swift
+game.update(timeStep: timeStep / worldSteps, input: input)
+```
+
+You'll also need to replace any remaining references to `world` with `game.world`.
+
+Now try running the game. You should see the same starting room as always, but with one small difference: The monster doesn't attack - and won't - until you tap the screen[[1]](#footnote1).
+
+The title state logic *is* working, it's just behaving more like a pause function than a title screen right now. To actually implement a separate screen, we'll need to update `Renderer`. But first, we'll need to add some new image assets.
+
+### Screen Painting
+
+In the DOS era, when the relatime graphical capabilities of the hardware could not yet match up to the aspirations of game artists, it was common to use a lavish, hand-painted backdrop for the title screen to establish atmosphere and fire the imagination of the player. For the background of the Retro Rampage title screen, I decided to use a screenshot of the game instead[[2]](#footnote2).
+
+You can draw your own background image, or just use the one from the tutorial project [here](https://github.com/nicklockwood/RetroRampage/tree/Part17/Source/Rampage/Assets.xcassets/titleBackground.imageset), but the way I created it was as follows:
+
+First, comment out the `// Player weapon` section in `Renderer.draw()` and then wander around the level to find a suitable angle to capture[[3]](#footnote3). I used the iPhone 11 Pro Max simulator to generate the widest, highest-resolution image possible[[4]](#footnote4), and also increased the bitmap resolution by 3x to match the native device resolution by replacing this line in `ViewController.swift`:
+
+```swift
+let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
+```
+
+with:
+
+```swift
+let width = Int(imageView.bounds.width) * 3, height = Int(imageView.bounds.height) * 3
+```
+
+To avoid a pill-shaped overlay on the screenshot from the home bar, add the following to `ViewController`:
+
+```swift
+override var prefersHomeIndicatorAutoHidden: Bool {
+ return true
+}
+```
+
+Once you have a suitable image for the background, add it to XCAssets (remember to revert any code changes made for screen capture purposes) then add a new case for it to the `Texture` enum in `Texture.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case titleBackground
+}
+```
+
+We currently have a `draw()` method that accepts an instance of `World`, but since the title screen is not part of the world, we'll need a new method that accepts a `Game` object instead. In `ViewController.swift` replace the following line:
+
+```swift
+renderer.draw(game.world)
+```
+
+with:
+
+```swift
+renderer.draw(game)
+```
+
+Then in `Renderer.swift` add this new method right before the current `draw(_ world:)` method:
+
+```swift
+mutating func draw(_ game: Game) {
+ switch game.state {
+ case .title:
+
+ case .playing:
+ draw(game.world)
+ }
+}
+```
+
+We only have one title background image to use for all devices, which is why we've used the highest resolution image we can, so that it can be cropped or scaled down as needed.
+
+To draw the title screen, we'll need to draw the background image centered in the display, and scaled to match the height of the output bitmap. We actually already have code to do this - the player weapon is drawn using the same logic - so we'll reuse that.
+
+Add the following code to `case .title:` block in `Renderer.draw(_ game:)`:
+
+```swift
+// Background
+let background = textures[.titleBackground]
+let backgroundScale = bitmap.size.y / background.size.y
+let backgroundSize = background.size * backgroundScale
+let backgroundPosition = (bitmap.size - backgroundSize) / 2
+bitmap.drawImage(background, at: backgroundPosition, size: backgroundSize)
+```
+
+Run the game again and you should now see the title screen instead of the game world.
+
+![Title screen background](Images/TitleScreenBackground.png)
+
+### Brand Recognition
+
+The title screen is a bit boring without some sort of text or logo. We could paste the logo directly into the title background image itself, but that would limit our ability to scale it independently of the background for different devices, or to move it around to make way for other content. Instead, we'll draw the logo at runtime in a separate layer.
+
+Add a suitable logo image to XCAssets and replace the following line in `Texture.swift`:
+
+```swift
+case titleBackground
+```
+
+with:
+
+```swift
+case titleBackground, titleLogo
+```
+
+The logo image used in the tutorial can be found [here](https://github.com/nicklockwood/RetroRampage/tree/Part17/Source/Rampage/Assets.xcassets/titleLogo.imageset), but as always you are free to create your own.
+
+
+
+The logo we've used in the tutorial has dimensions of 176x64. Unlike the weapon or title background images, the logo is not intended to fit either the height or width of the screen, but it also doesn't match the HUD scale. Instead, we will scale the logo to fill approximately half the screen height (if you have used a different size or aspect ratio for your own logo image, you may need to tweak this scale factor).
+
+Add the following code to the `Renderer.draw(_ game:)` method, just below the `// Background` block:
+
+```swift
+// Logo
+let logo = textures[.titleLogo]
+let logoScale = bitmap.size.y / logo.size.y / 2
+let logoSize = logo.size * logoScale
+let logoPosition = (bitmap.size - logoSize) / 2
+bitmap.drawImage(logo, at: logoPosition, size: logoSize)
+```
+
+Run the game again to see the title screen in all its glory!
+
+![Title screen with logo](Images/TitleScreenWithLogo.png)
+
+### Hair Trigger
+
+There's a small bug in the title state logic. When we tap fire to start the game, we also immediately fire *in* the game.
+
+![Accidental discharge](Images/AccidentalDischarge.png)
+
+This problem with the player's gun firing prematurely is caused by a combination of factors. As you may recall, in [Part 2](Part2#baby-steps) we added code to decouple the simulation time step from the rendering frame rate, with the result that the engine actually performs multiple updates for each frame displayed.
+
+Take at the look at the latest iteration of this logic inside `ViewController.update()`:
+
+```swift
+let input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)),
+ isFiring: lastFiredTime > lastFrameTime
+)
+let worldSteps = (timeStep / worldTimeStep).rounded(.up)
+for _ in 0 ..< Int(worldSteps) {
+ game.update(timeStep: timeStep / worldSteps, input: input)
+}
+lastFrameTime = displayLink.timestamp
+```
+
+Note that although `game.update()` is being called multiple times, the `input` is only sampled once per frame. That makes sense, since touch input on iOS is processed synchronously on the main thread, so it isn't going to change between loop iterations. But it does mean that `isFiring` will remain true for a minimum of four game updates after the user taps.
+
+That explains why the player's weapon fires as soon as the game begins. When the user taps, the input is initially routed to the `title` case, which interprets the tap as a request to start the game. But once the state shifts to `playing`, the same input is then routed to the game world on the next update, which interprets it as a request to fire.
+
+Since that shift can happen mid-frame, there's no opportunity for the `isFiring` flag to be reset. We can fix that by resetting it manually after the first update. In `ViewController.update()`, change the line:
+
+```swift
+let input = Input(
+```
+
+to:
+
+```swift
+var input = Input(
+```
+
+Then at the end of the for loop, before the closing brace, insert the following line:
+
+```swift
+input.isFiring = false
+```
+
+This will reset the `isFiring` state after the first iteration, so that it doesn't continue to be set for every subsequent update within the frame.
+
+This *should* solve the problem, but when I tried it on the simulator I found that the weapon still fired sometimes when the game starts, and this turned out to be due to a mismatch between `displayLink.timestamp` and `CACurrentMediaTime()`. It seems that these are not always in sync, and so the `lastFiredTime` is sometimes ahead of the timestamp by more than the frame duration, causing a single tap to register on two successive frames.
+
+To mitigate this, move the line:
+
+```swift
+lastFrameTime = displayLink.timestamp
+```
+
+from below the for loop to just before the line `let worldSteps = (timeStep / worldTimeStep).rounded(.up)`. Then add the following line just beneath it:
+
+```swift
+lastFiredTime = min(lastFiredTime, displayLink.timestamp)
+```
+
+The result should look like this:
+
+```swift
+var input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)),
+ isFiring: lastFiredTime > lastFrameTime
+)
+lastFrameTime = displayLink.timestamp
+lastFiredTime = min(lastFiredTime, lastFrameTime)
+
+let worldSteps = (timeStep / worldTimeStep).rounded(.up)
+for _ in 0 ..< Int(worldSteps) {
+ game.update(timeStep: timeStep / worldSteps, input: input)
+ input.isFiring = false
+}
+```
+
+### Fade to Black
+
+The transition from title screen to game is rather sudden. It would be nice if we could cover this with a gentle crossfade, as we do with level transitions.
+
+The logic for crossfading is managed using the `effects` array inside `World`, but this isn't accessible externally. In any case, it doesn't really make sense for `World` to manage a transition that starts before the game even begins.
+
+Instead we'll put the code for managing crossfades in the `Game` object. In `Game.swift`, add a new case called `starting` to `GameState`:
+
+```swift
+public enum GameState {
+ case title
+ case starting
+ case playing
+}
+```
+
+Then add the following property to the `Game` struct:
+
+```swift
+public private(set) var transition: Effect?
+```
+
+Next, in the `update()` method, replace the lines:
+
+```swift
+case .title:
+ if input.isFiring {
+ state = .playing
+ }
+```
+
+with:
+
+```swift
+case .title:
+ if input.isFiring {
+ transition = Effect(type: .fadeOut, color: .black, duration: 0.5)
+ state = .starting
+ }
+case .starting:
+ if transition?.isCompleted == true {
+ transition = Effect(type: .fadeIn, color: .black, duration: 0.5)
+ state = .playing
+ }
+```
+
+In the same method, insert the following code between the `guard self = self ...` statement and the `// Update state` block:
+
+```swift
+// Update transition
+if var effect = transition {
+ effect.time += timeStep
+ transition = effect
+}
+```
+
+Now we need to actually draw the transition effect. We have the logic to do that already, but it's currently inlined inside the `draw(_ world:)` method, and we now need to use it in `draw(_ game:)` instead, so let's extract it.
+
+In `Renderer.swift`, add the following new method below `draw(_ world:)`:
+
+```swift
+mutating func draw(_ effect: Effect) {
+
+}
+```
+
+Cut (copy and delete) the following block of code from inside the for loop in the `// Effects` block at the bottom of the `draw(_ world:)` method, and paste it into the `draw(_ effect:)` method you just added:
+
+```swift
+switch effect.type {
+case .fadeIn:
+ bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
+case .fadeOut:
+ bitmap.tint(with: effect.color, opacity: effect.progress)
+case .fizzleOut:
+ let threshold = Int(effect.progress * Double(fizzle.count))
+ for x in 0 ..< bitmap.width {
+ for y in 0 ..< bitmap.height {
+ let granularity = 4
+ let index = y / granularity * bitmap.width + x / granularity
+ let fizzledIndex = fizzle[index % fizzle.count]
+ if fizzledIndex <= threshold {
+ bitmap[x, y] = effect.color
+ }
+ }
+ }
+}
+```
+
+Put the following inside the now-empty for loop:
+
+```swift
+draw(effect)
+```
+
+Still in `Renderer.swift`, at the top of the extension block, replace the following line inside the `draw(_ game:)` method:
+
+```swift
+case .title:
+```
+
+with:
+
+```swift
+case .title, .starting:
+```
+
+Then, after the switch statement, add the following:
+
+```swift
+// Transition
+if let effect = game.transition {
+ draw(effect)
+}
+```
+
+Try running the game again. You should see a smooth fade to black and back again after tapping to start, before the game begins.
+
+### Model Citizen
+
+It's not very satisfactory that so much of the HUD logic resides in the Renderer module. The Renderer should only be concerned with *view* logic (i.e. drawing), and things like switching the indicator color based on a damage threshold should really be done in the *model*.
+
+Let's move that code into the Engine module instead by introducing a new type. Add a new file to the Engine module called `HUD.swift` with the following contents:
+
+```swift
+public struct HUD {
+ public let healthString: String
+ public let healthTint: Color
+ public let ammoString: String
+ public let playerWeapon: Texture
+ public let weaponIcon: Texture
+
+ public init(player: Player) {
+ let health = Int(max(0, player.health))
+ switch health {
+ case ...10:
+ self.healthTint = .red
+ case 10 ... 30:
+ self.healthTint = .yellow
+ default:
+ self.healthTint = .green
+ }
+ self.healthString = String(health)
+ self.ammoString = String(Int(max(0, min(99, player.ammo))))
+ self.playerWeapon = player.animation.texture
+ self.weaponIcon = player.weapon.attributes.hudIcon
+ }
+}
+```
+
+The `HUD` type is a simple struct that encapsulates all of the information that the Renderer needs in order to draw the HUD. You can think of it as a [view model](https://en.wikipedia.org/wiki/Model–view–viewmodel). In `Game.swift`, add the following computed property to the top of the extension block, just before the `update()` method:
+
+```swift
+var hud: HUD {
+ return HUD(player: world.player)
+}
+```
+
+In `Renderer.swift` add the following empty method just before the `draw(_ effect:)` definition:
+
+```swift
+mutating func draw(_ hud: HUD) {
+
+}
+```
+
+Now cut (copy and delete) everything in the `draw(_ world:)` method from the start of the `// Player weapon` block up until `// Effects`, and paste it inside the `draw(_ hud:)` method you just added.
+
+And what of the `// Effects` block? We can't move it to `draw(_ hud:)` because it's not part of the HUD, but we also can't leave it inside `draw(_ world:)` since the effects need to be drawn *in front* of the HUD. So just delete the entire `// Effects` block for now.
+
+Inside `draw(_ hud:)` we now have several broken references to `world`. We'll fix those as follows:
+
+In the `// Player weapon` block, replace the line:
+
+```swift
+let weaponTexture = textures[world.player.animation.texture]
+```
+
+with:
+
+```swift
+let weaponTexture = textures[hud.playerWeapon]
+```
+
+Then in the `// Health` block, replace the following lines:
+
+```swift
+let health = Int(max(0, world.player.health))
+let healthTint: Color
+switch health {
+case ...10:
+ healthTint = .red
+case 10 ... 30:
+ healthTint = .yellow
+default:
+ healthTint = .green
+}
+for char in String(health) {
+```
+
+with:
+
+```swift
+let healthTint = hud.healthTint
+for char in hud.healthString {
+```
+
+Next, in the `// Ammunition` block, replace:
+
+```swift
+let ammo = Int(max(0, min(99, world.player.ammo)))
+for char in String(ammo).reversed() {
+```
+
+with:
+
+```swift
+for char in hud.ammoString.reversed() {
+```
+
+Then in the `// Weapon icon` block, replace:
+
+```swift
+let weaponIcon = textures[world.player.weapon.attributes.hudIcon]
+```
+
+with:
+
+```swift
+let weaponIcon = textures[hud.weaponIcon]
+```
+
+Finally, at the top of the file, in the `draw(_ game:)` method, add the following lines just after `draw(game.world)`:
+
+```swift
+draw(game.hud)
+
+// Effects
+for effect in game.world.effects {
+ draw(effect)
+}
+```
+
+That's a bit neater, but what does this have to do with the title screen? Well, it puts us in a better position to implement the next step:
+
+### Text Adventure
+
+We've set up the title screen so that pressing fire starts the game. Players will probably figure this out, since it's natural to tap when you aren't sure what else to do, but it would be even better if we actually told them.
+
+Let's add the text "Tap to start" to the title screen. We could cheat and just do this by creating a new image containing the entire phrase, but since we already added a rudimentary text engine in [Part 16](Part16.md), let's extend that to handle letters as well as numbers.
+
+First, we'll need a more complete character set. Replace the existing font asset in XCAssets with [this new one](https://github.com/nicklockwood/RetroRampage/tree/Part17/Source/Rampage/Assets.xcassets/font.imageset).
+
+
+
+You can create your own font image instead if you prefer, but if so ensure that it has at least the full uppercase alphabet and a space character in addition to the digits 0 - 9.
+
+Because the characters in the bitmap font no longer represent a contiguous range of ASCII code points, the trick we used in [Part 16](Part16.md#font-of-life) for mapping characters to sprite sheet ranges will no longer work. Instead, let's add a more explicit mapping in the form of a JSON file that defines the characters in the font.
+
+Add a new file to the main app called `Font.json` with the following contents:
+
+```swift
+{
+ "texture": "font",
+ "characters": [
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
+ "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
+ "U", "V", "W", "X", "Y", "Z", " "
+ ]
+}
+```
+
+We'll need an object to represent this data in the game, so add a new file to the Engine module called `Font.swift` with the following contents:
+
+```swift
+public struct Font: Decodable {
+ public let texture: Texture
+ public let characters: [String]
+}
+```
+
+The `Font` struct contains the name of the font texture, along with an ordered list of characters[[5]](#footnote5) that appear in the font.
+
+We've included the font `Texture` name in the same file because it makes sense to keep the image and character list together, but that means we'll need to make `Texture` decodable, so in `Texture.swift` replace the following line:
+
+```swift
+public enum Texture: String, CaseIterable {
+```
+
+with:
+
+```swift
+public enum Texture: String, CaseIterable, Decodable {
+```
+
+In `HUD.swift`, add a `font` property to the `HUD` struct and its initializer:
+
+```swift
+public struct HUD {
+ ...
+ public let font: Font
+
+ public init(player: Player, font: Font) {
+ ...
+ self.font = font
+ }
+}
+
+```
+
+Then do the same in `Game.swift`:
+
+```swift
+public struct Game {
+ ...
+ public let font: Font
+
+ public init(levels: [Tilemap], font: Font) {
+ ...
+ self.font = font
+ }
+}
+```
+
+And while we're here, add the following additional property just below `public let font: Font`:
+
+```swift
+public var titleText = "TAP TO START"
+```
+
+Still in `Games.swift`, in the computed `hud` property, replace the line:
+
+```swift
+return HUD(player: world.player)
+```
+
+with:
+
+```swift
+return HUD(player: world.player, font: font)
+```
+
+Then, in `ViewController.swift`, add the following just below the `loadLevels()` function declaration:
+
+```swift
+public func loadFont() -> Font {
+ let jsonURL = Bundle.main.url(forResource: "Font", withExtension: "json")!
+ let jsonData = try! Data(contentsOf: jsonURL)
+ return try! JSONDecoder().decode(Font.self, from: jsonData)
+}
+```
+
+And replace the line:
+
+```swift
+private var game = Game(levels: loadLevels())
+```
+
+with:
+
+```swift
+private var game = Game(levels: loadLevels(), font: loadFont())
+```
+
+In `Renderer.swift`, in the `draw(_ hud:)` method, we now have access to the font, and so we can replace the hard-coded (and now-broken) character bitmap range calculations. In the `// Health` block, replace the lines:
+
+```swift
+let font = textures[.font]
+let charSize = Vector(x: font.size.x / 10, y: font.size.y)
+```
+
+with:
+
+```swift
+let font = textures[hud.font.texture]
+let charSize = Vector(x: Double(font.width / hud.font.characters.count), y: font.size.y)
+```
+
+Then, inside the for loop, replace the line:
+
+```swift
+let index = Int(char.asciiValue!) - 48
+```
+
+with:
+
+```swift
+let index = hud.font.characters.firstIndex(of: String(char)) ?? 0
+```
+
+Do the same for the equivalent line in the `// Ammunition` block, and if you run the game (and you've done everything right), the HUD text should look exactly the same as before.
+
+So now we just need to actually draw the text on the title screen, and here we run into a slight problem. We avoided pre-calculating the text width in the HUD by just drawing from the left or right edges respectively. But for the title screen we want the text to be centered, so we'll need to know its width.
+
+Since we're using a [monospaced](https://en.wikipedia.org/wiki/Monospaced_font) font, the width of the text is equal to the size of any character multiplied by the number of characters in the string. In `draw(_ game:)`, add the following code just below the `// Logo` block:
+
+```swift
+// Text
+let textScale = bitmap.size.y / 64
+let font = textures[game.font.texture]
+let charSize = Vector(x: Double(font.width / game.font.characters.count), y: font.size.y)
+let textWidth = charSize.x * Double(game.titleText.count) * textScale
+```
+
+This calculates the total width of the title screen text, based on the character size and count.
+
+From that we can derive the starting horizontal offset as the width of the screen minus the textWidth, divided by two. For the vertical offset, we'll use three quarters of the screen height. Add the following line:
+
+```swift
+var offset = Vector(x: (bitmap.size.x - textWidth) / 2, y: bitmap.size.y * 0.75)
+```
+
+Next we need to actually draw the text. The logic for this is the same as the code we already used to draw the player health indicator in the HUD. Add the following lines:
+
+```swift
+for char in game.titleText {
+ let index = game.font.characters.firstIndex(of: String(char)) ?? 0
+ let step = Int(charSize.x)
+ let xRange = index * step ..< (index + 1) * step
+ bitmap.drawImage(
+ font,
+ xRange: xRange,
+ at: offset,
+ size: charSize * textScale,
+ tint: .yellow
+ )
+ offset.x += charSize.x * textScale
+}
+```
+
+We'll also need to move the logo up a bit to allow room. Currently the logo is centered in the screen by this line in the `// Logo` block:
+
+```swift
+let logoPosition = (bitmap.size - logoSize) / 2
+```
+
+Replace this with:
+
+```swift
+let logoPosition = Vector(x: (bitmap.size.x - logoSize.x) / 2, y: bitmap.size.y * 0.15)
+```
+
+Run the game again and you can see the title screen text in all its glory.
+
+![Title screen with text](Images/TitleScreenWithText.png)
+
+And that brings us to the end of Part 17. It's amazing how much more finished a game feels with the addition of even the most basic title screen. To recap, in this this part we:
+
+* Added a new `Game` object to manage higher-level interaction
+* Added a title screen with a background and logo
+* Moved logic out of Renderer and ViewController into Engine
+* Upgraded the font system to support text as well as numbers
+
+Stay tuned for more retro mayhem in Part 18 (TBD).
+
+### Reader Exercises
+
+Finished the tutorial and hungry for more? Here are some ideas you can try out for yourself:
+
+1. How would you go about localizing the "TAP TO START" text on the title screen? You should be able to use the standard Apple mechanism for localized strings, but not from inside the Engine module - you'll need to find a way to inject the text from the platform layer.
+
+2. Building on the solution for the first exercise, how would you go about supporting languages that don't use the Roman alphabet? Hint: Apple's localized resources system works for images and JSON files, not just strings.
+
+3. We've used a monospaced font for the title screen text, but could you make it work with a proportional (aka *variable-width*) font as well? Besides the changes to the font image, you'll need some extra metadata in the `Font` object, and some changes to the `Renderer` itself.
+
+
+
+[[1]](#reference1) If nothing happens when you tap, you probably forgot to set `game.delegate = self`.
+
+[[2]](#reference2) Mostly because I'm a terrible artist, and this was an easy option.
+
+[[3]](#reference3) You might also want to disable the monsters if they interfere with your sightseeing tour. A simple way to do this is to edit the `Levels.json` and remove all the monsters. Alternatively, you can lure them into the first room and kill them (which is what I did).
+
+[[4]](#reference4) If you're reading this in the future on your iPhone Ultra Max Pro Extreme with 360 wraparound 16K VR display, just take my word for it that in 2020 the Pro Max screen seemed like a lot of pixels.
+
+[[5]](#reference5) If you're wondering why the characters are stored as an array of `String`, instead of the more-obvious `Character` type, it's because `Character` does not conform to the `Decodable` protocol. We could write a custom decoding implementation that mapped from strings to characters, but defining font characters using strings potentially gives us more flexibility anyway - for example we could potentially implement multi-character [ligatures](https://en.wikipedia.org/wiki/Orthographic_ligature).
diff --git a/Tutorial/Part2.md b/Tutorial/Part2.md
new file mode 100644
index 0000000..bbcf3fd
--- /dev/null
+++ b/Tutorial/Part2.md
@@ -0,0 +1,700 @@
+## Part 2: Mazes and Motion
+
+In [Part 1](Part1.md) we laid the groundwork for our game, creating a cross-platform graphics engine, a rectangular world with a moving player avatar, and a platform layer for iOS.
+
+We will now build upon the code we created in Part 1, adding a 2D maze, user input and collision handling. If you haven't already done so, I strongly suggest you work through Part 1 and write the code yourself, but if you prefer you can just download the project from [here](https://github.com/nicklockwood/RetroRampage/archive/Part1.zip).
+
+### Amaze Me
+
+Wolfenstein, at its heart, is a 2D maze game built on a 64x64 tile grid. A grid of fixed-size tiles is a little too crude to create realistic architecture, but it's great for making twisting corridors, hidden alcoves and secret chambers. It's also really easy to define in code.
+
+Create a new file in the Engine module called `Tile.swift` with the following contents:
+
+```swift
+public enum Tile: Int, Decodable {
+ case floor
+ case wall
+}
+```
+
+Then, create another file called `Tilemap.swift`:
+
+```swift
+public struct Tilemap: Decodable {
+ private let tiles: [Tile]
+ public let width: Int
+}
+
+public extension Tilemap {
+ var height: Int {
+ return tiles.count / width
+ }
+
+ var size: Vector {
+ return Vector(x: Double(width), y: Double(height))
+ }
+
+ subscript(x: Int, y: Int) -> Tile {
+ return tiles[y * width + x]
+ }
+}
+```
+
+If you were thinking this looks familiar, you'd be right. `Tilemap` is pretty similar to the `Bitmap` struct we defined in the first part (they even both have "map" in the name). The only difference is that instead of colors, `Tilemap` contains tiles[[1]](#footnote1).
+
+In the `World` struct, replace the stored `size` property with:
+
+```swift
+public let map: Tilemap
+```
+
+Then modify the `World.init()` method to look like this:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.player = Player(position: map.size / 2)
+}
+```
+
+Since `size` is now a sub-property of `map`, rather than being of a property of the world itself, let's add a computed property for the size so we don't break all the existing references:
+
+```swift
+public extension World {
+ var size: Vector {
+ return map.size
+ }
+
+ ...
+}
+```
+
+To draw the map we will need to know if each tile is a wall or not. We could just check if the tile type is equal to `.wall`, but later if we add other kinds of wall tile, such code would break silently.
+
+Instead, add the following code to `Tile.swift`:
+
+```
+public extension Tile {
+ var isWall: Bool {
+ switch self {
+ case .wall:
+ return true
+ case .floor:
+ return false
+ }
+ }
+}
+```
+
+The `switch` statement in the `isWall` property is *exhaustive*, meaning it doesn't include a `default:` clause, so it will fail to compile if we add another case without handling it. That means we can't accidentally introduce a bug by forgetting to update it when we add new wall types.
+
+Repeating that `switch` everywhere we access the tiles would be a nuisance, but by using the `isWall` property instead, we can ensure the code is robust without being verbose.
+
+In `Renderer.draw()` add the following block of code before `// Draw player`:
+
+```swift
+// Draw map
+for y in 0 ..< world.map.height {
+ for x in 0 ..< world.map.width where world.map[x, y].isWall {
+ let rect = Rect(
+ min: Vector(x: Double(x), y: Double(y)) * scale,
+ max: Vector(x: Double(x + 1), y: Double(y + 1)) * scale
+ )
+ bitmap.fill(rect: rect, color: .white)
+ }
+}
+```
+
+That takes care of drawing the wall tiles. But so that we can actually see the white walls against the background, in `Renderer.init()` change the line:
+
+```swift
+self.bitmap = Bitmap(width: width, height: height, color: .white)
+```
+
+to:
+
+```swift
+self.bitmap = Bitmap(width: width, height: height, color: .black)
+```
+
+Now that we have code to draw the map, *we need a map to draw*.
+
+Here we hit upon another natural division between architectural layers. So far we have only really dealt with two layers: the game engine and the platform layer. Gameplay specifics such as particular map layouts do not really belong in the engine, but they are also not tied to a particular platform. So where do we put them?
+
+### The Best Code is No Code
+
+We could create a whole new module for game-specific code, but even better would be if we could treat our game as *data* to be consumed by the engine, rather than code at all.
+
+You may have noticed that the `Tile` enum has an `Int` type, and conforms to `Decodable` (as does `Tilemap` itself). The reason for this is to facilitate defining maps in JSON rather than having to hard-code them.
+
+Create a new empty file in the main project called `Map.json` and add the following contents:
+
+```swift
+{
+ "width": 8,
+ "tiles": [
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 1, 1, 1, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+}
+```
+
+The numbers match the case values in the `Tile` enum, so `0` is a floor tile and `1` is a wall. This is a tiny and trivial map, but it will serve to test the engine. You can replace it with a different map size or layout if you prefer - it won't affect the rest of the tutorial.
+
+In `ViewController.swift`, add the following free function outside the `ViewController` class[[2]](#footnote2):
+
+```swift
+private func loadMap() -> Tilemap {
+ let jsonURL = Bundle.main.url(forResource: "Map", withExtension: "json")!
+ let jsonData = try! Data(contentsOf: jsonURL)
+ return try! JSONDecoder().decode(Tilemap.self, from: jsonData)
+}
+```
+
+Then replace the line:
+
+```swift
+private var world = World()
+```
+
+with:
+
+```swift
+private var world = World(map: loadMap())
+```
+
+Run the app again and you should see the map rendered in all its glory:
+
+![The tile map displayed in white](Images/Tilemap.png)
+
+The player looks a bit silly now though, drifting diagonally across the map without regard for the placement of walls. Let's fix that.
+
+### Position is Everything
+
+For maximum flexibility in the map design, the player's starting position should be defined in the JSON file rather than hard-coded.
+
+We could add a new case called `player` to the `Tile` enum, and then place the player in the map array the same way we place walls, but if we later add different types of floor tile we'd have no way to specify the type of floor underneath the player if they both occupied the same element in the `tiles` array.
+
+Instead, let's add a new type to represent non-static objects in the map. Create a new file called `Thing.swift` in the Engine module, with the following contents:
+
+```swift
+public enum Thing: Int, Decodable {
+ case nothing
+ case player
+}
+```
+
+Then add a `things` property to `Tilemap`:
+
+```swift
+public struct Tilemap: Decodable {
+ private let tiles: [Tile]
+ public let things: [Thing]
+ public let width: Int
+}
+```
+
+Finally, add an array of `things` to the `Map.json` file:
+
+```
+{
+ "width": 8,
+ "tiles": [
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 1, 1, 1, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1
+ ],
+ "things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+}
+```
+
+Unlike the map tiles, we can't just look up the player position from the `things` array each time we need it, because once the player starts moving their position will no longer align to the tile grid.
+
+Instead we'll use `things` to get the *initial* player position, after which the `Player` object will keep track of its own position. In `World.init()`, replace the line:
+
+```swift
+self.player = Player(position: map.size / 2)
+```
+
+with:
+
+```swift
+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]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ }
+ }
+}
+```
+
+This code scans through the things array and adds any non-zero things (currently just the player) to the world. Note that we add `0.5` to the map X and Y coordinate when we set the player's starting position so that the player will be placed in the center of the tile instead of at the top-left corner.
+
+If we try to run the app now the compiler will complain that we are trying to return from the initializer without initializing all properties. Although we can see that there is a `1` in the `things` array, the Swift compiler isn't able to detect that statically.
+
+To fix that, add a `!` to the `player` property, converting it into an [Implicitly Unwrapped Optional](https://www.hackingwithswift.com/example-code/language/what-are-implicitly-unwrapped-optionals).
+
+```swift
+public var player: Player!
+```
+
+Using an IUO means the game will crash if we forget to include the player in `things`, but the game can't work without a player anyway.
+
+With the player avatar in the correct starting position, we now need to fix the player's movement. We'll start by removing the hard-coded diagonal motion. In `Player.swift`, change the line:
+
+```swift
+self.velocity = Vector(x: 1, y: 1)
+```
+
+to:
+
+```swift
+self.velocity = Vector(x: 0, y: 0)
+```
+
+Now, to make the player move properly we need to implement *user input*. User input is handled by the platform layer, as it will vary significantly between different target devices. On a Mac you'd probably use arrow keys for movement. On tvOS you'd use a joypad. For iOS, we'll need to implement touch-based movement.
+
+### Pan Handling
+
+The most effective form of touch-based interface is direct manipulation, but that doesn't translate well to games where player movement often extends beyond a single screen. Traditional game control schemes also don't work well on a touch screen - some early ports of classic games to iOS had *truly horrible* controls because they tried to replicate physical buttons and joysticks with on-screen icons[[3]](#footnote3).
+
+The best general-purpose movement control I've found for action games is a *floating joystick*, where the stick position is measured relative to where the finger first touched down instead of a fixed point. The benefit of this approach is that it's much less susceptible to finger-drift than a fixed joystick, meaning that the player can keep their eyes on the action without worrying about their finger slipping off the controls.
+
+There are a couple of different ways to implement a joystick on iOS, but the most straightforward is to use a `UIPanGestureRecognizer`. Go ahead and add a `panGesture` property to the `ViewController` class:
+
+```swift
+class ViewController: UIViewController {
+ private let imageView = UIImageView()
+ private let panGesture = UIPanGestureRecognizer()
+
+ ...
+}
+```
+
+Then add the following line to `viewDidLoad()`:
+
+```swift
+view.addGestureRecognizer(panGesture)
+```
+
+Conveniently, `UIPanGestureRecognizer` already computes the relative finger position from the point at which the gesture started, so we don't need to store any state information beyond what the gesture recognizer already tracks for us.
+
+In a normal app we'd use the target/action pattern to bind a method that would be called whenever the gesture was recognized, but that's not really helpful in this case because we don't want to be informed about pan gestures *as they happen*, we want to sample them once per frame. Instead of an action method, we'll use a computed property for the input vector.
+
+Add the following code to `ViewController`:
+
+```swift
+private var inputVector: Vector {
+ switch panGesture.state {
+ case .began, .changed:
+ let translation = panGesture.translation(in: view)
+ return Vector(x: Double(translation.x), y: Double(translation.y))
+ default:
+ return Vector(x: 0, y: 0)
+ }
+}
+```
+
+This input needs to be passed to the engine. We could directly pass the vector, but we'll be adding other inputs in future (e.g. a fire button), so let's package the input up inside a new type. Create a new file in the Engine module called `Input.swift` with the following contents:
+
+```swift
+public struct Input {
+ public var velocity: Vector
+
+ public init(velocity: Vector) {
+ self.velocity = velocity
+ }
+}
+```
+
+Then replace the following line in `ViewController.update()`:
+
+```swift
+world.update(timeStep: timeStep)
+```
+
+with:
+
+```swift
+let input = Input(velocity: inputVector)
+world.update(timeStep: timeStep, input: input)
+```
+
+And in the `World.swift` file, change the `update()` method to:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ player.velocity = input.velocity
+ player.position += player.velocity * timeStep
+ player.position.x.formTruncatingRemainder(dividingBy: size.x)
+ player.position.y.formTruncatingRemainder(dividingBy: size.y)
+}
+```
+
+If you try running the app now, and drag your finger around the screen, you'll see that the player moves way too fast. The problem here is that we are measuring the input vector in screen points, but player movement in the game is supposed to be specified in world units - a finger swipe across the screen covers far more points than tiles.
+
+The solution is to divide the input vector by some value - but what value? We could use the relative scale factor of the screen to the world, but we don't really want the player speed to depend on the size of the world and/or screen.
+
+What we need to do is [normalize](https://en.wikipedia.org/wiki/Normalization_(statistics)) the input velocity, so that its value is independent of the input method. Regardless of the raw input values produced by the platform layer the game should always receive a value in the range 0 to 1.
+
+
+
+In order to normalize the input, we first need to pick a maximum value. If you think about a real joystick on a gamepad, they typically have a travel distance of about half an inch. On an iPhone that's roughly 80 screen points, so a sensible joystick radius would be around 40 points (equivalent to 80 points diameter).
+
+Add a new constant to the top of the `ViewController.swift` file (not inside the `ViewController` class itself):
+
+```
+private let joystickRadius: Double = 40
+```
+
+Now we have defined the maximum, we can divide the input by that value to get the normalized result. In the `inputVector` getter, replace the line:
+
+```swift
+return Vector(x: Double(translation.x), y: Double(translation.y))
+```
+
+with:
+
+```swift
+var vector = Vector(x: Double(translation.x), y: Double(translation.y))
+vector /= joystickRadius
+return vector
+```
+
+That's *almost* the solution, but there's still a problem. If the user drags their finger further than 40 points, the resultant vector magnitude will be > 1, so we need to clamp it. In order to do that, we need to calculate the actual length of the vector.
+
+[Pythagoras's theorem](https://en.wikipedia.org/wiki/Pythagorean_theorem#Other_forms_of_the_theorem) states that the length of the hypotenuse of a right-angle triangle is equal to the square root of the sum of the squares of the other two sides. This relationship allows us to derive the length of a vector from its X and Y components.
+
+
+
+In `Vector.swift`, add a computed property for `length`:
+
+```swift
+public extension Vector {
+ var length: Double {
+ return (x * x + y * y).squareRoot()
+ }
+
+ ...
+}
+```
+
+Then in `ViewController.inputVector`, replace the line:
+
+```swift
+vector /= joystickRadius
+```
+
+with:
+
+```swift
+vector /= max(joystickRadius, vector.length)
+```
+
+With that extra check, we can be confident that the resultant magnitude will never be greater than one. Run the app again and you should find that the player now moves at reasonable speed. If anything, one world unit per second is a bit *slow* for the maximum speed. Add a `speed` constant to `Player` so we can configure the maximum rate of movement:
+
+```swift
+public struct Player {
+ public let speed: Double = 2
+ ...
+}
+```
+
+Then, in `World.update()`, multiply the input velocity by the player's speed to get the final velocity:
+
+```swift
+func update(timeStep: Double, input: Vector) {
+ player.velocity = input.velocity * player.speed
+ ...
+}
+```
+
+The speed is reasonable now, but movement feels a bit *laggy*. The problem is, if you drag more than 40 points and then try to change direction, you have to move your finger back inside the joystick radius before it will register movement again. This was exactly the problem we wanted to mitigate by having a floating joystick, but although the joystick re-centers itself whenever you touch down, its position still remains fixed until you raise your finger.
+
+Ideally, it should never be possible to move your finger off the joystick. Attempting to move your finger outside the joystick radius should just drag the joystick to a new location.
+
+
+
+In the same way that we clamped the vector to prevent input values > 1, we can also clamp the cumulative translation value of the gesture itself. `UIPanGestureRecognizer` has a `setTranslation()` method that lets you update the distance travelled, so in the `inputVector` computed property, just before the `return vector` line, add the following:
+
+```swift
+panGesture.setTranslation(CGPoint(
+ x: vector.x * joystickRadius,
+ y: vector.y * joystickRadius
+), in: view)
+```
+
+This uses the clamped `vector` value multiplied by the `joystickRadius` to get the clamped translation distance in screen points, then reassigns that to the gesture recognizer, effectively changing its record of where the gesture started.
+
+### Barrier to Entry
+
+Next, we need to solve the problem of the player moving through walls. To prevent this, we first need a way to detect if the player is intersecting a wall.
+
+Because the map is a grid, we can identify the tiles that overlap the player rectangle by taking the integer parts of the `min` and `max` coordinates of the rectangle to get the equivalent tile coordinates, then looping through the tiles in that range to see if any of them is a wall.
+
+
+
+We'll implement that as an `isIntersecting()` method on `Player`:
+
+```swift
+public extension Player {
+ ...
+
+ func isIntersecting(map: Tilemap) -> Bool {
+ let minX = Int(rect.min.x), maxX = Int(rect.max.x)
+ let minY = Int(rect.min.y), maxY = Int(rect.max.y)
+ for y in minY ... maxY {
+ for x in minX ... maxX {
+ if map[x, y].isWall {
+ return true
+ }
+ }
+ }
+ return false
+ }
+}
+```
+
+That gives us a way to detect if the player is colliding with a wall. Now we need a way to *stop* them from doing that. A simple approach is to wait for the collision to happen, then *undo* it. Replace the code in `World.update()` with the following:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ let oldPosition = player.position
+ player.velocity = input.velocity * player.speed
+ player.position += player.velocity * timeStep
+ if player.isIntersecting(map: map) {
+ player.position = oldPosition
+ }
+}
+```
+
+If you run the game again, you'll see that it's now not possible to walk through walls anymore. The only problem is that now if you walk into a wall, you get *stuck*, unless you walk directly away from the wall again.
+
+Once you touch the wall, any attempt at lateral movement is likely to lead to interpenetration and be rejected by the update handler. In fact, it's not really possible to move down the one-unit-wide corridor because of this.
+
+We can mitigate the problem a bit by reducing the player's `radius`. The current value of `0.5`, means the player's diameter is one whole world unit - the same width as the corridor. Reducing it to `0.25` will give us a bit more freedom to move around. In `Player.swift` replace the line:
+
+```swift
+public let radius: Double = 0.5
+```
+
+with:
+
+```swift
+public let radius: Double = 0.25
+```
+
+That helps with the narrow corridors, but we still stick to the walls when we hit them. It would be much nicer if you could slide along the wall when you walk into it, instead of stopping dead like you'd walked into flypaper.
+
+To fix this, we need to go beyond collision *detection*, and implement collision *response*. Instead of just working out if the player is intersecting a wall, we'll calculate *how far* into the wall they have moved, and then push them out by exactly that amount.
+
+To simplify the problem, we'll start by computing the intersection vector between two rectangles, then we can extend it to work with player/map collisions.
+
+To get the intersection, we measure the overlaps between all four edges of the two rectangles and create an intersection vector for each. If any of these overlaps is zero or negative, it means the rectangles are not intersecting, so we return `nil`. Then we sort the vectors to find the shortest.
+
+
+
+Add the following code in `Rect.swift`:
+
+```swift
+public extension Rect {
+ func intersection(with rect: Rect) -> Vector? {
+ let left = Vector(x: max.x - rect.min.x, y: 0)
+ if left.x <= 0 {
+ return nil
+ }
+ let right = Vector(x: min.x - rect.max.x, y: 0)
+ if right.x >= 0 {
+ return nil
+ }
+ let up = Vector(x: 0, y: max.y - rect.min.y)
+ if up.y <= 0 {
+ return nil
+ }
+ let down = Vector(x: 0, y: min.y - rect.max.y)
+ if down.y >= 0 {
+ return nil
+ }
+ return [left, right, up, down]
+ .sorted(by: { $0.length < $1.length }).first
+ }
+}
+```
+
+In `Player.swift`, replace the `isIntersecting(map:)` method with the following:
+
+```
+func intersection(with map: Tilemap) -> Vector? {
+ let minX = Int(rect.min.x), maxX = Int(rect.max.x)
+ let minY = Int(rect.min.y), maxY = Int(rect.max.y)
+ var largestIntersection: Vector?
+ for y in minY ... maxY {
+ for x in minX ... maxX where map[x, y].isWall {
+ let wallRect = Rect(
+ min: Vector(x: Double(x), y: Double(y)),
+ max: Vector(x: Double(x + 1), y: Double(y + 1))
+ )
+ if let intersection = rect.intersection(with: wallRect),
+ intersection.length > largestIntersection?.length ?? 0 {
+ largestIntersection = intersection
+ }
+ }
+ }
+ return largestIntersection
+}
+```
+
+The basic structure of the new method is the same as before: identifying the potentially overlapping tiles and looping through them. But now we actually compute the intersection vectors - rather than just a boolean to indicate that an intersection has occurred - and return the largest intersection detected.
+
+In `World.swift`, change the `update()` method to use the new intersection method:
+
+```swift
+mutating func update(timeStep: Double, input: Input) {
+ player.velocity = input.velocity * player.speed
+ player.position += player.velocity * timeStep
+ if let intersection = player.intersection(with: map) {
+ player.position -= intersection
+ }
+}
+```
+
+If an intersection was detected, we subtract the vector from the player's current position. Unlike the previous solution, this won't move the player back to where they were before, but instead it will move them the shortest distance necessary to push them out of the wall. If they approach the wall at an angle, their lateral movement will be preserved and they will appear to slide along the wall instead of stopping dead.
+
+If you run the game now and try sliding against a wall you may find that the player occasionally sinks into it, or even passes right through it to the other side.
+
+The problem is that if the player is at an intersection between two walls, pushing them out of one wall tile can potentially push them right into another one, since we only handle the first intersection we detect.
+
+That would probably be corrected by a subsequent collision response, but that wouldn't happen until one or more frames later, and in the meantime the player may have moved further into the wall.
+
+
+
+Instead of waiting until the next frame to apply further collision response steps, we can replace the `if` statement in the update logic with a `while` loop.
+
+In `World.update()` replace the following line:
+
+```swift
+if let intersection = player.intersection(with: map) {
+```
+
+with:
+
+```swift
+while let intersection = player.intersection(with: map) {
+```
+
+With that change in place, the collision logic will keep nudging the player until there are no further intersections detected.
+
+This collision response mechanism is still a bit fragile however, because it assumes that a player can never walk more than their own diameter's depth through a wall in a single frame. If they did somehow manage to do that, they could get trapped - stuck in an infinite loop of being nudged out of one wall into another and then back again. If they managed to go even faster they might pass the center point of the wall and end up getting pushed out the other side.
+
+
+
+So what would it take for that to happen? At the current player movement speed of 2 units-per-second, and a player diameter of 0.5 units, they will travel their own diameter's distance in 0.25 seconds (one quarter of a second). In order to prevent the player potentially getting stuck, we need to *guarantee* that the size of each time step remains well below that limit.
+
+### Baby Steps
+
+Of course, the game would be completely unplayable at 4 frames per second, and we would never ship it in that state, but there are plenty of scenarios where a game might suffer a single-frame stutter. For example, if the player answers a phone call in the middle of a game, the game will move into the background and the frame timer will be paused. When the player resumes 10 minutes later, we don't want their avatar to suddenly jump forward several thousand pixels as if they had been moving that whole time.
+
+A brute-force solution is to cap the update `timeStep` at some sensible maximum like 1/20 (i.e. a minimum of 20 FPS). If the frame period ever goes over that, we'll just pass 1/20 as the `timeStep` anyway.
+
+Add a new constant to the top of the `ViewController.swift` file:
+
+```swift
+private let maximumTimeStep: Double = 1 / 20
+```
+
+Then in `update()`, replace the line:
+
+```swift
+let timeStep = displayLink.timestamp - lastFrameTime
+```
+
+with:
+
+```swift
+let timeStep = min(maximumTimeStep, displayLink.timestamp - lastFrameTime)
+```
+
+That gives us a little bit of breathing room, but what happens later if we want to add projectiles with a radius of 0.1 units that travel 100 units per second? Well, we can't very well set a minimum frame rate of 1000 FPS, so we need a way to make the time steps as small as possible without increasing the frame rate.
+
+The solution is to perform multiple world updates per frame. There is no rule that says world updates have to happen in lock-step with drawing[[4]](#footnote4). For a game like this, where the frame rate will likely be limited by rendering rather than physics or AI, it makes sense to perform multiple world updates for every frame drawn.
+
+If the actual frame time is 1/20th of a second, we could update the world twice, passing a `timeStep` of 1/40th each time. The player won't see the effect of those intermediate steps on screen, so we won't bother drawing them, but by reducing the time between updates we can improve the accuracy of collisions and avoid any weirdness due to lag.
+
+Since the frame rate will be 60 FPS on most systems, a time step of 1/120 (120 FPS) seems reasonable for now (we can always increase it later if we need to handle tiny and/or fast-moving objects). Add the following constant to the top of `ViewController.swift`:
+
+```swift
+private let worldTimeStep: Double = 1 / 120
+```
+
+Then, in the `update()` method, replace the line:
+
+```swift
+world.update(timeStep: timeStep, input: input)
+```
+
+with:
+
+```swift
+let worldSteps = (timeStep / worldTimeStep).rounded(.up)
+for _ in 0 ..< Int(worldSteps) {
+ world.update(timeStep: timeStep / worldSteps, input: input)
+}
+```
+
+Regardless of the frame rate, the world will now always be updated at a minimum of 120 FPS, ensuring consistent and reliable collision handling.
+
+You might be thinking that with the `worldTimeStep` logic, we no longer need the `maximumTimeStep` check, but actually this saves us from another problem - the so-called *spiral of death* that can occur when your world updates take longer to execute than your frame step.
+
+By limiting the maximum frame time, we also limit the number of iterations of the world update. 1/20 divided by 1/120 is 6, so there will never be more than six world updates performed for a single frame drawn. That means that if a frame happens to take a long time (e.g. because the game was backgrounded or paused), then when the game wakes up it won't try to execute hundreds of world updates in order to catch up with the frame time, blocking the main thread and delaying the next frame - and so on - leading to the death spiral.
+
+That's it for Part 2. In this part we...
+
+* Loaded and displayed a simple maze
+* Added touch input for moving the player
+* Added collision detection and response so the player doesn't pass through walls
+* Refined the game loop and decoupled world updates from the frame rate
+
+In [Part 3](Part3.md) we'll take the jump into 3D and learn how to view a 2D maze from a first person perspective.
+
+### Reader Exercises
+
+1. Try creating a larger, more intricate maze. Does the maze have to be square?
+
+2. Add a new object type, such as pillar. Should this be a `Tile` or a `Thing`? How would you draw it? What about collision handling?
+
+3. Physics-based games often use a fixed frame rate to ensure deterministic behavior. Our frame rate is now guaranteed to be at least 120 steps per second, but it's not actually deterministic. For example, at a frame rate of 50 frames per second, 120/50 == 2.4 steps per frame. We round that up to 3 steps, so our total world steps would be 50 * 3 = 150 steps per second, not 120. How could you modify the update loop to perform 120 steps per second, regardless of the frame rate?
+
+
+
+[[1]](#reference1) We could replace both of these with a single `Map` type, perhaps using generics or a protocol in place of `Color`/`Tile`, but despite the superficial similarity, these two structs serve significantly different roles, and are likely to diverge as we add more code, rather than benefitting from a shared implementation.
+
+[[2]](#reference2) You may wonder why we haven't bothered with any error handling in the `loadMap()` function? Since the `Map.json` file is bundled with the app at build time, any runtime errors when loading it would really be symptomatic of a programmer error, so there is no point in trying to recover gracefully.
+
+[[3]](#reference3) I'd name and shame some examples, but due to the 32-bit *Appocalypse* of iOS 11, most of them don't exist anymore.
+
+[[4]](#reference4) Modern games often run their game logic on a different thread from graphics rendering. This has some nice advantages - such as improved input responsiveness - but it adds a lot of complexity because you need to ensure that all the data structures shared by the game logic and the renderer are thread-safe.
\ No newline at end of file
diff --git a/Tutorial/Part3.md b/Tutorial/Part3.md
new file mode 100644
index 0000000..d57c35b
--- /dev/null
+++ b/Tutorial/Part3.md
@@ -0,0 +1,771 @@
+## Part 3: Ray Casting
+
+In [Part 2](Part2.md) we created a 2D tile-based maze, implemented a player avatar with simple physics and collision handling, and added a touch-based joystick for movement.
+
+Starting with [that code](https://github.com/nicklockwood/RetroRampage/archive/Part2.zip) we will now make the jump into 3D using a technique called ray casting. But first, we need to lay some groundwork.
+
+### Sense of Direction
+
+The player avatar currently has an implicit direction of movement, but in a first person game we will need to have an *explicit* direction that the player is facing, even when stationary. Go ahead and add a `direction` property to the player:
+
+```swift
+public struct Player {
+ public let speed: Double = 2
+ public let radius: Double = 0.25
+ public var position: Vector
+ public var velocity: Vector
+ public var direction: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 0, y: 0)
+ self.direction = Vector(x: 1, y: 0)
+ }
+}
+```
+
+Note that we've used a vector for the direction, rather than an angle. Vectors are a better representation for directions as they avoid ambiguities such as where the angle is measured from, and whether it's measured clockwise or counter-clockwise. They're also easier to work with mathematically.
+
+A direction vector should ideally always be *normalized* (have a length of 1), which is why we've initialized the direction with 1,0 instead of 0,0.
+
+For now, we'll derive the direction from the input velocity vector. The input vector may have a length less than 1, so we can't use it directly, but we can get the normalized direction by dividing the vector by its own length.
+
+Insert the following code at the beginning of the `World.update()` method to set the player's direction:
+
+```swift
+let length = input.velocity.length
+if length > 0 {
+ player.direction = input.velocity / length
+}
+```
+
+### Plotting a Course
+
+The player's direction should now always face whichever way they last moved. But the player's avatar is just a square - how can we see which way it's facing? We need a way to visualize the player's line of sight.
+
+Let's draw a line to illustrate the direction vector. Add a new method to `Bitmap`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func drawLine(from: Vector, to: Vector, color: Color) {
+
+ }
+}
+```
+
+To draw the line, we will need to fill each pixel in the bitmap that it touches. We'll start by working out the vector between the start and end of the line, which we'll call `difference`:
+
+```swift
+mutating func drawLine(from: Vector, to: Vector, color: Color) {
+ let difference = to - from
+
+}
+```
+
+To fill every pixel along the line, without leaving gaps or filling the same pixel twice, we need to step along its length exactly one pixel at a time. Each step should be a vector that, when added to the current position, moves to the next pixel along the line.
+
+To ensure the correct step length, the number of steps should be equal to the larger of the X or Y components of the total length. That way the step vector will be exactly one pixel in the longer axis and <= to one pixel in the other axis, so there will be no gaps when we drawn the line.
+
+Add the following code to `drawLine()` to compute the step count:
+
+```swift
+let stepCount: Int
+if abs(difference.x) > abs(difference.y) {
+ stepCount = Int(abs(difference.x).rounded(.up))
+
+} else {
+ stepCount = Int(abs(difference.y).rounded(.up))
+
+}
+```
+
+The longer component of that step vector will have a value of one (because we're moving one pixel at a time). If the longer component is the X component, the Y component will have a length of `difference.y / difference.x`.
+
+
+
+In other words it will be equal to the height of the line divided by the width, so that the slope of the step vector is the same as the slope of the line itself. If the longer component is Y, the X component will have a length of `difference.x / difference.y`, for the same reason.
+
+Update the `drawLine()` code to compute the step vector as follows:
+
+```swift
+mutating func drawLine(from: Vector, to: Vector, color: Color) {
+ let difference = to - from
+ let stepCount: Int
+ let step: Vector
+ if abs(difference.x) > abs(difference.y) {
+ stepCount = Int(abs(difference.x).rounded(.up))
+ let sign = difference.x > 0 ? 1.0 : -1.0
+ step = Vector(x: 1, y: difference.y / difference.x) * sign
+ } else {
+ stepCount = Int(abs(difference.y).rounded(.up))
+ let sign = difference.y > 0 ? 1.0 : -1.0
+ step = Vector(x: difference.x / difference.y, y: 1) * sign
+ }
+
+}
+```
+
+Complete the `drawLine()` method by adding a loop from 0 to `stepCount`, advancing the step each time and filling the specified pixel:
+
+```swift
+var point = from
+for _ in 0 ..< stepCount {
+ self[Int(point.x), Int(point.y)] = color
+ point += step
+}
+```
+
+This is a very basic line-drawing routine - it makes no attempt at [antialiasing](https://en.wikipedia.org/wiki/Spatial_anti-aliasing), or even to correct for the floating point offset of the start and end points within each pixel. It will suffice for visualization purposes, however.
+
+Add the following code to the end of the `Renderer.draw()` method:
+
+```swift
+// Draw line of sight
+let end = world.player.position + world.player.direction * 100
+bitmap.drawLine(from: world.player.position * scale, to: end * scale, color: .green)
+```
+
+This draws a green line extending out from the center of the player avatar, along their line of sight. The line of sight is technically infinite, but since we can't draw an infinitely long line, we've simply used an arbitrary large number for the length (100 world units).
+
+Run the app and move around a bit. You should see something like this:
+
+![Green line of sight extending from player](Images/LineOfSight.png)
+
+### Ray, Interrupted
+
+The line we have drawn is known as a *ray* - a line that extends out indefinitely from a fixed point until it hits something. Right now, we don't actually detect if it hits anything though, so let's fix that.
+
+First, let's create a data structure to represent the ray. A ray has an *origin* (the starting point) and a direction (a normalized vector). It *doesn't* have an end-point because it continues indefinitely. Create a new file called `Ray.swift` in the Engine module with the following contents:
+
+```swift
+public struct Ray {
+ public var origin, direction: Vector
+
+ public init(origin: Vector, direction: Vector) {
+ self.origin = origin
+ self.direction = direction
+ }
+}
+```
+
+To detect where the ray is interrupted by a wall, we need to perform a *hit test*. The hit test method will take a ray as an argument and return a vector indicating the point at which the ray should terminate.
+
+The problem of detecting the hit point inside the map is in some ways quite similar to the `drawLine()` function we wrote earlier. In both cases we need to step along a line inside a grid (in this case a grid of tiles rather than pixels) and find the intersection points.
+
+For the map hit test though, we'll need to be much more precise. It is not enough to know which tile we hit - we also need to know exactly *where* on the tile the collision occurred.
+
+We'll start by simplifying the problem to a single tile. Given that we are standing inside a map tile, where is the point on the tile's boundary where our line of sight exits the tile?
+
+Although the ray origin and direction are vectors, it's actually simpler in this case if we take the X and Y components and handle them individually.
+
+Since the tiles lie on a 1x1 grid, we can get the horizontal and vertical positions of the current tile's edges by rounding the X and Y components of ray's origin up or down, depending on the direction of the ray. From those we can subtract the un-rounded X and Y values to get the distance of the origin from those edges.
+
+
+
+Add the following method to `TileMap`:
+
+```swift
+public extension Tilemap {
+ ...
+
+ func hitTest(_ ray: Ray) -> Vector {
+ var position = ray.origin
+ let edgeDistanceX, edgeDistanceY: Double
+ if ray.direction.x > 0 {
+ edgeDistanceX = position.x.rounded(.down) + 1 - position.x
+ } else {
+ edgeDistanceX = position.x.rounded(.up) - 1 - position.x
+ }
+ if ray.direction.y > 0 {
+ edgeDistanceY = position.y.rounded(.down) + 1 - position.y
+ } else {
+ edgeDistanceY = position.y.rounded(.up) - 1 - position.y
+ }
+
+ }
+}
+```
+
+You might wonder why the code to compute `edgeDistanceX` and `edgeDistanceY` is using `rounded(.down) + 1` instead of `rounded(.up)` and `rounded(.up) - 1` instead of `rounded(.down)` to get the tile boundaries? This is to handle the edge case (*literally*) where the player is already standing at the edge of the tile.
+
+For example, if the player is standing at the exact leftmost edge of a tile looking right, `rounded(.up)` will give the coordinate of the leftmost edge (not what we want), whereas `rounded(.down) + 1` will give the position of the rightmost edge. The same applies if they are standing at the rightmost edge looking left (and so on for up and down).
+
+Now that we know the X and Y distances from the edges of our current tile, we can compute the step vectors we would need to take along the ray to reach either of those edges. We can use the slope of the ray (the X component of the direction divided by the Y component) to derive the missing components of the step vectors, which we'll call `step1` and `step2`.
+
+
+
+Append the following code to `hitTest()` to compute `step1` and `step2`:
+
+```swift
+let slope = ray.direction.x / ray.direction.y
+let step1 = Vector(x: edgeDistanceX, y: edgeDistanceX / slope)
+let step2 = Vector(x: edgeDistanceY * slope, y: edgeDistanceY)
+```
+
+Adding the shortest of these two steps to our current position gives us the position at which the ray would exit the tile we are current standing in. Complete the `hitTest()` method by adding this last block of code:
+
+```swift
+if step1.length < step2.length {
+ position += step1
+} else {
+ position += step2
+}
+return position
+```
+
+This is only the first piece of the puzzle, but it's a good point at which to check our work. In `Renderer.draw()`, replace the line:
+
+```swift
+let end = world.player.position + world.player.direction * 100
+```
+
+With:
+
+```swift
+let ray = Ray(origin: world.player.position, direction: world.player.direction)
+let end = world.map.hitTest(ray)
+```
+
+Now run the app and you should see something like this:
+
+![Ray stopping at edge of current tile](Images/TileEdgeHit.png)
+
+The line length may at first seem arbitrary, but if you move around it should become clear that the line always stops at the nearest edge of whatever tile we are currently standing in (which isn't necessarily a wall).
+
+Now that we have the code to calculate where the ray hits the next tile, we can put that code inside a loop to extend the ray until the edge we have hit is a wall. Update `TileMap.hitTest()` as follows:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector {
+ var position = ray.origin
+ let slope = ray.direction.x / ray.direction.y
+ repeat {
+ let edgeDistanceX, edgeDistanceY: Double
+ if ray.direction.x > 0 {
+ edgeDistanceX = position.x.rounded(.down) + 1 - position.x
+ } else {
+ edgeDistanceX = position.x.rounded(.up) - 1 - position.x
+ }
+ if ray.direction.y > 0 {
+ edgeDistanceY = position.y.rounded(.down) + 1 - position.y
+ } else {
+ edgeDistanceY = position.y.rounded(.up) - 1 - position.y
+ }
+ let step1 = Vector(x: edgeDistanceX, y: edgeDistanceX / slope)
+ let step2 = Vector(x: edgeDistanceY * slope, y: edgeDistanceY)
+ if step1.length < step2.length {
+ position += step1
+ } else {
+ position += step2
+ }
+ } while // TODO: Check if we hit a wall
+ return position
+}
+```
+
+Checking the type of tile we have just hit is not quite as simple as rounding the coordinates of `position` down to the nearest whole tile. All the collision points will be on the boundary between two (or even four) tiles, and which of those tiles we need to check depends on the direction the ray is pointing.
+
+Since this is a nontrivial piece of logic (and it may be useful later), let's extract it into its own method. Add a the following method to `TileMap`, just before the `hitTest()` method:
+
+```swift
+func tile(at position: Vector, from direction: Vector) -> Tile {
+ var offsetX = 0, offsetY = 0
+ if position.x.rounded(.down) == position.x {
+ offsetX = direction.x > 0 ? 0 : -1
+ }
+ if position.y.rounded(.down) == position.y {
+ offsetY = direction.y > 0 ? 0 : -1
+ }
+ return self[Int(position.x) + offsetX, Int(position.y) + offsetY]
+}
+```
+
+As before, we've simplified the problem by handling the X and Y components separately. If either component is a whole number (i.e. it lies on a tile boundary), we use the ray direction to determine which side should be checked.
+
+We can now use `tile(at:from:)` to complete the `hitTest()` method. Replace the line:
+
+```swift
+} while // TODO: Check if we hit a wall
+```
+
+with:
+
+```swift
+} while tile(at: position, from: ray.direction).isWall == false
+```
+
+Run the app again and you'll see that the ray now terminates at the first wall it hits, instead of the first tile boundary:
+
+![Ray stopping at first wall that it hits](Images/WallHit.png)
+
+This may not look like much (it certainly doesn't look much like Wolfenstein!) but the ray intersection logic we just wrote is the key to the ray casting engine.
+
+### Custom Frustum
+
+To render the whole scene using ray casting (instead of casting just a single ray in the direction the player is facing) we need to cast a *fan* of rays - one for each column of pixels on the screen. In a true 3D engine we would need to cast a ray for *every pixel* on the screen, but because our map is 2D we don't need to worry about the vertical axis.
+
+The area described by the fan represents a horizontal cross-section of the [view frustum](https://en.wikipedia.org/wiki/Viewing_frustum). In most 3D projections, the frustum has so-called *near* and *far* clipping planes that define the nearest and farthest things that can be seen. The 3D scene is projected onto the near plane to form the image that appears on the screen.
+
+We don't have a far plane, as the rays we are projecting go on indefinitely until they hit something. For this reason we'll refer to the near plane as the *view plane* instead.
+
+
+
+Since our world is two-dimensional, we can think of the view plane as a line rather than a rectangle. The direction of this line is always orthogonal to the direction that the camera is facing. The orthogonal vector can be computed as (-Y, X). This will be useful, so let's add another computed property to `Vector`:
+
+```swift
+public extension Vector {
+ var orthogonal: Vector {
+ return Vector(x: -y, y: x)
+ }
+
+ ...
+}
+```
+
+The length of the line represents the *view width* in world units. This has no direct relationship to how many pixels wide the view is on-screen, it's more about how big we want the world to appear from the player's viewpoint.
+
+Since the player's `direction` vector is normalized (has a length of 1) the orthogonal vector will be as well. That means we can just multiply the orthogonal vector by the view width to get a line of the correct length to represent the view plane.
+
+```swift
+let viewPlane = world.player.direction.orthogonal * viewWidth
+```
+
+The distance of the view plane from the player is the *focal length*. This affects how near things appear to be. Together, the view width and focal length define the *Field of View* (FoV), which determines how much of the world the player can see at once.
+
+
+
+We'll set both the view width and the focal length to `1.0` for now. This gives a FoV angle of ~53 degrees[[1]](#footnote1), which is a little narrow, but we'll fix that later. Add the following code to the end of the `Renderer.draw()` method:
+
+```swift
+// Draw view plane
+let focalLength = 1.0
+let viewWidth = 1.0
+let viewPlane = world.player.direction.orthogonal * viewWidth
+let viewCenter = world.player.position + world.player.direction * focalLength
+let viewStart = viewCenter - viewPlane / 2
+let viewEnd = viewStart + viewPlane
+bitmap.drawLine(from: viewStart * scale, to: viewEnd * scale, color: .red)
+```
+
+Run the app and you will the view plane drawn as a red line in front of the player.
+
+![Red view plane line in front of player](Images/ViewPlane.png)
+
+### Fan Service
+
+Now we have defined the view plane, we can replace the single line-of-sight ray with a fan of rays spanning the player's view. Delete the following lines from `Renderer.draw()`, as we won't need them anymore:
+
+```swift
+// Draw line of sight
+let ray = Ray(origin: world.player.position, direction: world.player.direction)
+let end = world.map.hitTest(ray)
+bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+```
+
+Eventually we'll need one ray for every horizontal pixel in the bitmap, but for now we'll just draw ten rays to test the principle. Add the following code to end of the `Renderer.draw()` method:
+
+```swift
+// Cast rays
+let columns = 10
+let step = viewPlane / Double(columns)
+var columnPosition = viewStart
+for _ in 0 ..< columns {
+ ...
+}
+```
+
+To get the direction of each ray, we subtract the player position from the current column position along the view plane. Add the following line inside the for loop:
+
+```swift
+let rayDirection = columnPosition - world.player.position
+```
+
+The length of `rayDirection` is the diagonal distance from the player to the view plane. We mentioned earlier that direction vectors should always be normalized, and while it doesn't matter right now, it will help us avoid some weird bugs later. To normalize the ray direction, we divide it by its length:
+
+```swift
+let viewPlaneDistance = rayDirection.length
+let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+)
+```
+
+Finally, we need to compute the ray intersection point and draw the ray (as before), then increment the column position by adding the step to it:
+
+```swift
+let end = world.map.hitTest(ray)
+bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+columnPosition += step
+```
+
+The completed loop looks like this:
+
+```swift
+// Cast rays
+let columns = 10
+let step = viewPlane / Double(columns)
+var columnPosition = viewStart
+for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+ columnPosition += step
+}
+```
+
+Run the app again and you should see a fan of ten green rays spanning the red view plane line we drew earlier.
+
+![A fan of 10 rays spanning the view plane](Images/RayFan.png)
+
+From the length of those rays, we can determine the distance of the walls of the maze at every pixel along the screen. Just one last thing to do before we enter the third dimension...
+
+### A Quick Turnaround
+
+The touch-based joystick we created in Part 2 works well for navigating a maze from a top-down perspective, but it's not set up quite right for a first-person game. Currently, pushing the joystick up always moves the player avatar towards the top of the screen, but in 3D we'll want it to move us *forward* relative to whatever direction the avatar is facing.
+
+The joystick code itself is fine, but we need to change the input model to something more appropriate. Remove the `velocity` property of the `Input` struct and replace it with the following:
+
+```swift
+public struct Input {
+ public var speed: Double
+ public var rotation: Rotation
+
+ public init(speed: Double, rotation: Rotation) {
+ self.speed = speed
+ self.rotation = rotation
+ }
+}
+```
+
+Instead of a velocity vector, our input will now be a scalar speed (positive for forwards and negative for backwards) and a left/right rotation. But what is that `Rotation` type?
+
+We used a vector previously to represent the player's direction, but that won't work for rotation. The obvious way to represent rotation is with an angle, but that introduces a slight problem. We committed to building the engine of the game without any external dependencies - even Foundation - but the Swift standard library (as of version 5.0) does not contain any trigonometric functions, which means working with angles is going to be a bit awkward.
+
+### Enter The Matrix
+
+Fortunately there is another, *even better* way to represent a rotation - a 2x2 [rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix). This matrix encapsulates a rotation in a form that can be efficiently applied to a vector using only ordinary multiplication and addition.
+
+Create a new file in the Engine module called `Rotation.swift` with the following contents:
+
+```swift
+public struct Rotation {
+ var m1, m2, m3, m4: Double
+}
+```
+
+A 2x2 matrix contains 4 numbers, hence the four parameters. The `m[x]` naming is conventional, but unless you are well-versed with linear algebra those parameters won't mean a whole lot. Let's add an initializer with slightly more ergonomic parameters:
+
+```swift
+public extension Rotation {
+ init(sine: Double, cosine: Double) {
+ self.init(m1: cosine, m2: -sine, m3: sine, m4: cosine)
+ }
+}
+```
+
+This initializer takes the sine and cosine of a given angle and produces a matrix representing a rotation by that angle. We already said that we can't (easily) use the `sin` and `cos` functions inside the engine itself, but that's OK because we'll we be doing that part in the platform layer.
+
+Finally, we'll add a function to apply the rotation to a vector. This feels most natural to write as an extension method on `Vector` itself, but we'll put that extension in the `Rotation.swift` file because it makes more sense from a grouping perspective. Add the following code in `Rotation.swift`:
+
+```swift
+public extension Vector {
+ func rotated(by rotation: Rotation) -> Vector {
+ return Vector(
+ x: x * rotation.m1 + y * rotation.m2,
+ y: x * rotation.m3 + y * rotation.m4
+ )
+ }
+}
+```
+
+Next, we need to update the code in `ViewController.swift` to send the new input model. We'll pass the Y component of the joystick `inputVector` as the speed, and use the X component to calculate the rotation.
+
+As you may recall from Part 2, the input velocity's magnitude ranges from 0 to 1, measured in world-units per second. On the engine side we multiply this by the player's maximum speed and the `timeStep` value before adding it to the position each frame.
+
+If we treat the X component as a rotational velocity value, it becomes *radians* per second rather than world units. This will also need to be multiplied by the time-step and a maximum turning speed, so let's add a `turningSpeed` property to `Player`:
+
+```swift
+public struct Player {
+ public let speed: Double = 3
+ public let turningSpeed: Double = .pi
+ ...
+}
+```
+
+We've chosen a value of `pi` radians (180 degrees) for the turning speed, which means the player will be able to turn a full revolution in two seconds.
+
+Because we are fudging things a little by doing the trigonometry in the platform layer, we'll need to multiply the rotation by the `timeStep` and `turningSpeed` on the platform layer side instead of in `World.update()` as we did for the velocity. This is a bit inelegant, but still preferable to writing our own `sin` and `cos` functions.
+
+In `ViewController.update()`, replace the line:
+
+```swift
+let input = Input(velocity: inputVector)
+```
+
+with:
+
+```swift
+let inputVector = self.inputVector
+let rotation = inputVector.x * world.player.turningSpeed * worldTimeStep
+let input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation))
+)
+```
+
+Finally, in `World.update()`, replace the lines:
+
+```swift
+let length = input.velocity.length
+if length > 0 {
+ player.direction = input.velocity / length
+}
+player.velocity = input.velocity * player.speed
+```
+
+with:
+
+```swift
+player.direction = player.direction.rotated(by: input.rotation)
+player.velocity = player.direction * input.speed * player.speed
+```
+
+If you run the app again now, it should *look* exactly the same, but movement works differently. Swiping the joystick right or left will rotate the player clockwise/counter-clockwise respectively, and swiping up/down will move the player forwards or backwards relative to the direction they are currently facing.
+
+This is a considerably more awkward way to move the player when viewing from a fixed, top-down perspective. But that's OK, because *we won't be using that perspective from now on*.
+
+### A New Dimension
+
+The top-down 2D drawing code we've written so far won't be needed for the first person view, so you can go ahead and delete most of the code in the `Renderer.draw()` function, leaving only the following:
+
+```swift
+mutating func draw(_ world: World) {
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+
+ // Cast rays
+ let columns = 10
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+
+ columnPosition += step
+ }
+}
+```
+
+Ten rays won't be enough to render a complete first-person view - we'll need one ray for every horizontal pixel of the bitmap, so replace the line:
+
+```swift
+let columns = 10
+```
+
+with:
+
+```swift
+let columns = bitmap.width
+```
+
+Inside the loop we need to compute the distance from the start of the ray to where it hits the wall:
+
+```swift
+let wallDistance = (end - ray.origin).length
+```
+
+From this we can use the [perspective projection equation](https://en.wikipedia.org/wiki/3D_projection#Weak_perspective_projection) to compute the height at which to draw the walls. The walls have a height of one world unit, so the code to compute the final wall height in pixels is:
+
+```swift
+let wallHeight = 1.0
+let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+```
+
+Then it's just a matter of drawing that line in the bitmap, for which we can use the `drawLine()` method we wrote earlier. But instead of drawing *along* the ray, this time we'll draw a vertical line from the top to the bottom of the wall. The complete code for the draw function is below, replace Renderer.draw() with the following:
+
+```swift
+mutating func draw(_ world: World) {
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+
+ // Cast rays
+ let columns = bitmap.width
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for x in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ let wallDistance = (end - ray.origin).length
+
+ // Draw wall
+ let wallHeight = 1.0
+ let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+ let wallColor = Color.white
+ bitmap.drawLine(
+ from: Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2),
+ to: Vector(x: Double(x), y: (Double(bitmap.height) + height) / 2),
+ color: wallColor
+ )
+
+ columnPosition += step
+ }
+}
+```
+
+If you run the app now you'll see that we are standing in a 3D world.
+
+![The world viewed from a first person perspective](Images/PerspectiveView.png)
+
+Try moving around - you should be able to walk around naturally by dragging the joystick up/down to move forwards/backwards, and left/right to turn. You'll notice that there's something a bit odd going on with the walls, but it's hard to tell exactly what it is when the view is so small and every surface is pure white.
+
+### Let There Be Light(ing)
+
+Eventually we'll be adding wall textures which will help with the contrast, but in the meantime, maybe we could do something to add some contrast?
+
+Early 3D games tended to use very simple lighting systems, as true, dynamic lights were too expensive. Wolfenstein actually had no lighting *at all*, it just used darker wall textures for North/South facing walls to add contrast.
+
+We don't have textures yet, but we can replicate Wolfenstein's approach by simply using two color tones. We know that walls are aligned on a 1x1 grid, so a wall coordinate with an exact integer Y value must be a North/South facing. In `Renderer.draw()`, replace the line
+
+```swift
+let wallColor = Color.white
+```
+
+with:
+
+```swift
+let wallColor: Color
+if end.x.rounded(.down) == end.x {
+ wallColor = .white
+} else {
+ wallColor = .gray
+}
+```
+
+Run the app again and you'll be able to see more clearly now where one wall ends and the next begins.
+
+![Walls lit according to distance from the player](Images/Lighting.png)
+
+### A Wider Perspective
+
+It would also help if we could see a bit more of the scene at once. We originally used a square aspect ratio for the bitmap because that matched up with the dimensions of the world, but now we've switched to a first-person perspective it seems a shame not to take advantage of the iPhone's glorious widescreen.
+
+Let's extend the bitmap to fill the full screen width. In `ViewController.update()` replace the lines:
+
+```swift
+let size = Int(min(imageView.bounds.width, imageView.bounds.height))
+var renderer = Renderer(width: size, height: size)
+```
+
+with:
+
+```swift
+let width = Int(imageView.bounds.width), height = Int(imageView.bounds.height)
+var renderer = Renderer(width: width, height: height)
+```
+
+Now run the app again. The view now fills the screen, but it looks stretched. Each wall tile ought to be square, but if you look at that doorway in front of us, it's clearly wider than it is tall.
+
+![In widescreen view the walls appear stretched](Images/StretchedWalls.png)
+
+The stretching effect is because we changed the aspect ratio of the bitmap without updating the width of the view plane accordingly. As far as the code is concerned, the view plane is still square, but we're now stretching it to fill a rectangular bitmap. To fix that, in `Renderer.draw()`, replace the line:
+
+```swift
+let viewWidth = 1.0
+```
+
+with:
+
+```swift
+let viewWidth = Double(bitmap.width) / Double(bitmap.height)
+```
+
+Running the app again, you can see that we've fixed the stretching effect. Since the focal length is the same but the view width is wider, we've also increased the *field of view*[[2]](#footnote2).
+
+![Walls appear curved](Images/CurvedWalls.png)
+
+It's now also a bit easier now to see what's going on with the walls. They're... curved? *How are the walls curved?! Literally every line we've drawn is straight!*
+
+### Fishy Business
+
+When the fan of rays hits a wall perpendicular to the player, the further the ray is from the center of the view, the longer it is. Since we are using the length of the ray to compute the distance from the wall, that means that parts of the wall further from the center appear further away, causing a *fisheye* effect as the wall bends away from the camera.
+
+
+
+What we really need to use is not the *length* of the ray but the perpendicular distance from the end of the ray to the view plane. We could use Pythagoras's Theorem[[3]](#footnote3) to derive the perpendicular length from the ray length, but we would need to know the length of the opposite side of the triangle, which we currently don't.
+
+Fortunately, we can make use of the handy fact that the ratio between the lengths of the sides doesn't change when you scale a triangle up or down[[4]](#footnote4).
+
+Wherever the ray hits a wall, the triangle it forms perpendicular to the view plane will have the same proportions as the triangle that the ray forms with the view plane itself, which we already know all the sides of. So we can calculate the perpendicular length as follows:
+
+1. Compute the vector from the player to the view plane along the ray (which happens to be the vector we already used for the ray direction).
+2. Divide the length of that vector by the focal length to get the ratio between distance and perpendicular distance.
+3. Divide the distance from the wall by the ratio to get the perpendicular distance to the wall.
+
+
+
+To implement that in code, in `Renderer.draw()` replace the line:
+
+```swift
+let height = wallHeight * focalLength / wallDistance * Double(bitmap.height)
+```
+
+with:
+
+```swift
+let distanceRatio = viewPlaneDistance / focalLength
+let perpendicular = wallDistance / distanceRatio
+let height = wallHeight * focalLength / perpendicular * Double(bitmap.height)
+```
+
+Run the app again and you should see that all trace of curvature has disappeared.
+
+![The walls are now straight](Images/StraightWalls.png)
+
+And that will do for now. To recap, in this part we...
+
+* Implemented ray casting to detect walls in the player's field of view
+* Changed from a directional to a rotational control scheme
+* Switched to a first-person perspective
+* Added simple lighting
+
+In [Part 4](Part4.md) we'll redecorate a bit and add some *texture* to our world.
+
+### Reader Exercises
+
+1. Try playing with the focal length and view width. Can you increase the player's field of view without making the walls appear stretched?
+
+2. Doom added a *gloom* effect, where walls become darker further from the camera. Try adding logic to vary the brightness of each wall column based on its distance from the player.
+
+3. The single-joystick interface we've added doesn't allow for side-stepping. Can you modify the controls to use a dual stick interface, with the left joystick controlling movement and the right joystick controlling rotation?
+
+
+
+[[1]](#reference1) If you're curious how the field of view angle was derived, the point where the line of sight meets the view plane forms a right-angled triangle. Using [SOHCAHTOA](http://mathworld.wolfram.com/SOHCAHTOA.html), with the `focalLength` as the *adjacent* side and half the `viewWidth` as the *opposite* side, the FoV (in radians) can be computed using the formula `fieldOfView = 2 * atan(viewWidth / 2 / focalLength)`. Multiply the result by `180 / .pi` to get the angle in degrees.
+
+[[2]](#reference2) If you recall, I mentioned before that the earlier value of 53 degrees for the field of view was a bit narrow? Well now it's ~90 degrees (assuming a screen aspect ratio of ~2:1 as found on X-series iPhones).
+
+[[3]](#reference3) The squared length of the diagonal of a right-angle triangle is equal to the sum of the squares of the other two sides. We used this before in [Part 2](Part2.md) to calculate the length of a vector length from its X and Y components.
+
+[[4]](#reference4) You can [thank Pythagoras](https://en.wikipedia.org/wiki/Pythagorean_theorem#Proof_using_similar_triangles) for that little factoid as well.
diff --git a/Tutorial/Part4.md b/Tutorial/Part4.md
new file mode 100644
index 0000000..6b4913f
--- /dev/null
+++ b/Tutorial/Part4.md
@@ -0,0 +1,687 @@
+## Part 4: Texture Mapping
+
+In [Part 3](Part3.md) we used ray casting to render our simple 2D maze in glorious 3D. The complete code from Part 3 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part3.zip).
+
+Walking around a 3D world of our own creation is pretty neat, but the novelty wears off pretty quick with only boring white walls to look at. Let's improve the decor a bit.
+
+### Sweet Release
+
+A quick note before we begin: Graphics code of the sort we are writing is extremely sensitive to compiler optimizations. You may find that the game is almost unplayably slow when running in *Debug* mode, but should be much smoother if you run in *Release* mode.
+
+To enable Release mode, go to Edit Scheme > Run > Build Configuration (see below).
+
+
+
+Note that when running in Release mode, debugging tools may not work as expected, so you may need to toggle back to Debug when diagnosing errors.
+
+### Surface Detail
+
+The traditional way to make walls more interesting in a 3D game is to use *texture mapping*. This is a technique where a 2D image is used to decorate a 3D surface, a bit like wallpaper. The term "texture" implies some sort of roughness, but the most common form of texture map is a *color* map, which only affects the color of the surface, not its shape[[1]](#footnote1).
+
+We'll need an image to use for the wall texture. This image should be square, and needs to wrap horizontally (i.e. the rightmost column of pixels should match up seamlessly with the leftmost column).
+
+I'm going to use a 16x16 image because big pixels make it easier to see what's going on[[2]](#footnote2), but you should feel free to use any size you like[[3]](#footnote3). Drawing performance is mainly constrained by the resolution of the *output* bitmap, not the size of individual input textures.
+
+
+
+You are welcome to use this image if you don't feel like drawing your own - you can find it [here](https://github.com/nicklockwood/RetroRampage/tree/Part4/Source/Rampage/Assets.xcassets), along with all other textures used in this tutorial.
+
+We'll need some way to get the image into the game. Since the Engine module knows nothing about the filesystem, it makes sense for the platform layer to load the images and pass them into the engine. Although in theory we could make `Bitmap` conform to `Decodable` and load it directly using our own file format, iOS already has optimized mechanisms for handling images using XCAssets and `.car` files, and we shouldn't fight the host operating system.
+
+In the main project, add your wall texture to `Assets.xcassets` and name it `wall` (all lower-case).
+
+We're going to need more than one texture eventually, so rather than just passing in a single `Bitmap`, let's create a wrapper we can modify later once we better understand what our requirements are. Create a new file called `Textures.swift` in the Engine module with the following contents:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall
+}
+
+public struct Textures {
+ private let textures: [Texture: Bitmap]
+}
+
+public extension Textures {
+ init(loader: (String) -> Bitmap) {
+ var textures = [Texture: Bitmap]()
+ for texture in Texture.allCases {
+ textures[texture] = loader(texture.rawValue)
+ }
+ self.init(textures: textures)
+ }
+
+ subscript(_ texture: Texture) -> Bitmap {
+ return textures[texture]!
+ }
+}
+```
+
+The `Texture` enum is backed by a `String` and conforms to `CaseIterable`. This allows for a very elegant implementation of the `Textures` initializer, which requests bitmaps for textures automatically based on their case names.
+
+The `Textures` struct is basically just a wrapper around a `Dictionary`, but because the initializer implementation makes it impossible to request an image that doesn't exist, the `subscript` can safely return a non-`Optional` value.
+
+In `Renderer.swift` add a `textures` property and change the initializer to accept a `textures` parameter:
+
+```swift
+public struct Renderer {
+ public private(set) var bitmap: Bitmap
+ private let textures: Textures
+
+ public init(width: Int, height: Int, textures: Textures) {
+ self.bitmap = Bitmap(width: width, height: height, color: .black)
+ self.textures = textures
+ }
+}
+```
+
+Images in XCAssets are typically loaded using `UIImage(named:)`. We already have code to convert a `Bitmap` to a `UIImage`, so let's add the *inverse* conversion. Open `UIImage+Bitmap.swift` and add the following code:
+
+```swift
+extension Bitmap {
+ init?(image: UIImage) {
+ guard let cgImage = image.cgImage else {
+ return nil
+ }
+
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = cgImage.width * bytesPerPixel
+
+ var pixels = [Color](repeating: .clear, count: cgImage.width * cgImage.height)
+ guard let context = CGContext(
+ data: &pixels,
+ width: cgImage.width,
+ height: cgImage.height,
+ bitsPerComponent: 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: alphaInfo.rawValue
+ ) else {
+ return nil
+ }
+
+ context.draw(cgImage, in: CGRect(origin: .zero, size: image.size))
+ self.init(width: cgImage.width, pixels: pixels)
+ }
+}
+```
+
+This method initializes a `Bitmap` from a `UIImage`. It works by creating a new `CGContext` backed by an array of `Color` pixels, then drawing the `UIImage` into that context, thereby filling the `Color` array with the image contents.
+
+In `ViewController.swift`, add the following free function at the top of the file[[4]](#footnote4):
+
+```swift
+private func loadTextures() -> Textures {
+ return Textures(loader: { name in
+ Bitmap(image: UIImage(named: name)!)!
+ })
+}
+```
+
+Then add the following property to the `ViewController` class:
+
+```swift
+private let textures = loadTextures()
+```
+
+And finally, in the `update()` method change the line:
+
+```swift
+var renderer = Renderer(width: width, height: height)
+```
+
+to:
+
+```swift
+var renderer = Renderer(width: width, height: height, textures: textures)
+```
+
+Now that the wall texture is available to the engine, we actually have to use it to draw the walls. The way that texture mapping works is that for each pixel of the 3D surface shown on screen, we find the equivalent coordinate on the texture, and then draw that color.
+
+This normally involves a lot of matrix math to convert between the two coordinate systems, but fortunately because our wall surfaces are squares that exactly match the texture dimensions, and are only rotated in one plane, the mapping in this case is fairly straightforward.
+
+We currently draw the walls one column of pixels at a time, with the height of each column determined by its distance from the camera. We know the top and bottom of the wall match up with the top and bottom of the texture, so the only tricky part will be mapping from the X position of the column on screen to the X position of the column within the texture.
+
+
+
+Again, the grid-based nature of the map really helps here. The `Tilemap.hitTest()` method provides the world coordinate at which each ray hits the wall. Because the walls are arranged on a 1x1 world-unit grid, the fractional part of the coordinate value represents how far along the wall the ray landed, in the range 0 - 1. We can take this value and multiply it by the texture width to get the correct X offset within the texture.
+
+We'll need to replace the `drawLine()` call with a new method that copies a column of the wall texture to the output bitmap. Add the following code to `Bitmap.swift`:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func drawColumn(_ sourceX: Int, of source: Bitmap, at point: Vector, height: Double) {
+ let start = Int(point.y), end = Int((point.y + height).rounded(.up))
+ let stepY = Double(source.height) / height
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ self[Int(point.x), y] = sourceColor
+ }
+ }
+}
+```
+
+This method is similar to `drawLine()`, but simpler because the line it draws is always vertical, so we are only stepping along the Y axis. The `sourceX` parameter is the X coordinate of the column of pixels within the source bitmap that we need to copy. The `point` parameter specifies the starting position in the destination bitmap to begin drawing, and `height` indicates the output height of the column of pixels to be drawn.
+
+The code first computes the `start` and `end` pixel positions in the destination bitmap, then loops through them. Because the actual size of the source bitmap may be larger or smaller than the size at which it is being drawn, we need to calculate the Y position of each pixel in the source for the current output Y position in the destination - that's what the `stepY` value is for.
+
+Now we need to update `Renderer.draw()` to use this method. Replace the lines:
+
+```swift
+let wallColor: Color
+if end.x.rounded(.down) == end.x {
+ wallColor = .white
+} else {
+ wallColor = .gray
+}
+bitmap.drawLine(
+ from: Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2),
+ to: Vector(x: Double(x), y: (Double(bitmap.height) + height) / 2),
+ color: wallColor
+)
+```
+
+with:
+
+```swift
+let wallTexture = textures[.wall]
+let wallX = end.x - end.x.rounded(.down)
+let textureX = Int(wallX * Double(wallTexture.width))
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2)
+bitmap.drawColumn(textureX, of: wallTexture, at: wallStart, height: height)
+```
+
+The first line takes the `end` position vector (the point where the ray intersects the wall) and extracts just the fractional part of the X component (`wallX`). This is then used to determine which column of the texture to draw (`textureX`). Try running the app:
+
+![Texture smearing on rear wall](Images/TextureSmearing.png)
+
+Hmm... well that's *almost* right. The problem is that we're only using the X component of the wall position for the texture offset, but we need to use either the X *or* Y parts, depending on whether it's a vertical (North/South) or horizontal (West/East) wall.
+
+We actually just deleted some code that checked if the wall was horizontal or vertical in order to select the correct wall color, so let's bring that back and use it to select either the X or Y component of the `end` position to use for the texture coordinate. Still in `Renderer.draw()`, replace the line:
+
+```swift
+let wallX = end.x - end.x.rounded(.down)
+```
+
+with:
+
+```swift
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+Run the app again and you'll see that the smeared rear wall is now textured correctly. If you look *closely* however, you may notice a small glitch running horizontally along the middle row of pixels on the screen.
+
+![Rounding errors in middle row of pixels](Images/RoundingErrors.png)
+
+This glitch is caused by floating point rounding errors where the edge of the texture pixels are too close to the pixel boundary on screen. The computed texture coordinate is almost exactly on the boundary between two texture pixels, so it flips between the two as alternate columns are drawn.
+
+A simple (if somewhat hack-y) solution is to apply a tiny offset to the rendering position so the values no longer clash. In `Renderer.draw()` replace the line:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2)
+```
+
+with:
+
+```swift
+let wallStart = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 - 0.001)
+```
+
+### Watch Your Tone
+
+The walls look a lot better with textures applied, but we've lost the nice two-tone lighting effect.
+
+Wolfenstein achieved this effect by using two sets of textures. The fixed 256-color palette meant it was no simple feat to adjust brightness programmatically. Darker versions of a given color didn't necessarily exist in the palette, and in those cases a suitable substitute would have to be chosen by hand by the texture artist.
+
+Since we're working with 32-bit color, it's actually a lot simpler for us to darken a given image at runtime, but for now we'll just do as Wolfenstein did and create a second copy of the texture.
+
+Add a second, darker copy of `wall` called `wall2` to XCAssets, and add a matching case to the `Texture` enum:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+}
+```
+
+The same `if` statement we originally used to select the wall color (and then repurposed to help calculate the texture coordinate) can be used to select the correct wall texture. In `Renderer.draw()` replace the following code:
+
+```swift
+let wallTexture = textures[.wall]
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+with:
+
+```swift
+let wallTexture: Bitmap
+let wallX: Double
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[.wall]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[.wall2]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+Run the app again and you'll see that the two-tone lighting is back, helping to make the world look more solid.
+
+![Two-tone wall lighting](Images/TexturesAndLighting.png)
+
+### The Floor in the Plan
+
+That's the wallpaper sorted, but what about the floor and ceiling? You may be surprised to hear (if you haven't played it in the last 25 years) that Wolfenstein actually didn't bother with floor or ceiling textures, instead opting for a simple solid color.
+
+The process for drawing floor and ceiling textures is more computationally expensive than for walls, requiring a depth calculation for every pixel rather than just one for each column. But while the machines of Wolfenstein's era may have struggled with it, it's not really a problem for a modern CPU.
+
+Let's depart from strict adherence to the limitations of the Wolfenstein engine, and draw ourselves a textured floor.
+
+To draw the floor we need to fill the column of pixels below each column of the wall up to the edge of the screen. Let's begin by just filling the floor with a solid color, one column at a time. In the `Renderer.draw()` method, just before `columnPosition += step`, add the following:
+
+```swift
+// Draw floor
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ bitmap[x, y] = .red
+}
+```
+
+The `floorStart` value is basically the same as the upper bound we used for the `drawColumn()` method - in other words the floor starts exactly where the wall ends. The `min()` function is needed because if the wall is too close to the player, `floorStart` would actually be below the bottom edge of the screen, resulting in an invalid range. Run the app and you should see a solid red floor.
+
+![Solid floor color](Images/SolidFloorColor.png)
+
+Now that we know which pixels we are going to fill, we need a texture to fill them with. Go ahead and add two new texture called `floor` and `ceiling` to both the XCAssets and the `Texture` enum in `Textures.swift`. Like the walls, these textures should be square, but this time they will need to tile both horizontally *and* vertically to avoid ugly seams on the floor.
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case floor, ceiling
+}
+```
+
+Because the pixels in each column of the floor are not all at the same distance from the camera, we cannot use the `drawColumn()` function we wrote earlier. We'll have to compute the correct color for each pixel individually.
+
+When drawing the walls, we first computed the position of the wall in world units, and then used the [perspective projection equation](https://en.wikipedia.org/wiki/3D_projection#Weak_perspective_projection) to transform that into screen coordinates. This time we're going to go the *other way* and derive the world coordinate from the screen position.
+
+The map position at `floorStart` is the same distance away as the wall itself - in other words it will be `wallDistance` world units from the camera. Each successive pixel we step over in the loop moves the position closer to the camera. The distance of that position from the camera can be derived by applying the perspective equation in reverse.
+
+The first step is to convert the Y position (in pixels) to a normalized position relative to the view plane. We do that by dividing by the output bitmap height:
+
+```swift
+let normalizedY = Double(y) / Double(bitmap.height)
+```
+
+That gives us a value for `normalizedY` where zero is at the top of the view plane and one is at the bottom. The vanishing point (the point at which the floor or ceiling would be infinitely away) is in the middle of the screen, so we actually want the zero value of `normalizedY` to be the *center* of the screen, not the top. We can achieve that by multiplying the value by two and then subtracting one:
+
+```swift
+let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
+```
+
+From the normalized Y value, we can use the inverse perspective equation to derive the perpendicular distance of that pixel from the camera:
+
+```swift
+let perpendicular = wallHeight * focalLength / normalizedY
+```
+
+Next, by dividing by the `distanceRatio` we calculated earlier we can determine the actual distance, and from that we can compute the map position:
+
+```swift
+let distance = perpendicular * distanceRatio
+let mapPosition = ray.origin + ray.direction * distance
+```
+
+Because the tiles are on a 1x1 grid, the integer parts of the `mapPosition` X and Y components give us the tile coordinate, and the fractional parts give us the precise position within the tile itself, which we can use to derive the texture coordinate:
+
+```swift
+let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+```
+
+That's everything we need to texture the floor. In `Renderer.draw()` replace the following lines:
+
+```swift
+// Draw floor
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ bitmap[x, y] = .red
+}
+```
+
+with:
+
+```swift
+// Draw floor
+let floorTexture = textures[.floor]
+let floorStart = Int(wallStart.y + height) + 1
+for y in min(floorStart, bitmap.height) ..< bitmap.height {
+ let normalizedY = (Double(y) / Double(bitmap.height)) * 2 - 1
+ let perpendicular = wallHeight * focalLength / normalizedY
+ let distance = perpendicular * distanceRatio
+ let mapPosition = ray.origin + ray.direction * distance
+ let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+ let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+ let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+ bitmap[x, y] = floorTexture[textureX, textureY]
+}
+```
+
+Run the game and you should see that the floor is now textured.
+
+![Textured floor](Images/TexturedFloor.png)
+
+To draw the ceiling we could duplicate this code, with a slight change to the `normalizedY` calculation. But there's actually an even simpler way - because the ceiling's coordinates are a mirror of the floor's, we can draw them both with the same loop by just inverting the output Y coordinate. Replace the lines:
+
+```swift
+// Draw floor
+let floorTexture = textures[.floor]
+```
+
+with:
+
+```swift
+// Draw floor and ceiling
+let floorTexture = textures[.floor], ceilingTexture = textures[.ceiling]
+```
+
+And then just below the line that sets the floor pixel color:
+
+```swift
+bitmap[x, y] = floorTexture[textureX, textureY]
+```
+
+add the following line to fill the equivalent ceiling pixel:
+
+```swift
+bitmap[x, bitmap.height - 1 - y] = ceilingTexture[textureX, textureY]
+```
+
+Run the game again and you'll see that the textured floor now has a matching ceiling.
+
+![Textured floor and ceiling](Images/TexturedCeiling.png)
+
+### Not Normal
+
+We cheated a little by re-using the floor texture coordinates for the ceiling - not because they don't match up (they do) - but because the ceiling texture isn't *necessarily* the same size as the floor texture. If the floor and ceiling textures had different resolutions then this logic wouldn't work.
+
+We could solve this by duplicating the `textureX` and `textureY` variables and calculating them separately for each texture, but that's nasty. What we really want is to be able to look up texture pixels using *normalized* coordinates, so we don't have to worry about their actual pixel dimensions.
+
+Let's add a way to do that. In `Bitmap.swift` add the following code just below the existing `subscript`:
+
+```swift
+subscript(normalized x: Double, y: Double) -> Color {
+ return self[Int(x * Double(width)), Int(y * Double(height))]
+}
+```
+
+Now, back in the `Renderer.draw()` method, a little way below the `// Draw floor and ceiling` comment, replace the lines:
+
+```swift
+let textureX = Int((mapPosition.x - tileX) * Double(floorTexture.width))
+let textureY = Int((mapPosition.y - tileY) * Double(floorTexture.height))
+bitmap[x, y] = floorTexture[textureX, textureY]
+bitmap[x, bitmap.height - 1 - y] = ceilingTexture[textureX, textureY]
+```
+
+with:
+
+```swift
+let textureX = mapPosition.x - tileX, textureY = mapPosition.y - tileY
+bitmap[x, y] = floorTexture[normalized: textureX, textureY]
+bitmap[x, bitmap.height - 1 - y] = ceilingTexture[normalized: textureX, textureY]
+```
+
+The new code is more concise, but more importantly it's *correct*, regardless of the relative texture resolutions.
+
+### Variety Show
+
+Even with textures, the world looks a bit monotonous when every wall is the same. Currently the wall and floor textures are always the same because they're hard-coded in the renderer, but we really want these to be specified by the *map*.
+
+We'll start by adding two new wall types and a new floor type to the XCAssets file. For the walls, we need two textures for each new type (light and dark).
+
+
+
+In `Textures.swift` extend the `Texture` enum with these five additional cases:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case crackWall, crackWall2
+ case slimeWall, slimeWall2
+ case floor
+ case crackFloor
+ case ceiling
+}
+```
+
+Note that the order and grouping of the cases in the enum isn't important, as the values are keyed by name rather than index.
+
+Next, we need to extend the `Tile` enum with new cases as well:
+
+```swift
+public enum Tile: Int, Decodable {
+ case floor
+ case wall
+ case crackWall
+ case slimeWall
+ case crackFloor
+}
+```
+
+In this case the order *is* important. Because we are decoding these values by index from the `Map.json` file, if we change the order then it will break the map. If you'd prefer to have the freedom to group them logically, you can preserve compatibility with the JSON by setting the indices explicitly:
+
+```swift
+public enum Tile: Int, Decodable {
+ // Floors
+ case floor = 0
+ case crackFloor = 4
+
+ // Walls
+ case wall = 1
+ case crackWall = 2
+ case slimeWall = 3
+}
+```
+
+Now that we have more wall types, we'll need to update the `Tile.isWall` helper property we wrote earlier. Add the extra cases as follows:
+
+```swift
+var isWall: Bool {
+ switch self {
+ case .wall, .crackWall, .slimeWall:
+ return true
+ case .floor, .crackFloor:
+ return false
+ }
+}
+```
+
+Since there are two textures for each wall tile (and for the floor tiles too, if you count the ceiling texture) we'll need some form of mapping between them. For now we'll just hard-code the textures for each case using a `switch`. Add a new computed property to `Tile` as follows::
+
+```swift
+public extension Tile {
+ ...
+
+ var textures: [Texture] {
+ switch self {
+ case .floor:
+ return [.floor, .ceiling]
+ case .crackFloor:
+ return [.crackFloor, .ceiling]
+ case .wall:
+ return [.wall, .wall2]
+ case .crackWall:
+ return [.crackWall, .crackWall2]
+ case .slimeWall:
+ return [.slimeWall, .slimeWall2]
+ }
+ }
+}
+```
+
+The map layout itself is specified in the `Map.json` file in the main project. Edit the `tiles` array in the JSON to include some of our new wall and floor tiles (the exact layout doesn't matter for the tutorial, so feel free to be creative):
+
+```swift
+"tiles": [
+ 1, 3, 1, 1, 3, 1, 1, 1,
+ 1, 0, 0, 2, 0, 0, 0, 1,
+ 1, 4, 0, 3, 4, 0, 0, 3,
+ 2, 0, 0, 0, 0, 0, 4, 3,
+ 1, 4, 0, 1, 1, 1, 0, 1,
+ 1, 0, 4, 2, 0, 0, 0, 1,
+ 1, 0, 0, 1, 0, 4, 4, 1,
+ 1, 3, 3, 1, 1, 3, 1, 1
+],
+```
+
+That's all of the updates to the game model - now we just need to update the renderer to use the textures specified by the `Tilemap` instead of hard-coded values.
+
+We'll start with the walls. In order to select the correct texture, we first need to work out which wall we hit. You may recall that in Part 3 we added a method `tile(at:from:)` to `Tilemap` that was used to determine which tile a given ray was hitting given a hit position and direction. We can call that method again with the output of `hitTest()` to tell us which wall tile we hit, and from that we can select the correct texture.
+
+In `Renderer.draw()`, a few lines below the `// Draw wall` comment, replace the lines:
+
+```swift
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[.wall]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[.wall2]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+with:
+
+```swift
+let tile = world.map.tile(at: end, from: ray.direction)
+if end.x.rounded(.down) == end.x {
+ wallTexture = textures[tile.textures[0]]
+ wallX = end.y - end.y.rounded(.down)
+} else {
+ wallTexture = textures[tile.textures[1]]
+ wallX = end.x - end.x.rounded(.down)
+}
+```
+
+We'll use a similar approach for the floor and ceiling. We can't look up the textures in advance anymore, so delete the following line from just below the `// Draw floor and ceiling` comment:
+
+```swift
+let floorTexture = textures[.floor], ceilingTexture = textures[.ceiling]
+```
+
+Now we'll use the `wallX` and `wallY` values we already computed to look up the tile, which will give us the textures. Still in the `// Draw floor and ceiling` section, find the line:
+
+```swift
+let tileX = mapPosition.x.rounded(.down), tileY = mapPosition.y.rounded(.down)
+```
+
+then insert the following code just below it:
+
+```swift
+let tile = world.map[Int(tileX), Int(tileY)]
+let floorTexture = textures[tile.textures[0]]
+let ceilingTexture = textures[tile.textures[1]]
+```
+
+And that should do it! Run the app again to see the new wall and floor tiles.
+
+![Varied wall and floor tile textures](Images/VariedTextures.png)
+
+### Be Wise, Optimize
+
+You may notice the frame rate dropping a bit as you move around, even when running in release mode.
+
+We've mostly avoided talking about optimization until now. It's generally a bad idea to optimize the code before you've finished implementing all the functionality, because optimized code can be harder to modify[[5]](#footnote5).
+
+In this case however, the performance *just* took an unexpected nose dive, which tells us that we probably did something dumb. Now is the best time to figure out what that might be, before we pile on more code and lose the context.
+
+You may already have an intuition what the problem is, but it's a good idea to verify your assumptions before you start performance tuning. Open up the *Time Profiler* tool in Instruments (to open Instruments, select `Product > Profile` from the menu bar in Xcode, or press `Cmd-I`), and run the game through it. Ideally this should be done on a device, but for major issues the Simulator will give a reasonable picture of what's happening as well.
+
+You should see something like the screenshot below. Pay close attention to the `Call Tree` settings - inverting the call tree and hiding system libraries makes the output easer to interpret.
+
+![Time Profiler trace showing relative time spent in various calls](Images/TextureLookupTrace.png)
+
+The top function here is what we'd expect - the game spends most of its time inside the `Renderer.draw()` method. The second line item is a bit of a surprise though - `Textures.subscript.getter`. The most expensive single call inside `draw()` is not anything to do with vector math, or writing or reading pixels from a bitmap - it's just looking up the bitmaps for the textures.
+
+It doesn't make a lot of sense that this would be an expensive operation. We aren't doing any loading or decoding - we loaded all the bitmaps for our textures in advance, and stored them in a `Dictionary`, keyed by the texture name. A hash lookup from a dictionary in Swift is by no means an expensive operation, so we'd have to be calling it *a lot* for it to dominate the trace.
+
+*And it seems that we are.*
+
+When drawing the walls, we have to do a texture lookup once for each ray, which is not too bad. Previously we were doing the same for the floor and ceiling textures (which never changed), but now that we are getting the texture from the map tile, we are looking it up again for *every single pixel* of the visible floor surface.
+
+Most of the time, the texture doesn't change between consecutive pixels, so we shouldn't have to look it up again, we can just cache the most recently used texture and use it again. The problem (as always) with caching is knowing when to expire the cache.
+
+As we draw the floor (and ceiling), we need to fetch a new texture every time the tile type changes, but *only* when it changes. The `Tile` type is just an `enum` backed by an `Int`, so it's very cheap to compare. Let's use the current `Tile` as a cache key, and re-fetch the textures whenever it changes.
+
+When you hear the word "cache", you might be thinking of something like a dictionary of `Tile` to `Bitmap`, but remember that the objective here was to *avoid* the cost of a hash lookup. We only need to store a single cache entry (the last pair of texture bitmaps we used), so let's just use a few local variables outside the floor-drawing loop.
+
+In `Renderer.draw()` just below the comment `// Draw floor and ceiling`, add the following lines:
+
+```swift
+var floorTile: Tile!
+var floorTexture, ceilingTexture: Bitmap!
+```
+
+That's our cache storage. Next, replace the lines:
+
+```swift
+let floorTexture = textures[tile.textures[0]]
+let ceilingTexture = textures[tile.textures[1]]
+```
+
+with:
+
+```swift
+if tile != floorTile {
+ floorTexture = textures[tile.textures[0]]
+ ceilingTexture = textures[tile.textures[1]]
+ floorTile = tile
+}
+```
+
+And that's the cache implementation. If the tile has changed since the last loop iteration, we fetch the new floor and ceiling textures and update the `floorTile` value used as the cache key. If it hasn't changed, we do nothing.
+
+Run the game again and you should see that the frame drops have been fixed. If you compare the new Time Profiler trace, you'll also see that `Textures.subscript.getter` has been pushed way down the list and now represents only a small percentage of the total frame time.
+
+![Time Profiler trace showing reduced texture lookup time](Images/ImprovedTextureTrace.png)
+
+That's it for part 4. In this part we...
+
+* Added wall, floor and ceiling textures
+* Added texture variants, configurable by the map JSON
+* Encountered and fixed our first performance blip
+
+In [Part 5](Part5.md) we'll see about adding some other maze inhabitants to keep the player company.
+
+### Reader Exercises
+
+1. What happens if you make a tile transparent, or partially transparent? Does it work as you'd expect? Why do you think that is?
+
+2. In the exercises for [Part 3](Part3.md#reader-exercises) you added distance-based lighting. Can you now make that work for textured walls and floor as well?
+
+3. Doom added outdoor areas using a trick where ceiling tiles are marked as transparent and display a background image of the sky. Can you implement something similar? How would you draw the background?
+
+
+
+[[1]](#reference1) There are other types of texture map, such as [displacement maps](https://en.wikipedia.org/wiki/Displacement_mapping) that actually *can* affect the surface geometry, but such textures weren't seen in games until around 2005, coinciding with the mainstream availability of programmable GPUs capable of running complex shaders.
+
+[[2]](#reference2) OK, it's also because I'm not much of an artist and fewer pixels are easier to draw.
+
+[[3]](#reference3) Wolfenstein used 64x64 images for the wall textures, in case you were wondering. Doom used 128x128.
+
+[[4]](#reference4) As with the `loadMap()` function, we haven't bothered with error handling in `loadTextures()`. An error here would be fatal anyway, and it's better to crash as early as possible.
+
+[[5]](#reference5) As Kent Beck famously said, *"Make it work, make it right, make it fast."* (in that order).
diff --git a/Tutorial/Part5.md b/Tutorial/Part5.md
new file mode 100644
index 0000000..4ce8418
--- /dev/null
+++ b/Tutorial/Part5.md
@@ -0,0 +1,741 @@
+## Part 5: Sprites
+
+In [Part 4](Part4.md) we added wall and floor textures to make our 3D environment more interesting. If you're just jumping in at this point, the code for Part 4 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part4.zip).
+
+So far we have succeeded in building a fairly compelling *wallpaper simulator*, however to make this into a game we'll need more than textured walls. It's time to add some other inhabitants to our world.
+
+### Monsters, Inc.
+
+Add a new file called `Monster.swift` to the Engine module with the following contents:
+
+```swift
+public struct Monster {
+ public var position: Vector
+
+ public init(position: Vector) {
+ self.position = position
+ }
+}
+```
+
+In `Thing.swift`, add a `monster` case to the `Thing` enum:
+
+```swift
+public enum Thing: Int, Decodable {
+ case nothing
+ case player
+ case monster
+}
+```
+
+This new monster has an index of 2, so go ahead and add a bunch of `2`s to the `things` array in `Map.json`:
+
+```swift
+"things": [
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+]
+```
+
+Finally, in `World.swift` add a `monsters` property to the `World` struct, and update the `switch` inside `init()` to handle the `monster` case:
+
+```swift
+public struct World {
+ ...
+ public var monsters: [Monster]
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ 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]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ case .monster:
+ self.monsters.append(Monster(position: position))
+ }
+ }
+ }
+ }
+}
+```
+
+That takes care of placing the monsters in the world, but how do we *draw* them?
+
+### Spritely Creatures
+
+Modern games use 3D models made from textured polygons. While this approach produces incomparable visual and animation detail, it requires a complex toolchain and engine to work with 3D assets. Back in the early '90s, the technology to render detailed 3D models in real time simply didn't exist, so a different solution had to be found. That solution was [sprites](https://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29).
+
+A *sprite* is just a fancy term for an image that moves around on the screen independently of the background. Sprites were employed in 2D games from the very earliest days of computing, and many arcade machines and consoles had dedicated hardware for handling sprites.
+
+By scaling a sprite in proportion to its distance from the viewer, it can be used to simulate a 3D object[[1]](#footnote1). Flat, 2D images might seem like a poor way to represent something solid, but from a first-person perspective, a flat image that always rotates to face the viewer can be pretty convincing.
+
+Because sprites must always face the player, they can't rotate freely. Rotating the plane on which the sprite is drawn would just reveal its lack of depth and break the illusion. In order for a sprite-based character to appear to face away from the player, the sprite has to be swapped out for another image drawn from a different angle.
+
+
+
+The original DOS version of Wolfenstein used characters drawn from eight different orientations to simulate rotation, but this meant eight copies of every animation frame and pose - a huge amount of work for the artists, as each frame had to be drawn by hand. Later ports of the game such as the [SNES and Mac versions](https://wolfenstein.fandom.com/wiki/Mac_Family) saved memory by doing away with the multiple orientations and simply drawing every enemy facing the player at all times[[2]](#footnote2).
+
+We'll keep things simple and just use a single image for the monster for now. The monster sprite will be one world unit in size - the same width and height as a wall tile. As with the walls, I've used a 16x16 image for the sprite image, but feel free to use whatever resolution you like as long as it's square.
+
+
+
+The monster sprite does not need to completely fill the bounds of the image, but its feet should touch the bottom edge (unless you want it to hover). The background behind the monster should be transparent (i.e. the pixels should have an alpha component of zero). If you would like to use my terrible artwork, you can download it [here](https://github.com/nicklockwood/RetroRampage/tree/Part5/Source/Rampage/Assets.xcassets/monster.imageset).
+
+
+Add an XCAsset image called `monster`, then add a `monster` case to the `Texture` enum in `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ case wall, wall2
+ case crackWall, crackWall2
+ ...
+ case monster
+}
+```
+
+To draw each monster we need to create a [billboard](https://en.wikipedia.org/wiki/2.5D#Billboarding) - a textured rectangle that always faces the player. As with the view plane, we can model this rectangle as a line because only its X and Y coordinates ever change.
+
+This next part will be much easier to visualize in 2D. In `Renderer.swift`, add a new method called `draw2D` containing the top-down drawing code we we used back in [Part 3](Part3.md):
+
+```swift
+mutating func draw2D(_ world: World) {
+ let scale = Double(bitmap.height) / world.size.y
+
+ // Draw map
+ for y in 0 ..< world.map.height {
+ for x in 0 ..< world.map.width where world.map[x, y].isWall {
+ let rect = Rect(
+ min: Vector(x: Double(x), y: Double(y)) * scale,
+ max: Vector(x: Double(x + 1), y: Double(y + 1)) * scale
+ )
+ bitmap.fill(rect: rect, color: .white)
+ }
+ }
+
+ // Draw player
+ var rect = world.player.rect
+ rect.min *= scale
+ rect.max *= scale
+ bitmap.fill(rect: rect, color: .blue)
+
+ // Draw view plane
+ let focalLength = 1.0
+ let viewWidth = 1.0
+ let viewPlane = world.player.direction.orthogonal * viewWidth
+ let viewCenter = world.player.position + world.player.direction * focalLength
+ let viewStart = viewCenter - viewPlane / 2
+ let viewEnd = viewStart + viewPlane
+ bitmap.drawLine(from: viewStart * scale, to: viewEnd * scale, color: .red)
+
+ // Cast rays
+ let columns = 10
+ let step = viewPlane / Double(columns)
+ var columnPosition = viewStart
+ for _ in 0 ..< columns {
+ let rayDirection = columnPosition - world.player.position
+ let viewPlaneDistance = rayDirection.length
+ let ray = Ray(
+ origin: world.player.position,
+ direction: rayDirection / viewPlaneDistance
+ )
+ let end = world.map.hitTest(ray)
+ bitmap.drawLine(from: ray.origin * scale, to: end * scale, color: .green)
+ columnPosition += step
+ }
+}
+```
+
+Then in `ViewController.update()`, replace the line:
+
+```swift
+renderer.draw(world)
+```
+
+with:
+
+```swift
+renderer.draw2D(world)
+```
+
+Although we have a method for drawing a line, we don't currently have a way to *model* one as a self-contained value. You might be thinking that `Ray` does this job, but `Ray` only has a start point and direction so its length is infinite/unspecified, whereas a line has a start *and* end point, so its length is part of the definition.
+
+Create a new file in the Engine module called `Billboard.swift` with the following contents:
+
+```swift
+public struct Billboard {
+ public var start: Vector
+ public var direction: Vector
+ public var length: Double
+
+ public init(start: Vector, direction: Vector, length: Double) {
+ self.start = start
+ self.direction = direction
+ self.length = length
+ }
+}
+
+public extension Billboard {
+ var end: Vector {
+ return start + direction * length
+ }
+}
+```
+
+Now, for every monster in the level, we need to create a `Billboard` that represents its sprite. Since the sprites all face the player, the plane of every sprite will be parallel to the view plane, which is itself orthogonal to the player's direction:
+
+```swift
+let spritePlane = player.direction.orthogonal
+```
+
+The width of the sprites will always be one world unit, so the length of the plane is already correct. We just need to subtract half of that length from each monster's position to get the starting points of their respective sprite billboards. Let's add a computed property to `World` that returns the billboards for each monster sprite. Add the following code in `World.swift`:
+
+```swift
+public extension World {
+ ...
+
+ var sprites: [Billboard] {
+ let spritePlane = player.direction.orthogonal
+ return monsters.map { monster in
+ Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1
+ )
+ }
+ }
+}
+```
+
+Now to draw those lines. Append the following code to the end of the `draw2D()` method in `Renderer.swift`:
+
+```swift
+// Draw sprites
+for line in world.sprites {
+ bitmap.drawLine(from: line.start * scale, to: line.end * scale, color: .green)
+}
+```
+
+Now run the game again. If you move around you'll see that the green lines representing the monster sprites always rotate to face the direction of the player[[3]](#footnote3).
+
+![Lines representing the player-facing monster sprites](Images/SpriteLines.png)
+
+The view rays currently pass right through the sprites to the wall behind. In order to draw the sprites in 3D we will need to find where the rays intersect them. In `Billboard.swift` add the following placeholder method:
+
+```swift
+public extension Billboard {
+ ...
+
+ func hitTest(_ ray: Ray) -> Vector? {
+
+ }
+}
+```
+
+This should look familiar - we wrote a similar method on `Tilemap` previously. In that case we were looking for the intersection point between a `Ray` and a grid of map tiles. So how do we find the intersection point between a `Ray` and a `Billboard`?
+
+The first step is to convert the lines of the `Ray` and `Billboard` to [slope-intercept form](https://en.wikipedia.org/wiki/Linear_equation#Slope–intercept_form). The slope intercept equation defines the relationship between the X and Y coordinates along a line as:
+
+```swift
+y = m * x + c
+```
+
+In this equation, `m` is the slope of the line and `c` is the Y value at the point where the line crosses the Y axis (i.e. where X is zero).
+
+
+
+Mathematicians love to use single-letter variable names for some reason, but in the programming world that's generally considered an anti-pattern, so let's rewrite that more explicitly:
+
+```swift
+y = slope * x + intercept
+```
+
+Slope-intercept form discards the length information, so there is no difference between the slope for a line segment or a ray. For that reason, we might as well convert the `Billboard` to a `Ray`, then we can avoid having to duplicate the logic for multiple types. Add the following line to `Billboard.hitTest()`:
+
+```swift
+let lhs = ray, rhs = Ray(origin: start, direction: direction)
+```
+
+This code assigns the incoming ray to a local variable (`lhs` - short for *left-hand side*) and creates a `Ray` instance from the billboard itself, which we assign to the `rhs` variable.
+
+Now that we have two rays, we need to compute their slope intercept parameters. To get the slope of a ray, we need to take the `direction` vector and divide it's height by its width:
+
+```
+let slope = direction.y / direction.x
+```
+
+If we rearrange the slope intercept equation by subtracting `slope * x` from both sides, we get `y - slope * x = intercept`. We already have a known position on the ray - its `origin` - so if we plug in the `origin` values for `x` and `y` that will give us the intercept value:
+
+```
+let intercept = origin.y - slope * origin.x
+```
+
+Because the intercept depends on the slope, instead of having two separate properties, we'll combine slope and intercept into a single *tuple*. Add the following computed property to `Ray.swift`:
+
+```swift
+public extension Ray {
+ ...
+
+ var slopeIntercept: (slope: Double, intercept: Double) {
+ let slope = direction.y / direction.x
+ let intercept = origin.y - slope * origin.x
+ return (slope, intercept)
+ }
+}
+```
+
+Back in the `Billboard.swift` file, add the following code to `hitTest()`:
+
+```swift
+// Calculate slopes and intercepts
+let (slope1, intercept1) = lhs.slopeIntercept
+let (slope2, intercept2) = rhs.slopeIntercept
+```
+
+You may have noticed that unlike the `Tilemap.hitTest()` method, `Billboard.hitTest()` returns an *optional* `Vector`. That's because its possible that the ray doesn't intersect the billboard at all. One case where this could happen is if the ray and billboard are parallel, which would mean their slope values would be equal. Let's add a check for that:
+
+```swift
+// Check if slopes are parallel
+if slope1 == slope2 {
+ return nil
+}
+```
+
+What's next? Well, we have our slope intercept equations for the two lines:
+
+```swift
+y = slope1 * x + intercept1
+y = slope2 * x + intercept2
+```
+
+These are [simultaneous equations](https://en.wikipedia.org/wiki/System_of_linear_equations) - two separate equations that have two variables in common. In this case the variables are the X and Y coordinates of the point where the two lines cross.
+
+You solve a pair of simultaneous equations by defining one in terms of the other. We can replace `y` in the second equation with the body of the first equation, eliminating the `y` variable so we only need to find `x`:
+
+```swift
+slope1 * x + intercept1 = slope2 * x + intercept2
+```
+
+Rearranging this equation then gives us the formula for `x` in terms of values we already know:
+
+```swift
+let x = (intercept1 - intercept2) / (slope2 - slope1)
+```
+
+We now have the X coordinate of the intersection point between the ray and billboard. Given X, we can use the `y = m * x + c` equation that we started with to find Y:
+
+```swift
+let y = slope1 * x + intercept1
+```
+
+Putting it all together, the code for the `Billboard.hitTest()` function is:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector? {
+ let lhs = ray, rhs = Ray(origin: start, direction: direction)
+
+ // Calculate slopes and intercepts
+ let (slope1, intercept1) = lhs.slopeIntercept
+ let (slope2, intercept2) = rhs.slopeIntercept
+
+ // Check if slopes are parallel
+ if slope1 == slope2 {
+ return nil
+ }
+
+ // Find intersection point
+ let x = (intercept1 - intercept2) / (slope2 - slope1)
+ let y = slope1 * x + intercept1
+
+ return Vector(x: x, y: y)
+}
+```
+
+Unfortunately there's an edge case (isn't there always?). For a perfectly vertical ray, the value of `direction.x` will be zero, which means that `direction.y / direction.x` will be infinite, and pretty soon we're going to end up with [NaNs](https://en.wikipedia.org/wiki/NaN) everywhere.
+
+We could add a bunch of logic to handle vertical rays as a special case, but instead we're going to use a time-honored tradition when working with floating point numbers - we're going to *fudge* it. When `direction.x` is zero (or very close to zero), we'll just add a small offset to the value (you may recall that we already used this trick once before when we added `0.001` to the texture coordinate in Part 4 to fix a rounding error).
+
+In `Billboard.hitTest()`, replace the following:
+
+```swift
+let lhs = ray, rhs = Ray(origin: start, direction: direction)
+```
+
+with:
+
+```swift
+var lhs = ray, rhs = Ray(origin: start, direction: direction)
+
+// Ensure rays are never exactly vertical
+let epsilon = 0.00001
+if abs(lhs.direction.x) < epsilon {
+ lhs.direction.x = epsilon
+}
+if abs(rhs.direction.x) < epsilon {
+ rhs.direction.x = epsilon
+}
+```
+
+The `epsilon` constant is the offset in world units. There's no great science to choosing this value - it just needs to be small enough that it won't be noticeable if an object is misplaced by this amount in the world, but large enough not to cause precision issues in the subsequent calculations[[4]](#footnote4).
+
+Now we have calculated the intersection point between the lines, let's update the drawing code so that the rays stop when they hit a sprite. In `Renderer.draw2D()`, in the `// Cast rays` section, replace the line:
+
+```swift
+let end = world.map.hitTest(ray)
+```
+
+with:
+
+```swift
+var end = world.map.hitTest(ray)
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ if spriteDistance > (end - ray.origin).length {
+ continue
+ }
+ end = hit
+}
+```
+
+This logic loops through each sprite and checks if the ray hits it. If it does, it compares the distance from the player to the hit position with the distance to the current `end`, and updates `end` if the new position is closer. Run the game again and see how it looks.
+
+![Rays pointing the wrong way](Images/SpriteRayBug.png)
+
+Hmm... not quite the effect we were hoping for. Several of the rays seem to be going the wrong way and/or stopping in mid-air.
+
+The problem is, our ray-to-billboard intersection code doesn't currently take the `origin` point of the ray into account. We are treating the ray as if it extends indefinitely in *both* directions. The rays in the top-down view are going the wrong way because the sprite behind us is closer than the one in front, and the slopes formed by the ray and that sprite *do* in fact intersect, even though they do so behind the player.
+
+
+
+We need to update the `hitTest()` method to return `nil` if the intersection point between the lines is outside the range we want to test. We can do that by checking if the vector between the ray origin and the intersection point lies in the same direction as the ray itself.
+
+Subtracting the ray origin from the intersection point gives us the direction vector. We can check if that direction matches the ray direction by comparing the signs of the X and Y components.
+
+```swift
+guard (x - lhs.origin.x).sign == lhs.direction.x.sign &&
+ (y - lhs.origin.y).sign == lhs.direction.y.sign else {
+ return nil
+}
+```
+
+But actually, since the X and Y components of a vector are both proportional to its length, and because of our hack that guarantees that `direction.x` will never be zero, we can figure this out using just the X components instead of the whole vector.
+
+If we take the X component of the vector from the ray origin to the intersection point, and divide it by the X component of the direction, the result will be proportional to the distance of the intersection point along the ray. If that result is less than zero it means that the intersection happens before the origin point of the ray (i.e. that the sprite is behind the player), so we can ignore it.
+
+In `Billboard.hitTest()` add the following code just before the line `return Vector(x: x, y: y)`:
+
+```swift
+// Check intersection point is in range
+let distanceAlongRay = (x - lhs.origin.x) / lhs.direction.x
+if distanceAlongRay < 0 {
+ return nil
+}
+```
+
+We also need to check that the intersection point lies between the start and end points of the sprite's billboard, otherwise the rays will stop in mid-air every time they cross any sprite plane. Add the following lines below the code we just wrote:
+
+```swift
+let distanceAlongBillboard = (x - rhs.origin.x) / rhs.direction.x
+if distanceAlongBillboard < 0 || distanceAlongBillboard > length {
+ return nil
+}
+```
+
+Run the game again and you should see that the rays all travel in the right direction, and no longer stop in mid-air.
+
+![Rays stopping at the correct sprite](Images/SpriteRayIntersection.png)
+
+Now that we've got the intersection logic figured out, we can switch back to first-person perspective and draw the sprites in 3D.
+
+### Erase and Rewind
+
+In `ViewController.update` restore the line:
+
+```swift
+renderer.draw2D(world)
+```
+
+back to:
+
+```swift
+renderer.draw(world)
+```
+
+Then, back in `Renderer.swift`, delete the `draw2D()` method, as we won't be needing it anymore.
+
+Next, add the following code to the `Renderer.draw()` method, just before the line `columnPosition += step`:
+
+```swift
+// Draw sprites
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+
+}
+```
+
+As in the `draw2D` method, we need to compare the distance from the sprite to the player with the distance from the wall to the player. But in this case, we already have the distance from the player to the wall stored in the `wallDistance` variable so we don't need to calculate it. Add the following code to the loop:
+
+```swift
+if spriteDistance > wallDistance {
+ continue
+}
+```
+
+From the `spriteDistance` we can derive the height at which to draw the sprite - it's the same exact logic we used to draw the wall previously:
+
+```swift
+let perpendicular = spriteDistance / distanceRatio
+let height = wallHeight / perpendicular * Double(bitmap.height)
+```
+
+Now we need to derive the X coordinate to use for the sprite texture. This is proportional to the distance from the start of the sprite billboard to the hit position, divided by the billboard's length:
+
+```swift
+let spriteX = (hit - sprite.start).length / sprite.length
+```
+
+We can then divide `spriteX` by the texture width to get the actual texture coordinate:
+
+```swift
+let spriteTexture = textures[.monster]
+let textureX = min(Int(spriteX * Double(spriteTexture.width)), spriteTexture.width - 1)
+```
+
+The rest of the code is the same as for drawing the walls. Putting it all together we have:
+
+```swift
+// Draw sprites
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ if spriteDistance > wallDistance {
+ continue
+ }
+ let perpendicular = spriteDistance / distanceRatio
+ let height = wallHeight / perpendicular * Double(bitmap.height)
+ let spriteX = (hit - sprite.start).length / sprite.length
+ let spriteTexture = textures[.monster]
+ let textureX = min(Int(spriteX * Double(spriteTexture.width)), spriteTexture.width - 1)
+ let start = Vector(x: Double(x), y: (Double(bitmap.height) - height) / 2 + 0.001)
+ bitmap.drawColumn(textureX, of: spriteTexture, at: start, height: height)
+}
+```
+
+Run the app again and we should see... *Why, hello friend!*
+
+![Monster sprite with black background](Images/BlackSpriteBackground.png)
+
+Why is the wall behind the monster black? You might be thinking it's because we aren't drawing the wall if the ray hits the sprite first, but that isn't it - we draw the wall first and then draw the sprite on top. The problem is actually to do with *blending*.
+
+### Will it Blend?
+
+As you may recall, every pixel in the bitmap is represented by a `Color` struct with four components. The `r`, `g` and `b` properties determine the color, and the `a` (*alpha*) component controls the transparency.
+
+Pixels with zero alpha should not be drawn to the screen. Ones with partial alpha values should be *blended* into the existing background color. At least, that's how it works normally on iOS. But we aren't using iOS to draw our pixels - we wrote our own drawing code.
+
+Our sprite has a transparent background, but at the moment, when the sprite is drawn into the bitmap, its pixels just replace the ones that are already there. The transparent pixels in the sprite aren't blended with the background, *they make the background transparent*. So why does it appear black? Because the `UIImageView` has a black background, and we're seeing right through to that.
+
+If we change the `UIImageView` background color to red, the background of the sprite will be red instead.
+
+![Monster sprite with red background](Images/RedSpriteBackground.png)
+
+To fix this, we'll need to implement alpha blending in `Bitmap.drawColumn()`. So how do we do that?
+
+In the era of indexed color, it wasn't practical to implement variable transparency (at least not without extremely clever palette management). Instead, games like Wolfenstein chose a single color in the palette to represent the transparency, and pixels that used that color were simply not drawn.
+
+The sprite we are using at the moment only has either fully transparent or opaque pixels, but since we are using full 32-bit color for our sprites, we may as well add proper support for alpha blending. In pseudo-code, the equation for alpha blending is:
+
+```swift
+result = backgroundColor * (1 - alpha) + foregroundColor * alpha
+```
+
+In this equation `alpha` is a value in the range 0 to 1, with 0 being fully transparent and 1 being fully opaque. This operation is actually quite expensive, requiring two multiplications for every component (a total of six for the red, green and blue components) per pixel.
+
+For this reason it is common practice to use an optimization called *premultiplied alpha* to eliminate half of those multiplications. With premultiplied alpha, the red, green and blue components in the image are premultiplied by the alpha component - i.e. every pixel in the image has already been multiplied by its alpha value. With premultiplied alpha, the blending equation is simplified to:
+
+```swift
+result = backgroundColor * (1 - alpha) + foregroundColor
+```
+
+So how do we know if our images are using premultiplied alpha or not? When we wrote the code to initialize a `Bitmap` from a `UIImage` we had to specify a `CGImageAlphaInfo` value, and the value we used was `premultipliedLast` which means that the color values in the bitmap *are* already premultiplied, so we can use the more efficient form of the blending equation.
+
+In `Bitmap.swift`, add the following new method:
+
+```swift
+public extension Bitmap {
+ ...
+
+ mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+ let oldColor = self[x, y]
+ let inverseAlpha = 1 - Double(newColor.a) / 255
+ self[x, y] = Color(
+ r: UInt8(Double(oldColor.r) * inverseAlpha) + newColor.r,
+ g: UInt8(Double(oldColor.g) * inverseAlpha) + newColor.g,
+ b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
+ )
+ }
+}
+```
+
+Then in the `drawColumn()` method, replace the line:
+
+```swift
+self[Int(point.x), y] = sourceColor
+```
+
+with:
+
+```swift
+blendPixel(at: Int(point.x), y, with: sourceColor)
+```
+
+Run the app again and you'll see that the wall now shows through the background of the sprite.
+
+![Monster sprite with transparent background](Images/ClearSpriteBackground.png)
+
+### We Don't Like Your Sort
+
+If you walk to the top-right corner of the map and stand behind the monster in that corner, looking down towards the monster in the bottom-right corner, you'll see something like this (that's assuming you're using the same map layout as we have in the tutorial - if not, you can just take my word for it).
+
+![Monster sprites drawn in the wrong order](Images/SpriteOrderBug.png)
+
+What's going on there? Well, basically we're seeing the far-away monster *through* the nearer one. This doesn't happen with the walls because the ray casting algorithm always stops at the wall nearest the camera, but because sprites have transparent areas we can't just draw the nearest sprite or we wouldn't be able to see other sprites showing through the gaps.
+
+The order in which we draw the sprites is determined by the order in which they are defined in the `things` array, so sprites that are further toward the bottom/right of the map will be drawn after (and therefore *on top of*) ones that are in the top/left, even if they are further away from the player.
+
+This problem still plagues modern graphics engines, and a common solution is to use the [Painter's algorithm](https://en.wikipedia.org/wiki/Painter's_algorithm) - a fancy name for a simple idea: When a painter wants to draw a person in front of a mountain, they draw the mountain first, and then the person. So likewise, when we want to draw one sprite in front of another, we need to make sure we draw the more distant sprite first.
+
+In other words, we need to sort the sprites according to their distance from the player before we draw them. In `Renderer.draw()`, add the following code just above the `// Draw sprites` comment:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+```
+
+The first part of this loop looks a lot like the sprite drawing loop we already wrote. It iterates over the sprites, performs a hit test, then computes the distance. But instead of drawing anything, it appends a tuple of the distance and sprite itself to an array.
+
+Just below the for loop, add the following line:
+
+```swift
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+This sorts the tuples in the array in reverse order of the `distance`, so the sprite with the greatest distance appears first in the array. Finally, in the `// Draw sprites` section below, replace the line:
+
+```swift
+for sprite in world.sprites {
+```
+
+with:
+
+```swift
+for (_, sprite) in spritesByDistance {
+```
+
+This means we are now looping through the pre-sorted tuples instead of the unsorted sprites. We aren't using the distance value from the tuple, so we discard it using the `_` syntax and just keep the sprite. Try running the game again and you should find that the problem with sprite order has been resolved.
+
+![Monster sprites drawn in correct order](Images/SortedSprites.png)
+
+### Measure Twice, Cut Once
+
+It seems inefficient that we are performing the hit test twice for each sprite, discarding the result in between. It's tempting to either include the hit position in the sorted tuples so we can reuse it in the drawing loop, or compute extra values like `textureX` in the first loop and store the results so they need not be recalculated in the second.
+
+This is the dangerous allure of *premature optimization*. It is easy to be distracted by small optimization opportunities, but applying a small optimization can often make it harder to recognize a larger one.
+
+The hit test is certainly an expensive calculation to be doing twice, but we are actually doing something *far more* wasteful. The sorting loop creates an array of billboards for every sprite in the level, then copies them into another array, then sorts them by distance from the player, and it does this every frame, for every ray. There is one ray cast for every horizontal pixel on the screen, so that's ~1000 times per frame.
+
+But because the sprites all face the player, their order doesn't change depending on their horizontal position on the screen. The (non-perpendicular) distance of each sprite varies depending on the angle of the ray, but the relative *order* of the sprites does not. That means we can hoist the sorting loop outside of the ray casting loop, and only do it once per frame instead of 1000 times.
+
+In `Renderer.draw()`, move the following block of code from its current position to just above the `//Cast rays` comment:
+
+```swift
+// Sort sprites by distance
+var spritesByDistance: [(distance: Double, sprite: Billboard)] = []
+for sprite in world.sprites {
+ guard let hit = sprite.hitTest(ray) else {
+ continue
+ }
+ let spriteDistance = (hit - ray.origin).length
+ spritesByDistance.append(
+ (distance: spriteDistance, sprite: sprite)
+ )
+}
+spritesByDistance.sort(by: { $0.distance > $1.distance })
+```
+
+At this point in the code we don't have a `ray` to use for the intersection test anymore, but we can use the distance from the player position to the starting point of the sprite billboard as an approximation. This won't always work correctly if two sprites are very close together, but the collision detection we'll add later should prevent that from happening anyway.
+
+In the `// Sort sprites by distance` block, replace the lines:
+
+```swift
+guard let hit = sprite.hitTest(ray) else {
+ continue
+}
+let spriteDistance = (hit - ray.origin).length
+```
+
+with:
+
+```swift
+let spriteDistance = (sprite.start - world.player.position).length
+```
+
+The `spriteDistance` value we are now using to sort the sprites is completely different from the one used inside the `//Draw sprites` loop. If we had "optimized" the rendering by combining those values, this (much more significant) optimization would not have been possible.
+
+In fact, the optimization we have done here is *also* a bit premature, and we may have cause to regret it later, but it's a good illustration of the principle that small optimizations can impede larger ones.
+
+That's it for Part 5. In this part we:
+
+* Added monsters to the map
+* Displayed the monsters in 3D using sprites
+* Implemented alpha blending
+* Learned a valuable lesson about premature optimization
+
+In [Part 6](Part6.md) we will bring the monsters to life with collision detection, animation, and some rudimentary AI.
+
+### Reader Exercises
+
+1. Try adding another kind of sprite - perhaps a pillar like we added in the the exercises for [Part 2](Part2.md#reader-exercises)?
+
+2. Can you make the distance-based lighting you implemented in the exercise for [Part 4](Part4.md#reader-exercises) work for sprites as well?
+
+3. Now that we've added alpha blending, can you work out how to make a partially transparent wall tile, like a tinted window or a wire fence? What other problems arise when doing this? How might you fix them?
+
+
+
+[[1]](#reference1) Long before modern GPU technology, many arcade machines and consoles included dedicated hardware for [scaling and rotating of 2D sprites and backgrounds](https://www.giantbomb.com/sprite-scaling/3015-7122/) to simulate 3D geometry.
+
+[[2]](#reference2) It's a testament to the immersiveness of Wolfenstein that I never noticed this fact when playing the Mac version as a teenager (although I did notice that the Mac graphics, despite being twice the resolution of the PC version, were significantly inferior artistically).
+
+[[3]](#reference3) If you're wondering why the map is left-aligned rather than centered as it was before, it's because we changed the aspect ratio of the output bitmap after we switched to a first-person view. The bitmap is now wider than it is tall, but the map is still square, so it only occupies the left-hand side.
+
+[[4]](#reference4) You might be tempted to use some *official* value for `epsilon` like `.ulpOfOne`, but don't do that. Dividing by `.ulpOfOne` will produce a gigantic number right at the limit of `Double` precision, and will certainly cause errors in the subsequent calculations.
+
diff --git a/Tutorial/Part6.md b/Tutorial/Part6.md
new file mode 100644
index 0000000..27e1922
--- /dev/null
+++ b/Tutorial/Part6.md
@@ -0,0 +1,726 @@
+## Part 6: Enemy Action
+
+In [Part 5](Part5.md) we added some monsters to the level, and displayed them using *sprites* - textured rectangles that scale with distance but always face the camera. The complete code for Part 5 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part5.zip).
+
+The monster sprites *look* good[[1]](#footnote1), but they don't *do* very much. In fact we can walk right through them as if they weren't even there.
+
+### Don't Ghost Me
+
+The reason you can walk through the sprites is that they are ghosts. I know they look more like zombies, but they're actually ghosts. It's a feature, not a bug.
+
+OK, *fine* - I suppose we can make them solid if you insist. To make the zombies less ghostly and more zombie-ie, we'll need to implement collision handling. Fortunately we already did almost all the work for this when we implemented player-wall collisions back in [Part 2](Part2.md).
+
+To detect if the player was colliding with a wall, we modeled the player as a `Rect` and then did a `Rect`/`Rect` intersection test with the walls. We can use the same approach for `Player`/`Monster` collisions.
+
+Start by copying the computed `rect` property from `Player.swift` and adding it to `Monster.swift` as follows:
+
+```swift
+public extension Monster {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+}
+```
+
+That code depends on a `radius` property, so add a `radius` to the `Monster` as well:
+
+```swift
+public struct Monster {
+ public let radius: Double = 0.25
+
+ ...
+}
+```
+
+Now, in`Player.swift`, add the following method to the end of the `Player` extension:
+
+```swift
+func intersection(with monster: Monster) -> Vector? {
+ return rect.intersection(with: monster.rect)
+}
+```
+
+This code is just a proxy to the `Rect.insersection()` method, which returns a vector representing the overlap between the rectangles (or `nil` if they don't intersect).
+
+This gives us everything we need to determine if the player is colliding with a monster, and if so by how much we need to move them so that they won't be anymore. In `World.update()`, add the following block of code just above the `while` loop:
+
+```swift
+// Handle collisions
+for monster in monsters {
+ if let intersection = player.intersection(with: monster) {
+ player.position -= intersection
+ }
+}
+```
+
+This code loops through every monster in the map, checks if the player is intersecting them, and if so moves the player away so that they aren't. This solution isn't necessarily perfect - it's possible that if you bumped into a group of monsters, you'd end up being buffeted from one into another, but it should be good enough since you expect a certain amount of squishiness if you walk into a monster, so it's not a big problem if the collision response has some looseness to it.
+
+It's because of this looseness that we perform the monster collision loop *before* the wall collision loop - we don't mind if the player gets bounced from one monster into another, but we *definitely* don't want them to end up embedded in a wall, so we make sure that wall collisions are processed last.
+
+Run the game now and you should find that you are no longer able to walk through the monsters
+
+### You've Got to Give a Little
+
+Something feels a bit... *off* about the collision handling right now. Walking into those monsters is like walking into a brick wall because they don't have any *give*. Since they are roughly the same size as the player, and assuming they aren't glued to the floor, you'd expect to be able to push them around.
+
+Instead of applying the collision response solely to the player, what if we split it evenly between the player and monster, so that a collision pushes both player and monster in opposite directions?
+
+In order to move the monsters, we'll need to change the loop a bit. Because the monsters are structs (value types) we can't modify the monster variable in the loop - we'll need to make a copy of the monster, modify that, and then assign it back to the correct position in the `monsters` array. Replace the `// Handle collisions` block we just wrote with the following:
+
+```swift
+// Handle collisions
+for i in monsters.indices {
+ var monster = monsters[i]
+ if let intersection = player.intersection(with: monster) {
+ player.position -= intersection
+ }
+ monsters[i] = monster
+}
+```
+
+Instead of looping over the monsters directly, we are now looping over the *indices* of the array, which allows us to replace the original monster (that's what the `monsters[i] = monster` line is doing at the end of the loop[[2]](#footnote2). Still in the same block of code, replace the line:
+
+```swift
+player.position -= intersection
+```
+
+with:
+
+```swift
+player.position -= intersection / 2
+monster.position += intersection / 2
+```
+
+Now, when we bump into a monster, it will be pushed back. Because we're also being pushed back, we will move more slowly when pushing, making the interaction feel more realistic.
+
+Try running the game now and pushing some monsters around. Pretty soon, you'll notice we've introduced a new problem.
+
+![Monster stuck inside the wall](Images/MonsterInWall.png)
+
+### They're in the Walls!
+
+Now that we can push the monsters, we can push them *through walls*. So far, all our collision handling has been player-centric, but now that monsters can move (even if it's not of their own volition), they need their own collision logic.
+
+We already have a method to detect collisions between the player and the walls, and since the player and monster are both represented by rectangles, we can re-use that collision logic. The problem is that currently the `intersection(with:)` method we need is a member of the `Player` type, and we don't really want to copy and paste it into `Monster`. It's time for a refactor.
+
+The `Monster` and `Player` types have a bunch of properties and behavior in common, so it makes sense to allow them to share these via a common abstraction. In a traditional object-oriented language like Objective-C or Java, we might have done this by making them inherit from a common superclass, but in our game these are structs rather than classes, so they don't support inheritance.
+
+We could make them into classes, but this has all sorts of downsides, such as losing the ability to magically add serializion via the `Codable` protocol, or easily adding multithreading due to the data structures all being immutable, so let's not do that. Fortunately, Swift has a nice mechanism for sharing logic between value types, in the form of *protocol extensions*.
+
+Create a new file in the Engine module called `Actor.swift`, with the following contents:
+
+```swift
+public protocol Actor {
+ var radius: Double { get }
+ var position: Vector { get set }
+}
+```
+
+"Actor" is a generic term for entities such as the player, non-player-characters, scenery, or anything else that potentially moves or has agency within the game, as per this description from [Game Coding Complete](https://www.amazon.co.uk/gp/product/1133776574/ref=as_li_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1133776574&linkCode=as2&tag=charcoaldesig-21&linkId=5b73f74ee5a9de652e35f8c305266802):
+
+> A game actor is an object that represents a single entity in your game world. It could be an ammo pickup, a tank, a couch, an NPC, or anything you can think of. In some cases, the world itself might even be an actor. It’s important to define the parameters of game actors and to ensure that they are as flexible and reusable as possible.
+
+The `Actor` protocol we've defined makes the guarantee that any object conforming to it will have a read-only `radius` property, and a read-write `position`, which is everything we need to implement collision handling.
+
+In `Player.swift`, replace the following line:
+
+```swift
+public struct Player {
+```
+
+with:
+
+```swift
+public struct Player: Actor {
+```
+
+That says that `Player` conforms to the `Actor` protocol. We don't have to do anything else to conform to the protocol because `Player` already has a `radius` and `position` property.
+
+Next, cut the entire `public extension Player { ... }` block from `Player.swift`, and paste it into the `Actor.swift` file. Then replace all references to `Player` or `Monster` with `Actor`. The result should look like this:
+
+```swift
+public extension Actor {
+ var rect: Rect {
+ let halfSize = Vector(x: radius, y: radius)
+ return Rect(min: position - halfSize, max: position + halfSize)
+ }
+
+ func intersection(with map: Tilemap) -> Vector? {
+ let minX = Int(rect.min.x), maxX = Int(rect.max.x)
+ let minY = Int(rect.min.y), maxY = Int(rect.max.y)
+ var largestIntersection: Vector?
+ for y in minY ... maxY {
+ for x in minX ... maxX where map[x, y].isWall {
+ let wallRect = Rect(
+ min: Vector(x: Double(x), y: Double(y)),
+ max: Vector(x: Double(x + 1), y: Double(y + 1))
+ )
+ if let intersection = rect.intersection(with: wallRect),
+ intersection.length > largestIntersection?.length ?? 0 {
+ largestIntersection = intersection
+ }
+ }
+ }
+ return largestIntersection
+ }
+
+ func intersection(with actor: Actor) -> Vector? {
+ return rect.intersection(with: actor.rect)
+ }
+}
+```
+
+Now we'll do the same for `Monster`. In `Monster.swift` replace:
+
+```swift
+public struct Monster {
+```
+
+with:
+
+```swift
+public struct Monster: Actor {
+```
+
+Then you can delete the `public extension Monster { ... }` block completely, since the computed `rect` property is now inherited from the `Actor` protocol.
+
+That's the beauty of Swift's protocol extensions. In Objective-C it was possible for classes to conform to a common protocol, but they couldn't inherit behavior from it. In Swift however, we can extend the protocol with functionality that is then available to all types that conform to it. That even works for value types like struct that aren't polymorphic and don't support traditional inheritance.
+
+Anyway, enough about how awesome Swift is - let's get on with the task at hand. In `World.update()`, add the following code inside the `for` loop, just before the line `monsters[i] = monster`:
+
+```swift
+while let intersection = monster.intersection(with: map) {
+ monster.position -= intersection
+}
+```
+
+This applies the same wall collision logic we used for `Player` to every `Monster` in the map. Collision with walls is handled *after* collisions with the player, so that it overrides previous collision response handling - the monster may end up being pushed back into the player, but they shouldn't get pushed through a wall.
+
+If you run the game again though, you'll see that's not quite the case.
+
+![Monster sprite slightly intersecting the wall](Images/MonsterInWall2.png)
+
+It's no longer possible to push the monster right through the wall as before, but they still seem to be able to get stuck a little way into it - why is that?
+
+In short, it's because we've used the wrong *radius*. We set the monster's radius to 0.25, meaning that it occupies half a tile's width. But the sprite graphic we are using for the monster is 14 pixels wide inside a 16-pixel texture. Since the texture is one tile wide, that means the monster is actually 14/16ths of a tile wide - equivalent to 0.875 tiles.
+
+
+
+If the collision rectangle we use for the monsters is smaller than the sprite, the sprite will clip into the walls when the monster bumps up agains them. If we don't want that to happen, the correct radius to use for the monsters is 0.4375 (half of 0.875). In `Monster.swift`, change the line:
+
+```swift
+public let radius: Double = 0.25
+```
+
+to:
+
+```swift
+public let radius: Double = 0.4375
+```
+
+The monsters should no longer intersect the walls, however much you push them. But what about *other monsters*? It shouldn't be possible to push one monster right through another one, but right now there's nothing to prevent that. The collision handling should be able to prevent any monster from intersecting another.
+
+In order to implement monster-monster collisions we'll need to do a pairwise collision test between each monster and every other monster. For that we'll need to add a second loop through all of the monsters, *inside* the first loop.
+
+Back inside the `World.update()` method, find the line:
+
+```swift
+while let intersection = monster.intersection(with: map) {
+```
+
+Just above that line, add the following:
+
+```swift
+for j in monsters.indices where i != j {
+ if let intersection = monster.intersection(with: monsters[j]) {
+ monster.position -= intersection / 2
+ monsters[j].position += intersection / 2
+ }
+}
+```
+
+This inner loop[[3]](#footnote3) runs through the monsters again, checking for an intersection between the current monster from the outer loop (`monsters[i]`) and the current monster in the inner loop (`monsters[j]`). The `where i != j` ensures we don't check if a monster is colliding with itself (which would always be true, leading to some interesting bugs).
+
+If you're familiar with [Big O notation](https://en.wikipedia.org/wiki/Big_O_notation) for describing algorithmic complexity, this algorithm has a a *Big O* of O(n2), where *n* is the total number of monsters.
+
+An algorithm with O(n2) complexity slows down rapidly with the number of elements, and is generally considered a *bad thing*. Modern physics engines use a variety of tricks to cut down the number of collision tests they need to perform, such as subdividing objects into buckets and only checking for collisions between objects in the same or neighboring buckets.
+
+With such a small map and so few monsters, n2 will never get large enough to be a problem, so we won't worry about it for now. There is, however, a trivial optimization we can make which will halve the work we are doing.
+
+We're currently comparing every pair of monsters twice because we always compare `monsters[i]` with `monsters[j]` for every value of `i` and `j`. But if we've already compared `monsters[1]` with `monsters[2]` we don't *also* need to compare `monsters[2]` with `monsters[1]` because they're equivalent.
+
+So it follows that for the inner `for` loop, we only need to loop over monsters that we have not *already* covered in the outer loop, so instead of looping through every index apart from `i`, we can just loop through every index *after* `i`. In `World.update()` replace the line:
+
+```swift
+for j in monsters.indices where i != j {
+```
+
+with:
+
+```swift
+for j in i + 1 ..< monsters.count {
+```
+
+That should take care of monster-monster collisions. Run the app again and you should find that you now can't push monsters through walls *or* each other.
+
+The `// Handle collisions` code is maybe not the most beautiful we've written, but I'll leave refactoring it as an exercise for the reader because we have more interesting fish to fry.
+
+### Enemy State
+
+The monsters have had enough of being pushed around - it's time they got their revenge. The only problem is, they don't actually have the power of independent thought. We need to give them some *artificial intelligence*.
+
+There's an old joke in tech circles that when companies talk about their new software using "advanced AI", they probably mean it has a giant bunch of `if`/`else` statements. And it's funny because it's true - if you are trying to build a program that can "reason" about a situation and decide on a course of action, the simplest approach in most cases is to figure out all of the possible scenarios and then build a big `if` or `switch` statement with the appropriate responses to each of them.
+
+The AI for non-player characters (including enemies) in a game can be built around a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine). At any given point in time, the character is in a particular state. While in that state they have a certain behavior. Certain events or *triggers* will cause them to transition to a different state, with different behavior.
+
+In Swift, the state machine can be implemented as a `switch` over an enum, with a bunch of `if`/`else` cases for handling state transitions. For now, we'll just define two states for the monsters - *idle* and *chasing*.
+
+Each monster will start out in the *idle* state. When they see the player they will switch to the *chasing* state. In the chasing state, the monster will pursue the player until it can't see them anymore, at which point it will revert back to idle.
+
+
+
+In `Monster.swift`, add the following code to the top of the file:
+
+```swift
+public enum MonsterState {
+ case idle
+ case chasing
+}
+```
+
+Then add a `state` property to the `Monster` itself:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var state: MonsterState = .idle
+
+ ...
+}
+```
+
+In `World.update()`, insert the following block of code above the `// Handle collisions` section:
+
+```swift
+// Update monsters
+for i in 0 ..< monsters.count {
+ var monster = monsters[i]
+ monster.update(in: self)
+ monsters[i] = monster
+}
+```
+
+Then, back in `Monster.swift` add the following block of code to the bottom of the file:
+
+```swift
+public extension Monster {
+ mutating func update(in world: World) {
+ switch state {
+ case .idle:
+
+ case .chasing:
+
+ }
+ }
+}
+```
+
+Here we have the outline for the AI routine - now we need to write the implementations for the two states. In `idle` mode, all the monster needs to do is wait until it sees the player. That means we need a method to detect if the monster can see the player.
+
+Since the monsters don't currently have a direction (they always face the player), we don't need to worry about their field of view. The only reason a monster *wouldn't* be able to see the player is if there was a wall between them.
+
+Still in `Monster.swift` add a `canSeePlayer()` method:
+
+```swift
+public extension Monster {
+ ...
+
+ func canSeePlayer(in world: World) -> Bool {
+
+ }
+}
+```
+
+The first thing we'll need to do is create a `Ray` from the monster to the `Player`. We've done this a few times now, so the code should require no explanation. Add the following lines to the start of the `canSeePlayer()` method:
+
+```swift
+let direction = world.player.position - position
+let playerDistance = direction.length
+let ray = Ray(origin: position, direction: direction / playerDistance)
+```
+
+We need to check if the view of the player is obstructed by a wall. We already have a way to check if a ray hits a wall, using the `Tilemap.hitTest()` method we wrote in [Part 3](Part3.md). Add the following line to the `canSeePlayer()` method:
+
+```swift
+let wallHit = world.map.hitTest(ray)
+```
+
+Now we have the point at which the ray intersects the map, we just need to check if the distance at which that occurs is closer or farther than the player. Add the following lines to complete the `canSeePlayer()` method:
+
+```swift
+let wallDistance = (wallHit - position).length
+return wallDistance > playerDistance
+```
+
+Using that method, we can now implement the monster's AI. Replace the empty switch cases in `Monster.update()` with the following:
+
+```swift
+switch state {
+case .idle:
+ if canSeePlayer(in: world) {
+ state = .chasing
+ }
+case .chasing:
+ guard canSeePlayer(in: world) else {
+ state = .idle
+ break
+ }
+}
+```
+
+So now if the monster sees the player it will enter the `chasing` state, and if it it can't see the player anymore it will return to the `idle` state. Just one last thing to do - we need to make it actually chase the player!
+
+### The Chase is On
+
+The `Player` has a `speed` property to control their maximum speed. Let's add one to `Monster` too:
+
+```swift
+public struct Monster: Actor {
+ public let speed: Double = 0.5
+ ...
+}
+```
+
+The player has a maximum speed of `2`, but we've set the monster's maximum speed to `0.5`. They're supposed to be zombies, so we don't really want them sprinting around. We may as well add a `velocity` property too while we're here:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var position: Vector
+ public var velocity: Vector = Vector(x: 0, y: 0)
+ public var state: MonsterState = .idle
+
+ ...
+}
+```
+
+In the `Monster.update()` method, add the following code inside `case .idle:`, after the `if` statement:
+
+```swift
+velocity = Vector(x: 0, y: 0)
+```
+
+Then, inside `case .chasing:`, after the `guard` statement, add the following:
+
+```swift
+let direction = world.player.position - position
+velocity = direction * (speed / direction.length)
+```
+
+Finally, back in `World.upate()` in the `// Update monsters` section, just before the line `monsters[i] = monster`, add the following:
+
+```swift
+monster.position += monster.velocity * timeStep
+```
+
+With these additions, each monster will move towards the player whenever it can see them. Run the game again to check everything is working as expected (prepare to be mobbed!)
+
+![Monsters crowding the player](Images/MonsterMob.png)
+
+### An Animated Performance
+
+It's pretty eery seeing the monsters float towards you like ghosts, but they are supposed to be zombies and zombies *stagger*, they don't float. It would help to improve the realism if the monsters had some animation.
+
+Start by adding some walking frames for the monster.
+
+
+
+The walking animation has four frames, but two of them are the same as the standing image we already have. Feel free to use as many or as few frames as needed for your own animation (you can also just use [these ones](https://github.com/nicklockwood/RetroRampage/tree/Part6/Source/Rampage/Assets.xcassets/) if you want).
+
+Add the images for the walking animation to XCAssets, then in `Textures.swift` extend the `Texture` enum with these two additional cases, matching the names of the image assets:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case monster
+ case monsterWalk1, monsterWalk2
+}
+```
+
+We'll need a new type to represent the animation itself. In the Engine module, create a new file called `Animation.swift` with the following contents:
+
+```swift
+public struct Animation {
+ public let frames: [Texture]
+ public let duration: Double
+
+ public init(frames: [Texture], duration: Double) {
+ self.frames = frames
+ self.duration = duration
+ }
+}
+```
+
+Then in `Monster.swift` add an `animation` property to `Monster`:
+
+```swift
+public struct Monster: Actor {
+ ...
+ public var state: MonsterState = .idle
+ public var animation: Animation = .monsterIdle
+
+ ...
+}
+```
+
+It may seem like the monster only has one animation, but it really has two - it's just that the idle/standing animation only has a single frame. Still in `Monster.swift`, add the following code to the bottom of the file:
+
+```swift
+public extension Animation {
+ static let monsterIdle = Animation(frames: [
+ .monster
+ ], duration: 0)
+ static let monsterWalk = Animation(frames: [
+ .monsterWalk1,
+ .monster,
+ .monsterWalk2,
+ .monster
+ ], duration: 0.5)
+}
+```
+
+We've added the animations as static constants on the `Animation` type because it means we can conveniently reference them using dot syntax, thanks to Swift's type inference. But since these animations are specific to the monster sprite, it makes sense to keep them together in with the other `Monster`-specific code rather than in the `Animation.swift` file.
+
+Now, in the `Monster.update()` method, modify the state machine again to swap the animations:
+
+```swift
+switch state {
+case .idle:
+ if monster.canSeePlayer(in: self) {
+ state = .chasing
+ animation = .monsterWalk
+ }
+ velocity = Vector(x: 0, y: 0)
+case .chasing:
+ guard monster.canSeePlayer(in: self) else {
+ state = .idle
+ animation = .monsterIdle
+ break
+ }
+ let direction = world.player.position - position
+ velocity = direction * (speed / direction.length)
+}
+```
+
+That's the data model side of animations taken care of, but what about the *rendering* side? Right now the renderer is just hard-coded to show the monster's standing image for each sprite. The renderer is going to need to know which animation frame to draw.
+
+Open `Billboard.swift` and add a `texture` property and initializer argument:
+
+```swift
+public struct Billboard {
+ public var start: Vector
+ public var direction: Vector
+ public var length: Double
+ public var texture: Texture
+
+ public init(start: Vector, direction: Vector, length: Double, texture: Texture) {
+ self.start = start
+ self.direction = direction
+ self.length = length
+ self.texture = texture
+ }
+}
+```
+
+Then, in `Renderer.draw()`, in the `// Draw sprites` section, replace the line:
+
+```swift
+let spriteTexture = textures[.monster]
+```
+
+with:
+
+```swift
+let spriteTexture = textures[sprite.texture]
+```
+
+It's now up to the `World` to supply the correct frame texture for each sprite billboard at a given point in time, but which frame should it select? We need to introduce a concept of the *current frame* for an animation.
+
+Back in `Animation.swift`, add a `time` property to the `Animation` struct:
+
+```swift
+public struct Animation {
+ public let frames: [Texture]
+ public let duration: Double
+ public var time: Double = 0
+
+ ...
+}
+```
+
+The `time` defaults to zero (the start of the animation), but it's a `var`, so we can advance the time of any given `Animation` instance in order to scrub through the frames. Still in `Animation.swift`, add the following extension method:
+
+```swift
+public extension Animation {
+ var texture: Texture {
+ guard duration > 0 else {
+ return frames[0]
+ }
+ let t = time.truncatingRemainder(dividingBy: duration) / duration
+ return frames[Int(Double(frames.count) * t)]
+ }
+}
+```
+
+This fetches the current frame texture for `time`. It calculates this by dividing `time` by the animation's `duration`, and then multiplying by the `frames` count. The `truncatingRemainder(dividingBy: duration)` means that if `time` is greater than `duration`, the animation will loop back around.
+
+In `World.swift`. update the computed `sprites` var, replacing the lines:
+
+```swift
+Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1
+)
+```
+
+with:
+
+```swift
+Billboard(
+ start: monster.position - spritePlane / 2,
+ direction: spritePlane,
+ length: 1,
+ texture: monster.animation.texture
+)
+```
+
+Each sprite billboard will now include the current animation frame for that monster as its texture. All that's left now is to actually advance the animation times as the game is playing. Still in `World.swift`, in the `// Update monsters` block inside the `update()` method, add the following line just before `monsters[i] = monster`:
+
+```swift
+monster.animation.time += timeStep
+```
+
+And that's it! Run the game now and you'll see the monsters striding towards you menacingly (it's actually a pretty creepy).
+
+### Space Invaders
+
+As terrifying as it is to be crowded by a bunch of zombies, it's a bit anticlimactic if all they do is try to stand uncomfortably close to you. Let's add a new state to the monster AI. First, we'll need a new animation:
+
+
+
+Add the images to XCAssets, then add the new cases to the `Texture` enum in `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case monsterScratch1, monsterScratch2, monsterScratch3, monsterScratch4
+ case monsterScratch5, monsterScratch6, monsterScratch7, monsterScratch8
+}
+```
+
+Next, at the bottom of the `Monster.swift` file, add a new static property for the `monsterScratch` animation:
+
+```swift
+public extension Animation {
+ ...
+
+ static let monsterScratch = Animation(frames: [
+ .monsterScratch1,
+ .monsterScratch2,
+ .monsterScratch3,
+ .monsterScratch4,
+ .monsterScratch5,
+ .monsterScratch6,
+ .monsterScratch7,
+ .monsterScratch8,
+ ], duration: 0.8)
+}
+```
+
+That's the animation taken care of - now we need to upgrade the monster's AI. At the top of `Monster.swift`, add a `scratching` case to the `MonsterState` enum:
+
+```swift
+public enum MonsterState {
+ case idle
+ case chasing
+ case scratching
+}
+```
+
+The monster can only scratch the player if they're in range, so we'll need some logic to determine that. This doesn't need to be anything fancy, we can just check if the distance between the player and monster is below a certain threshold.
+
+Still in `Monster.swift` add the following method just below the `canSeePlayer()` method we added earlier:
+
+```swift
+func canReachPlayer(in world: World) -> Bool {
+ let reach = 0.25
+ let playerDistance = (world.player.position - position).length
+ return playerDistance - radius - world.player.radius < reach
+}
+```
+
+This computes the distance from the monster to the player (we don't care about the direction) and then compares it with a `reach` constant that determines how far the monster can reach out when attacking.
+
+We subtract the player and monster radii from the `playerDistance` before comparing with `reach`. The player and monster cannot actually stand in exactly the same spot because of collision handling, so `reach` is measured relative to their minimum separation distance, which is the sum of their radii.
+
+In `Monster.update()`, inside `case .chasing:`, find the following block of code:
+
+```swift
+guard canSeePlayer(in: world) else {
+ state = .idle
+ animation = .monsterIdle
+ break
+}
+```
+
+Just below it, before the `let direction = ...`, add the following:
+
+```switch
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+}
+```
+
+Finally, add this extra case to the end of the `switch` block to complete the monster attack logic:
+
+```swift
+case .scratching:
+ guard canReachPlayer(in: world) else {
+ state = .chasing
+ animation = .monsterWalk
+ break
+ }
+```
+
+The monsters can now both chase and attack the player (albeit without actually doing any damage). Run the game again, but prepare for a scare!
+
+![Monsters attacking](Images/MonstersAttacking.png)
+
+That's it for Part 6. In this part we:
+
+* Added collision handling for the monsters
+* Extracted common code between the `Player` and `Monster` classes
+* Created a state machine to act as the monster's brain
+* Gave the monsters the ability to chase and attack the player
+* Added walking and attack animations for the monsters
+
+In [Part 7](Part7.md) we'll raise the stakes a bit by giving the monsters the ability to hurt (and eventually kill) the player.
+
+### Reader Exercises
+
+1. Can you add an idle animation for the monsters? Perhaps every few seconds they could blink?
+
+2. As soon as they lose sight of you, the monsters forget you existed. Make them a little bit smarter by having them keep going until they reach the place where they last saw the player.
+
+3. Now that we've added animation to the sprites, could you use the same approach to make an animated wall tile? Maybe a ventilation shaft with a spinning fan? Or a computer terminal with flashing lights?
+
+
+
+[[1]](#reference1) Beauty is in the eye of the beholder, OK?
+
+[[2]](#reference2) The Swift experts among you will probably be turning up your noses at this unapologetically imperative code and wondering why I don't just use `Array.map()` like a civilized person. This will be explained, so try to contain your disgust for now and read on.
+
+[[3]](#reference3) The inner loop is why I didn't use `map()`. There may still be a way to solve this using functional programming, but I didn't want to write it and I *definitely* didn't want to have to explain it.
diff --git a/Tutorial/Part7.md b/Tutorial/Part7.md
new file mode 100644
index 0000000..09cc015
--- /dev/null
+++ b/Tutorial/Part7.md
@@ -0,0 +1,854 @@
+## Part 7: Death and Pixels
+
+In [Part 6](Part6.md) we added animations to the monsters inhabiting the maze, and gave them rudimentary intelligence so they could hunt and attack the player. The complete code for Part 6 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part6.zip).
+
+Although the monsters appear to attack, their blows don't actually have any effect on the player. Time to fix that.
+
+### Healthy Living
+
+In order for the player to be hurt (and eventually die), they need to have some sort of health meter. In `Player.swift`, add a `health` property to the `Player` and in the initializer set its starting value to `100`:
+
+```swift
+public struct Player: Actor {
+ public let speed: Double = 2
+ public let turningSpeed: Double = .pi
+ public let radius: Double = 0.25
+ public var position: Vector
+ public var velocity: Vector
+ public var direction: Vector
+ public var health: Double
+
+ public init(position: Vector) {
+ self.position = position
+ self.velocity = Vector(x: 0, y: 0)
+ self.direction = Vector(x: 1, y: 0)
+ self.health = 100
+ }
+}
+```
+
+In the same file, add the following convenience extension:
+
+```swift
+public extension Player {
+ var isDead: Bool {
+ return health <= 0
+ }
+}
+```
+
+### Hurt Me Plenty
+
+Now that the player has health, the monster needs a way to deplete it.
+
+Changes to the player are currently handled inline within the `World.update()` method. This is a *mutating* method, so it has the ability to make changes to the world and its contents.
+
+Monster behavior is delegated out to the `Monster.update()` method. This method is also marked as mutating - which means that it can make changes to the monster itself - but the `world` parameter it receives is immutable, which means it cannot make changes to the world.
+
+In order for the monster to be able to hurt the player, we need some way for its behavior to affect objects outside of itself. We could just inline the monster update logic inside `World.update()`, but that method is already doing more than it should so we don't really want to overload it any further.
+
+Instead, we can pass the world to `Monster.update()` as an `inout` parameter, thereby making it mutable. In `Monster.swift`, replace the line:
+
+```swift
+mutating func update(in world: World) {
+```
+
+with:
+
+```swift
+mutating func update(in world: inout World) {
+```
+
+Then, in `World.update()`, in the `// Update monsters` section, change the line:
+
+```swift
+monster.update(in: self)
+```
+
+to:
+
+```swift
+monster.update(in: &self)
+```
+
+This means that we are passing the world *by reference*, giving `Monster.update()` the ability to make changes to the world, along with anything in it (such as the player).
+
+We don't really want the monster to just manipulate the player's health directly though, because harming the player will potentially have side-effects in the game logic that the monster shouldn't need to know about.
+
+Let's lock the interface down a bit so that we don't accidentally break encapsulation. Find the following property declarations at the top of the `World` struct:
+
+```swift
+public var monsters: [Monster]
+public var player: Player!
+```
+
+And change them to:
+
+```swift
+public private(set) var monsters: [Monster]
+public private(set) var player: Player!
+```
+
+Then, still in `World.swift`, add the following method to the bottom of the extension:
+
+```swift
+mutating func hurtPlayer(_ damage: Double) {
+ player.health -= damage
+}
+```
+
+That gives the monster a way to hurt the player in a structured way without directly manipulating properties. Next, in `Monster.update()`, add the following code to the end of the `case .scratching:` block, just after the guard statement:
+
+```swift
+world.hurtPlayer(10)
+```
+
+### Game Over Man, Game Over!
+
+I mentioned earlier that reducing player health might have side-effects. One such side-effect is that if the player's health drops to zero, the game should end. This is a change that affects the entire world, which is why `hurtPlayer()` is a method of the world itself and not the `Player` type.
+
+Eventually, the player dying will have effects that extend even beyond the game world, such as displaying a "Game Over" screen, and presenting a menu for the player to quit or try again, etc. We haven't built any of the infrastructure for that yet, so for now we'll just reset the world to its initial conditions.
+
+The world is currently created by `ViewController` (in the platform layer) which might seem like it would cause a headache if we need to trigger re-initialization from *inside* the world itself. Fortunately, the initial conditions for the world are all encoded within the `TileMap`, which is never actually modified.
+
+Since the world already contains all the information needed to reset itself, we can go ahead and add a method to do that. Near the top of `World.swift`, find the `init()` method and copy the following lines:
+
+```swift
+self.monsters = []
+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]
+ switch thing {
+ case .nothing:
+ break
+ case .player:
+ self.player = Player(position: position)
+ case .monster:
+ monsters.append(Monster(position: position))
+ }
+ }
+}
+```
+
+Create a new mutating method in the extension block called `reset()` and paste the copied code into it:
+
+```swift
+mutating func reset() {
+ ...
+}
+```
+
+Then, still in `World.swift`, replace the `init()` implementation with the following:
+
+```swift
+public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ reset()
+}
+```
+
+That gives us the means to reinitialize the world in its starting state when the player dies. Find the `World.hurtPlayer()` function we created earlier and append the following code inside it:
+
+```swift
+if player.isDead {
+ reset()
+}
+```
+
+Now go ahead and run the game.
+
+*Hmm, that's odd...* as soon the the monster touches the player, everything starts flickering and the player can't move. What's going on?
+
+There are actually *two* problems here. The first is that the game is being reset every time the monster touches the player. The second is that the monster itself is unaffected by the reset, and continues to attack the player immediately after player re-spawns.
+
+### One Punch Man
+
+The monster only does 10 damage per swipe, so how come the player dies instantly? Let's look at the monster attack logic again:
+
+```swift
+case .scratching:
+ guard canReachPlayer(in: world) else {
+ state = .chasing
+ animation = .monsterWalk
+ break
+ }
+ world.hurtPlayer(10)
+}
+```
+
+Here is the problem - as long as the monster is in `scratching` mode and in range of the player, `hurtPlayer()` will be called on every update. Since `update()` is called ~120 times per second, it's no wonder that the player drops dead almost instantly.
+
+We will need to keep track of when the monster last struck the player, and only apply damage again if a minimum time has passed - we'll call this the *cooldown* period.
+
+The scratching animation is 0.8 seconds long, and the monster swipes at the player twice during each cycle of the attack animation, so a cooldown period of 0.4 seconds would make sense.
+
+In `Monster.swift`, add the following two properties to the `Monster` struct:
+
+```swift
+public let attackCooldown: Double = 0.4
+public private(set) var lastAttackTime: Double = 0
+```
+
+Then in `Monster.update()`, in the `case .scratching:` block, replace the line:
+
+```swift
+world.hurtPlayer(10)
+```
+
+with:
+
+```swift
+if animation.time - lastAttackTime >= attackCooldown {
+ lastAttackTime = animation.time
+ world.hurtPlayer(10)
+}
+```
+
+Then, just above in the `case .chasing:` block, find the code:
+
+```swift
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+}
+```
+
+And add the following line inside the `if` block:
+
+```swift
+lastAttackTime = -attackCooldown
+```
+
+We set the `lastAttackTime` to `-attackCooldown` when the `scratching` state begins to ensure that the `animation.time - lastAttackTime >= attackCooldown` condition is initially true, otherwise the monster's first swipe wouldn't do any damage.
+
+If you run the game again, you should find that the player can take a few punches before the game resets. Now for the second problem - why isn't the monster being reset with the rest of the world?
+
+### Left Behind
+
+Take a look at the `// Update monsters` loop in the `World.update()` method:
+
+```swift
+for i in 0 ..< monsters.count {
+ var monster = monsters[i]
+ monster.update(in: &self)
+ monster.position += monster.velocity * timeStep
+ monster.animation.time += timeStep
+ monsters[i] = monster
+}
+```
+
+Inside the loop, we copy the monster at the current index into a local variable, then call its `update()` method, then modify its `position` and `animation` before assigning it back to the `monsters` array.
+
+If the player is killed during `Monster.update()` the world will be reset immediately, but the local `monster` var inside the `// Update monsters` loop will be unaffected by the reset, and will then be written back into the `monsters` array. That's why the monster is still in the same position after the reset occurs.
+
+Maybe it's not such a good idea for the `hurtPlayer()` method to reset the world immediately? Remove the following lines from the `World.hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ reset()
+}
+```
+
+Then, at the very top of the `World.update()` method, add the following:
+
+```swift
+// Update player
+if player.isDead {
+ reset()
+ return
+}
+```
+
+In this way, the reset is deferred until the next update cycle after the player is killed, and performed before any other updates so it can't introduce inconsistencies in the world model.
+
+Run the game again and you should find that it now resets correctly when the player dies.
+
+### Cause and Effect
+
+The visual impact of being hacked to death by a ravenous zombie is currently rather underwhelming. Eventually we'll add an on-screen health meter, but in the meantime it would be good to at least have *some* visual indication of player damage.
+
+Modern games use a variety of effects to indicate the player being hurt - camera shake, blood spatter on the screen, blurred vision, etc. Wolfenstein 3D used a brief red flash of the screen, and so will we.
+
+Create a new file in the Engine module called `Effect.swift` with the following contents:
+
+```swift
+public enum EffectType {
+ case fadeIn
+}
+
+public struct Effect {
+ public let type: EffectType
+ public let color: Color
+ public let duration: Double
+ public var time: Double = 0
+
+ public init(type: EffectType, color: Color, duration: Double) {
+ self.type = type
+ self.color = color
+ self.duration = duration
+ }
+}
+```
+
+The `Effect` type is quite similar to the `Animation` type we added in [Part 6](Part6.md), but instead of an array of image frames it has a `type` and `color`. Just below the `Effect` struct, add the following extension:
+
+```swift
+public extension Effect {
+ var isCompleted: Bool {
+ return time >= duration
+ }
+
+ var progress: Double {
+ return min(1, time / duration)
+ }
+}
+```
+
+These convenience properties allow us to easily get the *progress* of the effect (a normalized representation of how far it is from completion) and whether it has already completed.
+
+Effects will span multiple frames, so we'll need to store them somewhere. It's debatable whether they really *exist* as physical objects within the world, but it seems like the logical place to put them for now. In `World.swift`, add an `effects` property to the `World` struct, and initialize it inside `World.init()`:
+
+```swift
+public struct World {
+ public let map: Tilemap
+ public private(set) var monsters: [Monster]
+ public private(set) var player: Player!
+ public private(set) var effects: [Effect]
+
+ public init(map: Tilemap) {
+ self.map = map
+ self.monsters = []
+ self.effects = []
+ reset()
+ }
+}
+```
+
+Like other objects in the world, effects will need to be updated over time. It seems like a good idea to update effects first, before any other updates because we don't want events like a world reset (which returns from the `update()` method early) to cause any ongoing effects to skip frames.
+
+Insert the following code at the very top of the `World.update()` method, above the `// Update player` section:
+
+```swift
+// Update effects
+effects = effects.map { effect in
+ var effect = effect
+ effect.time += timeStep
+ return effect
+}
+```
+
+This code is a little different from the other update loops. As with animations, we're incrementing the elapsed `time` property of each effect, but this time we're using `map()` to replace the original `effects` array, instead of a `for` loop.
+
+Functional programming is cool and all, but *why now*?
+
+Well, there's something else we need to do in this loop in addition to updating the effects - once an effect is complete we want to remove it from the array. That's awkward and error-prone to do with a traditional `for` loop because removing an item messes up the index (unless you resort to tricks like iterating the loop backwards).
+
+But with Swift's functional programming extensions, we can do this very cleanly using `compactMap()`. The `compactMap()` function works like a combination of `map()` (to replace the values in a collection) and `filter()` (to conditionally remove them). In the `// Update effects` block we just added, replace the line:
+
+```swift
+effects = effects.map { effect in
+```
+
+with:
+
+```swift
+effects = effects.compactMap { effect in
+ if effect.isCompleted {
+ return nil
+ }
+```
+
+So now we return `nil` for any effect that has completed, thereby removing it from the `effects` array. This check is performed *before* incrementing the time, so that the final state of the effect is displayed at least once before it is removed[[1]](#footnote1).
+
+The red flash effect should be triggered whenever the player is hurt. Still in `World.swift`, add the following line to the `hurtPlayer()` method:
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.2))
+```
+
+Finally, we need to actually draw the effect.
+
+Wolfenstein implemented the red screen flash using a trick called [color cycling](https://en.wikipedia.org/wiki/Color_cycling) (also known as *palette animation*), made possible by the game's use of an indexed color palette. It would have been expensive to tint every pixel on-screen, and also impractical to craft a palette that had multiple red-shifted shades of each color, but color cycling neatly solves both of these problems.
+
+The palette introduces a layer of indirection between the 8-bit color indexes drawn into the output buffer and the colors that actually appeared on screen. By gradually red-shifting all the colors in the palette over time, the image drawn on the screen automatically tinted to red without the game needing to actually needing to redraw anything. And shifting the palette only required changing the 256 values in the palette itself, not the 64,000 pixels on screen[[2]](#footnote2).
+
+Pretty clever, right? Unfortunately, since we're using 32-bit "true" color instead of an indexed palette, we can't use the same trick[[3]](#footnote3). *Fortunately*, computers are immensely fast now so we can just use a brute-force approach and blend every pixel of the output bitmap.
+
+In `Renderer.swift` add the following code to the bottom of the `draw()` function (be careful to put it *outside* the `for` loop, as the nested closing braces can be confusing):
+
+```swift
+// Effects
+for effect in world.effects {
+ let color = effect.color
+ for y in 0 ..< bitmap.height {
+ for x in 0 ..< bitmap.width {
+ bitmap.blendPixel(at: x, y, with: color)
+ }
+ }
+}
+```
+
+Run the game and you should see the screen blink red every time the monster swipes at the player. The effect isn't very pretty yet though - we need to add the fading effect. In the `// Effects` code we just added, replace the line:
+
+```swift
+let color = effect.color
+```
+
+with:
+
+```swift
+var color = effect.color
+color.a = UInt8((1 - effect.progress) * 255)
+```
+
+If you recall from earlier, the `progress` property is the normalized effect time (in the range 0 - 1). We can use that to calculate the `a` (alpha) value for the color by subtracting it from 1 and multiplying by 255, so that the value falls from 255 to 0 over the duration of the effect.
+
+Run the game and... *crash*. Oh.
+
+
+
+The game crashed inside the `blendPixel()` function. A bit of investigation reveals that the cause of the crash is that we overflowed the range of `UInt8` - we tried to put a number bigger than 255 into it. How did that happen?
+
+If you recall, in [Part 5](Part5.md), when we originally added the `blendPixel()` method, we designed it to work with *premultiplied alpha* values, because it's better for performance. Premultiplied alpha is common when dealing with images, but it's a bit of a weird requirement for a public API.
+
+We can fix the crash by manually premultiplying the color components by the alpha value. In `Renderer.draw()`, inside the `// Effects` update loop, replace the lines:
+
+```swift
+var color = effect.color
+color.a = UInt8((1 - effect.progress) * 255)
+```
+
+with:
+
+```swift
+let opacity = 1 - effect.progress
+let color = Color(
+ r: UInt8(opacity * Double(effect.color.r)),
+ g: UInt8(opacity * Double(effect.color.g)),
+ b: UInt8(opacity * Double(effect.color.b)),
+ a: UInt8(opacity * 255)
+)
+```
+
+Now run the game again, and you should see the fading effect working correctly.
+
+![Screen flashing red as player is hurt](Images/RedFlashEffect.png)
+
+### It's a Trap
+
+We've fixed the bug, but it seems a bit dangerous to have such a sharp edge exposed in the `Bitmap` API. The behavior of `blendPixel()` is pretty unintuitive. There are three possible ways I can think of to improve it:
+
+* Change the name to more accurately reflect its purpose
+* Add bounds checking so it won't crash for out-of-range values
+* Make it private
+
+This is a performance-critical method (it's called at least once for every pixel on screen), so adding bounds checking is probably not desirable. It's also not clear that would be the right solution anyway since a bounds error here is indicative of a programming mistake - it should't happen with valid input.
+
+For now, we'll just make the `blendPixel()` method private. In `Bitmap.swift`, replace the line:
+
+```swift
+mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+```
+
+with:
+
+```swift
+private mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+```
+
+If the method is private, we can't call it from outside of `Bitmap`, but applying a translucent tint seems like a general enough operation that we can just make a method for it on `Bitmap` itself.
+
+Still in `Bitmap.swift`, add the following method to the bottom of the extension:
+
+```swift
+mutating func tint(with color: Color, opacity: Double) {
+ let color = Color(
+ r: UInt8(opacity * Double(color.r)),
+ g: UInt8(opacity * Double(color.g)),
+ b: UInt8(opacity * Double(color.b)),
+ a: UInt8(opacity * 255)
+ )
+ for y in 0 ..< height {
+ for x in 0 ..< width {
+ blendPixel(at: x, y, with: color)
+ }
+ }
+}
+```
+
+This is pretty much the same logic we used in the effects loop. One extra enhancement we can make while we're here though is to take the original alpha value from the `color` into account, so that the maximum opacity of the overlay can be adjusted by changing the alpha of the effect color.
+
+Insert the following line at the beginning of the `Bitmap.tint()` function we just added:
+
+```swift
+let opacity = min(1, max(0, Double(color.a) / 255 * opacity))
+```
+
+Now, back in `Renderer.swift`, replace the whole `// Effects` block at the end of the `draw()` function with the following code:
+
+```swift
+// Effects
+for effect in world.effects {
+ bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
+}
+```
+
+As you can see, this is a much nicer (and safer) API to work with.
+
+We mentioned above that the new `tint()` method supports using a partially transparent base color. In `World.hurtPlayer()`, replace the line:
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.2))
+```
+
+with:
+
+```swift
+let color = Color(r: 255, g: 0, b: 0, a: 191)
+effects.append(Effect(type: .fadeIn, color: color, duration: 0.2))
+```
+
+An alpha value of `191` is equivalent to 75% opacity (255 * 0.75), so now the flash of red when the monster strikes will start at 75% opacity instead of 100%. This isn't just a cosmetic improvement - it also means the red flash won't completely obscure your vision if you are attacked repeatedly.
+
+### Fade to Black Red
+
+The red flash effect works well for player damage, but the re-spawn when the player eventually dies is much too sudden. We need a longer, more pronounced effect for the player's death to give the them time to react to what has happened, and prepare for the next attempt.
+
+Instead of a short fade *from* red, when the player dies let's show a longer fade *to* red. Since this is basically just the same effect in reverse, we can re-use a lot of the logic we've already written.
+
+In `Effect.swift`, add a `fadeOut` case to the `EffectType` enum:
+
+```swift
+public enum EffectType {
+ case fadeIn
+ case fadeOut
+}
+```
+
+Then in `Renderer.draw()`, modify the `// Effects` block again as follows:
+
+```swift
+// Effects
+for effect in world.effects {
+ switch effect.type {
+ case .fadeIn:
+ bitmap.tint(with: effect.color, opacity: 1 - effect.progress)
+ case .fadeOut:
+ bitmap.tint(with: effect.color, opacity: effect.progress)
+ }
+}
+```
+
+As you can see, the code for `fadeOut` is nearly identical to the original `fadeIn` effect, except that we are no longer subtracting the `progress` value from 1, so the opacity goes up from 0 to 1 instead of down from 1 to 0.
+
+In `World.swift`, append the following code to the `hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ effects.append(Effect(type: .fadeOut, color: .red, duration: 2))
+}
+```
+
+This triggers a two-second fade to red. If you run the game again, you'll find this doesn't quite do what we wanted however. The fade works, but the game still resets instantly when the player dies, so the fade begins much too late.
+
+We need to defer the game reset until after the effect has completed. Let's take a look at the code responsible for resetting the game, which is located near the top of the `World.update()` method:
+
+```swift
+// Update player
+if player.isDead {
+ reset()
+ return
+}
+```
+
+In this block, replace the line:
+
+```swift
+if player.isDead {
+```
+
+with:
+
+```swift
+if player.isDead, effects.isEmpty {
+```
+
+Then try running the game again. When the player dies, the game now keeps running until the fade is complete. But now the fade never goes away - what's going on?
+
+### Rest in Peace
+
+Because the game now keeps running after the player is dead, the monster is going to keep hitting the player, causing more and more fade effects to trigger. Because of this, the `effects` array is never empty, so the game never actually resets.
+
+We could modify the monster AI so that it stops attacking the player once they're dead, but seeing the zombies continue to hack away at your corpse as you pass away is kind of cool[[4]](#footnote4), so let's solve it a different way. Add the following code to the top of the `World.hurtPlayer()` method:
+
+```swift
+if player.isDead {
+ return
+}
+```
+
+Now, once the player is dead, the monsters can't hurt them anymore (even though they'll keep trying). That means no further effects will be spawned, and the reset should happen on schedule.
+
+The last slightly weird thing about the way the death sequence works currently is that the player can keep moving after death. We need to keep the update loop running if we want the effects to keep animating and the monsters to keep attacking, but we don't necessarily need to keep accepting player input.
+
+To prevent the player wandering around after death (there are enough zombies in this game already), in `World.update()`, rearrange the `// Update player` code section as follows:
+
+```swift
+// Update player
+if player.isDead == false {
+ player.direction = player.direction.rotated(by: input.rotation)
+ player.velocity = player.direction * input.speed * player.speed
+ player.position += player.velocity * timeStep
+} else if effects.isEmpty {
+ reset()
+ return
+}
+```
+
+Now the player can finally die with bit of dignity. With, um, zombies hacking at their lifeless corpse.
+
+### Easy Does It
+
+The game-over fade looks pretty good, but it ends rather abruptly.
+
+We can mitigate this by adding a short fade-in after the reset. Still in `World.update()`, in the `// Update player` section, add the following just after the `reset()` line (or immediately before it - it doesn't really matter since effects aren't cleared by the reset):
+
+```swift
+effects.append(Effect(type: .fadeIn, color: .red, duration: 0.5))
+```
+
+You can play with the duration value a bit [[5]](#footnote5), but it still doesn't completely fix the problem. The issue is that the human eye is quite sensitive to discontinuities in animation timing. This is much more apparent with moving objects, but it also applies to fading effects.
+
+If we plot a graph of the change in opacity of the fade effect over time, we get the following:
+
+
+
+There is a sharp discontinuity in the middle where we transition from the `fadeOut` to `fadeIn` effects, which is why it doesn't quite feel right. We can smooth it out a bit by using a technique called *easing*.
+
+Easing (also known as *tweening*) is a (typically non-linear) mapping of time to an animated property such as position or scale (or in our case, opacity). If you were a Flash programmer in the early 2000s, the term *easing* may be almost synonymous with the name [Robert Penner](http://robertpenner.com/easing/), whose open-source easing functions have been ported to almost every language and platform you can think of.
+
+There are an infinite number of possible easing functions, but the most common variants are: *linear*, *ease-in*, *ease-out* and *ease-in-ease-out*.
+
+
+
+Real-life objects do not start and stop moving instantly, they have to accelerate up to speed and then decelerate back to a stop. The purpose of easing is to create realistic motion without all the complexities of a true physics engine, with simulated mass, friction, torque, etc.
+
+The best all-round easing function for general use is *ease-in-ease-out*, because it starts slow, reaches maximum speed at the half-way point, and then slows down again before stopping, just like a real object. That doesn't mean it's necessarily the best choice for every situation though.
+
+For the fade to red as the player dies, we want to start quickly at first and then go slower so that the effect seems more drawn out towards the end. For that, we can use the *ease-out* curve. And for the subsequent fade in we want the reverse effect (*ease-in*) so that the overall curve is smooth.
+
+Easing functions take a normalized input time and produce an output time that has been delayed or sped up according to the type of easing applied. We could implement these as free functions, but grouping them under a common namespace helps with autocompletion and prevents naming collisions.
+
+Create a new file in the Engine module called `Easing.swift` with the following contents:
+
+```swift
+public enum Easing {}
+
+public extension Easing {
+ static func linear(_ t: Double) -> Double {
+ return t
+ }
+
+ static func easeIn(_ t: Double) -> Double {
+ return t * t
+ }
+}
+```
+
+The first line here may seem a little baffling. An enum with no cases?!
+
+This is actually a neat little pattern in Swift for when you want to create a namespace. There is no `namespace` keyword in Swift, so to group functions together you have to make them members of a type. We could use a class or struct, but an empty enum has the nice property that you can't accidentally create an instance of it. It's what's known as an *uninhabited type* - a type with no possible values.
+
+After the empty enum we have an extension with some static methods. The first is the `linear()` easing function - this just returns the same time you pass in.
+
+Next is the `easeIn()` function. There are actually many ways to implement `easeIn()` - for example, UIKit and Core Animation use Bezier curves to implement easing. But you can also use sine/cosine, or various orders of [polynomial](https://en.wikipedia.org/wiki/Polynomial).
+
+In this case we have used a *quadratic* `easeIn()` implementation, meaning that it's proportional to the input time *squared*. If we had used `t * t * t`, it would have been a *cubic* `easeIn()` instead. There's no right or wrong type to use, so feel free to experiment and see what feels right.
+
+Let's add the equivalent quadratic implementations for ease-out and ease-in-ease-out:
+
+```swift
+public extension Easing {
+ ...
+
+ static func easeOut(_ t: Double) -> Double {
+ return 1 - easeIn(1 - t)
+ }
+
+ static func easeInEaseOut(_ t: Double) -> Double {
+ if t < 0.5 {
+ return 2 * easeIn(t)
+ } else {
+ return 4 * t - 2 * easeIn(t) - 1
+ }
+ }
+}
+```
+
+The `easeOut()` function is just the opposite of ease-in, so we can implement it by calling `easeIn()` with inverted input. The `easeInEaseOut()` function is a little more complex but essentially it behaves like ease-in up to the half-way point, then it behaves like ease-out for the rest (shifted, so it aligns seamlessly with the first half).
+
+In `Effect.swift`, find the code for the computed `progress` property and update it as follows:
+
+```swift
+var progress: Double {
+ let t = min(1, time / duration)
+ switch type {
+ case .fadeIn:
+ return Easing.easeIn(t)
+ case .fadeOut:
+ return Easing.easeOut(t)
+ }
+}
+```
+
+We're still using `time / duration` to compute the normalized time, but now we pass it through either `easeIn()` or `easeOut()` depending on the effect type. The game-over fade effect timing is now a smooth curve.
+
+
+
+Try running the game again and you should notice a subtle improvement.
+
+### Not with a Bang, but with a Fizzle
+
+Wolfenstein 3D didn't actually use a palette fade for the player death. Instead, John Carmack created a rather special custom effect he called *fizzlefade*. Fizzlefade is a sort of stippling transition where the pixels flip to red at random.
+
+The naive way to implement this would be to repeatedly set pixels to red at random until the screen is filled, but this doesn't work well in practice because as the screen fills up, the chances of randomly stumbling on an unfilled pixel tends towards zero. As a result, the effect runs slower and slower over time, and the total duration is non-deterministic.
+
+Wolfenstein used an ingenious solution to this problem which Fabien Sanglard [wrote about here](http://fabiensanglard.net/fizzlefade/). I'm not *sure* I fully understand how it works (let alone to a level where I can explain it), but fortunately we aren't as resource-constrained as Wolfenstein was, and can use a much simpler approach.
+
+To restate the problem: We need to visit every pixel on the screen only once, but in a random order. If we put the indexes of all the pixels into an array and then shuffle it before we begin, we can step through the shuffled array in order to ensure we only touch each pixel once.
+
+In `Effect.swift`, add a new case to the `EffectType` enum called `fizzleOut`:
+
+```swift
+public enum EffectType {
+ case fadeIn
+ case fadeOut
+ case fizzleOut
+}
+```
+
+Then add the following extra case to the switch statement in the computed `progress` property below:
+
+```swift
+case .fizzleOut:
+ return Easing.easeInEaseOut(t)
+```
+
+Although it's similar to fade-out, I found that `easeOut()` didn't work as well for the fizzle effect, so I've used `easeInEaseOut()` instead (it also looks pretty good with just `linear()` easing, which is what Wolfenstein used).
+
+Next, in `World.hurtPlayer()` replace the line:
+
+```swift
+effects.append(Effect(type: .fadeOut, color: .red, duration: 2))
+```
+
+with:
+
+```swift
+effects.append(Effect(type: .fizzleOut, color: .red, duration: 2))
+```
+
+To implement the fizzle effect itself, we need to pre-generate a buffer of pixel indexes. It's not immediately obvious where we should store that. We could put it in the renderer itself if it was persistent, but we are currently creating a new `Renderer` instance each frame so we can't store anything in it long-term. Another reasonable option would be to store it in the `Effect` itself, but since it's only needed for fizzle-type effects that's not ideal either.
+
+For now we'll store the fizzle buffer in a global constant. It's not a very elegant solution because it introduces potential issues with thread safety, but since the renderer is currently single-threaded anyway it will do as a stop-gap measure.
+
+At the top of the `Renderer.swift` file, add the following line:
+
+```swift
+private let fizzle = (0 ..< 10000).shuffled()
+```
+
+This populates the fizzle buffer with the values 0 to 9999, shuffled in a random order. A typical iPhone screen has a lot more than 10,000 pixels but we don't need a buffer entry for every single pixel, just enough so that any repetition isn't obvious.
+
+At the bottom of the `Renderer.draw()` method, add the missing `.fizzleOut` case to the `// Effects` block:
+
+```swift
+case .fizzleOut:
+ let threshold = Int(effect.progress * Double(fizzle.count))
+
+}
+```
+
+The threshold value is the number of pixels in the `fizzle` buffer that should be filled in a given frame. For example, if `effect.progress` is 0.5, we want to fill 50% of the pixels on screen, so any pixel that maps to the lower half of the buffer should be filled, and any in the upper half won't be.
+
+Next, we need to loop through every pixel in the image and determine if we should fill the pixel or not. Add the following code just after the `let threshold` line:
+
+```swift
+for y in 0 ..< bitmap.height {
+ for x in 0 ..< bitmap.width {
+ let index = y * bitmap.width + x
+
+ }
+}
+```
+
+The `let index...` line converts the X and Y pixel coordinates into a linear buffer index. Just below that line, add the following:
+
+```swift
+let fizzledIndex = fizzle[index % fizzle.count]
+```
+
+This looks up the fizzled index from the buffer. Since there are more pixels in the bitmap than entries in the `fizzle` buffer, we use `%` (the modulo operator) to wrap the index value within the buffer's range.
+
+Finally, we need to compare the fizzled index value to the threshold. If it's below the threshold we'll fill the pixel, otherwise we do nothing. Add the following code below the line we just added:
+
+```swift
+if fizzledIndex <= threshold {
+ bitmap[x, y] = effect.color
+}
+```
+
+Run the game and get yourself killed. You should see the original fade to red has now been replaced by the fizzle effect.
+
+![Fizzle fade](Images/FizzleFade.png)
+
+This effect is pretty faithful to the original Wolfenstein 3D death sequence, but it doesn't really suit the lo-fi look of Retro Rampage because the resolution of a modern iPhone (even the virtual 1x resolution that we are using) is a bit too dense.
+
+We can improve the effect by artificially lowering the resolution for a more pixelated fizzle. In the code we just wrote, replace the line:
+
+```swift
+let index = y * bitmap.width + x
+```
+
+with:
+
+```swift
+let granularity = 4
+let index = y / granularity * bitmap.width + x / granularity
+```
+
+The `granularity` constant controls the sampling size. By dividing the real X and Y coordinates by the desired granularity, we increase the apparent size of each sampled pixel on screen.
+
+![Fizzle fade with larger pixels](Images/BigPixelFizzle.png)
+
+And with that, we'll bring this (rather morbid) chapter to a close. In this part we:
+
+* Added player health and damage
+* Handled player death and re-spawn
+* Added fullscreen fade effects for when the player is hurt or killed
+* Implemented animation easing for smoother fades
+* Recreated the old-school fizzlefade effect from Wolfenstein
+
+In [Part 8](Part8.md) we'll give the player the means to fight back!
+
+### Reader Exercises
+
+1. Can you modify the monster's logic so that there is only a 1 in 3 chance of them doing damage to the player on each swipe?
+
+2. Change the fizzle fade to a dripping blood effect, where red lines drop down from the top of the top of the screen at random speeds until it's all filled up.
+
+3. Doom replaced the fizzlefade with a [screen melt](https://doom.fandom.com/wiki/Screen_melt) effect, where the current scene drips down to reveal a new one behind. Can you implement something like that?
+
+
+
+[[1]](#reference1) This isn't actually guaranteed to work since there are multiple world updates per frame, but it doesn't seem to be a problem in practice.
+
+[[2]](#reference2) The original Wolfenstein 3D screen resolution was 320x200 (320 * 200 = 64000).
+
+[[3]](#reference3) There are actually similar tricks that you can do on modern machines by altering the [display gamma](https://en.wikipedia.org/wiki/Gamma_correction), but that's highly platform-specific and outside the scope of what we're trying to do here.
+
+[[4]](#reference4) It's, uh... completely normal to think like this, right?
+
+[[5]](#reference5) Don't make it too long, or the monster will be on top of you by the time your vision has cleared!
diff --git a/Tutorial/Part8.md b/Tutorial/Part8.md
new file mode 100644
index 0000000..1452ff9
--- /dev/null
+++ b/Tutorial/Part8.md
@@ -0,0 +1,870 @@
+## Part 8: Target Practice
+
+In [Part 7](Part7.md) we added the ability for monsters to hurt and even kill the player, causing the game to reset. The complete code for Part 7 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part7.zip).
+
+Now it's time to give the player the means to fight back!
+
+### Pistols at Dawn
+
+I don't know about you, but I've had about enough of being mauled to death by zombies. It's *payback time*.
+
+We're going to need a weapon. In keeping with FPS tradition, we'll start with a humble pistol.
+
+
+
+Like the monster sprite, the pistol has a transparent background. Even though the sprite only occupies the bottom-right corner of the screen, to keep the math simple we're going to scale the sprite image to fit the whole screen height.
+
+I've used a 32x32 image for the pistol instead of the 16x16 images used previously, so that the apparent resolution is more similar to the monster sprites and textures, which are normally seen from further away[[1]](#footnote1).
+
+If you'd like to use the artwork from the tutorial, you can find it [here](https://github.com/nicklockwood/RetroRampage/tree/Part8/Source/Rampage/Assets.xcassets/), but feel free to use any weapon graphics you like for your own project. The logic for handling sprites is resolution-independent, and the image size has no significant impact on performance.
+
+Once you've added the "pistol" image to XCAssets, open `Textures.swift` and extend the `Texture` enum with the new case:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case pistol
+}
+```
+
+### Weapon Drawn
+
+So how do we go about actually drawing the pistol on-screen?
+
+In terms of screen area, the sprite that we want to draw looks like this:
+
+
+
+We already have a mechanism for drawing sprites using *billboards* (flat, textured rectangles that always face the player), so it might seem reasonable to implement the pistol as a billboard positioned a constant distance in front of the player.
+
+This wouldn't work well in practice however, because if we try to position the pistol sprite in 3D we'll introduce problems like the sprite being clipped by walls, or nearby monster sprites being drawn in front of it.
+
+We always want to draw the pistol in front of everything else. The height needs to match the screen height, but there is no perspective distortion, nor do we want any part of the image to be clipped or occluded by other objects in the scene. This doesn't really seem like a 3D problem at all - we just want to draw a scaled 2D image.
+
+We already have logic to draw a vertical slice of an image at any scale, so let's extend that to draw an entire image. In `Bitmap.swift` find the `drawColumn()` method. Add a new method underneath it called `drawImage()`, as follows:
+
+```swift
+mutating func drawImage(_ source: Bitmap, at point: Vector, size: Vector) {
+
+}
+```
+
+The method signature is quite similar to `drawColumn()`, but we don't need the `x` parameter, and instead of a scalar output `height`, we have a vector `size`. The bodies of the methods will also be similar, but instead of stepping vertically through the pixels of a single column, we're going to step horizontally through the columns of the whole image.
+
+Add this code inside `drawImage()`:
+
+```swift
+let start = Int(point.x), end = Int(point.x + size.x)
+let stepX = Double(source.width) / size.x
+for x in max(0, start) ..< min(width, end) {
+ let sourceX = (Double(x) - point.x) * stepX
+
+}
+```
+
+Inside the loop, `x` is the position at which the column will be displayed on-screen, and `sourceX` is the position in the source image from which the column will be sampled. We can now use the `drawColumn()` function to actually draw each column of pixels. Add the following two lines inside the loop to complete the `drawImage()` method:
+
+```swift
+let outputPosition = Vector(x: Double(x), y: point.y)
+drawColumn(Int(sourceX), of: source, at: outputPosition, height: size.y)
+```
+
+In `Renderer.draw()`, add this block of code just before the `// Effects` section:
+
+```swift
+// Player weapon
+let screenHeight = Double(bitmap.height)
+bitmap.drawImage(
+ textures[.pistol],
+ at: Vector(x: Double(bitmap.width) / 2 - screenHeight / 2, y: 0),
+ size: Vector(x: screenHeight, y: screenHeight)
+)
+```
+
+This ensures the pistol will be drawn in front of all 3D objects in the scene, but underneath the effects overlays. Run the game again and you should see the pistol displayed over the background.
+
+![Pistol sprite drawn over background](Images/PistolOverlay.png)
+
+### Fired Up
+
+We've *drawn* a pistol, but currently there's no way to actually fire it. To do that, we need to add a new type of input.
+
+Many FPS games on the iPhone use an on-screen button for firing weapons. I find that this suffers from the same problem as fixed-position joysticks - it's too easy for your finger position to slip, and with no tactile feedback it's hard to feel your way back to the button without taking your eyes off the action.
+
+Instead, we're going to turn the *entire screen* into a fire button. In `ViewController.swift`, add the following new property just below `panGesture`:
+
+```swift
+private let tapGesture = UITapGestureRecognizer()
+```
+
+For `panGesture` we didn't bother adding an action handler, and just sampled the current value each frame. That approach won't work for `tapGesture` though, because we only want to send one fire event to the engine each time the user taps, and there's not any obvious way to do that without storing additional state outside the `UITapGestureRecognizer` itself.
+
+Add the following property to `ViewController`:
+
+```swift
+private var lastFiredTime = 0.0
+```
+
+Then the following method:
+
+```swift
+@objc func fire(_ gestureRecognizer: UITapGestureRecognizer) {
+ lastFiredTime = CACurrentMediaTime()
+}
+```
+
+Finally, in `viewDidLoad()` add the lines:
+
+```swift
+view.addGestureRecognizer(tapGesture)
+tapGesture.addTarget(self, action: #selector(fire))
+```
+
+In `Input.swift`, add a new `isFiring` property and update `init()` to set the new property:
+
+```swift
+public struct Input {
+ public var speed: Double
+ public var rotation: Rotation
+ public var isFiring: Bool
+
+ public init(speed: Double, rotation: Rotation, isFiring: Bool) {
+ self.speed = speed
+ self.rotation = rotation
+ self.isFiring = isFiring
+ }
+}
+```
+
+Then, in `ViewController.update()`, replace the lines:
+
+```swift
+let input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation))
+)
+```
+
+with:
+
+```swift
+let input = Input(
+ speed: -inputVector.y,
+ rotation: Rotation(sine: sin(rotation), cosine: cos(rotation)),
+ isFiring: lastFiredTime > lastFrameTime
+)
+```
+
+That takes care of the platform layer changes - now to implement the firing logic in the engine itself.
+
+### Flash Forward
+
+We'll start by implementing the muzzle flash. This will give us a clear visual indication of whether the tap handling is working as intended. First, we'll need some animation frames:
+
+
+
+The pistol firing sequence has five frames, but the last is the same as the idle state, so there's only four new images to add to XCAssets. Do that, then add the new cases to `Textures.swift`:
+
+```swift
+public enum Texture: String, CaseIterable {
+ ...
+ case pistol
+ case pistolFire1, pistolFire2, pistolFire3, pistolFire4
+}
+```
+
+We'll need to create some `Animation` instances for the pistol, but where to put them? The pistol is the closest thing we have to a player avatar, so it seems like it makes sense to put them in `Player.swift`. Add the following code to the bottom of the `Player.swift` file:
+
+```swift
+public extension Animation {
+ static let pistolIdle = Animation(frames: [
+ .pistol
+ ], duration: 0)
+ static let pistolFire = Animation(frames: [
+ .pistolFire1,
+ .pistolFire2,
+ .pistolFire3,
+ .pistolFire4,
+ .pistol
+ ], duration: 0.5)
+}
+```
+
+Now we need some code to manage weapon state. Add the following enum to the top of `Player.swift`:
+
+```swift
+public enum PlayerState {
+ case idle
+ case firing
+}
+```
+
+Then add these properties to the `Player` struct[[2]](#footnote2):
+
+```swift
+public var state: PlayerState = .idle
+public var animation: Animation = .pistolIdle
+public let attackCooldown: Double = 0.4
+```
+
+The `Monster` attack logic is handled inside `Monster.update()`, but the player doesn't currently have their own `update()` method. Let's change that. In `World.update()`, find the following two lines in the `// Update player` section:
+
+```swift
+player.direction = player.direction.rotated(by: input.rotation)
+player.velocity = player.direction * input.speed * player.speed
+```
+
+and replace them with:
+
+```swift
+player.animation.time += timeStep
+player.update(with: input)
+```
+
+Then, back in `Player.swift`, add an `update()` method containing the logic we just removed:
+
+```swift
+extension Player {
+ ...
+
+ mutating func update(with input: Input) {
+ direction = direction.rotated(by: input.rotation)
+ velocity = direction * input.speed * speed
+
+ }
+}
+```
+
+Now that we've moved the player's update logic into the `Player` type itself, we'll add the new code for managing weapon state. Append the following lines to the `Player.update()` method:
+
+```swift
+switch state {
+case .idle:
+ if input.isFiring {
+ state = .firing
+ animation = .pistolFire
+ }
+case .firing:
+ if animation.time >= attackCooldown {
+ state = .idle
+ animation = .pistolIdle
+ }
+}
+```
+
+The state logic here is as follows:
+
+* If we're in the `idle` state, and the fire button was pressed during the frame, switch to the `firing` state and trigger the `pistolFire` animation.
+* Once in the `firing` state, wait until the `attackCooldown` period has passed, then return to the `idle` state so the weapon can be fired again.
+
+In `Renderer.draw()`, in the `// Player weapon` section, replace the line:
+
+```swift
+textures[.pistol],
+```
+
+with:
+
+```swift
+textures[world.player.animation.texture],
+```
+
+This ensures that we draw the current animation frame of the pistol instead of just its idle state.
+
+Run the game again and try tapping to fire the pistol. You should see a satisfying muzzle flash.
+
+![Pistol sprite rendered as a clipboard](Images/MuzzleFlash.png)
+
+At least, you should see it for about a second, before a zombie starts chomping on your brain!
+
+*It's time we put this guy down.*
+
+### All Flash, No Substance
+
+A weapon isn't much good if it doesn't inflict any damage on your attacker. But before we can hurt the monster, we need to detect which (if any) monster we've hit.
+
+The monster's own attack is just based on distance from the player - it isn't directional. By contrast, a projectile weapon like a pistol requires a very different implementation.
+
+As we discussed in [Part 2](Part.md), a small and/or fast-moving object (like a pistol bullet) poses a potential problem for our collision handling, since it operates in discrete time steps. A sufficiently fast projectile might appear to teleport through a wall unless we increase the update frequency to compensate.
+
+We currently update the world at ~120 FPS. Is that sufficient to reliably simulate a pistol bullet?
+
+[According to Wikipedia](https://en.wikipedia.org/wiki/Muzzle_velocity), muzzle velocity for a bullet ranges from around 120 m/s (meters-per-second) for an old-fashioned musket, to around 1200 m/s for a modern high-powered rifle. That actually makes the math super-simple, because it means that at 120 FPS, the bullet would travel anywhere between 1 and 10 meters per frame.
+
+One world unit is roughly equivalent to a meter, so we can say that a realistic bullet would travel between one and ten world units per frame. The size of a bullet is pretty close to zero (about 0.02 units) so we can discount that. The implication then is that 120 FPS is just on the borderline if we want to simulate a bullet as a moving projectile.
+
+We are free to increase our world simulation rate since we decoupled it from the frame rate. Pushing it up to 1000 FPS would solve any collision accuracy problems. Alternatively, we could compromise by reducing the bullet speed a bit - after all the player is unlikely to really notice if a bullet travels at 60 m/s instead of 120.
+
+But there is a third option which requires neither a reduction in bullet speed nor an increase in simulation speed - we can model the bullet as a *ray* instead of a projectile.
+
+### Ray Gun
+
+Instead of moving a fixed distance each frame, a ray instantly traverses the entire map like a laser beam, stopping at the first object it intersects (a technique known as [hitscanning](https://en.wikipedia.org/wiki/Hitscan)). This isn't really realistic of course, but the difference between a bullet moving 10 units vs the entire map diameter is not really perceptible[[3]](#footnote3), and it greatly simplifies the implementation.
+
+We've already written the logic for ray/wall and ray/sprite intersections in the `Renderer`, but the use-cases for drawing and bullet collisions are slightly different, so we don't really want to couple them together. Instead, we'll add a new `hitTest()` method to `Monster`.
+
+To implement that method we'll need the monster's sprite `Billboard`, but that's currently generated directly inside `World.sprites` and isn't available here[[4]](#footnote4), so we'll need to fix that first.
+
+In `Monster.swift` add the following new method near the bottom of the extension:
+
+```swift
+func billboard(for ray: Ray) -> Billboard {
+ let plane = ray.direction.orthogonal
+ return Billboard(
+ start: position - plane / 2,
+ direction: plane,
+ length: 1,
+ texture: animation.texture
+ )
+}
+```
+
+Then, in `World.swift`, find the computed `sprites` var and replace it with this much-simplified version so that we aren't duplicating the billboard-generation logic:
+
+```swift
+var sprites: [Billboard] {
+ let ray = Ray(origin: player.position, direction: player.direction)
+ return monsters.map { $0.billboard(for: ray) }
+}
+```
+
+With that refactoring out the the way, we can get back to the task at hand. Back in `Monster.swift`, just below the `billboard()` method we just added, insert following:
+
+```swift
+func hitTest(_ ray: Ray) -> Vector? {
+ guard let hit = billboard(for: ray).hitTest(ray) else {
+ return nil
+ }
+ return hit
+}
+```
+
+This method checks whether the ray intercepts the monster's billboard, and returns the hit location if so, or `nil` otherwise. We're not quite done with the `hitTest()` implementation yet though.
+
+As you may recall from [Part 6](Part6.md), the monster sprite image doesn't extend right to the edges of the bitmap. We don't want bullets that should pass by the monster to be treated as hits, so we need to do a further check to ensure that the distance of the hit position along the billboard is within the monster's radius.
+
+Add the following code to the `hitTest()` method, just before the `return hit` line:
+
+```swift
+guard (hit - position).length < radius else {
+ return nil
+}
+```
+
+Now that we have a way to test a ray against an individual monster, we need to write the logic to test each monster in turn to see a) if they've been hit, and b) which was hit first (a process known as *picking*).
+
+In `World.swift`, add the following placeholder method to the bottom of the extension:
+
+```swift
+func hitTest(_ ray: Ray) -> Int? {
+
+}
+```
+
+You may have noticed that the signature of this `hitTest()` method is different from all other similarly-named methods in the game. In most cases we are interested in the *location* at which the ray intersects the object, but in this case we just need to know *which* object was hit, so we return an integer index instead of a vector position.
+
+The method will start by performing a hit-test against the map itself, and recording the distance. This is useful because any monster hit that is further away than this can be discarded (the bullet would have stopped at the wall first).
+
+Add these lines inside `hitTest()`:
+
+```swift
+let wallHit = map.hitTest(ray)
+var distance = (wallHit - ray.origin).length
+```
+
+Next, we need to loop through all the monsters and check if the ray hits them. Add the following code to complete the method:
+
+```swift
+var result: Int? = nil
+for i in monsters.indices {
+ guard let hit = monsters[i].hitTest(ray) else {
+ continue
+ }
+ let hitDistance = (hit - ray.origin).length
+ guard hitDistance < distance else {
+ continue
+ }
+ result = i
+ distance = hitDistance
+}
+return result
+```
+
+For each monster, if a hit is detected we compute the distance and then compare it with the previous nearest hit. At the end, the index of the closest sprite is returned (or `nil`, if no sprites were hit).
+
+### Health Hazard
+
+Once we have isolated the monster that was hit, we'll need a way to inflict damage. In [Part 7](Part7.md) we made the player mortal by giving them a `health` property - we'll now do the same for the monsters. In `Monster.swift` add the following property to `Monster`:
+
+```swift
+public var health: Double = 50
+```
+
+Then add a computed `isDead` property to the extension:
+
+```swift
+public extension Monster {
+ var isDead: Bool {
+ return health <= 0
+ }
+
+ ...
+}
+```
+
+Since we've made the `World.monsters` array setter private, it's not possible for the player to directly manipulate the monster's health. For player damage, we solved this problem by adding a `hurtPlayer()` method to `World`. We'll use the same solution here.
+
+In `World.swift`, add the following method to the extension:
+
+```swift
+mutating func hurtMonster(at index: Int, damage: Double) {
+ monsters[index].health -= damage
+}
+```
+
+The `Player.update()` method is going to need some way to call this, which means it needs access to the `World` instance. As with `Monster.update()`, we'll pass the world to `Player.update()` via an `inout` parameter. In `Player.swift`, replace the method signature:
+
+```swift
+mutating func update(with input: Input) {
+```
+
+with:
+
+```swift
+mutating func update(with input: Input, in world: inout World) {
+```
+
+Then, in `World.update()`, in the `// Update player` section, replace the line:
+
+```swift
+player.update(with: input)
+```
+
+with:
+
+```swift
+player.update(with: input, in: &self)
+```
+
+Uh-oh, what's this?
+
+
+
+It seems that we can't pass a mutable reference to `World` to `Player.update()` because this would permit simultaneous modifications to be made to the `player` property via two different references.
+
+This isn't a problem for `Monster.update()` because we copy the monster into a local variable before calling `update()`. We'll use the same solution for the player. Still in `World.update()`, replace the lines:
+
+```swift
+player.animation.time += timeStep
+player.update(with: input, in: &self)
+player.position += player.velocity * timeStep
+```
+
+with:
+
+```swift
+var player = self.player!
+player.animation.time += timeStep
+player.update(with: input, in: &self)
+player.position += player.velocity * timeStep
+self.player = player
+```
+
+Back in `Player.update()`, just after the line `animation = .pistolFire`, add the following:
+
+```swift
+let ray = Ray(origin: position, direction: direction)
+if let index = world.hitTest(ray) {
+ world.hurtMonster(at: index, damage: 10)
+}
+```
+
+OK, it seems like that should be everything we need. If you run the game though, it seems like the monster still won't die, no matter how much you shoot it.
+
+### Life After Death
+
+Although *we* know that you are supposed to die when your health reaches zero, we forgot to tell the monster. Rather ironically for a zombie, the monster is *quite literally* a member of the walking dead.
+
+The monster's behavior is guided by a state machine. We could just bypass that logic when the monster is dead, but it makes more sense to extend the state machine rather than work around it.
+
+In `Monster.swift`, add two new cases to the `MonsterState` enum:
+
+```swift
+public enum MonsterState {
+ ...
+ case hurt
+ case dead
+}
+```
+
+We'll need some new monster animation frames to represent these new states.
+
+
+
+Add these images to XCAssets, then add the new cases in `Textures.swift`:
+
+```swift
+case monsterHurt, monsterDeath1, monsterDeath2, monsterDead
+```
+
+Next, at the bottom of `Monster.swift`, add three new animations to the extension:
+
+```swift
+public extension Animation {
+ ...
+ static let monsterHurt = Animation(frames: [
+ .monsterHurt
+ ], duration: 0.2)
+ static let monsterDeath = Animation(frames: [
+ .monsterHurt,
+ .monsterDeath1,
+ .monsterDeath2
+ ], duration: 0.5)
+ static let monsterDead = Animation(frames: [
+ .monsterDead
+ ], duration: 0)
+}
+```
+
+For the `monsterHurt` animation we've just re-used the first frame of the death animation, but you could do something more interesting in your own project, like having the monster shake its head or clutch its chest. Even though the animation is only one frame, we've made it last for 0.2 seconds because we want the monster to pause for a while each time it is shot.
+
+When hurt, the monster will pause its attack on the player, then resume again after the animation has finished. When dead, it should play the death animation once and then switch to the single-frame `monsterDead` animation so it doesn't appear to keep dying over and over.
+
+In `Animation.swift`, add the following code to the extension:
+
+```swift
+public extension Animation {
+ var isCompleted: Bool {
+ return time >= duration
+ }
+
+ ...
+}
+```
+
+Then back in `Monster.swift`, add the following cases to the `switch` statement inside the `update()` method:
+
+```swift
+case .hurt:
+ if animation.isCompleted {
+ state = .idle
+ animation = .monsterIdle
+ }
+case .dead:
+ if animation.isCompleted {
+ animation = .monsterDead
+ }
+```
+
+### Rogue States
+
+That takes care of how the monster should behave in the `hurt` and `dead` states, but how does it actually get into those states in the first place? Unlike the other cases in the `MonsterState` enum, being hurt or killed isn't something that the monster AI *decides* to do, it's something that is *done to* the monster.
+
+Let's look at the `World.hurtPlayer()` method for inspiration. If we ignore anything specific to effects we are left with:
+
+```swift
+mutating func hurtPlayer(_ damage: Double) {
+ if player.isDead {
+ return
+ }
+ player.health -= damage
+ ...
+ if player.isDead {
+ ...
+ }
+}
+```
+
+We can use more-or-less the same code for the monster. In `World.hurtMonster()`, replace the line:
+
+```swift
+monsters[index].health -= damage
+```
+
+with:
+
+```swift
+var monster = monsters[index]
+if monster.isDead {
+ return
+}
+monster.health -= damage
+if monster.isDead {
+ monster.state = .dead
+ monster.animation = .monsterDeath
+} else {
+ monster.state = .hurt
+ monster.animation = .monsterHurt
+}
+monsters[index] = monster
+```
+
+Run the game and you should find that your bullets now hurt and kill the monsters. There are a few weird glitches though:
+
+* The monster's corpse keeps sliding towards the player after it dies.
+* We sometimes see a flash of the monster standing up again just at the point of death.
+
+### Death Slide
+
+The posthumous slide is happening because the monster's velocity isn't reset when it dies.
+
+The velocity of the monster is controlled by its state machine. In the `chasing` state it always maneuvers towards the player. The velocity gets reset to zero in the `idle` state, but we never reset it when the monster is hurt or killed, so it just retains whatever value it had previously.
+
+Since it's only the `chasing` state that should be actively changing the velocity, it makes more sense for the velocity to be set at the point of transition to another state, instead of constantly while in that state.
+
+In `Monster.update()` remove the following line from inside `case .idle:`:
+
+```swift
+velocity = Vector(x: 0, y: 0)
+```
+
+Then update the body of `case .chasing:` to look like this:
+
+```swift
+guard canSeePlayer(in: world) else {
+ state = .idle
+ animation = .monsterIdle
+ velocity = Vector(x: 0, y: 0)
+ break
+}
+if canReachPlayer(in: world) {
+ state = .scratching
+ animation = .monsterScratch
+ lastAttackTime = -attackCooldown
+ velocity = Vector(x: 0, y: 0)
+ break
+}
+let direction = world.player.position - position
+velocity = direction * (speed / direction.length)
+```
+
+So that the velocity is reset to zero each time we switch state.
+
+Finally, in `World.hurtMonster()`, find the line:
+
+```swift
+monster.health -= damage
+```
+
+and immediately after it, insert:
+
+```swift
+monster.velocity = Vector(x: 0, y: 0)
+```
+
+That should solve the issue with monsters sliding around after death.
+
+We don't currently experience this bug with the player, but only because we decided to stop updating player position when they are dead. We might change that later and accidentally introduce the same bug, so it's better to code defensively.
+
+In `World.hurtPlayer()`, find the line:
+
+```swift
+player.health -= damage
+```
+
+and immediately after it, insert:
+
+```swift
+player.velocity = Vector(x: 0, y: 0)
+```
+
+With that done, we're ready to move on to the second bug.
+
+### Strange Loop
+
+The death animation glitch is due to a race condition. In `World.update()`, in the `// Update monsters` section, we update each monster *before* we update the playback time for its animation. That means that the monster may be redrawn in between when the animation has ended and when the monster switches to the `dead` state, by which point the animation will have wrapped around to its first frame again.
+
+In `World.update()`, in the `// Update monsters` section, move the line:
+
+```swift
+monster.animation.time += timeStep
+```
+
+from its current position to just above the line:
+
+```swift
+monster.update(in: &self)
+```
+
+That will ensure that `animation.isCompleted` gets checked and handled before the animation wraps around.
+
+### Dead Weight
+
+There's just one last issue with post-death monster behavior - dead monsters still behave like solid objects. They can be pushed around the level, and they absorb bullets.
+
+This isn't exactly a bug, but it's not optimal from a gameplay perspective. Let's disable collision detection when the monster is dead.
+
+In `Monster.swift`, in the `hitTest()` method, replace the line:
+
+```swift
+guard let hit = billboard(for: ray).hitTest(ray) else {
+```
+
+with:
+
+```swift
+guard isDead == false, let hit = billboard(for: ray).hitTest(ray) else {
+```
+
+That means the monster corpse will no longer absorb bullets, so you can now shoot over the top of one corpse to hit the monster behind.
+
+It's a little more complex to disable collisions between the player and monster, because that logic has been abstracted out into `Actor`. Fortunately, since both `Monster` and `Player` already have an `isDead` property, we can easily abstract that as well.
+
+In `Actor.swift`, add the following property to the `Actor` protocol:
+
+```swift
+var isDead: Bool { get }
+```
+
+Then insert the following code at the start of the `intersection(with actor:)` method:
+
+```swift
+if isDead || actor.isDead {
+ return nil
+}
+```
+
+If you run the game now, you should see that all the post-death monster glitches are solved. But it's also apparent that the game is actually pretty *hard*.
+
+There are a couple of reasons for this:
+
+* It's not possible to fire and turn at the same time.
+* Even when locked on target, it's still difficult to land enough hits to bring down a monster before it attacks.
+
+### Point and Shoot
+
+The first problem is due to the way that `UIGestureRecognizer` works. By default, only one gesture can be recognized at a time, so if iOS thinks you're making a pan gesture, it won't also recognize a tap gesture, and vice-versa.
+
+This is probably a sensible default as it helps to prevent false positives, but in the context of our game it's annoying. We can override this behavior using `UIGestureRecognizerDelegate`. In `ViewController.swift`, add the following code to the bottom of the file:
+
+```swift
+extension ViewController: UIGestureRecognizerDelegate {
+ func gestureRecognizer(
+ _ gestureRecognizer: UIGestureRecognizer,
+ shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
+ ) -> Bool {
+ return true
+ }
+}
+```
+
+Next, in the `viewDidLoad()` method, just before the line:
+
+```swift
+view.addGestureRecognizer(panGesture)
+```
+
+add:
+
+```swift
+panGesture.delegate = self
+```
+
+Finally, in the same method, just after the line:
+
+```swift
+tapGesture.addTarget(self, action: #selector(fire))
+```
+
+add:
+
+```swift
+tapGesture.delegate = self
+```
+
+With the delegates hooked up, the gestures can now be recognized concurrently, so we no longer have to choose between aiming and firing. Let's take a look at the second problem.
+
+### Too Cool for Comfort
+
+Although we set the monster health to 50 (half of the player's), they approach fast and we can only fire relatively slowly.
+
+We can tip the odds a bit further in the player's favor by reducing their cooldown period (the minimum time between shots). In `Player.swift`, change the line:
+
+```swift
+public let attackCooldown: Double = 0.4
+```
+
+to:
+
+```swift
+public let attackCooldown: Double = 0.25
+```
+
+If you run the game again, you'll see you can now fire much faster, making it easier to take down a monster before they get close enough to start draining your health.
+
+But an unintended side-effect of this is that the pistol firing animation no longer plays to completion. Because the player state resets to `idle` as soon as the cooldown period of 0.25 seconds has elapsed, the pistol animation (which is supposed to last 0.5 seconds) is never able to finish.
+
+We didn't notice this problem earlier because the cooldown period of 0.4, combined with the fact that the `pistolFire` animation's final frame is the same as the `pistolIdle` frame anyway, meant that the animation wasn't clipped.
+
+Interrupting the animation in order to fire more frequently than the full duration makes sense, but when we *don't* interrupt the animation, it should play to completion.
+
+In `Player.swift`, add the following code just below the `var isDead` property:
+
+```swift
+var canFire: Bool {
+ switch state {
+ case .idle:
+ return true
+ case .firing:
+ return animation.time >= attackCooldown
+ }
+}
+```
+
+This computed property allows us to hoist the firing logic outside of the `idle` state handler. Next, in `Player.update()`, insert this code just before the `switch` statement:
+
+```swift
+if input.isFiring, canFire {
+ state = .firing
+ animation = .pistolFire
+ let ray = Ray(origin: position, direction: direction)
+ if let index = world.hitTest(ray) {
+ world.hurtMonster(at: index, damage: 10)
+ }
+}
+```
+
+then replace the body of `case .idle:` with just a `break` statement, since we no longer need to be in the `idle` state to fire.
+
+Finally, in `case .firing:`, replace the line:
+
+```swift
+if animation.time >= attackCooldown {
+```
+
+with:
+
+```swift
+if animation.isCompleted {
+```
+
+So now when the `pistolFire` animation is playing, if the player *doesn't* tap to fire, the state will only transition back to `idle` once the animation has actually completed.
+
+That's all for Part 8. In this part we:
+
+* Added a player weapon sprite and figured out how best to draw it
+* Implemented tap-to-fire and added a muzzle flash animation
+* Used ray casting to simulate bullets hitting enemies
+* Added the ability to hurt and kill the monsters in the game
+* Discovered and fixed a bunch of bugs
+
+We've added a bunch of new effects over the last few chapters, and the frame rate is starting to suffer, especially on older devices. In [Part 9](Part9.md) we'll take a short break from pixelated mayhem to tune the performance a bit before we pile on any more features.
+
+### Reader Exercises
+
+1. Try adding a small random offset to the angle of the bullet each time so it's more challenging to try to pick off a monster in the distance.
+
+2. Replace the pistol with a shotgun that fires a spread of several bullets at once (If you're wondering how to create a spread of rays without trigonometry functions, remember how the player's vision works).
+
+3. We've added sprites for the pistol and a muzzle flash, but what about the point of impact? Can you figure out how to implement a blood splash when the weapon hits a monster, or a spark when it hits a wall?
+
+
+
+[[1]](#reference1) It's also pretty difficult to draw a pistol any smaller than this.
+
+[[2]](#reference2) You may have noticed that these properties closely mirror the ones we used for the `Monster` attack sequence. There's probably an opportunity to consolidate some of this shared code, but let's follow the [rule of three](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)) and hold off on refactoring until we've completed more of the functionality.
+
+[[3]](#reference3) Especially given that our test map isn't even 10 units wide.
+
+[[4]](#reference4) What idiot made *that* decision?
+
diff --git a/Tutorial/Part9.md b/Tutorial/Part9.md
new file mode 100644
index 0000000..32300e0
--- /dev/null
+++ b/Tutorial/Part9.md
@@ -0,0 +1,830 @@
+## Part 9: Performance Tuning
+
+In [Part 8](Part8.md) we gave the player a pistol to kill the monsters so the game isn't quite so one-sided. The complete code for Part 8 can be found [here](https://github.com/nicklockwood/RetroRampage/archive/Part8.zip).
+
+We've held off on doing any optimization work so far because optimized code tends to be harder to read and harder to modify. It's also just generally an inefficient use of our time to optimize code that might be changed or even deleted.
+
+But now that we've reached a point of relative stability with the renderer, let's take a break from adding new features to do some much-needed performance tuning.
+
+### Standardized Testing
+
+The cardinal rule of performance tuning is *measure first*. Before we do anything, we need to give ourselves a way to measure the progress we are making. We need a [test harness](https://en.wikipedia.org/wiki/Test_harness).
+
+It's no secret that the games industry hasn't embraced automated testing to quite the same extent as app developers, preferring to rely on teams of manual testers.
+
+Videogames are an inherently visual medium, and while some aspects of our code base could benefit from automated testing, the kind of bugs we've encountered so far - such as fisheye rendering distortion, or monster corpses absorbing bullets - would take hours to write tests for, and can be reproduced in seconds by just *playing the game*.
+
+Performance testing is an altogether different matter. A human can tell if a game seems smooth or choppy, but it's very hard for them to make objective comparisons between the effects of small optimizations. For that we need a more scientific approach, and this is where automated tests really shine.
+
+We're going to start by adding a test target to the project. Xcode usually adds this by default, so you may already have a folder called *RampageTests* in your project, in which case you can skip the next step.
+
+If you *don't* already have a test target, go to `File > New > Target...` and select *iOS Unit Testing Bundle*.
+
+
+
+Once you've added the test target, edit the Rampage scheme and change the *Build Configuration* in the *Test* tab to "Release".
+
+We already set the *Run* mode to Release in [Part 4](Part4.md), but we need to do it for the Test target as well otherwise the tests won't be representative of real-world performance conditions.
+
+
+
+### Public Service
+
+It's common practice to use the `@testable` attribute when importing modules in a test file. `@testable` makes `internal` (module-scoped) code accessible to the test. Unfortunately `@testable` only works in Debug mode, so we can't use it here. That means we're going to have to make a few things in the Rampage module public.
+
+In `ViewController.swift`, make `loadMap()` public by replacing the line:
+
+```swift
+private func loadMap() -> Tilemap {
+```
+
+with:
+
+```swift
+public func loadMap() -> Tilemap {
+```
+
+Then do the same for the `loadTextures()` method.
+
+### Purity Test
+
+In general it's much easier to write tests for *pure* functions than stateful ones. A [pure function](https://en.wikipedia.org/wiki/Pure_function) is one that takes an input argument and returns an output value without accessing any external state.
+
+Modern rendering engines are multi-threaded and highly stateful, as they need to interact with GPU hardware (which is basically a giant state machine). The software renderer that we've written for Retro Rampage however is *completely stateless*, and should be super-simple to test.
+
+In the RampageTests folder in your project, find the file called `RampageTests.swift` and edit the contents to look as follows:
+
+```swift
+import XCTest
+import Engine
+import Rampage
+
+class RampageTests: XCTestCase {
+ let world = World(map: loadMap())
+ let textures = loadTextures()
+
+ func testRenderFrame() {
+ self.measure {
+ var renderer = Renderer(width: 1000, height: 1000, textures: textures)
+ renderer.draw(world)
+ }
+ }
+}
+```
+
+That's all the code needed to render a single frame of the game under test conditions. The `measure {}` block wrapped around the drawing code is a built-in method of `XCTestCase` that automatically executes and benchmarks the code inside it.
+
+Normally a test function would include one or more `XCAssert()` statements to test the output of the code under test, but in this case we aren't interested in the *output* of the test, only how long it takes to run.
+
+Go ahead and run the tests (select the `Product > Test` menu, or press `Cmd-U` on the keyboard). Note that you can (and should) run the tests on an actual iOS device rather than the Simulator.
+
+You might be surprised to see that the game boots up and runs for a few seconds. That's normal, but we don't really want it to happen in this case because we're trying to measure performance in an isolated test, and having the game running at the same time is likely to peg the CPU, impacting the result.
+
+In `ViewController.swift` add the following code at the top of the `viewDidLoad()` method, just below the `super.viewDidLoad()` line:
+
+```swift
+guard NSClassFromString("XCTestCase") == nil else {
+ return
+}
+```
+
+This (slightly eccentric) code performs a runtime check for the presence of the `XCTestCase` class. If found, it indicates that the app is running in a test environment, so we bail out early before the game has been initialized.
+
+Run the tests again and you should see something like this:
+
+
+
+Each time the tests are run, the code inside `measure {}` is evaluated multiple times and the average time is compared against the previously recorded value. Because this is the first run, we don't have a baseline time for the performance test yet, so we need to set one.
+
+Click the small grey diamond icon on the left-hand side of the `self.measure {` line and you'll see the following popup that will allow you to save the last recorded time as the baseline:
+
+
+
+Once set, the baseline value is saved inside the project. Future test runs will be compared against this value, and the test will fail if the new time is significantly worse.
+
+Successive performance test values are only meaningful when the tests are run on the same hardware. Even on the same device, test results can be distorted by background processes or other external factors. For this reason I usually run the tests a few times and then take the best reproducible result.
+
+The baseline time we've recorded is 0.11 seconds, so we're only getting ~9 FPS. That means we've got plenty of room for improvement.
+
+### Low Hanging Fruit
+
+You can spend an almost unlimited amount of time making small improvements to performance, but the return on investment tends to diminish exponentially. So how do we decide where best to focus our efforts?
+
+Instincts about what makes code slow are often wildly misguided, but fortunately Xcode has some great tools that we can use to disabuse us of our misconceptions. In [Part 4](Part4.md) we briefly used the Time Profiler Instrument to diagnose an unexpected performance dip. We'll use it again now (use `Cmd-I` to launch Instruments and select the Time Profiler tool).
+
+Here is the trace for the game as it currently stands.
+
+![Time Profiler trace showing relative time spent in various calls](Images/UnoptimizedTrace.png)
+
+The Time Profiler is not especially good for measuring absolute performance (for that, we have the performance test we just wrote), but it's great for identifying the bottlenecks in your code.
+
+From the screenshot you can see that the top function in the list is `Bitmap.blendPixel()` with a whole 24% of the total frame time spent in just that single function. It's clearly a prime candidate for optimization.
+
+Let's take a look at `blendPixel()`:
+
+```swift
+private mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+ let oldColor = self[x, y]
+ let inverseAlpha = 1 - Double(newColor.a) / 255
+ self[x, y] = Color(
+ r: UInt8(Double(oldColor.r) * inverseAlpha) + newColor.r,
+ g: UInt8(Double(oldColor.g) * inverseAlpha) + newColor.g,
+ b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
+ )
+}
+```
+
+At first glance there isn't much opportunity for improvement here. We're already using premultiplied alpha, and common code like `inverseAlpha` has already been extracted. But if you can't make code faster, what about *not calling it in the first place*?
+
+The `blendPixel()` method is called from inside both `drawColumn()` and `tint()`. Looking at the Time Profiler trace, `Bitmap.drawColumn()` is higher in the list, which makes sense since we only use `tint()` for `Effect` overlays when the player is hurt, whereas `drawColumn()` is called many times each frame for both walls and sprites.
+
+So let's concentrate on `drawColumn()` for now. The `drawColumn()` method calls `blendPixel()` because it's used for sprites, which have a transparent background. But it's also used for walls, which don't. We pay a significant price for that flexibility.
+
+We could fork the `drawColumn()` method into opaque and non-opaque variants, or add an `isOpaque` parameter to the function, but there's a more elegant solution.
+
+### Clear as Mud
+
+In `Color.swift`, add the following computed property to the extension:
+
+```swift
+public extension Color {
+ var isOpaque: Bool {
+ return a == 255
+ }
+
+ ...
+}
+```
+
+Then in `Bitmap.swift`, update the `Bitmap` struct to include an `isOpaque` property, as follows:
+
+```swift
+public struct Bitmap {
+ public private(set) var pixels: [Color]
+ public let width: Int
+ public let isOpaque: Bool
+
+ public init(width: Int, pixels: [Color]) {
+ self.width = width
+ self.pixels = pixels
+ self.isOpaque = pixels.allSatisfy { $0.isOpaque }
+ }
+}
+```
+
+The `allSatisfy` method returns true *only* if its closure argument returns true for every element in the array, so this is a handy way for us to determine up-front if every pixel in the bitmap is opaque, and then store that value so we can efficiently query it later.
+
+The `Bitmap` type also has a second convenience initializer which creates an image filled with a solid color. Find the following method in the extension block below:
+
+```swift
+init(width: Int, height: Int, color: Color) {
+ self.pixels = Array(repeating: color, count: width * height)
+ self.width = width
+}
+```
+
+and add this line to it:
+
+```swift
+self.isOpaque = color.isOpaque
+```
+
+Now, in `Bitmap.drawColumn()`, replace the loop:
+
+```swift
+for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ blendPixel(at: Int(point.x), y, with: sourceColor)
+}
+```
+
+with:
+
+```swift
+if source.isOpaque {
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ self[Int(point.x), y] = sourceColor
+ }
+} else {
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ blendPixel(at: Int(point.x), y, with: sourceColor)
+ }
+}
+```
+
+So now we completely avoid the cost of blending for opaque surfaces such as walls. If we run the performance test again we can see a 16% improvement - not bad!
+
+**Disclaimer:** It's unlikely you will see *exactly* the same result in your own tests. You may see a smaller improvement, or the performance may even appear to get *worse*. If so, run the tests a few times to make sure it wasn't a random dip. If the result is consistently worse, try reverting the `isOpaque` optimization and re-recording the baseline before trying again, as your original baseline value may have been a fluke.
+
+
+
+There's no point applying the same optimization to `Bitmap.tint()` because the way it's used means that overlays will almost always be translucent. But given that `isOpaque` worked so well, maybe there's a way we can optimize `blendPixel()` after all?
+
+### Clear as Crystal
+
+The `isOpaque` trick works because wall images are completely solid, however it doesn't help with sprites, where some pixels are opaque and others aren't.
+
+But even with sprites, many of the pixels *are* opaque, and others are completely transparent. Transparent pixels can be handled even more efficiently than opaque ones since we can skip them completely. But of course no image is *completely* transparent, so to take advantage of this we'll have to check the alpha for each pixel as we draw it.
+
+In `Bitmap.swift`, update the `blendPixel()` method as follows:
+
+```swift
+private mutating func blendPixel(at x: Int, _ y: Int, with newColor: Color) {
+ switch newColor.a {
+ case 0:
+ break
+ case 255:
+ self[x, y] = newColor
+ default:
+ let oldColor = self[x, y]
+ let inverseAlpha = 1 - Double(newColor.a) / 255
+ self[x, y] = Color(
+ r: UInt8(Double(oldColor.r) * inverseAlpha) + newColor.r,
+ g: UInt8(Double(oldColor.g) * inverseAlpha) + newColor.g,
+ b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
+ )
+ }
+}
+```
+
+This does add a slight overhead for each pixel, but in return we avoid the cost of blending entirely for fully opaque or fully transparent pixels. Run the tests again and let's see if it helped.
+
+
+
+Whoa... A whopping *45% improvement* - we've almost doubled our frame rate!
+
+We didn't update the baseline after the last set of changes, so that 45% includes the 16% we got from `Bitmap.isOpaque`, but even so it's pretty huge.
+
+### Now You See Me...
+
+Now that we've made a significant improvement to performance, let's run the Time Profiler again.
+
+![Time Profiler trace after optimizing blendPixels()](Images/OptimizedBlendTrace.png)
+
+Thanks to our optimizations, `blendPixel()` is now taking up only 6.2% of the frame time (down from 24%) and has been bumped down to number five in the list. The new highest priority is `Bitmap.drawColumn()`.
+
+Wait a moment though - shouldn't the changes we just made have improved `drawColumn()` as well? Why has it gotten *slower*? Have we just shifted the performance overhead from one method to another?
+
+Let's take a look at the `drawColumn()` implementation again.
+
+```swift
+mutating func drawColumn(_ sourceX: Int, of source: Bitmap, at point: Vector, height: Double) {
+ let start = Int(point.y), end = Int((point.y + height).rounded(.up))
+ let stepY = Double(source.height) / height
+ if source.isOpaque {
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ self[Int(point.x), y] = sourceColor
+ }
+ } else {
+ for y in max(0, start) ..< min(self.height, end) {
+ let sourceY = max(0, Double(y) - point.y) * stepY
+ let sourceColor = source[sourceX, Int(sourceY)]
+ blendPixel(at: Int(point.x), y, with: sourceColor)
+ }
+ }
+}
+```
+
+Previously this method called `blendPixel()` in a tight loop. Now, the loop *either* calls `blendPixel()` *or* it sets the pixel color directly via `Bitmap.subscript`.
+
+In the first trace we ran, `Bitmap.subscript.setter` was the second highest line item after `Bitmap.blendPixel()`. You would expect that `Bitmap.subscript.setter` would now be the highest item in the list, but it seems to have vanished completely from the trace. What's going on?
+
+When you run your app in the Profiler it uses an *optimized* build (equivalent to Release mode). This makes sense because we are interested in how it will perform in the App Store, not how it performs in Debug mode. But the optimizer has a broad remit to make changes to the code structure, and one of the changes it makes is [method inlining](https://en.wikipedia.org/wiki/Inline_expansion).
+
+Due to inlining, the method you need to optimize *may not appear in traces at all*. The subscripting code is still there, but as far as the profiler is concerned that code is now just part of `drawColumn()`.
+
+Optimization is a bit like detective work. When examining a trace, it's not enough to look at which methods appear - you also need to keep an eye out for methods that are conspicuous by their absence. We have some pretty good circumstantial evidence that `Bitmap.setter()` is our performance bottleneck, but how can we *prove* it?
+
+Swift has an `@inline()` attribute that can be used to override the optimizer. In general, I don't recommend using this unless you are extremely confident that you know better than the compiler, but I see no harm in using it to test our theory.
+
+In `Bitmap.subscript(x:y:)` replace the line:
+
+```swift
+set {
+```
+
+with:
+
+```swift
+@inline(never) set {
+```
+
+This tells Swift *never* to inline the subscript setter. Run the time profiler again and you'll see that our suspicions have been confirmed.
+
+![Time Profiler trace after disabling inlining of subscript setter](Images/NoInliningSubscript.png)
+
+### Set Cost
+
+Before we do anything else, remove the `@inline(never)` again so it doesn't negatively affect our performance.
+
+Next, let's take a closer look at the `Bitmap.subscript` setter:
+
+```swift
+set {
+ guard x >= 0, y >= 0, x < width, y < height else { return }
+ pixels[y * width + x] = newValue
+}
+```
+
+There are 4 range comparisons and some math happening for each pixel we write to the screen. The comparisons are a safety check which we added in [Part 1](Part1.md) to prevent crashes if we try to draw outside the bitmap. We can skip those checks in places like `drawColumn()` where we already constrain the loop bounds to the bitmap height.
+
+In `Bitmap.drawColumn()`, replace the line:
+
+```swift
+self[Int(point.x), y] = sourceColor
+```
+
+with:
+
+```swift
+pixels[y * width + Int(point.x)] = sourceColor
+```
+
+Run the tests again (`Cmd-U`) and see if that helps the performance at all.
+
+
+
+That's almost another 10% percent faster - not massive, but certainly worth doing.
+
+We've bypassed the range checks by manually inlining the pixel index calculation, but not the multiplication and addition, which are needed to convert between a 2D pixel coordinate and the linear array index. Since we are stepping through a vertical column of pixels, each pixel should be exactly `width` pixels offset from the previous one, so if we keep a running index we can get rid of the multiplication.
+
+Add the following line just before `if source.isOpaque {`:
+
+```swift
+var index = max(0, start) * width + Int(point.x)
+```
+
+Then replace the line:
+
+```swift
+pixels[y * width + Int(point.x)] = sourceColor
+```
+
+with:
+
+```swift
+pixels[index] = sourceColor
+index += width
+```
+
+And if we run the tests again... oh.
+
+
+
+After eliminating the multiplication, the performance is no better - maybe even a little worse. So what happened?
+
+It's important to remember that the compiler is pretty smart. The chances are, if we can spot something simple like a multiplication that can be replaced by addition, the compiler can too. My guess is that it was already performing this optimization for us, so doing it manually ourselves gains us nothing.
+
+Instead of wasting our time on fruitless micro-optimizations, let's see if we can find some bigger wins elsewhere.
+
+### Out of Order
+
+We know that most of the drawing code goes through the `drawColumn()` method. It's pretty fundamental to the design of the ray-casting engine that everything in the world is drawn using vertical strips.
+
+When we created the `Bitmap` type in [Part 1](Part1.md) we decided to lay out the pixels in row order. This made a lot of sense at the time because it's how native iOS image data is arranged, which makes it easy to convert to and from `UIImage` without needing to swizzle the pixels.
+
+But we spend a lot more of our frame time reading and writing pixels from bitmaps inside the Engine module than we do converting to and from `UIImage`, and in most cases we are accessing the pixels column-by-column rather than row-by-row.
+
+Switching `Bitmap.pixels` to column-order would simplify the math for stepping through columns, but based on what we've just seen it's possible that wouldn't improve performance much beyond what the compiler can do for us. There's a much more important reason why it's worth aligning our pixel data with the order we access it though, and that's [cache locality](https://en.wikipedia.org/wiki/Locality_of_reference).
+
+Computer memory is arranged in a [hierarchy](https://en.wikipedia.org/wiki/Memory_hierarchy). At the top of the hierarchy are the CPU registers. There are only a handful of these, but they are super-fast to access. At the bottom of the hierarchy is main memory[[1]](#footnote1). This is normally numbered in gigabytes, and dwarfs the register capacity, but it is hundreds or even thousands of times slower to access.
+
+In order to make up the performance gap between registers and main memory, modern CPUs include several layers of [cache](https://en.wikipedia.org/wiki/CPU_cache), known as L1 cache, L2 cache, etc. Each level of cache is progressively larger and slower.
+
+
+
+The key to good performance is to try to keep the data you are working on as near to the top of the hierarchy as possible.
+
+The first time you try to access some data in main memory, the processor copies a 64-byte chunk of that data into the cache. That chunk is known as a *cache line*. Subsequent accesses to data inside the same chunk are essentially free, but accessing data outside the cache line will force another line to be fetched from memory, which takes much longer.
+
+The most efficient way to access data is therefore to do it *sequentially*, because this maximizes the chances that subsequent data accesses will land in the same cache line. Predictable data access patterns also allow the CPU to preemptively fetch cache lines before they are needed, reducing the access latency even further.
+
+So how does this relate to `Bitmap`? Well, each pixel in the bitmap is 4 bytes in size, so we can only fit around 16,000 pixels in a single cache line. The screen is about 1000 pixels wide, so that means the CPU can only fetch ~16 rows at a time. If we are looping through a vertical column of pixels, every 16th pixel we access will be in a separate cache line. But if the pixels were stored in column order, the entire column would fit in a single line.
+
+In practice, the processor can fetch and store multiple cache lines from different parts of main memory at once, and given the regularity of the access pattern we are using, it's likely that the processor is already making up most of the cost of our out-of-order accesses. But it's worth at least *trying* to structure our data to match our usage patterns.
+
+To switch the pixel layout, we're going to have to make a few changes in `Bitmap.swift`. First, we'll replace the stored `width` property with `height`, and update `init()`:
+
+```swift
+public struct Bitmap {
+ public private(set) var pixels: [Color]
+ public let height: Int
+ public let isOpaque: Bool
+
+ public init(height: Int, pixels: [Color]) {
+ self.height = height
+ self.pixels = pixels
+ self.isOpaque = pixels.allSatisfy { $0.isOpaque }
+ }
+}
+```
+
+Next, in the extension, replace the computed `height` var with:
+
+```swift
+var width: Int {
+ return pixels.count / height
+}
+```
+
+The `subscript` will also need to be updated as follows:
+
+```swift
+subscript(x: Int, y: Int) -> Color {
+ get { return pixels[x * height + y] }
+ set {
+ guard x >= 0, y >= 0, x < width, y < height else { return }
+ pixels[x * height + y] = newValue
+ }
+}
+```
+
+And in `init(width:height:color:)`, replace the line:
+
+```swift
+self.width = width
+```
+
+with:
+
+```swift
+self.height = height
+```
+
+The `drawColumn()` function will now be broken because the optimizations we made previously were row-order specific. To fix it, replace:
+
+```swift
+var index = max(0, start) * width + Int(point.x)
+```
+
+with:
+
+```swift
+let offset = Int(point.x) * self.height
+```
+
+Then replace the lines:
+
+```swift
+pixels[index] = sourceColor
+index += width
+```
+
+with:
+
+```swift
+pixels[offset + y] = sourceColor
+```
+
+Finally, in `UIImage+Bitmap.swift`, in the `Bitmap` extension block, replace:
+
+```swift
+self.init(width: cgImage.width, pixels: pixels)
+```
+
+with:
+
+```swift
+self.init(height: cgImage.height, pixels: pixels)
+```
+
+Let's try running the tests again and see what difference we've made.
+
+
+
+That's about another 10% improvement. A bit disappointing, but we're not done yet.
+
+Before we make any further changes, let's run the game quickly and check everything still works after the switch to column order.
+
+![Scrambled output](Images/ScrambledOutput.png)
+
+Ha ha, whoops! It seems like the change we made in `UIImage+Bitmap.swift` wasn't quite sufficient to adapt to the new pixel layout. We'll need to swap over all the references to `width` and `height` in the `UIImage` extension so that it looks like this:
+
+```swift
+public extension UIImage {
+ convenience init?(bitmap: Bitmap) {
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = bitmap.height * bytesPerPixel
+
+ guard let providerRef = CGDataProvider(data: Data(
+ bytes: bitmap.pixels, count: bitmap.width * bytesPerRow
+ ) as CFData) else {
+ return nil
+ }
+
+ guard let cgImage = CGImage(
+ width: bitmap.height,
+ height: bitmap.width,
+ bitsPerComponent: 8,
+ bitsPerPixel: bytesPerPixel * 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: CGBitmapInfo(rawValue: alphaInfo.rawValue),
+ provider: providerRef,
+ decode: nil,
+ shouldInterpolate: true,
+ intent: .defaultIntent
+ ) else {
+ return nil
+ }
+
+ self.init(cgImage: cgImage)
+ }
+}
+```
+
+Let's try that again...
+
+![Rotated output](Images/RotatedOutput.png)
+
+Nope! I guess we also need to rotate the output image since we flipped the rows and columns? Fortunately iOS makes it quite easy to do this without having to actually swizzle the pixels. In `UIImage.init()` change the line:
+
+```swift
+self.init(cgImage: cgImage)
+```
+
+to:
+
+```swift
+self.init(cgImage: cgImage, scale: 1, orientation: .leftMirrored)
+```
+
+That tells iOS that the image data is stored left-side-up, and that it should rotate the image at display time to compensate. Let's see if it helps.
+
+![Rotated output](Images/RotatedSprites.png)
+
+LOL! Well, the world is now the right way up, but the textures are all rotated 90 degrees and flipped, and it looks like the monsters are walking on the walls[[2]](#footnote2).
+
+This isn't a problem with `UIImage.init()`, it's actually the `Bitmap` initializer that we need to fix this time. We could cheat and just pre-rotate all our texture images before importing them[[3]](#footnote3), but it would be nicer if we could keep the pixel layout as an internal implementation detail as much as possible.
+
+Still in `UIImage+Bitmap.swift`, go ahead and flip all the `height` and `width` references inside `Bitmap.init()`:
+
+```swift
+public extension Bitmap {
+ init?(image: UIImage) {
+ guard let cgImage = image.cgImage else {
+ return nil
+ }
+
+ let alphaInfo = CGImageAlphaInfo.premultipliedLast
+ let bytesPerPixel = MemoryLayout.size
+ let bytesPerRow = cgImage.height * bytesPerPixel
+
+ var pixels = [Color](repeating: .clear, count: cgImage.width * cgImage.height)
+ guard let context = CGContext(
+ data: &pixels,
+ width: cgImage.height,
+ height: cgImage.width,
+ bitsPerComponent: 8,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: alphaInfo.rawValue
+ ) else {
+ return nil
+ }
+
+ context.draw(cgImage, in: CGRect(origin: .zero, size: image.size))
+ self.init(height: cgImage.height, pixels: pixels)
+ }
+}
+```
+
+Finally, to actually rotate the image, we need to do the inverse of the transform we performed in `UIImage.init()`, and replace the line:
+
+```swift
+context.draw(cgImage, in: CGRect(origin: .zero, size: image.size))
+```
+
+with:
+
+```swift
+UIGraphicsPushContext(context)
+UIImage(cgImage: cgImage, scale: 1, orientation: .left).draw(at: .zero)
+UIGraphicsPopContext()
+```
+
+### Loose Ends
+
+There are still a handful of places where we either aren't taking full advantage of the column-order optimization, or where it may actually be making things *slower*, because we are still enumerating pixels in row-first order.
+
+We'll start with the `Bitmap.fill()` function. We aren't using it in the game currently, but we don't want to forget to update it. In `fill()`, swap the lines:
+
+```swift
+for y in Int(rect.min.y) ..< Int(rect.max.y) {
+ for x in Int(rect.min.x) ..< Int(rect.max.x)
+```
+
+so they read:
+
+
+```swift
+for x in Int(rect.min.x) ..< Int(rect.max.x) {
+ for y in Int(rect.min.y) ..< Int(rect.max.y) {
+```
+
+That ensures that the pixels are being accessed in column-first order, giving the CPU cache an easier time.
+
+Next, let's take a look at the `tint()` function:
+
+```swift
+mutating func tint(with color: Color, opacity: Double) {
+ let alpha = min(1, max(0, Double(color.a) / 255 * opacity))
+ let color = Color(
+ r: UInt8(Double(color.r) * alpha),
+ g: UInt8(Double(color.g) * alpha),
+ b: UInt8(Double(color.b) * alpha),
+ a: UInt8(255 * alpha)
+ )
+ for y in 0 ..< height {
+ for x in 0 ..< width {
+ blendPixel(at: x, y, with: color)
+ }
+ }
+}
+```
+
+Here we are also processing pixels in row-first order, but there's actually a more significant optimization to be made here than just swapping the loops like we did in `fill()`.
+
+Because `tint()` is always applied to every pixel in the bitmap, we don't need to use X, Y indexing, we can just process the pixels in order. To do that, we'll first need to update `blendPixel()` to use linear indexing, as follows:
+
+```swift
+private mutating func blendPixel(at index: Int, with newColor: Color) {
+ switch newColor.a {
+ case 0:
+ break
+ case 255:
+ pixels[index] = newColor
+ default:
+ let oldColor = pixels[index]
+ let inverseAlpha = 1 - Double(newColor.a) / 255
+ pixels[index] = Color(
+ r: UInt8(Double(oldColor.r) * inverseAlpha) + newColor.r,
+ g: UInt8(Double(oldColor.g) * inverseAlpha) + newColor.g,
+ b: UInt8(Double(oldColor.b) * inverseAlpha) + newColor.b
+ )
+ }
+}
+```
+
+Then in `tint()` replace the lines:
+
+```swift
+for y in 0 ..< height {
+ for x in 0 ..< width {
+ blendPixel(at: x, y, with: color)
+ }
+}
+```
+
+with:
+
+```swift
+for i in pixels.indices {
+ blendPixel(at: i, with: color)
+}
+```
+
+And finally, in `drawColumn()` replace the line:
+
+```swift
+blendPixel(at: Int(point.x), y, with: sourceColor)
+```
+
+with:
+
+```swift
+blendPixel(at: offset + y, with: sourceColor)
+```
+
+That's it for the `Bitmap` changes. There's just one other place where our code assumes we're using row-order pixels.
+
+In `Renderer.swift` in the `// Effects` block, swap the lines:
+
+```swift
+for y in 0 ..< bitmap.height {
+ for x in 0 ..< bitmap.width {
+```
+
+to:
+
+```swift
+for x in 0 ..< bitmap.width {
+ for y in 0 ..< bitmap.height {
+```
+
+Now run the tests again and see how we're doing.
+
+
+
+We've gained another couple of %, but I think it's time to accept that we may have reached the point of diminishing returns, at least for bitmap-related optimizations.
+
+Before we down tools though, there is one last improvement that we can get, essentially for free.
+
+### Safeties Off
+
+By default, Swift performs safety checks to prevent array bounds violations and integer overflows. These checks aren't designed to prevent crashes - on the contrary, they are designed to *force* a crash, instead of allowing more insidious problems such as silent memory corruption or undefined output. But there is a runtime cost to these checks, and we'd rather not pay it.
+
+We can bypass the numeric overflow checks by replacing integer operators like `+`, `-`, `*` and `/` with their unchecked variants `&+`, `&-`, `&* and `&/`, but that's pretty tedious and it won't help with array bounds checks. Instead, let's just disable safety checks completely for the Engine module.
+
+In the Build Settings for the Engine target, find the *Disable Safety Checks* option, and set it to *Yes* for Release builds.
+
+
+
+Let's see how that affects the performance:
+
+
+
+Great! That brings our frame time down by another 7%, for a total saving of 73%.
+
+So after all those optimizations, what are now the top functions on the Time Profiler trace?
+
+![Time Profiler trace after applying column-order optimizations](Images/OptimizedOrderTrace.png)
+
+At the very top is `Renderer.draw()`, which is not surprising since it's the primary call site for all the code we've been working on.
+
+There are undoubtedly many optimization opportunities inside `Renderer.draw()` itself, but we aren't done adding features to the game yet, and making optimizations to the rendering logic at this stage is likely to make that code harder to change, which is why we've mostly restricted our changes to the code inside `Bitmap`[[4]](#footnote4).
+
+The function that shows up a couple of lines *below* `Renderer.draw()` is a bit more surprising - it seems that almost 11% of our frame time is now being spent in the computed getter for `Bitmap.width`.
+
+### Does Not Compute
+
+It's hard to believe such a trivial function could be a bottleneck, but (as mentioned earlier) performance is often counter-intuitive. In any case, switching `width` to a stored property instead of a computed property should be a trivial fix.
+
+In `Bitmap.swift`, in the `Bitmap` struct itself, replace the line:
+
+```swift
+public let height: Int
+```
+
+with:
+
+```swift
+public let width, height: Int
+```
+
+Then in `init(height:pixels:)`, add the line:
+
+```swift
+self.width = pixels.count / height
+```
+
+A little further down, in the extension block, delete the computed property:
+
+```swift
+var width: Int {
+ return pixels.count / height
+}
+```
+
+And finally, in `init(width:height:color:)` add the line:
+
+```swift
+self.width = width
+```
+
+If we run the tests again, we should expect to see around another 10% performance boost. And we do!
+
+
+
+All in all, we've reduced rendering time by 83% - an almost *6X improvement* in frame rate - and we've done it without significantly complicating the code, or hampering our ability to add new features.
+
+Performance testing can be frustrating, counterintuitive and full of dead ends. But hopefully I've convinced you that with the right tools, and a rigorous approach, it can also be quite rewarding.
+
+If you are interested in learning more about optimizing Swift code, [Apple has published some tips](https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst) on the Swift Github page, including several we didn't cover here.
+
+That's all for Part 9. In this part we:
+
+* Created a performance test harness for making accurate frame time measurements
+* Used the Time Profiler to identify bottlenecks in the code
+* Switched our bitmaps to column-order
+* Disabled safety checks
+* Made many other minor improvements, adding up to a huge overall performance win
+
+With these optimizations in place, we're in good shape to add more graphics and gameplay features in [Part 10](Part10.md).
+
+### Reader Exercises
+
+1. In the current implementation we recreate the `Renderer` every frame, which means we waste cycles reallocating and clearing pixels that will be overwritten anyway. Can you modify the performance test to measure if it would be worth reusing a single `Renderer` instance between frames?
+
+2. In `UIImage(bitmap:)`, the way we are using `CGDataProvider` currently requires an expensive copy. We aren't currently measuring the cost of the `Bitmap` to `UIImage` conversion in the performance tests either. Modify the tests to measure the current cost of this conversion, then try using `CGDataProvider(dataInfo:data:size:releaseData:)` to convert the bitmap without copying, and see if it helps performance.
+
+3. So far our optimizations have been mostly low-level improvements to the Bitmap drawing logic, but what about higher-level rendering optimizations? After all, the best way to draw something faster is not to draw it all! Currently we are ray-casting every sprite in the level, even if it's behind a wall. Can you think of a way to detect which tiles are visible, and only render the sprites in those tiles?
+
+
+
+[[1]](#reference1) You can actually extend the hierarchy to include the disk drive, and then network storage, etc. In a sense these are forms of memory, each layer exponentially larger in capacity and proportionally slower to access.
+
+[[2]](#reference2) The game is actually fully playable like this, and I highly recommend you try it because it's hilarious.
+
+[[3]](#reference3) Which is exactly how Wolfenstein 3D handled it - Wolfenstein's sprites were originally pre-rotated by 90 degrees so they could be efficiently rendered column-by-column.
+
+[[4]](#reference4) Although that's also where most of the bottlenecks have been anyway.
+