Learn Programming by Making a Text Adventure Game in Bash

My programming journey started when I was a young Junior System Administrator, and I needed to learn how to use the Windows Command Prompt so that I could write (and read) scripts that were used to manager our Windows environment. After working through the first half of a (very dry) book on the subject, I realized that while I had read quite a bit, I hadn’t really learned anything.

Sure, I needed to understand how to use tools such as cacls and wecutil eventually, but if I was going to be scripting, I needed to understand how to use things like variables, conditionals, and loop statements.

And so for that, I decided to create a simple text adventure game. It became overly-complex, and is very messy, but you can see the actual code for yourself on my Github.

Text adventure games, reminiscent of the choose-your-own-adventure books, are interactive stories where players make decisions that influence the outcome. By creating text adventure games, beginners can dive into the world of programming in a way that’s not only accessible but also fun!

Why Bash?

This mini-essay is going to use Bash as the language because it’s fairly universal (being pre-installed on MacOS and Linux, and fairly painless to get running on Windows).

Also, unlike more complex programming languages, Bash requires minimal setup (no hefty .Net installs, or dedicated IDE’s), allowing you to focus on learning the core concepts of programming through immediate, hands-on experience.

It’s the perfect playground for beginners to experiment, make mistakes, and learn, turning the daunting task of coding into a series of manageable, enjoyable steps.

Benefits of Learning Through Gaming

Learning programming through gaming, especially by creating text adventure games, offers a unique blend of education and entertainment.

This method brings abstract programming concepts to life. Variables hold choices and outcomes, user input shapes the story line, and printing to the screen narrates your adventure.

It encourages problem-solving, critical thinking, and creativity, all within the framework of a game. Moreover, the instant feedback from running your game enhances understanding and retention.

As you navigate through creating and playing your game, you’re not just learning to code; you’re embarking on a journey that makes each lesson memorable and engaging.

Getting Started with Bash for Text Adventure Games

Diving into the world of text adventure games with Bash is a straightforward journey. First, ensure you have access to a terminal: Linux and macOS users already have this, while Windows users might need to install a tool like Git Bash.

Next, open your terminal and get ready to type your commands directly into this interface (for testing and experimentation).

For writing the game code, no fancy software is required — just your computer, a text editor (like Notepad or TextEdit), and your imagination. For the following tutorial, I saved my script as test.sh.

Our Game

This will be a very simple game…

Getting Started

Begin your script with #!/bin/bash to tell your computer to use Bash to execute the file. Then, introduce variables and user input:

#!/bin/bash

# 'echo' prints the statemet in quotes onto the screen for the player. 
# By including "[left/right]" we're letting the player know what options are available
echo "You're standing at a crossroad. Where do you want to go? [left/right]"

# 'read' indicates that we are capturing user input from the terminal.
# 'direction' is the name of the variable, and will hold whatever value the player puts in.
read direction

Reacting to User Input

Now let’s use some conditional statements to create game logic based on the player’s choices. To the bottom of the script, add:

# Now we're testing the value of the 'direction' variable to make sure that it
# matches what we're expecting

# We use 'if' to start the statement
if [[ $direction == "left" ]]; then 
  echo "You go left"
# 'elif' stands for 'else, if'
elif [[ $direction == "right" ]]; then 
  echo "You go right"
# If none of the previous statements match (i.e. the player did not enter 'left' or 'right'
# then we let the player know that their choice was invalid
else 
  echo $direction "is an invalid choice. Game over."
fi

You can now run and test this code:

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
left
You go left

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
north
north is an invalid choice. Game over.

Organizing the Code

Now, we can use nested if statements to define what happens when the player goes right or left. But a better way would be to separate that code out into their own functions.

Functions are snippets of functionality that can be called from elsewhere in the script.

So we’re going to create these two functions (NOTE: functions must be defined before the rest of the code. So these will go above the rest of the code, just below the #!/bin/bash statement):

#!/bin/bash

function go_left() {
  echo "You see a heavy chest on the ground in front of you. What do you do? [open/leave]"
}

function go_right() {
  echo "You see a sleeping dragon. What do you do? [fight/leave]"
  read action
}

And we’ll re-write our conditional statement so that these functions are called:

# We use 'if' to start the statement
if [[ $direction == "left" ]]; then 
  echo "You go left" 
  go_left # Call the go_left function
# 'elif' stands for 'else, if'
elif [[ $direction == "right" ]]; then 
  echo "You go right"
  go_right # call the go_right function
# If none of the previous statements match (i.e. the player did not enter 'left' or 'right'
# then we let the player know that their choice was invalid
else 
  echo $direction "is an invalid choice. Game over."
fi

Now when we run the game, we should see…

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
left
You go left
You see a heavy chest on the ground in front of you. What do you do? [open/leave]

Giving the Player More Options

Let’s give the player the obvious outcomes using the tools we’ve already learned:

function go_left() {
  echo "You see a heavy chest on the ground in front of you. What do you do? [open/leave]"
  read action
  if [[ $action == "open" ]]; then
    echo "You open the chest, and it's full of glittering gold coins and priceless gems."
	echo "YOU WON!"
	echo "Game Over." 
  fi
}

		
function go_right() {
  echo "You see a sleeping dragon. What do you do? [fight/leave]"
  read action
  if [[ $action == "fight" ]]; then
    echo "Oh no! Why would you do that?"
	echo "The dragon swallowed you in a single gulp."
	echo "Game Over." 
  fi
}

And when we test…

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
right
You go right
You see a sleeping dragon. What do you do? [fight/leave]
fight
Oh no! Why would you do that?
The dragon swallowed you in a single gulp.
Game Over.

That all works as expected, but what if the player chooses to leave? In that case, we can put them back at the crossroads. So what we’ll do, is we’ll wrap that first bit of code in a new function called start. And if the player chooses “leave” when they get to the chest or the dragon, we’ll call start to put them back at the crossroads:

# By wrapping this section of the game in a function, we can call it from elsewhere
# to restart the adventure.
function start() {
	# 'echo' prints the statemet in quotes onto the screen for the player. 
	# By including "[left/right]" we're letting the player know what options are available
	echo "You're standing at a crossroad. Where do you want to go? [left/right]"

	# 'read' indicates that we are capturing user input from the terminal.
	# 'direction' is the name of the variable, and will hold whatever value the player puts in.
	read direction

	# Now we're testing the value of the 'direction' variable to make sure that it
	# matches what we're expecting

	# We use 'if' to start the statement
	if [[ $direction == "left" ]]; then 
	  echo "You go left"
	  go_left
	# 'elif' stands for 'else, if'
	elif [[ $direction == "right" ]]; then 
	  echo "You go right"
	  go_right
	# If none of the previous statements match (i.e. the player did not enter 'left' or 'right'
	# then we let the player know that their choice was invalid
	else 
	  echo $direction "is an invalid choice. Game over."
	# the 'fi' keyword ends our conditional statment
	fi
}

Now there’s two things we need to do:

  1. We need to update our go_left and go_right functions so that the player can “leave” back to the starting point
  2. Now that we’ve placed all of our code in functions, we actually need to add a function call at the bottom of the script

Updating the go_left and go_right functions

function go_left() {
  echo "You see a heavy chest on the ground in front of you. What do you do? [open/leave]"
  read action
  if [[ $action == "open" ]]; then
    echo "You open the chest, and it's full of glittering gold coins and priceless gems."
	echo "YOU WON!"
	echo "Game Over."  
  elif [[ $action == "leave" ]]; then
	echo "You go back..."
	start
  else
    echo $action "is an invalid choice. Game over."
  fi
}

		
function go_right() {
  echo "You see a sleeping dragon. What do you do? [fight/leave]"
  read action
  if [[ $action == "fight" ]]; then
    echo "Oh no! Why would you do that?"
	echo "The dragon swallowed you in a single gulp."
	echo "Game Over." 
  elif [[ $action == "leave" ]]; then
	echo "You live to fight another day!"
	start
  else
    echo $action "is an invalid choice. Game over."
  fi
}

Adding the start Function Call to Kick the Game Off

Add this simple line to the very bottom of your script:

start

Now when we test, we can do something like this:

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
left
You go left
You see a heavy chest on the ground in front of you. What do you do? [open/leave]
leave
You go back...
You're standing at a crossroad. Where do you want to go? [left/right]
right
You go right
You see a sleeping dragon. What do you do? [fight/leave]
leave
You live to fight another day!
You're standing at a crossroad. Where do you want to go? [left/right]
left
You go left
You see a heavy chest on the ground in front of you. What do you do? [open/leave]
open
You open the chest, and it's full of glittering gold coins and priceless gems.
YOU WON!
Game Over.

Play Again?

Now that’s a complete, albeit very simple, game. There is a win-state and a lose-state, and the player can make choices that effect the outcome.

But before we finish, let’s give the player a way to easily play again.

We’ll create a simple function that we’ll call after the game ends. This function will ask the player if they want to play again. If they do, we’ll restart the game. If they do not, then we’ll exit.

function play_again() {
  echo "Would you like to play again? [y/n]"
  read choice 
  if [[ $choice == "y" ]]; then
    start
  fi
}

As you can see, this function is pretty simple, but it’s effective! If we test it, we can see how it works…

$ bash test.sh
You're standing at a crossroad. Where do you want to go? [left/right]
right
You go right
You see a sleeping dragon. What do you do? [fight/leave]
fight
Oh no! Why would you do that?
The dragon swallowed you in a single gulp.
Game Over.
Would you like to play again? [y/n]
y
You're standing at a crossroad. Where do you want to go? [left/right]

The finished code, with my comments removed, should look like this:

#!/bin/bash

function go_left() {
  echo "You see a heavy chest on the ground in front of you. What do you do? [open/leave]"
  read action
  if [[ $action == "open" ]]; then
    echo "You open the chest, and it's full of glittering gold coins and priceless gems."
	echo "YOU WON!"
	echo "Game Over."
    play_again	
  elif [[ $action == "leave" ]]; then
	echo "You go back..."
	start
  else
    echo $action "is an invalid choice. Game over."
  fi
}
		
function go_right() {
  echo "You see a sleeping dragon. What do you do? [fight/leave]"
  read action
  if [[ $action == "fight" ]]; then
    echo "Oh no! Why would you do that?"
	echo "The dragon swallowed you in a single gulp."
	echo "Game Over." 
	play_again
  elif [[ $action == "leave" ]]; then
	echo "You live to fight another day!"
	start
  else
    echo $action "is an invalid choice. Game over."
  fi
}

function start() {
	echo "You're standing at a crossroad. Where do you want to go? [left/right]"
	read direction
	if [[ $direction == "left" ]]; then 
	  echo "You go left"
	  go_left
	elif [[ $direction == "right" ]]; then 
	  echo "You go right"
	  go_right
	else 
	  echo $direction "is an invalid choice. Game over."
	fi
}

function play_again() {
  echo "Would you like to play again? [y/n]"
  read choice
  
  if [[ $choice == "y" ]]; then
    start
  fi
}

start

Continuing Onward

Now, we haven’t gone too deep into scripting with Bash. But even so, you have all the tools you need to expand your game into something that might be a little bit more engaging.

For example, try adding the following:

  • A welcome message to greet the when the user when they play the game for the first time
  • A better way of handling bad input (right now, we just kick them out of the game)
  • A lock for the chest — maybe the key is hidden somewhere in the dragon’s area!
  • Additional areas that the player can travel to
  • A basic combat system for dealing with that pesky dragon

Each of these game elements can be added with the concepts that we covered in this mini-essay. The combat system would take a bit of tinkering with, but by using variables to hold numeric values such as hero_health, hero_damage, dragon health, and dragon_damage, you can use conditional expressions to determine when one or the other is dead, and handle the game flow accordingly.

Additionally, these concepts – program flow, handling different conditions, using variables to hold different values – are directly applicable to virtually every other type of programming – from building enterprise-level desktop applications, to data analysis, to web design, and beyond.

One of the great things about programming is the boundless options for creativity and problem-solving that it opens up to you, and if you’re new, I hope this little essay has sparked that curiosity in you, just like a similar project did for me all those years ago.