aboutsummaryrefslogtreecommitdiff
path: root/internal/mapio/mapio.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-29 04:03:38 -0400
committerhistoria <[not public]>2026-06-29 04:03:38 -0400
commitba34490aaed5f7c242c2b0453130fefc07d0d5d0 (patch)
treedb5818757d5080221494cead1c7e61a576d1cb65 /internal/mapio/mapio.go
parentb1e252c996a6332d391f96624a7d6f149eb097c4 (diff)
downloadtui-ascii-mapper-ba34490aaed5f7c242c2b0453130fefc07d0d5d0.tar.gz
restructured projectHEADmain
Diffstat (limited to 'internal/mapio/mapio.go')
-rw-r--r--internal/mapio/mapio.go212
1 files changed, 212 insertions, 0 deletions
diff --git a/internal/mapio/mapio.go b/internal/mapio/mapio.go
new file mode 100644
index 0000000..58a8848
--- /dev/null
+++ b/internal/mapio/mapio.go
@@ -0,0 +1,212 @@
+package mapio
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "tui-ascii-mapper/internal/model"
+ "tui-ascii-mapper/internal/tools"
+
+ "gopkg.in/yaml.v3"
+)
+
+type SaveData struct {
+ Name string `yaml:"name"`
+ Width int `yaml:"width"`
+ Height int `yaml:"height"`
+ Palette []model.Terrain `yaml:"palette"`
+ GridBody string `yaml:"grid_body,omitempty"`
+ GridColors []string `yaml:"grid_colors,omitempty"`
+ TextLabels []model.TextLabel `yaml:"text_labels,omitempty"`
+ Submaps []SubmapRef `yaml:"submaps,omitempty"`
+}
+
+type SubmapRef struct {
+ X int `yaml:"x"`
+ Y int `yaml:"y"`
+ Data SaveData `yaml:"data"`
+}
+
+func SerializeMap(m *model.Map, path string) error {
+ data := buildSaveData(m)
+
+ yamlBytes, err := yaml.Marshal(data)
+ if err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ buf.WriteString("---\n")
+ buf.Write(yamlBytes)
+ buf.WriteString("...\n")
+
+ for _, row := range m.Grid {
+ for _, cell := range row {
+ if cell.Terrain < 0 || cell.Terrain >= len(m.Palette) {
+ buf.WriteByte(' ')
+ } else {
+ buf.WriteString(m.Palette[cell.Terrain].ASCII)
+ }
+ }
+ buf.WriteByte('\n')
+ }
+
+ dir := filepath.Dir(path)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+ return os.WriteFile(path, buf.Bytes(), 0644)
+}
+
+func buildSaveData(m *model.Map) SaveData {
+ sd := SaveData{
+ Name: m.Name,
+ Width: m.Width,
+ Height: m.Height,
+ Palette: m.Palette,
+ }
+ var gb strings.Builder
+ for _, row := range m.Grid {
+ for _, cell := range row {
+ if cell.Terrain < 0 || cell.Terrain >= len(m.Palette) {
+ gb.WriteByte(' ')
+ } else {
+ gb.WriteString(m.Palette[cell.Terrain].ASCII)
+ }
+ }
+ gb.WriteByte('\n')
+ }
+ if m.Parent != nil {
+ sd.GridBody = gb.String()
+ }
+
+ for y := range m.Grid {
+ for x := range m.Grid[y] {
+ c := m.Grid[y][x]
+ if c.Color != "" {
+ sd.GridColors = append(sd.GridColors, fmt.Sprintf("%d,%d,%s", x, y, c.Color))
+ }
+ }
+ }
+ for _, tl := range m.TextLabels {
+ sd.TextLabels = append(sd.TextLabels, tl)
+ }
+ for pt, sub := range m.Submaps {
+ sd.Submaps = append(sd.Submaps, SubmapRef{
+ X: pt.X, Y: pt.Y,
+ Data: buildSaveData(sub),
+ })
+ }
+ return sd
+}
+
+func DeserializeMap(path string) (*model.Map, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ content := string(data)
+
+ if !strings.HasPrefix(content, "---\n") {
+ return nil, fmt.Errorf("invalid save file: missing YAML header")
+ }
+
+ endIdx := strings.Index(content, "\n...\n")
+ if endIdx < 0 {
+ return nil, fmt.Errorf("invalid save file: missing ... terminator")
+ }
+
+ yamlPart := content[4:endIdx]
+ gridPart := content[endIdx+5:]
+
+ var sd SaveData
+ if err := yaml.Unmarshal([]byte(yamlPart), &sd); err != nil {
+ return nil, fmt.Errorf("invalid YAML header: %w", err)
+ }
+
+ m := model.NewMap(sd.Name, sd.Width, sd.Height, sd.Palette)
+ m.Filename = path
+
+ for _, entry := range sd.GridColors {
+ var x, y int
+ var c string
+ if n, _ := fmt.Sscanf(entry, "%d,%d,%s", &x, &y, &c); n == 3 {
+ if y >= 0 && y < m.Height && x >= 0 && x < m.Width {
+ m.Grid[y][x].Color = c
+ }
+ }
+ }
+
+ lines := strings.Split(strings.TrimRight(gridPart, "\n"), "\n")
+ for y, line := range lines {
+ if y >= m.Height {
+ break
+ }
+ for x, ch := range line {
+ if x >= m.Width {
+ break
+ }
+ for i, t := range m.Palette {
+ if string(ch) == t.ASCII {
+ m.Grid[y][x].Terrain = i
+ break
+ }
+ }
+ }
+ }
+
+ for _, tl := range sd.TextLabels {
+ tools.PlaceTextLabel(m, tl.Start, tl.Text, tl.Color)
+ }
+
+ for _, sr := range sd.Submaps {
+ sub := restoreMap(&sr.Data, m)
+ m.Submaps[model.Point{X: sr.X, Y: sr.Y}] = sub
+ }
+
+ return m, nil
+}
+
+func restoreMap(sd *SaveData, parent *model.Map) *model.Map {
+ m := model.NewMap(sd.Name, sd.Width, sd.Height, sd.Palette)
+ m.Parent = parent
+ for _, entry := range sd.GridColors {
+ var x, y int
+ var c string
+ if n, _ := fmt.Sscanf(entry, "%d,%d,%s", &x, &y, &c); n == 3 {
+ if y >= 0 && y < m.Height && x >= 0 && x < m.Width {
+ m.Grid[y][x].Color = c
+ }
+ }
+ }
+ if sd.GridBody != "" {
+ lines := strings.Split(strings.TrimRight(sd.GridBody, "\n"), "\n")
+ for y, line := range lines {
+ if y >= m.Height {
+ break
+ }
+ for x, ch := range line {
+ if x >= m.Width {
+ break
+ }
+ for i, t := range m.Palette {
+ if string(ch) == t.ASCII {
+ m.Grid[y][x].Terrain = i
+ break
+ }
+ }
+ }
+ }
+ }
+ for _, tl := range sd.TextLabels {
+ tools.PlaceTextLabel(m, tl.Start, tl.Text, tl.Color)
+ }
+ for _, sr := range sd.Submaps {
+ sub := restoreMap(&sr.Data, m)
+ m.Submaps[model.Point{X: sr.X, Y: sr.Y}] = sub
+ }
+ return m
+}