Inventory screen

This commit is contained in:
Miroslav Vasilev 2024-05-03 13:46:32 +03:00
parent 099155c186
commit 5864ab41ad
19 changed files with 1145 additions and 94 deletions

View file

@ -29,3 +29,9 @@
- Underground City ( Caverns + Dungeon combo ) - Underground City ( Caverns + Dungeon combo )
- Objective: Pick up the Last Light and bring it to its Altar ( Altar of the Last Light ) - Objective: Pick up the Last Light and bring it to its Altar ( Altar of the Last Light )
- The light is always on level 9, but the Altar can be anywhere - The light is always on level 9, but the Altar can be anywhere
Items:
- `»o>` - fish
- `╪══` - longsword
- `o─╖`
- `█▄`

BIN
__debug_bin2404317323 Executable file

Binary file not shown.

View file

@ -0,0 +1,323 @@
package model
import (
"math/rand"
"mvvasilev/last_light/util"
)
type splitDirection bool
const (
splitDirectionVertical splitDirection = true
splitDirectionHorizontal splitDirection = false
)
type bspNode struct {
origin util.Position
size util.Size
room Room
hasRoom bool
left *bspNode
right *bspNode
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 {
root := new(bspNode)
root.origin = util.PositionAt(0, 0)
root.size = util.SizeOf(width, height)
split(root, numSplits)
tiles := make([][]Tile, height)
for h := range height {
tiles[h] = make([]Tile, width)
}
rooms := make([]Room, 0, 2^numSplits)
iterateBspLeaves(root, func(leaf *bspNode) {
x := util.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
y := util.RandInt(leaf.origin.Y(), leaf.origin.Y()+leaf.size.Height()/4)
w := util.RandInt(3, leaf.size.Width()-1)
h := util.RandInt(3, leaf.size.Height()-1)
if x+w >= width {
w = w - (x + w - width) - 1
}
if y+h >= height {
h = h - (y + h - height) - 1
}
room := Room{
position: util.PositionAt(x, y),
size: util.SizeOf(w, h),
}
rooms = append(rooms, room)
makeRoom(tiles, room)
leaf.room = room
leaf.hasRoom = true
})
iterateBspParents(root, func(parent *bspNode) {
roomLeft := findRoom(parent.left)
roomRight := findRoom(parent.right)
zCorridor(
tiles,
util.PositionAt(
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,
),
parent.splitDir,
)
})
bsp := new(BSPDungeonLevel)
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,
)
return bsp
}
func (bsp *BSPDungeonLevel) PlayerSpawnPoint() util.Position {
return bsp.playerSpawnPoint
}
func findRoom(parent *bspNode) Room {
if parent.hasRoom {
return parent.room
}
if rand.Float32() > 0.5 {
return findRoom(parent.left)
} else {
return findRoom(parent.right)
}
}
func zCorridor(tiles [][]Tile, from util.Position, to util.Position, direction splitDirection) {
switch direction {
case splitDirectionHorizontal:
xMidPoint := (from.X() + to.X()) / 2
horizontalTunnel(tiles, from.X(), xMidPoint, from.Y())
horizontalTunnel(tiles, to.X(), xMidPoint, to.Y())
verticalTunnel(tiles, from.Y(), to.Y(), xMidPoint)
case splitDirectionVertical:
yMidPoint := (from.Y() + to.Y()) / 2
verticalTunnel(tiles, from.Y(), yMidPoint, from.X())
verticalTunnel(tiles, to.Y(), yMidPoint, to.X())
horizontalTunnel(tiles, from.X(), to.X(), yMidPoint)
}
}
func iterateBspParents(parent *bspNode, iter func(parent *bspNode)) {
if parent.left != nil && parent.right != nil {
iter(parent)
}
if parent.left != nil {
iterateBspParents(parent.left, iter)
}
if parent.right != nil {
iterateBspParents(parent.right, iter)
}
}
func iterateBspLeaves(parent *bspNode, iter func(leaf *bspNode)) {
if parent.left == nil && parent.right == nil {
iter(parent)
return
}
if parent.left != nil {
iterateBspLeaves(parent.left, iter)
}
if parent.right != nil {
iterateBspLeaves(parent.right, iter)
}
}
func split(parent *bspNode, numSplits int) {
if numSplits <= 0 {
return
}
// split vertically
if parent.size.Width() > parent.size.Height() {
// New splits will be between 45% and 65% of the parent's width
leftSplitWidth := util.RandInt(int(float32(parent.size.Width())*0.45), int(float32(parent.size.Width())*0.65))
parent.splitDir = splitDirectionVertical
parent.left = new(bspNode)
parent.left.origin = parent.origin
parent.left.size = util.SizeOf(leftSplitWidth, parent.size.Height())
parent.right = new(bspNode)
parent.right.origin = parent.origin.WithOffset(leftSplitWidth, 0)
parent.right.size = util.SizeOf(parent.size.Width()-leftSplitWidth, parent.size.Height())
} else { // split horizontally
// New splits will be between 45% and 65% of the parent's height
leftSplitHeight := util.RandInt(int(float32(parent.size.Height())*0.45), int(float32(parent.size.Height())*0.65))
parent.splitDir = splitDirectionHorizontal
parent.left = new(bspNode)
parent.left.origin = parent.origin
parent.left.size = util.SizeOf(parent.size.Width(), leftSplitHeight)
parent.right = new(bspNode)
parent.right.origin = parent.origin.WithOffset(0, leftSplitHeight)
parent.right.size = util.SizeOf(parent.size.Width(), parent.size.Height()-leftSplitHeight)
}
split(parent.left, numSplits-1)
split(parent.right, numSplits-1)
}
func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
if x1 > x2 {
tx := x2
x2 = x1
x1 = tx
}
placeWallAtIfNotPassable(tiles, x1, y-1)
placeWallAtIfNotPassable(tiles, x1, y)
placeWallAtIfNotPassable(tiles, x1, y+1)
for x := x1; x <= x2; x++ {
if tiles[y][x] != nil && tiles[y][x].Passable() {
continue
}
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
placeWallAtIfNotPassable(tiles, x, y-1)
placeWallAtIfNotPassable(tiles, x, y+1)
}
placeWallAtIfNotPassable(tiles, x2, y-1)
placeWallAtIfNotPassable(tiles, x2, y)
placeWallAtIfNotPassable(tiles, x2, y+1)
}
func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
if y1 > y2 {
ty := y2
y2 = y1
y1 = ty
}
placeWallAtIfNotPassable(tiles, x-1, y1)
placeWallAtIfNotPassable(tiles, x, y1)
placeWallAtIfNotPassable(tiles, x+1, y1)
for y := y1; y <= y2; y++ {
if tiles[y][x] != nil && tiles[y][x].Passable() {
continue
}
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
placeWallAtIfNotPassable(tiles, x-1, y)
placeWallAtIfNotPassable(tiles, x+1, y)
}
placeWallAtIfNotPassable(tiles, x-1, y2)
placeWallAtIfNotPassable(tiles, x, y2)
placeWallAtIfNotPassable(tiles, x+1, y2)
}
func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
if tiles[y][x] != nil && tiles[y][x].Passable() {
return
}
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()
for w := x; w < x+width+1; w++ {
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
tiles[y+height][w] = CreateStaticTile(w, y+height, TileTypeWall())
}
for h := y; h < y+height+1; h++ {
tiles[h][x] = CreateStaticTile(x, h, TileTypeWall())
tiles[h][x+width] = CreateStaticTile(x+width, h, TileTypeWall())
}
for h := y + 1; h < y+height; h++ {
for w := x + 1; w < x+width; w++ {
tiles[h][w] = CreateStaticTile(w, h, TileTypeGround())
}
}
}
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

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

View file

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

View file

@ -21,7 +21,7 @@ func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
for w := range width { for w := range width {
if w == 0 || h == 0 || w >= width-1 || h >= height-1 { if w == 0 || h == 0 || w >= width-1 || h >= height-1 {
level.tiles[h][w] = CreateStaticTile(w, h, Rock()) level.tiles[h][w] = CreateStaticTile(w, h, TileTypeRock())
continue continue
} }
@ -35,16 +35,16 @@ func CreateFlatGroundDungeonLevel(width, height int) *FlatGroundDungeonLevel {
func genRandomGroundTile(width, height int) Tile { func genRandomGroundTile(width, height int) Tile {
switch rand.Intn(2) { switch rand.Intn(2) {
case 0: case 0:
return CreateStaticTile(width, height, Ground()) return CreateStaticTile(width, height, TileTypeGround())
case 1: case 1:
return CreateStaticTile(width, height, Grass()) return CreateStaticTile(width, height, TileTypeGrass())
default: default:
return CreateStaticTile(width, height, Ground()) return CreateStaticTile(width, height, TileTypeGround())
} }
} }
func (edl *FlatGroundDungeonLevel) Size() util.Size { func (edl *FlatGroundDungeonLevel) Size() util.Size {
return util.SizeOfInt(len(edl.tiles[0]), len(edl.tiles)) return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
} }
func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) { func (edl *FlatGroundDungeonLevel) SetTileAt(x int, y int, t Tile) {

81
game/model/inventory.go Normal file
View file

@ -0,0 +1,81 @@
package model
import "mvvasilev/last_light/util"
type Inventory struct {
contents []*Item
shape util.Size
}
func CreateInventory(shape util.Size) *Inventory {
inv := new(Inventory)
inv.contents = make([]*Item, 0, shape.Height()*shape.Width())
inv.shape = shape
return inv
}
func (i *Inventory) Items() (items []*Item) {
return i.contents
}
func (i *Inventory) Shape() util.Size {
return i.shape
}
func (i *Inventory) Push(item Item) (success bool) {
if len(i.contents) == i.shape.Area() {
return false
}
itemType := item.Type()
// Try to first find a matching item with capacity
for index, existingItem := range i.contents {
if existingItem != nil && existingItem.itemType == itemType {
if existingItem.Quantity()+1 > existingItem.Type().MaxStack() {
continue
}
it := CreateItem(itemType, existingItem.Quantity()+1)
i.contents[index] = &it
return true
}
}
// Next, try to find an intermediate empty slot to fit this item into
for index, existingItem := range i.contents {
if existingItem == nil {
i.contents[index] = &item
return true
}
}
// Finally, just append the new item at the end
i.contents = append(i.contents, &item)
return true
}
func (i *Inventory) Drop(x, y int) {
index := y*i.shape.Width() + x
if index > len(i.contents)-1 {
return
}
i.contents[index] = nil
}
func (i *Inventory) ItemAt(x, y int) (item *Item) {
index := y*i.shape.Width() + x
if index > len(i.contents)-1 {
return nil
}
return i.contents[index]
}

185
game/model/item.go Normal file
View file

@ -0,0 +1,185 @@
package model
import (
"math/rand"
"github.com/gdamore/tcell/v2"
)
type ItemType struct {
name string
description string
tileIcon rune
itemIcon string
maxStack int
style tcell.Style
}
func (it *ItemType) Name() string {
return it.name
}
func (it *ItemType) Description() string {
return it.description
}
func (it *ItemType) TileIcon() rune {
return it.tileIcon
}
func (it *ItemType) Icon() string {
return it.itemIcon
}
func (it *ItemType) Style() tcell.Style {
return it.style
}
func (it *ItemType) MaxStack() int {
return it.maxStack
}
func ItemTypeFish() *ItemType {
return &ItemType{
name: "Fish",
description: "What's a fish doing down here?",
tileIcon: '>',
itemIcon: "»o>",
style: tcell.StyleDefault.Foreground(tcell.ColorDarkCyan),
maxStack: 16,
}
}
func ItemTypeGold() *ItemType {
return &ItemType{
name: "Gold",
description: "Not all those who wander are lost",
tileIcon: '¤',
itemIcon: " ¤ ",
style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
maxStack: 255,
}
}
func ItemTypeArrow() *ItemType {
return &ItemType{
name: "Arrow",
description: "Ammunition for a bow",
tileIcon: '-',
itemIcon: "»->",
style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
maxStack: 32,
}
}
func ItemTypeBow() *ItemType {
return &ItemType{
name: "Bow",
description: "To shoot arrows with",
tileIcon: ')',
itemIcon: " |)",
style: tcell.StyleDefault.Foreground(tcell.ColorBrown),
maxStack: 1,
}
}
func ItemTypeLongsword() *ItemType {
return &ItemType{
name: "Longsword",
description: "You know nothing.",
tileIcon: '/',
itemIcon: "╪══",
style: tcell.StyleDefault.Foreground(tcell.ColorSilver),
maxStack: 1,
}
}
func ItemTypeKey() *ItemType {
return &ItemType{
name: "Key",
description: "Indispensable for unlocking things",
tileIcon: '¬',
itemIcon: " o╖",
style: tcell.StyleDefault.Foreground(tcell.ColorDarkGoldenrod),
maxStack: 1,
}
}
type ItemTypeGenTable struct {
}
func GenerateItemType(genTable map[float32]*ItemType) *ItemType {
num := rand.Float32()
for k, v := range genTable {
if num > k {
return v
}
}
return nil
}
type Item struct {
name string
description string
itemType *ItemType
quantity int
}
func EmptyItem() Item {
return Item{
itemType: &ItemType{
name: "",
description: "",
tileIcon: ' ',
itemIcon: " ",
style: tcell.StyleDefault,
maxStack: 0,
},
}
}
func CreateItem(itemType *ItemType, quantity int) Item {
return Item{
itemType: itemType,
quantity: quantity,
}
}
func (i Item) WithName(name string) Item {
i.name = name
return i
}
func (i Item) Name() string {
if i.name == "" {
return i.itemType.name
}
return i.name
}
func (i Item) Description() string {
if i.description == "" {
return i.itemType.description
}
return i.description
}
func (i Item) WithDescription(description string) Item {
i.description = description
return i
}
func (i Item) Type() *ItemType {
return i.itemType
}
func (i Item) Quantity() int {
return i.quantity
}

View file

@ -10,3 +10,52 @@ type Map interface {
TileAt(x, y int) Tile TileAt(x, y int) Tile
Tick() Tick()
} }
type BasicMap struct {
tiles [][]Tile
}
func CreateBasicMap(tiles [][]Tile) *BasicMap {
bm := new(BasicMap)
bm.tiles = tiles
return bm
}
func (bm *BasicMap) Tick() {
}
func (bm *BasicMap) Size() util.Size {
return util.SizeOf(len(bm.tiles[0]), len(bm.tiles))
}
func (bm *BasicMap) SetTileAt(x int, y int, t Tile) {
if len(bm.tiles) <= y || len(bm.tiles[0]) <= x {
return
}
if x < 0 || y < 0 {
return
}
bm.tiles[y][x] = t
}
func (bm *BasicMap) TileAt(x int, y int) Tile {
if x < 0 || y < 0 {
return CreateStaticTile(x, y, TileTypeVoid())
}
if x >= bm.Size().Width() || y >= bm.Size().Height() {
return CreateStaticTile(x, y, TileTypeVoid())
}
tile := bm.tiles[y][x]
if tile == nil {
return CreateStaticTile(x, y, TileTypeVoid())
}
return tile
}

View file

@ -42,16 +42,64 @@ func (mm *MultilevelMap) SetTileAtHeight(x, y, height int, t Tile) {
mm.layers[height].SetTileAt(x, y, t) mm.layers[height].SetTileAt(x, y, t)
} }
func (mm *MultilevelMap) TileAt(x int, y int) Tile { func (mm *MultilevelMap) CollectTilesAt(x, y int, filter func(t Tile) bool) []Tile {
tiles := make([]Tile, len(mm.layers))
if x < 0 || y < 0 {
return tiles
}
if x >= mm.Size().Width() || y >= mm.Size().Height() {
return tiles
}
for i := len(mm.layers) - 1; i >= 0; i-- { for i := len(mm.layers) - 1; i >= 0; i-- {
tile := mm.layers[i].TileAt(x, y) tile := mm.layers[i].TileAt(x, y)
if tile != nil { if tile != nil && !tile.Transparent() && filter(tile) {
return tile tiles = append(tiles, tile)
} }
} }
return nil return tiles
}
func (mm *MultilevelMap) TileAt(x int, y int) Tile {
if x < 0 || y < 0 {
return CreateStaticTile(x, y, TileTypeVoid())
}
if x >= mm.Size().Width() || y >= mm.Size().Height() {
return CreateStaticTile(x, y, TileTypeVoid())
}
for i := len(mm.layers) - 1; i >= 0; i-- {
tile := mm.layers[i].TileAt(x, y)
if tile != nil && !tile.Transparent() {
return tile
}
}
return CreateStaticTile(x, y, TileTypeVoid())
}
func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
if x < 0 || y < 0 {
return CreateStaticTile(x, y, TileTypeVoid())
}
if x >= mm.Size().Width() || y >= mm.Size().Height() {
return CreateStaticTile(x, y, TileTypeVoid())
}
if height > len(mm.layers)-1 {
return CreateStaticTile(x, y, TileTypeVoid())
}
return mm.layers[height].TileAt(x, y)
} }
func (mm *MultilevelMap) Tick() { func (mm *MultilevelMap) Tick() {

View file

@ -10,6 +10,8 @@ import (
type Player struct { type Player struct {
id uuid.UUID id uuid.UUID
position util.Position position util.Position
inventory *Inventory
} }
func CreatePlayer(x, y int) *Player { func CreatePlayer(x, y int) *Player {
@ -17,6 +19,7 @@ func CreatePlayer(x, y int) *Player {
p.id = uuid.New() p.id = uuid.New()
p.position = util.PositionAt(x, y) p.position = util.PositionAt(x, y)
p.inventory = CreateInventory(util.SizeOf(8, 4))
return p return p
} }
@ -33,14 +36,22 @@ func (p *Player) Move(dir Direction) {
p.position = p.Position().WithOffset(MovementDirectionOffset(dir)) p.position = p.Position().WithOffset(MovementDirectionOffset(dir))
} }
func (p *Player) Presentation() rune { func (p *Player) Presentation() (rune, tcell.Style) {
return '@' return '@', tcell.StyleDefault
} }
func (p *Player) Passable() bool { func (p *Player) Passable() bool {
return false return false
} }
func (p *Player) Transparent() bool {
return false
}
func (p *Player) Inventory() *Inventory {
return p.inventory
}
func (p *Player) Input(e *tcell.EventKey) { func (p *Player) Input(e *tcell.EventKey) {
} }

View file

@ -1,12 +1,17 @@
package model package model
import "mvvasilev/last_light/util" import (
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2"
)
type Material uint type Material uint
const ( const (
MaterialGround Material = iota MaterialGround Material = iota
MaterialRock MaterialRock
MaterialWall
MaterialGrass MaterialGrass
MaterialVoid MaterialVoid
) )
@ -15,44 +20,65 @@ type TileType struct {
Material Material Material Material
Passable bool Passable bool
Presentation rune Presentation rune
Transparent bool
Style tcell.Style
} }
func Ground() TileType { func TileTypeGround() TileType {
return TileType{ return TileType{
Material: MaterialGround, Material: MaterialGround,
Passable: true, Passable: true,
Presentation: '.', Presentation: '.',
Transparent: false,
Style: tcell.StyleDefault,
} }
} }
func Rock() TileType { func TileTypeRock() TileType {
return TileType{ return TileType{
Material: MaterialRock, Material: MaterialRock,
Passable: false, Passable: false,
Presentation: '█', Presentation: '█',
Transparent: false,
Style: tcell.StyleDefault,
} }
} }
func Grass() TileType { func TileTypeGrass() TileType {
return TileType{ return TileType{
Material: MaterialGrass, Material: MaterialGrass,
Passable: true, Passable: true,
Presentation: ',', Presentation: ',',
Transparent: false,
Style: tcell.StyleDefault,
} }
} }
func Void() TileType { func TileTypeVoid() TileType {
return TileType{ return TileType{
Material: MaterialVoid, Material: MaterialVoid,
Passable: false, Passable: false,
Presentation: ' ', Presentation: ' ',
Transparent: true,
Style: tcell.StyleDefault,
}
}
func TileTypeWall() TileType {
return TileType{
Material: MaterialWall,
Passable: false,
Presentation: '#',
Transparent: false,
Style: tcell.StyleDefault.Background(tcell.ColorGray),
} }
} }
type Tile interface { type Tile interface {
Position() util.Position Position() util.Position
Presentation() rune Presentation() (rune, tcell.Style)
Passable() bool Passable() bool
Transparent() bool
} }
type StaticTile struct { type StaticTile struct {
@ -73,14 +99,58 @@ func (st *StaticTile) Position() util.Position {
return st.position return st.position
} }
func (st *StaticTile) Presentation() rune { func (st *StaticTile) Presentation() (rune, tcell.Style) {
return st.t.Presentation return st.t.Presentation, st.t.Style
} }
func (st *StaticTile) Passable() bool { func (st *StaticTile) Passable() bool {
return st.t.Passable return st.t.Passable
} }
func (st *StaticTile) Transparent() bool {
return st.t.Transparent
}
func (st *StaticTile) Type() TileType { func (st *StaticTile) Type() TileType {
return st.t return st.t
} }
type ItemTile struct {
position util.Position
itemType *ItemType
quantity int
}
func CreateItemTile(position util.Position, itemType *ItemType, quantity int) *ItemTile {
it := new(ItemTile)
it.position = position
it.itemType = itemType
it.quantity = quantity
return it
}
func (it *ItemTile) Type() *ItemType {
return it.itemType
}
func (it *ItemTile) Quantity() int {
return it.quantity
}
func (it *ItemTile) Position() util.Position {
return it.position
}
func (it *ItemTile) Presentation() (rune, tcell.Style) {
return it.itemType.tileIcon, it.itemType.style
}
func (it *ItemTile) Passable() bool {
return true
}
func (it *ItemTile) Transparent() bool {
return false
}

View file

@ -1,30 +1,126 @@
package state package state
import ( import (
"fmt"
"mvvasilev/last_light/game/model" "mvvasilev/last_light/game/model"
"mvvasilev/last_light/render" "mvvasilev/last_light/render"
"mvvasilev/last_light/ui" "mvvasilev/last_light/ui"
"mvvasilev/last_light/util"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/views"
) )
type InventoryScreenState struct { type InventoryScreenState struct {
prevState GameState prevState PausableState
exitMenu bool exitMenu bool
inventoryMenu *ui.UIWindow inventoryMenu *ui.UIWindow
armourLabel *ui.UILabel
armourGrid *render.Grid
leftHandLabel *ui.UILabel
leftHandBox render.Rectangle
rightHandLabel *ui.UILabel
rightHandBox render.Rectangle
inventoryGrid *render.Grid
playerItems *render.ArbitraryDrawable
selectedItem *render.ArbitraryDrawable
help *ui.UILabel
player *model.Player player *model.Player
moveInventorySlotDirection model.Direction
selectedInventorySlot util.Position
dropSelectedInventorySlot bool
} }
func CreateInventoryScreenState(player *model.Player, prevState GameState) *InventoryScreenState { func CreateInventoryScreenState(player *model.Player, prevState PausableState) *InventoryScreenState {
iss := new(InventoryScreenState) iss := new(InventoryScreenState)
iss.prevState = prevState iss.prevState = prevState
iss.player = player iss.player = player
iss.exitMenu = false iss.exitMenu = false
iss.selectedInventorySlot = util.PositionAt(0, 0)
iss.inventoryMenu = ui.CreateWindow(40, 0, 40, 24, "INVENTORY", tcell.StyleDefault) iss.inventoryMenu = ui.CreateWindow(43, 0, 37, 24, "INVENTORY", tcell.StyleDefault)
iss.armourLabel = ui.CreateSingleLineUILabel(58, 1, "ARMOUR", tcell.StyleDefault)
iss.armourGrid = render.CreateGrid(
53, 2, 3, 1, 4, 1, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray),
)
iss.leftHandLabel = ui.CreateUILabel(
46, 1, 5, 1, "OFF", tcell.StyleDefault,
)
iss.leftHandBox = render.CreateRectangle(
45, 2, 5, 3,
'┌', '─', '┐',
'│', ' ', '│',
'└', '─', '┘',
false, true,
tcell.StyleDefault,
)
iss.rightHandLabel = ui.CreateUILabel(
74, 1, 5, 1, "DOM", tcell.StyleDefault,
)
iss.rightHandBox = render.CreateRectangle(
73, 2, 5, 3,
'┌', '─', '┐',
'│', ' ', '│',
'└', '─', '┘',
false, true,
tcell.StyleDefault,
)
iss.inventoryGrid = render.CreateGrid(
45, 5, 3, 1, 8, 4, '┌', '─', '┬', '┐', '│', ' ', '│', '│', '├', '─', '┼', '┤', '└', '─', '┴', '┘', tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray),
)
iss.playerItems = render.CreateDrawingInstructions(func(v views.View) {
for y := range player.Inventory().Shape().Height() {
for x := range player.Inventory().Shape().Width() {
item := player.Inventory().ItemAt(x, y)
isHighlighted := x == iss.selectedInventorySlot.X() && y == iss.selectedInventorySlot.Y()
if item == nil {
if isHighlighted {
ui.CreateSingleLineUILabel(45+1+x*4, 5+1+y*2, " ", tcell.StyleDefault.Background(tcell.ColorDarkSlateGray)).Draw(v)
}
continue
}
style := item.Type().Style()
if isHighlighted {
style = style.Background(tcell.ColorDarkSlateGray)
}
ui.CreateSingleLineUILabel(45+1+x*4, 5+y*2, fmt.Sprintf("%03d", item.Quantity()), style).Draw(v)
ui.CreateSingleLineUILabel(45+1+x*4, 5+1+y*2, item.Type().Icon(), style).Draw(v)
}
}
})
iss.selectedItem = render.CreateDrawingInstructions(func(v views.View) {
ui.CreateWindow(45, 14, 33, 8, "ITEM", tcell.StyleDefault).Draw(v)
item := player.Inventory().ItemAt(iss.selectedInventorySlot.XY())
if item == nil {
return
}
ui.CreateSingleLineUILabel(46, 15, fmt.Sprintf("Name: %v", item.Name()), tcell.StyleDefault).Draw(v)
ui.CreateSingleLineUILabel(46, 16, fmt.Sprintf("Desc: %v", item.Description()), tcell.StyleDefault).Draw(v)
})
iss.help = ui.CreateSingleLineUILabel(45, 22, "hjkl - move, x - drop, e - equip", tcell.StyleDefault)
return iss return iss
} }
@ -33,19 +129,89 @@ func (iss *InventoryScreenState) OnInput(e *tcell.EventKey) {
if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') { if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') {
iss.exitMenu = true iss.exitMenu = true
} }
if e.Key() == tcell.KeyRune && e.Rune() == 'x' {
iss.dropSelectedInventorySlot = true
}
if e.Key() != tcell.KeyRune {
return
}
switch e.Rune() {
case 'k':
iss.moveInventorySlotDirection = model.DirectionUp
case 'j':
iss.moveInventorySlotDirection = model.DirectionDown
case 'h':
iss.moveInventorySlotDirection = model.DirectionLeft
case 'l':
iss.moveInventorySlotDirection = model.DirectionRight
}
} }
func (iss *InventoryScreenState) OnTick(dt int64) GameState { func (iss *InventoryScreenState) OnTick(dt int64) GameState {
if iss.exitMenu { if iss.exitMenu {
iss.prevState.Unpause()
return iss.prevState return iss.prevState
} }
if iss.dropSelectedInventorySlot {
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
iss.dropSelectedInventorySlot = false
}
if iss.moveInventorySlotDirection != model.DirectionNone {
switch iss.moveInventorySlotDirection {
case model.DirectionUp:
if iss.selectedInventorySlot.Y() == 0 {
break
}
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
case model.DirectionDown:
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
break
}
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
case model.DirectionLeft:
if iss.selectedInventorySlot.X() == 0 {
break
}
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
case model.DirectionRight:
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
break
}
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(+1, 0)
}
iss.inventoryGrid.Highlight(iss.selectedInventorySlot)
iss.moveInventorySlotDirection = model.DirectionNone
}
return iss return iss
} }
func (iss *InventoryScreenState) CollectDrawables() []render.Drawable { func (iss *InventoryScreenState) CollectDrawables() []render.Drawable {
return append( drawables := append(
iss.prevState.CollectDrawables(), iss.prevState.CollectDrawables(),
iss.inventoryMenu, iss.inventoryMenu,
iss.armourLabel,
iss.armourGrid,
iss.leftHandLabel,
iss.leftHandBox,
iss.rightHandLabel,
iss.rightHandBox,
iss.inventoryGrid,
iss.playerItems,
iss.selectedItem,
iss.help,
) )
return drawables
} }

View file

@ -1,6 +1,7 @@
package state package state
import ( import (
"math/rand"
"mvvasilev/last_light/game/model" "mvvasilev/last_light/game/model"
"mvvasilev/last_light/render" "mvvasilev/last_light/render"
"mvvasilev/last_light/util" "mvvasilev/last_light/util"
@ -18,6 +19,7 @@ type PlayingState struct {
movePlayerDirection model.Direction movePlayerDirection model.Direction
pauseGame bool pauseGame bool
openInventory bool openInventory bool
pickUpUnderPlayer bool
} }
func BeginPlayingState() *PlayingState { func BeginPlayingState() *PlayingState {
@ -25,18 +27,29 @@ func BeginPlayingState() *PlayingState {
mapSize := util.SizeOf(128, 128) mapSize := util.SizeOf(128, 128)
s.player = model.CreatePlayer(40, 12) dungeonLevel := model.CreateBSPDungeonLevel(mapSize.Width(), mapSize.Height(), 4)
itemTiles := spawnItems(dungeonLevel)
itemLevel := model.CreateEmptyDungeonLevel(mapSize.Width(), mapSize.Height())
for _, it := range itemTiles {
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
}
s.player = model.CreatePlayer(dungeonLevel.PlayerSpawnPoint().XY())
s.level = model.CreateMultilevelMap( s.level = model.CreateMultilevelMap(
model.CreateFlatGroundDungeonLevel(mapSize.WH()), dungeonLevel,
itemLevel,
model.CreateEmptyDungeonLevel(mapSize.WH()), model.CreateEmptyDungeonLevel(mapSize.WH()),
) )
s.level.SetTileAtHeight(40, 12, 1, s.player) s.level.SetTileAtHeight(dungeonLevel.PlayerSpawnPoint().X(), dungeonLevel.PlayerSpawnPoint().Y(), 2, s.player)
s.viewport = render.CreateViewport( s.viewport = render.CreateViewport(
util.PositionAt(0, 0), util.PositionAt(0, 0),
util.PositionAt(40, 12), dungeonLevel.PlayerSpawnPoint(),
util.SizeOf(80, 24), util.SizeOf(80, 24),
tcell.StyleDefault, tcell.StyleDefault,
) )
@ -44,6 +57,48 @@ func BeginPlayingState() *PlayingState {
return s return s
} }
func spawnItems(level *model.BSPDungeonLevel) []model.Tile {
rooms := level.Rooms()
genTable := make(map[float32]*model.ItemType)
genTable[0.2] = model.ItemTypeFish()
genTable[0.05] = model.ItemTypeBow()
genTable[0.051] = model.ItemTypeLongsword()
genTable[0.052] = model.ItemTypeKey()
itemTiles := make([]model.Tile, 0, 10)
for _, r := range rooms {
maxItems := int(0.10 * float64(r.Size().Area()))
if maxItems < 1 {
continue
}
numItems := rand.Intn(maxItems)
for range numItems {
itemType := model.GenerateItemType(genTable)
if itemType == nil {
continue
}
pos := util.PositionAt(
util.RandInt(r.Position().X()+1, r.Position().X()+r.Size().Width()-1),
util.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
)
itemTiles = append(itemTiles, model.CreateItemTile(
pos, itemType, 1,
))
}
}
return itemTiles
}
func (ps *PlayingState) Pause() { func (ps *PlayingState) Pause() {
ps.pauseGame = true ps.pauseGame = true
} }
@ -66,15 +121,36 @@ func (ps *PlayingState) MovePlayer() {
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(), 1, nil) ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, nil)
ps.player.Move(ps.movePlayerDirection) ps.player.Move(ps.movePlayerDirection)
ps.viewport.SetCenter(ps.player.Position()) ps.viewport.SetCenter(ps.player.Position())
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 1, ps.player) ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 2, ps.player)
} }
ps.movePlayerDirection = model.DirectionNone ps.movePlayerDirection = model.DirectionNone
} }
func (ps *PlayingState) PickUpItemUnderPlayer() {
pos := ps.player.Position()
tile := ps.level.TileAtHeight(pos.X(), pos.Y(), 1)
itemTile, ok := tile.(*model.ItemTile)
if !ok {
return
}
item := model.CreateItem(itemTile.Type(), itemTile.Quantity())
success := ps.player.Inventory().Push(item)
if !success {
return
}
ps.level.SetTileAtHeight(pos.X(), pos.Y(), 1, nil)
}
func (ps *PlayingState) OnInput(e *tcell.EventKey) { func (ps *PlayingState) OnInput(e *tcell.EventKey) {
ps.player.Input(e) ps.player.Input(e)
@ -88,6 +164,11 @@ func (ps *PlayingState) OnInput(e *tcell.EventKey) {
return return
} }
if e.Key() == tcell.KeyRune && e.Rune() == 'p' {
ps.pickUpUnderPlayer = true
return
}
switch e.Key() { switch e.Key() {
case tcell.KeyUp: case tcell.KeyUp:
ps.movePlayerDirection = model.DirectionUp ps.movePlayerDirection = model.DirectionUp
@ -119,6 +200,7 @@ func (ps *PlayingState) OnTick(dt int64) GameState {
} }
if ps.openInventory { if ps.openInventory {
ps.openInventory = false
return CreateInventoryScreenState(ps.player, ps) return CreateInventoryScreenState(ps.player, ps)
} }
@ -126,19 +208,24 @@ func (ps *PlayingState) OnTick(dt int64) GameState {
ps.MovePlayer() ps.MovePlayer()
} }
if ps.pickUpUnderPlayer {
ps.pickUpUnderPlayer = false
ps.PickUpItemUnderPlayer()
}
return ps return ps
} }
func (ps *PlayingState) CollectDrawables() []render.Drawable { func (ps *PlayingState) CollectDrawables() []render.Drawable {
return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) { return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) {
ps.viewport.DrawFromProvider(v, func(x, y int) rune { ps.viewport.DrawFromProvider(v, func(x, y int) (rune, tcell.Style) {
tile := ps.level.TileAt(x, y) tile := ps.level.TileAt(x, y)
if tile != nil { if tile != nil {
return tile.Presentation() return tile.Presentation()
} }
return ' ' return ' ', tcell.StyleDefault
}) })
})) }))
} }

View file

@ -16,6 +16,7 @@ type Grid struct {
numCellsVertical int numCellsVertical int
position util.Position position util.Position
style tcell.Style style tcell.Style
highlightStyle tcell.Style
northBorder rune northBorder rune
westBorder rune westBorder rune
@ -36,6 +37,9 @@ type Grid struct {
horizontalRightTJunction rune horizontalRightTJunction rune
crossJunction rune crossJunction rune
isHighlighted bool
highlightedGrid util.Position
fillRune rune fillRune rune
} }
@ -44,15 +48,15 @@ func CreateSimpleGrid(
cellWidth, cellHeight int, cellWidth, cellHeight int,
numCellsHorizontal, numCellsVertical int, numCellsHorizontal, numCellsVertical int,
borderRune, fillRune rune, borderRune, fillRune rune,
style tcell.Style, style tcell.Style, highlightStyle tcell.Style,
) Grid { ) *Grid {
return CreateGrid( return CreateGrid(
x, y, cellWidth, cellHeight, numCellsHorizontal, numCellsVertical, x, y, cellWidth, cellHeight, numCellsHorizontal, numCellsVertical,
borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune,
borderRune, fillRune, borderRune, borderRune, borderRune, fillRune, borderRune, borderRune,
borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune,
borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune, borderRune,
style, style, highlightStyle,
) )
} }
@ -71,15 +75,17 @@ func CreateGrid(
westBorder, fillRune, internalVerticalBorder, eastBorder, westBorder, fillRune, internalVerticalBorder, eastBorder,
horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction, horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction,
swCorner, southBorder, verticalUpwardsTJunction, seCorner rune, swCorner, southBorder, verticalUpwardsTJunction, seCorner rune,
style tcell.Style, style tcell.Style, highlightStyle tcell.Style,
) Grid { ) *Grid {
return Grid{ return &Grid{
id: uuid.New(), id: uuid.New(),
internalCellSize: util.SizeOf(cellWidth, cellHeight), internalCellSize: util.SizeOf(cellWidth, cellHeight),
numCellsHorizontal: numCellsHorizontal, numCellsHorizontal: numCellsHorizontal,
numCellsVertical: numCellsVertical, numCellsVertical: numCellsVertical,
isHighlighted: false,
position: util.PositionAt(x, y), position: util.PositionAt(x, y),
style: style, style: style,
highlightStyle: highlightStyle,
northBorder: northBorder, northBorder: northBorder,
eastBorder: eastBorder, eastBorder: eastBorder,
southBorder: southBorder, southBorder: southBorder,
@ -100,10 +106,19 @@ func CreateGrid(
} }
} }
func (g Grid) UniqueId() uuid.UUID { func (g *Grid) UniqueId() uuid.UUID {
return g.id return g.id
} }
func (g *Grid) Highlight(highlightedGrid util.Position) {
g.isHighlighted = true
g.highlightedGrid = highlightedGrid
}
func (g *Grid) Unhighlight() {
g.isHighlighted = false
}
// C###T###T###C // C###T###T###C
// # # # # // # # # #
// # # # # // # # # #
@ -117,7 +132,7 @@ func (g Grid) UniqueId() uuid.UUID {
// # # # # // # # # #
// # # # # // # # # #
// C###T###T###C // C###T###T###C
func (g Grid) drawBorders(v views.View) { func (g *Grid) drawBorders(v views.View) {
iCellSizeWidth := g.internalCellSize.Width() iCellSizeWidth := g.internalCellSize.Width()
iCellSizeHeight := g.internalCellSize.Height() iCellSizeHeight := g.internalCellSize.Height()
width := 1 + (iCellSizeWidth * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal)) width := 1 + (iCellSizeWidth * int(g.numCellsHorizontal)) + (int(g.numCellsHorizontal))
@ -125,59 +140,61 @@ func (g Grid) drawBorders(v views.View) {
x := g.position.X() x := g.position.X()
y := g.position.Y() y := g.position.Y()
v.SetContent(x, y, g.nwCorner, nil, g.style) style := g.style
v.SetContent(x+width-1, y, g.neCorner, nil, g.style)
v.SetContent(x, y+height-1, g.swCorner, nil, g.style)
v.SetContent(x+width-1, y+height-1, g.seCorner, nil, g.style)
for w := 1; w < width-1; w++ { for w := 0; w < width; w++ {
for iw := 1; iw < g.numCellsVertical; iw++ {
for iw := 1; iw < int(g.numCellsVertical); iw++ { v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.internalHorizontalBorder, nil, style)
if w%(iCellSizeWidth+1) == 0 {
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.crossJunction, nil, g.style)
continue
}
v.SetContent(x+w, y+(iw*iCellSizeHeight+iw), g.internalHorizontalBorder, nil, g.style)
} }
if w%(iCellSizeWidth+1) == 0 { if w%(iCellSizeWidth+1) == 0 {
v.SetContent(x+w, y, g.verticalDownwardsTJunction, nil, g.style) v.SetContent(x+w, y, g.verticalDownwardsTJunction, nil, style)
v.SetContent(x+w, y+height-1, g.verticalUpwardsTJunction, nil, g.style) v.SetContent(x+w, y+height-1, g.verticalUpwardsTJunction, nil, style)
continue continue
} }
v.SetContent(x+w, y, g.northBorder, nil, g.style) v.SetContent(x+w, y, g.northBorder, nil, style)
v.SetContent(x+w, y+height-1, g.southBorder, nil, g.style) v.SetContent(x+w, y+height-1, g.southBorder, nil, style)
} }
for h := 1; h < height-1; h++ { for h := 0; h < height; h++ {
if h == 0 {
v.SetContent(x, y, g.nwCorner, nil, style)
v.SetContent(x, y+height-1, g.swCorner, nil, style)
continue
}
for ih := 1; ih < int(g.numCellsHorizontal); ih++ { if h == height-1 {
v.SetContent(x+width-1, y, g.neCorner, nil, style)
v.SetContent(x+width-1, y+height-1, g.seCorner, nil, style)
continue
}
for ih := 1; ih < g.numCellsHorizontal; ih++ {
if h%(iCellSizeHeight+1) == 0 { if h%(iCellSizeHeight+1) == 0 {
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.crossJunction, nil, g.style) v.SetContent(x+(ih*iCellSizeWidth+ih), y+h, g.crossJunction, nil, style)
continue continue
} }
v.SetContent(x+(ih*iCellSizeHeight+ih), y+h, g.internalVerticalBorder, nil, g.style) v.SetContent(x+(ih*iCellSizeWidth+ih), y+h, g.internalVerticalBorder, nil, style)
} }
if h%(iCellSizeHeight+1) == 0 { if h%(iCellSizeHeight+1) == 0 {
v.SetContent(x, y+h, g.horizontalRightTJunction, nil, g.style) v.SetContent(x, y+h, g.horizontalRightTJunction, nil, style)
v.SetContent(x+width-1, y+h, g.horizontalLeftTJunction, nil, g.style) v.SetContent(x+width-1, y+h, g.horizontalLeftTJunction, nil, style)
continue continue
} }
v.SetContent(x, y+h, g.westBorder, nil, g.style) v.SetContent(x, y+h, g.westBorder, nil, style)
v.SetContent(x+width-1, y+h, g.eastBorder, nil, g.style) v.SetContent(x+width-1, y+h, g.eastBorder, nil, style)
} }
} }
func (g Grid) drawFill(v views.View) { func (g *Grid) drawFill(v views.View) {
} }
func (g Grid) Draw(v views.View) { func (g *Grid) Draw(v views.View) {
g.drawBorders(v) g.drawBorders(v)
g.drawFill(v) g.drawFill(v)
} }

View file

@ -149,7 +149,9 @@ func (c *RenderContext) Draw(deltaTime int64, drawables []Drawable) {
c.view.Clear() c.view.Clear()
fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%v FPS", fps), tcell.StyleDefault) msPerFrame := float32(fps) / 1000.0
fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%vms", msPerFrame), tcell.StyleDefault)
for _, d := range drawables { for _, d := range drawables {
d.Draw(c.view) d.Draw(c.view)

View file

@ -69,8 +69,11 @@ func (t *Text) Draw(s views.View) {
currentVPos := 0 currentVPos := 0
drawText := func(text string) { drawText := func(text string) {
for i, r := range text { lastPos := 0
s.SetContent(x+currentHPos+i, y+currentVPos, r, nil, t.style)
for _, r := range text {
s.SetContent(x+currentHPos+lastPos, y+currentVPos, r, nil, t.style)
lastPos++
} }
} }

View file

@ -50,14 +50,15 @@ func (vp *Viewport) ScreenLocation() util.Position {
return vp.screenLocation return vp.screenLocation
} }
func (vp *Viewport) DrawFromProvider(v views.View, provider func(x, y int) rune) { func (vp *Viewport) DrawFromProvider(v views.View, provider func(x, y int) (rune, tcell.Style)) {
width, height := vp.viewportSize.WH() width, height := vp.viewportSize.WH()
originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY() originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY()
screenX, screenY := vp.screenLocation.XY() screenX, screenY := vp.screenLocation.XY()
for h := originY; h < originY+height; h++ { for h := originY; h < originY+height; h++ {
for w := originX; w < originX+width; w++ { for w := originX; w < originX+width; w++ {
v.SetContent(screenX, screenY, provider(w, h), nil, vp.style) r, style := provider(w, h)
v.SetContent(screenX, screenY, r, nil, style)
screenX += 1 screenX += 1
} }

View file

@ -1,5 +1,7 @@
package util package util
import "math/rand"
type Position struct { type Position struct {
x int x int
y int y int
@ -36,10 +38,6 @@ func SizeOf(width int, height int) Size {
return Size{int(width), int(height)} return Size{int(width), int(height)}
} }
func SizeOfInt(width int, height int) Size {
return Size{width, height}
}
func (s Size) Width() int { func (s Size) Width() int {
return s.width return s.width
} }
@ -52,6 +50,10 @@ func (s Size) WH() (int, int) {
return s.width, s.height return s.width, s.height
} }
func (s Size) Area() int {
return s.width * s.height
}
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
@ -67,3 +69,7 @@ func LimitDecrement(i int, limit int) int {
return i - 1 return i - 1
} }
func RandInt(min, max int) int {
return min + rand.Intn(max-min)
}