Moved level to separate package (soko).
Moved lineparser to gut package.
This commit is contained in:
parent
a19d33cb9f
commit
1a8f27d787
@ -2,10 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/krampus19/soko"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entity struct {
|
type entity struct {
|
||||||
typ entityType
|
typ soko.EntityType
|
||||||
pos geom.Point
|
pos geom.Point
|
||||||
scr entityLoc
|
scr entityLoc
|
||||||
}
|
}
|
||||||
@ -15,6 +16,6 @@ type entityLoc struct {
|
|||||||
z float32
|
z float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntity(typ entityType, pos geom.Point) *entity {
|
func newEntity(typ soko.EntityType, pos geom.Point) *entity {
|
||||||
return &entity{typ, pos, entityLoc{pos.ToF32(), 0}}
|
return &entity{typ, pos, entityLoc{pos.ToF32(), 0}}
|
||||||
}
|
}
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
|
||||||
)
|
|
||||||
|
|
||||||
type entityType byte
|
|
||||||
type tile byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
entityTypeInvalid entityType = entityType(0)
|
|
||||||
entityTypeNone = '_'
|
|
||||||
entityTypeCharacter = '@'
|
|
||||||
entityTypeEgg = 'X'
|
|
||||||
entityTypeBrick = 'B'
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e entityType) IsValid() bool {
|
|
||||||
switch e {
|
|
||||||
case entityTypeNone:
|
|
||||||
case entityTypeCharacter:
|
|
||||||
case entityTypeEgg:
|
|
||||||
case entityTypeBrick:
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tileInvalid tile = tile(0)
|
|
||||||
tileNothing = '.'
|
|
||||||
tileBasic = '#'
|
|
||||||
tileMagma = '~'
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t tile) IsValid() bool {
|
|
||||||
switch t {
|
|
||||||
case tileNothing:
|
|
||||||
case tileBasic:
|
|
||||||
case tileMagma:
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type level struct {
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
tiles []tile
|
|
||||||
entities []entityType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l level) idxToPos(i int) geom.Point { return geom.Pt(i%l.width, i/l.width) }
|
|
||||||
|
|
||||||
func (l level) posToIdx(p geom.Point) int {
|
|
||||||
if p.X < 0 || p.Y < 0 || p.X >= l.width || p.Y >= l.height {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return p.Y*l.width + p.X
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLevelAsset(r io.Reader) (level, error) {
|
|
||||||
var l level
|
|
||||||
ctx := levelContext{&l}
|
|
||||||
err := parseLines(r, ctx.parse)
|
|
||||||
if err != nil {
|
|
||||||
return level{}, err
|
|
||||||
}
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type levelContext struct {
|
|
||||||
level *level
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *levelContext) parse(p *lineParser) parseLineFn {
|
|
||||||
if p.eof() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch p.peek() {
|
|
||||||
case "level:":
|
|
||||||
return c.parseContent
|
|
||||||
case "":
|
|
||||||
p.next() // skip
|
|
||||||
return c.parse
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *levelContext) parseContent(p *lineParser) parseLineFn {
|
|
||||||
if p.next() != "level:" {
|
|
||||||
return p.emitErr(errors.New("expected level start"))
|
|
||||||
}
|
|
||||||
return c.parseRow
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *levelContext) parseRow(p *lineParser) parseLineFn {
|
|
||||||
if p.eof() {
|
|
||||||
return p.emitErr(errors.New("unexpected end of file"))
|
|
||||||
}
|
|
||||||
line := p.next()
|
|
||||||
if line == ":level" {
|
|
||||||
return c.parse
|
|
||||||
}
|
|
||||||
if c.level.height == 0 {
|
|
||||||
c.level.width = len(line) / 2
|
|
||||||
}
|
|
||||||
return c.addRow(p, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *levelContext) addRow(p *lineParser, line string) parseLineFn {
|
|
||||||
var tiles []tile
|
|
||||||
var entities []entityType
|
|
||||||
for i := 0; i < len(line); i += 2 {
|
|
||||||
tiles = append(tiles, tile(line[i]))
|
|
||||||
entities = append(entities, entityType(line[i+1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, t := range tiles {
|
|
||||||
if !t.IsValid() {
|
|
||||||
return p.emitErr(fmt.Errorf("level contains invalid tile at (%d, %d)", i, c.level.height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, e := range entities {
|
|
||||||
if !e.IsValid() {
|
|
||||||
return p.emitErr(fmt.Errorf("level contains invalid entity type at (%d, %d)", i, c.level.height))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.level.height++
|
|
||||||
c.level.tiles = append(c.level.tiles, tiles...)
|
|
||||||
c.level.entities = append(c.level.entities, entities...)
|
|
||||||
|
|
||||||
return c.parseRow
|
|
||||||
}
|
|
@ -4,12 +4,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"opslag.de/schobers/krampus19/gut"
|
||||||
|
|
||||||
|
"opslag.de/schobers/krampus19/soko"
|
||||||
)
|
)
|
||||||
|
|
||||||
type levelPack struct {
|
type levelPack struct {
|
||||||
name string
|
name string
|
||||||
order []string
|
order []string
|
||||||
levels map[string]level
|
levels map[string]soko.Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p levelPack) find(level string) int {
|
func (p levelPack) find(level string) int {
|
||||||
@ -38,19 +42,19 @@ type parseLevelPackContext struct {
|
|||||||
|
|
||||||
func parseLevelPackAsset(r io.Reader, openLevelFn func(id string) (io.ReadCloser, error)) (levelPack, error) {
|
func parseLevelPackAsset(r io.Reader, openLevelFn func(id string) (io.ReadCloser, error)) (levelPack, error) {
|
||||||
ctx := &parseLevelPackContext{}
|
ctx := &parseLevelPackContext{}
|
||||||
err := parseLines(r, ctx.Parse)
|
err := gut.ParseLines(r, ctx.Parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return levelPack{}, err
|
return levelPack{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pack := levelPack{name: ctx.name, levels: map[string]level{}}
|
pack := levelPack{name: ctx.name, levels: map[string]soko.Level{}}
|
||||||
for _, id := range ctx.levels {
|
for _, id := range ctx.levels {
|
||||||
rc, err := openLevelFn(id)
|
rc, err := openLevelFn(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return levelPack{}, err
|
return levelPack{}, err
|
||||||
}
|
}
|
||||||
defer rc.Close()
|
defer rc.Close()
|
||||||
level, err := parseLevelAsset(rc)
|
level, err := soko.ParseLevel(rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return levelPack{}, err
|
return levelPack{}, err
|
||||||
}
|
}
|
||||||
@ -61,65 +65,65 @@ func parseLevelPackAsset(r io.Reader, openLevelFn func(id string) (io.ReadCloser
|
|||||||
return pack, nil
|
return pack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *parseLevelPackContext) Parse(p *lineParser) parseLineFn {
|
func (c *parseLevelPackContext) Parse(p *gut.LineParser) gut.ParseLineFn {
|
||||||
if p.skipSpaceEOF() {
|
if p.SkipSpaceEOF() {
|
||||||
return p.emitErr(errors.New("empty level pack"))
|
return p.EmitErr(errors.New("empty level pack"))
|
||||||
}
|
}
|
||||||
return c.parse
|
return c.parse
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *parseLevelPackContext) parse(p *lineParser) parseLineFn {
|
func (c *parseLevelPackContext) parse(p *gut.LineParser) gut.ParseLineFn {
|
||||||
const levelsTag = "levels:"
|
const levelsTag = "levels:"
|
||||||
const nameTag = "name:"
|
const nameTag = "name:"
|
||||||
|
|
||||||
if p.skipSpaceEOF() {
|
if p.SkipSpaceEOF() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
line := p.next()
|
line := p.Next()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, nameTag):
|
case strings.HasPrefix(line, nameTag):
|
||||||
c.name = strings.TrimSpace(line[len(nameTag):])
|
c.name = strings.TrimSpace(line[len(nameTag):])
|
||||||
return c.parse
|
return c.parse
|
||||||
case strings.HasPrefix(line, levelsTag):
|
case strings.HasPrefix(line, levelsTag):
|
||||||
return skipSpaceBeforeContent(c.parseLevels)
|
return gut.SkipSpaceBeforeContent(c.parseLevels)
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("tag not allowed"))
|
return p.EmitErr(errors.New("tag not allowed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *parseLevelPackContext) parseLevels(p *lineParser) parseLineFn {
|
func (c *parseLevelPackContext) parseLevels(p *gut.LineParser) gut.ParseLineFn {
|
||||||
const levelTag = "level:"
|
const levelTag = "level:"
|
||||||
const levelsEndTag = ":levels"
|
const levelsEndTag = ":levels"
|
||||||
|
|
||||||
line := p.next()
|
line := p.Next()
|
||||||
switch line {
|
switch line {
|
||||||
case levelTag:
|
case levelTag:
|
||||||
return skipSpaceBeforeContent(c.parseLevel)
|
return gut.SkipSpaceBeforeContent(c.parseLevel)
|
||||||
case levelsEndTag:
|
case levelsEndTag:
|
||||||
return c.parse
|
return c.parse
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("tag not allowed"))
|
return p.EmitErr(errors.New("tag not allowed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *parseLevelPackContext) parseLevel(p *lineParser) parseLineFn {
|
func (c *parseLevelPackContext) parseLevel(p *gut.LineParser) gut.ParseLineFn {
|
||||||
const idTag = "id:"
|
const idTag = "id:"
|
||||||
|
|
||||||
line := p.next()
|
line := p.Next()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, idTag):
|
case strings.HasPrefix(line, idTag):
|
||||||
c.levels = append(c.levels, strings.TrimSpace(line[len(idTag):]))
|
c.levels = append(c.levels, strings.TrimSpace(line[len(idTag):]))
|
||||||
return skipSpaceBeforeContent(c.parseLevelEnd)
|
return gut.SkipSpaceBeforeContent(c.parseLevelEnd)
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("must have an id tag"))
|
return p.EmitErr(errors.New("must have an id tag"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *parseLevelPackContext) parseLevelEnd(p *lineParser) parseLineFn {
|
func (c *parseLevelPackContext) parseLevelEnd(p *gut.LineParser) gut.ParseLineFn {
|
||||||
const levelEndTag = ":level"
|
const levelEndTag = ":level"
|
||||||
|
|
||||||
line := p.next()
|
line := p.Next()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, levelEndTag):
|
case strings.HasPrefix(line, levelEndTag):
|
||||||
return skipSpaceBeforeContent(c.parseLevels)
|
return gut.SkipSpaceBeforeContent(c.parseLevels)
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("tag not allowed"))
|
return p.EmitErr(errors.New("tag not allowed"))
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type lineParser struct {
|
|
||||||
lines []string
|
|
||||||
i int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var errUnexpectedEnd = errors.New("unexpected end of file")
|
|
||||||
|
|
||||||
func (p *lineParser) eof() bool { return p.i == len(p.lines) }
|
|
||||||
func (p *lineParser) peek() string { return p.lines[p.i] }
|
|
||||||
func (p *lineParser) next() string {
|
|
||||||
i := p.i
|
|
||||||
p.i++
|
|
||||||
return p.lines[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lineParser) emitErr(err error) parseLineFn {
|
|
||||||
p.err = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *lineParser) skipSpaceEOF() bool {
|
|
||||||
for !p.eof() && len(strings.TrimSpace(p.peek())) == 0 {
|
|
||||||
p.next()
|
|
||||||
}
|
|
||||||
return p.eof()
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipSpaceBeforeContent(next parseLineFn) parseLineFn {
|
|
||||||
return func(p *lineParser) parseLineFn {
|
|
||||||
if p.skipSpaceEOF() {
|
|
||||||
return p.emitErr(errUnexpectedEnd)
|
|
||||||
}
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type parseLineFn func(p *lineParser) parseLineFn
|
|
||||||
|
|
||||||
func parseLines(r io.Reader, fn parseLineFn) error {
|
|
||||||
content, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(content), "\n")
|
|
||||||
for i, line := range lines {
|
|
||||||
lines[i] = strings.TrimRight(line, "\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
parser := &lineParser{lines: lines}
|
|
||||||
for fn != nil {
|
|
||||||
fn = fn(parser)
|
|
||||||
}
|
|
||||||
return parser.err
|
|
||||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/krampus19/alui"
|
"opslag.de/schobers/krampus19/alui"
|
||||||
|
"opslag.de/schobers/krampus19/soko"
|
||||||
)
|
)
|
||||||
|
|
||||||
type playLevel struct {
|
type playLevel struct {
|
||||||
@ -121,13 +122,13 @@ func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
l.offset = geom.PointF32{}
|
l.offset = geom.PointF32{}
|
||||||
|
|
||||||
level := l.state.Level()
|
level := l.state.Level()
|
||||||
var contentCenter = l.posToScreenF32(geom.PtF32(.5*float32(level.width), .5*float32(level.height)), 0)
|
var contentCenter = l.posToScreenF32(geom.PtF32(.5*float32(level.Width), .5*float32(level.Height)), 0)
|
||||||
var content = geom.RectF32(contentCenter.X, contentCenter.Y, contentCenter.X, contentCenter.Y)
|
var content = geom.RectF32(contentCenter.X, contentCenter.Y, contentCenter.X, contentCenter.Y)
|
||||||
for idx, tile := range l.state.Level().tiles {
|
for idx, tile := range l.state.Level().Tiles {
|
||||||
if tile == tileNothing || tile == tileInvalid {
|
if tile == soko.TileNothing || tile == soko.TileInvalid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := level.idxToPos(idx).ToF32()
|
pos := level.IdxToPos(idx).ToF32()
|
||||||
bottomLeft := l.posToScreenF32(pos.Add2D(-1.5, 1.5), 100)
|
bottomLeft := l.posToScreenF32(pos.Add2D(-1.5, 1.5), 100)
|
||||||
content.Min = geom.MinPtF32(content.Min, bottomLeft)
|
content.Min = geom.MinPtF32(content.Min, bottomLeft)
|
||||||
content.Max = geom.MaxPtF32(content.Max, bottomLeft)
|
content.Max = geom.MaxPtF32(content.Max, bottomLeft)
|
||||||
@ -191,17 +192,17 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, t := range level.tiles {
|
for i, t := range level.Tiles {
|
||||||
pos := geom.Pt(i%level.width, i/level.width)
|
pos := geom.Pt(i%level.Width, i/level.Width)
|
||||||
scr := entityLoc{pos.ToF32(), 0}
|
scr := entityLoc{pos.ToF32(), 0}
|
||||||
switch t {
|
switch t {
|
||||||
case tileBasic:
|
case soko.TileBasic:
|
||||||
if l.state.IsNextToMagma(pos) {
|
if l.state.IsNextToMagma(pos) {
|
||||||
l.drawSprite("lava_brick", "magma", scr)
|
l.drawSprite("lava_brick", "magma", scr)
|
||||||
} else {
|
} else {
|
||||||
l.drawSprite("lava_brick", "lava_brick", scr)
|
l.drawSprite("lava_brick", "lava_brick", scr)
|
||||||
}
|
}
|
||||||
case tileMagma:
|
case soko.TileMagma:
|
||||||
l.drawSprite("magma", "magma", scr)
|
l.drawSprite("magma", "magma", scr)
|
||||||
brick := l.state.FindSunkenBrick(pos)
|
brick := l.state.FindSunkenBrick(pos)
|
||||||
if brick != nil {
|
if brick != nil {
|
||||||
@ -224,11 +225,11 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
|
|
||||||
for _, e := range entities {
|
for _, e := range entities {
|
||||||
switch e.typ {
|
switch e.typ {
|
||||||
case entityTypeBrick:
|
case soko.EntityTypeBrick:
|
||||||
l.drawSprite("brick", "brick", e.scr)
|
l.drawSprite("brick", "brick", e.scr)
|
||||||
case entityTypeCharacter:
|
case soko.EntityTypeCharacter:
|
||||||
l.drawSprite("dragon", "dragon", e.scr)
|
l.drawSprite("dragon", "dragon", e.scr)
|
||||||
case entityTypeEgg:
|
case soko.EntityTypeEgg:
|
||||||
l.drawSprite("egg", "egg", e.scr)
|
l.drawSprite("egg", "egg", e.scr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@ import (
|
|||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/krampus19/gut"
|
"opslag.de/schobers/krampus19/gut"
|
||||||
|
"opslag.de/schobers/krampus19/soko"
|
||||||
)
|
)
|
||||||
|
|
||||||
type playLevelState struct {
|
type playLevelState struct {
|
||||||
ctx *Context
|
ctx *Context
|
||||||
|
|
||||||
pack levelPack
|
pack levelPack
|
||||||
level level
|
level soko.Level
|
||||||
player *entity
|
player *entity
|
||||||
egg *entity
|
egg *entity
|
||||||
bricks entityList
|
bricks entityList
|
||||||
@ -66,21 +67,21 @@ func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func(
|
|||||||
s.bricks = nil
|
s.bricks = nil
|
||||||
s.sunken = nil
|
s.sunken = nil
|
||||||
s.splash = map[geom.Point]*splashAnimation{}
|
s.splash = map[geom.Point]*splashAnimation{}
|
||||||
for i, e := range s.level.entities {
|
for i, e := range s.level.Entities {
|
||||||
switch e {
|
switch e {
|
||||||
case entityTypeBrick:
|
case soko.EntityTypeBrick:
|
||||||
s.bricks = append(s.bricks, newEntity(e, s.level.idxToPos(i)))
|
s.bricks = append(s.bricks, newEntity(e, s.level.IdxToPos(i)))
|
||||||
case entityTypeCharacter:
|
case soko.EntityTypeCharacter:
|
||||||
s.player = newEntity(e, s.level.idxToPos(i))
|
s.player = newEntity(e, s.level.IdxToPos(i))
|
||||||
case entityTypeEgg:
|
case soko.EntityTypeEgg:
|
||||||
s.egg = newEntity(e, s.level.idxToPos(i))
|
s.egg = newEntity(e, s.level.IdxToPos(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.keysDown = keyPressedState{}
|
s.keysDown = keyPressedState{}
|
||||||
s.onComplete = onComplete
|
s.onComplete = onComplete
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) Level() level { return s.level }
|
func (s *playLevelState) Level() soko.Level { return s.level }
|
||||||
|
|
||||||
func (s *playLevelState) PressKey(key allg5.Key) {
|
func (s *playLevelState) PressKey(key allg5.Key) {
|
||||||
s.keysDown[key] = true
|
s.keysDown[key] = true
|
||||||
@ -176,16 +177,16 @@ func (s *playLevelState) canMove(from, dir geom.Point) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool) bool {
|
func (s *playLevelState) checkTile(pos geom.Point, check func(pos geom.Point, idx int, t soko.Tile) bool) bool {
|
||||||
return s.checkTileNotFound(pos, check, false)
|
return s.checkTileNotFound(pos, check, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool, notFound bool) bool {
|
func (s *playLevelState) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t soko.Tile) bool, notFound bool) bool {
|
||||||
idx := s.level.posToIdx(pos)
|
idx := s.level.PosToIdx(pos)
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
return notFound
|
return notFound
|
||||||
}
|
}
|
||||||
return check(pos, idx, s.level.tiles[idx])
|
return check(pos, idx, s.level.Tiles[idx])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
||||||
@ -199,31 +200,33 @@ func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
|||||||
return s.sunken.FindEntity(pos)
|
return s.sunken.FindEntity(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) isObstructed(pos geom.Point, idx int, t tile) bool {
|
func (s *playLevelState) isObstructed(pos geom.Point, idx int, t soko.Tile) bool {
|
||||||
if s.bricks.FindEntity(pos) != nil {
|
if s.bricks.FindEntity(pos) != nil {
|
||||||
return true // brick
|
return true // brick
|
||||||
}
|
}
|
||||||
switch s.level.tiles[idx] {
|
switch s.level.Tiles[idx] {
|
||||||
case tileMagma:
|
case soko.TileMagma:
|
||||||
return false
|
return false
|
||||||
case tileBasic:
|
case soko.TileBasic:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) isMagma(pos geom.Point, idx int, t tile) bool { return t == tileMagma }
|
func (s *playLevelState) isMagma(pos geom.Point, idx int, t soko.Tile) bool {
|
||||||
|
return t == soko.TileMagma
|
||||||
|
}
|
||||||
|
|
||||||
func (s *playLevelState) isSolidTile(pos geom.Point, idx int, t tile) bool {
|
func (s *playLevelState) isSolidTile(pos geom.Point, idx int, t soko.Tile) bool {
|
||||||
switch t {
|
switch t {
|
||||||
case tileBasic:
|
case soko.TileBasic:
|
||||||
return true
|
return true
|
||||||
case tileMagma:
|
case soko.TileMagma:
|
||||||
return s.sunken.FindEntity(pos) != nil
|
return s.sunken.FindEntity(pos) != nil
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t tile) bool {
|
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t soko.Tile) bool {
|
||||||
return t == tileMagma && s.sunken.FindEntity(pos) == nil
|
return t == soko.TileMagma && s.sunken.FindEntity(pos) == nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/krampus19/gut"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sprite struct {
|
type sprite struct {
|
||||||
@ -33,7 +34,7 @@ type spritePart struct {
|
|||||||
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
||||||
var l sprite
|
var l sprite
|
||||||
ctx := spriteContext{&l, nil}
|
ctx := spriteContext{&l, nil}
|
||||||
err := parseLines(r, ctx.parse)
|
err := gut.ParseLines(r, ctx.parse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sprite{}, err
|
return sprite{}, err
|
||||||
}
|
}
|
||||||
@ -45,44 +46,44 @@ type spriteContext struct {
|
|||||||
part *spritePart
|
part *spritePart
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *spriteContext) parse(p *lineParser) parseLineFn {
|
func (c *spriteContext) parse(p *gut.LineParser) gut.ParseLineFn {
|
||||||
if p.skipSpaceEOF() {
|
if p.SkipSpaceEOF() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
line := p.peek()
|
line := p.Peek()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, "sprite:"):
|
case strings.HasPrefix(line, "sprite:"):
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parseContent
|
return c.parseContent
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *spriteContext) parseContent(p *lineParser) parseLineFn {
|
func (c *spriteContext) parseContent(p *gut.LineParser) gut.ParseLineFn {
|
||||||
const partTag = "part:"
|
const partTag = "part:"
|
||||||
const textureTag = "texture:"
|
const textureTag = "texture:"
|
||||||
const spriteEndTag = ":sprite"
|
const spriteEndTag = ":sprite"
|
||||||
if p.skipSpaceEOF() {
|
if p.SkipSpaceEOF() {
|
||||||
return p.emitErr(errUnexpectedEnd)
|
return p.EmitErr(gut.ErrUnexpectedEnd)
|
||||||
}
|
}
|
||||||
line := p.peek()
|
line := p.Peek()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, textureTag):
|
case strings.HasPrefix(line, textureTag):
|
||||||
c.sprite.texture = strings.TrimSpace(line[len(textureTag):])
|
c.sprite.texture = strings.TrimSpace(line[len(textureTag):])
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parseContent
|
return c.parseContent
|
||||||
case line == partTag:
|
case line == partTag:
|
||||||
p.next()
|
p.Next()
|
||||||
c.part = &spritePart{}
|
c.part = &spritePart{}
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
case line == spriteEndTag:
|
case line == spriteEndTag:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("unexpected content of sprite"))
|
return p.EmitErr(errors.New("unexpected content of sprite"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
func (c *spriteContext) parsePart(p *gut.LineParser) gut.ParseLineFn {
|
||||||
mustAtois := func(s ...string) []int {
|
mustAtois := func(s ...string) []int {
|
||||||
res := make([]int, len(s))
|
res := make([]int, len(s))
|
||||||
var err error
|
var err error
|
||||||
@ -110,14 +111,14 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
const anchorTag = "anchor:"
|
const anchorTag = "anchor:"
|
||||||
const scaleTag = "scale:"
|
const scaleTag = "scale:"
|
||||||
const partEndTag = ":part"
|
const partEndTag = ":part"
|
||||||
if p.skipSpaceEOF() {
|
if p.SkipSpaceEOF() {
|
||||||
return p.emitErr(errUnexpectedEnd)
|
return p.EmitErr(gut.ErrUnexpectedEnd)
|
||||||
}
|
}
|
||||||
line := p.peek()
|
line := p.Peek()
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, nameTag):
|
case strings.HasPrefix(line, nameTag):
|
||||||
c.part.name = strings.TrimSpace(line[len(nameTag):])
|
c.part.name = strings.TrimSpace(line[len(nameTag):])
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
case strings.HasPrefix(line, subTextureTag):
|
case strings.HasPrefix(line, subTextureTag):
|
||||||
var coords = mustCoords(line[len(subTextureTag):])
|
var coords = mustCoords(line[len(subTextureTag):])
|
||||||
@ -125,7 +126,7 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
panic("expected four coordinates (min x, min y, size x, size y)")
|
panic("expected four coordinates (min x, min y, size x, size y)")
|
||||||
}
|
}
|
||||||
c.part.sub = geom.Rect(coords[0], coords[1], coords[0]+coords[2], coords[1]+coords[3])
|
c.part.sub = geom.Rect(coords[0], coords[1], coords[0]+coords[2], coords[1]+coords[3])
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
case strings.HasPrefix(line, anchorTag):
|
case strings.HasPrefix(line, anchorTag):
|
||||||
var coords = mustCoords(line[len(anchorTag):])
|
var coords = mustCoords(line[len(anchorTag):])
|
||||||
@ -133,16 +134,16 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
|||||||
panic("expected two coordinates (min x, min y)")
|
panic("expected two coordinates (min x, min y)")
|
||||||
}
|
}
|
||||||
c.part.anchor = geom.Pt(coords[0], coords[1])
|
c.part.anchor = geom.Pt(coords[0], coords[1])
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
case strings.HasPrefix(line, scaleTag):
|
case strings.HasPrefix(line, scaleTag):
|
||||||
c.part.scale = mustAtof(line[len(scaleTag):])
|
c.part.scale = mustAtof(line[len(scaleTag):])
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parsePart
|
return c.parsePart
|
||||||
case line == partEndTag:
|
case line == partEndTag:
|
||||||
c.sprite.parts = append(c.sprite.parts, *c.part)
|
c.sprite.parts = append(c.sprite.parts, *c.part)
|
||||||
p.next()
|
p.Next()
|
||||||
return c.parseContent
|
return c.parseContent
|
||||||
}
|
}
|
||||||
return p.emitErr(errors.New("unexpected content of part"))
|
return p.EmitErr(errors.New("unexpected content of part"))
|
||||||
}
|
}
|
||||||
|
66
gut/lineparser.go
Normal file
66
gut/lineparser.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package gut
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LineParser struct {
|
||||||
|
lines []string
|
||||||
|
i int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrUnexpectedEnd = errors.New("unexpected end of file")
|
||||||
|
|
||||||
|
func (p *LineParser) EOF() bool { return p.i == len(p.lines) }
|
||||||
|
|
||||||
|
func (p *LineParser) EmitErr(err error) ParseLineFn {
|
||||||
|
p.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LineParser) Next() string {
|
||||||
|
i := p.i
|
||||||
|
p.i++
|
||||||
|
return p.lines[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LineParser) Peek() string { return p.lines[p.i] }
|
||||||
|
|
||||||
|
func (p *LineParser) SkipSpaceEOF() bool {
|
||||||
|
for !p.EOF() && len(strings.TrimSpace(p.Peek())) == 0 {
|
||||||
|
p.Next()
|
||||||
|
}
|
||||||
|
return p.EOF()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipSpaceBeforeContent(next ParseLineFn) ParseLineFn {
|
||||||
|
return func(p *LineParser) ParseLineFn {
|
||||||
|
if p.SkipSpaceEOF() {
|
||||||
|
return p.EmitErr(ErrUnexpectedEnd)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseLineFn func(p *LineParser) ParseLineFn
|
||||||
|
|
||||||
|
func ParseLines(r io.Reader, fn ParseLineFn) error {
|
||||||
|
content, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
lines[i] = strings.TrimRight(line, "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := &LineParser{lines: lines}
|
||||||
|
for fn != nil {
|
||||||
|
fn = fn(parser)
|
||||||
|
}
|
||||||
|
return parser.err
|
||||||
|
}
|
62
soko/level.go
Normal file
62
soko/level.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityType byte
|
||||||
|
type Tile byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntityTypeInvalid EntityType = EntityType(0)
|
||||||
|
EntityTypeNone = '_'
|
||||||
|
EntityTypeCharacter = '@'
|
||||||
|
EntityTypeEgg = 'X'
|
||||||
|
EntityTypeBrick = 'B'
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e EntityType) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case EntityTypeNone:
|
||||||
|
case EntityTypeCharacter:
|
||||||
|
case EntityTypeEgg:
|
||||||
|
case EntityTypeBrick:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TileInvalid Tile = Tile(0)
|
||||||
|
TileNothing = '.'
|
||||||
|
TileBasic = '#'
|
||||||
|
TileMagma = '~'
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Tile) IsValid() bool {
|
||||||
|
switch t {
|
||||||
|
case TileNothing:
|
||||||
|
case TileBasic:
|
||||||
|
case TileMagma:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level struct {
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
Tiles []Tile
|
||||||
|
Entities []EntityType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Level) IdxToPos(i int) geom.Point { return geom.Pt(i%l.Width, i/l.Width) }
|
||||||
|
|
||||||
|
func (l Level) PosToIdx(p geom.Point) int {
|
||||||
|
if p.X < 0 || p.Y < 0 || p.X >= l.Width || p.Y >= l.Height {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return p.Y*l.Width + p.X
|
||||||
|
}
|
85
soko/levelparser.go
Normal file
85
soko/levelparser.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"opslag.de/schobers/krampus19/gut"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseLevel(r io.Reader) (Level, error) {
|
||||||
|
var l Level
|
||||||
|
ctx := levelContext{&l}
|
||||||
|
err := gut.ParseLines(r, ctx.parse)
|
||||||
|
if err != nil {
|
||||||
|
return Level{}, err
|
||||||
|
}
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type levelContext struct {
|
||||||
|
level *Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *levelContext) parse(p *gut.LineParser) gut.ParseLineFn {
|
||||||
|
if p.EOF() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch p.Peek() {
|
||||||
|
case "level:":
|
||||||
|
return c.parseContent
|
||||||
|
case "":
|
||||||
|
p.Next() // skip
|
||||||
|
return c.parse
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *levelContext) parseContent(p *gut.LineParser) gut.ParseLineFn {
|
||||||
|
if p.Next() != "level:" {
|
||||||
|
return p.EmitErr(errors.New("expected level start"))
|
||||||
|
}
|
||||||
|
return c.parseRow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *levelContext) parseRow(p *gut.LineParser) gut.ParseLineFn {
|
||||||
|
if p.EOF() {
|
||||||
|
return p.EmitErr(errors.New("unexpected end of file"))
|
||||||
|
}
|
||||||
|
line := p.Next()
|
||||||
|
if line == ":level" {
|
||||||
|
return c.parse
|
||||||
|
}
|
||||||
|
if c.level.Height == 0 {
|
||||||
|
c.level.Width = len(line) / 2
|
||||||
|
}
|
||||||
|
return c.addRow(p, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *levelContext) addRow(p *gut.LineParser, line string) gut.ParseLineFn {
|
||||||
|
var tiles []Tile
|
||||||
|
var entities []EntityType
|
||||||
|
for i := 0; i < len(line); i += 2 {
|
||||||
|
tiles = append(tiles, Tile(line[i]))
|
||||||
|
entities = append(entities, EntityType(line[i+1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range tiles {
|
||||||
|
if !t.IsValid() {
|
||||||
|
return p.EmitErr(fmt.Errorf("level contains invalid Tile at (%d, %d)", i, c.level.Height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, e := range entities {
|
||||||
|
if !e.IsValid() {
|
||||||
|
return p.EmitErr(fmt.Errorf("level contains invalid entity type at (%d, %d)", i, c.level.Height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.level.Height++
|
||||||
|
c.level.Tiles = append(c.level.Tiles, tiles...)
|
||||||
|
c.level.Entities = append(c.level.Entities, entities...)
|
||||||
|
|
||||||
|
return c.parseRow
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user