Just learning to code with Xcode and Swift. Need a little hand

Hi!

A few years ago while I was in school (I'm 34 now so it was just a "few" years ago, hehe) I was interested in coding with a programing language that's called "Turbo Pascal". Back then I was "kind" of fluent in it. I used to make DOS programs to tell interactive stories with multiple endings based on your decisions. I can't remember the code now, but just to give you an idea this is what it looked like:

PRINT "AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?"

IF "ROCK" THEN PRINT "A MONSTER COMES BEHIND YOUR BACK IN THE DARK AND ATTACKS YOU." - GOTO=[DEAD]

IF "LAMP" THEN PRINT "YOU PICK UP THE LAMP AND START WALKING UNTIL YOU SEE ANOTHER DOOR RIGHT IN FRONT OF YOU." - GOTO=[DOOR2]

[DOOR2] BLAH BLAH YOU GET THE POINT =)

[DEAD]
PRINT "GAME OVER" - GOTO [NEWGAME]

I have tried for the past week to read about coding with swift online, following some tutorials on Youtube and on the webpage codewithchris.com only to find myself motivated into coding and creating new apps for my kids and to share with other people.

I have to be honest, it's kind of overwhelming. I feel years have made me slow to learn new things avidly or maybe I am not exploring in the right direction. Sometimes I get frustrated that most of the tutorials teach you the basic coding commands like "var, let, equations, creating your own data with classes, data types, optionals" etc, and then all suddenly they jump into using Xcode to create a GUI for the iOS app while there is still so much to cover behind all that basic coding.

I wonder if it is possible to create this type of story to run on macOS right on the terminal. What would be the programing language I need to focus on and if there is any Swift guide you recommend that can be more "spoon-fed" for beginners. If I can make this interactive on an iOS device, that would be great. If all I need to learn to make iOS apps is Swift, then great! (having to start on HTML, CSS, SQL, JAVA, C, C++, is not what I am looking for right now. I am struggling with "dad time").

If you feel like a good samaritan, could you write the code for the story above so I can compile it on Xcode (not sure how to do that yet but I can try and figure it out!) and run it on the terminal right on macOS?

Just a short quick code, so I can have an idea of how it's done.

Anyways, thank you so much in advance for all your help.

Glad to have joined your community.

This actually isn't as trivial as it seems, mainly because Swift doesn't have GOTO statements (nor do most modern languages, since GOTOs have a tendency to cause bugs).

Here's what I came up with:

// A GameScene can be either...
enum GameScene {
    // A function that returns a new GameScene
    case scene(() -> GameScene)
    // or the end of the game
    case exit
}

func adventureGame() -> GameScene {
    print("AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?")
    
    // 'while let' means to repeat this for as long as
    // 'readLine()' returns a non-nil (valid) value.
    while let nextLine = readLine() {
        switch nextLine {
        case "ROCK":
            print("A MONSTER COMES BEHIND YOUR BACK IN THE DARK AND ATTACKS YOU.")
            return .scene(dead)
        case "LAMP":
            print("YOU PICK UP THE LAMP AND START WALKING UNTIL YOU SEE ANOTHER DOOR RIGHT IN FRONT OF YOU.")
            return .scene(door2)
        default:
            print("I don't understand '\(nextLine)'.")
        }
    }
    return .exit
}

func door2() -> GameScene {
    fatalError("What should happen here?")
}

func dead() -> GameScene {
    print("GAME OVER")
    print("Start new game?")
    
    if let text = readLine(), // If we can get a response from the user
       let textValue = Bool(text), // and that response can be interpreted as a boolean (e.g. is 'true' or 'false')
       textValue == true { // and that boolean is 'true'
        return .scene(adventureGame) /// Start the game over
    } else {
        return .exit
    }
}

var currentScene = GameScene.scene(adventureGame)

gameLoop: while true {
    switch currentScene {
    case .scene(let sceneFunction):
        currentScene = sceneFunction()
    case .exit:
        break gameLoop
    }
}

I deliberately used a few more complex Swift features here to make it closer to actual code – for example, rather than constantly calling more functions in a nested manner the code always stays in that main game loop. Hopefully you should be able to understand what it's doing, though, if not why exactly it's written that way.

If you copy the code into a .swift file and then run swift FileName.swift in a terminal you should be able to play it without needing to use Xcode – however, if you want to use Xcode, the macOS Command Line Tool template is what you're looking for.

2 Likes

First of all, welcome! I don't have a good source, but I suppose I can walk you through the text-based game you mentioned.

TL;DR
You can compile it Terminal style. You can put the code snippet below in a file, say adventure.swift and run the command

swiftc ./adventure.swift

which will produce executable named adventure for you to run, again, in Terminal.
After the snippet, I'll go on about how the code works, and gives you some more improvement over it. I'll have a few snippets, each becoming more Swifty.

Swift doesn't have goto like Pascal. It is considered "unsafe" and is largely abandoned by many modern languages nowadays. So instead of literally goto different part of the code to print different thing, we will use variable to keep track of the state. Since you're already familiar with programming a few years back :wink:, you'd realise it'll more or less work like this:

  • Store current state of your game in currentState
  • In an infinite loop
    • Check what state you are in, and print appropriate message
    • Read user input
    • Based on current state and user input, goto the next state

Here's a code example

var currentState = "INTRO"

while true {
    // Print appropriate message
    
    if currentState == "INTRO" {
        print("AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?")
    } else if currentState == "DEAD" {
        print("Something Something you are dead")

        // This state is special, we don't wait for user input, so we move to `INTRO` immediately
        currentState = "INTRO"
        continue
    } else if currentState == "DOOR2" {
        print("You found a locked door, hmmm...")
    } // else if
    
    // Read user input from terminal
    guard let userInput = readLine() else {
        // Usually reach here because user press Ctrl+D, or we reach EOF
        // Better to break out of the `while` loop
        break
    }
    
    if currentState == "INTRO" && userInput == "ROCK" {
        print("A MONSTER COMES BEHIND YOUR BACK IN THE DARK AND ATTACKS YOU.")
        
        // goto [dead]
        currentState = "DEAD"
    } else if currentState == "INTRO" && userInput == "LAMP" {
        print("YOU PICK UP THE LAMP AND START WALKING UNTIL YOU SEE ANOTHER DOOR RIGHT IN FRONT OF YOU.")
        
        // goto [door2]
        currentState = "DOOR2"
    } // else if ...
}

Note that Swift is a strongly-typed language, like Pascal, so everything will have a type. Furthermore it is very smart and try to infer what type you want to use based on context. The line

var currentState = "INTRO"

says a few things. First of, it uses var currentState, which means that we create variable currentState, and assign "INTRO" into it. As we use double quote to signify string of characters, swift knows that currentState must be String.

The rest should be familiar to you with the while, if/else construct, with things like currentState == "INTRO" && userInput == "LAMP" being TRUE if both currentState is "INTRO" and that userInput is "LAMP".

Another thing is guard let:

guard let userInput = readLine() else {
    // Usually reach here because user press Ctrl+D, or we reach EOF
    // Better to break out of the `while` loop
    break
}

This does a few things. readLine() is a function that reads from user input, its return type is String? (note the trailing ?) which means that it'll return a working String, or it may return nil, signifying that there's no String to return. So we want to check if the value is assigned to userInput is not nil, that's why we put it in guard condition.

Right now you can think of guard as if-else (it's alright folks, I know the nuance, put your pitchfork down :blush:) that is

  • If the condition is true, do nothing
  • Otherwise execute the block after else

The requirement for guard is that you must do return or continue or break at the end, which is a deliberate choice, and I won't go into details here

Note also that I use let userInput, similar to var currentState, it creates a variable named userInput. The only difference is that let creates a constant, you can not change the value after you created it.

You probably notice that you do a lot of comparison against a variable or two, so, similar to Pascal, you can do swift over variables. Though Swift's switch is more powerful in a few notable ways. So the code v2 becomes:

var currentState = "INTRO"

while true {
    // Print appropriate message
    
    switch currentState {
    case "INTRO":
        print("AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?")
    case "DOOR2":
        print("You found a locked door, hmmm...")
    case "DEAD":
        print("Something Something you are dead")
        
        // This state is special, we don't wait for user input, so we move to `INTRO` immediately
        currentState = "INTRO"
        continue
    default: fatalError("Hmm, we should've handled all the possibilities here. Let's crash a program if we hit this location")
    }   
    
    // Read user input from terminal 
    guard let userInput = readLine() else {
        // Usually reach here because user press Ctrl+D, or we reach EOF
        // Better to break out of the `while` loop
        break
    }
    
    switch (currentState, userInput) {
    case ("INTRO", "ROCK"):
        print("A MONSTER COMES BEHIND YOUR BACK IN THE DARK AND ATTACKS YOU.")
        currentState = "DEAD"
    case ("INTRO", "LAMP"):
        print("YOU PICK UP THE LAMP AND START WALKING UNTIL YOU SEE ANOTHER DOOR RIGHT IN FRONT OF YOU.")
        currentState = "DOOR2"
    default:
        // Well, we don't know what the user is trying to do, lets just go to the next loop silently.
        // Break out of the switch without doing anything.
        break
    }
}

So you declare the variable you want to match against with switch, and all the candidates in case <SOME VALUE HERE>:. Note that unlike old school switch, Swift won't go to the next case by default, ie, Swift's switch doesn't fallthrough. Furthermore, switch is very pedantic about you handling all possibilities. Since I'm using a string and there's no way I'll be able to check all possible string combination, I use default case to handle any thing that doesn't matched existing cases.

One other nice thing about switch is that it can matches multiple variables (by means of tuple, again, I won't go into details here) like what I did in the second switch.

Now, using String to save state is pretty cumbersome, you can misspell, or forget some cases in switch. Swift can help you with enum. It is a collection of limited possible values. Like in this case, we know that the states will be either intro, door2, or dead, and nothing else. So you can convert currentState to State as the code below.

// Create type State
enum State {
    // Declare 3 possible cases, begin with keyword case
    case intro, door2, dead
}

// Set currentState to value `intro` in the `State` type
var currentState = State.intro

while true {
    // Print appropriate message

    switch currentState {
    case .intro: print("AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?")
    case .door2: print("You found a locked door, hmmm...")
    case .dead:
        print("Something Something you are dead")
        // This state is special, we don't wait for user input, so we move to `INTRO` immediately
        currentState = .intro
        continue
    }

    // Read user input from terminal
    guard let userInput = readLine() else {
        // Usually reach here because user press Ctrl+D, or we reach EOF
        // Better to break out of the `while` loop
        break
    }

    switch (currentState, userInput) {
    case (.intro, "ROCK"):
        print("A MONSTER COMES BEHIND YOUR BACK IN THE DARK AND ATTACKS YOU.")
        currentState = .dead
    case (.intro, "LAMP"):
        print("YOU PICK UP THE LAMP AND START WALKING UNTIL YOU SEE ANOTHER DOOR RIGHT IN FRONT OF YOU.")
        currentState = .door2
    default:
        // Well, we don't know what the user is trying to do, lets just go to the next loop silently.
        break
    }
}

So now we don't need to worry about misspelling the states. Better yet, since the compiler can now proof that we exhausted all the possibility (there're only 3 in this case), we don't even need the default case! Note that later I use .door2 to refer to the value, this is because you can normally do State.door2 to achieve the same effect, but since Swift can infer that it is expecting a State where you write it, you can just use .door2 to refer to it.

3 Likes

Also recommend if you have an iPad trying out Swift Playgrounds. You could definitely implement this kind of thing as a Playground.

Hi!

Thank you so much for your reply.

I can't really find a way to make the code work on playgrounds. It doesn't run.

I can only run it while exporting it on Xcode and running the command "swiftc ./adventure.swift" and then executing the output file on the terminal.

Hi Lantuan!

Thank you so much for your reply. It's amazing how incredible your help was. I learned sooo much from your answers.

This is the code im using. I found the last one easier to handle and understand:

Blockquote
//
// main.swift
// AdventureGame
//
// Created by Cesar Morales on 7/19/19.
// Copyright © 2019 Cesar Morales. All rights reserved.
//

// Create type State
enum State {
// Declare 3 possible cases, begin with keyword case
case newGame, firstRoom, secondRoomNoRock, secondRoomWithRock, thirdRoom, thirdRoomDecisionLeft, thirdRoomDecisionForward, thirdRoomDecision2, thirdRoomEnd,thirdRoomEndDoorNoCandles, thirdRoomEndCandles, dead
}

// Set currentState to value intro in the State type
var currentState = State.newGame

while true {
// Print appropriate message

switch currentState {
    
case .newGame: print("You wake up in the middle of a dim lit room. You don't remember who you are. How did you get here?. You don't remember your name or where you came from.  You stand up, your knees shaking. This is a terrible place to be. You soon realize there's a door in front of you. You can barely see it. There's no handle on the door but it seems it's easily movable. What do you do?")
    print("Push or Pull?")
    
case .firstRoom: print("AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?")
    
case .secondRoomNoRock: print("You found a locked door, hmmm... There's a padlock keeping it shut. If only you had something to smash it with.")
    print("You get that feeling of being watched... you turn around and something hits you in the head.")
    currentState = .dead
    
case .secondRoomWithRock: print("You found a locked door... but there's a padlock keeping it shut. It's rusty and it seems very old. You have a lamp in one hand and a rock in the other. Maybe you can smash it with something. What do you use?")
    
case .thirdRoom: print("Theres a stench in the air. You don't know where it comes from as there are three different corridors on this room. You must choose one of them. They are identical. Do you choose to go left, right or forward?")
    
case .thirdRoomDecisionLeft: print("Do you want to try and continue moving forward or head back?. The way out can be just ahead, but you don't know that for sure")

case .thirdRoomDecisionForward: print("You decide to go forward. You feel you have walked a straight mile. Is your mind playing tricks on you?.")
print("Your lamp is runing low on fuel. Do you want to continue walking or head back?")


case .thirdRoomDecision2: print("Do you continue forward or head back?")

case .thirdRoomEnd: print("What do you do?")
    print("...you take the candles or push the door?")
    
    
case .thirdRoomEndDoorNoCandles: print("You push the door and hear the sound of heavy gears moving. Before you can even take the first step forward through the door, a huge boulder drops on your head.")
    currentState = .dead
    
case .thirdRoomEndCandles: print("You pick up the candles. One on each hand. Their warmth is conforting. The idead of having a light source is reassuring. It doesn't remove your doubts or fears, but it's helping alright. You hear a subtle sound of what appears to be a moving platform right on top of you. It sounds like somethign moved.")
    print("You take a few steps forward and push the door.")
    print("You can't believe your eyes. It's another room!")
    print("...but this time is a civilized room. Like in a basement or a bunker of some sort. It has lightbulbs installed on the ceiling. Blinding artificial light.")
    print("You no longer need these candles. You throw them on the floor or blow them off?")
    
    
case .dead:
    print("... you are dead")
    // This state is special, we don't wait for user input, so we move to `INTRO` immediately
    currentState = .newGame
    continue
    

}

// Read user input from terminal
guard let userInput = readLine() else {
    // Usually reach here because user press Ctrl+D, or we reach EOF
    // Better to break out of the `while` loop
    break
}





//--------------------------Decisions-------------------------------------




switch (currentState, userInput) {
case (.newGame, "push"):
    print("You push the door and enter a new closed room. This one is less darker than the first one.")
    currentState = .firstRoom
case (.newGame, "pull"):
    print("You try to pull the door to no avail. Several minutes pass as you try to pry open the door by placing your fingers on the cringes of the door. Your fingers hurt. Suddently a loud noise coming from the ceiling catches your attention. As soon as you look up,you realize there is a huge hole on the ceiling which you didn't see before. In a split second a giant monster jumps at you. You loose conciousness. Again.")
    currentState = .newGame

case (.firstRoom, "rock"):
    print("You pick up the rock and start walking forward. The room becomes darker as you move away from the lamp's light source. Your eyes try to adjust but just before they do, a shadow passes right in front of your eyes.")
    print("You turn around in fear and try to head back to the light. Something grabs your foot and drags you across the room. The last thing you felt was your bones being crushed.")
    currentState = .dead
    
case (.firstRoom, "lamp"):
    print("You pick up the lamp and start walking. There's another door right in front of you.")
    currentState = .secondRoomNoRock
    
case (.firstRoom, "wood"):
    print("You pick up the piece of wood just to realize you just can't hold it anymore than a couple of seconds. It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long. You wonder how long were you there?")
    print("Your fingertips start to tingle and become extremely warm. You look at the piece of wood now laying with the oposite face up and realize there is a strange mold stuck on it. It's now on your fingers!")
    print("Your heart beats rapidly...")
    print("You can't breathe")
    currentState = .dead
    
case (.firstRoom, "lamp and rock"):
    print("You pick up the lamp with one hand and the rock in the other. The rock is heavy, but you can hold it with one hand confortably. You feel more secure holding it.")
    currentState = .secondRoomWithRock
    
case (.firstRoom, "rock and lamp"):
    print("You pick up the lamp with one hand and the rock in the other. The rock is heavy. You feel more secure holding it.")
    currentState = .secondRoomWithRock
    
case (.secondRoomNoRock, "nil"):
    currentState = .dead
    
case (.secondRoomWithRock, "lamp"):
    print("You smash the lamp on the padlock and it breaks open. It emits a very loud noice as the glass shatters and metal parts collide. An awful smell of terpentine invades the air as it burns like a pool of fire on the ground.")
    print("As soon as the fuel is consumed... darkness... you can't see anything.")
    print("You hear a sound, as if something is aproaching... it hits you in the head. You fall unconcious on the ground.")
    currentState = .newGame
    
case (.secondRoomWithRock, "rock"):
    print("You smash the padlock with the rock one time and it does nothing. You continue to hit it a couple more, harder and harder each time. So much noise... you can feel the echo on the first room bouncing back towards you. The rock breaks apart right on your hand. Little pieces fall into the ground.")
    print("The rock is torn to pieces. You feel less secure now... but you realize the padlock is also broken.")
    print("You open the door and find yourself at a different room.")
    currentState = .thirdRoom
    
case (.thirdRoom, "left"):
    print("You choose to go left. As you move ahead the walls are slowly closing in...")
    print("You keep going forward a couple more meters when you suddently get stuck in between the walls.")
    currentState = .thirdRoomDecisionLeft
   
case (.thirdRoomDecisionLeft, "continue"):
    print("You make an effort to continue, moving a couple more inches forward. Now you are stuck for good. All your strenght is depleted as hours pass by trying to break free with no use. You scream, try jumping so you can break lose of this death grip these walls have you in. Your arms get lacerated with the textured stone walls and start bleeding. Frustration and pain govern your mind and soul. You faint because of the lack of bloodflow... you die...")
    currentState = .dead
    
case (.thirdRoomDecisionLeft, "back"):
    print("With much effort you get lose from the wall confinement and head back into the room with the three corridors.")
    currentState = .thirdRoom
    
case (.thirdRoom, "forward"):
    print("You keep moving straight ahead.")
    currentState = .thirdRoomDecisionForward
    
case (.thirdRoom, "right"):
    print("You take the right path. You have been walking for a couple of minutes and notice a very weak light on the distance.")
    print("...maybe theres someone there? or maybe if there is, was it that someone the responsible of your confinement in this place?")
    print("So many questions... your lamp is starting to die")
    print("You catch a glimpse of the wall to your right. THERE ARE BONES STUCK TO THE WALL!")
    print("...this path starts to fee very dangerous all of sudden.")
    currentState = .thirdRoomDecision2

case (.thirdRoomDecision2, "continue"):
    print("Despite the contradictions, you continue forward. As you approach the weak light you realize it's coming from two candles that are stuck to the wall on both sides of a door.")
    print("...your lamp suddenly depletes. You feel calm at the presence of another source of light.")
    print("What do you do? You can only push the door. You can tell by the hinges.")
    print("...maybe there's something else you can do?")
    print("You are not holding anything in your hards.")
    currentState = .thirdRoomEnd
    
case (.thirdRoomDecision2, "back"):
    print("...you decide to head back. It's just too risky to move forward seen these bones. Maybe a creature of some sort is the responsible.")
    print("You just can't risk it...")
    print("...the lamp dies...")
    print("You can't recall anything but loosing conciousness.")
    currentState = .newGame
    
    
case (.thirdRoomDecisionForward, "continue"):
    print("You certainly feel tired... you have been walking for a long time. Your lamp is now depleted. The little fuel it had left is now completely consumed. There's no reason to keep carrying it around. You throw it on the floor. The sound makes no echo... it's just emptiness. Everything is dark again. You can barely see your way through. You keep walking as carefully as you can when suddenly you fall down a slippery slope.. then a freefall...")
    print("^^^^^^^^^ You land in a pit full of spikes pointing upwards...^^^^^^^^^^")
    print("You die...")
    currentState = .newGame
    
case (.thirdRoomDecisionForward, "back"):
    print("You decide to go back, you lamp is almost out of fuel. You are barely half way to the room where you just came from. You start runing but the light depletes completly.")
    print("...darness")
    print("A very loud sound emits a couple of feet from where you are. Last thing you felt was something hitting you right on the back of your head.")
    
case (.thirdRoomEnd, "take candles"):
    currentState = .thirdRoomEndCandles
    
case (.thirdRoomEndCandles, "throw, floor, drop"):
    print("You decide to drop the candles right there on the floor. Why put them out. You might need them later.")
    print("...the candles hit the floor")
    print("The floor catches on FIRE!!!!!")
    print("... you burn to death")
    currentState = .dead
    
    case (.thirdRoomEndCandles, "put, out, blow, off, put them out"):
    print("TO BE CONTINUED...--------------------------------------------------------")
  
    
    
default:
    // Well, we don't know what the user is trying to do, lets just go to the next loop silently.
    break
}

}

There are just a couple of things I am trying to do here. For example, I want the terminal to display a ">Command:" whenever the user needs to type a decision. With a blinking cursor right next to it, of course.

Is there a way to let the answers we provide accept anything if the decision word is triggered?. For example: We could write the decision as "rock" or "Rock" or "pick up rock" or "PICK UP THE ROCK". I don't want it to be case sensitive. I wan't it to accept the answer as long as there is one word in the sentence. I tried to so this by adding all the possible ways to answer. But as you can see this is a lot of work. Maybe some simple code added to each decision instance or a code added to the ** switch (currentState, userInput) {**

A couple more things. Is there a way to make the story feel like it is being typed on a typewriter effect? And being able to control the speed of the effect that's making the words appear on the screen as if they were being typed one letter at a time. I hope you get the point =)

Is there a way to make this work on playgrounds? And display each decision like a button? If I could only find a way to import this to playgrounds. The code doesn't seem to run the same way there as in terminal.

Could this code also be ran so that each instance after each answer the screen clears out? There is so much gibberish on screen because it doesn't clear out after each answer.

Finally, every time I need to test the code I have to export it as .swift and then compile it into an executable by running swiftc ./adventure.swift. Is there a way I can do it from within Xcode? I remember Pascal having a compiler that ran your code whenever you wanted to test it out for yourself.

I am so grateful to you guys for helping me out. Sorry I lasted so long to reply, but between spending time with the kids and work, I can only find a few hours after everyone has gone to bed to be able to "study".

Thank you so much for your time and diligence. I am forever grateful.

First of, this forum will assume anything with 4-space indentation is a code block. You can also explicitly note something as a code by using tripple `, like this

```
Some code here.
```

Which will do

Some code here.

I find this very helpful for code dump.

Making things case-insensitive is surprisingly simple. You can cast them all to uppercase (or lowercase) before using the data.

// Read user input from terminal
guard let userInput = readLine()?.lowercased() else {
    // Usually reach here because user press Ctrl+D, or we reach EOF
    // Better to break out of the `while` loop
    break
}

Since readLine() returns a string, it enjoys all the functionality of the String including lowercased function which returns a new string with the same text, but in lower case.

Note the ? symbol after readLine(), this is because readLine() originally returns String? which can be nil. What swift does is that

  • If readLine() returns normal string, it'll run whatever after the ? symbol and return the result
  • If readLine() returns nil, it will not run lowercased, but instead return nil immediately.

This is called Optional Chaining if you're interested.
Now you're dealing exclusively with lowercase data regardless of user's input.

As for checking if it contains data, you can do a more complicated (but versatile) way such as regex. Swift provides this functionality via NSRegularExpression.

You can also opt-in for an easier approach, by splitting text into words, then check if any of those words exactly match "rock". It'd look like this.

// Put this at the very beginning of the file
import Foundation

// Split userInputs input multiple words using whitespace.
// The type of inputWords is `[String]` or `Array<String>`
let inputWords = userInput.components(separatedBy: .whitespaces)

switch currentState {
case .newGame where inputWords.contains("rock"):
  // Do something when currentState is newGame AND inputWords contains rock
}

This take advantage of case-where construct. Instead of just matching currentState, it runs a function after where and make sure that it returns TRUE.

Swift isn't exactly design for this. You can definitely print a single character, wait for a fixed moment, then print the next character, but it could be tricky to get right.

Clearing the screen is not part of Swift (not that I know of). You can try to print special character to make the screen delete certain character or flood the screen with whitespaces until it pushes all the previous text which many other programs of the day tend to do. All you need to do is to put it in print with appropriate character. You can use "\n" for newline character for example.

I don't know how to make it work with Playground, but you can definite make a command-line xcode project and compile (and run) from inside xcode, which you could find help online, or spawn another thread if you're stuck.

It's all cool. You're welcome here, cheers :3

You can also refactor code to separate the story from game logic. It's slightly more advance. I'll put it here if you're interested.

First, you may find multi-line string useful, so you can put a long string, that may contain special characters, including newline character.

So instead of

print("You pick up the piece of wood just to realize you just can't hold it anymore than a couple of seconds. It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long. You wonder how long were you there?")
print("Your fingertips start to tingle and become extremely warm. You look at the piece of wood now laying with the oposite face up and realize there is a strange mold stuck on it. It's now on your fingers!")
print("Your heart beats rapidly...")
print("You can't breathe")

You can do in one print

print("""
    You pick up the piece of wood just to realize you just can't hold it anymore than a couple of seconds. It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long. You wonder how long were you there?
    Your fingertips start to tingle and become extremely warm. You look at the piece of wood now laying with the oposite face up and realize there is a strange mold stuck on it. It's now on your fingers!
    Your heart beats rapidly...
    You can't breathe
    """)

The text will follow the indentation of the closing triple quote ("""), so make sure that everything has at least that much indentation.

What's cool about this is that you can now save the state->text into a variable, separating your game logic from the actual story. That is, you can put your message in a variable somewhere like this

/// current state,          beginning text
///      |                        |
///      |                        |
///      -----------------V       V
let beginningMessages: [State: String] = [
    .newGame: """
    You wake up in the middle of a dim lit room. You don't remember who you are. How did you get here?. You don't remember your name or where you came from.  You stand up, your knees shaking. This is a terrible place to be. You soon realize there's a door in front of you. You can barely see it. There's no handle on the door but it seems it's easily movable. What do you do?
    Push or Pull?
    """,
    .firstRoom: """
    AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD. YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?
    """,
    .secondRoomNoRock: """
    You found a locked door, hmmm... There's a padlock keeping it shut. If only you had something to smash it with.
    You get that feeling of being watched... you turn around and something hits you in the head.
    """,
]

///       Current state, input, next state, response
///               |        |       |          |
///               |        |       |        *-*
///               V        V       V        V
let responses: [State: [(String, State, String)]] = [
    .newGame: [
        ("push", .firstRoom, "You push the door and enter a new closed room. This one is less darker than the first one."),
        ("pull", .newGame, """
            You try to pull the door to no avail. Several minutes pass as you try to pry open the door by placing your fingers on the cringes of the door. Your fingers hurt. Suddently a loud noise coming from the ceiling catches your attention. As soon as you look up,you realize there is a huge hole on the ceiling which you didn't see before. In a split second a giant monster jumps at you. You loose conciousness. Again.
            """),
    ],
    .firstRoom: [
        ("rock", .dead, """
            You pick up the rock and start walking forward. The room becomes darker as you move away from the lamp's light source. Your eyes try to adjust but just before they do, a shadow passes right in front of your eyes.
            You turn around in fear and try to head back to the light. Something grabs your foot and drags you across the room. The last thing you felt was your bones being crushed.
            """),
        ("lamp", .secondRoomNoRock, "You pick up the lamp and start walking. There's another door right in front of you."),
        ("wood", .dead, """
            You pick up the piece of wood just to realize you just can't hold it anymore than a couple of seconds. It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long. You wonder how long were you there?
            Your fingertips start to tingle and become extremely warm. You look at the piece of wood now laying with the oposite face up and realize there is a strange mold stuck on it. It's now on your fingers!
            Your heart beats rapidly...
            You can't breathe
            """),
    ],
]

This make use of the Dictionary in Swift. beginningMessages let you access the right value (beginning text) if you know the left one (current state) by doing

beginningMessages[.newGame]

Which will return String?, it will return normal String if the dictionary contains it, and return nil if dictionary doesn't have it (it's not in the list). responses works on the same principle, only that the returned value will be array.

Dictionary requires the left type (State) to be Hashable. Since State is a simple enum, Swift will make it Hashable for you free-of-charge if you express the intend to do so, like this.

enum State: Hashable {
    case newGame, firstRoom, secondRoomNoRock, secondRoomWithRock, thirdRoom, thirdRoomDecisionLeft, thirdRoomDecisionForward, thirdRoomDecision2, thirdRoomEnd,thirdRoomEndDoorNoCandles, thirdRoomEndCandles, dead
}

So now, instead of switch with the state to print beginning message, you can do

if let message = beginningMessages[currentState] {
    // there's a message in the `beginningMessages`. Lets print it.
    print(message)
}

And so your code becomes.

import Foundation

enum State: Hashable {
    case newGame, firstRoom, secondRoomNoRock, secondRoomWithRock, thirdRoom, thirdRoomDecisionLeft, thirdRoomDecisionForward, thirdRoomDecision2, thirdRoomEnd,thirdRoomEndDoorNoCandles, thirdRoomEndCandles, dead
}

let beginningMessages: [State: String] = ...
let responses: [State: [(String, State, String)]] = ...

var currentState = State.newGame

while true {
    // Check if there's a message to print.
    if let message = beginningMessages[currentState] {
        // There's a message. Lets print it
        print(message)
    } else {
        // There no message here, hmm, let's print some error just in case
        // If this is intentional, just remove this line
        print("Error, no message for state \(currentState)")
    }
    
    guard let userInputWords = readLine()?.lowercased().components(separatedBy: .whitespaces) else {
        // Usually reach here because user press Ctrl+D, or we reach EOF
        // Better to break out of the `while` loop
        break
    }
    
    // Note the ??
    // since responses[currentState] can be `nil`
    // We tell swift to use `[]` (whatever after ??), instead of it indeed returns nil
    for (keyword, newState, response) in responses[currentState] ?? [] {
        // Check if any of the input words is the keyword
        if userInputWords.contains(keyword) {
            // Found the right keyword
            // print the response, and move to next state
            print(response)
            currentState = newState

            // We don't want to check anymore, lets break out of the `for` loop
            break
        }
    }
}

So once you can put everything in a project, you can put beginningMessages and responses in a separate file. You can even try to have Swift load it dynamically when the program starts, but that's a story for another time.

I’m sorry that I wasn’t clear. What I meant was if you go through the Apple-provided Playgrounds you will gain the skills you need to write your own implementation inside Swift Playgrounds.

Hi Lantua!

I am already using as much as I can comprehend into my code.

Certainly the:

print('''

has been so much help. Again, I cannot express how grateful I am to you guys for helping out. This is a challenge for me. And a tough one to be honest.

I'm kind of stuck trying to figure something out. Take a look at this code:

if {
case (.thirdRoomEndDoorCode, "1337"):

        print("""

SUCCESS!... The door opens.
The red light now turns green.
You push the door.
It makes a loud noise. These hinges have not moved in a long time.
Dust particles fly in the air.
You slowly walk though the door.

""")
currentState = .bunkerRoom1

        else do {
        
        print("""
        
        ----------------------------------------------------------------
        A loud alarm is triggered!...
        Suddenly a green gas is descending from the holes in the ceiling.
        You try not to breathe it...
        You cannot hold your breath anymore...
        
        """)
        currentState = .dead
        }
    }

Basically what I am trying to do is:

-If the user input is "1337", then I want to currentState = .bunkerRoom1
-If the userInput is "nil" (nothing is typed but enter is pressed) or userInput is anything BUT "1337" then I want to currentState = .dead or:

print("""

    ----------------------------------------------------------------
    A loud alarm is triggered!...
    Suddenly a green gas is descending from the holes in the ceiling.
    You try not to breathe it...
    You cannot hold your breath anymore...
    
    """)
    currentState = .dead

I have tried assigning a "let code = "1337" but I just don't know how to make it so that the program calls for the let value and check if it's true or false.

Also tried "var code = 1" but need an if statement that let's the "var code" be equal to 1 if userInput is "1337", or if anything else then "var code = 0" and then I can do a simple code to say:

if var code = 1
print("SUCESS!")
currentState =.bunkerRoom1

if var code = 0
print("WRONG CODE!")
currentState = .dead

I know what I'm trying to do, but I just don't know how to do it. Or am I approaching this all wrong?

I'm gonna assume that you're somewhere a little past version 3, where you have userInput, but not inputWords and responseMessages.

In that case you'll have userInput of type String.

You want to check if userInput is exactly equal to code. In that case you can use equality operator.

let code = "1337"

if currentState == .thirdRoomEndDoorCode && userInput == code {
  // Reads currentState *equals* thirdRoomEndDoorCode **AND** userInput *equals* code

  // user input is "1337", do something
  currentState = .bunkerRoom1
  // We're in the while loop right now
  // Since we change the state, it's probably wise to start from the beginning of the loop again.
  // continue will do just that, ends this cycle immediately, and go to the next one
  continue
} else if currentState == .thirdRoomEndDoorCode {
  // user input is something else, do something else
  print("""
    hmm, wrong code. You're probably dead.
    """)
  currentState = .dead
  // We're in the while loop right now
  // Since we change the state, it's probably wise to start from the beginning of the loop again.
  // continue will do just that, ends this cycle immediately, and go to the next one
  continue
}

A few things to note here

  • if/else requires you to use bracket {} to denote everything that you want to execute. This is deliberate to make sure that you don't put things in the wrong place.
  • readLine() will return nil only when user press Ctrl+D (End-Of-File signal). If user simply press enter, it will return empty string "". The difference is subtle, but important.
  • let and var creates variable, and do nothing else. You can create variable named code, then compare if it is equal to userInput, but you'll need to do them on 2 separate lines, like the code above.
  • I'm sure you still have the switch clause that does just this. You could try to incorporate checking into the switch
switch (currentState, userInput) {
case (.thirdRoomEndDoorCode, "1337"):
  // Correct code, do something
case (.thirdRoomEndDoorCode, _):
  // Everything else. we use wilecard (_) to match everything.
  // Since we put this after "1337" case, if user input is actually 1337,
  // it'll match the above case first, and never this one.
}

Or

switch (currentState) {
case .thirdRoomEndDoorCode where userInput == "1337":
  // Again, correct code. Note the difference in the beginning of switch variables, and the way we compare `userInput`
case .thirdRoomEndDoorCode:
  // Everything else. Again, since this is after 1337 case, it won't match the actual 1337 case.
}

PS

Be careful with the quotes.

  • This forum uses grave accent (`) and tripple grave accent (```) to denote the beginning and the end of code block.
    `some code` becomes some code
  • Double quote (", """) has special meaning in Swift and is used to denote String values, while single quote (') is not used.

Mixing them up can make it confusing in the forum as to whether or not there's something wrong with the code.

I can't get to insert the identifier "userInput". For some reason this:

switch (currentState, userInput) {

returns an error "Use of unresolved identifier 'userInput' "

It is the only error I am getting in all the code I just implemented.

Also, had to add all this so it doesn't error:

switch (currentState, userInput) {
    case (.thirdRoomEndDoorCode, "1337"):
        currentState = .bunkerRoom1
    // Correct code, do something
    case (.thirdRoomEndDoorCode, _):
        currentState = .dead
        // Everything else. we use wilecard (_) to match everything.
        // Since we put this after "1337" case, if user input is actually 1337,
        // it'll match the above case first, and never this one.
    case (.newGame, _):
        <#code#>
    case (.firstRoom, _):
        <#code#>
    case (.secondRoomNoRock, _):
        <#code#>
    case (.secondRoomWithRock, _):
        <#code#>
    case (.thirdRoom, _):
        <#code#>
    case (.thirdRoomDecisionLeft, _):
        <#code#>
    case (.thirdRoomDecisionForward, _):
        <#code#>
    case (.thirdRoomDecision2, _):
        <#code#>
    case (.thirdRoomEnd, _):
        <#code#>
    case (.thirdRoomEndDoorNoCandles, _):
        <#code#>
    case (.thirdRoomEndCandles, _):
        <#code#>
    case (.thirdRoomEndDoor, _):
        <#code#>
    case (.thirdRoomEndDoorExplore, _):
        <#code#>
    case (.bunkerRoom1, _):
        <#code#>
    case (.dead, _):
        <#code#>
    }
}
    

A im playing with this a little more to see if I can get it to work before going to bed.

Thanks!

When every you want to use some variable, you need to create it first (via let or var, or just take it as function argument).

Did you write this part after you created userInput?

I did say it before, but switch wants you to be exhaustive, and handle all possibilities, that’s why you need to add all possible cases. You can add default to match everything else like so:

switch somethingYouWantToMatch {
case possibility1:
  // do something
case possibility2:
  // do something
default:
  // This will match everything else that doesn't match any above case.
  // Switch will try to match each case in sequential order, so put this last.
}

Though it’s generally better if you can handle all cases and don’t need to write default.

Hey. I think I really, really broke it.

I think I will have to start all over again. The code is not making sense to me. Maybe it's something simple, I dunno. Had to remove the accept lower case, upper case. After that, for some reason the game was stuck on the point where the userInput needs to be "blow candles" it just kept repeating and repeating asking for the answer.

But now, it's just stuck on an endless loop. XD

It would be easier for me if I could insert another .swift file into the project and all the decisions code could be written there. It's so confusing having to scroll up and down on such a large file. And it's bound to get a LOT bigger.

Is there a way to make a decisions.swift inside the project and have main.swift communicate with it?

Anyways, this is the code. Maybe you can take a look? Please!

I think I made a really big mess somewhere or all over. It doesn't feel structured and organized.

//
//  main.swift
//  AdventureGame
//
//  Created by Cesar Morales on 7/19/19.
//  Copyright © 2019 Cesar Morales. All rights reserved.
//

import Foundation
// Create type State
enum State {
    // Declare 3 possible cases, begin with keyword case
    case newGame, firstRoom, secondRoomNoRock, secondRoomWithRock, thirdRoom, thirdRoomDecisionLeft, thirdRoomDecisionForward, thirdRoomDecision2, thirdRoomEnd,thirdRoomEndDoorNoCandles, thirdRoomEndCandles,thirdRoomEndCandlesDrop,thirdRoomEndCandlesBlow, thirdRoomEndDoor, thirdRoomEndDoorExplore, thirdRoomEndDoorCode, bunkerRoom1, dead
}

let code = "1337"
var userInput = ""

// Set currentState to value `intro` in the `State` type
var currentState = State.newGame

while true {
    // Print appropriate message
    
    switch currentState {
        
    case .newGame: print("""


-----------------------------------------------------------
You wake up in the middle of a dim lit room. You don't remember who you are.
How did you get here?. You don't remember your name or where you came from.
You stand up, your knees shaking. This is a terrible place to be.
You soon realize there's a door in front of you. You can barely see it.
There's no handle on the door but it seems it's easily movable.
What do you do?")

Push or Pull?
""")
        print("\n")
        print("\n")
        
    case .firstRoom: print("""


-----------------------------------------------------------
AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD.
YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?
""")
    print("\n")
    print("\n")
        
    case .secondRoomNoRock: print("""


-----------------------------------------------------------
You found a locked door, hmmm... There's a padlock keeping it shut.
If only you had something to smash it with.
You get that feeling of being watched...
...you turn around and something hits you in the head.
""")
        currentState = .dead
        
    case .secondRoomWithRock: print("""


-----------------------------------------------------------
You found a locked door... but there's a padlock keeping it shut.
It's rusty and it seems very old.
You have a lamp in one hand and a rock in the other.
Maybe you can smash it with something. What do you use?
""")
    print("\n")
    print("\n")
        
    case .thirdRoom: print("""


-----------------------------------------------------------
Theres a stench in the air.
You don't know where it comes from as there are three different corridors on this room.
You must choose one of them. They are identical.
Do you choose to go left, right or forward?
""")
    print("\n")
    print("\n")
        
    case .thirdRoomDecisionLeft: print("""


-----------------------------------------------------------
Do you want to try and continue moving forward or head back?.
The way out can be just ahead, but you don't know that for sure.
""")
    print("\n")
    print("\n")
    
    case .thirdRoomDecisionForward: print("""


-----------------------------------------------------------
You decide to go forward. You feel you have walked a straight mile.
Is your mind playing tricks on you?.
Your lamp is runing low on fuel. Do you want to continue walking or head back?
""")
    print("\n")
    print("\n")
    
   
    case .thirdRoomDecision2: print("Do you continue forward or head back?")
    print("\n")
    print("\n")
    
    case .thirdRoomEnd: print("""


-----------------------------------------------------------
What do you do?
Do you take the candles or push the door?
""")
    print("\n")
    print("\n")
        
        
    case .thirdRoomEndDoorNoCandles: print("""


-----------------------------------------------------------
You push the door and hear the sound of heavy gears moving.
Before you can even take the first step forward through the door,
a huge boulder drops on your head.
""")
        currentState = .dead
        
    case .thirdRoomEndCandles: print("""


-----------------------------------------------------------
You pick up the candles. One on each hand. Their warmth is conforting.
The idead of having a light source is reassuring.
It doesn't remove your doubts or fears, but it's helping alright.
You hear a subtle sound of what appears to be a moving platform right on top of you.
It sounds like something moved.

You take a few steps forward and push the door.
You can't believe your eyes. It's another room!
...but this time is constructed room.
You are certainly out of the cave you just woke up into.
This room looks like a basement or a bunker of some sort.
It has lightbulbs installed on the ceiling.
Blinding artificial light.
You no longer need these candles.
Do you throw them on the floor or blow them off?
""")
    
    print("\n")
        
    case .thirdRoomEndCandlesDrop:
        print("""


--------------------------------------------------------------
You decide to drop the candles right there on the floor.
Why put them out. You might need them later.
...the candles hit the floor
The floor catches on FIRE!!!!!
... you immediatedly burn to death.

""")
        currentState = .dead
        
    case .thirdRoomEndCandlesBlow:
            print("""


--------------------------------------------------------------
You inhale a lot of air and exhale through you mouth.
The candle fire is gone.                                      .______.
Looking closely You notice some barrels with a tag that reads |"FUEL"|
                                                              '------'
The ground is full of fuel that has been leaking from the barrels.
What would of happened had you thrown the lit candle to the floor.
This is the first time death crosses your mind.
You could of died...

There's a very old looking generator.
The generator is turned off.
Maybe there is another generator runing?
Maybe this electricity comes from the grid.
Which would mean there could be people runing this place.

The generator is inside a metal cage.
There's a lock with a keyhole on the cage's door.
You see some wires attached to one of the sidea of the metal cage.
---------------------------------------------------------------

""")
            currentState = .thirdRoomEndDoor
        
    case .thirdRoomEndDoor: print("""


-----------------------------------------------------------
What do you do?
You can try and see if the door is unlocked by pushing or pulling the door.
You can also explore the room a with more detail.

""")
        
    case .dead:
        print("... you are dead")
        // This state is special, we don't wait for user input, so we move to `INTRO` immediately
        currentState = .newGame
        continue
        
  
    case .thirdRoomEndDoorExplore:
        print("""


-----------------------------------------------------------
You've decided to explore the room with a little more detail.
To your upper left corner there is the metal mesh cage that encases the old generator.
Right in front of that, between you and the cage there are some barrels of fuel.
Right next to the metal cage there is a metal door. It seems locked.
You can tell because there is a red light on top of it.
-----------------------------------------------------------
Right next to the door there's a keypad. Some keys are stripped out.

  .--------------.
  |[7]  [8]   X  |'
  |              | '
  | X   [5]   X  | |
  |              | |
  |[1]   X   [3] | |
  .--------------/'

Only keys 1, 3, 5, 7 and 8 are functioning.
Inspecting the keypad closely you find keys 7-3-1 are worn more than the others.
There's a very small label on the bottom that reads:
"only 1 imput allowed. Please verify your 4 digit code before input"
-----------------------------------------------------------
To the right of the door theres a shelf, nothing is on it but a piece of paper.
You take a look at the space to your right upper corner.
There's some empty boxes and a very old and worn desk.
There's oil all over the table. Maybe it was used by a technician of some sort.
-----------------------------------------------------------
You turn around and find a message on the door you just came from.
You barely notice it bacause the door is just partially open.
It reads:

.--------------------------------.
|            [CAUTION]           |
|   [DO NOT OPEN. NEVER EVER.]   |
|    [DANGER! DANGER! DANGER!]   |
|                                |
.--------------------------------.

You just came from there...
-----------------------------------------------------------
What do you want to do?
1. You can push or pull the metal mesh door.
2. You can try and input the code on the keypad.
3. You can inspect the note that's on the shelf to the right.
4. You can head back to the tunnel where you came from.
5. You can close the door where you just came from.
6. You can inspect the ceiling.


""")
    case .thirdRoomEndDoorCode:
        print("""

------------------------------------------------------------
You stand in front of the keypad beside the door...
INPUT CODE:
>

""")

        
        
    case .bunkerRoom1:
        print("to be continued")
 
    }
    
   
    
}
// Read user input from terminal

    
    
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
     //--------------------------Decisions-------------------------------------
    





switch (currentState, userInput) {
    

    
case (.thirdRoomEndDoorCode, "1337"):
    
    print("""
                
----------------------------------------------------------------
SUCCESS!... The door opens.
The red light now turns green.
You push the door.
It makes a loud noise. These hinges have not moved in a long time.
Dust particles fly in the air.
You slowly walk though the door.
                
""")
    
    currentState = .bunkerRoom1
// Correct code, do something
case (.thirdRoomEndDoorCode, _):
    print("""
                        
----------------------------------------------------------------
A loud alarm is triggered!...
Suddenly a green gas is descending from the holes in the ceiling.
You try not to breathe it...
You cannot hold your breath anymore...

""")
    currentState = .dead
    // Everything else. we use wilecard (_) to match everything.
    // Since we put this after "1337" case, if user input is actually 1337,
    // it'll match the above case first, and never this one.
    
    
    
    
    
    
case (.newGame, "push, push the door, push door"):
    print("""


-----------------------------------------------------------
You push the door and enter a new closed room.
This one is less darker than the first one.
""")
    currentState = .firstRoom
    
    if userInput == "1337" {
        currentState = .bunkerRoom1
        
    }
    
case (.newGame, "pull"):
    print("""


-----------------------------------------------------------
You try to pull the door to no avail.
Several minutes pass as you try to pry open the door by placing
your fingers on the cringes of the door.
Your fingers hurt.
Suddently a loud noise coming from the ceiling catches your attention.
As soon as you look up,you realize there is a huge hole on the ceiling
which you didn't notice before.
In a split second a giant monster jumps at you.
You loose conciousness. Again.
""")
    currentState = .newGame
    
case (.firstRoom, "rock"):
    print("""


-----------------------------------------------------------
You pick up the rock and start walking forward.
The room becomes darker as you move away from the lamp's light source.
Your eyes try to adjust but just before they do, a shadow passes right in front of your eyes.
You turn around in fear and try to head back to the light.
Something grabs your foot and drags you across the room.
The last thing you felt was your bones being crushed.
""")
    currentState = .dead
    
case (.firstRoom, "lamp"):
    print("You pick up the lamp and start walking.")
    print("There's another door right in front of you.")
    currentState = .secondRoomNoRock
    
case (.firstRoom, "wood"):
    print("""


-----------------------------------------------------------
You pick up the piece of wood just to realize you just can't hold it anymore
than a couple of seconds.
It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long.
You wonder how long were you there? Your fingertips start to tingle and become extremely warm.
You look at the piece of wood now laying with the oposite face up and realize
there is a strange mold stuck on it.
It's now on your fingers!!!
Your heart beats rapidly...
You can't breathe
""")
    currentState = .dead
    
case (.firstRoom, "lamp and rock"):
    print("""


-----------------------------------------------------------
You pick up the lamp with one hand and the rock in the other.
The rock is heavy, but you can hold it with one hand confortably.
You feel more secure holding it.
""")
    currentState = .secondRoomWithRock
    
case (.firstRoom, "rock and lamp"):
    print("""


-----------------------------------------------------------
You pick up the lamp with one hand and the rock in the other.
The rock is heavy. You feel more secure holding it.
""")
    currentState = .secondRoomWithRock
    
case (.secondRoomNoRock, "nil"):
    currentState = .dead
    
case (.secondRoomWithRock, "lamp"):
    print("""


-----------------------------------------------------------
You smash the lamp on the padlock and it breaks open.
It emits a very loud noice as the glass shatters and metal parts collide.
An awful smell of terpentine invades the air as it burns like a pool of fire on the ground.
As soon as the fuel is consumed... darkness... you can't see anything.
You hear a sound, as if something is aproaching... it hits you in the head.
You fall unconcious on the ground.
""")
    currentState = .newGame
    
case (.secondRoomWithRock, "rock"):
    print("""


-----------------------------------------------------------
You smash the padlock with the rock one time and it does nothing.
You continue to hit it a couple more, harder and harder each time.
So much noise...
You can feel the echo on the first room bouncing back towards you.
The rock breaks apart right on your hand. Little pieces fall into the ground.
The rock is torn to pieces. You feel less secure now...
...but you realize the padlock is also broken.
You open the door and find yourself at a different room.
""")
    currentState = .thirdRoom
    
case (.thirdRoom, "left"):
    print("You choose to go left. As you move ahead the walls are slowly closing in...")
    print("You keep going forward a couple more meters when you suddently get stuck in between the walls.")
    currentState = .thirdRoomDecisionLeft
    
case (.thirdRoomDecisionLeft, "continue, forward, continue forward, keep going"):
    print("""


-----------------------------------------------------------
You make an effort to continue, moving a couple more inches forward.
Now you are stuck for good. All your strenght is depleted as hours pass
by trying to break free with no use.
You scream because you feel impotent.
You try jumping so you can break lose of this death grip these walls have you in.
Your arms get lacerated with the textured stone walls and start bleeding.
Frustration and pain govern your mind and soul.
You faint because of the lack of bloodflow...
... you die...
""")
    currentState = .dead
    
case (.thirdRoomDecisionLeft, "back"):
    print("With much effort you get lose from the wall confinement and head back into the room with the three corridors.")
    currentState = .thirdRoom
    
case (.thirdRoom, "forward"):
    print("""


-----------------------------------------------------------
You keep moving straight ahead.
""")
    currentState = .thirdRoomDecisionForward
    
case (.thirdRoom, "right"):
    print("""


-----------------------------------------------------------
You take the right path.
You have been walking for a couple of minutes and notice a very weak light on the distance.
...maybe theres someone there? or maybe if there is...
...was it that someone the responsible of your confinement in this place?
So many questions... your lamp is starting to die.
You catch a glimpse of the wall to your right. THERE ARE BONES STUCK TO THE WALL!
...this path starts to feel very dangerous all of sudden.
You wonder if you want to continue down this path...
""")
    currentState = .thirdRoomDecision2
    
case (.thirdRoomDecision2, "continue"):
    print("""


-----------------------------------------------------------
Despite the contradictions, you continue forward.
As you approach the weak light you realize it's coming from two candles that are stuck to the wall on both sides of a door.
...your lamp suddenly depletes. You feel calm at the presence of another source of light.
What do you do? You can only push the door. You can tell by the hinges.
...maybe there's something else you can do?
You are not holding anything in your hards.
""")
    currentState = .thirdRoomEnd
    
case (.thirdRoomDecision2, "back"):
    print("...you decide to head back. It's just too risky to move forward seen these bones. Maybe a creature of some sort is the responsible.")
    print("You just can't risk it...")
    print("...the lamp dies...")
    print("You can't recall anything but loosing conciousness.")
    currentState = .newGame
    
    
case (.thirdRoomDecisionForward, "continue"):
    print("""


-----------------------------------------------------------
You certainly feel tired... you have been walking for a long time.
Your lamp is now depleted. The little fuel it had left is now completely consumed.
There's no reason to keep carrying it around. You throw it on the floor.
The sound makes no echo... it's just emptiness. Everything is dark again.
You can barely see your way through.
You keep walking as carefully as you can when suddenly you fall down a slippery slope..
then a freefall...
------------------------------------------------------------
""")
    print("^^^^^^^^^ You land in a pit full of spikes pointing upwards...^^^^^^^^^^")
    print("You die...")
    currentState = .newGame
    
case (.thirdRoomDecisionForward, "back"):
    print("""
You decide to go back, you lamp is almost out of fuel.
You are barely half way to the room where you just came from.
You start runing but the light depletes completly.
...darness
-------------------------------------------------------------
A very loud sound emits a couple of feet from where you are. Last thing you felt was something hitting you right on the back of your head.
""")
    
case (.thirdRoomEnd, "take candles, candles"):
    currentState = .thirdRoomEndCandles
    
case (.thirdRoomEndCandles, "throw, floor, drop, drop on floor, throw candles, throw candles to the floor"):
    
    currentState = .thirdRoomEndCandlesDrop
    
case (.thirdRoomEndCandles, "put, out, blow, off, put them out, blow candles"):
    
    currentState = .thirdRoomEndCandlesBlow
    
    
case (.thirdRoomEndDoor, "explore, explore the room"):
    currentState = .thirdRoomEndDoorExplore
    
case (.thirdRoomEndDoor, "push, pull, push the door, pull the door"):
    print("""


---------------------------------------------------------------
You've decided to open the door, and as soon as your fingers touch the mesh metal door...

You get electrocuted...
... you die...
""")
    
case (.thirdRoomEndDoorExplore, "1"):
    print("""


---------------------------------------------------------------
You've decided to open the door, and as soon as your fingers touch the mesh metal door...

220 volts run through your body...

You get electrocuted...

... you die...
""")
    
    
case (.thirdRoomEndDoorExplore, "2"):
    currentState = .thirdRoomEndDoorCode
    
    
    
    
default:
    // Well, we don't know what the user is trying to do, lets just go to the next loop silently.
    break
}






// Read user input from terminal
// Usually reach here because user press Ctrl+D, or we reach EOF
// Better to break out of the `while` loop




Remember, you need to take input from the user at some point, then assign it to userInput. I believed you deleted that accidentally.

TD;DR The snippet for 2 files are at the bottom

I see the copyright claim at the beginning of the file. Which means that you successfully make a project and compile from it!

In that case, you can definitely create a new file by going to
File -> new -> file. Then choose Swift file.

Everything in the project can see each other so if you do.

// In decision.swift

func decisionForFirstRoom(input: String) -> State {
  // do something
  return .dead
}

other files can see it too, (there's a finer grained control, Access Control, but it's fine to use the default setting for now).

// In main.swift
// I can see `test` and can do this

let newState = decisionForFirstRoom(input: userInput)

Hmm, I've never explain the concept of function, have I?
At times, you'll find it useful to put a chunk of code somewhere else, or you want to run the same code at multiple places (which used to be done using goto).
This is where function comes in. This code

//   function name (argument name : type ) -> return type { some code }
//          |               |        |         |
func decisionForFirstRoom(input: String) -> State {
  // do something
  // put some code here
  return .dead
}

creates a function that you can use elsewhere. It doesn't run anything; it simply defines what decisionForFirstRoom will look like. That why it's called function definition. It says that it's a function that takes one argument or input (named input and has type String), and return one output of type State.

So anywhere in your code, you can do

let newState = decisionForFirstRoom(input: userInput)

And it will run a code inside decisionForFirstRoom, using value of userInput as input inside the code block, then return whatever is after the return keyword. You then save it into newState.

If you don't want to return anything, just want to execute something, or if you want multiple inputs (arguments) you can do.

// 2 arguments named argumentName1 and argumentName2 respectively
func someFunction(argumentName1: String, argumentName2: Int) {
  // do something
}

which you can call by running

someFunction(argumentName1: "SomeString", argumentName2: 42)

And again, it'll run whatever inside the someFunction definition.

So you can make function, one for each state, and have a big switch to choose which one to run, like this.

// In main.swift
enum State {
    case newGame, firstRoom, secondRoomNoRock, secondRoomWithRock, thirdRoom, thirdRoomDecisionLeft, thirdRoomDecisionForward, thirdRoomDecision2, thirdRoomEnd,thirdRoomEndDoorNoCandles, thirdRoomEndCandles,thirdRoomEndCandlesDrop,thirdRoomEndCandlesBlow, thirdRoomEndDoor, thirdRoomEndDoorExplore, thirdRoomEndDoorCode, bunkerRoom1, dead
}

// Set currentState to value `intro` in the `State` type
var currentState = State.newGame

while true {
    // Print appropriate message
    switch currentState {
    case .newGame: printNewGameMessage()
    case .firstRoom: printFirstRoomMessage()
    // ... fill the rest

    // YOU SHOULDNT NEED DEFAULT, INCLUDE EVERY CASES THEN REMOVE THIS
    default: break
    }

    // READ USER INPUT HERE, THEN WE WILL HAVE USERINPUT TO TO USE LATER ON
    guard let userInput = readLine().lowercased() else {
        // User press Ctrl+D, lets bail out
        break
    }
    
    switch currentState {
    case .newGame: currentState = stateAfterNewGame(input: userInput)
    case .firstRoom: currentState = stateAfterFirstRoom(input: userInput)
    // ... fill the rest

    // YOU SHOULDNT NEED DEFAULT, INCLUDE EVERY CASES THEN REMOVE THIS
    default: break
    }

Then put these functions in separate file

// In decision.swift
import Foundation

func printNewGameMessage() {
    print("""
        -----------------------------------------------------------
        You wake up in the middle of a dim lit room. You don't remember who you are.
        How did you get here?. You don't remember your name or where you came from.
        You stand up, your knees shaking. This is a terrible place to be.
        You soon realize there's a door in front of you. You can barely see it.
        There's no handle on the door but it seems it's easily movable.
        What do you do?
        
        Push or Pull?
        
        
        """)
}

func printFirstRoomMessage() {
    print("""
        -----------------------------------------------------------
        AS SOON AS YOU ENTER THE DOOR YOU SEE A LAMP, A ROCK AND A PIECE OF WOOD.
        YOU NEED TO CONTINUE WALKING FORWARD. WHAT DO YOU PICK UP?
        
        
        """)
}

func stateAfterNewGame(input: String) -> State {
    // Lets split input by words, so we have easier time detect certain keywords
    let inputWords = input.components(separatedBy: .whitespaces)

    if inputWords.contains("push") {
        print("""
            -----------------------------------------------------------
            You push the door and enter a new closed room.
            This one is less darker than the first one.
            """)
        // At this point, we can tell whoever is calling this function that the next state is .firstRoom.
        // The return keyword will also immediately exit the function without doing anything else
        return .firstRoom
    }

    if userInput == "1337" {
        return .bunkerRoom1
    }

    if inputWords.contains("pull") {
        print("""
            -----------------------------------------------------------
            You try to pull the door to no avail.
            Several minutes pass as you try to pry open the door by placing 
            your fingers on the cringes of the door.
            Your fingers hurt.
            Suddently a loud noise coming from the ceiling catches your attention.
            As soon as you look up,you realize there is a huge hole on the ceiling
            which you didn't notice before.
            In a split second a giant monster jumps at you.
            You loose conciousness. Again.
            """)
        return .newGame
    }
}

func stateAfterFirstRoom(input: String) {
    // Split input by whitespace again, this way we will have easier time detecting if input contains certain keyword.
    let inputWords = input.components(separatedBy: .whitespaces)

    if inputWords.contains("rock") {
        print("""
            -----------------------------------------------------------
            You pick up the rock and start walking forward.
            The room becomes darker as you move away from the lamp's light source.
            Your eyes try to adjust but just before they do, a shadow passes right in front of your eyes.
            You turn around in fear and try to head back to the light.
            Something grabs your foot and drags you across the room.
            The last thing you felt was your bones being crushed.
            """)
        return .dead    
    }

    if inputWords.contains("lamp") {
        print("""
            You pick up the lamp and start walking.
            There's another door right in front of you.
            """)
        return .secondRoomNoRock
    }
    
    if inputWords.contains("wood") {
        print("""
            -----------------------------------------------------------
            You pick up the piece of wood just to realize you just can't hold it anymore
            than a couple of seconds.
            It's just too heavy. Your back hurts. Maybe from laying down on the floor for too long.
            You wonder how long were you there? Your fingertips start to tingle and become extremely warm.
            You look at the piece of wood now laying with the oposite face up and realize
            there is a strange mold stuck on it.
            It's now on your fingers!!!
            Your heart beats rapidly...
            You can't breathe
            """)
        return .dead
    }

    if inputWords.contains("lamp") && inputWords.contains("rock") {
        print("""
            -----------------------------------------------------------
            You pick up the lamp with one hand and the rock in the other.
            The rock is heavy, but you can hold it with one hand confortably.
            You feel more secure holding it.
            """)
        return .secondRoomWithRock
    }
}

As I said that every file sees each other, you can separate them in as much as you'd like. You can even split them into earlyGame.swift, midGame.swift and endGame.swift.
Or even printingMessage.swift and decision.swift.
The freedom is yours.

Note that, aside from within main.swift, you can not run any code. You can only declare/define new functions, variables, types (like how I defines printNewGameMessage, I didn't actually run it).

PS

I also put on comments throughout the files explaining what the code is doing. Everything after // will be ignored, so you can safely delete it once you no longer need it.

This code is wayyyyyy more difficult than the other I was using. I've been studying it and adding the missing story to it but I don't yet have the complete knowledge to handle this level right now. :exploding_head:

I prefer just sticking the other one (the one I've been using) and splitting it into 2 files. One that has all the messages and another one that has all the userInputs. I have been successful in fixing the issue that when you run it, it endlessly loops. Now it's fixed.

The thing I am trying to fix from the code is that I can't get pass the 1st userInput, the one on .newGame where you have to push or pull the door.

Second, If I try to copy everything after the /////////DECISIONS///////// in the middle of the file to a second file named "decisions.swift", says an error on top that reads "Statements are not allowed on the top-level", right next to

switch (currentState, userInput) {

The main.swift is a little special, it lets you put a code in there, and the compiler will realize that you want run that code when you start the program.

In other files, what you can put there is more restricted.
It does make sense when you think about it. If you just dump a bunch of code. Swift doesn't know how to run code in both files. Should it run them concurrently? Should it run one after another? Which one should run first?

I'd suggest that you try to learn about function if you want to increase the scale of your project. Without it, organizing your code (splitting things into files, for example) will be very hard, if not impossible. As you can see, it'll quickly become a big chunk of text dump that is hard to track.