diff --git a/game/game_context.go b/game/game_context.go index f862815..2d04fe4 100644 --- a/game/game_context.go +++ b/game/game_context.go @@ -31,22 +31,27 @@ func CreateGameContext() *GameContext { } func (gc *GameContext) Run() { + lastLoop := time.Now() lastTick := time.Now() for { - deltaTime := 1 + time.Since(lastTick).Microseconds() - lastTick = time.Now() + deltaTime := 1 + time.Since(lastLoop).Microseconds() + lastLoop = time.Now() for _, e := range gc.renderContext.CollectInputEvents() { gc.game.Input(e) } - stop := !gc.game.Tick(deltaTime) + if time.Since(lastTick).Milliseconds() >= TICK_RATE { + stop := !gc.game.Tick(deltaTime) - if stop { - gc.renderContext.Stop() - os.Exit(0) - break + if stop { + gc.renderContext.Stop() + os.Exit(0) + break + } + + lastTick = time.Now() } drawables := gc.game.CollectDrawables() diff --git a/game/logic/logic_snippet.go b/game/logic/logic_snippet.go new file mode 100644 index 0000000..7786ee3 --- /dev/null +++ b/game/logic/logic_snippet.go @@ -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) +} diff --git a/game/model/dungeon.go b/game/model/dungeon.go deleted file mode 100644 index 515eef6..0000000 --- a/game/model/dungeon.go +++ /dev/null @@ -1,7 +0,0 @@ -package model - -type Dungeon struct { - player *Player - - levels []Map -} diff --git a/game/model/empty_dungeon_level.go b/game/model/empty_dungeon_level.go deleted file mode 100644 index d6f1e66..0000000 --- a/game/model/empty_dungeon_level.go +++ /dev/null @@ -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() { - -} diff --git a/game/model/entity.go b/game/model/entity.go index 94b8dba..ca74272 100644 --- a/game/model/entity.go +++ b/game/model/entity.go @@ -40,5 +40,7 @@ type Entity interface { type MovableEntity interface { Position() util.Position - Move(dir Direction) + MoveTo(newPosition util.Position) + + Entity } diff --git a/game/model/flat_ground_dungeon_level.go b/game/model/flat_ground_dungeon_level.go deleted file mode 100644 index 62e9da7..0000000 --- a/game/model/flat_ground_dungeon_level.go +++ /dev/null @@ -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() { -} diff --git a/game/model/player.go b/game/model/player.go index a45bade..b534d77 100644 --- a/game/model/player.go +++ b/game/model/player.go @@ -32,8 +32,8 @@ func (p *Player) Position() util.Position { return p.position } -func (p *Player) Move(dir Direction) { - p.position = p.Position().WithOffset(MovementDirectionOffset(dir)) +func (p *Player) MoveTo(newPos util.Position) { + p.position = newPos } func (p *Player) Presentation() (rune, tcell.Style) { diff --git a/game/state/look_state.go b/game/state/look_state.go new file mode 100644 index 0000000..a091a26 --- /dev/null +++ b/game/state/look_state.go @@ -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 +} diff --git a/game/state/playing_state.go b/game/state/playing_state.go index 6407915..f2bec6b 100644 --- a/game/state/playing_state.go +++ b/game/state/playing_state.go @@ -3,6 +3,7 @@ package state import ( "math/rand" "mvvasilev/last_light/game/model" + "mvvasilev/last_light/game/world" "mvvasilev/last_light/render" "mvvasilev/last_light/util" @@ -11,8 +12,9 @@ import ( ) type PlayingState struct { - player *model.Player - level *model.MultilevelMap + player *model.Player + entityMap *world.EntityMap + level *world.MultilevelMap viewport *render.Viewport @@ -27,11 +29,11 @@ func BeginPlayingState() *PlayingState { mapSize := util.SizeOf(128, 128) - dungeonLevel := model.CreateBSPDungeonLevel(mapSize.Width(), mapSize.Height(), 4) + dungeonLevel := world.CreateBSPDungeonMap(mapSize.Width(), mapSize.Height(), 4) itemTiles := spawnItems(dungeonLevel) - itemLevel := model.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height()) + itemLevel := world.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height()) for _, it := range itemTiles { itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it) @@ -39,13 +41,15 @@ func BeginPlayingState() *PlayingState { s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY()) - s.level = model.CreateMultilevelMap( + s.entityMap = world.CreateEntityMap(mapSize.WH()) + + s.level = world.CreateMultilevelMap( dungeonLevel, 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( util.PositionAt(0, 0), @@ -57,7 +61,7 @@ func BeginPlayingState() *PlayingState { return s } -func spawnItems(level *model.BSPDungeonLevel) []model.Tile { +func spawnItems(level *world.BSPDungeonMap) []world.Tile { rooms := level.Rooms() genTable := make(map[float32]*model.ItemType) @@ -67,7 +71,7 @@ func spawnItems(level *model.BSPDungeonLevel) []model.Tile { genTable[0.051] = model.ItemTypeLongsword() genTable[0.052] = model.ItemTypeKey() - itemTiles := make([]model.Tile, 0, 10) + itemTiles := make([]world.Tile, 0, 10) for _, r := range rooms { 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), ) - itemTiles = append(itemTiles, model.CreateItemTile( + itemTiles = append(itemTiles, world.CreateItemTile( pos, itemType, 1, )) } @@ -117,14 +121,12 @@ func (ps *PlayingState) MovePlayer() { } newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection)) - tileAtMovePos := ps.level.TileAt(newPlayerPos.XY()) if tileAtMovePos.Passable() { - ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, nil) - ps.player.Move(ps.movePlayerDirection) + dx, dy := model.MovementDirectionOffset(ps.movePlayerDirection) + ps.entityMap.MoveEntity(ps.player.UniqueId(), dx, dy) ps.viewport.SetCenter(ps.player.Position()) - ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, ps.player) } ps.movePlayerDirection = model.DirectionNone @@ -134,7 +136,7 @@ func (ps *PlayingState) PickUpItemUnderPlayer() { pos := ps.player.Position() tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1) - itemTile, ok := tile.(*model.ItemTile) + itemTile, ok := tile.(*world.ItemTile) if !ok { return @@ -178,17 +180,6 @@ func (ps *PlayingState) OnInput(e *tcell.EventKey) { ps.movePlayerDirection = model.DirectionLeft case tcell.KeyRight: 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 - } } } diff --git a/game/world/bsp_map.go b/game/world/bsp_map.go new file mode 100644 index 0000000..2be2b5e --- /dev/null +++ b/game/world/bsp_map.go @@ -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 +} diff --git a/game/world/dungeon.go b/game/world/dungeon.go new file mode 100644 index 0000000..26418f7 --- /dev/null +++ b/game/world/dungeon.go @@ -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 +} diff --git a/game/world/empty_map.go b/game/world/empty_map.go new file mode 100644 index 0000000..a5b07be --- /dev/null +++ b/game/world/empty_map.go @@ -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) { + +} diff --git a/game/world/entity_map.go b/game/world/entity_map.go new file mode 100644 index 0000000..a44a3f8 --- /dev/null +++ b/game/world/entity_map.go @@ -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) + } +} diff --git a/game/model/bsp_dungeon_level.go b/game/world/generate_bsp_map.go similarity index 79% rename from game/model/bsp_dungeon_level.go rename to game/world/generate_bsp_map.go index 358a8a2..b209def 100644 --- a/game/model/bsp_dungeon_level.go +++ b/game/world/generate_bsp_map.go @@ -1,4 +1,4 @@ -package model +package world import ( "math/rand" @@ -16,7 +16,7 @@ type bspNode struct { origin util.Position size util.Size - room Room + room util.Room hasRoom bool left *bspNode @@ -25,27 +25,7 @@ type bspNode struct { splitDir splitDirection } -type Room struct { - 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 { +func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap { root := new(bspNode) root.origin = util.PositionAt(0, 0) @@ -59,7 +39,7 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel { tiles[h] = make([]Tile, width) } - rooms := make([]Room, 0, 2^numSplits) + rooms := make([]util.Room, 0, 2^numSplits) iterateBspLeaves(root, func(leaf *bspNode) { 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 } - room := Room{ - position: util.PositionAt(x, y), - size: util.SizeOf(w, h), + room := util.Room{ + Positioned: util.WithPosition(util.PositionAt(x, y)), + Sized: util.WithSize(util.SizeOf(w, h)), } rooms = append(rooms, room) @@ -95,36 +75,32 @@ func CreateBSPDungeonLevel(width, height int, numSplits int) *BSPDungeonLevel { zCorridor( tiles, util.PositionAt( - roomLeft.position.X()+roomLeft.size.Width()/2, - roomLeft.position.Y()+roomLeft.size.Height()/2, + roomLeft.Position().X()+roomLeft.Size().Width()/2, + roomLeft.Position().Y()+roomLeft.Size().Height()/2, ), util.PositionAt( - roomRight.position.X()+roomRight.size.Width()/2, - roomRight.position.Y()+roomRight.size.Height()/2, + roomRight.Position().X()+roomRight.Size().Width()/2, + roomRight.Position().Y()+roomRight.Size().Height()/2, ), parent.splitDir, ) }) - bsp := new(BSPDungeonLevel) + bsp := new(BSPDungeonMap) spawnRoom := findRoom(root.left) bsp.rooms = rooms bsp.level = CreateBasicMap(tiles) bsp.playerSpawnPoint = util.PositionAt( - spawnRoom.position.X()+spawnRoom.size.Width()/2, - spawnRoom.position.Y()+spawnRoom.size.Height()/2, + spawnRoom.Position().X()+spawnRoom.Size().Width()/2, + spawnRoom.Position().Y()+spawnRoom.Size().Height()/2, ) return bsp } -func (bsp *BSPDungeonLevel) PlayerSpawnPoint() util.Position { - return bsp.playerSpawnPoint -} - -func findRoom(parent *bspNode) Room { +func findRoom(parent *bspNode) util.Room { if parent.hasRoom { return parent.room } @@ -280,11 +256,11 @@ func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) { tiles[y][x] = CreateStaticTile(x, y, TileTypeWall()) } -func makeRoom(tiles [][]Tile, room Room) { - width := room.size.Width() - height := room.size.Height() - x := room.position.X() - y := room.position.Y() +func makeRoom(tiles [][]Tile, room util.Room) { + width := room.Size().Width() + height := room.Size().Height() + x := room.Position().X() + y := room.Position().Y() for w := x; w < x+width+1; w++ { 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 -} diff --git a/game/world/generate_empty_map.go b/game/world/generate_empty_map.go new file mode 100644 index 0000000..79cd137 --- /dev/null +++ b/game/world/generate_empty_map.go @@ -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 +} diff --git a/game/model/map.go b/game/world/map.go similarity index 84% rename from game/model/map.go rename to game/world/map.go index d5cfdd1..e4c3841 100644 --- a/game/model/map.go +++ b/game/world/map.go @@ -1,4 +1,4 @@ -package model +package world import ( "mvvasilev/last_light/util" @@ -8,7 +8,17 @@ type Map interface { Size() util.Size SetTileAt(x, y int, t 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 { diff --git a/game/model/multilevel_map.go b/game/world/multilevel_map.go similarity index 96% rename from game/model/multilevel_map.go rename to game/world/multilevel_map.go index 95f2e6a..e2979e5 100644 --- a/game/model/multilevel_map.go +++ b/game/world/multilevel_map.go @@ -1,4 +1,4 @@ -package model +package world 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) } -func (mm *MultilevelMap) Tick() { +func (mm *MultilevelMap) Tick(dt int64) { for _, l := range mm.layers { - l.Tick() + l.Tick(dt) } } diff --git a/game/model/tile.go b/game/world/tile.go similarity index 60% rename from game/model/tile.go rename to game/world/tile.go index 2766e83..d138ec3 100644 --- a/game/model/tile.go +++ b/game/world/tile.go @@ -1,6 +1,7 @@ -package model +package world import ( + "mvvasilev/last_light/game/model" "mvvasilev/last_light/util" "github.com/gdamore/tcell/v2" @@ -14,6 +15,8 @@ const ( MaterialWall MaterialGrass MaterialVoid + MaterialClosedDoor + MaterialOpenDoor ) 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 { Position() util.Position Presentation() (rune, tcell.Style) @@ -117,11 +140,11 @@ func (st *StaticTile) Type() TileType { type ItemTile struct { position util.Position - itemType *ItemType + itemType *model.ItemType 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.position = position @@ -131,7 +154,7 @@ func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *I return it } -func (it *ItemTile) Type() *ItemType { +func (it *ItemTile) Type() *model.ItemType { return it.itemType } @@ -144,7 +167,7 @@ func (it *ItemTile) Position() util.Position { } 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 { @@ -154,3 +177,43 @@ func (it *ItemTile) Passable() bool { func (it *ItemTile) Transparent() bool { 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 +} diff --git a/util/util.go b/util/util.go index a9cbc7a..c8385f7 100644 --- a/util/util.go +++ b/util/util.go @@ -2,6 +2,20 @@ package util 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 { x int y int @@ -29,6 +43,25 @@ func (p Position) WithOffset(xOffset int, yOffset int) Position { 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 { width int height int @@ -54,6 +87,10 @@ func (s Size) Area() int { 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 { if (i + 1) > limit { return i @@ -73,3 +110,8 @@ func LimitDecrement(i int, limit int) int { func RandInt(min, max int) int { return min + rand.Intn(max-min) } + +type Room struct { + Positioned + Sized +}