diff --git a/entity/entity.go b/entity/entity.go index 7001eeb..e86df88 100644 --- a/entity/entity.go +++ b/entity/entity.go @@ -6,20 +6,23 @@ import ( "github.com/kettek/goro" ) -// Entity is a type that represents an active entity in the world. type Entity struct { x, y int rune rune style goro.Style + name string + flags uint } -// NewEntity returns a pointer to a newly created Entity. -func NewEntity(x int, y int, r rune, s goro.Style) interfaces.Entity { +// NewEntity returns an interface to a new populated Entity. +func NewEntity(x int, y int, r rune, style goro.Style, name string, flags Flags) interfaces.Entity { return &Entity{ x: x, y: y, rune: r, - style: s, + style: style, + name: name, + flags: flags, } } @@ -34,7 +37,7 @@ func (e *Entity) X() int { return e.x } -// SetX sets the entity's X value +// SetX sets the entity's x value func (e *Entity) SetX(x int) { e.x = x } @@ -65,6 +68,36 @@ func (e *Entity) Style() goro.Style { } // SetStyle sets the entity's style. -func (e *Entity) SetStyle(s goro.Style) { - e.style = s +func (e *Entity) SetStyle(style goro.Style) { + e.style = style +} + +// Name gets the entity's name. +func (e *Entity) Name() string { + return e.name +} + +// SetName sets the entity's name. +func (e *Entity) SetName(n string) { + e.name = n +} + +// Flags gets the entity's flags. +func (e *Entity) Flags() uint { + return e.flags +} + +// SetFlags sets the entity's flags. +func (e *Entity) SetFlags(f uint) { + e.flags = f +} + +// FindEntityAtLocation finds and returns the first entity at x and y matching the provided flags. If none exists, it returns nil. +func FindEntityAtLocation(entities []interfaces.Entity, x, y int, checkMask uint, matchFlags uint) interfaces.Entity { + for _, e := range entities { + if (e.Flags()&checkMask) == matchFlags && e.X() == x && e.Y() == y { + return e + } + } + return nil } diff --git a/entity/flags.go b/entity/flags.go new file mode 100644 index 0000000..a9fdd28 --- /dev/null +++ b/entity/flags.go @@ -0,0 +1,6 @@ +package entity + +// Our entity flags. +const ( + BlockMovement uint = 1 << iota +) \ No newline at end of file diff --git a/gamestate.go b/gamestate.go new file mode 100644 index 0000000..ca4a86d --- /dev/null +++ b/gamestate.go @@ -0,0 +1,10 @@ +package main + +// GameState is a numerical representation of a game state. +type GameState = uint8 + +// Our various game states. +const ( + PlayerTurnState GameState = iota + NPCTurnState +) \ No newline at end of file diff --git a/main.go b/main.go index 2e9233e..d416f51 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,8 @@ func main() { // Our initial variables. mapWidth, mapHeight := 80, 40 maxRooms, roomMinSize, roomMaxSize := 30, 6, 10 + maxMonstersPerRoom := 3 + gameState := PlayerTurnState fovRadius := 10 fovRecompute := true @@ -38,17 +40,15 @@ func main() { "lightGround": goro.Color{R: 150, G: 150, B: 150, A: 255}, } - player := entity.NewEntity(screen.Columns/2, screen.Rows/2+5, '@', goro.Style{Foreground: goro.ColorWhite}) - npc := entity.NewEntity(screen.Columns/2-5, screen.Rows/2, '@', goro.Style{Foreground: goro.ColorYellow}) + player := entity.NewEntity(0, 0, '@', goro.Style{Foreground: goro.ColorWhite}, "Player", entity.BlockMovement) entities := []interfaces.Entity{ player, - npc, } gameMap := mapping.NewGameMap(mapWidth, mapHeight) - gameMap.MakeMap(maxRooms, roomMinSize, roomMaxSize, player) + gameMap.MakeMap(maxRooms, roomMinSize, roomMaxSize, &entities, maxMonstersPerRoom) fovMap := InitializeFoV(gameMap) @@ -70,16 +70,36 @@ func main() { case goro.EventKey: switch action := handleKeyEvent(event).(type) { case ActionMove: - if !gameMap.IsBlocked(player.X()+action.X, player.Y()+action.Y) { - player.Move(action.X, action.Y) - fovRecompute = true + if gameState == PlayerTurnState { + x := player.X() + action.X + y := player.Y() + action.Y + if !gameMap.IsBlocked(x, y) { + otherEntity := entity.FindEntityAtLocation(entities, x, y, entity.BlockMovement, entity.BlockMovement) + if otherEntity != nil { + fmt.Printf("You lick the %s in the shins, much to its enjoyment!\n", otherEntity.Name()) + } else { + player.Move(action.X, action.Y) + forRecompute = true + } } - case ActionQuit: - goro.Quit() + gameState = NPCTurnState } + case ActionQuit: + goro.Quit() + } case goro.EventQuit: return } + + // Handle entity updates. + if gameState == NPCTurnState { + for i, e := range entities { + if i > 0 { + fmt.Printf("The &s punders.\n", e.Name()) + } + } + gameState = PlayerTurnState + } } }) } diff --git a/mapping/gamemap.go b/mapping/gamemap.go index 2d948de..d6dcff1 100644 --- a/mapping/gamemap.go +++ b/mapping/gamemap.go @@ -1,9 +1,10 @@ package mapping import ( - "github.com/kettek/goro" - + "steel/entity" "steel/interfaces" + + "github.com/kettek/goro" ) // GameMap is our map type for holding our tiles and dimensions. @@ -31,7 +32,7 @@ func NewGameMap(width, height int) interfaces.GameMap { } // MakeMap creates a new randomized map. This is built according to the passed arguments. -func (g *GameMap) MakeMap(maxRooms, roomMinSize, roomMaxSize int, player interfaces.Entity) { +func (g *GameMap) MakeMap(maxRooms, roomMinSize, roomMaxSize int, entities *[]interfaces.Entity, maxMonsters int) { var rooms []Rect for r := 0; r < maxRooms; r++ { @@ -59,8 +60,8 @@ func (g *GameMap) MakeMap(maxRooms, roomMinSize, roomMaxSize int, player interfa // Always place the player in the center of the first room. if len(rooms) == 0 { - player.SetX(roomCenterX) - player.SetY(roomCenterY) + (*entities)[0].SetX(roomCenterX) + (*entities)[0].SetY(roomCenterY) } else { prevCenterX, prevCenterY := rooms[len(rooms)-1].Center() @@ -73,6 +74,9 @@ func (g *GameMap) MakeMap(maxRooms, roomMinSize, roomMaxSize int, player interfa g.CreateHTunnel(prevCenterX, roomCenterX, roomCenterY) } } + // Place random monsters in the room. + g.PlaceEntities(room, entities, maxMonsters) + // Append our new room to our rooms list. rooms = append(rooms, room) } @@ -108,6 +112,28 @@ func (g *GameMap) CreateVTunnel(y1, y2, x int) { } } +// PlaceEntities places 0 to maxMonsters monster entities in the provided room. +func (g *GameMap) PlaceEntities(room Rect, entities *[]interfaces.Entity, maxMonsters int) { + monstersCount := goro.Random.Intn(maxMonsters) + + for i := 0; i < monstersCount; i++ { + var monster interfaces.Entity + // Acquire a random location within the room. + x := (1 + room.X1) + goro.Random.Intn(room.X2-room.X1-1) + y := (1 + room.Y1) + goro.Random.Intn(room.Y2-room.Y1-1) + + if entity.FindEntityAtLocation(*entities, x, y, 0, 0) == nil { + // Generate an orc with 80% probably or a troll with 20%. + if goro.Random.Intn(100) < 80 { + monster = entity.NewEntity(x, y, 'o', goro.Style{Foreground: goro.ColorLime}, "Orc", entity.BlockMovement) + } else { + monster = entity.NewEntity(x, y, 'T', goro.Style{Foreground: goro.ColorGreen}, "Troll", entity.BlockMovement) + } + *entities = append(*entities, monster) + } + } +} + // Explored returns if the tile at x by y has been explored. func (g *GameMap) Explored(x, y int) bool { if g.InBounds(x, y) {