aboutsummaryrefslogtreecommitdiff
path: root/tools.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 /tools.go
parentb1e252c996a6332d391f96624a7d6f149eb097c4 (diff)
downloadtui-ascii-mapper-ba34490aaed5f7c242c2b0453130fefc07d0d5d0.tar.gz
restructured projectHEADmain
Diffstat (limited to 'tools.go')
-rw-r--r--tools.go385
1 files changed, 0 insertions, 385 deletions
diff --git a/tools.go b/tools.go
deleted file mode 100644
index e745892..0000000
--- a/tools.go
+++ /dev/null
@@ -1,385 +0,0 @@
-package main
-
-import "fmt"
-
-// Brush applies terrain to a block centered at p. Each cell gets independent random color.
-func Brush(m *Map, center Point, terrain int, size int, palette []Terrain) {
- half := size / 2
- for dy := -half; dy <= half; dy++ {
- for dx := -half; dx <= half; dx++ {
- color := ""
- if terrain >= 0 && terrain < len(palette) {
- color = palette[terrain].PickColor()
- }
- m.SetCell(Point{center.X + dx, center.Y + dy}, terrain, color)
- }
- }
-}
-
-// ThickenPoints expands a set of points by the given brush size, returning deduplicated points.
-func ThickenPoints(pts []Point, size int) []Point {
- if size <= 1 {
- return pts
- }
- half := size / 2
- seen := make(map[Point]bool)
- var result []Point
- for _, p := range pts {
- for dy := -half; dy <= half; dy++ {
- for dx := -half; dx <= half; dx++ {
- np := Point{p.X + dx, p.Y + dy}
- if !seen[np] {
- seen[np] = true
- result = append(result, np)
- }
- }
- }
- }
- return result
-}
-
-// FloodFill fills a contiguous area from start with terrain.
-func FloodFill(m *Map, start Point, terrain int, palette []Terrain) {
- if !m.InBounds(start) {
- return
- }
- target := m.CellAt(start).Terrain
- if target == terrain {
- return
- }
- color := ""
- if terrain >= 0 && terrain < len(palette) {
- color = palette[terrain].PickColor()
- }
- type pt struct{ x, y int }
- stack := []pt{{start.X, start.Y}}
- visited := make([][]bool, m.Height)
- for i := range visited {
- visited[i] = make([]bool, m.Width)
- }
- for len(stack) > 0 {
- p := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
- if !m.InBounds(Point{p.x, p.y}) || visited[p.y][p.x] {
- continue
- }
- if m.Grid[p.y][p.x].Terrain != target {
- continue
- }
- visited[p.y][p.x] = true
- m.SetCell(Point{p.x, p.y}, terrain, color)
- stack = append(stack, pt{p.x + 1, p.y}, pt{p.x - 1, p.y}, pt{p.x, p.y + 1}, pt{p.x, p.y - 1})
- }
-}
-
-// BresenhamLine returns points along a line from a to b.
-func BresenhamLine(a, b Point) []Point {
- var pts []Point
- x0, y0 := a.X, a.Y
- x1, y1 := b.X, b.Y
- dx := abs(x1 - x0)
- dy := -abs(y1 - y0)
- sx, sy := 1, 1
- if x0 > x1 {
- sx = -1
- }
- if y0 > y1 {
- sy = -1
- }
- err := dx + dy
- for {
- pts = append(pts, Point{x0, y0})
- if x0 == x1 && y0 == y1 {
- break
- }
- e2 := 2 * err
- if e2 >= dy {
- err += dy
- x0 += sx
- }
- if e2 <= dx {
- err += dx
- y0 += sy
- }
- }
- return pts
-}
-
-func abs(x int) int {
- if x < 0 {
- return -x
- }
- return x
-}
-
-// DrawRect returns points for the outline (or fill) of a rectangle.
-func DrawRect(a, b Point, filled bool) []Point {
- x0, x1 := a.X, b.X
- y0, y1 := a.Y, b.Y
- if x0 > x1 {
- x0, x1 = x1, x0
- }
- if y0 > y1 {
- y0, y1 = y1, y0
- }
- var pts []Point
- if filled {
- for y := y0; y <= y1; y++ {
- for x := x0; x <= x1; x++ {
- pts = append(pts, Point{x, y})
- }
- }
- return pts
- }
- for x := x0; x <= x1; x++ {
- pts = append(pts, Point{x, y0}, Point{x, y1})
- }
- for y := y0 + 1; y < y1; y++ {
- pts = append(pts, Point{x0, y}, Point{x1, y})
- }
- return pts
-}
-
-// DrawCircle returns points for the outline (or fill) of a circle.
-func DrawCircle(center, edge Point, filled bool) []Point {
- r2 := (edge.X-center.X)*(edge.X-center.X) + (edge.Y-center.Y)*(edge.Y-center.Y)
- r := r2
- if r < 0 {
- return nil
- }
- // integer sqrt approximation, good enough for grid
- radius := intSqrt(r)
- var pts []Point
- for dy := -radius; dy <= radius; dy++ {
- for dx := -radius; dx <= radius; dx++ {
- dist2 := dx*dx + dy*dy
- if filled {
- if dist2 <= r {
- pts = append(pts, Point{center.X + dx, center.Y + dy})
- }
- } else {
- // outline: approximate ring
- if dist2 <= r && dist2 > (radius-1)*(radius-1) {
- pts = append(pts, Point{center.X + dx, center.Y + dy})
- }
- }
- }
- }
- return pts
-}
-
-// DrawOval returns points for the outline (or fill) of an ellipse with two foci.
-func DrawOval(f1, f2 Point, filled bool) []Point {
- // semi-major axis: enough to pass through f2 from f1, plus a bit
- dx := f2.X - f1.X
- dy := f2.Y - f1.Y
- // Use distance between foci as 2c, major axis 2a = 2c * 1.5 (so oval extends)
- dist := intSqrt(dx*dx + dy*dy)
- if dist == 0 {
- return nil
- }
- a := dist * 3 / 2 // major semi-axis (oval extends beyond both foci)
- if a < 1 {
- a = 1
- }
- a2 := a * a
- c2 := dist * dist / 4 // c = half distance between foci
- b2 := a2 - c2 // b² = a² - c²
- if b2 < 0 {
- b2 = 0
- }
-
- // Center of ellipse
- cx := (f1.X + f2.X) / 2
- cy := (f1.Y + f2.Y) / 2
-
- // Bounding box
- minX := cx - a - 1
- maxX := cx + a + 1
- minY := cy - a - 1
- maxY := cy + a + 1
-
- var pts []Point
- for py := minY; py <= maxY; py++ {
- for px := minX; px <= maxX; px++ {
- // Distances to foci
- d1 := distSq(px, py, f1.X, f1.Y)
- d2 := distSq(px, py, f2.X, f2.Y)
- sum := intSqrt(d1) + intSqrt(d2)
-
- if filled {
- if sum <= 2*a {
- pts = append(pts, Point{px, py})
- }
- } else {
- // Outline: near the ellipse boundary
- if sum >= 2*a-1 && sum <= 2*a+1 {
- pts = append(pts, Point{px, py})
- }
- }
- }
- }
- return pts
-}
-
-func distSq(x1, y1, x2, y2 int) int {
- dx := x1 - x2
- dy := y1 - y2
- return dx*dx + dy*dy
-}
-
-func intSqrt(n int) int {
- if n <= 0 {
- return 0
- }
- lo, hi := 0, n
- for lo < hi {
- mid := (lo + hi + 1) / 2
- if mid*mid <= n {
- lo = mid
- } else {
- hi = mid - 1
- }
- }
- return lo
-}
-
-// ApplyPoints writes terrain to all given points.
-func ApplyPoints(m *Map, pts []Point, terrain int, palette []Terrain) {
- color := ""
- if terrain >= 0 && terrain < len(palette) {
- color = palette[terrain].PickColor()
- }
- for _, p := range pts {
- m.SetCell(p, terrain, color)
- }
-}
-
-// PlaceTextLabel adds a text label at start, clearing any prior text in those cells.
-func PlaceTextLabel(m *Map, start Point, text string, color string) {
- // Remove any existing label starting at the same point
- RemoveTextLabel(m, start)
- tl := TextLabel{Text: text, Start: start, Color: color}
- m.TextLabels = append(m.TextLabels, tl)
- runes := []rune(text)
- for i, r := range runes {
- p := Point{start.X + i, start.Y}
- if m.InBounds(p) {
- m.Grid[p.Y][p.X].Text = string(r)
- }
- }
-}
-
-// RemoveTextLabel removes the text label starting at start.
-func RemoveTextLabel(m *Map, start Point) {
- for i, tl := range m.TextLabels {
- if tl.Start == start {
- m.TextLabels = append(m.TextLabels[:i], m.TextLabels[i+1:]...)
- break
- }
- }
- // Also clear from grid cells
- for y := range m.Grid {
- for x := range m.Grid[y] {
- if m.Grid[y][x].Text == "" {
- continue
- }
- // Check if this cell belongs to a label
- found := false
- for _, tl := range m.TextLabels {
- runes := []rune(tl.Text)
- for i := range runes {
- if tl.Start.X+i == x && tl.Start.Y == y {
- found = true
- break
- }
- }
- if found {
- break
- }
- }
- if !found {
- m.Grid[y][x].Text = ""
- }
- }
- }
-}
-
-// FindTextLabelAt returns the label index that covers point p, or -1.
-func FindTextLabelAt(m *Map, p Point) int {
- for i, tl := range m.TextLabels {
- runes := []rune(tl.Text)
- for j := range runes {
- if tl.Start.X+j == p.X && tl.Start.Y == p.Y {
- return i
- }
- }
- }
- return -1
-}
-
-// MoveTextLabel moves label at oldStart to newStart.
-func MoveTextLabel(m *Map, oldStart, newStart Point) {
- for i, tl := range m.TextLabels {
- if tl.Start == oldStart {
- // Clear old cells
- for _, p := range LabelPositions(tl) {
- if m.InBounds(p) {
- m.Grid[p.Y][p.X].Text = ""
- }
- }
- m.TextLabels[i].Start = newStart
- // Set new cells
- runes := []rune(tl.Text)
- for j, r := range runes {
- p := Point{newStart.X + j, newStart.Y}
- if m.InBounds(p) {
- m.Grid[p.Y][p.X].Text = string(r)
- }
- }
- return
- }
- }
-}
-
-func LabelPositions(tl TextLabel) []Point {
- var pts []Point
- runes := []rune(tl.Text)
- for i := range runes {
- pts = append(pts, Point{tl.Start.X + i, tl.Start.Y})
- }
- return pts
-}
-
-func DemoTools() {
- m := NewMap("test", 10, 10, nil)
- Brush(m, Point{5, 5}, 0, 3, nil)
- if m.Grid[5][5].Terrain != 0 {
- panic("Brush failed")
- }
- pts := BresenhamLine(Point{0, 0}, Point{3, 0})
- if len(pts) != 4 || pts[0] != (Point{0, 0}) || pts[3] != (Point{3, 0}) {
- panic(fmt.Sprintf("Line failed: %v", pts))
- }
- // intSqrt sanity
- if intSqrt(25) != 5 || intSqrt(26) != 5 || intSqrt(0) != 0 {
- panic("intSqrt failed")
- }
- // Color picking
- t := Terrain{Colors: []TerrainColor{{Color: "22", Weight: 100}}}
- if t.PickColor() != "22" {
- panic("PickColor failed")
- }
- // Text labels — Grid[y][x]
- PlaceTextLabel(m, Point{2, 2}, "ABC", "")
- if m.Grid[2][2].Text != "A" || m.Grid[2][3].Text != "B" {
- panic("TextLabel: " + m.Grid[2][2].Text + "," + m.Grid[2][3].Text)
- }
- RemoveTextLabel(m, Point{2, 2})
- if m.Grid[2][2].Text != "" {
- panic("TextLabel remove failed")
- }
- fmt.Println("tools: ok")
-}
-
-