Game: Stranger Wings

Game: Stranger Wings #

Stranger Wings is a text adventure game along the lines of Colossal Cave Adventure (1976) or Zork (1977).

The player’s character will start the game by waking up in the alley behind the Stranger Wings chicken wings restaurant where they work. The player enters commands to explore and move around the environment, pick up items, and use things on other things.

Design #

Stranger Wings is a bit more involved than the games we’ve developed so far, so it will pay to plan out the design of the game first before we start coding. Once we have a high level design, we can drill down into some details to determine what features exactly need to be implemented.

High Level #

The player character (PC) begins the game by waking up without any pants in the alleyway behind the Stranger Wings restaurant where they work. The PC has presumably had a pretty bad day so far to find themselves in this situation and would like nothing more than to hop onto their bicycle and head home, if only their pants weren’t missing.

The player must explore the environment to find the PC’s pants so they can head home. Unfortunately, the PC’s pants are being guarded by Bitey, the restaurant owner’s “cat”. Bitey loves chicken wings though, so the player needs to cook up a quick batch of wings for Bitey in exchange for their pants. Now properly clothed, the player can finally leave the restaurant without dying of embarassment.

Environment #

The Stranger Wings environment consists of several locations, and each location has directions that the player can choose to travel in order to move to a different location.

Stranger Wings: Location Map, a line drawing of the game's locations relative to each other
  • Alley
    • down into the basement
  • West Basement
    • east into the east basement
  • East Basement
    • west into the west basement
    • up into the kitchen
  • Kitchen
    • down into the west basement
    • east into the storage room
    • south into the walk in freezer
    • north into the dining room
  • Storage Room
    • west into the kitchen
  • Walk In Freezer
    • north into the kitchen
  • Dining Room
    • north onto the street
    • south into the kitchen

Note that there is a connection from the Alley down into the basement, but there is no connection from the basement back up to the alley. After dropping down from the alley into the basement, they player won’t be able to return to the alley, but they are otherwise free to roam through all of the other rooms.

To win the game, all they need to do is walk out the front door of the restaurant, but in order to do that, they’re going to need to find their pants first.

Items #

There are several items the player can pick up and use, scattered through the environment. The most important item is of course the protagonist’s pants, which allow the player to exit the restaurant safely.

Let’s work backwards from the end to build the puzzle…

  • Bitey the cat will gaurd the pants. Bitey demands cooked chicken wings, so if the player offers Bitey wings, the player will receive the pants in exchange.
  • In order to obtain the cooked wings, the player will have to find the frozen chicken wings in the freezer, and then use them on the deep fryer in the kitchen to cook them.
  • The freezer will be frozen shut, so they’ll need a crowbar to open the freezer.
  • The crowbar can be found on the floor in the basement.

We have our full list of items: pants, cooked wings, frozen chicken, crowbar.

Objects #

You may have noticed that we’ve quietly introduced a new concept, which I will call objects. An object is a thing in the environment that can’t be picked up, but it can have items used on it to produce some result. The two objects are the deep fryer, and Bitey the cat.

To keep things simple, in both cases these objects will convert one specific item into another specific item. The deep fryer will accept raw chicken and return cooked chicken wings, Bitey will accept cooked chicek wings, and give pants back in exchange.

Keep in mind that the term object here is somewhat arbitrary, I chose this word to mean this kind of thing in this particular game. What constitutes a “game object” will vary from game to game, and engine to engine. Isn’t an item an object? Is a door an object? Is the player an object? Maybe! For this game though, I’ll use it only for the special category of things which include Bity and the deep fryer.

Actions #

Now that we know what’s going into the game world, what can the player actually DO? What actions can they take in order to progress through the game?

At a minimum we’ll need to be able to:

  • LOOK around the environment
  • GO to a new location
  • TAKE items from the environment
  • USE items on objects in the environment

So we’ll need to implement the commands LOOK, GO, TAKE, and USE.

Putting It All Together #

We can build up these systems step by step:

  1. We’ll start by handling multiple word commands
  2. We’ll construct the environment that the player can move around
  3. Next we’ll add items the user can pick up
  4. And then objects which those items can be used on
  5. Finally we’ll add conditions and requirements that the player must fulfill in order to perform certain actions, turning the game into a puzzle for the player to solve.

Player Commands #

We’ll start off as usual with a basic game loop saved as stranger-wings.lua

function main_loop()
  print("Welcome to Stranger Wings\n")

  game_over = false

  while not game_over do
    io.write("\nEnter command: ")

    player_command = string.lower(io.read())
    if player_command == "quit" then
      game_over = true
    end
  end
end

main_loop()

So far we’ve only handled commands that consist of a single word, like loot, or quit.

But what if we want the player to be able to specify more information, such as which direction they want to go? One way of handling this would be to create a long list of possible combinations of things the player might enter, eg. go north, go east, go south, go west and so on.

As long as there are a reasonably limited number of combinations, this might work, but things can quickly get out of hand if for example you want to support commands like use sandwich on racoon… you aren’t going to have a good time creating a list of every possible combination of items in your game.

A much more efficient and flexible way of dealing with this situation is to split up the words of the command, and handle each one according to it’s purpose. We’re going to make a few assumptions to make this easier on ourselves…

First of all, we’ll assume that each word is separated by space ( ) characters.

And second we’ll assume that the first word is always a verb, such as go, look, take. For each verb we’ll write a separate function that knows how to perform that action. Then each function can look at the remaining words in the command and decide what to do with them. The function for go is going to look at the second word, check which direction the player wants to move (north, east, south, west), and update the game accordingly.

Splitting up strings like this to determine what each part means is a simple form of parsing, which is programming task that comes up all the time. This is what the Command Prompt is doing when you enter commands, what the Lua interpreter is doing when it runs a Lua script, or what your web browser does to read URLs or display HTML as a web page.

Splitting Strings #

In order to operate on each word in the string separately, we’re going to split the string, starting with the original single string that the player enters (go north), and ending up with an array contiaining each word on it’s own {"go", "north"}.

Once we have the words separated into the array, it’s a simple matter to access each individual word, if our array is named command then command[1] will give us the first word "go", and command[2] will give us the second word "north".

Lua doesn’t have a built in function to split strings like this, so we’ll need to write (or steal) our own. There are quite a few possible ways to do this, and it is a surprisingly nuanced problem.

For now, I’ll provide an implementation that should work for our current purposes:

function split_words(str)
  local t = {}
  local function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

The local keyword is new, and will be covered in the next section.

How does split_words actually work?

It isn’t necessary that you understand the details of split_words right now, but if you’re curious…

string.gsub is a Lua provided function intended to substitute characters within a string, using a pattern to express what series of characters to match, and in this case, using a helper function to provide the replacement for each series. (Programming in Lua, Pattern-Matching Functions and Patterns )

Basically, string.gsub will find each word in the string matching the pattern %S+ (which means, “one or more characters in a row that are not spaces”), and call our function helper for each match found. Our function helper takes this opportunity to add each words into an array, t.

Since all we want is the list of words, helper doesn’t return any value to replace each match with, leaving the original string untouched.

When string.gsub finishes, each of the words in the string have been passed to helper which has in turn added them into the array t. We return t as the final result.

Let’s test this out by printing out the size and contents of the array that we get back:

function split_words(str)
  local t = {}
  local function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

function main_loop()
  print("Welcome to Stranger Wings\n")

  local game_over = false
  while not game_over do
    io.write("\nEnter command: ")

    local player_command = string.lower(io.read())
    local split_command = split_words(player_command)

    print("word count:", #split_command)
    for i=1,#split_command do
      print ( "\"" ..split_command[i] .. "\"" )
    end

    local cmd = split_command[1]
    if cmd == "quit" then
      game_over = true
    end
  end
end

main_loop()

Running this and entering a variety of test commands should give results like the following:

Welcome to Stranger Wings


Enter command: hello world
word count:     2
"hello"
"world"

Enter command:
word count:     0

Enter command: go north
word count:     2
"go"
"north"

Enter command: my spoon   is too big!
word count:     5
"my"
"spoon"
"is"
"too"
"big!"

Enter command:   oooOOOO NO!@#$
word count:     2
"ooooooo"
"no!@#$"

Now we’re ready to start handling some more complicated commands.

Local vs Global Scope #

An important topic that I haven’t managed to cover yet is local variables. You may have noticed that split_words contains the keyword local in two places, marking the table t and the function helper as being local.

function split_words(str)
  local t = {}
  local function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

Whether a variable is local or global is referred to as it’s scope. Scope determines where a variable can be accessed from.

A variable that has global scope can be accessed and used anywhere in the program.

Whereas a variable that has local scope can only be accessed in the local block of code, which can be one of a few different cases depending on where they are declared:

  • local variables outside of a function are local to the file that contains them. We’ve only used single file programs so far, but more complex projects will be built of multiple .lua files, and file local variables can only be seen or used inside the same file that declares them.
  • local variables declared inside of a function, can only be accessed within that same function.
  • local variables declared inside of a loop such as for or while, or an if/else block, can only be used within that same block.

Variables you declare have global scope by default, unless you explicitly make them local, so up until now, nearly all of the variables we’ve used have been global.

Why does this matter?

While there are performance and technical implications of scope, the big thing that matters for us at this point is organization and avoiding bugs.

As your programs increase in size, it becomes inevitable that you’ll end up wanting to use the same variable name inside multiple different functions. By declaring these variables as local, you can ensure that each of them is protected from accidental interference from other functions.

Here’s an example of how this can play out…

Imagine a world where our split function bad_split, doesn’t use local, and our main_loop also uses a global variable named t that keeps track of how many times to repeat back the command that the player enters when using the repeat command.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function bad_split(str)
  t = {}
  function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

function main_loop()
  print("Welcome to Bad Global")

  t = 5
  game_over = false

  while not game_over do
    io.write("\nEnter command: ")

    player_command = string.lower(io.read())
    split_command = bad_split(player_command)

    cmd = split_command[1]

    if cmd == "quit" then
      game_over = true
    elseif cmd == "repeat" then
      for i=1,t do
        print ( player_command )
      end
    end

  end
end

main_loop()

Saving this program as bad-global.lua, running it and using the repeat command results in an error:

Welcome to Bad Global

Enter command: repeat me
..\bin\lua53: bad-global.lua:27: 'for' limit must be a number
stack traceback:
        bad-global.lua:27: in function 'main_loop'
        bad-global.lua:35: in main chunk
        [C]: in ?

On line 13 we set t to 5, we want to repeat the player’s command back 5 times.

Then on line 27, we tried to use a for loop, from 1 to t (which should be 5)… but instead we get an error about t not being a number… even though we just set it to 5 earlier.

Since t is a global variable, when we called bad_split on line 20, the bad_split overwrote the global variable t with a table, and our number 5 was lost. By the time we execute line 27, t has been changed and no longer stores the value 5 like we wanted.

One way of fixing this issue would be to simply rename one of the variables so they have different names and don’t overlap. You could try and make sure that you never use the same variable name twice in the same program but, trust me, this isn’t a reliable solution once programs become more than a few dozen lines long, and is hopeless if you begin using code provided by other people, or on multi-person teams.

Instead, we can resolve the bug by make each t variable local

  local t = {}
  local t = 5

And if we try again, the bug has been fixed:

Welcome to Bad Global

Enter command: repeat me
repeat me
repeat me
repeat me
repeat me
repeat me

Now each function has it’s own separate variables named t that won’t interfere with each other.

For the very small programs we’ve made so far, there hasn’t been much risk of running into these kinds of problem, but going forward, I’ll be using local variables as standard best practice.

Some variables that are truely global to the entire game, such as the world map data, or the player’s location or score, will continue to be global, but it still pays to be careful about how these variables will be named and used.

The Environment #

We’re building a game with multiple locations that the player can move between. To accomplish this, we need to set up 3 things:

  • Data that defines the locations of the world, and how they connect to each other
  • A variable to track where the player currently is
  • Commands that allow the player to examine the environment, and move from one location to another.

The World Map #

So far we’ve used simple array-type tables, and stored counts of things in our inventory in earlier games. Now we’re going to step it up a notch and use tables to store more tables!

Similar to how putting files and folders inside other folders works on your hard drive, putting tables inside tables is a convenient way to collect and organize the data that describes our game world.

Our world map will consist of a world_locations table, inside which there will be a table for each location accessable by a simple name such as "alley" or "kitchen". Each location table will contain information about that location, such as the narrative description that we will show to players, and a list of which directions the player can travel.

Let’s start off with a simple world with only two locations. Then we can implement the commands to move between them and look around. Once we have that working, it will be a simple matter to add in all of the rest of the locations.

The main list of locations will start off like a simple table:

world_locations = {}

And then we can add a new table for each location, into the world_locations table. We could do it this way:

world_locations = {}
world_locations.alley = {}
world_locations.kitchen = {}

This can get a bit repetative depending on how much data you’re declaring. Another way of setting up this same structure is to nest the location tables inside the world_locations table when we declare it, instead of adding them after the fact…

world_locations = {
  alley = {},
  kitchen = {},
}

This will have exactly the same result as the previous example, but it’s less repetative and you can also hopefully begin to see the structure of the data a bit better, as alley and kitchen are contained inside the brackets {} of the world_locations table.

When we declare tables this way, we put a list of key = value statements inside the {}, separated by commas. This can also be a source of bugs, as it’s easy to miss a comma between different elements of the table, or to misplace the final }, so you’ll want to be particularly attentive when creating tables this way.

Putting a comma after the final element is optional, but I often do just so that I don’t need to remember to add it later when I add another entry to the list.

Another thing to note here is that Lua doesn’t care much about the spacing or separation onto separate lines. The following code does the same thing:

world_locations = { alley = {}, kitchen = {} }

When you’re only declaring small tables, this is a nice compact way of writing them, but since we plan on adding quite a bit of information to this table, it makes more sense to have things separated out into different lines with indentation to make it more obvious to ourselves where tables start and end.

Now that we have our two location tables, we can add additional information inside each of them. We’ll start off with the narrative description:

world_locations = {
  alley = {
    description = "You stand in the alley beside Stranger Wings chicken restaurant.",
  },
  kitchen = {
    description = "You are in the Stranger Wings restaurant's kitchen.",
  },
}

And our final piece of data for now, inside each location table we’ll add a new table that contains a list of directions the player can move, and which location they’ll end up in.

world_locations = {
  alley = {
    description = "You stand in the alley beside Stranger Wings chicken restaurant.",
    directions = { 
      east = "kitchen",
    },
  },
  kitchen = {
    description = "You are in the Stranger Wings' restaurant kitchen.",
    directions = { 
      west = "alley",
    },
  },
}

If you’re in the alley and move east you’ll end up in the kitchen, conversely if you’re in the kitchen and move west you’ll end up in the alley.

This is all of the data we’ll need to implement our first few commands, look and go.

We’ll be removing the direct connection between the alley and the kitchen later, to direct the player down into the basement. This is just to make testing easier.

Player Location #

Before we implement the commands, let’s make sure we know which location we’re presently in. The game will start with the player in the alley, so we’ll just create a new variable to represent this:

player_location = "alley"

Easy!

Now that we have the player’s location stored in a variable, we can access the correct table for whatever their location happens to be with world_locations[player_location]

Just make sure that the value of player_location matches one of the keys in world_locations. If it doesn’t match any of the keys in the table, we’ll get a nil result, and probably crash as soon as we try to use it.

Why does "alley" have quotes here and not when we created alley table inside the world_locations table?

As mentioned back in Infinite Treasure II, Lua has some convenience syntax to make working with tables easier on the eyes. When declaring table keys as we did in the world_locations table, we’re allowed to leave out the [""] and Lua will treat the key alley as a string.

We also have two options when accessing table keys, world_locations.alley and world_locations["alley"] will both access the same location table using the given key. But we can only use the simplified version if there are no spaces or other punctuation… world_locations["in a dark pit"] would work because the key is a single string, but world_locations.in a dark pit wouldn’t work, as Lua doesn’t know where the key ends and the rest of the code resumes.

In other situations there is no simplified shortcut, we need to be explicit that the thing we’re typing is a string and use the quoted version, "alley".

Looking Around #

Now to add our first command look, which will display the description of the player’s current location.

The function to handle the look command is simple, we just access the location table based on the player’s current location, and then print out the description value:

function command_look()
  local location = world_locations[player_location]
  print(location.description)
end

Then all we need to do is call command_look when the player enters the look command. We can take out the debugging information we printed out earlier as well (or leave it in, your choice!)

function main_loop()
  print("Welcome to Stranger Wings\n")

  local game_over = false

  while not game_over do
    io.write("\nEnter command: ")

    local player_command = string.lower(io.read())
    local split_command = split_words(player_command)

    local cmd = split_command[1]
    if cmd == "quit" then
      game_over = true
    elseif cmd == "look" then
      command_look()
    end
  end
end

Putting everything together and running the game, we can now use the look command to see where we are.

Welcome to Stranger Wings


Enter command: look
You stand in the alley beside Stranger Wings chicken restaurant.

Enter command: look around
You stand in the alley beside Stranger Wings chicken restaurant.

Enter command: go north

Enter command: look over there!
You stand in the alley beside Stranger Wings chicken restaurant.

Enter command: hey look

As long as the first word in the command is look, our look function will run. This can be a benefit in cases where the player types something comfortable like look around, but it also means they can type any old nonsense after the look command and it will be ignored by the game.

We’ll revisit this situation later as we’ll want to let the player specify an object they want to look at.

Code So Far
world_locations = {
  alley = {
    description = "You stand in the alley beside Stranger Wings chicken restaurant.",
    directions = { 
      east = "kitchen",
    },
  },
  kitchen = {
    description = "You are in the Stranger Wings' restaurant kitchen.",
    directions = { 
      west = "alley",
    },
  },
}

player_location = "alley"

function split_words(str)
  local t = {}
  local function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

function command_look()
  local location = world_locations[player_location]
  print(location.description)
end

function main_loop()
  print("Welcome to Stranger Wings\n")

  local game_over = false
  while not game_over do
    io.write("\nEnter command: ")

    local player_command = string.lower(io.read())
    local split_command = split_words(player_command)

    local cmd = split_command[1]
    if cmd == "quit" then
      game_over = true
    elseif cmd == "look" then
      command_look()
    end
  end
end

main_loop()

Moving Between Locations #

Moving is a bit more involved than just looking around, as we need to check where the player is asking to go, check that their request is a valid option, and update the game state to reflect the player’s new location.

First we’ll set up a function to handle the go command:

function command_go(direction)
  local location_info = world_locations[player_location]
  local destination = location_info.directions[direction]

  if destination ~= nil then
    player_location = destination
    print(string.format("You go %s", direction))
  else
    print(string.format("You can not go %s", direction))
  end
end

Similar to looking around, we first use the global player_location variable to look up our location table in the global world_locations table, and store it in the local variable location_info.

Note that command_go takes a parameter, direction which will contain the second word that the player entered. If the player enters go north then the direction parameter will be "north".

We use the direction parameter as a key into the location_info.directions table, and store the value in the local destination variable.

If the player is currently in the alley, then the location_info variable will refer to the alley table, and the available directions will be east, whereas if the player is currently in the kitchen, the available direction will be west. The value that we get back tells us the new location the player will be in if they travel in that direction. Starting in the alley and going east, for example, will change the player’s location to kitchen.

It’s possible the player enters a direction that isn’t contained in the table (eg. they type go away), in which case we’ll get a nil value since "away" isn’t one of the entries in the directions table, so we’ll need to check this first before using it.

If destination is not nil (destination ~= nil) then we update the global player_location value to this new location, and report to the player that they have successfully moved in the direction they requested.

But if destination is nil, we report back to the player that they can’t go in whatever direction they specified.

Now we just have to hook up our command_go function in main_loop.

For the go command we also need to pass the second word in the split_command array as the parameter to command_go, so that we can tell where it is exactly that we’re trying to go.

function main_loop()
  print("Welcome to Stranger Wings\n")

  local game_over = false
  while not game_over do
    io.write("\nEnter command: ")

    local player_command = string.lower(io.read())
    local split_command = split_words(player_command)

    local cmd = split_command[1]
    if cmd == "quit" then
      game_over = true
    elseif cmd == "look" then
      command_look()
    elseif cmd == "go" then
      local where = split_command[2]
      command_go(where)
    end
  end
end

With this new command in place, we’re now able to move around the world to explore different locations:

Welcome to Stranger Wings


Enter command: look
You stand in the alley beside Stranger Wings chicken restaurant.

Enter command: go west
You can not go west

Enter command: go east
You go east

Enter command: look
You are in the Stranger Wings' restaurant kitchen.

Enter command: go east
You can not go east

Enter command: go west
You go west

Enter command: look
You stand in the alley beside Stranger Wings chicken restaurant.

Now that our go and look commands are working, it becomes a trivial matter to fill out the rest of the world map. We can just add new entries to the world_locations table, and make sure that their directions tables correctly list the possible directions they can travel, and where they will end up:

world_locations = {
  alley = {
    description = "You stand in the alley beside Stranger Wings chicken restaurant.",
    directions = { 
      east = "kitchen",
      down = "west_basement",
    },
  },
  kitchen = {
    description = "You are in the Stranger Wings' restaurant kitchen.",
    directions = { 
      north = "dining",
      east = "storage",
      south = "freezer",
      west = "alley",
      down = "east_basement",
    },
  },
  west_basement = {
    description = "You are in the Stranger Wings' basement.",
    directions = { 
      east = "east_basement",
    },
  },
  east_basement = {
    description = "You are in the Stranger Wings' basement.",
    directions = { 
      west = "west_basement",
      up = "kitchen",
    },
  },
  dining = {
    description = "You stand among the tables and chairs of the Stranger Wings' dining area.",
    directions = { 
      south = "kitchen",
    },
  },
  storage = {
    description = "You are in a storage room surrounded by shelves.",
    directions = { 
      west = "kitchen",
    },
  },
  freezer = {
    description = "You stand in a walk-in freezer, it is very cold. " .. 
      "One might even say freezing.",
    directions = { 
      north = "kitchen",
    },
  },
}
Code So Far
world_locations = {
  alley = {
    description = "You stand in the alley beside Stranger Wings chicken restaurant.",
    directions = { 
      east = "kitchen",
      down = "west_basement",
    },
  },
  kitchen = {
    description = "You are in the Stranger Wings' restaurant kitchen.",
    directions = { 
      north = "dining",
      east = "storage",
      south = "freezer",
      west = "alley",
      down = "east_basement",
    },
  },
  west_basement = {
    description = "You are in the Stranger Wings' basement.",
    directions = { 
      east = "east_basement",
    },
  },
  east_basement = {
    description = "You are in the Stranger Wings' basement.",
    directions = { 
      west = "west_basement",
      up = "kitchen",
    },
  },
  dining = {
    description = "You stand among the tables and chairs of the Stranger Wings' dining area.",
    directions = { 
      south = "kitchen",
    },
  },
  storage = {
    description = "You are in a storage room surrounded by shelves.",
    directions = { 
      west = "kitchen",
    },
  },
  freezer = {
    description = "You stand in a walk-in freezer, it is very cold. " .. 
      "One might even say freezing.",
    directions = { 
      north = "kitchen",
    },
  },
}

player_location = "alley"

function split_words(str)
  local t = {}
  local function helper(word)
    table.insert(t, word)
  end
  string.gsub(str, "%S+", helper)
  return t
end

function command_look()
  local location = world_locations[player_location]
  print(location.description)
end

function command_go(direction)
  local location_info = world_locations[player_location]
  local destination = location_info.directions[direction]

  if destination ~= nil then
    player_location = destination
    print(string.format("You go %s", direction))
  else
    print(string.format("You can not go %s", direction))
  end
end

function main_loop()
  print("Welcome to Stranger Wings\n")

  local game_over = false
  while not game_over do
    io.write("\nEnter command: ")

    local player_command = string.lower(io.read())
    local split_command = split_words(player_command)

    local cmd = split_command[1]
    if cmd == "quit" then
      game_over = true
    elseif cmd == "look" then
      command_look()
    elseif cmd == "go" then
      local where = split_command[2]
      command_go(where)
    end
  end
end

main_loop()

On To Part II #

This section is getting quite long, so we’ll finish the game in Part II, where we will implement items, objects, and the rules that control where the player can go based on what items they’ve collected.

Next - Stranger Wings II