Created
May 13, 2019 15:06
-
-
Save shawn42/3bbe4c7488ed506d98aee2d3dbd100e7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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