DEV Community

Cover image for A Stab At Roguish Go Part 02
Steve Layton
Steve Layton

Posted on • Updated on

A Stab At Roguish Go Part 02

ATLG Rogue

Continuing Adventures

After we finished the last post I continued to mess with the code a little bit more. So, we're going to continue our short trip down the rogue-like way. This time we're hacking in a static map and some basic "collision detection". And on that note, let's jump right in and get started.

in action


Code Walkthrough

The first bit of our code is more or less the same. We altered our player struct to add the rune which stands for our player character. We'll have to cast to a string() when we emit the string to the screen.

package main

import (
  "fmt"
  "math/rand"
  "os"
  "time"

  "github.com/gdamore/tcell"
  "github.com/mattn/go-runewidth"
)

type player struct {
  r      rune
  x      int
  y      int
}

func emitStr(s tcell.Screen, x, y int, style tcell.Style, str string) {
  for _, c := range str {
    var comb []rune
    w := runewidth.RuneWidth(c)
    if w == 0 {
      comb = []rune{c}
      c = ' '
      w = 1
    }
    s.SetContent(x, y, c, comb, style)
    x += w
  }
}

func main() {

  debug := false

  player := player{
    r: '@',
    x: 3,
    y: 3,
  }

  var msg string
Enter fullscreen mode Exit fullscreen mode

Get Mapping

Since we're using a static "map" we're just going to use a multidimensional array. For walls we're using # and the floor is ..

  mapp := [9][9]rune{
    {'#', '#', '#', '#', '#', '#', '#', '#', '#'},
    {'#', '.', '.', '.', '.', '.', '.', '.', '#'},
    {'#', '.', '.', '.', '.', '.', '.', '.', '#'},
    {'#', '.', '.', '.', '#', '.', '.', '.', '#'},
    {'#', '.', '.', '#', '#', '#', '.', '.', '#'},
    {'#', '.', '.', '.', '#', '.', '.', '.', '#'},
    {'#', '.', '.', '.', '.', '.', '.', '.', '#'},
    {'#', '.', '.', '.', '.', '.', '.', '.', '#'},
    {'#', '#', '#', '#', '#', '#', '#', '#', '#'},
  }
Enter fullscreen mode Exit fullscreen mode

Colors

We have added a couple more colors which we'll use for the walls and the ground when we draw. Nothing too exciting, just a couple of browns and grey.

  tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
  s, e := tcell.NewScreen()
  if e != nil {
    fmt.Fprintf(os.Stderr, "%v\n", e)
    os.Exit(1)
  }
  if e = s.Init(); e != nil {
    fmt.Fprintf(os.Stderr, "%v\n", e)
    os.Exit(1)
  }

  white := tcell.StyleDefault.
    Foreground(tcell.ColorWhite).
    Background(tcell.ColorBlack)
  grey := tcell.StyleDefault.
    Foreground(tcell.ColorGrey).
    Background(tcell.ColorBlack)
  burlyWood := tcell.StyleDefault.
    Foreground(tcell.ColorBurlyWood).
    Background(tcell.ColorBlack)
  brown := tcell.StyleDefault.
    Foreground(tcell.ColorBrown).
    Background(tcell.ColorBlack)

  s.SetStyle(tcell.StyleDefault.
    Foreground(tcell.ColorWhite).
    Background(tcell.ColorBlack))
  s.Clear()
Enter fullscreen mode Exit fullscreen mode

Controls and Collision

Our controls section is still a work in progress. Due to the way that tcell.EventKey works we have to check tcell.KeyRune and ev.Rune(). This way we can see if we have received a normal letter key. We want to be able to use Vim keys to move around so for now, we have a bit of duplication in our code. Before that, though let's take a closer look at how we are achieving our basic collision detection.

r, _, _, _ := s.GetContent(player.x-1, player.y)
if r == '#' {

} else if player.x-1 >= 0 {
  player.x--
}

We are using GetContent() to get the rune that is currently in the location that the player wants to move to. If that is a wall rune the nothing happens. We could extend this in the future to allow for combat, opening doors, and other interactions.

For now, we're going to deal with the duplication. I'll take a closer look at the tcell code and see if there is another alternative.

  quit := make(chan struct{})
  go func() {
    for {
      x, y := s.Size()
      ev := s.PollEvent()
      switch ev := ev.(type) {
      case *tcell.EventKey:

        switch ev.Key() {
        case tcell.KeyRune:
          switch ev.Rune() {
          case 'h':
            r, _, _, _ := s.GetContent(player.x-1, player.y)
            if r == '#' {

            } else if player.x-1 >= 0 {
              player.x--
            }
          case 'l':
            r, _, _, _ := s.GetContent(player.x+1, player.y)
            if r == '#' {

            } else if player.x+1 < x {
              player.x++
            }
          case 'k':
            r, _, _, _ := s.GetContent(player.x, player.y-1)
            if r == '#' {

            } else if player.y-1 >= 0 {
              player.y--
            }
          case 'j':
            r, _, _, _ := s.GetContent(player.x, player.y+1)
            if r == '#' {

            } else if player.y+1 < y {
              player.y++
            }
          }
        case tcell.KeyEscape, tcell.KeyEnter:
          close(quit)
          return
        case tcell.KeyRight:
          r, _, _, _ := s.GetContent(player.x+1, player.y)
          if r == '#' {

          } else if player.x+1 < x {
            player.x++
          }
        case tcell.KeyLeft:
          r, _, _, _ := s.GetContent(player.x-1, player.y)
          if r == '#' {

          } else if player.x-1 >= 0 {
            player.x--
          }
        case tcell.KeyUp:
          r, _, _, _ := s.GetContent(player.x, player.y-1)
          if r == '#' {

          } else if player.y-1 >= 0 {
            player.y--
          }
        case tcell.KeyDown:
          r, _, _, _ := s.GetContent(player.x, player.y+1)
          if r == '#' {

          } else if player.y+1 < y {
            player.y++
          }
        case tcell.KeyCtrlD:
          debug = !debug
        case tcell.KeyCtrlL:
          s.Sync()
        }
      case *tcell.EventResize:
        s.Sync()
      }
    }
  }()
Enter fullscreen mode Exit fullscreen mode

The first part of our labeled loop hasn't changed.

loop:
  for {
    select {
    case <-quit:
      break loop
    case <-time.After(time.Millisecond * 50):
    }
    s.Clear()
    dbg := fmt.Sprintf("player x: %d y: %d", player.x, player.y)
    if debug == true {
      var yy int
      if player.y == 0 {
        _, yy = s.Size()
        yy--
      } else {
        yy = 0
      }
      emitStr(s, 0, yy, white, dbg)
    }
    var color tcell.Style
Enter fullscreen mode Exit fullscreen mode

Mapping

Now we're finally to our basic map drawing routine. As you can see it's a basic loop. We'll draw our map to the screen before our player character.

    for i := 0; i < 9; i++ {
      for j := 0; j < 9; j++ {
        if mapp[i][j] == '#' {
          color = grey
        }
        if mapp[i][j] == '.' {
            color = burlyWood
          }
        }
        emitStr(s, i+1, j+1, color, string(mapp[i][j]))
      }
    }

    emitStr(s, 0, 0, white, msg)

    emitStr(s, player.x, player.y, white, player.r)
    s.Show()
  }

  s.Fini()
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

And there we have it a nice simple addition to our original code and a sort of framework that we can build off if we want to. I am a bit tempted to work on this a bit long to see if I can add a simple system for adding creatures and items. I may start by seeing if we can simplify the movement code first - we'll see where the mood takes us.


You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.




Top comments (0)