commit 014209898fc358f0dc6a8719273ca0adb90f9a8b Author: Thomas von Dein Date: Tue Feb 6 15:26:20 2024 +0100 1st commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..deb4c05 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: clean build + @echo ok + +clean: + rm -f openquell + +build: + go build + + +test: + @echo 1 diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..243c589 --- /dev/null +++ b/TODO.md @@ -0,0 +1,9 @@ +## Levels: + +- get rid of floor image +- use background image over whole screen size +- put level on top of it +- paint level in levels/**.lvl on whole screen into the middle +- use first line as background image name +- ignore comments in lvl files +- add whitespace-mode flag in lvl files diff --git a/assets/levels/gen.sh b/assets/levels/gen.sh new file mode 100755 index 0000000..8d57af4 --- /dev/null +++ b/assets/levels/gen.sh @@ -0,0 +1,29 @@ +#!/bin/sh +width=$(grep "width int" ../main.go | awk '{print $4}') +height=$(grep "height int" ../main.go | awk '{print $4}') + +read -p " Enter level name: " name +read -p " Enter background: " background +read -p "Enter description: " des + +if test -z "$name"; then + name="newlevel" +fi + +if test -z "$bbackground"; then + background="background-lila" +fi + +( + echo "Description: $des" + echo "Background: $background" + w=$(($width / 32)) + h=$(($height / 32)) + + for x in $(seq 1 $h); do + for y in $(seq 1 $w); do + echo -n " " + done + echo + done +) diff --git a/assets/levels/gurke.lvl b/assets/levels/gurke.lvl new file mode 100644 index 0000000..bd2ad5d --- /dev/null +++ b/assets/levels/gurke.lvl @@ -0,0 +1,17 @@ +Description: find the fruit +Background: background-orange + + + ############## + # # + ############ # + # S # + # ############ + # # + ############ # + # # # # + # ##### # # # + # # # + ############## + + diff --git a/assets/levels/intro.lvl b/assets/levels/intro.lvl new file mode 100644 index 0000000..8bc0a05 --- /dev/null +++ b/assets/levels/intro.lvl @@ -0,0 +1,17 @@ +Description: Introduction: collect the goods by moving the ball onto them +Background: background-lila +#################### +# # +# # +# # +# # +# # +# # +# ###### +# +# +# +# +# +# +#################### diff --git a/assets/loader-levels.go b/assets/loader-levels.go new file mode 100644 index 0000000..89d5a9c --- /dev/null +++ b/assets/loader-levels.go @@ -0,0 +1,150 @@ +package assets + +import ( + "bufio" + "fmt" + _ "image/png" + "io/fs" + "log" + "openquell/util" + "os" + "path/filepath" + "strings" + + "github.com/hajimehoshi/ebiten/v2" +) + +var Levels = LoadLevels("levels") +var Tiles = InitTiles() + +// Tile: contains image, identifier (as used in level data) and +// additional properties +type Tile struct { + Id byte + Sprite *ebiten.Image + Class string + Solid bool + Player bool + Renderable bool + Velocity bool +} + +func NewTilePlayer() *Tile { + return &Tile{ + Id: 'S', + Sprite: Assets["sphere-blue"], + Class: "sphere", + Renderable: true, + Player: true, + Velocity: true, + } +} + +func NewTileBlock(class string) *Tile { + return &Tile{ + Id: '#', + Sprite: Assets[class], + Class: class, + Solid: true, + Renderable: true, + } +} + +// used to map level data bytes to actual tiles +type TileRegistry map[byte]*Tile + +// holds a raw level spec: +// +// Name: the name of the level file w/o the .lvl extension +// Background: an image name used as game background for this level +// Description: text to display on top +// Data: a level spec consisting of chars of the above mapping and spaces, e.g.: +// #### +// # # +// #### +// +// Each level data must be 20 chars wide (= 640 px width) and 15 chars +// high (=480 px height). +type RawLevel struct { + Name string + Description string + Background *ebiten.Image + Data []byte +} + +func InitTiles() TileRegistry { + return TileRegistry{ + ' ': {Id: ' ', Class: "floor", Renderable: false}, + '#': NewTileBlock("block-grey32"), + 'S': NewTilePlayer(), + } +} + +// load levels at compile time into ram, creates a slice of raw levels +func LoadLevels(dir string) []RawLevel { + levels := []RawLevel{} + + // we use embed.FS to iterate over all files in ./levels/ + entries, err := assetfs.ReadDir(dir) + if err != nil { + log.Fatalf("failed to read level dir %s: %s", dir, err) + } + + for _, levelfile := range entries { + if levelfile.Type().IsRegular() && strings.Contains(levelfile.Name(), ".lvl") { + path := filepath.Join("assets", dir) + fmt.Printf("LOADING level %s/%s ... ", path, levelfile) + level := ParseRawLevel(path, levelfile) + fmt.Printf("done\n") + levels = append(levels, level) + } + } + + return levels +} + +func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel { + fd, err := os.Open(filepath.Join(dir, levelfile.Name())) + if err != nil { + log.Fatalf("failed to read level file %s: %s", levelfile.Name(), err) + } + defer fd.Close() + + name := strings.TrimSuffix(levelfile.Name(), ".lvl") + des := "" + background := &ebiten.Image{} + data := []byte{} + + scanner := bufio.NewScanner(fd) + for scanner.Scan() { + // ignore any whitespace + line := scanner.Text() + + // ignore empty lines + if len(line) == 0 { + continue + } + + switch { + case strings.Contains(line, "Background:"): + haveit := strings.Split(line, ": ") + if util.Exists(Assets, haveit[1]) { + background = Assets[haveit[1]] + } + case strings.Contains(line, "Description:"): + haveit := strings.Split(line, ": ") + des = haveit[1] + default: + // all other non-empty and non-equalsign lines are + // level definition matrix data, merge thes into data + data = append(data, line+"\n"...) + } + } + + return RawLevel{ + Name: name, + Data: data, + Background: background, + Description: des, + } +} diff --git a/assets/loader-sprites.go b/assets/loader-sprites.go new file mode 100644 index 0000000..20e0ab5 --- /dev/null +++ b/assets/loader-sprites.go @@ -0,0 +1,58 @@ +package assets + +import ( + "embed" + "fmt" + "image" + _ "image/png" + "log" + "os" + "path/filepath" + "strings" + + "github.com/hajimehoshi/ebiten/v2" +) + +// Maps image name to image data +type AssetRegistry map[string]*ebiten.Image + +//go:embed levels/*.lvl sprites/*.png +var assetfs embed.FS + +var Assets = LoadImages("sprites") + +func LoadImages(dir string) AssetRegistry { + images := AssetRegistry{} + + // we use embed.FS to iterate over all files in ./assets/ + entries, err := assetfs.ReadDir(dir) + if err != nil { + log.Fatalf("failed to read assets dir %s: %s", dir, err) + } + + for _, entry := range entries { + fmt.Println(entry.Name()) + } + + for _, imagefile := range entries { + path := filepath.Join("assets", dir, imagefile.Name()) + fmt.Printf("LOADING %s ... ", path) + fd, err := os.Open(path) + if err != nil { + log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err) + } + defer fd.Close() + + name := strings.TrimSuffix(imagefile.Name(), ".png") + + img, _, err := image.Decode(fd) + if err != nil { + log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err) + } + + images[name] = ebiten.NewImageFromImage(img) + fmt.Printf("done\n") + } + + return images +} diff --git a/assets/sprites/background-lila.png b/assets/sprites/background-lila.png new file mode 100644 index 0000000..b65bd2d Binary files /dev/null and b/assets/sprites/background-lila.png differ diff --git a/assets/sprites/background-orange.png b/assets/sprites/background-orange.png new file mode 100644 index 0000000..87fbd3e Binary files /dev/null and b/assets/sprites/background-orange.png differ diff --git a/assets/sprites/block-grey.png b/assets/sprites/block-grey.png new file mode 100644 index 0000000..87614a6 Binary files /dev/null and b/assets/sprites/block-grey.png differ diff --git a/assets/sprites/block-grey32.png b/assets/sprites/block-grey32.png new file mode 100644 index 0000000..17fc10a Binary files /dev/null and b/assets/sprites/block-grey32.png differ diff --git a/assets/sprites/block-orange-32.png b/assets/sprites/block-orange-32.png new file mode 100644 index 0000000..35e41c5 Binary files /dev/null and b/assets/sprites/block-orange-32.png differ diff --git a/assets/sprites/sphere-blue.png b/assets/sprites/sphere-blue.png new file mode 100644 index 0000000..dd91d5c Binary files /dev/null and b/assets/sprites/sphere-blue.png differ diff --git a/components/components.go b/components/components.go new file mode 100644 index 0000000..9ca2deb --- /dev/null +++ b/components/components.go @@ -0,0 +1,17 @@ +package components + +import ( + "github.com/hajimehoshi/ebiten/v2" +) + +// virtual location, aka tile address + +type Renderable struct { + Image *ebiten.Image +} + +// only tile entities will have those +type Tilish struct{} +type Solid struct{} +type Floor struct{} +type Player struct{} diff --git a/components/position.go b/components/position.go new file mode 100644 index 0000000..65a030b --- /dev/null +++ b/components/position.go @@ -0,0 +1,106 @@ +package components + +import ( + "fmt" + "image" + . "openquell/config" +) + +// physical location on screen +type Position struct { + X int + Y int + Cellsize int + Rect *image.Rectangle +} + +func (position *Position) Update(x, y int, size ...int) { + if len(size) == 1 { + position.Cellsize = size[0] + } + + position.X = x + position.Y = y + + rect := image.Rect(x, y, x+position.Cellsize, y+position.Cellsize) + position.Rect = &rect +} + +func NewPosition(point *image.Point, cellsize int) *Position { + position := &Position{} + position.Update(point.X*cellsize, point.Y*cellsize, cellsize) + return position +} + +func (position *Position) GetMoved(velosity *Velocity) *Position { + pos := &Position{} + pos.Update(position.X+velosity.Data.X, position.Y+velosity.Data.Y, position.Cellsize) + + return pos +} + +func (position *Position) Point() *image.Point { + return &image.Point{position.X / position.Cellsize, position.Y / position.Cellsize} +} + +func (position *Position) Left() int { + return position.X +} + +func (position *Position) Right() int { + return position.X + position.Cellsize +} + +func (position *Position) Top() int { + return position.Y +} + +func (position *Position) Bottom() int { + return position.Y + position.Cellsize +} + +func (position *Position) String() string { + return fmt.Sprintf("[%d,%d](Left:%d,Top:%d) x (Right:%d,Bottom:%d)", + position.X/32, + position.Y/32, + position.X, + position.Y, + position.Right(), + position.Bottom(), + ) +} + +func (position *Position) Move(velocity *Velocity) { + position.Update(position.X+velocity.Data.X, position.Y+velocity.Data.Y) +} + +func (position *Position) Set(newpos *Position) { + position.Update(newpos.X, newpos.Y) +} + +func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *Position) { + object := moving.GetMoved(velocity) + + is := tile.Rect.Bounds().Intersect(object.Rect.Bounds()) + if is != image.ZR { + fmt.Printf(" velocity: %d,%d\n", velocity.Data.X, velocity.Data.Y) + fmt.Printf(" player: %s\n", moving.Rect) + fmt.Printf("moved player: %s\n", object.Rect) + fmt.Printf("collision at: %s\n", is) + // collision, snap into neighbouring tile depending on the direction + switch velocity.Direction { + case West: + object.Update(tile.Rect.Max.X, tile.Y) + case East: + object.Update(tile.Rect.Min.X-tile.Cellsize, tile.Y) + case South: + object.Update(tile.X, tile.Rect.Min.Y-tile.Cellsize) + case North: + object.Update(tile.X, tile.Rect.Max.Y) + } + + return true, object + } + + return false, nil +} diff --git a/components/velocity.go b/components/velocity.go new file mode 100644 index 0000000..a3c1cd7 --- /dev/null +++ b/components/velocity.go @@ -0,0 +1,39 @@ +package components + +import ( + . "openquell/config" +) + +// movement in relation to position +type Velocity struct { + Data Position + Direction int +} + +func (velocity *Velocity) Change(direction int) { + ticks := 4 + + switch direction { + case East: + velocity.Data.X = ticks + velocity.Data.Y = 0 + case West: + velocity.Data.X = ticks - (ticks * 2) + velocity.Data.Y = 0 + case South: + velocity.Data.X = 0 + velocity.Data.Y = ticks + case North: + velocity.Data.X = 0 + velocity.Data.Y = ticks - (ticks * 2) + case Stop: + velocity.Data.X = 0 + velocity.Data.Y = 0 + } + + velocity.Direction = direction +} + +func (velocity *Velocity) Moving() bool { + return velocity.Data.X != 0 || velocity.Data.Y != 0 +} diff --git a/config/static.go b/config/static.go new file mode 100644 index 0000000..0bd3ff6 --- /dev/null +++ b/config/static.go @@ -0,0 +1,9 @@ +package config + +const ( + Stop = iota + East + West + South + North +) diff --git a/game/game.go b/game/game.go new file mode 100644 index 0000000..8f9094e --- /dev/null +++ b/game/game.go @@ -0,0 +1,51 @@ +package game + +import ( + "image" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/mlange-42/arche/ecs" +) + +type Game struct { + World *ecs.World + Bounds image.Rectangle + ScreenWidth, ScreenHeight int + CurrentLevel int + Scenes []Scene +} + +func NewGame(width, height, startlevel int) *Game { + world := ecs.NewWorld() + + game := &Game{ + Bounds: image.Rectangle{}, + CurrentLevel: startlevel, + World: &world, + ScreenWidth: width, + ScreenHeight: height, + } + + levelscene := NewLevelScene(game, startlevel) + game.Scenes = append(game.Scenes, levelscene) + + return game +} + +func (game *Game) Update() error { + for _, scene := range game.Scenes { + scene.Update() + } + + return nil +} + +func (game *Game) Draw(screen *ebiten.Image) { + for _, scene := range game.Scenes { + scene.Draw(screen) + } +} + +func (g *Game) Layout(newWidth, newHeight int) (int, int) { + return g.ScreenWidth, g.ScreenHeight +} diff --git a/game/grid.go b/game/grid.go new file mode 100644 index 0000000..d195cf5 --- /dev/null +++ b/game/grid.go @@ -0,0 +1,130 @@ +package game + +import ( + "fmt" + "image" + "log" + "openquell/assets" + "openquell/components" + . "openquell/config" + + "github.com/mlange-42/arche/generic" +) + +type Grid struct { + Width int + Height int + Size int + Tilesize int + TilesX int + TilesY int + Map map[image.Point]*assets.Tile +} + +// FIXME: put component addition into extra callable function, to be called +// by game.go in a level-loop. Also remove components of previous level +func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *Grid { + // we use this to turn our tiles into iterable entities, used for + // collision detection, transformation and other things + playermapper := generic.NewMap4[ + components.Position, + components.Velocity, + components.Renderable, + components.Player](game.World) + solidmapper := generic.NewMap4[ + components.Position, + components.Renderable, + components.Tilish, + components.Solid](game.World) + emptymapper := generic.NewMap2[components.Position, components.Tilish](game.World) + + var pos *components.Position + var render *components.Renderable + + for point, tile := range mapslice { + switch tile.Renderable { + case true: + switch { + case tile.Solid: + entity := solidmapper.New() + pos, render, _, _ = solidmapper.Get(entity) + case tile.Player: + entity := playermapper.New() + pos, _, render, _ = playermapper.Get(entity) + fmt.Printf("player start pos: %d,%d\n", point.X*tilesize, point.Y*tilesize) + default: + log.Fatalln("unsupported tile type encountered") + } + + render.Image = tile.Sprite + + default: + // empty cell, this is where the player[s] move. No + // sprite required since every level has a background + // image. + entity := emptymapper.New() + pos, _ = emptymapper.Get(entity) + } + + // however, every tile has a position + pos.Update(point.X*tilesize, point.Y*tilesize, tilesize) + } + + return &Grid{ + Size: len(mapslice), + Tilesize: tilesize, + Width: game.ScreenWidth, + Height: game.ScreenHeight, + TilesX: game.ScreenWidth / tilesize, + TilesY: game.ScreenHeight / tilesize, + Map: mapslice, + } +} + +func (grid *Grid) GetSolidNeighborPosition( + position *components.Position, + velocity *components.Velocity, + solid bool) (bool, *components.Position) { + + if !solid { + return false, nil + } + + fmt.Println(position) + // set to true, ifwe are already on the last tile in the current + // direction, i.e. on the edge of the grid + edge := true + neighborpos := position.Point() + + switch velocity.Direction { + case East: + if neighborpos.X < grid.TilesX { + neighborpos.X++ + edge = false + } + case West: + if neighborpos.X > 0 { + neighborpos.X-- + edge = false + } + case South: + if neighborpos.Y < grid.TilesY { + neighborpos.Y++ + edge = false + } + case North: + if neighborpos.Y > 0 { + neighborpos.Y-- + edge = false + } + } + + newpos := components.NewPosition(neighborpos, grid.Tilesize) + fmt.Println(newpos, edge) + + if !edge && grid.Map[*neighborpos].Solid { + return true, newpos + } + + return false, nil +} diff --git a/game/levels.go b/game/levels.go new file mode 100644 index 0000000..bdb351f --- /dev/null +++ b/game/levels.go @@ -0,0 +1,172 @@ +package game + +import ( + "fmt" + "image" + "log" + "openquell/assets" + "openquell/components" + . "openquell/config" + "strings" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/mlange-42/arche/ecs" + "github.com/mlange-42/arche/filter" +) + +type Level struct { + Grid *Grid + Cellsize, Width, Height int + World *ecs.World + Name string + Description string + Background *ebiten.Image + Mapslice map[image.Point]*assets.Tile +} + +func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { + //grid := NewGrid(game, cellsize, plan) + + return &Level{ + //Grid: grid, + Mapslice: LevelToSlice(game, plan, cellsize), + Cellsize: cellsize, + World: game.World, + Width: game.ScreenWidth, + Height: game.ScreenHeight, + Description: plan.Description, + Background: plan.Background, + } +} + +func (level *Level) Update() { + //playermapper := generic.NewMap3[components.Position, components.Velocity, components.Renderable](level.World) + positionid := ecs.ComponentID[components.Position](level.World) + velocityid := ecs.ComponentID[components.Velocity](level.World) + playerid := ecs.ComponentID[components.Player](level.World) + //solidid := ecs.ComponentID[components.Solid](level.World) + //renderid := ecs.ComponentID[components.Renderable](level.World) + + selector := filter.All(positionid, velocityid, playerid) + query := level.World.Query(selector) + + //tileselector := filter.All(positionid, solidid, renderid) + //tilequery := level.World.Query(tileselector) + + for query.Next() { + playerposition := (*components.Position)(query.Get(positionid)) + velocity := (*components.Velocity)(query.Get(velocityid)) + + switch { + case ebiten.IsKeyPressed(ebiten.KeyRight): + velocity.Change(East) + case ebiten.IsKeyPressed(ebiten.KeyLeft): + velocity.Change(West) + case ebiten.IsKeyPressed(ebiten.KeyDown): + velocity.Change(South) + case ebiten.IsKeyPressed(ebiten.KeyUp): + velocity.Change(North) + // other keys: : switch player, etc + } + + /* + // Use this to check against obstacles, collectibles etc + for tilequery.Next() { + tilepos := (*components.Position)(tilequery.Get(positionid)) + + if velocity.Moving() { + intersects, newpos := tilepos.Intersects(playerposition, velocity) + if intersects { + fmt.Printf("collision detected. tile: %s\n", tilepos) + fmt.Printf(" player: %s\n", playerposition) + fmt.Printf(" new: %s\n", newpos) + + playerposition.Set(newpos) + fmt.Printf(" player new: %s\n", playerposition) + velocity.Change(Stop) + } + } + } + */ + + if velocity.Moving() { + ok, tilepos := level.Grid.GetSolidNeighborPosition(playerposition, velocity, true) + if ok { + intersects, newpos := tilepos.Intersects(playerposition, velocity) + if intersects { + fmt.Printf("collision detected. tile: %s\n", tilepos) + fmt.Printf(" player: %s\n", playerposition) + fmt.Printf(" new: %s\n", newpos) + + playerposition.Set(newpos) + fmt.Printf(" player new: %s\n", playerposition) + velocity.Change(Stop) + } + } + } + + playerposition.Move(velocity) + } +} + +func (level *Level) Position2Point(position *components.Position) image.Point { + return image.Point{ + int(position.X) / level.Cellsize, + int(position.Y) / level.Cellsize, + } +} + +// return the tile the moving object would end up on if indeed moving +func (level *Level) GetTile(position *components.Position, velocity *components.Velocity) *assets.Tile { + newpoint := image.Point{ + int(position.X+velocity.Data.X) / level.Cellsize, + int(position.Y+velocity.Data.Y) / level.Cellsize, + } + + tile := level.Mapslice[newpoint] + return tile +} + +func (level *Level) Draw(screen *ebiten.Image) { + // FIXME: move out + op := &ebiten.DrawImageOptions{} + screen.DrawImage(level.Background, op) + + rid := ecs.ComponentID[components.Renderable](level.World) + pid := ecs.ComponentID[components.Position](level.World) + + selector := filter.All(rid, pid) + + query := level.World.Query(selector) + for query.Next() { + pos := (*components.Position)(query.Get(pid)) + sprite := (*components.Renderable)(query.Get(rid)) + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(pos.X), float64(pos.Y)) + + screen.DrawImage(sprite.Image, op) + } +} + +func (level *Level) SetupGrid(game *Game) { + level.Grid = NewGrid(game, level.Cellsize, level.Mapslice) +} + +func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile { + size := game.ScreenWidth * game.ScreenHeight + mapslice := make(map[image.Point]*assets.Tile, size) + + for y, line := range strings.Split(string(level.Data), "\n") { + if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize { + log.Fatalf("line %d doesn't contain %d tiles, but %d", + y, game.ScreenWidth/tilesize, len(line)) + } + + for x, char := range line { + mapslice[image.Point{x, y}] = assets.Tiles[byte(char)] + } + } + + return mapslice +} diff --git a/game/scene.go b/game/scene.go new file mode 100644 index 0000000..11a2c86 --- /dev/null +++ b/game/scene.go @@ -0,0 +1,51 @@ +package game + +import ( + "fmt" + "openquell/assets" + + "github.com/hajimehoshi/ebiten/v2" +) + +// Wrapper for different screens to be shown, as Welcome, Options, +// About, Select Level and of course the actual Levels. +// Scenes are responsible for screen clearing! That way a scene is able +// to render its content onto the running level, e.g. the options scene +// etc. +type Scene interface { + Update() error + Draw(screen *ebiten.Image) +} + +type LevelScene struct { + Game *Game + CurrentLevel int + Levels []*Level +} + +// Implements the actual playing Scene +func NewLevelScene(game *Game, startlevel int) Scene { + scene := &LevelScene{Game: game, CurrentLevel: startlevel} + + scene.GenerateLevels() + scene.Levels[game.CurrentLevel].SetupGrid(game) + fmt.Println(game.World.Stats().String()) + + return scene +} + +func (scene *LevelScene) GenerateLevels() { + for _, level := range assets.Levels { + scene.Levels = append(scene.Levels, NewLevel(scene.Game, 32, &level)) + } +} + +func (scene *LevelScene) Update() error { + scene.Levels[scene.CurrentLevel].Update() + return nil +} + +func (scene *LevelScene) Draw(screen *ebiten.Image) { + screen.Clear() + scene.Levels[scene.CurrentLevel].Draw(screen) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..20c8c8d --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module openquell + +go 1.21 + +require ( + github.com/hajimehoshi/ebiten/v2 v2.6.5 + github.com/mlange-42/arche v0.10.0 +) + +require ( + github.com/alecthomas/repr v0.3.0 // indirect + github.com/ebitengine/purego v0.5.0 // indirect + github.com/jezek/xgb v1.1.0 // indirect + golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/image v0.12.0 // indirect + golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..65ad455 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8= +github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo= +github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/hajimehoshi/ebiten/v2 v2.6.5 h1:lALv+qhEK3CBWViyiGpz4YcR6slVJEjCiS7sExKZ9OE= +github.com/hajimehoshi/ebiten/v2 v2.6.5/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI= +github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= +github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/mlange-42/arche v0.10.0 h1:fEFDAYMAnWa+xHc1oq4gVcA4PuEQOCGSRXSKITXawMw= +github.com/mlange-42/arche v0.10.0/go.mod h1:gJ5J8vBreqrf4TcBomBFPGnWdE5P3qa4LtxYHn1gDcg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg= +golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= +golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= +golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= +golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 h1:Q6NT8ckDYNcwmi/bmxe+XbiDMXqMRW1xFBtJ+bIpie4= +golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1ae13f7 --- /dev/null +++ b/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "log" + "openquell/game" + + "github.com/hajimehoshi/ebiten/v2" +) + +const ( + width int = 640 + height int = 480 +) + +func main() { + ebiten.SetWindowSize(width, height) + ebiten.SetWindowTitle("openquell") + + g := game.NewGame(width, height, 0) + + err := ebiten.RunGame(g) + if err != nil { + log.Fatalf("unable to run game: %s", err) + } + +} diff --git a/openquell b/openquell new file mode 100755 index 0000000..29df0e5 Binary files /dev/null and b/openquell differ diff --git a/src/00111971.jpg b/src/00111971.jpg new file mode 100644 index 0000000..93a5779 Binary files /dev/null and b/src/00111971.jpg differ diff --git a/src/221000622904_001.webp b/src/221000622904_001.webp new file mode 100644 index 0000000..2ef4135 Binary files /dev/null and b/src/221000622904_001.webp differ diff --git a/src/block-grey.xcf b/src/block-grey.xcf new file mode 100644 index 0000000..7d4da3d Binary files /dev/null and b/src/block-grey.xcf differ diff --git a/src/block-orange-32.xcf b/src/block-orange-32.xcf new file mode 100644 index 0000000..f489fcb Binary files /dev/null and b/src/block-orange-32.xcf differ diff --git a/src/floor.png b/src/floor.png new file mode 100644 index 0000000..a66836d Binary files /dev/null and b/src/floor.png differ diff --git a/src/floor.xcf b/src/floor.xcf new file mode 100644 index 0000000..7dd6642 Binary files /dev/null and b/src/floor.xcf differ diff --git a/src/kugel.xcf b/src/kugel.xcf new file mode 100644 index 0000000..33ab6b9 Binary files /dev/null and b/src/kugel.xcf differ diff --git a/util/generics.go b/util/generics.go new file mode 100644 index 0000000..b14dd19 --- /dev/null +++ b/util/generics.go @@ -0,0 +1,21 @@ +package util + +// find an item in a list, generic variant +func Contains[E comparable](s []E, v E) bool { + for _, vs := range s { + if v == vs { + return true + } + } + + return false +} + +// look if a key in a map exists, generic variant +func Exists[K comparable, V any](m map[K]V, v K) bool { + if _, ok := m[v]; ok { + return true + } + + return false +}