Moved level to separate package (soko).
Moved lineparser to gut package.
This commit is contained in:
parent
a19d33cb9f
commit
a4058df9c8
@ -2,10 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/soko"
|
||||
)
|
||||
|
||||
type entity struct {
|
||||
typ entityType
|
||||
typ soko.EntityType
|
||||
pos geom.Point
|
||||
scr entityLoc
|
||||
}
|
||||
@ -15,6 +16,6 @@ type entityLoc struct {
|
||||
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}}
|
||||
}
|
||||
|
@ -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"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
|
||||
"opslag.de/schobers/krampus19/soko"
|
||||
)
|
||||
|
||||
type levelPack struct {
|
||||
name string
|
||||
order []string
|
||||
levels map[string]level
|
||||
levels map[string]soko.Level
|
||||
}
|
||||
|
||||
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) {
|
||||
ctx := &parseLevelPackContext{}
|
||||
err := parseLines(r, ctx.Parse)
|
||||
err := gut.ParseLines(r, ctx.Parse)
|
||||
if err != nil {
|
||||
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 {
|
||||
rc, err := openLevelFn(id)
|
||||
if err != nil {
|
||||
return levelPack{}, err
|
||||
}
|
||||
defer rc.Close()
|
||||
level, err := parseLevelAsset(rc)
|
||||
level, err := soko.ParseLevel(rc)
|
||||
if err != nil {
|
||||
return levelPack{}, err
|
||||
}
|
||||
@ -61,65 +65,65 @@ func parseLevelPackAsset(r io.Reader, openLevelFn func(id string) (io.ReadCloser
|
||||
return pack, nil
|
||||
}
|
||||
|
||||
func (c *parseLevelPackContext) Parse(p *lineParser) parseLineFn {
|
||||
if p.skipSpaceEOF() {
|
||||
return p.emitErr(errors.New("empty level pack"))
|
||||
func (c *parseLevelPackContext) Parse(p *gut.LineParser) gut.ParseLineFn {
|
||||
if p.SkipSpaceEOF() {
|
||||
return p.EmitErr(errors.New("empty level pack"))
|
||||
}
|
||||
return c.parse
|
||||
}
|
||||
|
||||
func (c *parseLevelPackContext) parse(p *lineParser) parseLineFn {
|
||||
func (c *parseLevelPackContext) parse(p *gut.LineParser) gut.ParseLineFn {
|
||||
const levelsTag = "levels:"
|
||||
const nameTag = "name:"
|
||||
|
||||
if p.skipSpaceEOF() {
|
||||
if p.SkipSpaceEOF() {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := p.next()
|
||||
line := p.Next()
|
||||
switch {
|
||||
case strings.HasPrefix(line, nameTag):
|
||||
c.name = strings.TrimSpace(line[len(nameTag):])
|
||||
return c.parse
|
||||
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 levelsEndTag = ":levels"
|
||||
|
||||
line := p.next()
|
||||
line := p.Next()
|
||||
switch line {
|
||||
case levelTag:
|
||||
return skipSpaceBeforeContent(c.parseLevel)
|
||||
return gut.SkipSpaceBeforeContent(c.parseLevel)
|
||||
case levelsEndTag:
|
||||
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:"
|
||||
|
||||
line := p.next()
|
||||
line := p.Next()
|
||||
switch {
|
||||
case strings.HasPrefix(line, 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"
|
||||
|
||||
line := p.next()
|
||||
line := p.Next()
|
||||
switch {
|
||||
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/geom"
|
||||
"opslag.de/schobers/krampus19/alui"
|
||||
"opslag.de/schobers/krampus19/soko"
|
||||
)
|
||||
|
||||
type playLevel struct {
|
||||
@ -121,13 +122,13 @@ func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
l.offset = geom.PointF32{}
|
||||
|
||||
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)
|
||||
for idx, tile := range l.state.Level().tiles {
|
||||
if tile == tileNothing || tile == tileInvalid {
|
||||
for idx, tile := range l.state.Level().Tiles {
|
||||
if tile == soko.TileNothing || tile == soko.TileInvalid {
|
||||
continue
|
||||
}
|
||||
pos := level.idxToPos(idx).ToF32()
|
||||
pos := level.IdxToPos(idx).ToF32()
|
||||
bottomLeft := l.posToScreenF32(pos.Add2D(-1.5, 1.5), 100)
|
||||
content.Min = geom.MinPtF32(content.Min, 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 {
|
||||
pos := geom.Pt(i%level.width, i/level.width)
|
||||
for i, t := range level.Tiles {
|
||||
pos := geom.Pt(i%level.Width, i/level.Width)
|
||||
scr := entityLoc{pos.ToF32(), 0}
|
||||
switch t {
|
||||
case tileBasic:
|
||||
case soko.TileBasic:
|
||||
if l.state.IsNextToMagma(pos) {
|
||||
l.drawSprite("lava_brick", "magma", scr)
|
||||
} else {
|
||||
l.drawSprite("lava_brick", "lava_brick", scr)
|
||||
}
|
||||
case tileMagma:
|
||||
case soko.TileMagma:
|
||||
l.drawSprite("magma", "magma", scr)
|
||||
brick := l.state.FindSunkenBrick(pos)
|
||||
if brick != nil {
|
||||
@ -224,11 +225,11 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
|
||||
for _, e := range entities {
|
||||
switch e.typ {
|
||||
case entityTypeBrick:
|
||||
case soko.EntityTypeBrick:
|
||||
l.drawSprite("brick", "brick", e.scr)
|
||||
case entityTypeCharacter:
|
||||
case soko.EntityTypeCharacter:
|
||||
l.drawSprite("dragon", "dragon", e.scr)
|
||||
case entityTypeEgg:
|
||||
case soko.EntityTypeEgg:
|
||||
l.drawSprite("egg", "egg", e.scr)
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,14 @@ import (
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
"opslag.de/schobers/krampus19/soko"
|
||||
)
|
||||
|
||||
type playLevelState struct {
|
||||
ctx *Context
|
||||
|
||||
pack levelPack
|
||||
level level
|
||||
level soko.Level
|
||||
player *entity
|
||||
egg *entity
|
||||
bricks entityList
|
||||
@ -66,21 +67,21 @@ func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func(
|
||||
s.bricks = nil
|
||||
s.sunken = nil
|
||||
s.splash = map[geom.Point]*splashAnimation{}
|
||||
for i, e := range s.level.entities {
|
||||
for i, e := range s.level.Entities {
|
||||
switch e {
|
||||
case entityTypeBrick:
|
||||
s.bricks = append(s.bricks, newEntity(e, s.level.idxToPos(i)))
|
||||
case entityTypeCharacter:
|
||||
s.player = newEntity(e, s.level.idxToPos(i))
|
||||
case entityTypeEgg:
|
||||
s.egg = newEntity(e, s.level.idxToPos(i))
|
||||
case soko.EntityTypeBrick:
|
||||
s.bricks = append(s.bricks, newEntity(e, s.level.IdxToPos(i)))
|
||||
case soko.EntityTypeCharacter:
|
||||
s.player = newEntity(e, s.level.IdxToPos(i))
|
||||
case soko.EntityTypeEgg:
|
||||
s.egg = newEntity(e, s.level.IdxToPos(i))
|
||||
}
|
||||
}
|
||||
s.keysDown = keyPressedState{}
|
||||
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) {
|
||||
s.keysDown[key] = true
|
||||
@ -176,16 +177,16 @@ func (s *playLevelState) canMove(from, dir geom.Point) bool {
|
||||
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)
|
||||
}
|
||||
|
||||
func (s *playLevelState) checkTileNotFound(pos geom.Point, check func(pos geom.Point, idx int, t tile) bool, notFound bool) bool {
|
||||
idx := s.level.posToIdx(pos)
|
||||
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)
|
||||
if idx == -1 {
|
||||
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 {
|
||||
@ -199,31 +200,33 @@ func (s *playLevelState) findEntityAt(pos geom.Point) *entity {
|
||||
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 {
|
||||
return true // brick
|
||||
}
|
||||
switch s.level.tiles[idx] {
|
||||
case tileMagma:
|
||||
switch s.level.Tiles[idx] {
|
||||
case soko.TileMagma:
|
||||
return false
|
||||
case tileBasic:
|
||||
case soko.TileBasic:
|
||||
return false
|
||||
}
|
||||
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 {
|
||||
case tileBasic:
|
||||
case soko.TileBasic:
|
||||
return true
|
||||
case tileMagma:
|
||||
case soko.TileMagma:
|
||||
return s.sunken.FindEntity(pos) != nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t tile) bool {
|
||||
return t == tileMagma && s.sunken.FindEntity(pos) == nil
|
||||
func (s *playLevelState) wouldBrickSink(pos geom.Point, idx int, t soko.Tile) bool {
|
||||
return t == soko.TileMagma && s.sunken.FindEntity(pos) == nil
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
"opslag.de/schobers/krampus19/gut"
|
||||
)
|
||||
|
||||
type sprite struct {
|
||||
@ -33,7 +34,7 @@ type spritePart struct {
|
||||
func loadSpriteAsset(r io.Reader) (sprite, error) {
|
||||
var l sprite
|
||||
ctx := spriteContext{&l, nil}
|
||||
err := parseLines(r, ctx.parse)
|
||||
err := gut.ParseLines(r, ctx.parse)
|
||||
if err != nil {
|
||||
return sprite{}, err
|
||||
}
|
||||
@ -45,44 +46,44 @@ type spriteContext struct {
|
||||
part *spritePart
|
||||
}
|
||||
|
||||
func (c *spriteContext) parse(p *lineParser) parseLineFn {
|
||||
if p.skipSpaceEOF() {
|
||||
func (c *spriteContext) parse(p *gut.LineParser) gut.ParseLineFn {
|
||||
if p.SkipSpaceEOF() {
|
||||
return nil
|
||||
}
|
||||
line := p.peek()
|
||||
line := p.Peek()
|
||||
switch {
|
||||
case strings.HasPrefix(line, "sprite:"):
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parseContent
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *spriteContext) parseContent(p *lineParser) parseLineFn {
|
||||
func (c *spriteContext) parseContent(p *gut.LineParser) gut.ParseLineFn {
|
||||
const partTag = "part:"
|
||||
const textureTag = "texture:"
|
||||
const spriteEndTag = ":sprite"
|
||||
if p.skipSpaceEOF() {
|
||||
return p.emitErr(errUnexpectedEnd)
|
||||
if p.SkipSpaceEOF() {
|
||||
return p.EmitErr(gut.ErrUnexpectedEnd)
|
||||
}
|
||||
line := p.peek()
|
||||
line := p.Peek()
|
||||
switch {
|
||||
case strings.HasPrefix(line, textureTag):
|
||||
c.sprite.texture = strings.TrimSpace(line[len(textureTag):])
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parseContent
|
||||
case line == partTag:
|
||||
p.next()
|
||||
p.Next()
|
||||
c.part = &spritePart{}
|
||||
return c.parsePart
|
||||
case line == spriteEndTag:
|
||||
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 {
|
||||
res := make([]int, len(s))
|
||||
var err error
|
||||
@ -110,14 +111,14 @@ func (c *spriteContext) parsePart(p *lineParser) parseLineFn {
|
||||
const anchorTag = "anchor:"
|
||||
const scaleTag = "scale:"
|
||||
const partEndTag = ":part"
|
||||
if p.skipSpaceEOF() {
|
||||
return p.emitErr(errUnexpectedEnd)
|
||||
if p.SkipSpaceEOF() {
|
||||
return p.EmitErr(gut.ErrUnexpectedEnd)
|
||||
}
|
||||
line := p.peek()
|
||||
line := p.Peek()
|
||||
switch {
|
||||
case strings.HasPrefix(line, nameTag):
|
||||
c.part.name = strings.TrimSpace(line[len(nameTag):])
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parsePart
|
||||
case strings.HasPrefix(line, 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)")
|
||||
}
|
||||
c.part.sub = geom.Rect(coords[0], coords[1], coords[0]+coords[2], coords[1]+coords[3])
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parsePart
|
||||
case strings.HasPrefix(line, 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)")
|
||||
}
|
||||
c.part.anchor = geom.Pt(coords[0], coords[1])
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parsePart
|
||||
case strings.HasPrefix(line, scaleTag):
|
||||
c.part.scale = mustAtof(line[len(scaleTag):])
|
||||
p.next()
|
||||
p.Next()
|
||||
return c.parsePart
|
||||
case line == partEndTag:
|
||||
c.sprite.parts = append(c.sprite.parts, *c.part)
|
||||
p.next()
|
||||
p.Next()
|
||||
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