diff --git a/cmd/game/main.go b/cmd/game/main.go index a908b48..500b934 100644 --- a/cmd/game/main.go +++ b/cmd/game/main.go @@ -5,6 +5,7 @@ import ( "github.com/rs/zerolog/log" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens" + "lab.zaar.be/thefish/alchemyst-go/engine/types" "lab.zaar.be/thefish/alchemyst-go/ui" "lab.zaar.be/thefish/alchemyst-go/ui/mainwindow" "lab.zaar.be/thefish/alchemyst-go/util" @@ -22,23 +23,6 @@ func init() { runtime.LockOSThread() } -type GameState struct { - mainfunc chan func() - exit chan struct{} - input chan string - rawInput chan int -} - -// do runs f on the main thread. -func (*GameState) Do(f func()) { - done := make(chan struct{}, 1) - State.mainfunc <- func() { - f() - done <- struct{}{} - } - <-done -} - //we can run logic in separate goroutines // // go doSometing(State,...) @@ -52,11 +36,13 @@ func (*GameState) Do(f func()) { // ... // } -var State = GameState{ - mainfunc: make(chan func()), - exit: make(chan struct{}, 1), - input: make(chan string, 1), - rawInput: make(chan int, 1), +var State = types.GameState{ + Mainfunc: make(chan func()), + Exit: make(chan struct{}, 1), + Input: make(chan string, 1), + RawInput: make(chan int, 1), + FovRecompute: make(chan struct{},1), + Redraw: make(chan struct{},1), } func main() { @@ -72,35 +58,37 @@ func main() { setupLayers(mw) - level := gamemap.NewLevel(mainCtx, "test", 1) - level = mapgens.DefaultGen(level) + level, rooms := mapgens.DefaultGen(gamemap.NewLevel(mainCtx, "test", 1)) + vp := mainwindow.NewViewPort(40, 0, 60, 47, level, mw.GetLayer("base")) - vp.Render() + vp.PlayerCoords = rooms[0].Center + vp.Render(State) go decodeInput(mainCtx, mw.GetLayer("base")) + go vp.Listen(State) //but every call to bearlibterminal must be wrapped to closure and passed to mainfunc var exit = false for !exit { select { - case State.rawInput <- ui.ReadKeyCode(): + case State.RawInput <- ui.ReadKeyCode(): break - case pressed := <-State.input: + case pressed := <-State.Input: mw.GetLayer("base").ClearArea(0, 3, 40, 1) mw.GetLayer("base").Print(1, 3, "Key: "+pressed) mw.GetLayer("base").Print(1, 6, "█") break - //case f := <-State.mainfunc: - // f() - // break - case <-State.exit: + //case f := <-State.mainfunc: + // f() + // break + case <-State.Exit: mainCtx.Logger().Warn().Msg("quitting NOW") exit = true break - // не оставляйте default в бесконесчном select {} - сожрет всё CPU + // не оставляйте default в бесконесчном select {} - сожрет всё CPU default: - vp.Render() + vp.Render(State) blt.Refresh() } @@ -117,20 +105,20 @@ func setupLayers(mainwindow *mainwindow.MainWindow) { func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) { var exit = false var waitForWCspam = true - for !exit{ + for !exit { select { - case keycode := <-State.rawInput: + case keycode := <-State.RawInput: if keycode == blt.TK_NONE { continue } if keycode == blt.TK_CLOSE && !waitForWCspam { ctx.Logger().Warn().Msg("exiting on window close...") - State.exit <- struct{}{} + State.Exit <- struct{}{} ctx.Logger().Warn().Msg("...done") return } - var pressed= "" - var isModifier, _= util.InArray(keycode, modifiers) + var pressed = "" + var isModifier, _ = util.InArray(keycode, modifiers) if !isModifier { pressed = ui.Scancodemap[keycode] @@ -147,6 +135,7 @@ func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) { //global hotkeys switch pressed { + //fixme testing only case "F10": State.Do(func() { blt.Set("window: size=100x47; font: ./resources/fonts-ttf/UbuntuMono-R.ttf, size=11;") @@ -155,13 +144,15 @@ func decodeInput(ctx util.ClientCtx, baseLayer *mainwindow.Layer) { fallthrough case "Escape": ctx.Logger().Info().Msg("exiting on quit command...") - State.exit <- struct{}{} + State.Exit <- struct{}{} ctx.Logger().Info().Msg("...done") exit = true return default: - waitForWCspam = false; - State.input <- pressed + if pressed != "" { + waitForWCspam = false; + State.Input <- pressed + } } } } diff --git a/config.json b/config.json index a7d5be7..9ae5595 100644 --- a/config.json +++ b/config.json @@ -4,6 +4,7 @@ "sizeX": 100, "sizeY": 47, "fpsLimit" : 60, - "font": "./resources/fonts-ttf/LiberationMono-Bold.ttf", + "font": "./resources/fonts-ttf/UbuntuMono-R.ttf", + "fontSize": "12x16", "verbosity": "debug" } \ No newline at end of file diff --git a/engine/gamemap/level.go b/engine/gamemap/level.go index 34b8c16..bb749c3 100644 --- a/engine/gamemap/level.go +++ b/engine/gamemap/level.go @@ -7,8 +7,8 @@ import ( ) //fixme move to config -var mapWidth = 150 -var mapHeight = 100 +var mapWidth = 70 +var mapHeight = 50 type Level struct { diff --git a/engine/gamemap/mapgens/default.go b/engine/gamemap/mapgens/default.go index 67fd62e..2a6bc3f 100644 --- a/engine/gamemap/mapgens/default.go +++ b/engine/gamemap/mapgens/default.go @@ -11,7 +11,7 @@ var maxRoomSize = 22 var maxrooms = 30 //fixme make closure to stack them -func DefaultGen(l *gamemap.Level) *gamemap.Level { +func DefaultGen(l *gamemap.Level) (*gamemap.Level, []*gamemap.Room) { rng := util.NewRNG() @@ -56,6 +56,9 @@ func DefaultGen(l *gamemap.Level) *gamemap.Level { if !failed { rooms = append(rooms, newRoom) } + + //addStairs(rooms) + //itemize(rooms) } //fillage := types.RectFill{ @@ -89,7 +92,7 @@ func DefaultGen(l *gamemap.Level) *gamemap.Level { } } - return l + return l, rooms } func connectRooms (l *gamemap.Level, room, otherRoom *gamemap.Room, fillage types.RectFill, toss int) { diff --git a/engine/gamemap/tile.go b/engine/gamemap/tile.go index 4294dc6..ae60388 100644 --- a/engine/gamemap/tile.go +++ b/engine/gamemap/tile.go @@ -1,69 +1,8 @@ package gamemap import ( - "github.com/gammazero/deque" - "lab.zaar.be/thefish/alchemyst-go/util" + . "lab.zaar.be/thefish/alchemyst-go/engine/types" ) -import blt "lab.zaar.be/thefish/bearlibterminal" - -type ColorHolder interface { - GetColor() uint32 -} - -type cdeque struct { - deque.Deque -} - -func (c *cdeque) Next() uint8 { - c.Rotate(1) - return c.Front().(uint8) -} - -type DanceColorHolder struct { - A uint8 - R *cdeque - G *cdeque - B *cdeque -} - -func (chd *DanceColorHolder) GetColor() uint32 { - return blt.ColorFromARGB( - chd.A, - chd.R.Next(), - chd.G.Next(), - chd.B.Next(), - ) -} - -type PlainColorHolder struct { - A uint8 - R uint8 - G uint8 - B uint8 -} - -func (chb *PlainColorHolder) GetColor() uint32 { - return blt.ColorFromARGB( - chb.A, - chb.R, - chb.G, - chb.B, - ) -} - -type TileColorSet struct { - Fg ColorHolder - Bg ColorHolder - DarkFg ColorHolder - DarkBg ColorHolder -} - -type Appearance struct { - Char string `json:"char"` - ColorSet *TileColorSet `json:"colorSet"` -} - -var crng = util.NewRNG() type Tile struct { *Appearance `json:"app"` @@ -97,33 +36,6 @@ func (t *Tile) GetRawBgColor() uint32 { } } -func singleColorRing(colorValue uint8) *cdeque { - c := &cdeque{} - c.PushBack(colorValue) - return c -} - -func fillColorRing(colorValue uint8, minGlow, maxGlow, step int) *cdeque { - q := make([]uint8, 0) - color := int(colorValue) - for color < maxGlow { - q = append(q, uint8(color)) - color = crng.Range(1, step) + color - } - color = crng.Range(0, step+minGlow) - q = append(q, uint8(color)) - //for uint8(color) < uint8(colorValue) { - // q = append(q, uint8(color)) - // color = crng.Range(1, step+minGlow) - //} - - c := &cdeque{} - for _, v := range q { - c.PushBack(uint8(v)) - } - return c -} - func NewWall() *Tile { return &Tile{ Name: "Wall", @@ -178,14 +90,14 @@ func NewWaterTile() *Tile { Appearance: &Appearance{ Char: " ", ColorSet: &TileColorSet{ - Fg: &PlainColorHolder{255, 220, 220, 250}, + Fg: &PlainColorHolder{255, 220, 220, 250}, Bg: &DanceColorHolder{ 255, - singleColorRing(19), - fillColorRing(19, 0, 15, 2), - fillColorRing(70, 120, 220, 12), + SingleColorRing(19), + FillColorRing(19, 0, 15, 2), + FillColorRing(70, 120, 220, 12), }, - DarkFg: &PlainColorHolder{255, 30, 20, 50 }, + DarkFg: &PlainColorHolder{255, 30, 20, 50}, DarkBg: &PlainColorHolder{255, 7, 7, 30}, }, }, @@ -205,16 +117,16 @@ func NewDeepWaterTile() *Tile { Appearance: &Appearance{ Char: " ", ColorSet: &TileColorSet{ - Fg: &PlainColorHolder{255, 220, 220, 250}, + Fg: &PlainColorHolder{255, 220, 220, 250}, Bg: &DanceColorHolder{ 255, - singleColorRing(5), - fillColorRing(2,2,42,4), - fillColorRing(154, 150, 229, 12), + SingleColorRing(5), + FillColorRing(2, 2, 42, 4), + FillColorRing(154, 150, 229, 12), }, DarkFg: &PlainColorHolder{255, 30, 20, 50}, DarkBg: &PlainColorHolder{255, 7, 7, 30}, }, }, } -} \ No newline at end of file +} diff --git a/engine/types/appearance.go b/engine/types/appearance.go new file mode 100644 index 0000000..d09ded5 --- /dev/null +++ b/engine/types/appearance.go @@ -0,0 +1,94 @@ +package types + +import ( + "github.com/gammazero/deque" + "lab.zaar.be/thefish/alchemyst-go/util" +) +import blt "lab.zaar.be/thefish/bearlibterminal" + +var crng = util.NewRNG() + +type ColorHolder interface { + GetColor() uint32 +} + +type cdeque struct { + deque.Deque +} + +func (c *cdeque) Next() uint8 { + c.Rotate(1) + return c.Front().(uint8) +} + +type DanceColorHolder struct { + A uint8 + R *cdeque + G *cdeque + B *cdeque +} + +func (chd *DanceColorHolder) GetColor() uint32 { + return blt.ColorFromARGB( + chd.A, + chd.R.Next(), + chd.G.Next(), + chd.B.Next(), + ) +} + +type PlainColorHolder struct { + A uint8 + R uint8 + G uint8 + B uint8 +} + +func (chb *PlainColorHolder) GetColor() uint32 { + return blt.ColorFromARGB( + chb.A, + chb.R, + chb.G, + chb.B, + ) +} + +type TileColorSet struct { + Fg ColorHolder + Bg ColorHolder + DarkFg ColorHolder + DarkBg ColorHolder +} + +type Appearance struct { + Char string `json:"char"` + ColorSet *TileColorSet `json:"colorSet"` +} + + +func SingleColorRing(colorValue uint8) *cdeque { + c := &cdeque{} + c.PushBack(colorValue) + return c +} + +func FillColorRing(colorValue uint8, minGlow, maxGlow, step int) *cdeque { + q := make([]uint8, 0) + color := int(colorValue) + for color < maxGlow { + q = append(q, uint8(color)) + color = crng.Range(1, step) + color + } + color = crng.Range(0, step+minGlow) + q = append(q, uint8(color)) + //for uint8(color) < uint8(colorValue) { + // q = append(q, uint8(color)) + // color = crng.Range(1, step+minGlow) + //} + + c := &cdeque{} + for _, v := range q { + c.PushBack(uint8(v)) + } + return c +} \ No newline at end of file diff --git a/engine/types/gamestate.go b/engine/types/gamestate.go new file mode 100644 index 0000000..8ae529e --- /dev/null +++ b/engine/types/gamestate.go @@ -0,0 +1,20 @@ +package types + +type GameState struct { + Mainfunc chan func() + Exit chan struct{} + Input chan string + RawInput chan int + FovRecompute chan struct{} + Redraw chan struct{} +} + +// do runs f on the main thread. +func (g *GameState) Do(f func()) { + done := make(chan struct{}, 1) + g.Mainfunc <- func() { + f() + done <- struct{}{} + } + <-done +} diff --git a/ui/mainwindow/layer.go b/ui/mainwindow/layer.go index 8d5af19..f7f131e 100644 --- a/ui/mainwindow/layer.go +++ b/ui/mainwindow/layer.go @@ -98,7 +98,7 @@ func (layer *Layer) Decorate(f func(args ...interface{})) func(args ...interface } } -func (layer *Layer) Clear(r *types.Rect) { +func (layer *Layer) ClearRect(r *types.Rect) { blt.ClearArea(r.X, r.Y, r.W, r.H) } diff --git a/ui/mainwindow/mainwindow.go b/ui/mainwindow/mainwindow.go index 3ca1f1b..1d7147a 100644 --- a/ui/mainwindow/mainwindow.go +++ b/ui/mainwindow/mainwindow.go @@ -42,13 +42,14 @@ func (mw *MainWindow) Open() { blt.Set( fmt.Sprintf( //"window: size=%dx%d, title='%s v%s'; font: ./resources/fonts-bitmap/ibmnew8x12.png, size=8x12;", - "window: size=%dx%d, title='%s v%s'; font: %s, size=8x16;", + "window: size=%dx%d, title='%s v%s'; font: %s, size=%s;", //"window: size=%dx%d, title='%s v%s'", config.MainWindowSizeX, config.MainWindowSizeY, config.Title, config.Version, config.Font, + config.FontSize, ), ) } diff --git a/ui/mainwindow/viewport.go b/ui/mainwindow/viewport.go index 749d24d..96404c8 100644 --- a/ui/mainwindow/viewport.go +++ b/ui/mainwindow/viewport.go @@ -2,6 +2,7 @@ package mainwindow import ( "errors" + "fmt" "lab.zaar.be/thefish/alchemyst-go/engine/fov" "lab.zaar.be/thefish/alchemyst-go/engine/fov/precomputed_shade" "lab.zaar.be/thefish/alchemyst-go/engine/gamemap" @@ -10,15 +11,14 @@ import ( var NotInViewError = errors.New("not in ViewPort") -const FPS_LIMIT = 60 - type ViewPort struct { *types.Rect + cameraCoords types.Coords level *gamemap.Level layer *Layer Fov fov.Fov - playerCoords types.Coords - playerTorchRadius int + PlayerCoords types.Coords + PlayerTorchRadius int } func NewViewPort(x, y, w, h int, level *gamemap.Level, layer *Layer) *ViewPort { @@ -32,13 +32,13 @@ func NewViewPort(x, y, w, h int, level *gamemap.Level, layer *Layer) *ViewPort { Fov: fov, } - vp.playerCoords = types.Coords{10, 10} - vp.playerTorchRadius = 10 + vp.PlayerCoords = types.Coords{10, 10} + vp.PlayerTorchRadius = 10 return &vp } -func (vp *ViewPort) Move(c *types.Coords) { +func (vp *ViewPort) Move(c *types.Coords, state types.GameState) { x := c.X - vp.Rect.W/2 y := c.Y - vp.Rect.H/2 @@ -49,22 +49,26 @@ func (vp *ViewPort) Move(c *types.Coords) { y = 0 } if x > vp.level.W-vp.W { - x = vp.level.W - vp.W + x = vp.level.W - vp.W - 1 } if y > vp.level.H-vp.H { - x = vp.level.H - vp.H + x = vp.level.H - vp.H - 1 } - //if x != vp.X || y != vp.Y { State.FovRecompute <- struct{}{}} - vp.X, vp.Y = x, y + if x != vp.cameraCoords.X || y != vp.cameraCoords.Y { + state.FovRecompute <- struct{}{} + } + vp.cameraCoords.X = x + vp.cameraCoords.Y = y + } -func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err error) { +func (vp *ViewPort) ToVPCoords(c types.Coords) (newCoords types.Coords, err error) { //coords on map to coords on vp - x, y := c.X-vp.X, c.Y-vp.Y - if x < 0 || y < 0 || x >= vp.W || y >= vp.H { - return &types.Coords{-1, -1}, NotInViewError + x, y := c.X-vp.cameraCoords.X, c.Y-vp.cameraCoords.Y + if x < 0 || y < 0 || x > vp.W || y > vp.H { + return types.Coords{-1, -1}, NotInViewError } - return &types.Coords{x, y}, nil + return types.Coords{x, y}, nil } ////call only from main thread @@ -75,15 +79,15 @@ func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err er // redraw := false // //fixme get player instance // -// vp.Move(&vp.playerCoords) +// vp.Move(&vp.PlayerCoords) // //fixme detect fovRecompute // if fovRecompute { -// vp.layer.Clear(vp.Rect) +// vp.layer.ClearRect(vp.Rect) // fovRecompute = false // redraw = true // //fixme // -// vp.Fov.ComputeFov(vp.level, vp.playerCoords, vp.playerTorchRadius) +// vp.Fov.ComputeFov(vp.level, vp.PlayerCoords, vp.PlayerTorchRadius) // } // //increase ticker // fpsTicker++ @@ -119,26 +123,54 @@ func (vp *ViewPort) ToVPCoords(c *types.Coords) (newCoords *types.Coords, err er var redraw = true var fovRecompute = true -func (vp *ViewPort) Render() { +func (vp *ViewPort) Listen(state types.GameState) { + for { + select { + case <-state.FovRecompute: + fovRecompute = true + case <-state.Redraw: + redraw = true + } + } +} + +func (vp *ViewPort) Render(state types.GameState) { + + vp.Move(&vp.PlayerCoords, state) if fovRecompute { - vp.layer.Clear(vp.Rect) + vp.layer.ClearRect(vp.Rect) fovRecompute = false redraw = true - vp.Fov.ComputeFov(vp.level, vp.playerCoords, vp.playerTorchRadius) + vp.Fov.ComputeFov(vp.level, vp.PlayerCoords, vp.PlayerTorchRadius) } if redraw { + //terrain for y := 0; y < vp.H; y++ { for x := 0; x < vp.W; x++ { - mapCoords := types.Coords{vp.X + x, vp.Y + y} + mapCoords := types.Coords{vp.cameraCoords.X + x, vp.cameraCoords.Y + y} - tile := vp.level.GetTile(mapCoords) - if tile.Explored || tile.MustDraw || tile.Visible { - vp.layer.PutToBase(mapCoords.X, mapCoords.Y, tile.GetChar(), tile.GetRawColor(), tile.GetRawBgColor()) + if vp.level.InBounds(mapCoords) { + tile := vp.level.GetTile(mapCoords) + if tile.Explored || tile.MustDraw || tile.Visible { + vp.layer.PutToBase(x + vp.X, y + vp.Y, tile.GetChar(), tile.GetRawColor(), tile.GetRawBgColor()) + } } } } + //mobs + pc,err := vp.ToVPCoords(vp.PlayerCoords) + _ = pc + if err != nil { + fmt.Println("error on getting player position") + } else { + vp.layer.WithColor("white").Put(pc.X + vp.X, pc.Y + vp.Y, "@") + //mw.GetLayer("base").WithColor("white").Put(42, 10, "B") + //mw.GetLayer("overlay").WithColor("white").Put(59, 10, "O") + } + + redraw = true //redraw = false } diff --git a/util/config.go b/util/config.go index 4d6afd9..a50cb53 100644 --- a/util/config.go +++ b/util/config.go @@ -14,6 +14,7 @@ type Config struct { MainWindowSizeX int `json:"sizeX"` MainWindowSizeY int `json:"sizeY"` Font string `json:"font"` + FontSize string `json:"fontSize"` //format is "8x12" Verbosity string `json:"verbosity"` }