diff options
Diffstat (limited to 'internal/mapio/mapio.go')
| -rw-r--r-- | internal/mapio/mapio.go | 212 |
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 +} |
