2019-07-02 23:55:29 -07:00
package mapping
2019-07-12 13:46:26 -07:00
import (
2019-07-24 17:03:53 -07:00
"steel/entity"
2019-07-17 20:05:05 -07:00
"steel/interfaces"
2019-07-24 17:03:53 -07:00
"github.com/kettek/goro"
2019-07-12 13:46:26 -07:00
)
2019-07-19 16:04:18 -07:00
// GameMap is our map type for holding our tiles and dimensions.
2019-07-02 23:55:29 -07:00
type GameMap struct {
2019-07-19 16:04:18 -07:00
width , height int
tiles [ ] [ ] Tile
2019-07-02 23:55:29 -07:00
}
2019-07-19 21:08:46 -07:00
// NewGameMap initializes a GameMap's tiles to match provided width and height and sets up a few tiles to block movement and sight. Returns a GameMap interface.
2019-07-19 16:04:18 -07:00
func NewGameMap ( width , height int ) interfaces . GameMap {
2019-07-19 21:08:46 -07:00
g := & GameMap {
2019-07-19 16:04:18 -07:00
width : width ,
height : height ,
}
g . tiles = make ( [ ] [ ] Tile , g . width )
2019-07-02 23:55:29 -07:00
2019-07-19 16:04:18 -07:00
for x := range g . tiles {
g . tiles [ x ] = make ( [ ] Tile , g . height )
for y := range g . tiles [ x ] {
g . tiles [ x ] [ y ] . Flags = BlockMovement | BlockSight
2019-07-10 19:44:16 -07:00
}
2019-07-17 20:05:05 -07:00
}
2019-07-19 16:04:18 -07:00
return g
2019-07-12 13:46:26 -07:00
}
2019-07-16 18:52:08 -07:00
// MakeMap creates a new randomized map. This is built according to the passed arguments.
2019-07-24 17:03:53 -07:00
func ( g * GameMap ) MakeMap ( maxRooms , roomMinSize , roomMaxSize int , entities * [ ] interfaces . Entity , maxMonsters int ) {
2019-07-19 16:04:18 -07:00
var rooms [ ] Rect
2019-07-16 18:52:08 -07:00
for r := 0 ; r < maxRooms ; r ++ {
// Generate a random width and height.
width := roomMinSize + goro . Random . Intn ( roomMaxSize )
height := roomMinSize + goro . Random . Intn ( roomMaxSize )
// Generate a random position within the map boundaries.
2019-07-19 16:04:18 -07:00
x := goro . Random . Intn ( g . width - width - 1 )
y := goro . Random . Intn ( g . height - height - 1 )
2019-07-16 18:52:08 -07:00
// Create a Rect according to our generated sizes.
room := NewRect ( x , y , width , height )
2019-07-17 20:05:05 -07:00
2019-07-16 18:52:08 -07:00
// Iterate through our existing rooms to check for intersection with our new room.
intersects := false
for _ , otherRoom := range rooms {
if room . Intersect ( otherRoom ) {
intersects = true
break
}
}
// Add the room if there is no intersection found.
if ! intersects {
g . CreateRoom ( room )
roomCenterX , roomCenterY := room . Center ( )
2019-07-17 20:05:05 -07:00
// Always place the player in the center of the first room.
if len ( rooms ) == 0 {
2019-07-24 17:03:53 -07:00
( * entities ) [ 0 ] . SetX ( roomCenterX )
( * entities ) [ 0 ] . SetY ( roomCenterY )
2019-07-16 18:52:08 -07:00
} else {
2019-07-17 20:05:05 -07:00
prevCenterX , prevCenterY := rooms [ len ( rooms ) - 1 ] . Center ( )
// Flip a coin if we should tunnel horizontally or vertically first.
if goro . Random . Intn ( 1 ) == 1 {
g . CreateHTunnel ( prevCenterX , roomCenterX , prevCenterY )
g . CreateVTunnel ( prevCenterY , roomCenterY , roomCenterX )
} else {
g . CreateVTunnel ( prevCenterY , roomCenterY , prevCenterX )
g . CreateHTunnel ( prevCenterX , roomCenterX , roomCenterY )
}
2019-07-16 18:52:08 -07:00
}
2019-07-24 17:03:53 -07:00
// Place random monsters in the room.
g . PlaceEntities ( room , entities , maxMonsters )
2019-07-16 18:52:08 -07:00
// Append our new room to our rooms list.
rooms = append ( rooms , room )
}
}
2019-07-12 13:46:26 -07:00
}
// CreateRoom creates a room from a provided rect.
2019-07-19 16:04:18 -07:00
func ( g * GameMap ) CreateRoom ( r Rect ) {
2019-07-12 13:46:26 -07:00
for x := r . X1 + 1 ; x < r . X2 ; x ++ {
for y := r . Y1 + 1 ; y < r . Y2 ; y ++ {
if g . InBounds ( x , y ) {
2019-07-19 21:08:46 -07:00
g . tiles [ x ] [ y ] = Tile { }
2019-07-12 13:46:26 -07:00
}
}
2019-07-02 23:55:29 -07:00
}
2019-07-12 13:46:26 -07:00
}
2019-07-02 23:55:29 -07:00
2019-07-19 21:08:46 -07:00
//CreateHTunnel creates a horizontal tunnel from x1 to/from x1 starting at y.
2019-07-12 13:46:26 -07:00
func ( g * GameMap ) CreateHTunnel ( x1 , x2 , y int ) {
for x := goro . MinInt ( x1 , x2 ) ; x <= goro . MaxInt ( x1 , x2 ) ; x ++ {
if g . InBounds ( x , y ) {
2019-07-19 21:08:46 -07:00
g . tiles [ x ] [ y ] = Tile { }
2019-07-12 13:46:26 -07:00
}
}
}
2019-07-17 20:05:05 -07:00
// CreateVTunnel creates a vertical tunnel from y1 to/from y2 starting at x.
2019-07-12 13:46:26 -07:00
func ( g * GameMap ) CreateVTunnel ( y1 , y2 , x int ) {
for y := goro . MinInt ( y1 , y2 ) ; y <= goro . MaxInt ( y1 , y2 ) ; y ++ {
if g . InBounds ( x , y ) {
2019-07-19 21:08:46 -07:00
g . tiles [ x ] [ y ] = Tile { }
2019-07-12 13:46:26 -07:00
}
}
2019-07-02 23:55:29 -07:00
}
2019-07-24 17:03:53 -07:00
// 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 )
}
}
}
2019-07-19 16:04:18 -07:00
// 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 ) {
return g . tiles [ x ] [ y ] . Flags & Explored != 0
}
return false
}
// SetExplored sets the explored state of the tile at x and y to the passed explored bool.
func ( g * GameMap ) SetExplored ( x , y int , explored bool ) {
if g . InBounds ( x , y ) {
if explored {
g . tiles [ x ] [ y ] . Flags = g . tiles [ x ] [ y ] . Flags | Explored
} else {
g . tiles [ x ] [ y ] . Flags = g . tiles [ x ] [ y ] . Flags &^ Explored
}
}
}
2019-07-02 23:55:29 -07:00
// IsBlocked returns if the given coordinates are blocking movement.
func ( g * GameMap ) IsBlocked ( x , y int ) bool {
// Always block if ourside our GameMap's bounds.
2019-07-16 18:52:08 -07:00
if ! g . InBounds ( x , y ) {
2019-07-02 23:55:29 -07:00
return true
}
2019-07-19 21:08:46 -07:00
return g . tiles [ x ] [ y ] . Flags & BlockMovement != 0
2019-07-19 16:04:18 -07:00
}
// IsOpaque returns if the given coordinates are blocking sight.
func ( g * GameMap ) IsOpaque ( x , y int ) bool {
if ! g . InBounds ( x , y ) {
return true
}
return g . tiles [ x ] [ y ] . Flags & BlockSight != 0
2019-07-16 18:52:08 -07:00
}
2019-07-02 23:55:29 -07:00
2019-07-19 21:08:46 -07:00
// Width returns the width of the map.
func ( g * GameMap ) Width ( ) int {
return g . width
}
// Height returns the height of the map.
func ( g * GameMap ) Height ( ) int {
return g . height
}
2019-07-12 13:46:26 -07:00
// InBounds returns if the given coordinates are within the map's bounds.
2019-07-17 20:05:05 -07:00
func ( g * GameMap ) InBounds ( x , y int ) bool {
2019-07-19 16:04:18 -07:00
if x < 0 || x >= g . width || y < 0 || y >= g . height {
2019-07-12 13:46:26 -07:00
return false
}
return true
}