added theme system, makes it easier to add more color schemes

This commit is contained in:
2024-06-07 17:27:08 +02:00
parent 4695338323
commit 81c4b976e2
5 changed files with 188 additions and 74 deletions

0
play.go Normal file
View File

View File

@@ -33,6 +33,8 @@ type Config struct {
ZoomOutFactor int ZoomOutFactor int
InitialCamPos []float64 InitialCamPos []float64
DelayedStart bool // if true game, we wait. like pause but program induced DelayedStart bool // if true game, we wait. like pause but program induced
Theme string
ThemeManager ThemeManager
// for internal profiling // for internal profiling
ProfileFile string ProfileFile string
@@ -50,6 +52,7 @@ const (
DEFAULT_CELLSIZE = 4 DEFAULT_CELLSIZE = 4
DEFAULT_ZOOMFACTOR = 150 DEFAULT_ZOOMFACTOR = 150
DEFAULT_GEOM = "640x384" DEFAULT_GEOM = "640x384"
DEFAULT_THEME = "light" // inverse => "dark"
) )
const KEYBINDINGS string = ` const KEYBINDINGS string = `
@@ -224,7 +227,11 @@ func ParseCommandline() (*Config, error) {
pflag.BoolVarP(&config.Debug, "debug", "d", false, "show debug info") pflag.BoolVarP(&config.Debug, "debug", "d", false, "show debug info")
pflag.BoolVarP(&config.ShowGrid, "show-grid", "g", false, "draw grid lines") pflag.BoolVarP(&config.ShowGrid, "show-grid", "g", false, "draw grid lines")
pflag.BoolVarP(&config.Empty, "empty", "e", false, "start with an empty screen") pflag.BoolVarP(&config.Empty, "empty", "e", false, "start with an empty screen")
// style
pflag.BoolVarP(&config.Invert, "invert", "i", false, "invert colors (dead cell: black)") pflag.BoolVarP(&config.Invert, "invert", "i", false, "invert colors (dead cell: black)")
pflag.StringVarP(&config.Theme, "theme", "T", "light", "color theme: dark, light (default: light)")
pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution traces") pflag.BoolVarP(&config.ShowEvolution, "show-evolution", "s", false, "show evolution traces")
pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode") pflag.BoolVarP(&config.Wrap, "wrap-around", "w", false, "wrap around grid mode")
pflag.BoolVarP(&config.UseShader, "use-shader", "k", false, "use shader for cell rendering") pflag.BoolVarP(&config.UseShader, "use-shader", "k", false, "use shader for cell rendering")
@@ -253,6 +260,12 @@ func ParseCommandline() (*Config, error) {
config.SetupCamera() config.SetupCamera()
if config.Theme == "light" && config.Invert {
config.Theme = "dark"
}
config.ThemeManager = NewThemeManager(config.Theme, config.Cellsize)
//repr.Println(config) //repr.Println(config)
return &config, nil return &config, nil
} }
@@ -267,9 +280,23 @@ func (config *Config) ToggleDebugging() {
func (config *Config) ToggleInvert() { func (config *Config) ToggleInvert() {
config.Invert = !config.Invert config.Invert = !config.Invert
switch config.ThemeManager.GetCurrentThemeName() {
case "dark":
config.ThemeManager.SetCurrentTheme("light")
case "light":
config.ThemeManager.SetCurrentTheme("dark")
default:
fmt.Println("unable to invert custom theme, only possible with dark and light themes.")
}
config.RestartCache = true config.RestartCache = true
} }
func (config *Config) SwitchTheme(theme string) {
config.ThemeManager.SetCurrentTheme(theme)
}
func (config *Config) ToggleGridlines() { func (config *Config) ToggleGridlines() {
config.ShowGrid = !config.ShowGrid config.ShowGrid = !config.ShowGrid
config.RestartCache = true config.RestartCache = true

View File

@@ -10,3 +10,10 @@ func Contains[E comparable](s []E, v E) bool {
return false return false
} }
func Exists[K comparable, V any](m map[K]V, v K) bool {
if _, ok := m[v]; ok {
return true
}
return false
}

View File

@@ -35,10 +35,7 @@ type ScenePlay struct {
History [][]int64 // holds state of past dead cells for evolution traces History [][]int64 // holds state of past dead cells for evolution traces
Index int // points to current grid Index int // points to current grid
Generations int64 // Stats Generations int64 // Stats
Black, White, Grey, Old color.RGBA
AgeColor1, AgeColor2, AgeColor3, AgeColor4 color.RGBA
TicksElapsed int // tick counter for game speed TicksElapsed int // tick counter for game speed
Tiles Images // pre-computed tiles for dead and alife cells
Camera Camera // for zoom+move Camera Camera // for zoom+move
World, Cache *ebiten.Image // actual image we render to World, Cache *ebiten.Image // actual image we render to
WheelTurned bool // when user turns wheel multiple times, zoom faster WheelTurned bool // when user turns wheel multiple times, zoom faster
@@ -49,6 +46,7 @@ type ScenePlay struct {
Mark, Point image.Point // area to marks+save Mark, Point image.Point // area to marks+save
RunOneStep bool // mutable flags from config RunOneStep bool // mutable flags from config
TPG int // current game speed (ticks per game) TPG int // current game speed (ticks per game)
Theme Theme
} }
func NewPlayScene(game *Game, config *Config) Scene { func NewPlayScene(game *Game, config *Config) Scene {
@@ -417,7 +415,7 @@ func (scene *ScenePlay) Update() error {
if scene.Config.RestartCache { if scene.Config.RestartCache {
scene.Config.RestartCache = false scene.Config.RestartCache = false
scene.InitTiles() scene.Theme = scene.Config.ThemeManager.GetCurrentTheme()
scene.InitCache() scene.InitCache()
return nil return nil
} }
@@ -473,7 +471,7 @@ func (scene *ScenePlay) Draw(screen *ebiten.Image) {
scene.DrawEvolution(screen, x, y, op) scene.DrawEvolution(screen, x, y, op)
} else { } else {
if scene.Grids[scene.Index].Data[y][x] { if scene.Grids[scene.Index].Data[y][x] {
scene.World.DrawImage(scene.Tiles.Black, op) scene.World.DrawImage(scene.Theme.Tile(ColLife), op)
} }
} }
} }
@@ -497,22 +495,22 @@ func (scene *ScenePlay) DrawEvolution(screen *ebiten.Image, x, y int, op *ebiten
switch scene.Grids[scene.Index].Data[y][x] { switch scene.Grids[scene.Index].Data[y][x] {
case Alive: case Alive:
if age > 50 && scene.Config.ShowEvolution { if age > 50 && scene.Config.ShowEvolution {
scene.World.DrawImage(scene.Tiles.Old, op) scene.World.DrawImage(scene.Theme.Tile(ColOld), op)
} else { } else {
scene.World.DrawImage(scene.Tiles.Black, op) scene.World.DrawImage(scene.Theme.Tile(ColLife), op)
} }
case Dead: case Dead:
// only draw dead cells in case evolution trace is enabled // only draw dead cells in case evolution trace is enabled
if scene.History[y][x] > 1 && scene.Config.ShowEvolution { if scene.History[y][x] > 1 && scene.Config.ShowEvolution {
switch { switch {
case age < 10: case age < 10:
scene.World.DrawImage(scene.Tiles.Age1, op) scene.World.DrawImage(scene.Theme.Tile(ColAge1), op)
case age < 20: case age < 20:
scene.World.DrawImage(scene.Tiles.Age2, op) scene.World.DrawImage(scene.Theme.Tile(ColAge2), op)
case age < 30: case age < 30:
scene.World.DrawImage(scene.Tiles.Age3, op) scene.World.DrawImage(scene.Theme.Tile(ColAge3), op)
default: default:
scene.World.DrawImage(scene.Tiles.Age4, op) scene.World.DrawImage(scene.Theme.Tile(ColAge4), op)
} }
} }
} }
@@ -529,7 +527,7 @@ func (scene *ScenePlay) DrawMark(screen *ebiten.Image) {
scene.World, scene.World,
x+1, y+1, x+1, y+1,
w, h, w, h,
1.0, scene.Old, false, 1.0, scene.Theme.Color(ColOld), false,
) )
} }
} }
@@ -561,10 +559,10 @@ func (scene *ScenePlay) DrawDebug(screen *ebiten.Image) {
FontRenderer.Renderer.SetSizePx(10 + int(scene.Game.Scale*10)) FontRenderer.Renderer.SetSizePx(10 + int(scene.Game.Scale*10))
FontRenderer.Renderer.SetTarget(screen) FontRenderer.Renderer.SetTarget(screen)
FontRenderer.Renderer.SetColor(scene.Black) FontRenderer.Renderer.SetColor(scene.Theme.Color(ColLife))
FontRenderer.Renderer.Draw(debug, 31, 31) FontRenderer.Renderer.Draw(debug, 31, 31)
FontRenderer.Renderer.SetColor(scene.Old) FontRenderer.Renderer.SetColor(scene.Theme.Color(ColOld))
FontRenderer.Renderer.Draw(debug, 30, 30) FontRenderer.Renderer.Draw(debug, 30, 30)
fmt.Println(debug) fmt.Println(debug)
@@ -582,9 +580,9 @@ func (scene *ScenePlay) InitCache() {
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
if scene.Config.ShowGrid { if scene.Config.ShowGrid {
scene.Cache.Fill(scene.Grey) scene.Cache.Fill(scene.Theme.Color(ColGrid))
} else { } else {
scene.Cache.Fill(scene.White) scene.Cache.Fill(scene.Theme.Color(ColDead))
} }
for y := 0; y < scene.Config.Height; y++ { for y := 0; y < scene.Config.Height; y++ {
@@ -595,7 +593,7 @@ func (scene *ScenePlay) InitCache() {
float64(y*scene.Config.Cellsize), float64(y*scene.Config.Cellsize),
) )
scene.Cache.DrawImage(scene.Tiles.White, op) scene.Cache.DrawImage(scene.Theme.Tile(ColDead), op)
} }
} }
} }
@@ -619,47 +617,6 @@ func (scene *ScenePlay) InitGrid() {
} }
} }
// prepare tile images
func (scene *ScenePlay) InitTiles() {
scene.Grey = color.RGBA{128, 128, 128, 0xff}
scene.Old = color.RGBA{255, 30, 30, 0xff}
scene.Black = color.RGBA{0, 0, 0, 0xff}
scene.White = color.RGBA{200, 200, 200, 0xff}
scene.AgeColor1 = color.RGBA{255, 195, 97, 0xff} // FIXME: use slice!
scene.AgeColor2 = color.RGBA{255, 211, 140, 0xff}
scene.AgeColor3 = color.RGBA{255, 227, 181, 0xff}
scene.AgeColor4 = color.RGBA{255, 240, 224, 0xff}
if scene.Config.Invert {
scene.White = color.RGBA{0, 0, 0, 0xff}
scene.Black = color.RGBA{200, 200, 200, 0xff}
scene.AgeColor1 = color.RGBA{82, 38, 0, 0xff}
scene.AgeColor2 = color.RGBA{66, 35, 0, 0xff}
scene.AgeColor3 = color.RGBA{43, 27, 0, 0xff}
scene.AgeColor4 = color.RGBA{25, 17, 0, 0xff}
}
scene.Tiles.Black = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.White = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.Old = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.Age1 = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.Age2 = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.Age3 = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
scene.Tiles.Age4 = ebiten.NewImage(scene.Config.Cellsize, scene.Config.Cellsize)
cellsize := scene.Config.ScreenWidth / scene.Config.Cellsize
FillCell(scene.Tiles.Black, cellsize, scene.Black)
FillCell(scene.Tiles.White, cellsize, scene.White)
FillCell(scene.Tiles.Old, cellsize, scene.Old)
FillCell(scene.Tiles.Age1, cellsize, scene.AgeColor1)
FillCell(scene.Tiles.Age2, cellsize, scene.AgeColor2)
FillCell(scene.Tiles.Age3, cellsize, scene.AgeColor3)
FillCell(scene.Tiles.Age4, cellsize, scene.AgeColor4)
}
func (scene *ScenePlay) Init() { func (scene *ScenePlay) Init() {
// setup the scene // setup the scene
scene.Camera = Camera{ scene.Camera = Camera{
@@ -685,7 +642,7 @@ func (scene *ScenePlay) Init() {
scene.Config.Height*scene.Config.Cellsize, scene.Config.Height*scene.Config.Cellsize,
) )
scene.InitTiles() scene.Theme = scene.Config.ThemeManager.GetCurrentTheme()
scene.InitCache() scene.InitCache()
if scene.Config.DelayedStart && !scene.Config.Empty { if scene.Config.DelayedStart && !scene.Config.Empty {

123
src/theme.go Normal file
View File

@@ -0,0 +1,123 @@
package main
import (
"image/color"
"github.com/hajimehoshi/ebiten/v2"
)
// Color definitions. ColLife could be black or white depending on theme
const (
ColLife = iota
ColDead
ColOld
ColAge1
ColAge2
ColAge3
ColAge4
ColGrid
)
// A Theme defines how the grid and the cells are colored. We define
// the colors and the actual tile images here, so that they are
// readily available from play.go
type Theme struct {
Tiles map[int]*ebiten.Image
Colors map[int]color.RGBA
Name string
}
// create a new theme
func NewTheme(life, dead, old, age1, age2, age3, age4, grid color.RGBA, cellsize int, name string) Theme {
theme := Theme{
Name: name,
Colors: map[int]color.RGBA{
ColLife: life,
ColDead: dead,
ColGrid: grid,
ColAge1: age1,
ColAge2: age2,
ColAge3: age3,
ColAge4: age4,
ColOld: old,
},
}
theme.Tiles = make(map[int]*ebiten.Image, 6)
for cid, col := range theme.Colors {
theme.Tiles[cid] = ebiten.NewImage(cellsize, cellsize)
FillCell(theme.Tiles[cid], cellsize, col)
}
return theme
}
// return the tile image for the requested color type. panic if
// unknown type is being used, which is ok, since the code is the only
// user anyway
func (theme *Theme) Tile(col int) *ebiten.Image {
return theme.Tiles[col]
}
func (theme *Theme) Color(col int) color.RGBA {
return theme.Colors[col]
}
type ThemeManager struct {
Theme string
Themes map[string]Theme
}
// Manager is used to easily switch themes from cli or menu
func NewThemeManager(initial string, cellsize int) ThemeManager {
light := NewTheme(
color.RGBA{0, 0, 0, 0xff}, // life
color.RGBA{200, 200, 200, 0xff}, // dead
color.RGBA{255, 30, 30, 0xff}, // old
color.RGBA{255, 195, 97, 0xff}, // age 1..4
color.RGBA{255, 211, 140, 0xff},
color.RGBA{255, 227, 181, 0xff},
color.RGBA{255, 240, 224, 0xff},
color.RGBA{128, 128, 128, 0xff}, // grid
cellsize,
"light",
)
dark := NewTheme(
color.RGBA{200, 200, 200, 0xff}, // life
color.RGBA{0, 0, 0, 0xff}, // dead
color.RGBA{255, 30, 30, 0xff}, // old
color.RGBA{82, 38, 0, 0xff}, // age 1..4
color.RGBA{66, 35, 0, 0xff},
color.RGBA{43, 27, 0, 0xff},
color.RGBA{25, 17, 0, 0xff},
color.RGBA{128, 128, 128, 0xff}, // grid
cellsize,
"dark",
)
manager := ThemeManager{
Themes: map[string]Theme{
"dark": dark,
"light": light,
},
Theme: initial,
}
return manager
}
func (manager *ThemeManager) GetCurrentTheme() Theme {
return manager.Themes[manager.Theme]
}
func (manager *ThemeManager) GetCurrentThemeName() string {
return manager.Theme
}
func (manager *ThemeManager) SetCurrentTheme(theme string) {
if Exists(manager.Themes, theme) {
manager.Theme = theme
}
}