Video Coming Soon...

Created by Zed A. Shaw Updated 2025-10-07 14:32:54

30: Rogue Part 3: Finishing Touches

WARNING This exercise is in DRAFT status, so there may be errors. If you find any, please email me at help@learncodethehardway.com so I can fix them.

The final exercise in this module should be the simplest one. You are only going to add basic combat to the game. Combat in games like this is generally based on "player moves into square where monster is." You'll get a basic form of this working, then I'll give you some extra things you can do to push this further so you can make your own game.

data.go Changes

Let's turn off SHOW_PATHS now that pathing is working:

View Source file curse-you-go-rogue/04_combat/data.go.diff Only

--- 03_pathing_enemies/data.go  2025-10-06 02:44:43.658927000 -0400
+++ 04_combat/data.go   2025-10-05 23:24:48.595383100 -0400
@@ -11,5 +11,5 @@
   RENDER = true
   SHOW_RENDER = false
-  SHOW_PATHS = true
+  SHOW_PATHS = false
   HEARING_DISTANCE = 6
 )

movement.go Changes

To make combat work you only need to detect that the player is trying to move into an Occupied() cell and then Attack():

View Source file curse-you-go-rogue/04_combat/movement.go.diff Only

--- 03_pathing_enemies/movement.go  2025-10-06 12:00:47.091211600 -0400
+++ 04_combat/movement.go   2025-10-06 12:00:49.969794700 -0400
@@ -7,5 +7,7 @@
   }
 
-  if !game.Occupied(target) {
+  if game.Occupied(target) {
+    game.Attack(target)
+  } else {
     game.Player.Pos = target
   }

This is only a first step, and the combat can get very complex from here if you want, but this is mostly what you do.

New main.go

It's easier to give you a new main.go then to use a .diff. The algorithm seems to really hate the changes to this file.

View Source file curse-you-go-rogue/04_combat/main.go Only

package main

func main() {
  DebugInit()

  game := NewGame(17, 11)
  game.InitScreen()

  for {
    game.NewMap()
    dead_ends := game.NewMaze()
    game.PlaceEnemies(dead_ends)
    game.Render()

    for game.HandleEvents() && game.Player.HP > 0 {
      game.EnemyDeath()
      game.CalculatePaths()
      game.EnemyPathing()
      game.Render()
    }

    if game.Player.HP <= 0 {
      game.Restart()
    } else {
      game.Exit()
    }
  }
}

New combat.go

There's now a new combat.go file that has the combat system:

View Source file curse-you-go-rogue/04_combat/combat.go Only

package main

import (
  "fmt"
  "math/rand"
)

func (game *Game) EnemyDeath() {
  is_dead := make([]Position, 0, len(game.Enemies))

  for pos, enemy := range game.Enemies {
    if enemy.HP < 0 {
      is_dead = append(is_dead, pos)
    }
  }

  for _, pos := range is_dead {
    delete(game.Enemies, pos)
  }
}

func (game *Game) ApplyDamage(attacker *Enemy, defender *Enemy) {
  damage := rand.Int() % attacker.Damage
  defender.HP -= damage
  if damage == 0 {
    game.SetStatus("MISSED!")
  } else if defender.HP > 0 {
    game.SetStatus(fmt.Sprintf("HIT %d damage", damage))
  } else {
    game.SetStatus("DEAD!")
  }
}

func (game *Game) Attack(target Position) {
  enemy, hit_enemy := game.Enemies[target]

  if hit_enemy {
    game.ApplyDamage(&game.Player, enemy)
    game.ApplyDamage(enemy, &game.Player)
  }
}

The combat system consists of three functions:

Going Farther

You have now reached the end of Module 2, but you aren't done yet. You should spend some time pushing this game as far as you can. Here's some suggestions you can implement:

Try to get those to work, and then see what you can do from there. Can you make the code better than mine?

Previous Lesson Back to Module

Register for Learn Go the Hard Way

Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.