package main import ( "flag" "log" "os" "time" "github.com/KEINOS/go-noise" "github.com/chompy/roguelike_rpg/internal/dungeon" ) func GenerateTileMap(width int, height int, algo noise.Algo, threshold int, smoothness int, seed int64) (dungeon.TileMap, error) { genNoise, err := noise.New(algo, seed) if err != nil { return dungeon.TileMap{}, err } tileMap := dungeon.NewTileMap(width, height) // First pass: generate noise-based terrain for x := 0; x < width; x++ { xx := float64(x) / float64(smoothness) for y := 0; y < height; y++ { yy := float64(y) / float64(smoothness) n := genNoise.Eval64(xx, yy) grayColor := ((1. - n) / 2.) * 255. // Apply edge falloff // Calculate distance to nearest edge (normalized 0 to 1) distToEdge := float64(min(x, width-1-x, y, height-1-y)) falloff := distToEdge / float64(min(width, height)/8) if falloff > 1 { falloff = 1 } // Apply falloff to pixel value grayColor = grayColor * falloff if grayColor >= float64(threshold) { tileMap.Tiles[(y*tileMap.Width)+x] = dungeon.TileTypeFloor } } } // Flood fill to find accessible areas visited := make([][]bool, height) for i := range visited { visited[i] = make([]bool, width) } // Start from center (assuming it's walkable) startX, startY := width/2, height/2 if tileMap.Get(startX, startY) == dungeon.TileTypeNone { // Find first walkable pixel near center found := false for dx := -1; dx <= 1 && !found; dx++ { for dy := -1; dy <= 1 && !found; dy++ { nx, ny := startX+dx, startY+dy if nx >= 0 && nx < width && ny >= 0 && ny < height && tileMap.Get(nx, ny) == dungeon.TileTypeFloor { startX, startY = nx, ny found = true } } } } // Perform flood fill queue := [][2]int{{startX, startY}} accessible := make([][]bool, height) for i := range accessible { accessible[i] = make([]bool, width) } for len(queue) > 0 { current := queue[0] queue = queue[1:] x, y := current[0], current[1] if x < 0 || x >= width || y < 0 || y >= height || visited[y][x] || tileMap.Get(x, y) == dungeon.TileTypeNone { continue } visited[y][x] = true accessible[y][x] = true // 4-directional neighbors queue = append(queue, [2]int{x + 1, y}) queue = append(queue, [2]int{x - 1, y}) queue = append(queue, [2]int{x, y + 1}) queue = append(queue, [2]int{x, y - 1}) } // Remove inaccessible areas for x := 0; x < width; x++ { for y := 0; y < height; y++ { if !accessible[y][x] { tileMap.Set(x, y, dungeon.TileTypeNone) } } } // Convert floor tiles adjacent to none tiles to wall tiles for x := 0; x < width; x++ { for y := 0; y < height; y++ { if tileMap.Get(x, y) == dungeon.TileTypeFloor { // Check 4-directional neighbors if (x > 0 && tileMap.Get(x-1, y) == dungeon.TileTypeNone) || (x < width-1 && tileMap.Get(x+1, y) == dungeon.TileTypeNone) || (y > 0 && tileMap.Get(x, y-1) == dungeon.TileTypeNone) || (y < height-1 && tileMap.Get(x, y+1) == dungeon.TileTypeNone) { tileMap.Set(x, y, dungeon.TileTypeWall) } } } } return tileMap, nil } func main() { width := flag.Int("width", 256, "generated tilemap width") height := flag.Int("height", 256, "generated tilemap height") algoStr := flag.String("algo", "perlin", "noise algorithm (perlin, simplex, opensimplex)") threshold := flag.Int("threshold", 128, "threshold for floor/empty (0-255)") smoothness := flag.Int("smoothness", 32, "noise smoothness factor") seed := flag.Int64("seed", 0, "random seed (0 for random)") flag.Parse() var algo noise.Algo switch *algoStr { case "perlin": algo = noise.Perlin case "simplex": algo = noise.OpenSimplex default: log.Fatalf("unknown noise algorithm: %s", *algoStr) } if *seed == 0 { *seed = time.Now().UnixNano() } tileMap, err := GenerateTileMap(*width, *height, algo, *threshold, *smoothness, *seed) if err != nil { log.Fatal(err) } tileMap.ExportBytes(os.Stdout) log.Printf("Generated tilemap %dx%d with seed %d", tileMap.Width, tileMap.Height, *seed) }