M cmd/gemifc/main.go => cmd/gemifc/main.go +16 -18
@@ 9,7 9,7 @@ import (
"log"
"os"
"path/filepath"
- "strings"
+ "regexp"
"gopkg.in/yaml.v2"
)
@@ 51,6 51,7 @@ func findFilesWithExtension(root, pattern string) ([]string, error) {
}
func processFile(path string) ([]gamemanager.Room, error) {
+ roomParser := newRoomParser()
fileRooms := []gamemanager.Room{}
thisFile, err := readFile(path)
@@ 58,28 59,25 @@ func processFile(path string) ([]gamemanager.Room, error) {
return fileRooms, fmt.Errorf("couldn't read .gemif file: %w", err)
}
- isMetadata := false
-
- currentRoom := gamemanager.Room{}
-
- for _, filePiece := range strings.Split(thisFile, "---") {
- if filePiece == "" {
- isMetadata = !isMetadata
-
+ sceneSeparator := regexp.MustCompile("(?m)^---")
+ for _, room := range sceneSeparator.Split(thisFile, -1) {
+ if room == "" {
continue
}
- if isMetadata {
- if err := yaml.Unmarshal([]byte(filePiece), ¤tRoom); err != nil {
- return fileRooms, fmt.Errorf("couldn't parse story metadata from yaml: %w", err)
- }
- } else {
- currentRoom.Description = filePiece
- fileRooms = append(fileRooms, currentRoom)
- currentRoom = gamemanager.Room{}
+ currentRoom, err := roomParser.findRoomInfo(room)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ currentRoom.Exits, err = roomParser.processExits(room)
+ if err != nil {
+ log.Fatal(err)
}
- isMetadata = !isMetadata
+ currentRoom.Description = roomParser.getRoomDescription(room)
+ fileRooms = append(fileRooms, currentRoom)
+ currentRoom = gamemanager.Room{}
}
return fileRooms, nil
A cmd/gemifc/roomparser.go => cmd/gemifc/roomparser.go +112 -0
@@ 0,0 1,112 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "gemif/pkg/gamemanager"
+ "regexp"
+ "strings"
+)
+
+// ErrInvalidRoomHeader happens when the header of a room is malformed or missing.
+var ErrInvalidRoomHeader = errors.New("invalid or missing room header")
+
+// ErrInvalidExitDescription happens when an exit is malformed.
+var ErrInvalidExitDescription = errors.New("invalid exit description")
+
+type roomParser struct {
+ roomInfoRexp *regexp.Regexp
+ exitRexp *regexp.Regexp
+ ifCondRexp *regexp.Regexp
+ nifCondRexp *regexp.Regexp
+ setCondRexp *regexp.Regexp
+ unsetCondRexp *regexp.Regexp
+}
+
+func newRoomParser() roomParser {
+ roomInfoRexp := regexp.MustCompile(`(?m)^# ([A-Za-z0-9_-]+) ([\w .,!/]+)$`)
+ exitRexp := regexp.MustCompile("(?m)^=> ([\\w]+) ({([\\+-~!\\w ]+)} )?([^\n{}]+)$")
+ setCondRexp := regexp.MustCompile(`\+([\w]+)`)
+ unsetCondRexp := regexp.MustCompile(`-([\w]+)`)
+ ifCondRexp := regexp.MustCompile(`~([\w]+)`)
+ nifCondRexp := regexp.MustCompile(`!([\w]+)`)
+
+ return roomParser{
+ roomInfoRexp: roomInfoRexp,
+ exitRexp: exitRexp,
+ ifCondRexp: ifCondRexp,
+ setCondRexp: setCondRexp,
+ unsetCondRexp: unsetCondRexp,
+ nifCondRexp: nifCondRexp,
+ }
+}
+
+func (rp *roomParser) findRoomInfo(room string) (gamemanager.Room, error) {
+ infoMatch := rp.roomInfoRexp.FindStringSubmatch(room)
+ if infoMatch == nil || len(infoMatch) != 3 {
+ return gamemanager.Room{}, fmt.Errorf("%w: %v", ErrInvalidRoomHeader, infoMatch)
+ }
+
+ return gamemanager.Room{
+ ID: infoMatch[1],
+ Name: infoMatch[2],
+ Exits: []gamemanager.Exit{},
+ Description: "",
+ }, nil
+}
+
+func (rp *roomParser) removeInfoFromDesc(room string) string {
+ return rp.roomInfoRexp.ReplaceAllString(room, "")
+}
+
+func (rp *roomParser) processExits(room string) ([]gamemanager.Exit, error) {
+ roomExits := []gamemanager.Exit{}
+
+ exitMatch := rp.exitRexp.FindAllStringSubmatch(room, -1)
+ for _, exitStr := range exitMatch {
+ if exitStr[1] == "" || exitStr[4] == "" {
+ return roomExits, fmt.Errorf("%w: %s", ErrInvalidExitDescription, exitStr)
+ }
+
+ thisExit := gamemanager.Exit{
+ Destination: exitStr[1],
+ Description: exitStr[4],
+ SetCondition: "",
+ UnsetCondition: "",
+ IfCondition: "",
+ NotCondition: "",
+ }
+
+ if exitStr[3] != "" {
+ thisExit.SetCondition = getMatchOrDefault(rp.setCondRexp, exitStr[3])
+ thisExit.UnsetCondition = getMatchOrDefault(rp.unsetCondRexp, exitStr[3])
+ thisExit.IfCondition = getMatchOrDefault(rp.ifCondRexp, exitStr[3])
+ thisExit.NotCondition = getMatchOrDefault(rp.nifCondRexp, exitStr[3])
+ }
+
+ roomExits = append(roomExits, thisExit)
+ }
+
+ return roomExits, nil
+}
+
+func (rp *roomParser) removeExitsFromDesc(room string) string {
+ return rp.exitRexp.ReplaceAllString(room, "")
+}
+
+func (rp *roomParser) getRoomDescription(room string) string {
+ room = rp.removeInfoFromDesc(room)
+ room = rp.removeExitsFromDesc(room)
+ room = strings.TrimSpace(room)
+
+ return room
+}
+
+func getMatchOrDefault(rexp *regexp.Regexp, input string) string {
+ setOpt := rexp.FindStringSubmatch(input)
+ if len(setOpt) > 1 {
+ return setOpt[1]
+ }
+
+ return ""
+}
M pkg/gamemanager/room.go => pkg/gamemanager/room.go +0 -7
@@ 1,7 1,5 @@
package gamemanager
-import "fmt"
-
// Room contains information about a room and its Exits.
type Room struct {
ID string `yaml:"room_id" json:"room_id" binding:"uuid"`
@@ 12,7 10,6 @@ type Room struct {
// Exit describes actions applied to GameState as part of leaving a Room.
type Exit struct {
- ID string `yaml:"exit_id" json:"exit_id" binding:"uuid"`
Description string `yaml:"exit_description" json:"exit_description"`
Destination string `yaml:"destination_id" json:"destination_id" binding:"uuid"`
SetCondition string `yaml:"set_condition,omitempty" json:"set_condition"`
@@ 26,14 23,10 @@ func (r Room) filterExits(gameState GameState) []Exit {
for _, e := range r.Exits {
if e.IfCondition != "" && !gameState.ConditionMet(e.IfCondition) {
- fmt.Printf("Cant use exit %s due to not meeting condition\n", e.ID)
-
continue
}
if e.NotCondition != "" && gameState.ConditionMet(e.NotCondition) {
- fmt.Printf("Can't use exit %s due to meeting condition\n", e.ID)
-
continue
}