Merge branch 'master' of lab.zaar.be:thefish/alchemyst-go

This commit is contained in:
thefish 2019-10-26 18:49:11 +03:00
commit 32c598f9e0
12 changed files with 530 additions and 117 deletions

5
engine/ecs/entity.go Normal file

@ -0,0 +1,5 @@
package ecs
type Entity struct {
ID int
}

88
engine/fov/fov.go Normal file

@ -0,0 +1,88 @@
package fov
import (
"github.com/jcerise/gogue/gamemap"
"math"
)
type FieldOfVision struct {
cosTable map[int]float64
sinTable map[int]float64
torchRadius int
}
func (f *FieldOfVision) Initialize() {
f.cosTable = make(map[int]float64)
f.sinTable = make(map[int]float64)
for i := 0; i < 360; i++ {
ax := math.Sin(float64(i) / (float64(180) / math.Pi))
ay := math.Cos(float64(i) / (float64(180) / math.Pi))
f.sinTable[i] = ax
f.cosTable[i] = ay
}
}
func (f *FieldOfVision) SetTorchRadius(radius int) {
if radius > 1 {
f.torchRadius = radius
}
}
func (f *FieldOfVision) SetAllInvisible(gameMap *gamemap.Map) {
for x := 0; x < gameMap.Width; x++ {
for y := 0; y < gameMap.Height; y++ {
gameMap.Tiles[x][y].Visible = false
}
}
}
func (f *FieldOfVision) RayCast(playerX, playerY int, gameMap *gamemap.Map) {
// Cast out rays each degree in a 360 circle from the player. If a ray passes over a floor (does not block sight)
// tile, keep going, up to the maximum torch radius (view radius) of the player. If the ray intersects a wall
// (blocks sight), stop, as the player will not be able to see past that. Every visible tile will get the Visible
// and Explored properties set to true.
for i := 0; i < 360; i++ {
ax := f.sinTable[i]
ay := f.cosTable[i]
x := float64(playerX)
y := float64(playerY)
// Mark the players current position as explored
tile := gameMap.Tiles[playerX][playerY]
tile.Explored = true
tile.Visible = true
for j := 0; j < f.torchRadius; j++ {
x -= ax
y -= ay
roundedX := int(Round(x))
roundedY := int(Round(y))
if x < 0 || x > float64(gameMap.Width-1) || y < 0 || y > float64(gameMap.Height-1) {
// If the ray is cast outside of the gamemap, stop
break
}
tile := gameMap.Tiles[roundedX][roundedY]
tile.Explored = true
tile.Visible = true
if gameMap.Tiles[roundedX][roundedY].BlocksSight == true {
// The ray hit a wall, go no further
break
}
}
}
}
func Round(f float64) float64 {
return math.Floor(f + .5)
}

14
engine/gamemap/level.go Normal file

@ -0,0 +1,14 @@
package gamemap
import "lab.zaar.be/thefish/alchemyst-go/engine/ecs"
type Level struct {
Name string
Branch string
Depth int
MaxRooms int
Width int
Height int
Objects []ecs.Entity
Tiles [][]*Tile
}

20
engine/gamemap/mapgen.go Normal file

@ -0,0 +1,20 @@
package gamemap
type mapGen interface {
generate(l *Level) *Level
}
type defaultGen struct {}
func (d defaultGen) generate(l *Level) *Level {
l.Tiles, rooms = addRooms(l)
l. Tiles = connectRooms(rooms)
l.Objects = populate(rooms)
return l
}
func addRooms(l *Level) {
}

143
engine/gamemap/tile.go Normal file

@ -0,0 +1,143 @@
package gamemap
import "lab.zaar.be/thefish/alchemyst-go/util"
import blt "lab.zaar.be/thefish/bearlibterminal"
type ColorHolder struct {
R uint8
G uint8
B uint8
}
type TileColorSet struct {
Fg func() uint32
Bg func() uint32
DarkFg func() uint32
DarkBg func() uint32
current *ColorHolder
}
type Appearance struct {
Char string
ColorSet *TileColorSet
}
var crng = util.NewRNG()
func colordance(colorValue uint8, minGlow, maxGlow, step int) uint8 {
color := crng.Range(0, step) + int(colorValue)
if color > maxGlow {
color = crng.Range(0, step) + minGlow
}
return uint8(color)
}
type Tile struct {
*Appearance
Name string
Description string
BlocksPass bool
BlocksSight bool
Explored bool
MustDraw bool
}
func NewWall() *Tile {
return &Tile{
Name: "Wall",
Description: "A dull rock wall",
BlocksPass: true,
BlocksSight: true,
Explored: false,
MustDraw: false,
Appearance: &Appearance{
Char: "#",
ColorSet: &TileColorSet{
Fg: func() uint32 {return blt.ColorFromARGB(255, 130,110,150)},
Bg: func() uint32 {return blt.ColorFromARGB(255, 172,170,173)},
DarkFg: func() uint32 {return blt.ColorFromARGB(255, 20,20,68)},
DarkBg: func() uint32 {return blt.ColorFromARGB(255, 7,7,30)},
},
},
}
}
func NewFloor() *Tile {
return &Tile{
Name: "Floor",
Description: "Dusty rock floor",
BlocksPass: false,
BlocksSight: false,
Explored: false,
MustDraw: false,
Appearance: &Appearance{
Char: ".",
ColorSet: &TileColorSet{
Fg: func() uint32 {return blt.ColorFromARGB(255, 220,220,250)},
Bg: func() uint32 {return blt.ColorFromARGB(255, 19,19,70)},
DarkFg: func() uint32 {return blt.ColorFromARGB(255, 30,20,50)},
DarkBg: func() uint32 {return blt.ColorFromARGB(255, 7,7,30)},
},
},
}
}
func NewWaterTile() *Tile {
ch := &ColorHolder{19,19,70}
return &Tile {
Name: "Water",
Description: "Murky water",
BlocksPass: false,
BlocksSight: false,
Explored: false,
MustDraw: true, //fixme debug
Appearance: &Appearance{
Char: ".",
ColorSet: &TileColorSet{
current: ch,
Fg: func() uint32 {return blt.ColorFromARGB(255, 220,220,250)},
Bg: func() uint32 {
return blt.ColorFromARGB(
255,
ch.R,
colordance(ch.G, 2, 42, 4 ),
colordance(ch.B, 180,229,12),
)
},
DarkFg: func() uint32 {return blt.ColorFromARGB(255, 30,20,50)},
DarkBg: func() uint32 {return blt.ColorFromARGB(255, 7,7,30)},
},
},
}
}
func NewDeepWaterTile() *Tile {
ch := &ColorHolder{5,2,154}
return &Tile {
Name: "Deep Water",
Description: "Deep water",
BlocksPass: false,
BlocksSight: false,
Explored: false,
MustDraw: true, //fixme debug
Appearance: &Appearance{
Char: " ",
ColorSet: &TileColorSet{
current: ch,
Fg: func() uint32 {return blt.ColorFromARGB(255, 220,220,250)},
Bg: func() uint32 {
return blt.ColorFromARGB(
255,
ch.R,
colordance(ch.G, 0, 15, 2 ),
colordance(ch.B, 120,180,5),
)
},
DarkFg: func() uint32 {return blt.ColorFromARGB(255, 30,20,50)},
DarkBg: func() uint32 {return blt.ColorFromARGB(255, 7,7,30)},
},
},
}
}

5
engine/gamemap/types.go Normal file

@ -0,0 +1,5 @@
package gamemap
type Coords struct {
x,y int
}

@ -59,7 +59,6 @@ func main() {
}
mainCtx.Logger().Info().Msg("pre-shutdown sequence")
}
// do runs f on the main thread.

43
ui/mainwindow/camera.go Normal file

@ -0,0 +1,43 @@
package mainwindow
type GameCamera struct {
X int
Y int
Width int
Height int
}
func (c *GameCamera) MoveCamera(targetX int, targetY int, mapWidth int, mapHeight int) {
// Update the camera coordinates to the target coordinates
x := targetX - c.Width/2
y := targetY - c.Height/2
if x < 0 {
x = 0
}
if y < 0 {
y = 0
}
if x > mapWidth - c.Width {
x = mapWidth - c.Width
}
if y > mapHeight - c.Height {
y = mapHeight - c.Height
}
c.X, c.Y = x, y
}
func (c *GameCamera) ToCameraCoordinates(mapX int, mapY int) (cameraX int, cameraY int) {
// Convert coordinates on the gamemap, to coordinates on the viewport
x, y := mapX-c.X, mapY-c.Y
if x < 0 || y < 0 || x >= c.Width || y >= c.Height {
return -1, -1
}
return x, y
}

@ -2,160 +2,64 @@ package mainwindow
import blt "lab.zaar.be/thefish/bearlibterminal"
var emptyCorners = [4]uint32{0,0,0,0}
type LayerInterface interface {
Render()
Put(x, y int, symbol rune, color string)
}
type LayerInterfaceImpl struct {
type Layer struct {
idx int
defaultColor uint32
}
func AddLayer(idx int, colorName string) LayerInterfaceImpl {
func AddLayer(idx int, colorName string) Layer {
c := blt.ColorFromName(colorName)
return LayerInterfaceImpl{idx: idx, defaultColor: c}
return Layer{idx: idx, defaultColor: c}
}
func (lii *LayerInterfaceImpl) before() *LayerInterfaceImpl {
blt.Layer(lii.idx)
return lii
func (layer *Layer) before() *Layer {
blt.Layer(layer.idx)
return layer
}
func (lii *LayerInterfaceImpl) WithColor(colorName string) *LayerInterfaceImpl {
lii.before()
func (layer *Layer) WithColor(colorName string) *Layer {
layer.before()
c := blt.ColorFromName(colorName)
blt.Color(c)
return lii
return layer
}
func (lii *LayerInterfaceImpl) after() *LayerInterfaceImpl {
blt.Color(lii.defaultColor)
func (layer *Layer) after() *Layer {
blt.Color(layer.defaultColor)
blt.Layer(0)
return lii
return layer
}
func (lii LayerInterfaceImpl) Put(x,y int, symbol string) {
func (layer Layer) Put(x,y int, symbol string) {
rnes := []rune(symbol)
if (len(rnes)) > 0 {
blt.Put(x, y, int(rnes[0]))
}
}
func (lii LayerInterfaceImpl) Print(x,y int, txt string) (w,h int) {
func (layer Layer) Print(x,y int, txt string) (w,h int) {
return blt.Print(x,y, txt)
}
type Rect struct {
x,y,w,h int
layer *LayerInterfaceImpl
}
type rectFill struct {
top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight, body string
}
func (lii *LayerInterfaceImpl) NewRect(x,y,w,h int) *Rect {
return &Rect{x,y,w,h, lii}
}
var noborder = rectFill{
top: "▄",
bottom: "▀",
left: "▐",
right: "▌",
topLeft: "▗",
topRight: "▖",
bottomLeft: "▝",
bottomRight: "▘",
body: "█",
}
var splash = rectFill{
top: "█",
bottom: "█",
left: "█",
right: "█",
topLeft: "█",
topRight: "█",
bottomLeft: "█",
bottomRight: "█",
body: "█",
}
var doubleBorder = rectFill {
top: "═",
bottom: "═",
left: "║",
right: "║",
topLeft: "╔",
topRight: "╗",
bottomLeft: "╚",
bottomRight: "╝",
}
func (r *Rect) Fill() {
r.render(noborder)
}
func (r *Rect) Splash() {
r.render(splash)
}
func (r *Rect) DrawBorder() {
r.render(doubleBorder)
}
func (r *Rect) render (fillage rectFill) {
if fillage.body != "" {
for i := r.x + 1; i < r.x+r.w; i++ {
for j := r.y + 1; j < r.y+r.h; j++ {
r.layer.Put(i, j, fillage.body);
//lii.Put(i, j, "X");
}
}
}
for i := r.x + 1; i < r.x+r.w; i++ {
r.layer.Put(i, r.y, fillage.top);
//lii.Put(i, y-1, "Q");
r.layer.Put(i, r.y+r.h, fillage.bottom);
//lii.Put(i, y+h, "H");
}
for j := r.y + 1; j < r.y+r.h; j++ {
r.layer.Put(r.x, j, fillage.left);
//lii.Put(x-1, j, "U");
r.layer.Put(r.x+r.w, j, fillage.right);
//lii.Put(x+w, j, "M");
}
r.layer.Put(r.x, r.y, fillage.topLeft);
//lii.Put(x-1, y-1, "T");
r.layer.Put(r.x, r.y+r.h, fillage.bottomLeft);
//lii.Put(x-1, y+h, "q");
r.layer.Put(r.x+r.w, r.y, fillage.topRight);
//lii.Put(x+w, y-1, "L");
r.layer.Put(r.x+r.w, r.y+r.h, fillage.bottomRight);
};
func (lii LayerInterfaceImpl) DrawWindow (title string, x, y, w, h int) {
func (layer Layer) DrawWindow (title string, x, y, w, h int) {
if len(title) > (w -2) {
title = title[:(w-2)]
}
lii.NewRect(x,y,w,h).DrawBorder()
layer.NewRect(x,y,w,h).DrawBorder()
centerX := x + (w / 2)
lii.Print(centerX - (len(title) / 2) - 1, y, "╡" + title + "╞")
layer.Print(centerX - (len(title) / 2) - 1, y, "╡" + title + "╞")
};
func (lii *LayerInterfaceImpl) Decorate (f func (args ...interface{})) func (args ...interface{}) {
func (layer *Layer) Decorate (f func (args ...interface{})) func (args ...interface{}) {
return func (args ...interface{}) {
lii.before()
layer.before()
f(args)
lii.after()
layer.after()
}
}

@ -0,0 +1,97 @@
package mainwindow
type Rect struct {
x,y,w,h int
layer *Layer
}
type rectFill struct {
top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight, body string
}
func (layer *Layer) NewRect(x,y,w,h int) *Rect {
return &Rect{x,y,w,h, layer}
}
var noborder = rectFill{
top: "▄",
bottom: "▀",
left: "▐",
right: "▌",
topLeft: "▗",
topRight: "▖",
bottomLeft: "▝",
bottomRight: "▘",
body: "█",
}
var splash = rectFill{
top: "█",
bottom: "█",
left: "█",
right: "█",
topLeft: "█",
topRight: "█",
bottomLeft: "█",
bottomRight: "█",
body: "█",
}
var doubleBorder = rectFill {
top: "═",
bottom: "═",
left: "║",
right: "║",
topLeft: "╔",
topRight: "╗",
bottomLeft: "╚",
bottomRight: "╝",
}
func (r *Rect) Fill() {
r.render(noborder)
}
func (r *Rect) Splash() {
r.render(splash)
}
func (r *Rect) DrawBorder() {
r.render(doubleBorder)
}
func (r *Rect) render (fillage rectFill) {
if fillage.body != "" {
for i := r.x + 1; i < r.x+r.w; i++ {
for j := r.y + 1; j < r.y+r.h; j++ {
r.layer.Put(i, j, fillage.body);
//lii.Put(i, j, "X");
}
}
}
for i := r.x + 1; i < r.x+r.w; i++ {
r.layer.Put(i, r.y, fillage.top);
//lii.Put(i, y-1, "Q");
r.layer.Put(i, r.y+r.h, fillage.bottom);
//lii.Put(i, y+h, "H");
}
for j := r.y + 1; j < r.y+r.h; j++ {
r.layer.Put(r.x, j, fillage.left);
//lii.Put(x-1, j, "U");
r.layer.Put(r.x+r.w, j, fillage.right);
//lii.Put(x+w, j, "M");
}
r.layer.Put(r.x, r.y, fillage.topLeft);
//lii.Put(x-1, y-1, "T");
r.layer.Put(r.x, r.y+r.h, fillage.bottomLeft);
//lii.Put(x-1, y+h, "q");
r.layer.Put(r.x+r.w, r.y, fillage.topRight);
//lii.Put(x+w, y-1, "L");
r.layer.Put(r.x+r.w, r.y+r.h, fillage.bottomRight);
};

@ -26,6 +26,7 @@ func (mw *MainWindow) Open() {
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'",
config.MainWindowSizeX,
config.MainWindowSizeY,
config.Title,

94
util/rng.go Normal file

@ -0,0 +1,94 @@
package util
import (
"math"
"math/rand"
"time"
)
type RNG struct {
seed int64
rand *rand.Rand
}
func NewRNG() *RNG {
rng := RNG{}
// Set the seed to the current time. This can be updated later by the user.
rng.seed = time.Now().UTC().UnixNano()
rng.rand = rand.New(rand.NewSource(rng.seed))
return &rng
}
func (rng *RNG) GetSeed() int64 {
return rng.seed
}
func (rng *RNG) SetSeed(seed int64) {
rng.seed = seed
}
func (rng *RNG) Uniform() float64 {
return rng.rand.Float64()
}
func (rng *RNG) UniformRange(a, b float64) float64 {
return a + rng.Uniform() * (b - a)
}
func (rng *RNG) Normal(mean, stddev float64) float64 {
var r, x float64
for r >= 1 || r == 0 {
x = rng.UniformRange(-1.0, 1.0)
y := rng.UniformRange(-1.0, 1.0)
r = x*x + y*y
}
result := x * math.Sqrt(-2 * math.Log(r) / r)
return mean + stddev * result
}
func (rng *RNG) Percentage() int {
return rng.rand.Intn(100)
}
func (rng *RNG) Range(min, max int) int {
if min == max {
return min
} else {
return rng.rand.Intn(max - min) + min
}
}
func (rng *RNG) RangeNegative(min, max int) int {
if min == max {
return min
} else {
return rng.rand.Intn(max - min + 1) + min
}
}
func (rng *RNG) GetWeightedEntity(values map[int]int) int {
// First up, get the total weight value from the gamemap
totalWeight := 0
for weight := range values {
totalWeight += weight
}
// Next, get a random integer in the range of the total weight
r := rng.Range(0, totalWeight)
for weight, value := range values {
r -= value
if r <= 0 {
return weight
}
}
return -1
}