Split logic up a bit

This commit is contained in:
Miroslav Vasilev 2024-05-06 18:59:14 +03:00
parent 5864ab41ad
commit b7b269e8b8
19 changed files with 402 additions and 228 deletions

View file

@ -31,16 +31,18 @@ func CreateGameContext() *GameContext {
} }
func (gc *GameContext) Run() { func (gc *GameContext) Run() {
lastLoop := time.Now()
lastTick := time.Now() lastTick := time.Now()
for { for {
deltaTime := 1 + time.Since(lastTick).Microseconds() deltaTime := 1 + time.Since(lastLoop).Microseconds()
lastTick = time.Now() lastLoop = time.Now()
for _, e := range gc.renderContext.CollectInputEvents() { for _, e := range gc.renderContext.CollectInputEvents() {
gc.game.Input(e) gc.game.Input(e)
} }
if time.Since(lastTick).Milliseconds() >= TICK_RATE {
stop := !gc.game.Tick(deltaTime) stop := !gc.game.Tick(deltaTime)
if stop { if stop {
@ -49,6 +51,9 @@ func (gc *GameContext) Run() {
break break
} }
lastTick = time.Now()
}
drawables := gc.game.CollectDrawables() drawables := gc.game.CollectDrawables()
gc.renderContext.Draw(deltaTime, drawables) gc.renderContext.Draw(deltaTime, drawables)
} }

View file

@ -0,0 +1,12 @@
package model
import (
"mvvasilev/last_light/game/model"
"github.com/gdamore/tcell/v2"
)
type LogicSnippet[T model.Entity] interface {
Input(e *tcell.EventKey)
Tick(dt int64, entity T)
}

View file

@ -1,7 +0,0 @@
package model
type Dungeon struct {
player *Player
levels []Map
}

View file

@ -1,37 +0,0 @@
package model
import "mvvasilev/last_light/util"
type EmptyDungeonLevel struct {
level *BasicMap
}
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonLevel {
m := new(EmptyDungeonLevel)
tiles := make([][]Tile, height)
for h := range height {
tiles[h] = make([]Tile, width)
}
m.level = CreateBasicMap(tiles)
return m
}
func (edl *EmptyDungeonLevel) Size() util.Size {
return edl.level.Size()
}
func (edl *EmptyDungeonLevel) SetTileAt(x int, y int, t Tile) {
edl.level.SetTileAt(x, y, t)
}
func (edl *EmptyDungeonLevel) TileAt(x int, y int) Tile {
return edl.level.TileAt(x, y)
}
func (edl *EmptyDungeonLevel) Tick() {
}

View file

@ -40,5 +40,7 @@ type Entity interface {
type MovableEntity interface { type MovableEntity interface {
Position() util.Position Position() util.Position
Move(dir Direction) MoveTo(newPosition util.Position)
Entity
} }

View file

@ -1,75 +0,0 @@
package model
import (
"math/rand"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
)
type FlatGroundDungeonLevel struct {
tiles [][]Tile
}
func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
level := new(FlatGroundDungeonLevel)
level.tiles = make([][]Tile, height)
for h := range height {
level.tiles[h] = make([]Tile, width)
for w := range width {
if w == 0 || h == 0 || w >= width-1 || h >= height-1 {
level.tiles[h][w] = CreateStaticTile(w, h, TileTypeRock())
continue
}
level.tiles[h][w] = genRandomGroundTile(w, h)
}
}
return level
}
func genRandomGroundTile(width, height int) Tile {
switch rand.Intn(2) {
case 0:
return CreateStaticTile(width, height, TileTypeGround())
case 1:
return CreateStaticTile(width, height, TileTypeGrass())
default:
return CreateStaticTile(width, height, TileTypeGround())
}
}
func (edl *FlatGroundDungeonLevel) Size() util.Size {
return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
}
func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) {
if len(edl.tiles) <= y || len(edl.tiles[0]) <= x {
return
}
edl.tiles[y][x] = t
}
func (edl *FlatGroundDungeonLevel) TileAt(x int, y int) Tile {
if y < 0 || y >= len(edl.tiles) {
return nil
}
if x < 0 || x >= len(edl.tiles[y]) {
return nil
}
return edl.tiles[y][x]
}
func (edl *FlatGroundDungeonLevel) Input(e *tcell.EventKey) {
}
func (edl *FlatGroundDungeonLevel) Tick() {
}

View file

@ -32,8 +32,8 @@ func (p *Player) Position() util.Position {
return p.position return p.position
} }
func (p *Player) Move(dir Direction) { func (p *Player) MoveTo(newPos util.Position) {
p.position = p.Position().WithOffset(MovementDirectionOffset(dir)) p.position = newPos
} }
func (p *Player) Presentation() (rune, tcell.Style) { func (p *Player) Presentation() (rune, tcell.Style) {

22
game/state/look_state.go Normal file
View file

@ -0,0 +1,22 @@
package state
import (
"mvvasilev/last_light/render"
"github.com/gdamore/tcell/v2"
)
type LookState struct {
}
func (ls *LookState) OnInput(e *tcell.EventKey) {
panic("not implemented") // TODO: Implement
}
func (ls *LookState) OnTick(dt int64) GameState {
panic("not implemented") // TODO: Implement
}
func (ls *LookState) CollectDrawables() []render.Drawable {
panic("not implemented") // TODO: Implement
}

View file

@ -3,6 +3,7 @@ package state
import ( import (
"math/rand" "math/rand"
"mvvasilev/last_light/game/model" "mvvasilev/last_light/game/model"
"mvvasilev/last_light/game/world"
"mvvasilev/last_light/render" "mvvasilev/last_light/render"
"mvvasilev/last_light/util" "mvvasilev/last_light/util"
@ -12,7 +13,8 @@ import (
type PlayingState struct { type PlayingState struct {
player *model.Player player *model.Player
level *model.MultilevelMap entityMap *world.EntityMap
level *world.MultilevelMap
viewport *render.Viewport viewport *render.Viewport
@ -27,11 +29,11 @@ func BeginPlayingState() *PlayingState {
mapSize := util.SizeOf(128, 128) mapSize := util.SizeOf(128, 128)
dungeonLevel := model.CreateBSPDungeonLevel(mapSize.Width(), mapSize.Height(), 4) dungeonLevel := world.CreateBSPDungeonMap(mapSize.Width(), mapSize.Height(), 4)
itemTiles := spawnItems(dungeonLevel) itemTiles := spawnItems(dungeonLevel)
itemLevel := model.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height()) itemLevel := world.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height())
for _, it := range itemTiles { for _, it := range itemTiles {
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it) itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
@ -39,13 +41,15 @@ func BeginPlayingState() *PlayingState {
s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY()) s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY())
s.level = model.CreateMultilevelMap( s.entityMap = world.CreateEntityMap(mapSize.WH())
s.level = world.CreateMultilevelMap(
dungeonLevel, dungeonLevel,
itemLevel, itemLevel,
model.CreateEmptyDungeonLevel(mapSize.WH()), s.entityMap,
) )
s.level.SetTileAtHeight(dungeonLevel.PlayerSpawnPoint().X(), dungeonLevel.PlayerSpawnPoint().Y(), 2, s.player) s.entityMap.AddEntity(s.player, '@', tcell.StyleDefault)
s.viewport = render.CreateViewport( s.viewport = render.CreateViewport(
util.PositionAt(0, 0), util.PositionAt(0, 0),
@ -57,7 +61,7 @@ func BeginPlayingState() *PlayingState {
return s return s
} }
func spawnItems(level *model.BSPDungeonLevel) []model.Tile { func spawnItems(level *world.BSPDungeonMap) []world.Tile {
rooms := level.Rooms() rooms := level.Rooms()
genTable := make(map[float32]*model.ItemType) genTable := make(map[float32]*model.ItemType)
@ -67,7 +71,7 @@ func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
genTable[0.051] = model.ItemTypeLongsword() genTable[0.051] = model.ItemTypeLongsword()
genTable[0.052] = model.ItemTypeKey() genTable[0.052] = model.ItemTypeKey()
itemTiles := make([]model.Tile, 0, 10) itemTiles := make([]world.Tile, 0, 10)
for _, r := range rooms { for _, r := range rooms {
maxItems := int(0.10 * float64(r.Size().Area())) maxItems := int(0.10 * float64(r.Size().Area()))
@ -90,7 +94,7 @@ func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1), util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
) )
itemTiles = append(itemTiles, model.CreateItemTile( itemTiles = append(itemTiles, world.CreateItemTile(
pos, itemType, 1, pos, itemType, 1,
)) ))
} }
@ -117,14 +121,12 @@ func (ps *PlayingState) MovePlayer() {
} }
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection)) newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection))
tileAtMovePos := ps.level.TileAt(newPlayerPos.XY()) tileAtMovePos := ps.level.TileAt(newPlayerPos.XY())
if tileAtMovePos.Passable() { if tileAtMovePos.Passable() {
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, nil) dx, dy := model.MovementDirectionOffset(ps.movePlayerDirection)
ps.player.Move(ps.movePlayerDirection) ps.entityMap.MoveEntity(ps.player.UniqueId(), dx, dy)
ps.viewport.SetCenter(ps.player.Position()) ps.viewport.SetCenter(ps.player.Position())
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, ps.player)
} }
ps.movePlayerDirection = model.DirectionNone ps.movePlayerDirection = model.DirectionNone
@ -134,7 +136,7 @@ func (ps *PlayingState) PickUpItemUnderPlayer() {
pos := ps.player.Position() pos := ps.player.Position()
tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1) tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1)
itemTile, ok := tile.(*model.ItemTile) itemTile, ok := tile.(*world.ItemTile)
if !ok { if !ok {
return return
@ -178,17 +180,6 @@ func (ps *PlayingState) OnInput(e *tcell.EventKey) {
ps.movePlayerDirection = model.DirectionLeft ps.movePlayerDirection = model.DirectionLeft
case tcell.KeyRight: case tcell.KeyRight:
ps.movePlayerDirection = model.DirectionRight ps.movePlayerDirection = model.DirectionRight
case tcell.KeyRune:
switch e.Rune() {
case 'w':
ps.movePlayerDirection = model.DirectionUp
case 'a':
ps.movePlayerDirection = model.DirectionLeft
case 's':
ps.movePlayerDirection = model.DirectionDown
case 'd':
ps.movePlayerDirection = model.DirectionRight
}
} }
} }

35
game/world/bsp_map.go Normal file
View file

@ -0,0 +1,35 @@
package world
import (
"mvvasilev/last_light/util"
)
type BSPDungeonMap struct {
level *BasicMap
playerSpawnPoint util.Position
rooms []util.Room
}
func (bsp *BSPDungeonMap) PlayerSpawnPoint() util.Position {
return bsp.playerSpawnPoint
}
func (bsp *BSPDungeonMap) Size() util.Size {
return bsp.level.Size()
}
func (bsp *BSPDungeonMap) SetTileAt(x int, y int, t Tile) {
bsp.level.SetTileAt(x, y, t)
}
func (bsp *BSPDungeonMap) TileAt(x int, y int) Tile {
return bsp.level.TileAt(x, y)
}
func (bsp *BSPDungeonMap) Tick(dt int64) {
}
func (bsp *BSPDungeonMap) Rooms() []util.Room {
return bsp.rooms
}

15
game/world/dungeon.go Normal file
View file

@ -0,0 +1,15 @@
package world
import "mvvasilev/last_light/game/model"
type dungeonLevel struct {
groundLevel Map
entityLevel *EntityMap
itemLevel *Map
}
type Dungeon struct {
player *model.Player
levels []*dungeonLevel
}

23
game/world/empty_map.go Normal file
View file

@ -0,0 +1,23 @@
package world
import "mvvasilev/last_light/util"
type EmptyDungeonMap struct {
level *BasicMap
}
func (edl *EmptyDungeonMap) Size() util.Size {
return edl.level.Size()
}
func (edl *EmptyDungeonMap) SetTileAt(x int, y int, t Tile) {
edl.level.SetTileAt(x, y, t)
}
func (edl *EmptyDungeonMap) TileAt(x int, y int) Tile {
return edl.level.TileAt(x, y)
}
func (edl *EmptyDungeonMap) Tick(dt int64) {
}

101
game/world/entity_map.go Normal file
View file

@ -0,0 +1,101 @@
package world
import (
"maps"
"mvvasilev/last_light/game/model"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
"github.com/google/uuid"
)
type EntityMap struct {
entities map[int]EntityTile
util.Sized
}
func CreateEntityMap(width, height int) *EntityMap {
return &EntityMap{
entities: make(map[int]EntityTile, 0),
Sized: util.WithSize(util.SizeOf(width, height)),
}
}
func (em *EntityMap) SetTileAt(x int, y int, t Tile) {
// if !em.FitsWithin(x, y) {
// return
// }
// index := em.Size().AsArrayIndex(x, y)
// TODO? May not be necessary
}
func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTile) {
for i, e := range em.entities {
if e.Entity().UniqueId() == uuid {
return i, e
}
}
return -1, nil
}
func (em *EntityMap) AddEntity(entity model.MovableEntity, presentation rune, style tcell.Style) {
if !em.FitsWithin(entity.Position().XY()) {
return
}
key := em.Size().AsArrayIndex(entity.Position().XY())
et := CreateBasicEntityTile(entity, presentation, style)
em.entities[key] = et
}
func (em *EntityMap) DropEntity(uuid uuid.UUID) {
maps.DeleteFunc(em.entities, func(i int, et EntityTile) bool {
if et.Entity().UniqueId() == uuid {
return true
}
return false
})
}
func (em *EntityMap) MoveEntity(uuid uuid.UUID, dx, dy int) {
oldKey, e := em.FindEntityByUuid(uuid)
if e == nil {
return
}
if !em.FitsWithin(e.Entity().Position().WithOffset(dx, dy).XY()) {
return
}
delete(em.entities, oldKey)
newPos := e.Entity().Position().WithOffset(dx, dy)
e.Entity().MoveTo(newPos)
newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
em.entities[newKey] = e
}
func (em *EntityMap) TileAt(x int, y int) Tile {
if !em.FitsWithin(x, y) {
return CreateStaticTile(x, y, TileTypeVoid())
}
key := em.Size().AsArrayIndex(x, y)
return em.entities[key]
}
func (em *EntityMap) Tick(dt int64) {
for _, e := range em.entities {
e.Entity().Tick(dt)
}
}

View file

@ -1,4 +1,4 @@
package model package world
import ( import (
"math/rand" "math/rand"
@ -16,7 +16,7 @@ type bspNode struct {
origin util.Position origin util.Position
size util.Size size util.Size
room Room room util.Room
hasRoom bool hasRoom bool
left *bspNode left *bspNode
@ -25,27 +25,7 @@ type bspNode struct {
splitDir splitDirection splitDir splitDirection
} }
type Room struct { func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
position util.Position
size util.Size
}
func (r Room) Size() util.Size {
return r.size
}
func (r Room) Position() util.Position {
return r.position
}
type BSPDungeonLevel struct {
level *BasicMap
playerSpawnPoint util.Position
rooms []Room
}
func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
root := new(bspNode) root := new(bspNode)
root.origin = util.PositionAt(0, 0) root.origin = util.PositionAt(0, 0)
@ -59,7 +39,7 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
tiles[h] = make([]Tile, width) tiles[h] = make([]Tile, width)
} }
rooms := make([]Room, 0, 2^numSplits) rooms := make([]util.Room, 0, 2^numSplits)
iterateBspLeaves(root, func(leaf *bspNode) { iterateBspLeaves(root, func(leaf *bspNode) {
x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4) x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
@ -75,9 +55,9 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
h = h - (y + h - height) - 1 h = h - (y + h - height) - 1
} }
room := Room{ room := util.Room{
position: util.PositionAt(x, y), Positioned: util.WithPosition(util.PositionAt(x, y)),
size: util.SizeOf(w, h), Sized: util.WithSize(util.SizeOf(w, h)),
} }
rooms = append(rooms, room) rooms = append(rooms, room)
@ -95,36 +75,32 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel {
zCorridor( zCorridor(
tiles, tiles,
util.PositionAt( util.PositionAt(
roomLeft.position.X()+roomLeft.size.Width()/2, roomLeft.Position().X()+roomLeft.Size().Width()/2,
roomLeft.position.Y()+roomLeft.size.Height()/2, roomLeft.Position().Y()+roomLeft.Size().Height()/2,
), ),
util.PositionAt( util.PositionAt(
roomRight.position.X()+roomRight.size.Width()/2, roomRight.Position().X()+roomRight.Size().Width()/2,
roomRight.position.Y()+roomRight.size.Height()/2, roomRight.Position().Y()+roomRight.Size().Height()/2,
), ),
parent.splitDir, parent.splitDir,
) )
}) })
bsp := new(BSPDungeonLevel) bsp := new(BSPDungeonMap)
spawnRoom := findRoom(root.left) spawnRoom := findRoom(root.left)
bsp.rooms = rooms bsp.rooms = rooms
bsp.level = CreateBasicMap(tiles) bsp.level = CreateBasicMap(tiles)
bsp.playerSpawnPoint = util.PositionAt( bsp.playerSpawnPoint = util.PositionAt(
spawnRoom.position.X()+spawnRoom.size.Width()/2, spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
spawnRoom.position.Y()+spawnRoom.size.Height()/2, spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
) )
return bsp return bsp
} }
func (bsp *BSPDungeonLevel) PlayerSpawnPoint() util.Position { func findRoom(parent *bspNode) util.Room {
return bsp.playerSpawnPoint
}
func findRoom(parent *bspNode) Room {
if parent.hasRoom { if parent.hasRoom {
return parent.room return parent.room
} }
@ -280,11 +256,11 @@ func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall()) tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
} }
func makeRoom(tiles [][]Tile, room Room) { func makeRoom(tiles [][]Tile, room util.Room) {
width := room.size.Width() width := room.Size().Width()
height := room.size.Height() height := room.Size().Height()
x := room.position.X() x := room.Position().X()
y := room.position.Y() y := room.Position().Y()
for w := x; w < x+width+1; w++ { for w := x; w < x+width+1; w++ {
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall()) tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
@ -302,22 +278,3 @@ func makeRoom(tiles [][]Tile, room Room) {
} }
} }
} }
func (bsp *BSPDungeonLevel) Size() util.Size {
return bsp.level.Size()
}
func (bsp *BSPDungeonLevel) SetTileAt(x int, y int, t Tile) {
bsp.level.SetTileAt(x, y, t)
}
func (bsp *BSPDungeonLevel) TileAt(x int, y int) Tile {
return bsp.level.TileAt(x, y)
}
func (bsp *BSPDungeonLevel) Tick() {
}
func (bsp *BSPDungeonLevel) Rooms() []Room {
return bsp.rooms
}

View file

@ -0,0 +1,15 @@
package world
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonMap {
m := new(EmptyDungeonMap)
tiles := make([][]Tile, height)
for h := range height {
tiles[h] = make([]Tile, width)
}
m.level = CreateBasicMap(tiles)
return m
}

View file

@ -1,4 +1,4 @@
package model package world
import ( import (
"mvvasilev/last_light/util" "mvvasilev/last_light/util"
@ -8,7 +8,17 @@ type Map interface {
Size() util.Size Size() util.Size
SetTileAt(x, y int, t Tile) SetTileAt(x, y int, t Tile)
TileAt(x, y int) Tile TileAt(x, y int) Tile
Tick() Tick(dt int64)
}
type WithPlayerSpawnPoint interface {
PlayerSpawnPoint() util.Position
Map
}
type WithRooms interface {
Rooms() []util.Room
Map
} }
type BasicMap struct { type BasicMap struct {

View file

@ -1,4 +1,4 @@
package model package world
import "mvvasilev/last_light/util" import "mvvasilev/last_light/util"
@ -102,8 +102,8 @@ func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
return mm.layers[height].TileAt(x, y) return mm.layers[height].TileAt(x, y)
} }
func (mm *MultilevelMap) Tick() { func (mm *MultilevelMap) Tick(dt int64) {
for _, l := range mm.layers { for _, l := range mm.layers {
l.Tick() l.Tick(dt)
} }
} }

View file

@ -1,6 +1,7 @@
package model package world
import ( import (
"mvvasilev/last_light/game/model"
"mvvasilev/last_light/util" "mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
@ -14,6 +15,8 @@ const (
MaterialWall MaterialWall
MaterialGrass MaterialGrass
MaterialVoid MaterialVoid
MaterialClosedDoor
MaterialOpenDoor
) )
type TileType struct { type TileType struct {
@ -74,6 +77,26 @@ func TileTypeWall() TileType {
} }
} }
func TileTypeClosedDoor() TileType {
return TileType{
Material: MaterialClosedDoor,
Passable: false,
Transparent: false,
Presentation: '[',
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue).Background(tcell.ColorSaddleBrown),
}
}
func TileTypeOpenDoor() TileType {
return TileType{
Material: MaterialClosedDoor,
Passable: false,
Transparent: false,
Presentation: '_',
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue),
}
}
type Tile interface { type Tile interface {
Position() util.Position Position() util.Position
Presentation() (rune, tcell.Style) Presentation() (rune, tcell.Style)
@ -117,11 +140,11 @@ func (st *StaticTile) Type() TileType {
type ItemTile struct { type ItemTile struct {
position util.Position position util.Position
itemType *ItemType itemType *model.ItemType
quantity int quantity int
} }
func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *ItemTile { func CreateItemTile(position util.Position, itemType *model.ItemType, quantity int) *ItemTile {
it := new(ItemTile) it := new(ItemTile)
it.position = position it.position = position
@ -131,7 +154,7 @@ func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *I
return it return it
} }
func (it *ItemTile) Type() *ItemType { func (it *ItemTile) Type() *model.ItemType {
return it.itemType return it.itemType
} }
@ -144,7 +167,7 @@ func (it *ItemTile) Position() util.Position {
} }
func (it *ItemTile) Presentation() (rune, tcell.Style) { func (it *ItemTile) Presentation() (rune, tcell.Style) {
return it.itemType.tileIcon, it.itemType.style return it.itemType.TileIcon(), it.itemType.Style()
} }
func (it *ItemTile) Passable() bool { func (it *ItemTile) Passable() bool {
@ -154,3 +177,43 @@ func (it *ItemTile) Passable() bool {
func (it *ItemTile) Transparent() bool { func (it *ItemTile) Transparent() bool {
return false return false
} }
type EntityTile interface {
Entity() model.MovableEntity
Tile
}
type BasicEntityTile struct {
entity model.MovableEntity
presentation rune
style tcell.Style
}
func CreateBasicEntityTile(entity model.MovableEntity, presentation rune, style tcell.Style) *BasicEntityTile {
return &BasicEntityTile{
entity: entity,
presentation: presentation,
style: style,
}
}
func (bet *BasicEntityTile) Entity() model.MovableEntity {
return bet.entity
}
func (bet *BasicEntityTile) Position() util.Position {
return bet.entity.Position()
}
func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
return bet.presentation, bet.style
}
func (bet *BasicEntityTile) Passable() bool {
return false
}
func (bet *BasicEntityTile) Transparent() bool {
return false
}

View file

@ -2,6 +2,20 @@ package util
import "math/rand" import "math/rand"
type Positioned struct {
pos Position
}
func WithPosition(pos Position) Positioned {
return Positioned{
pos: pos,
}
}
func (wp *Positioned) Position() Position {
return wp.pos
}
type Position struct { type Position struct {
x int x int
y int y int
@ -29,6 +43,25 @@ func (p Position) WithOffset(xOffset int, yOffset int) Position {
return p return p
} }
type Sized struct {
size Size
}
func WithSize(size Size) Sized {
return Sized{
size: size,
}
}
// Checks if the provided coordinates fit within the sized struct, [0, N)
func (ws *Sized) FitsWithin(x, y int) bool {
return 0 <= x && x < ws.size.width && 0 <= y && y < ws.size.height
}
func (ws *Sized) Size() Size {
return ws.size
}
type Size struct { type Size struct {
width int width int
height int height int
@ -54,6 +87,10 @@ func (s Size) Area() int {
return s.width * s.height return s.width * s.height
} }
func (s Size) AsArrayIndex(x, y int) int {
return y*s.width + x
}
func LimitIncrement(i int, limit int) int { func LimitIncrement(i int, limit int) int {
if (i + 1) > limit { if (i + 1) > limit {
return i return i
@ -73,3 +110,8 @@ func LimitDecrement(i int, limit int) int {
func RandInt(min, max int) int { func RandInt(min, max int) int {
return min + rand.Intn(max-min) return min + rand.Intn(max-min)
} }
type Room struct {
Positioned
Sized
}