mirror of
https://github.com/huderlem/porygion.git
synced 2026-04-25 23:37:19 -05:00
Initial commit
This commit is contained in:
commit
8810634401
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 huderlem
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
README.md
Normal file
7
README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Porygion
|
||||
|
||||
Porygion is a procedural region map generator in the Gen-3 Pokémon style.
|
||||
|
||||
It's available online in the [Porygion Playground](http://www.huderlem.com/porygion-playground/).
|
||||
|
||||
This repository contains the library code for generating the region map, and the interactive website's code is located in the [Porygion Playground](https://github.com/huderlem/porygion-playground) repository.
|
||||
9
go.mod
Normal file
9
go.mod
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/huderlem/porygion
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36
|
||||
github.com/muesli/kmeans v0.0.0-20200718051629-66f1657148c0
|
||||
github.com/ojrac/opensimplex-go v1.0.1
|
||||
)
|
||||
16
go.sum
Normal file
16
go.sum
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/blend/go-sdk v2.0.0+incompatible/go.mod h1:3GUb0YsHFNTJ6hsJTpzdmCUl05o8HisKjx5OAlzYKdw=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36 h1:KMCH+/bbZsAbFgzCXD3aB0DRZXnwAO8NYDmfIfslo+M=
|
||||
github.com/muesli/clusters v0.0.0-20180605185049-a07a36e67d36/go.mod h1:mw5KDqUj0eLj/6DUNINLVJNoPTFkEuGMHtJsXLviLkY=
|
||||
github.com/muesli/kmeans v0.0.0-20200718051629-66f1657148c0 h1:xptegMPjfY9TDf49s3E5QFV1dZeA3acCq9IKTGirMLo=
|
||||
github.com/muesli/kmeans v0.0.0-20200718051629-66f1657148c0/go.mod h1:+s8P1Egy3DkSU5+p4yBBn9SQQP1RhzZEgLw98DNYljQ=
|
||||
github.com/ojrac/opensimplex-go v1.0.1 h1:XslvpLP6XqQSATUtsOnGBYtFPw7FQ6h6y0ihjVeOLHo=
|
||||
github.com/ojrac/opensimplex-go v1.0.1/go.mod h1:MoSgj04tZpH8U0RefZabnHV2AbLgv/2mo3hLJtWqSEs=
|
||||
github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible h1:ahpaSRefPekV3gcXot2AOgngIV8WYqzvDyFe3i7W24w=
|
||||
github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
359
porygion.go
Normal file
359
porygion.go
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
package porygion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math/rand"
|
||||
|
||||
"github.com/muesli/clusters"
|
||||
"github.com/muesli/kmeans"
|
||||
simplex "github.com/ojrac/opensimplex-go"
|
||||
)
|
||||
|
||||
// RegionMap represents a generated region map.
|
||||
type RegionMap struct {
|
||||
PixelWidth int
|
||||
PixelHeight int
|
||||
Elevations [][]float64
|
||||
Cities []Tile
|
||||
Routes []Tile
|
||||
}
|
||||
|
||||
// GenerateRegionMap generates a new complete region map.
|
||||
func GenerateRegionMap(seed int64, pixelWidth, pixelHeight int, numCities int) (RegionMap, error) {
|
||||
rand.Seed(seed)
|
||||
elevations := getNewElevationMap(pixelWidth, pixelHeight)
|
||||
generateElevations(elevations)
|
||||
validTiles := getValidLandmarkTiles(elevations)
|
||||
partitions := partitionTilesByLocation(100, 100, pixelWidth/8, pixelHeight/8, validTiles)
|
||||
cities := generateCities(partitions, numCities)
|
||||
cityClusters, err := clusterCities(cities)
|
||||
if err != nil {
|
||||
return RegionMap{}, err
|
||||
}
|
||||
routes := generateRoutes(cityClusters)
|
||||
return RegionMap{
|
||||
PixelWidth: pixelWidth,
|
||||
PixelHeight: pixelHeight,
|
||||
Elevations: elevations,
|
||||
Cities: cities,
|
||||
Routes: routes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateBaseRegionMap generates a new region map containing only elevations.
|
||||
func GenerateBaseRegionMap(seed int64, pixelWidth, pixelHeight int) RegionMap {
|
||||
rand.Seed(seed)
|
||||
elevations := getNewElevationMap(pixelWidth, pixelHeight)
|
||||
generateElevations(elevations)
|
||||
return RegionMap{
|
||||
PixelWidth: pixelWidth,
|
||||
PixelHeight: pixelHeight,
|
||||
Elevations: elevations,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateRegionMapWithCities generates a new region map with new city locations, using
|
||||
// the provided region map.
|
||||
func GenerateRegionMapWithCities(seed int64, numCities int, regionMap RegionMap) RegionMap {
|
||||
rand.Seed(seed)
|
||||
validTiles := getValidLandmarkTiles(regionMap.Elevations)
|
||||
partitions := partitionTilesByLocation(100, 100, regionMap.PixelWidth/8, regionMap.PixelHeight/8, validTiles)
|
||||
cities := generateCities(partitions, numCities)
|
||||
regionMap.Cities = cities
|
||||
return regionMap
|
||||
}
|
||||
|
||||
// GenerateRegionMapWithRoutes generates a new region map with new route locations, using
|
||||
// the provided region map.
|
||||
func GenerateRegionMapWithRoutes(seed int64, regionMap RegionMap) (RegionMap, error) {
|
||||
rand.Seed(seed)
|
||||
cityClusters, err := clusterCities(regionMap.Cities)
|
||||
if err != nil {
|
||||
return RegionMap{}, err
|
||||
}
|
||||
routes := generateRoutes(cityClusters)
|
||||
regionMap.Routes = routes
|
||||
return regionMap, nil
|
||||
}
|
||||
|
||||
// RenderBaseRegionMap renders a region map using only its elevations.
|
||||
func RenderBaseRegionMap(regionMap RegionMap) image.Image {
|
||||
img := renderRegionMapImage(regionMap.Elevations, []Tile{}, []Tile{})
|
||||
return img
|
||||
}
|
||||
|
||||
// RenderRegionMapWithCities renders a region map using only its elevations and cities.
|
||||
func RenderRegionMapWithCities(regionMap RegionMap) image.Image {
|
||||
img := renderRegionMapImage(regionMap.Elevations, regionMap.Cities, []Tile{})
|
||||
return img
|
||||
}
|
||||
|
||||
// RenderFullRegionMap renders a full region map.
|
||||
func RenderFullRegionMap(regionMap RegionMap) image.Image {
|
||||
img := renderRegionMapImage(regionMap.Elevations, regionMap.Cities, regionMap.Routes)
|
||||
return img
|
||||
}
|
||||
|
||||
func getNewElevationMap(width, height int) [][]float64 {
|
||||
elevations := make([][]float64, width)
|
||||
for i := range elevations {
|
||||
elevations[i] = make([]float64, height)
|
||||
}
|
||||
return elevations
|
||||
}
|
||||
|
||||
func generateElevations(elevations [][]float64) {
|
||||
baseNoise := simplex.New(rand.Int63())
|
||||
secondaryNoise := simplex.New(rand.Int63())
|
||||
jitterNoise := simplex.New(rand.Int63())
|
||||
jitterCoeffNoise := simplex.New(rand.Int63())
|
||||
for i := range elevations {
|
||||
for j := range elevations[i] {
|
||||
baseElevation := baseNoise.Eval2(float64(i)/100.0, float64(j)/100.0) + 0.2
|
||||
secondaryElevation := secondaryNoise.Eval2(float64(i)/20.0, float64(j)/20.0) * 0.15
|
||||
jitterElevation := jitterNoise.Eval2(float64(i)/15.0, float64(j)/15.0)
|
||||
jitterCoeff := jitterCoeffNoise.Eval2(float64(i)/50.0, float64(j)/50.0) * 0.6
|
||||
elevation := baseElevation + secondaryElevation + jitterElevation*jitterCoeff
|
||||
elevations[i][j] = elevation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getValidLandmarkTiles(elevations [][]float64) []Tile {
|
||||
validTiles := []Tile{}
|
||||
tilesWidth := len(elevations) / 8
|
||||
tilesHeight := len(elevations[0]) / 8
|
||||
for i := 0; i < tilesWidth; i++ {
|
||||
for j := 0; j < tilesHeight; j++ {
|
||||
// A tile is valid if it has at least a certain number
|
||||
// of non-water pixels.
|
||||
numLandPixels := 0
|
||||
found := false
|
||||
for x := 0; x < 8; x++ {
|
||||
for y := 0; y < 8; y++ {
|
||||
px := i*8 + x
|
||||
py := j*8 + y
|
||||
if elevations[px][py] >= 0 {
|
||||
numLandPixels++
|
||||
if numLandPixels > 20 {
|
||||
validTiles = append(validTiles, Tile{i, j})
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return validTiles
|
||||
}
|
||||
|
||||
func partitionTilesByLocation(partitionWidth, partitionHeight, tileWidth, tileHeight int, tiles []Tile) map[string][]Tile {
|
||||
// Groups tiles into separate partitions, based on a grid.
|
||||
partitions := map[string][]Tile{}
|
||||
for _, t := range tiles {
|
||||
partitionX := t.X / partitionWidth
|
||||
partitionY := t.Y / partitionHeight
|
||||
key := fmt.Sprintf("%d:%d", partitionX, partitionY)
|
||||
if _, ok := partitions[key]; !ok {
|
||||
partitions[key] = []Tile{}
|
||||
}
|
||||
partitions[key] = append(partitions[key], t)
|
||||
}
|
||||
return partitions
|
||||
}
|
||||
|
||||
func generateCities(partitions map[string][]Tile, numCities int) []Tile {
|
||||
// First, get a randomized order of the partitions.
|
||||
partitionKeys := make([]string, len(partitions))
|
||||
i := 0
|
||||
for k := range partitions {
|
||||
partitionKeys[i] = k
|
||||
i++
|
||||
}
|
||||
rand.Shuffle(len(partitionKeys), func(i, j int) { partitionKeys[i], partitionKeys[j] = partitionKeys[j], partitionKeys[i] })
|
||||
|
||||
// Loop through partitions, placing one city at a time.
|
||||
cities := map[Tile]bool{}
|
||||
for c := 0; c < numCities; c++ {
|
||||
partition := partitions[partitionKeys[c%len(partitionKeys)]]
|
||||
// Attempt to place the city many times, in case several attempts fail,
|
||||
// due to contraints.
|
||||
for i := 0; i < 50; i++ {
|
||||
if city, ok := tryPickCityTile(partition); ok {
|
||||
if _, ok = cities[city]; !ok {
|
||||
cities[city] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result := make([]Tile, len(cities))
|
||||
i = 0
|
||||
for city := range cities {
|
||||
result[i] = city
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func tryPickCityTile(partition []Tile) (Tile, bool) {
|
||||
// Pick a random tile from the partition, and evaluate whether or not
|
||||
// we can place a city there.
|
||||
for j := 0; j < 50; j++ {
|
||||
candidate := partition[rand.Intn(len(partition))]
|
||||
// Only allow cities on a 2x2 grid, to avoid adjacent cities
|
||||
// and routes.
|
||||
if candidate.X%2 != 1 || candidate.Y%2 != 1 {
|
||||
continue
|
||||
}
|
||||
// Don't allow cities to be placed where the in-game UI elements allow.
|
||||
if candidate.X < 1 || candidate.Y < 2 || candidate.X > 28 || candidate.Y > 16 {
|
||||
continue
|
||||
}
|
||||
if candidate.X > 14 && candidate.Y > 14 {
|
||||
continue
|
||||
}
|
||||
if candidate.X > 19 && candidate.Y < 5 {
|
||||
continue
|
||||
}
|
||||
return candidate, true
|
||||
}
|
||||
return Tile{}, false
|
||||
}
|
||||
|
||||
func clusterCities(cities []Tile) ([][]Tile, error) {
|
||||
// Cluster the cities into 2 groups, using k-means.
|
||||
var points clusters.Observations
|
||||
for _, city := range cities {
|
||||
points = append(points, clusters.Coordinates{float64(city.X), float64(city.Y)})
|
||||
}
|
||||
km := kmeans.New()
|
||||
clusters, err := km.Partition(points, 2)
|
||||
if err != nil {
|
||||
return [][]Tile{}, fmt.Errorf("Failed to cluster cities: %s", err)
|
||||
}
|
||||
cityClusters := make([][]Tile, len(clusters))
|
||||
for i, cluster := range clusters {
|
||||
for _, o := range cluster.Observations {
|
||||
coords := o.Coordinates()
|
||||
cityClusters[i] = append(cityClusters[i], Tile{int(coords[0]), int(coords[1])})
|
||||
}
|
||||
}
|
||||
return cityClusters, nil
|
||||
}
|
||||
|
||||
func generateRoutes(cityClusters [][]Tile) []Tile {
|
||||
routeTiles := map[Tile]bool{}
|
||||
// Connect cities within each cluster to each other.
|
||||
for _, cities := range cityClusters {
|
||||
if len(cities) < 2 {
|
||||
continue
|
||||
}
|
||||
connectedCities := map[Tile]bool{}
|
||||
var city *Tile
|
||||
var firstCity *Tile
|
||||
var lastCity Tile
|
||||
for len(connectedCities) < len(cities) {
|
||||
if city == nil {
|
||||
// Get an unconnected city.
|
||||
for _, c := range cities {
|
||||
if _, ok := connectedCities[c]; !ok {
|
||||
city = &Tile{c.X, c.Y}
|
||||
break
|
||||
}
|
||||
}
|
||||
firstCity = &Tile{city.X, city.Y}
|
||||
}
|
||||
lastCity = *city
|
||||
// Find the nearest unconnected city, and connect it.
|
||||
var nearestCity *Tile
|
||||
minDistance := 99999
|
||||
for _, other := range cities {
|
||||
if other == *city {
|
||||
continue
|
||||
}
|
||||
if _, ok := connectedCities[other]; ok {
|
||||
continue
|
||||
}
|
||||
dist := city.Distance(other)
|
||||
if dist < minDistance {
|
||||
minDistance = dist
|
||||
nearestCity = &Tile{other.X, other.Y}
|
||||
}
|
||||
}
|
||||
if nearestCity == nil {
|
||||
continue
|
||||
}
|
||||
connectCities(*city, *nearestCity, routeTiles)
|
||||
connectedCities[*city] = true
|
||||
connectedCities[*nearestCity] = true
|
||||
*city = *nearestCity
|
||||
}
|
||||
connectCities(*firstCity, lastCity, routeTiles)
|
||||
}
|
||||
|
||||
// Connect the two clusters of cities together by
|
||||
// connecting the two nearest cities.
|
||||
minDistance := 99999
|
||||
var cityA Tile
|
||||
var cityB Tile
|
||||
for _, a := range cityClusters[0] {
|
||||
for _, b := range cityClusters[1] {
|
||||
dist := a.Distance(b)
|
||||
if dist < minDistance {
|
||||
minDistance = dist
|
||||
cityA = a
|
||||
cityB = b
|
||||
}
|
||||
}
|
||||
}
|
||||
connectCities(cityA, cityB, routeTiles)
|
||||
|
||||
// Return a slice of tiles, rather than a map.
|
||||
result := make([]Tile, len(routeTiles))
|
||||
i := 0
|
||||
for tile := range routeTiles {
|
||||
result[i] = tile
|
||||
i++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func connectCities(cityA Tile, cityB Tile, routeTiles map[Tile]bool) {
|
||||
if rand.Intn(2) == 0 {
|
||||
start := connectHorizontalRoute(cityA, cityB, routeTiles)
|
||||
connectVerticalRoute(start, cityB, routeTiles)
|
||||
} else {
|
||||
start := connectVerticalRoute(cityA, cityB, routeTiles)
|
||||
connectHorizontalRoute(start, cityB, routeTiles)
|
||||
}
|
||||
}
|
||||
|
||||
func connectHorizontalRoute(start Tile, end Tile, routeTiles map[Tile]bool) Tile {
|
||||
inc := 1
|
||||
if start.X > end.X {
|
||||
inc = -1
|
||||
}
|
||||
for i := start.X; i != end.X; i += inc {
|
||||
t := Tile{i, start.Y}
|
||||
routeTiles[t] = true
|
||||
}
|
||||
return Tile{end.X, start.Y}
|
||||
}
|
||||
|
||||
func connectVerticalRoute(start Tile, end Tile, routeTiles map[Tile]bool) Tile {
|
||||
inc := 1
|
||||
if start.Y > end.Y {
|
||||
inc = -1
|
||||
}
|
||||
for j := start.Y; j != end.Y; j += inc {
|
||||
t := Tile{start.X, j}
|
||||
routeTiles[t] = true
|
||||
}
|
||||
return Tile{start.X, end.Y}
|
||||
}
|
||||
89
render.go
Normal file
89
render.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package porygion
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Standard colors for various properties on the region map.
|
||||
var (
|
||||
colorWater0 = color.RGBA{152, 208, 248, 255}
|
||||
colorWater1 = color.RGBA{160, 176, 248, 255}
|
||||
colorLand0 = color.RGBA{0, 112, 0, 255}
|
||||
colorLand1 = color.RGBA{56, 168, 8, 255}
|
||||
colorLand2 = color.RGBA{96, 208, 0, 255}
|
||||
colorLand3 = color.RGBA{168, 232, 48, 255}
|
||||
colorLand4 = color.RGBA{208, 248, 120, 255}
|
||||
colorRouteWater0 = color.RGBA{72, 152, 224, 255}
|
||||
colorRouteWater1 = color.RGBA{40, 128, 224, 255}
|
||||
colorRouteLand0 = color.RGBA{224, 160, 0, 255}
|
||||
colorRouteLand1 = color.RGBA{232, 184, 56, 255}
|
||||
colorRouteLand2 = color.RGBA{240, 208, 80, 255}
|
||||
colorRouteLand3 = color.RGBA{232, 224, 112, 255}
|
||||
colorRouteLand4 = color.RGBA{232, 224, 168, 255}
|
||||
)
|
||||
|
||||
var routeConversionColors = map[color.Color]color.RGBA{
|
||||
colorWater0: colorRouteWater0,
|
||||
colorWater1: colorRouteWater1,
|
||||
colorLand0: colorRouteLand0,
|
||||
colorLand1: colorRouteLand1,
|
||||
colorLand2: colorRouteLand2,
|
||||
colorLand3: colorRouteLand3,
|
||||
colorLand4: colorRouteLand4,
|
||||
}
|
||||
|
||||
func renderRegionMapImage(elevations [][]float64, cities []Tile, routes []Tile) image.Image {
|
||||
width := len(elevations)
|
||||
height := len(elevations[0])
|
||||
img := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{width, height}})
|
||||
for i := 0; i < width; i++ {
|
||||
for j := 0; j < height; j++ {
|
||||
c := getColorForElevation(elevations[i][j], j)
|
||||
img.SetRGBA(i, j, c)
|
||||
}
|
||||
}
|
||||
for _, route := range routes {
|
||||
for i := 0; i < 8; i++ {
|
||||
for j := 0; j < 8; j++ {
|
||||
x := route.X*8 + i
|
||||
y := route.Y*8 + j
|
||||
c := routeConversionColors[img.At(x, y)]
|
||||
img.SetRGBA(x, y, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, city := range cities {
|
||||
for i := 0; i < 8; i++ {
|
||||
for j := 0; j < 8; j++ {
|
||||
x := city.X*8 + i
|
||||
y := city.Y*8 + j
|
||||
img.SetRGBA(x, y, color.RGBA{255, 0, 0, 255})
|
||||
}
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func getColorForElevation(elevation float64, y int) color.RGBA {
|
||||
if elevation > 0 {
|
||||
switch {
|
||||
case elevation > 1.10:
|
||||
return colorLand4
|
||||
case elevation > 0.85:
|
||||
return colorLand3
|
||||
case elevation > 0.60:
|
||||
return colorLand2
|
||||
case elevation > 0.35:
|
||||
return colorLand1
|
||||
default:
|
||||
return colorLand0
|
||||
}
|
||||
}
|
||||
|
||||
// The water alternates blue hues each row.
|
||||
if y%2 == 0 {
|
||||
return colorWater0
|
||||
}
|
||||
return colorWater1
|
||||
}
|
||||
19
tile.go
Normal file
19
tile.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package porygion
|
||||
|
||||
// Tile is a 8x8-pixel section in a region map.
|
||||
type Tile struct {
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// Distance computes the manhattan distance between two Tiles.
|
||||
func (t Tile) Distance(other Tile) int {
|
||||
xDiff := t.X - other.X
|
||||
if xDiff < 0 {
|
||||
xDiff = -xDiff
|
||||
}
|
||||
yDiff := t.Y - other.Y
|
||||
if yDiff < 0 {
|
||||
yDiff = -yDiff
|
||||
}
|
||||
return xDiff + yDiff
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user