Writing a Text Adventure Game in Go - Part 1

SpaceShipImperium Part 2: Combat >>>

Intro (Location and Movement)

So I have always liked coding games. I started many years ago on the Amiga computer a friend and I coded a small turn based space conquest game that we called Cosmic Conquest and it was a blast. Since that time I have not had serious time to put into games but when I picked up Go I figured it would be a nice way to learn the language and do something fun. Combine that with some internet research where I found plenty of python text adventure game tutorials so I will present here a 4 or 5 part series on developing not only one text adventure game but more of a system that you can re-use to create multiple text adventures with different scenarios.



Part 1 - Where but why, the world will end in 10 minutes, Go Go Go


Location, Location, Location


The most common concept in any adventure game is the Location and how to get from one place to another so this logically follows that we need a struct in go to keep track of locations and help with where are you? and where can you go? questions. We will also add some basic events that can happen

Locations, Transitions and Events will be declared in a struct and kept in maps for easier retrieval and less confusion. We could use pointer to other locations and events but then sometimes the code gets more confusing so I chose to avoid pointers as much as possible. The use of maps lets me create an instance of a room and access it from anywhere using the “key” from the map. Checkout the definitions below

type Location struct {
	Description string
	Transitions []string
	Events      []string
}

var locationMap = map[string]*Location{
	"Bridge":      {"You are on the bridge of a spaceship sitting in the Captain's chair.", []string{"Ready Room", "Turbo Lift"}, []string{"alienAttack"}},
	"Ready Room":  {"The Captain's ready room.", []string{"Bridge"}, []string{}},
	"Turbo Lift":  {"A Turbo Lift that takes you anywhere in the ship.", []string{"Bridge", "Lounge", "Engineering"}, []string{"android"}},
	"Engineering": {"You are in engineering where you see the star drive", []string{"Turbo Lift"}, []string{"alienAttack"}},
	"Lounge":      {"You are in the lounge, you feel very relaxed", []string{"Turbo Lift"}, []string{"relaxing"}},
}

If you look at the declarations above you can see that the locationMap contains a key which is a short name for the location and then in the struct itself you have the Description, a list of keys for locations you can go from here and the list of “key” of events that can happen here.

Events (Boom or the world will end in 10 minutes)


Let’s add events to this now to complete the picture.

type Event struct {
	Type        string
	Chance      int
	Description string
	Health      int
	Evt         string
}

var evts = map[string]*Event{
	"alienAttack":     {Chance: 20, Description: "An alien beams in front of you and shoots you with a ray gun.", Health: -50, Evt: "doctorTreatment"},
	"doctorTreatment": {Chance: 10, Description: "The doctor rushes in and inject you with a health boost.", Health: +30, Evt: ""},
	"android":         {Chance: 50, Description: "Data is in the turbo lift and says hi to you", Health: 0, Evt: ""},
	"relaxing":        {Chance: 100, Description: "In the lounge you are so relaxed that your health improves.", Health: +10, Evt: ""},
}

Again we follow the same pattern to declare the events and the list of events that can happen in this game.
An Event is simply the percent chance that the event can occur, the description, it’s effect on our hero’s health and finally if an event can be triggered by this event it is declared here (In this way you can chain events to a logical conclusion).

If we look at the events that we have created there is a percentage chance the event will happen and also there is result to the hero’s health so to process this when the events “might” happen in the game we need a struct function to process all that so here it is:

func (e *Event) ProcessEvent() int {
	s1 := rand.NewSource(time.Now().UnixNano())
	r1 := rand.New(s1)
	if e.Chance >= r1.Intn(100) {
            hp := e.Health
            if e.Type == "Combat" {
                fmt.Println("Combat Event")
            }
            fmt.Printf("\t%s\n", e.Description)
            if e.Evt != "" {
                hp = hp + evts[e.Evt].ProcessEvent()
            }
            return hp
        }
        return 0
}

The above code decides if the event happened, if it has a follow on event that needs to run and then return the effect on the hero’s health.


Game Loop Go


Now we are ready to ut it all together in the “game loop”. First, what is a game loop? Game Loops is a programming loop that never ends and is characterized by printing valuable information to the player, waiting for his/her imput and running the results of the action, the loop then restart at the beginning. So how do we do that in our basic system here…

type Game struct {
	Welcome             string
	Health              int
	CurrentLocation     string
}

func (g *Game) Play() {
    CurrentLocation = locationMap["Bridge"]
	fmt.Println(g.Welcome)
    for {
		fmt.Println(CurrentLocation.Description)        //Where are you?
		g.ProcessEvents(CurrentLocation.Events)         //Did anything happen here?
		if g.Health <= 0 {                               //Are you dead?
			fmt.Println("You are dead, game over!!!")
			return
		}
		fmt.Printf("Health: %d\n", g.Health)            //Print health information
		fmt.Println("You can go to these places:")      //Where can you go from here?
		for index, loc := range CurrentLocation.Transitions {
			fmt.Printf("\t%d - %s\n", index+1, loc)
		}
		i := 0
		for i < 1 || i > len(CurrentLocation.Transitions) {     //What would you like to do?
			fmt.Printf("%s%d%s\n", "Where do you want to go (0 - to quit), [1...", len(CurrentLocation.Transitions), "]: ")
			fmt.Scan(&i)
		}
		newLoc := i - 1                                            
		CurrentLocation = locationMap[CurrentLocation.Transitions[newLoc]] //Go to new location based on input

	}
}

func (g *Game) ProcessEvents(events []string) {
	for _, evtName := range events {
		g.Health += evts[evtName].ProcessEvent()
	}
}

func main() {
	g := &Game{Health: 100, Welcome: "Welcome to the Starship Enterprise\n\n", shield: "Minor Shield", weapon: "Minor Raygun"}
	g.Play()
}

In the above code we added a Game Struct to keep track of a welcome message and our hero’s health and Current Location. We also attached 2 methods to that called Play and ProcessEvents. Play is the actual game loop while process events handles loading events in the current location and running them.


Final words


Check out the complete working code in this Gist.

I hope you enjoyed this first part of writing a text adventure in Go and I look forward to writing and having you read and comment on part 2 next week, when we will add real opponents and true combat to the game.

Part 2: Combat >>>

*** Sign up for my email list to keep in touch with all the interesting new happenings in the go community with the GolangNewsFeed

comments powered by Disqus