From ba34490aaed5f7c242c2b0453130fefc07d0d5d0 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 29 Jun 2026 04:03:38 -0400 Subject: restructured project --- tools.go | 385 --------------------------------------------------------------- 1 file changed, 385 deletions(-) delete mode 100644 tools.go (limited to 'tools.go') 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") -} - - -- cgit v1.2.3