view grid.rb @ 0:1eef88068f9f tip

initial commit of maze game source
author ferencd
date Sun, 15 Sep 2019 11:46:47 +0200
parents
children
line wrap: on
line source
#---
# Excerpted from "Mazes for Programmers",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material, 
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose. 
# Visit http://www.pragmaticprogrammer.com/titles/jbmaze for more book information.
#---
require 'cell'

# Corner constants, to know where to put a torch
TOP_LEFT = 1
TOP_RIGHT = 2
BOTTOM_LEFT = 4
BOTTOM_RIGHT = 8

class Room
  attr_reader :x1, :y1, :x2, :y2

  def initialize(px1,py1,px2,py2)
    @x1 = px1
    @x2 = px2
    @y1 = py1
    @y2 = py2
  end

  def in(x, y)
    return x > @x1-1 && x < @x2+1 && y > @y1-1 && y < @y2+1
  end
end


class Grid
  attr_reader :rows, :columns, :dead_ends

  def initialize(rows, columns)
    @rows = rows
    @columns = columns
    @grid = prepare_grid
    @rooms = []

    # Where food will go
    @dead_ends = {}
    @dead_ends[1] = []
    @dead_ends[2] = []
    @dead_ends[4] = []
    @dead_ends[8] = []

    configure_cells

  end

  def prepare_grid
    Array.new(rows) do |row|
      Array.new(columns) do |column|
        Cell.new(row, column)
      end
    end
  end

  def configure_cells
    each_cell do |cell|
      row, col = cell.row, cell.column

      cell.north = self[row - 1, col]
      cell.south = self[row + 1, col]
      cell.west  = self[row, col - 1]
      cell.east  = self[row, col + 1]
    end
  end

  #
  # Creates a room in the grid
  #
  def create_room(x1,y1, x2,y2)
    # Firstly: empty the room
    puts "x1:#{x1}, y1, x2, y2"
    (x1..x2).each do |col|
      (y1..y2).each do |row|
        @grid[row][col].link(@grid[row][col+1]) # -> Next to right
        @grid[row][col].link(@grid[row+1][col]) # -> Next to down
      end
    end
    # then create the surrounding walls
    (x1 + 1..x2-1).each do |col|
      @grid[y1+1][col].unlink(@grid[y1][col])
      @grid[y2-1][col].unlink(@grid[y2][col])
    end

    (y1+1..y2-1).each do |row|
      @grid[row][x1+1].unlink(@grid[row][x1])
      @grid[row][x2-1].unlink(@grid[row][x2])
    end

    r = Room.new(x1,y1,x2,y2)
    @rooms << r

  end

  def [](row, column)
    return nil unless row.between?(0, @rows - 1)
    return nil unless column.between?(0, @grid[row].count - 1)
    @grid[row][column]
  end

  def random_cell
    row = rand(@rows)
    column = rand(@grid[row].count)

    self[row, column]
  end

  def size
    @rows * @columns
  end

  def each_row
    @grid.each do |row|
      yield row
    end
  end

  def each_cell
    each_row do |row|
      row.each do |cell|
        yield cell if cell
      end
    end
  end

  # By default, we'll simply display a cell as a blank space. We'll use this
  # to add other ways of displaying cells.
  def contents_of(cell)
    ' '
  end
#
  def background_color_for(cell)
    nil
  end

  def to_s
    maze_js_array
  end

  #
  # Returns the number for the given cell
  #
  def number_for_cell(cell)
    number = 0
    number |= EAST if cell.linked?(cell.east)
    number |= NORTH if cell.linked?(cell.north)
    number |= SOUTH if cell.linked?(cell.south)
    number |= WEST if cell.linked?(cell.west)
    number
  end

  #
  # Returns the maze as a javascript array. This also will initialize all the dead ends
  # where we will put food or jevelry items
  #
   def maze_js_array
    output = "var maze = [\n"
    dist_strings = []

    each_row do |row| 
      # num array, the number for the row
      numarray = []
      row_string = ''

      row.each do |cell| 
        cell = Cell.new(-1, -1) unless cell
        cell_nr = number_for_cell(cell)
        numarray << cell_nr

        # Add this cell as a dead end destination
        @dead_ends[cell_nr] << cell if [1,2,4,8].include? cell_nr

      end

      row_string << '[' << numarray.join(', ') << ']'
      dist_strings << row_string
      
    end
    output << dist_strings.join( ",\n" )
    output << "];\n"
    output
  end

  def direction(d)
    return 'N' if d == NORTH
    return 'W' if d == WEST
    return 'E' if d == EAST
    return 'S' if d == SOUTH
    return '?'
  end

  #
  # Returns the distances as a js array
  #
  def distances_js_array_path
    output = ''
    dist_strings = []

    each_row do |row|
      row.each do |cell|
        cell = Cell.new(-1, -1) unless cell
        conts = contents_of(cell).to_s
        if conts.strip.length > 0
          dist_strings << {:idx => conts.to_i, :row => cell.row, :col => cell.column}
        end
      end
    end
    sorted = dist_strings.sort_by {|el| el[:idx]}

    # The directions this will go
    walk_string = ''
    # Now create a direction array from the elements
    current_cell = self[sorted[0][:row], sorted[0][:col] ]
    sorted.each_with_index do |cell, index|
      if index > 0
          # Find the direction from curent_cell to cell
        the_cell = self[cell[:row], cell[:col] ]
        dest = direction(current_cell.direction(the_cell))
        walk_string << dest
        current_cell = the_cell
      end
    end
    output << "#{walk_string}"
    output
  end

  #
  # Returns the distances as a js array
  #
  def distances_js_array
    output = "var distances = [\n"
    dist_strings = []

    each_row do |row|

      row_string = ''
      numarray = []

      row.each do |cell|
        cell = Cell.new(-1, -1) unless cell
        conts = contents_of(cell).to_s
        if conts.strip.length > 0
            numarray << conts
        else
          numarray << '-1'
        end
      end
      row_string << '[' << numarray.join(', ') << ']'
      dist_strings << row_string
    end
    output << dist_strings.join(",\n")
    output << "];\n"
    output
  end

  def maze_serialized_text
    output = []
    each_row do |row|
      numarray = []
      row.each do |cell|
        cell = Cell.new(-1, -1) unless cell
        numarray << number_for_cell(cell)
      end
      output << numarray.join(',')
    end
    output.join(':')
  end

  def deadends
    list = []

    each_cell do |cell|
      list << cell if cell.links.count == 1
    end

    list
  end

  def braid(p=1.0)
    deadends.shuffle.each do |cell| 
      next if cell.links.count != 1 || rand > p

      neighbors = cell.neighbors.reject { |n| cell.linked?(n) } 
      best = neighbors.select { |n| n.links.count == 1 } 
      best = neighbors if best.empty? 

      neighbor = best.sample 
      cell.link(neighbor)
    end
  end


  #
  # Tells us if we can make a room at the given coordinates
  #
  def can_make_room(x1, y1, x2, y2)

    @rooms.each do |room|
      return FALSE if room.in(x1, y1)
      return FALSE if room.in(x2, y2)
      return FALSE if room.in(x1, y2)
      return FALSE if room.in(x2, y1)
    end

    TRUE
  end

  #
  # Creates a list of locations where torches can be found, mostly in corners at unfinshed walls
  #
  def identify_torch_locations
    numarray = []
    j = 0
    each_row do |row|
      i = 0
      row.each do |cell|
        number = 0
        number |= TOP_RIGHT if cell.east && cell.north && cell.linked?(cell.east) && cell.linked?(cell.north) && !cell.east.linked?(cell.east.north) && !cell.north.linked?(cell.north.east)
        number |= TOP_LEFT if cell.west && cell.north && cell.linked?(cell.west) && cell.linked?(cell.north) && !cell.west.linked?(cell.west.north) && !cell.north.linked?(cell.north.west)
        number |= BOTTOM_RIGHT if cell.east && cell.south && cell.linked?(cell.east) && cell.linked?(cell.south) && !cell.east.linked?(cell.east.south) && !cell.south.linked?(cell.south.east)
        number |= BOTTOM_LEFT if cell.west && cell.south && cell.linked?(cell.west) && cell.linked?(cell.south) && !cell.west.linked?(cell.west.south) && !cell.south.linked?(cell.south.west)
        if number != 0
          torch_data = { :i => i, :j => j, :number => number}
          numarray << torch_data
        end
        i += 1
      end
      j += 1
    end
    numarray
  end

end