> Hi all, > > I did the quiz :) I am new to Ruby so any tips are appreciated. Hooray! :) I've had a quick squiz through your code, and I think I understand what's going on. I'll play with it a bit more tonight. Cheers, Benjohn > > View at > > http://curi.us/code/dungeon.html > > or below: > > # dungeon.rb Version 1.0 > # Elliot Temple > # May 31, 2006 > # > # This is my first Ruby Quiz entry > # > # For Ruby Quiz #80 > # http://rubyquiz.com/quiz80.html > # > # Generates an ASCII dungeon with an evolutionary algorithm. Makes > random > # changes and calls undo if the evaluate method returns a lower number. > # Continues for a while, then makes sure to get valid output. > # > # It works but could benefit from tuning various numbers and some new > features > # in the evaluate function. It could also be faster. Sample output at > bottom. > > class Tile > attr_accessor :x, :y > @@TileType = Struct.new(:graphic, :frequency, :walkable) > @@data = { > :wall => @@TileType.new("#", 250, false), > :open => @@TileType.new(" ", 120, true), > :water => @@TileType.new("~", 10, true), > :stairs_up => @@TileType.new("<", 1, true), > :stairs_down => @@TileType.new(">", 1, true) > } > def initialize x, y, type = :any > @x = x > @y = y > if type == :any > @type_history = [get_rand_tile] > else > @type_history = [type] > end > end > def type > @type_history.last > end > def to_s > @@data[@type_history.last].graphic > end > def get_rand_tile > total = @@data.inject(0) { |total, pair| total + pair > [1].frequency } > @@data.each do |k,v| > return k if rand(total) < v.frequency > total -= v.frequency > end > end > def random_change > @type_history << get_rand_tile > end > def undo(n=1) > n.times do > @type_history.pop > end > end > def walkable? > @@data[@type_history.last].walkable > end > end > > class Map > def initialize(height,width) > @height = height > @width = width > @map = [] > @changeable_tiles = [] > @last_changed = [] > width.times do |i| > column = [] > height.times do |j| > if (j == 0) or (j == width - 1) or (i == 0) or (i == width - 1) > column << Tile.new(i, j, :wall) > else > tmp = Tile.new(i, j) > column << tmp > @changeable_tiles << tmp > end > end > @map << column > end > @changeable_tiles = @changeable_tiles.sort_by {rand} > # x = @changeable_tiles.shift > # x.become_stairs_up > # x = @changeable_tiles.shift > # x.become_stairs_down > end > def to_s > # old version that put # around the output > # '#' * (@width+2) + "\n" + (@map.collect { |row| '#' + > row.collect {|tile| tile.to_s}.join("") + '#' }.join "\n") + "\n" + > '#' * (@width+2) > @map.collect { |row| row.collect {|tile| tile.to_s}.join > ("") }.join "\n" > end > > def update n=1 > n.times do > x = @changeable_tiles[rand(@changeable_tiles.length)] > x.random_change > @last_changed << x > end > end > > def undo n=1 > n.times do > @last_changed.pop.undo > end > end > > def path_between start, destination, exclude = [] > return false if start.nil? > return true if start == destination > return false unless start.walkable? > return false if exclude.include?(start) > exclude << start > path_between(self.down(start), destination, exclude) or > path_between(self.up(start), destination, exclude) or path_between > (self.left(start), destination, exclude) or path_between(self.right > (start), destination, exclude) > end > > def path_between2 start, destination > g = find_group(start) > g.include?(destination) > end > > def find_group start, walkable = true, group = [] > return group if start.nil? > return group unless start.walkable? == walkable > return group if group.include?(start) > group << start > find_group(self.down(start), walkable, group) > find_group(self.up(start), walkable, group) > find_group(self.left(start), walkable, group) > find_group(self.right(start), walkable, group) > return group > end > > def count_groups walkable = true > tiles = @map.flatten.select { |tile| tile.walkable? == walkable } > count = 0 > while tiles.any? > count += 1 > tiles -= find_group(tiles[0], walkable) > end > count > end > > def left tile > @map[tile.x - 1][tile.y] rescue nil > end > def right tile > @map[tile.x + 1][tile.y] rescue nil > end > def down tile > @map[tile.x][tile.y - 1] rescue nil > end > def up tile > @map[tile.x][tile.y + 1] rescue nil > end > > def stair_distance > (find_one(:stairs_up).x - find_one(:stairs_down).x).abs + > (find_one(:stairs_up).y - find_one(:stairs_down).y).abs > end > > def find_one tile_type > @map.flatten.detect {|tile| tile_type == tile.type} > end > > def number_of tile_type > @map.flatten.inject(0) do |total, tile| > tile.type == tile_type ? total + 1 : total > end > end > > def valid? > return false unless number_of(:stairs_up) == 1 > return false unless number_of(:stairs_down) == 1 > return false unless path_between(find_one(:stairs_up), find_one > (:stairs_down)) > true > end > > def evaluate > score = 0 > score -= 200 unless valid? > if (number_of(:stairs_up) == 1) && (number_of(:stairs_down) == 1) > score += 200 * stair_distance > end > score -= 100 * count_groups(true) > score -= 70 * count_groups(false) > end > end > > map = Map.new 15,15 > > tmp = map.to_s > map.update 50 > map.undo 40 > map.update 50 > map.undo 60 > raise "undo bug" unless tmp == map.to_s > > valid_steps = 0 > e = map.evaluate > n = 25 > undos = 0 > > 2000.times do > map.update n > if map.evaluate >= e > e = map.evaluate > else > map.undo n > undos += 1 > end > end > > until map.valid? > map.update 1 > valid_steps += 1 > end > > puts map > puts "steps to validate #{valid_steps}" > puts "undos #{undos}" > puts "stair distance is #{map.stair_distance}" > puts map.count_groups > puts map.count_groups(false) > puts map.evaluate > > =begin > Sample Output: > > ############### > ## ##### ## # > ## ## ## ### ## > # # ## > ## > ## ####### > ## ## ####### > ####### ## # ## > ### ### ## # > ### ## # # > # #### #~~### > ### #### ## ## > ####~ ##### > ##### # #### # > # <## ###### > ############### > steps to validate 0 > undos 1959 > stair distance is 11 > 4 > 1 > 1730 > =end > > -- Elliot Temple > http://www.curi.us/blog/ > > > > >