# explicitly provide a ruby-processing constrain instance method # to return a float:- amt, where range = (low .. high) def constrain(amt, range) return amt if range.include? amt return range.first if amt < range.first return range.end end
Here is a use case example, with excluded top value version of range
# # A Processing implementation of Game of Life # By Joan Soler-Adillon # # Press SPACE BAR to pause and change the cell's values with the mouse # On pause, click to activate/deactivate cells # Press R to randomly reset the cells' grid # Press C to clear the cells' grid # # The original Game of Life was created by John Conway in 1970. # CELL_SIZE = 5 ALIVE = true DEAD = false ALIVE_START = 150 INTERVAL = 100 attr_reader :pause, :cells, :row, :column, :last_time, :alive, :cells_buffer def setup size(960, 640) @pause = false # Instantiate arrays @row = width / CELL_SIZE @column = height / CELL_SIZE @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}} @last_time = 0 @alive = color(100, 255, 100) # This stroke will draw the background grid (live cells) stroke(48, 100) noSmooth end def draw background(0) #Draw live cells row.times do |x| column.times do |y| if (cells[x][y]) fill(alive) rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE) end end end # Iterate if timer ticks if (millis - last_time > INTERVAL) if (!pause) tick! @last_time = millis end end # Create new cells manually on pause if (pause && mouse_pressed?) # # Map and avoid out of bound errors over_x = (map(mouse_x, 0, width, 0, row)).to_i over_x = constrain(over_x, (0 ... row)) over_y = (map(mouse_y, 0, height, 0, column)).to_i over_y = constrain(over_y, (0 ... column)) # Check against cells in buffer if (cells_buffer[over_x][over_y]) # Cell is alive cells[over_x][over_y] = DEAD # Kill fill(0) #reflect changed status else # Cell is dead cells[over_x][over_y] = ALIVE # Make alive fill(alive) # Fill alive color end elsif (pause && !mouse_pressed?) # And then save to buffer once mouse goes up # Save cells to buffer (so we operate with one array keeping the other intact) @cells_buffer = clone2d(cells) end end def tick! # When the clock ticks # Save cells to buffer (so we operate with one array keeping the other intact) @cells_buffer = clone2d(cells) # Visit each cell: row.times do |x| column.times do |y| # And visit all the neighbours of each cell neighbours = 0 # We'll count the neighbours (x - 1 .. x + 1).each do |xx| (y - 1 .. y + 1).each do |yy| # Make sure you are not out of bounds if [(xx>=0), (xx<row), (yy>=0), (yy<column)].all? {|in_bounds| in_bounds == true} # Make sure to check against self if ![(xx == x), (yy == y)].all? {|is_self| is_self == true} if (cells_buffer[xx][yy]) # true == ALIVE neighbours += 1 # Check alive neighbours and count them end # alive end # End of if self end # End of if grid bounds end # End of yy loop end #End of xx loop # We've checked the neighbours: apply rules in one line (only in ruby)! cells[x][y] = (cells_buffer[x][y])? ((2 .. 3) === neighbours) : (neighbours == 3) end # End of y loop end # End of x loop end # End of function def key_pressed case key when 'r', 'R' # Restart: reinitialization of cells @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}} when ' ' # On/off of pause @pause = !pause when 'c', 'C' # Clear all @cells = Array.new(row) {Array.new(column) {DEAD}} end end def clone2d array result = [] array.each do |val| result << val.clone end return result end
Here using included top value version:-
# Based on SeekingNeural example by Daniel Shiffman # The Nature of Code # http://natureofcode.com load_library :vecmath module SeekingNeural class Perceptron # Perceptron is created with n weights and learning constant def initialize(n, c) @weights = Array.new(n) { rand(0 .. 1.0) } @c = c end # Function to train the Perceptron # Weights are adjusted based on vehicle's error def train(forces, error) trained = @weights.zip(forces.map { |f| f.to_a } .map { |a, b| (a * error.x + b * error.y) * @c }) .map { |w, c| constrain(w + c, (0.0 .. 1.0)) } @weights = trained end # Give me a steering result def feedforward(forces) # Sum all values forces.zip(@weights).map { |a, b| a * b }.inject(Vec2D.new, :+) end end # Seek # Daniel Shiffman <http://www.shiffman.net> class Vehicle MAX_SPEED = 4 MAX_FORCE = 0.1 attr_reader :brain, :sz, :location, :targets, :desired def initialize(n, x, y) @brain = Perceptron.new(n, 0.001) @acceleration = Vec2D.new @velocity = Vec2D.new @location = Vec2D.new(x, y) @sz = 6.0 end # Method to update location def update(width, height) # Update velocity @velocity += @acceleration # Limit speed @velocity.set_mag(MAX_SPEED) { @velocity.mag > MAX_SPEED } @location += @velocity # Reset acceleration to 0 each cycle @acceleration *= 0 @location.x = constrain(location.x, (0 .. width)) @location.y = constrain(location.y, (0 .. height)) end def apply_force(force) # We could add mass here if we want A = F / M @acceleration += force end # Here is where the brain processes everything def steer(targets, desired) # Steer towards all targets forces = targets.map { |target| seek(target) } # That array of forces is the input to the brain result = brain.feedforward(forces) # Use the result to steer the vehicle apply_force(result) # Train the brain according to the error error = desired - location brain.train(forces, error) end # A method that calculates a steering force towards a target # STEER = DESIRED MINUS VELOCITY def seek(target) desired = target - location # A vector pointing from the location to the target # Normalize desired and scale to the maximum speed desired.normalize! desired *= MAX_SPEED # Steering = Desired minus velocity steer = desired - @velocity steer.set_mag(MAX_FORCE) { steer.mag > MAX_FORCE } # Limit to a maximum steering force steer end def display # Draw a triangle rotated in the direction of velocity theta = @velocity.heading + Math::PI / 2 fill(175) stroke(0) stroke_weight(1) push_matrix translate(location.x, location.y) rotate(theta) begin_shape vertex(0, -sz) vertex(-sz * 0.5, sz) vertex(sz * 0.5, sz) end_shape(CLOSE) pop_matrix end end end include SeekingNeural # A Vehicle controlled by a Perceptron attr_reader :targets, :desired, :v def setup size(640, 360) # The Vehicle's desired location @desired = Vec2D.new(width / 2, height / 2) # Create a list of targets make_targets # Create the Vehicle (it has to know about the number of targets # in order to configure its brain) @v = Vehicle.new(targets.size, rand(width), rand(height)) end # Make a random ArrayList of targets to steer towards def make_targets @targets = Array.new(8) { Vec2D.new(rand(width), rand(height)) } end def draw background(255) # Draw a circle to show the Vehicle's goal stroke(0) stroke_weight(2) fill(0, 100) ellipse(desired.x, desired.y, 36, 36) # Draw the targets targets.each do |target| no_fill stroke(0) stroke_weight(2) ellipse(target.x, target.y, 16, 16) line(target.x, target.y - 16, target.x, target.y + 16) line(target.x - 16, target.y, target.x + 16, target.y) end # Update the Vehicle v.steer(targets, desired) v.update(width, height) v.display end def mouse_pressed make_targets end
No comments:
Post a Comment