Skip to content

Instantly share code, notes, and snippets.

@shawn42
Created May 13, 2019 15:06
Show Gist options
  • Save shawn42/3bbe4c7488ed506d98aee2d3dbd100e7 to your computer and use it in GitHub Desktop.
Save shawn42/3bbe4c7488ed506d98aee2d3dbd100e7 to your computer and use it in GitHub Desktop.
require 'rainbow'
RULES = {
coast: [:land, :coast, :sea],
sea: [:sea, :coast],
land: [:land, :coast],
}
DISPLAY = {
land: Rainbow("-").bg(:orange),
coast: Rainbow(".").color(:orange).bg(:linen),
sea: Rainbow("~").bg(:blue).color(:lightblue),
}
def draw(map)
puts
map.each do |row|
row.each do |tile|
t = tile.value ? DISPLAY[tile.value] : "?"
STDOUT.write t
end
puts
end
end
class Map
include Enumerable
attr_reader :width, :height
def initialize(w, h)
@width = w
@height = h
@tile_type_counts = Hash.new {|h,k| h[k] = 0 }
@collapsed_count = 0
@_map = []
h.times do |y|
@_map[y] = []
w.times do |x|
@_map[y][x] = Cell.new(x, y, TILES)
end
end
end
def [](i)
@_map[i]
end
def each(&blk)
@_map.each &blk
end
def flatten
@_map.flatten
end
def weighted_rand(weighted_map)
# {a: 0.4, b: 0.4, c: 0.2}
min = weighted_map.values.min
if min < 0
amount_to_slide = -min
weighted_map = Hash[weighted_map.map{|k,v| [k, [v+amount_to_slide, 0].max]}]
end
total = weighted_map.values.reduce(&:+)
normalized = Hash[weighted_map.map{|k,v| [k, v.to_f/total]}]
choice = rand
last = 0
ranges = normalized.map do |k,v|
r = (last...v+last)
last = last + v
[r, k]
end
ranges.find{|rr|rr[0].include?(choice)}[1]
end
def collapse!(cell)
weights = {
coast: 0.2,
sea: 0.4,
land: 0.4,
}
opts = cell.options
diffs = opts.each_with_object({}) do |o, h|
perc_on_map = @tile_type_counts[o].to_f/@collapsed_count
h[o] = @collapsed_count.zero? ? weights[o] : weights[o] - perc_on_map
end
new_val = weighted_rand diffs
@collapsed_count += 1
@tile_type_counts[new_val] += 1
cell.value = new_val
cell.options = [cell.value]
end
end
class Cell
attr_reader :x, :y
attr_accessor :value, :options
def initialize(x, y, options)
@x = x
@y = y
@options = options.dup
end
def entrophy
options.size
end
def to_s
"C: #{x},#{y} #{options} -> #{value ? value : '?'}"
end
end
def neighbors(map, cell)
x = cell.x
y = cell.y
[
[x+1, y],
[x-1, y],
[x, y-1],
[x, y+1],
].map{|nx,ny| cell_for map, nx, ny}.compact
end
def cell_for(map, x, y)
# puts "looking for cell: #{[x,y]} vs #{[map.first.size, map.size]}"
if y >= 0 && y < map.height
if x >= 0 && x < map.width
return map[y][x]
end
end
nil
end
def allowed_opts_for(cell, instigated_by:)
cell.options.select do |opt|
instigated_by.options.any? do |iopt|
RULES[opt].include? iopt
end
end
end
def propogate_from(map, changed_cell)
changed = [changed_cell]
until changed.empty?
open = [changed.pop]
until open.empty?
cell = open.pop
ns = neighbors(map, cell)
# puts "neighbors of cell: #{cell} : #{ns.size}"
ns.each do |ncell|
# puts "ncell: #{ncell}"
new_options = allowed_opts_for(ncell, instigated_by: cell)
# puts "newopts: #{new_options}"
# gets
if ncell.options.size != new_options.size
ncell.options = new_options
# we collapsed a little.. propogate again
changed << ncell
end
end
end
end
end
def step(map, cell=nil)
cell ||= map.flatten.select{|cell|cell.entrophy > 1}.sort_by(&:entrophy).first
map.collapse! cell
propogate_from map, cell
end
WEIGHTS = {
land: 6,
coast: 1,
sea: 3,
}
TILES = WEIGHTS.keys
w = 80
h = 40
map = Map.new w, h
# draw map
step map, map[rand(w)][rand(h)]
# draw map
while map.any?{|col| col.any?{ |cell| cell.value.nil? }}
step map
# draw map
end
draw map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment