2-step level generator

This commit is contained in:
koehr 2018-04-22 23:00:10 +02:00
parent 4e3fda1732
commit 5f66a76cb6
12 changed files with 175 additions and 135 deletions

View file

@ -1,7 +1,7 @@
{
"name": "building-game",
"description": "A blocky, side-scrolling, building and exploration game",
"version": "1.0.0",
"name": "digging-game",
"description": "A blocky, side-scrolling, digging and exploration game",
"version": "0.0.1",
"author": "koehr <n@koehr.in>",
"license": "MIT",
"private": true,

View file

@ -13,7 +13,7 @@
</template>
<script>
import Level from './lib/level-generator'
import Level from './level'
const WIDTH = 32
const HEIGHT = 32

28
src/level/def.js Normal file
View file

@ -0,0 +1,28 @@
export const type = {
air: {type: 'air', hp: 0},
grass: {type: 'grass', hp: 1},
leaves: {type: 'leaves', hp: 1},
wood: {type: 'wood', hp: 5},
soil: {type: 'soil', hp: 2},
gravel: {type: 'gravel', hp: 5},
stone: {type: 'stone', hp: 10},
bedrock: {type: 'bedrock', hp: 25},
cave: {type: 'cave', hp: 0}
}
export const level = {
peak: 24,
ground: 28,
rock: 32,
underground: 48,
cave_max: 250
}
export const probability = {
tree: 0.1,
soil_hole: 0.3,
soil_gravel: 0.2,
rock_gravel: 0.1,
cave: 0.5,
fray: 0.4
}

View file

@ -0,0 +1,58 @@
import SeedRng from 'seedrandom'
import FastSimplexNoise from 'fast-simplex-noise'
import {type as T, level as L, probability as P} from './def'
export default class BlockGen {
constructor (seed = 'so freakin random') {
const simplex = new FastSimplexNoise({ random: SeedRng(seed) })
this.rand = (x, y) => 0.5 + 0.5 * simplex.raw2D(x, y)
}
level (level, row, previousRow) {
for (let i = 0; i < row.length; i++) {
row[i] = this.block(level, i, row[i], row[i - 1], previousRow[i])
}
}
block (level, column, current, before, above) {
if (level < L.peak) return this.air()
const r = this.rand(level, column)
if (level < L.ground) return this.treeTop(r)
if (level < L.rock) return this.ground(r)
if (level < L.underground) return this.rock(r)
return this.underground(r, above, before, level - L.underground)
}
// always returns air
air () {
return T.air
}
// returns mostly air, but sometimes starts a tree
treeTop (r) {
if (r < P.tree) return T.wood
return T.air
}
// returns mostly soil and grass, sometimes gravel and sometimes air
ground (r) {
if (r < P.soil_gravel) return T.gravel
return T.soil
}
// returns mostly stones, sometimes gravel
rock (r) {
return r < P.rock_gravel ? T.gravel : T.stone
}
// return mostly bedrock, sometimes caves, depending on the level
underground (r, above, before, level) {
// the probability for a cave rises with the level
const a = P.cave / L.cave_max**2
const p = Math.min(P.cave, a * level**2)
if (r < p) return T.cave
return T.bedrock
}
}

39
src/level/index.js Normal file
View file

@ -0,0 +1,39 @@
import SeedRng from 'seedrandom'
import FastSimplexNoise from 'fast-simplex-noise'
import {type as T, level as L} from './def'
import BlockGen from './first-iteration'
import BlockExt from './second-iteration'
export default class Level {
constructor (width, height, seed = 'super random seed') {
const random = SeedRng(seed)
const noiseGen = new FastSimplexNoise({ random })
this._x = 0
this._y = 0
this._w = width
this._h = height
this._grid = new Array(this._h)
this.blockGen = new BlockGen(noiseGen)
this.blockExt = new BlockExt(noiseGen)
}
grid (x, y) {
this._x = x
this._y = y
this.generate()
return this._grid
}
generate () {
for (let i = 0; i < this._h; i++) {
const level = this._y + i
const row = Array(this._w)
const previousRow = this._grid[i - 1] || Array()
this.blockGen.level(level, row, previousRow)
this.blockExt.level(level, row, previousRow)
this._grid[i] = row
}
}
}

View file

@ -0,0 +1,46 @@
import {type as T, level as L, probability as P} from './def'
export default class BlockExt {
constructor (noiseGen) {
this.rand = (x, y) => 0.5 + 0.5 * noiseGen.raw2D(x, y)
}
level (level, row, previousRow) {
for (let i = 0; i < row.length; i++) {
const r = Math.abs(this.rand(level, i))
if (level < L.ground) this.trees(r, i, row, previousRow, level)
else if (level < L.rock) this.ground(r, i, row, previousRow)
else if (level < L.underground) this.rock(r, i, row, previousRow)
else this.underground(r, i, row, previousRow)
}
}
trees (r, i, row, previousRow, level) {
if (row[i] === T.wood && previousRow[i] === T.air) {
if (row[i - 1] === T.air) row[i - 1] = T.leaves
if (row[i + 1] === T.air) row[i + 1] = T.leaves
previousRow[i] = T.leaves
} else if (previousRow[i] === T.wood) {
row[i] = T.wood
if (row[i - 1] === T.wood) row[i - 1] = T.air
}
}
ground (r, i, row, previousRow) {
if (previousRow[i] === T.air) {
if (r < P.soil_hole) row[i] = T.air
if (row[i] === T.soil) row[i] = T.grass
} else if (previousRow[i] === T.wood && row[i - 1] === T.grass) {
row[i] = T.wood
}
}
rock (r, i, row, previousRow) {
if (previousRow[i] === T.soil && r < P.fray) row[i] = T.soil
}
underground (r, i, row, previousRow) {
if (previousRow[i] === T.stone && r < P.fray) row[i] = T.stone
}
}

View file

View file

@ -1,57 +0,0 @@
import SeedRng from 'seedrandom'
import FastSimplexNoise from 'fast-simplex-noise'
import * as T from './block-types'
import * as P from './block-probabilities'
import * as L from './block-levels'
export default class BlockGen {
constructor (seed = 'so freakin random') {
const simplex = new FastSimplexNoise({ random: SeedRng(seed) })
this.rand = (x, y) => 0.5 + 0.5 * simplex.raw2D(x, y)
}
block (level, column, above, before) {
if (level < L.PEAK) return this.air()
const r = Math.abs(this.rand(level, column))
if (level < L.GROUND) return this.tree(r, above)
if (level < L.ROCK) return this.ground(r, above)
if (level < L.UNDERGROUND) return this.rock(r)
return this.underground(r, above, before, level - L.UNDERGROUND)
}
// always returns air
air () {
return T.AIR
}
// returns mostly air, but sometimes starts a tree
tree (r, above) {
const peak = above === T.AIR && r < P.TREE
if (peak || above === T.WOOD) return T.WOOD
return T.AIR
}
// returns mostly soil and grass, sometimes gravel and sometimes air
ground (r, above) {
if (above === T.AIR && r < P.SOIL_HOLE) return T.AIR
if (above === T.AIR) return T.GRASS
if (above === T.WOOD) return T.SOIL
return r < P.SOIL_GRAVEL ? T.GRAVEL : T.SOIL
}
// returns mostly stones, sometimes gravel
rock (r) {
return r < P.ROCK_GRAVEL ? T.GRAVEL : T.STONE
}
// return mostly bedrock, sometimes caves, depending on the level
underground (r, above, before, level) {
if (above === T.STONE || above === T.GRAVEL) return T.BEDROCK
const a = P.CAVE / P.CAVE_MAX**2
const p = Math.min(P.CAVE, a * level**2)
if (r < p) return T.CAVE
return T.BEDROCK
}
}

View file

@ -1,4 +0,0 @@
export const PEAK = 24
export const GROUND = 28
export const ROCK = 32
export const UNDERGROUND = 48

View file

@ -1,6 +0,0 @@
export const TREE = 0.1
export const SOIL_HOLE = 0.3
export const SOIL_GRAVEL = 0.2
export const ROCK_GRAVEL = 0.1
export const CAVE = 0.5
export const CAVE_MAX = 250

View file

@ -1,9 +0,0 @@
export const AIR = {type: 'air', hp: 0, damage: 0}
export const GRASS = {type: 'grass', hp: 1, damage: 0}
export const LEAVES = {type: 'leaves', hp: 1, damage: 0}
export const WOOD = {type: 'wood', hp: 5, damage: 0}
export const SOIL = {type: 'soil', hp: 2, damage: 0}
export const GRAVEL = {type: 'gravel', hp: 5, damage: 0}
export const STONE = {type: 'stone', hp: 10, damage: 0}
export const BEDROCK = {type: 'bedrock', hp: 25, damage: 0}
export const CAVE = {type: 'cave', hp: 0, damage: 0}

View file

@ -1,55 +0,0 @@
import * as T from './block-types'
import * as L from './block-levels'
import BlockGen from './block-generator'
export default class Level {
constructor (width, height) {
this._x = 0
this._y = 0
this._w = width
this._h = height
this._grid = new Array(height)
this.blockGen = new BlockGen('super random seed')
}
grid (x, y) {
this._x = x
this._y = y
this.generate()
return this._grid
}
generate () {
// TODO: caching
for (let i = 0; i < this._h; i++) {
this._grid[i] = this._row(i + this._y)
}
}
_row (level = 0) {
const row = Array(this._w)
const previousRow = this._grid[level - 1] || Array()
// first step: generate a row for the given level
for (let i = 0; i < row.length; i++) {
const above = previousRow[i]
row[i] = this.blockGen.block(level, i, above, row[i - 1])
}
// second step: add extras like tree leaves
if (level < L.GROUND && level > L.PEAK) {
for (let i = 0; i < row.length; i++) {
const above = previousRow[i]
const block = row[i]
if (block === T.WOOD && above === T.AIR) {
if (row[i - 1] === T.AIR) row[i - 1] = T.LEAVES
if (row[i + 1] === T.AIR) row[i + 1] = T.LEAVES
previousRow[i] = T.LEAVES
}
}
}
return row
}
}