mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-05-12 07:09:52 +03:00
New drawing system, start of inventory menu
This commit is contained in:
parent
c0f80f0e0c
commit
099155c186
32 changed files with 890 additions and 278 deletions
16
game/game.go
16
game/game.go
|
@ -2,13 +2,15 @@ package game
|
|||
|
||||
import (
|
||||
"mvvasilev/last_light/game/state"
|
||||
"mvvasilev/last_light/render"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
state state.GameState
|
||||
|
||||
quitGame bool
|
||||
}
|
||||
|
||||
func CreateGame() *Game {
|
||||
|
@ -20,10 +22,18 @@ func CreateGame() *Game {
|
|||
}
|
||||
|
||||
func (g *Game) Input(ev *tcell.EventKey) {
|
||||
if ev.Key() == tcell.KeyCtrlC {
|
||||
g.quitGame = true
|
||||
}
|
||||
|
||||
g.state.OnInput(ev)
|
||||
}
|
||||
|
||||
func (g *Game) Tick(dt int64) bool {
|
||||
if g.quitGame {
|
||||
return false
|
||||
}
|
||||
|
||||
s := g.state.OnTick(dt)
|
||||
|
||||
switch s.(type) {
|
||||
|
@ -36,6 +46,6 @@ func (g *Game) Tick(dt int64) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (g *Game) Draw(v views.View) {
|
||||
g.state.OnDraw(v)
|
||||
func (g *Game) CollectDrawables() []render.Drawable {
|
||||
return g.state.CollectDrawables()
|
||||
}
|
||||
|
|
55
game/game_context.go
Normal file
55
game/game_context.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package game
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mvvasilev/last_light/render"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const TICK_RATE int64 = 50 // tick every 50ms ( 20 ticks per second )
|
||||
|
||||
type GameContext struct {
|
||||
renderContext *render.RenderContext
|
||||
|
||||
game *Game
|
||||
}
|
||||
|
||||
func CreateGameContext() *GameContext {
|
||||
gc := new(GameContext)
|
||||
|
||||
rc, err := render.CreateRenderContext()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("%~v", err)
|
||||
}
|
||||
|
||||
gc.renderContext = rc
|
||||
gc.game = CreateGame()
|
||||
|
||||
return gc
|
||||
}
|
||||
|
||||
func (gc *GameContext) Run() {
|
||||
lastTick := time.Now()
|
||||
|
||||
for {
|
||||
deltaTime := 1 + time.Since(lastTick).Microseconds()
|
||||
lastTick = time.Now()
|
||||
|
||||
for _, e := range gc.renderContext.CollectInputEvents() {
|
||||
gc.game.Input(e)
|
||||
}
|
||||
|
||||
stop := !gc.game.Tick(deltaTime)
|
||||
|
||||
if stop {
|
||||
gc.renderContext.Stop()
|
||||
os.Exit(0)
|
||||
break
|
||||
}
|
||||
|
||||
drawables := gc.game.CollectDrawables()
|
||||
gc.renderContext.Draw(deltaTime, drawables)
|
||||
}
|
||||
}
|
1
game/model/dungeon.go
Normal file
1
game/model/dungeon.go
Normal file
|
@ -0,0 +1 @@
|
|||
package model
|
47
game/model/empty_dungeon_level.go
Normal file
47
game/model/empty_dungeon_level.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package model
|
||||
|
||||
import "mvvasilev/last_light/util"
|
||||
|
||||
type EmptyDungeonLevel struct {
|
||||
tiles [][]Tile
|
||||
}
|
||||
|
||||
func CreateEmptyDungeonLevel(width, height int) *EmptyDungeonLevel {
|
||||
m := new(EmptyDungeonLevel)
|
||||
|
||||
m.tiles = make([][]Tile, height)
|
||||
|
||||
for h := range height {
|
||||
m.tiles[h] = make([]Tile, width)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (edl *EmptyDungeonLevel) Size() util.Size {
|
||||
return util.SizeOf(len(edl.tiles[0]), len(edl.tiles))
|
||||
}
|
||||
|
||||
func (edl *EmptyDungeonLevel) 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 *EmptyDungeonLevel) 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 *EmptyDungeonLevel) Tick() {
|
||||
|
||||
}
|
|
@ -4,22 +4,36 @@ import (
|
|||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
Up Direction = iota
|
||||
Down
|
||||
Left
|
||||
Right
|
||||
DirectionNone Direction = iota
|
||||
DirectionUp
|
||||
DirectionDown
|
||||
DirectionLeft
|
||||
DirectionRight
|
||||
)
|
||||
|
||||
func MovementDirectionOffset(dir Direction) (int, int) {
|
||||
switch dir {
|
||||
case DirectionUp:
|
||||
return 0, -1
|
||||
case DirectionDown:
|
||||
return 0, 1
|
||||
case DirectionLeft:
|
||||
return -1, 0
|
||||
case DirectionRight:
|
||||
return 1, 0
|
||||
}
|
||||
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
type Entity interface {
|
||||
UniqueId() uuid.UUID
|
||||
Draw(v views.View)
|
||||
Input(e *tcell.EventKey)
|
||||
Tick(dt int64)
|
||||
}
|
||||
|
|
75
game/model/flat_ground_dungeon_level.go
Normal file
75
game/model/flat_ground_dungeon_level.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
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, Rock())
|
||||
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, Ground())
|
||||
case 1:
|
||||
return CreateStaticTile(width, height, Grass())
|
||||
default:
|
||||
return CreateStaticTile(width, height, Ground())
|
||||
}
|
||||
}
|
||||
|
||||
func (edl *FlatGroundDungeonLevel) Size() util.Size {
|
||||
return util.SizeOfInt(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() {
|
||||
}
|
12
game/model/map.go
Normal file
12
game/model/map.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/util"
|
||||
)
|
||||
|
||||
type Map interface {
|
||||
Size() util.Size
|
||||
SetTileAt(x, y int, t Tile)
|
||||
TileAt(x, y int) Tile
|
||||
Tick()
|
||||
}
|
61
game/model/multilevel_map.go
Normal file
61
game/model/multilevel_map.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package model
|
||||
|
||||
import "mvvasilev/last_light/util"
|
||||
|
||||
type MultilevelMap struct {
|
||||
layers []Map
|
||||
}
|
||||
|
||||
func CreateMultilevelMap(maps ...Map) *MultilevelMap {
|
||||
m := new(MultilevelMap)
|
||||
|
||||
m.layers = maps
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) Size() util.Size {
|
||||
if len(mm.layers) == 0 {
|
||||
return util.SizeOf(0, 0)
|
||||
}
|
||||
|
||||
return mm.layers[0].Size()
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) SetTileAt(x, y int, t Tile) {
|
||||
mm.layers[0].SetTileAt(x, y, t)
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) UnsetTileAtHeight(x, y, height int) {
|
||||
if len(mm.layers) < height {
|
||||
return
|
||||
}
|
||||
|
||||
mm.layers[height].SetTileAt(x, y, nil)
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) SetTileAtHeight(x, y, height int, t Tile) {
|
||||
if len(mm.layers) < height {
|
||||
return
|
||||
}
|
||||
|
||||
mm.layers[height].SetTileAt(x, y, t)
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) TileAt(x int, y int) Tile {
|
||||
for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||
tile := mm.layers[i].TileAt(x, y)
|
||||
|
||||
if tile != nil {
|
||||
return tile
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mm *MultilevelMap) Tick() {
|
||||
for _, l := range mm.layers {
|
||||
l.Tick()
|
||||
}
|
||||
}
|
|
@ -4,17 +4,15 @@ import (
|
|||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
id uuid.UUID
|
||||
position util.Position
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
func CreatePlayer(x, y uint16, style tcell.Style) *Player {
|
||||
func CreatePlayer(x, y int) *Player {
|
||||
p := new(Player)
|
||||
|
||||
p.id = uuid.New()
|
||||
|
@ -27,48 +25,23 @@ func (p *Player) UniqueId() uuid.UUID {
|
|||
return p.id
|
||||
}
|
||||
|
||||
func (p *Player) Position() util.Position {
|
||||
return p.position
|
||||
}
|
||||
|
||||
func (p *Player) Move(dir Direction) {
|
||||
x, y := p.position.XYUint16()
|
||||
|
||||
switch dir {
|
||||
case Up:
|
||||
p.position = util.PositionAt(x, y-1)
|
||||
case Down:
|
||||
p.position = util.PositionAt(x, y+1)
|
||||
case Left:
|
||||
p.position = util.PositionAt(x-1, y)
|
||||
case Right:
|
||||
p.position = util.PositionAt(x+1, y)
|
||||
}
|
||||
p.position = p.Position().WithOffset(MovementDirectionOffset(dir))
|
||||
}
|
||||
|
||||
func (p *Player) Draw(v views.View) {
|
||||
x, y := p.position.XY()
|
||||
v.SetContent(x, y, '@', nil, p.style)
|
||||
func (p *Player) Presentation() rune {
|
||||
return '@'
|
||||
}
|
||||
|
||||
func (p *Player) Passable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Player) Input(e *tcell.EventKey) {
|
||||
switch e.Key() {
|
||||
case tcell.KeyUp:
|
||||
p.Move(Up)
|
||||
case tcell.KeyDown:
|
||||
p.Move(Down)
|
||||
case tcell.KeyLeft:
|
||||
p.Move(Left)
|
||||
case tcell.KeyRight:
|
||||
p.Move(Right)
|
||||
case tcell.KeyRune:
|
||||
switch e.Rune() {
|
||||
case 'w':
|
||||
p.Move(Up)
|
||||
case 'a':
|
||||
p.Move(Left)
|
||||
case 's':
|
||||
p.Move(Down)
|
||||
case 'd':
|
||||
p.Move(Right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) Tick(dt int64) {
|
||||
|
|
86
game/model/tile.go
Normal file
86
game/model/tile.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package model
|
||||
|
||||
import "mvvasilev/last_light/util"
|
||||
|
||||
type Material uint
|
||||
|
||||
const (
|
||||
MaterialGround Material = iota
|
||||
MaterialRock
|
||||
MaterialGrass
|
||||
MaterialVoid
|
||||
)
|
||||
|
||||
type TileType struct {
|
||||
Material Material
|
||||
Passable bool
|
||||
Presentation rune
|
||||
}
|
||||
|
||||
func Ground() TileType {
|
||||
return TileType{
|
||||
Material: MaterialGround,
|
||||
Passable: true,
|
||||
Presentation: '.',
|
||||
}
|
||||
}
|
||||
|
||||
func Rock() TileType {
|
||||
return TileType{
|
||||
Material: MaterialRock,
|
||||
Passable: false,
|
||||
Presentation: '█',
|
||||
}
|
||||
}
|
||||
|
||||
func Grass() TileType {
|
||||
return TileType{
|
||||
Material: MaterialGrass,
|
||||
Passable: true,
|
||||
Presentation: ',',
|
||||
}
|
||||
}
|
||||
|
||||
func Void() TileType {
|
||||
return TileType{
|
||||
Material: MaterialVoid,
|
||||
Passable: false,
|
||||
Presentation: ' ',
|
||||
}
|
||||
}
|
||||
|
||||
type Tile interface {
|
||||
Position() util.Position
|
||||
Presentation() rune
|
||||
Passable() bool
|
||||
}
|
||||
|
||||
type StaticTile struct {
|
||||
position util.Position
|
||||
t TileType
|
||||
}
|
||||
|
||||
func CreateStaticTile(x, y int, t TileType) Tile {
|
||||
st := new(StaticTile)
|
||||
|
||||
st.position = util.PositionAt(x, y)
|
||||
st.t = t
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
func (st *StaticTile) Position() util.Position {
|
||||
return st.position
|
||||
}
|
||||
|
||||
func (st *StaticTile) Presentation() rune {
|
||||
return st.t.Presentation
|
||||
}
|
||||
|
||||
func (st *StaticTile) Passable() bool {
|
||||
return st.t.Passable
|
||||
}
|
||||
|
||||
func (st *StaticTile) Type() TileType {
|
||||
return st.t
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/render"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type GameState interface {
|
||||
OnInput(e *tcell.EventKey)
|
||||
OnTick(dt int64) GameState
|
||||
OnDraw(c views.View)
|
||||
CollectDrawables() []render.Drawable
|
||||
}
|
||||
|
||||
type PausableState interface {
|
51
game/state/inventory_screen_state.go
Normal file
51
game/state/inventory_screen_state.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/game/model"
|
||||
"mvvasilev/last_light/render"
|
||||
"mvvasilev/last_light/ui"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
type InventoryScreenState struct {
|
||||
prevState GameState
|
||||
exitMenu bool
|
||||
|
||||
inventoryMenu *ui.UIWindow
|
||||
|
||||
player *model.Player
|
||||
}
|
||||
|
||||
func CreateInventoryScreenState(player *model.Player, prevState GameState) *InventoryScreenState {
|
||||
iss := new(InventoryScreenState)
|
||||
|
||||
iss.prevState = prevState
|
||||
iss.player = player
|
||||
iss.exitMenu = false
|
||||
|
||||
iss.inventoryMenu = ui.CreateWindow(40, 0, 40, 24, "INVENTORY", tcell.StyleDefault)
|
||||
|
||||
return iss
|
||||
}
|
||||
|
||||
func (iss *InventoryScreenState) OnInput(e *tcell.EventKey) {
|
||||
if e.Key() == tcell.KeyEsc || (e.Key() == tcell.KeyRune && e.Rune() == 'i') {
|
||||
iss.exitMenu = true
|
||||
}
|
||||
}
|
||||
|
||||
func (iss *InventoryScreenState) OnTick(dt int64) GameState {
|
||||
if iss.exitMenu {
|
||||
return iss.prevState
|
||||
}
|
||||
|
||||
return iss
|
||||
}
|
||||
|
||||
func (iss *InventoryScreenState) CollectDrawables() []render.Drawable {
|
||||
return append(
|
||||
iss.prevState.CollectDrawables(),
|
||||
iss.inventoryMenu,
|
||||
)
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type MainMenuState struct {
|
||||
|
@ -78,10 +77,14 @@ func (mms *MainMenuState) OnTick(dt int64) GameState {
|
|||
return mms
|
||||
}
|
||||
|
||||
func (mms *MainMenuState) OnDraw(c views.View) {
|
||||
mms.menuTitle.Draw(c)
|
||||
func (mms *MainMenuState) CollectDrawables() []render.Drawable {
|
||||
arr := make([]render.Drawable, 0)
|
||||
|
||||
arr = append(arr, mms.menuTitle)
|
||||
|
||||
for _, b := range mms.buttons {
|
||||
b.Draw(c)
|
||||
arr = append(arr, b)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type PauseGameState struct {
|
||||
|
@ -27,13 +26,13 @@ func PauseGame(prevState PausableState) *PauseGameState {
|
|||
|
||||
highlightStyle := tcell.StyleDefault.Attributes(tcell.AttrBold)
|
||||
|
||||
s.pauseMenuWindow = ui.CreateWindow(uint16(render.TERMINAL_SIZE_WIDTH)/2-15, uint16(render.TERMINAL_SIZE_HEIGHT)/2-7, 30, 14, "PAUSED", tcell.StyleDefault)
|
||||
s.pauseMenuWindow = ui.CreateWindow(int(render.TERMINAL_SIZE_WIDTH)/2-15, int(render.TERMINAL_SIZE_HEIGHT)/2-7, 30, 14, "PAUSED", tcell.StyleDefault)
|
||||
s.buttons = make([]*ui.UISimpleButton, 0)
|
||||
s.buttons = append(
|
||||
s.buttons,
|
||||
ui.CreateSimpleButton(
|
||||
uint16(s.pauseMenuWindow.Position().X())+3,
|
||||
uint16(s.pauseMenuWindow.Position().Y())+1,
|
||||
int(s.pauseMenuWindow.Position().X())+3,
|
||||
int(s.pauseMenuWindow.Position().Y())+1,
|
||||
"Resume",
|
||||
tcell.StyleDefault,
|
||||
highlightStyle,
|
||||
|
@ -45,8 +44,8 @@ func PauseGame(prevState PausableState) *PauseGameState {
|
|||
s.buttons = append(
|
||||
s.buttons,
|
||||
ui.CreateSimpleButton(
|
||||
uint16(s.pauseMenuWindow.Position().X())+3,
|
||||
uint16(s.pauseMenuWindow.Position().Y())+3,
|
||||
int(s.pauseMenuWindow.Position().X())+3,
|
||||
int(s.pauseMenuWindow.Position().Y())+3,
|
||||
"Exit To Main Menu",
|
||||
tcell.StyleDefault,
|
||||
highlightStyle,
|
||||
|
@ -97,12 +96,16 @@ func (pg *PauseGameState) OnTick(dt int64) GameState {
|
|||
return pg
|
||||
}
|
||||
|
||||
func (pg *PauseGameState) OnDraw(c views.View) {
|
||||
pg.prevState.OnDraw(c)
|
||||
func (pg *PauseGameState) CollectDrawables() []render.Drawable {
|
||||
arr := make([]render.Drawable, 0)
|
||||
|
||||
pg.pauseMenuWindow.Draw(c)
|
||||
arr = append(arr, pg.prevState.CollectDrawables()...)
|
||||
|
||||
arr = append(arr, pg.pauseMenuWindow)
|
||||
|
||||
for _, b := range pg.buttons {
|
||||
b.Draw(c)
|
||||
arr = append(arr, b)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/game/model"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type PlayingState struct {
|
||||
player *model.Player
|
||||
|
||||
pauseGame bool
|
||||
}
|
||||
|
||||
func BeginPlayingState() *PlayingState {
|
||||
s := new(PlayingState)
|
||||
|
||||
s.player = model.CreatePlayer(10, 10, tcell.StyleDefault)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (ps *PlayingState) Pause() {
|
||||
ps.pauseGame = true
|
||||
}
|
||||
|
||||
func (ps *PlayingState) Unpause() {
|
||||
ps.pauseGame = false
|
||||
}
|
||||
|
||||
func (ps *PlayingState) SetPaused(paused bool) {
|
||||
ps.pauseGame = paused
|
||||
}
|
||||
|
||||
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
||||
ps.player.Input(e)
|
||||
|
||||
if e.Key() == tcell.KeyEsc {
|
||||
ps.pauseGame = true
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PlayingState) OnTick(dt int64) GameState {
|
||||
ps.player.Tick(dt)
|
||||
|
||||
if ps.pauseGame {
|
||||
return PauseGame(ps)
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (ps *PlayingState) OnDraw(c views.View) {
|
||||
ps.player.Draw(c)
|
||||
}
|
144
game/state/playing_state.go
Normal file
144
game/state/playing_state.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/game/model"
|
||||
"mvvasilev/last_light/render"
|
||||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type PlayingState struct {
|
||||
player *model.Player
|
||||
level *model.MultilevelMap
|
||||
|
||||
viewport *render.Viewport
|
||||
|
||||
movePlayerDirection model.Direction
|
||||
pauseGame bool
|
||||
openInventory bool
|
||||
}
|
||||
|
||||
func BeginPlayingState() *PlayingState {
|
||||
s := new(PlayingState)
|
||||
|
||||
mapSize := util.SizeOf(128, 128)
|
||||
|
||||
s.player = model.CreatePlayer(40, 12)
|
||||
|
||||
s.level = model.CreateMultilevelMap(
|
||||
model.CreateFlatGroundDungeonLevel(mapSize.WH()),
|
||||
model.CreateEmptyDungeonLevel(mapSize.WH()),
|
||||
)
|
||||
|
||||
s.level.SetTileAtHeight(40, 12, 1, s.player)
|
||||
|
||||
s.viewport = render.CreateViewport(
|
||||
util.PositionAt(0, 0),
|
||||
util.PositionAt(40, 12),
|
||||
util.SizeOf(80, 24),
|
||||
tcell.StyleDefault,
|
||||
)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (ps *PlayingState) Pause() {
|
||||
ps.pauseGame = true
|
||||
}
|
||||
|
||||
func (ps *PlayingState) Unpause() {
|
||||
ps.pauseGame = false
|
||||
}
|
||||
|
||||
func (ps *PlayingState) SetPaused(paused bool) {
|
||||
ps.pauseGame = paused
|
||||
}
|
||||
|
||||
func (ps *PlayingState) MovePlayer() {
|
||||
if ps.movePlayerDirection == model.DirectionNone {
|
||||
return
|
||||
}
|
||||
|
||||
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(), 1, nil)
|
||||
ps.player.Move(ps.movePlayerDirection)
|
||||
ps.viewport.SetCenter(ps.player.Position())
|
||||
ps.level.SetTileAtHeight(ps.player.Position().X(), ps.player.Position().Y(), 1, ps.player)
|
||||
}
|
||||
|
||||
ps.movePlayerDirection = model.DirectionNone
|
||||
}
|
||||
|
||||
func (ps *PlayingState) OnInput(e *tcell.EventKey) {
|
||||
ps.player.Input(e)
|
||||
|
||||
if e.Key() == tcell.KeyEsc {
|
||||
ps.pauseGame = true
|
||||
return
|
||||
}
|
||||
|
||||
if e.Key() == tcell.KeyRune && e.Rune() == 'i' {
|
||||
ps.openInventory = true
|
||||
return
|
||||
}
|
||||
|
||||
switch e.Key() {
|
||||
case tcell.KeyUp:
|
||||
ps.movePlayerDirection = model.DirectionUp
|
||||
case tcell.KeyDown:
|
||||
ps.movePlayerDirection = model.DirectionDown
|
||||
case tcell.KeyLeft:
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *PlayingState) OnTick(dt int64) GameState {
|
||||
ps.player.Tick(dt)
|
||||
|
||||
if ps.pauseGame {
|
||||
return PauseGame(ps)
|
||||
}
|
||||
|
||||
if ps.openInventory {
|
||||
return CreateInventoryScreenState(ps.player, ps)
|
||||
}
|
||||
|
||||
if ps.movePlayerDirection != model.DirectionNone {
|
||||
ps.MovePlayer()
|
||||
}
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (ps *PlayingState) CollectDrawables() []render.Drawable {
|
||||
return render.Multidraw(render.CreateDrawingInstructions(func(v views.View) {
|
||||
ps.viewport.DrawFromProvider(v, func(x, y int) rune {
|
||||
tile := ps.level.TileAt(x, y)
|
||||
|
||||
if tile != nil {
|
||||
return tile.Presentation()
|
||||
}
|
||||
|
||||
return ' '
|
||||
})
|
||||
}))
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/render"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
|
||||
type QuitState struct {
|
||||
|
@ -16,6 +17,6 @@ func (q *QuitState) OnTick(dt int64) GameState {
|
|||
return q
|
||||
}
|
||||
|
||||
func (q *QuitState) OnDraw(c views.View) {
|
||||
|
||||
func (q *QuitState) CollectDrawables() []render.Drawable {
|
||||
return render.Multidraw(nil)
|
||||
}
|
51
main.go
51
main.go
|
@ -1,53 +1,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"mvvasilev/last_light/game"
|
||||
"mvvasilev/last_light/render"
|
||||
"os"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
)
|
||||
import "mvvasilev/last_light/game"
|
||||
|
||||
func main() {
|
||||
|
||||
c, err := render.CreateRenderContext()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("%~v", err)
|
||||
}
|
||||
|
||||
g := game.CreateGame()
|
||||
|
||||
c.HandleInput(func(ev *tcell.EventKey) {
|
||||
if ev.Key() == tcell.KeyCtrlC {
|
||||
c.Stop()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
g.Input(ev)
|
||||
})
|
||||
|
||||
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
|
||||
|
||||
c.HandleRender(func(view views.View, deltaTime int64) {
|
||||
fps := 1_000_000 / deltaTime
|
||||
|
||||
fpsText := render.CreateText(0, 0, 16, 1, fmt.Sprintf("%v FPS", fps), defStyle)
|
||||
|
||||
keepGoing := g.Tick(deltaTime)
|
||||
|
||||
if !keepGoing {
|
||||
c.Stop()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
g.Draw(view)
|
||||
|
||||
fpsText.Draw(view)
|
||||
})
|
||||
|
||||
c.BeginRendering()
|
||||
gc := game.CreateGameContext()
|
||||
gc.Run()
|
||||
}
|
||||
|
|
28
render/arbitrary_drawable.go
Normal file
28
render/arbitrary_drawable.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ArbitraryDrawable struct {
|
||||
id uuid.UUID
|
||||
drawingInstructions func(v views.View)
|
||||
}
|
||||
|
||||
func CreateDrawingInstructions(instructions func(v views.View)) *ArbitraryDrawable {
|
||||
a := new(ArbitraryDrawable)
|
||||
|
||||
a.id = uuid.New()
|
||||
a.drawingInstructions = instructions
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (ab *ArbitraryDrawable) UniqueId() uuid.UUID {
|
||||
return ab.id
|
||||
}
|
||||
|
||||
func (ab *ArbitraryDrawable) Draw(v views.View) {
|
||||
ab.drawingInstructions(v)
|
||||
}
|
|
@ -12,8 +12,8 @@ type Grid struct {
|
|||
id uuid.UUID
|
||||
|
||||
internalCellSize util.Size
|
||||
numCellsHorizontal uint16
|
||||
numCellsVertical uint16
|
||||
numCellsHorizontal int
|
||||
numCellsVertical int
|
||||
position util.Position
|
||||
style tcell.Style
|
||||
|
||||
|
@ -40,9 +40,9 @@ type Grid struct {
|
|||
}
|
||||
|
||||
func CreateSimpleGrid(
|
||||
x, y uint16,
|
||||
cellWidth, cellHeight uint16,
|
||||
numCellsHorizontal, numCellsVertical uint16,
|
||||
x, y int,
|
||||
cellWidth, cellHeight int,
|
||||
numCellsHorizontal, numCellsVertical int,
|
||||
borderRune, fillRune rune,
|
||||
style tcell.Style,
|
||||
) Grid {
|
||||
|
@ -61,12 +61,12 @@ func CreateSimpleGrid(
|
|||
// '├', '─', '┼', '┤',
|
||||
// '└', '─', '┴', '┘',
|
||||
func CreateGrid(
|
||||
x uint16,
|
||||
y uint16,
|
||||
cellWidth uint16,
|
||||
cellHeight uint16,
|
||||
numCellsHorizontal uint16,
|
||||
numCellsVertical uint16,
|
||||
x int,
|
||||
y int,
|
||||
cellWidth int,
|
||||
cellHeight int,
|
||||
numCellsHorizontal int,
|
||||
numCellsVertical int,
|
||||
nwCorner, northBorder, verticalDownwardsTJunction, neCorner,
|
||||
westBorder, fillRune, internalVerticalBorder, eastBorder,
|
||||
horizontalRightTJunction, internalHorizontalBorder, crossJunction, horizontalLeftTJunction,
|
||||
|
|
|
@ -15,7 +15,7 @@ type Raw struct {
|
|||
style tcell.Style
|
||||
}
|
||||
|
||||
func CreateRawDrawable(x, y uint16, style tcell.Style, buffer ...string) *Raw {
|
||||
func CreateRawDrawable(x, y int, style tcell.Style, buffer ...string) *Raw {
|
||||
r := new(Raw)
|
||||
|
||||
r.position = util.PositionAt(x, y)
|
||||
|
@ -30,10 +30,43 @@ func CreateRawDrawable(x, y uint16, style tcell.Style, buffer ...string) *Raw {
|
|||
return r
|
||||
}
|
||||
|
||||
func CreateRawDrawableFromBuffer(x, y int, style tcell.Style, buffer [][]rune) *Raw {
|
||||
r := new(Raw)
|
||||
|
||||
r.position = util.PositionAt(x, y)
|
||||
r.buffer = buffer
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Raw) UniqueId() uuid.UUID {
|
||||
return r.id
|
||||
}
|
||||
|
||||
func (r *Raw) DrawWithin(screenX, screenY, originX, originY, width, height int, v views.View) {
|
||||
for h := originY; h < originY+height; h++ {
|
||||
|
||||
if h < 0 || h >= len(r.buffer) {
|
||||
screenY += 1
|
||||
continue
|
||||
}
|
||||
|
||||
for w := originX; w < originX+width; w++ {
|
||||
if w < 0 || w >= len(r.buffer[h]) {
|
||||
screenX += 1
|
||||
continue
|
||||
}
|
||||
|
||||
v.SetContent(screenX, screenY, r.buffer[h][w], nil, r.style)
|
||||
|
||||
screenX += 1
|
||||
}
|
||||
|
||||
screenX = 0
|
||||
screenY += 1
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Raw) Draw(v views.View) {
|
||||
x := r.position.X()
|
||||
y := r.position.Y()
|
||||
|
|
|
@ -31,7 +31,7 @@ type Rectangle struct {
|
|||
fillRune rune
|
||||
}
|
||||
|
||||
func CreateBorderlessRectangle(x, y uint16, width, height uint16, fillRune rune, style tcell.Style) Rectangle {
|
||||
func CreateBorderlessRectangle(x, y int, width, height int, fillRune rune, style tcell.Style) Rectangle {
|
||||
return CreateRectangle(
|
||||
x, y, width, height,
|
||||
0, 0, 0,
|
||||
|
@ -41,7 +41,7 @@ func CreateBorderlessRectangle(x, y uint16, width, height uint16, fillRune rune,
|
|||
)
|
||||
}
|
||||
|
||||
func CreateSimpleEmptyRectangle(x, y uint16, width, height uint16, borderRune rune, style tcell.Style) Rectangle {
|
||||
func CreateSimpleEmptyRectangle(x, y int, width, height int, borderRune rune, style tcell.Style) Rectangle {
|
||||
return CreateRectangle(
|
||||
x, y, width, height,
|
||||
borderRune, borderRune, borderRune,
|
||||
|
@ -51,7 +51,7 @@ func CreateSimpleEmptyRectangle(x, y uint16, width, height uint16, borderRune ru
|
|||
)
|
||||
}
|
||||
|
||||
func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, borderRune rune, fillRune rune, style tcell.Style) Rectangle {
|
||||
func CreateSimpleRectangle(x int, y int, width int, height int, borderRune rune, fillRune rune, style tcell.Style) Rectangle {
|
||||
return CreateRectangle(
|
||||
x, y, width, height,
|
||||
borderRune, borderRune, borderRune,
|
||||
|
@ -62,7 +62,7 @@ func CreateSimpleRectangle(x uint16, y uint16, width uint16, height uint16, bord
|
|||
}
|
||||
|
||||
func CreateRectangleV2(
|
||||
x, y uint16, width, height uint16,
|
||||
x, y int, width, height int,
|
||||
upper, middle, lower string,
|
||||
isBorderless, isFilled bool,
|
||||
style tcell.Style,
|
||||
|
@ -91,10 +91,10 @@ func CreateRectangleV2(
|
|||
//
|
||||
// )
|
||||
func CreateRectangle(
|
||||
x uint16,
|
||||
y uint16,
|
||||
width uint16,
|
||||
height uint16,
|
||||
x int,
|
||||
y int,
|
||||
width int,
|
||||
height int,
|
||||
nwCorner, northBorder, neCorner,
|
||||
westBorder, fillRune, eastBorder,
|
||||
swCorner, southBorder, seCorner rune,
|
||||
|
|
|
@ -2,8 +2,8 @@ package render
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
|
@ -22,39 +22,51 @@ type Drawable interface {
|
|||
Draw(v views.View)
|
||||
}
|
||||
|
||||
func Multidraw(drawables ...Drawable) []Drawable {
|
||||
arr := make([]Drawable, 0)
|
||||
|
||||
if drawables == nil {
|
||||
return arr
|
||||
}
|
||||
|
||||
for _, d := range drawables {
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
arr = append(arr, d)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
type RenderContext struct {
|
||||
screen tcell.Screen
|
||||
view *views.ViewPort
|
||||
defaultStyle tcell.Style
|
||||
|
||||
events chan tcell.Event
|
||||
quit chan struct{}
|
||||
|
||||
lastRenderTime time.Time
|
||||
|
||||
renderHandler func(view views.View, deltaTime int64)
|
||||
inputHandler func(ev *tcell.EventKey)
|
||||
drawables chan Drawable
|
||||
}
|
||||
|
||||
func CreateRenderContext() (*RenderContext, error) {
|
||||
s, err := tcell.NewScreen()
|
||||
screen, sErr := tcell.NewScreen()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
if sErr != nil {
|
||||
log.Fatalf("%~v", sErr)
|
||||
}
|
||||
|
||||
stopScreen := func() {
|
||||
s.Fini()
|
||||
screen.Fini()
|
||||
}
|
||||
|
||||
if err := s.Init(); err != nil {
|
||||
if err := screen.Init(); err != nil {
|
||||
stopScreen()
|
||||
log.Fatal(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
width, height := s.Size()
|
||||
width, height := screen.Size()
|
||||
|
||||
if width < TERMINAL_SIZE_WIDTH || height < TERMINAL_SIZE_HEIGHT {
|
||||
stopScreen()
|
||||
|
@ -63,27 +75,25 @@ func CreateRenderContext() (*RenderContext, error) {
|
|||
}
|
||||
|
||||
view := views.NewViewPort(
|
||||
s,
|
||||
screen,
|
||||
(width/2)-(TERMINAL_SIZE_WIDTH/2),
|
||||
(height/2)-(TERMINAL_SIZE_HEIGHT/2),
|
||||
TERMINAL_SIZE_WIDTH,
|
||||
TERMINAL_SIZE_HEIGHT,
|
||||
)
|
||||
|
||||
defStyle := tcell.StyleDefault.Background(DEFAULT_STYLE_BACKGROUND).Foreground(DEFAULT_STYLE_FOREGROUND)
|
||||
|
||||
events := make(chan tcell.Event)
|
||||
quit := make(chan struct{})
|
||||
|
||||
go s.ChannelEvents(events, quit)
|
||||
go screen.ChannelEvents(events, quit)
|
||||
|
||||
context := new(RenderContext)
|
||||
|
||||
context.screen = s
|
||||
context.defaultStyle = defStyle
|
||||
context.screen = screen
|
||||
context.events = events
|
||||
context.quit = quit
|
||||
context.view = view
|
||||
context.drawables = make(chan Drawable)
|
||||
|
||||
return context, nil
|
||||
}
|
||||
|
@ -92,12 +102,31 @@ func (c *RenderContext) Stop() {
|
|||
c.screen.Fini()
|
||||
}
|
||||
|
||||
func (c *RenderContext) HandleRender(renderHandler func(view views.View, deltaTime int64)) {
|
||||
c.renderHandler = renderHandler
|
||||
func (c *RenderContext) CollectInputEvents() []*tcell.EventKey {
|
||||
events := make([]tcell.Event, len(c.events))
|
||||
|
||||
select {
|
||||
case e := <-c.events:
|
||||
events = append(events, e)
|
||||
default:
|
||||
}
|
||||
|
||||
func (c *RenderContext) HandleInput(inputHandler func(ev *tcell.EventKey)) {
|
||||
c.inputHandler = inputHandler
|
||||
inputEvents := make([]*tcell.EventKey, 0, len(events))
|
||||
|
||||
for _, e := range events {
|
||||
switch ev := e.(type) {
|
||||
case *tcell.EventKey:
|
||||
inputEvents = append(inputEvents, ev)
|
||||
case *tcell.EventResize:
|
||||
c.onResize(ev)
|
||||
}
|
||||
}
|
||||
|
||||
return inputEvents
|
||||
}
|
||||
|
||||
func (c *RenderContext) DrawableQueue() chan Drawable {
|
||||
return c.drawables
|
||||
}
|
||||
|
||||
func (c *RenderContext) onResize(ev *tcell.EventResize) {
|
||||
|
@ -115,33 +144,18 @@ func (c *RenderContext) onResize(ev *tcell.EventResize) {
|
|||
c.screen.Sync()
|
||||
}
|
||||
|
||||
func (c *RenderContext) BeginRendering() {
|
||||
c.lastRenderTime = time.Now()
|
||||
func (c *RenderContext) Draw(deltaTime int64, drawables []Drawable) {
|
||||
fps := 1_000_000 / deltaTime
|
||||
|
||||
for {
|
||||
deltaTime := 1 + time.Since(c.lastRenderTime).Microseconds()
|
||||
c.lastRenderTime = time.Now()
|
||||
c.view.Clear()
|
||||
|
||||
c.screen.Clear()
|
||||
fpsText := CreateText(0, 0, 16, 1, fmt.Sprintf("%v FPS", fps), tcell.StyleDefault)
|
||||
|
||||
c.renderHandler(c.view, deltaTime)
|
||||
for _, d := range drawables {
|
||||
d.Draw(c.view)
|
||||
}
|
||||
|
||||
fpsText.Draw(c.view)
|
||||
|
||||
c.screen.Show()
|
||||
|
||||
select {
|
||||
case ev, ok := <-c.events:
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventResize:
|
||||
c.onResize(ev)
|
||||
case *tcell.EventKey:
|
||||
c.inputHandler(ev)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,8 +19,8 @@ type Text struct {
|
|||
}
|
||||
|
||||
func CreateText(
|
||||
x, y uint16,
|
||||
width, height uint16,
|
||||
x, y int,
|
||||
width, height int,
|
||||
content string,
|
||||
style tcell.Style,
|
||||
) *Text {
|
||||
|
|
96
render/viewport.go
Normal file
96
render/viewport.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Viewport struct {
|
||||
id uuid.UUID
|
||||
screenLocation util.Position
|
||||
|
||||
viewportCenter util.Position
|
||||
viewportSize util.Size
|
||||
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
func CreateViewport(screenLoc, viewportCenter util.Position, size util.Size, style tcell.Style) *Viewport {
|
||||
v := new(Viewport)
|
||||
|
||||
v.id = uuid.New()
|
||||
v.screenLocation = screenLoc
|
||||
v.viewportCenter = viewportCenter
|
||||
v.viewportSize = size
|
||||
v.style = style
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (vp *Viewport) UniqueId() uuid.UUID {
|
||||
return vp.id
|
||||
}
|
||||
|
||||
func (vp *Viewport) Center() util.Position {
|
||||
return vp.viewportCenter
|
||||
}
|
||||
|
||||
func (vp *Viewport) SetCenter(pos util.Position) {
|
||||
vp.viewportCenter = pos
|
||||
}
|
||||
|
||||
func (vp *Viewport) Size() util.Size {
|
||||
return vp.viewportSize
|
||||
}
|
||||
|
||||
func (vp *Viewport) ScreenLocation() util.Position {
|
||||
return vp.screenLocation
|
||||
}
|
||||
|
||||
func (vp *Viewport) DrawFromProvider(v views.View, provider func(x, y int) rune) {
|
||||
width, height := vp.viewportSize.WH()
|
||||
originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY()
|
||||
screenX, screenY := vp.screenLocation.XY()
|
||||
|
||||
for h := originY; h < originY+height; h++ {
|
||||
for w := originX; w < originX+width; w++ {
|
||||
v.SetContent(screenX, screenY, provider(w, h), nil, vp.style)
|
||||
|
||||
screenX += 1
|
||||
}
|
||||
|
||||
screenX = 0
|
||||
screenY += 1
|
||||
}
|
||||
}
|
||||
|
||||
func (vp *Viewport) Draw(v views.View, buffer [][]rune) {
|
||||
width, height := vp.viewportSize.WH()
|
||||
originX, originY := vp.viewportCenter.WithOffset(-width/2, -height/2).XY()
|
||||
screenX, screenY := vp.screenLocation.XY()
|
||||
|
||||
for h := originY; h < originY+height; h++ {
|
||||
|
||||
if h < 0 || h >= len(buffer) {
|
||||
screenY += 1
|
||||
continue
|
||||
}
|
||||
|
||||
for w := originX; w < originX+width; w++ {
|
||||
if w < 0 || w >= len(buffer[h]) {
|
||||
screenX += 1
|
||||
continue
|
||||
}
|
||||
|
||||
v.SetContent(screenX, screenY, buffer[h][w], nil, vp.style)
|
||||
|
||||
screenX += 1
|
||||
}
|
||||
|
||||
screenX = 0
|
||||
screenY += 1
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ func (b *UIBorderedButton) UniqueId() uuid.UUID {
|
|||
return b.id
|
||||
}
|
||||
|
||||
func (b *UIBorderedButton) MoveTo(x uint16, y uint16) {
|
||||
func (b *UIBorderedButton) MoveTo(x int, y int) {
|
||||
panic("not implemented") // TODO: Implement
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ type UIContainer struct {
|
|||
elements []UIElement
|
||||
}
|
||||
|
||||
func CreateUIContainer(x, y uint16, width, height uint16, layout UIContainerLayout) *UIContainer {
|
||||
func CreateUIContainer(x, y int, width, height int, layout UIContainerLayout) *UIContainer {
|
||||
container := new(UIContainer)
|
||||
|
||||
container.id = uuid.New()
|
||||
|
@ -57,7 +57,7 @@ func (uic *UIContainer) UniqueId() uuid.UUID {
|
|||
return uic.id
|
||||
}
|
||||
|
||||
func (uic *UIContainer) MoveTo(x, y uint16) {
|
||||
func (uic *UIContainer) MoveTo(x, y int) {
|
||||
uic.position = util.PositionAt(x, y)
|
||||
}
|
||||
|
||||
|
|
10
ui/label.go
10
ui/label.go
|
@ -15,7 +15,7 @@ type UILabel struct {
|
|||
text *render.Text
|
||||
}
|
||||
|
||||
func CreateUILabel(x, y uint16, width, height uint16, content string, style tcell.Style) *UILabel {
|
||||
func CreateUILabel(x, y int, width, height int, content string, style tcell.Style) *UILabel {
|
||||
label := new(UILabel)
|
||||
|
||||
label.id = uuid.New()
|
||||
|
@ -24,11 +24,11 @@ func CreateUILabel(x, y uint16, width, height uint16, content string, style tcel
|
|||
return label
|
||||
}
|
||||
|
||||
func CreateSingleLineUILabel(x, y uint16, content string, style tcell.Style) *UILabel {
|
||||
func CreateSingleLineUILabel(x, y int, content string, style tcell.Style) *UILabel {
|
||||
label := new(UILabel)
|
||||
|
||||
label.id = uuid.New()
|
||||
label.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(content)), 1, content, style)
|
||||
label.text = render.CreateText(x, y, int(utf8.RuneCountInString(content)), 1, content, style)
|
||||
|
||||
return label
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ func (t *UILabel) UniqueId() uuid.UUID {
|
|||
return t.id
|
||||
}
|
||||
|
||||
func (t *UILabel) MoveTo(x uint16, y uint16) {
|
||||
t.text = render.CreateText(x, y, uint16(t.text.Size().Width()), uint16(t.Size().Height()), t.text.Content(), t.text.Style())
|
||||
func (t *UILabel) MoveTo(x int, y int) {
|
||||
t.text = render.CreateText(x, y, int(t.text.Size().Width()), int(t.Size().Height()), t.text.Content(), t.text.Style())
|
||||
}
|
||||
|
||||
func (t *UILabel) Position() util.Position {
|
||||
|
|
|
@ -20,11 +20,11 @@ type UISimpleButton struct {
|
|||
highlightedStyle tcell.Style
|
||||
}
|
||||
|
||||
func CreateSimpleButton(x, y uint16, text string, unhighlightedStyle, highlightedStyle tcell.Style, onSelect func()) *UISimpleButton {
|
||||
func CreateSimpleButton(x, y int, text string, unhighlightedStyle, highlightedStyle tcell.Style, onSelect func()) *UISimpleButton {
|
||||
sb := new(UISimpleButton)
|
||||
|
||||
sb.id = uuid.New()
|
||||
sb.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(text)), 1, text, unhighlightedStyle)
|
||||
sb.text = render.CreateText(x, y, int(utf8.RuneCountInString(text)), 1, text, unhighlightedStyle)
|
||||
sb.isHighlighted = false
|
||||
sb.selectHandler = onSelect
|
||||
sb.highlightedStyle = highlightedStyle
|
||||
|
@ -51,8 +51,8 @@ func (sb *UISimpleButton) Highlight() {
|
|||
newContent := "[ " + sb.text.Content() + " ]"
|
||||
|
||||
sb.text = render.CreateText(
|
||||
uint16(sb.Position().X()-2), uint16(sb.Position().Y()),
|
||||
uint16(utf8.RuneCountInString(newContent)), 1,
|
||||
int(sb.Position().X()-2), int(sb.Position().Y()),
|
||||
int(utf8.RuneCountInString(newContent)), 1,
|
||||
newContent,
|
||||
sb.highlightedStyle,
|
||||
)
|
||||
|
@ -66,8 +66,8 @@ func (sb *UISimpleButton) Unhighlight() {
|
|||
contentLen := utf8.RuneCountInString(content)
|
||||
|
||||
sb.text = render.CreateText(
|
||||
uint16(sb.Position().X()+2), uint16(sb.Position().Y()),
|
||||
uint16(contentLen), 1,
|
||||
int(sb.Position().X()+2), int(sb.Position().Y()),
|
||||
int(contentLen), 1,
|
||||
content,
|
||||
sb.unhighlightedStyle,
|
||||
)
|
||||
|
@ -81,8 +81,8 @@ func (sb *UISimpleButton) UniqueId() uuid.UUID {
|
|||
return sb.id
|
||||
}
|
||||
|
||||
func (sb *UISimpleButton) MoveTo(x uint16, y uint16) {
|
||||
sb.text = render.CreateText(x, y, uint16(utf8.RuneCountInString(sb.text.Content())), 1, sb.text.Content(), sb.highlightedStyle)
|
||||
func (sb *UISimpleButton) MoveTo(x int, y int) {
|
||||
sb.text = render.CreateText(x, y, int(utf8.RuneCountInString(sb.text.Content())), 1, sb.text.Content(), sb.highlightedStyle)
|
||||
}
|
||||
|
||||
func (sb *UISimpleButton) Position() util.Position {
|
9
ui/ui.go
9
ui/ui.go
|
@ -1,20 +1,19 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"mvvasilev/last_light/render"
|
||||
"mvvasilev/last_light/util"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/gdamore/tcell/v2/views"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type UIElement interface {
|
||||
UniqueId() uuid.UUID
|
||||
MoveTo(x, y uint16)
|
||||
MoveTo(x, y int)
|
||||
Position() util.Position
|
||||
Size() util.Size
|
||||
Draw(v views.View)
|
||||
Input(e *tcell.EventKey)
|
||||
|
||||
render.Drawable
|
||||
}
|
||||
|
||||
type UIHighlightableElement interface {
|
||||
|
|
|
@ -17,14 +17,14 @@ type UIWindow struct {
|
|||
box render.Rectangle
|
||||
}
|
||||
|
||||
func CreateWindow(x, y, width, height uint16, title string, style tcell.Style) *UIWindow {
|
||||
func CreateWindow(x, y, width, height int, title string, style tcell.Style) *UIWindow {
|
||||
w := new(UIWindow)
|
||||
|
||||
titleLen := utf8.RuneCountInString(title)
|
||||
|
||||
titlePos := (width / 2) - uint16(titleLen/2)
|
||||
titlePos := (width / 2) - int(titleLen/2)
|
||||
|
||||
w.title = render.CreateText(x+titlePos, y, uint16(titleLen), 1, title, style)
|
||||
w.title = render.CreateText(x+titlePos, y, int(titleLen), 1, title, style)
|
||||
|
||||
w.box = render.CreateRectangle(
|
||||
x, y, width, height,
|
||||
|
@ -41,7 +41,7 @@ func (w *UIWindow) UniqueId() uuid.UUID {
|
|||
return w.id
|
||||
}
|
||||
|
||||
func (w *UIWindow) MoveTo(x uint16, y uint16) {
|
||||
func (w *UIWindow) MoveTo(x int, y int) {
|
||||
|
||||
}
|
||||
|
||||
|
|
18
util/util.go
18
util/util.go
|
@ -5,7 +5,7 @@ type Position struct {
|
|||
y int
|
||||
}
|
||||
|
||||
func PositionAt(x uint16, y uint16) Position {
|
||||
func PositionAt(x int, y int) Position {
|
||||
return Position{int(x), int(y)}
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,10 @@ func (p Position) XY() (int, int) {
|
|||
return p.x, p.y
|
||||
}
|
||||
|
||||
func (p Position) XYUint16() (uint16, uint16) {
|
||||
return uint16(p.x), uint16(p.y)
|
||||
func (p Position) WithOffset(xOffset int, yOffset int) Position {
|
||||
p.x = p.x + xOffset
|
||||
p.y = p.y + yOffset
|
||||
return p
|
||||
}
|
||||
|
||||
type Size struct {
|
||||
|
@ -30,10 +32,14 @@ type Size struct {
|
|||
height int
|
||||
}
|
||||
|
||||
func SizeOf(width uint16, height uint16) Size {
|
||||
func SizeOf(width int, height int) Size {
|
||||
return Size{int(width), int(height)}
|
||||
}
|
||||
|
||||
func SizeOfInt(width int, height int) Size {
|
||||
return Size{width, height}
|
||||
}
|
||||
|
||||
func (s Size) Width() int {
|
||||
return s.width
|
||||
}
|
||||
|
@ -42,8 +48,8 @@ func (s Size) Height() int {
|
|||
return s.height
|
||||
}
|
||||
|
||||
func (s Size) WHUint16() (uint16, uint16) {
|
||||
return uint16(s.width), uint16(s.height)
|
||||
func (s Size) WH() (int, int) {
|
||||
return s.width, s.height
|
||||
}
|
||||
|
||||
func LimitIncrement(i int, limit int) int {
|
||||
|
|
Loading…
Add table
Reference in a new issue