/* * BiomorphBounce randomly walks through the phase-space for biomorphs. * Copyright (C) 1998 Scott Maxwell * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * You can reach me at s-max@pacbell.net. */ // BiomorphBounce.java -- randomly walk around in the phase space for // Dawkins-style biomorphs, showing the results as we go. The code is // arranged according to the Model-View-Controller paradigm. import java.awt.*; import java.applet.*; import java.util.*; // Remember a line's endpoints and draw it on request. class Line { private final int x1, y1, x2, y2; // Ctor. public Line(int x1_, int y1_, int x2_, int y2_) { x1 = x1_; y1 = y1_; x2 = x2_; y2 = y2_; } // Draw me on g, translating to (ox, oy). public void draw(Graphics g, int ox, int oy) { g.drawLine(x1 + ox, y1 + oy, x2 + ox, y2 + oy); } } // Remember a biomorph's genome and "interpret" the genome to produce // the corresponding lines (that functionality really belongs in the // View). class BiomorphModel extends Observable { protected static final int N = 9; // # genes. protected int genome [] = new int[N]; private Vector lines; // Vector of Lines (where to draw me). private int siz; // Scale for Biomorph. // Ctor. public BiomorphModel(int sz) { siz = sz; // for (int i = 0; i < a.length; ++i) // genome[i] = (int) (Math.random() * 11) - 5; genome[1] = genome[2] = genome[3] = genome[4] = genome[5] = 1; genome[6] = genome[7] = -1; genome[8] = 7; // This one matters, don't change it. } // Return the vector of lines that represent me visually. public Vector getLines() { return lines; } // Compute the vector of lines that represent me visually. public void draw() { Vector l = new Vector(); int ox = 0, oy = 0; int [] br = new int [20]; int [] bs = new int [20]; int [] lx = new int [20]; int [] ly = new int [20]; int stp = 1, nux = 0, nuy = 0, mag; br[1] = 1; bs[1] = 0; lx[1] = ox; ly[1] = oy; while (true) { while (br[stp] == 0) if (--stp == 0) { lines = l; return; } mag = siz / (stp + 8); switch (br[stp]) { case 1: nux = 0; nuy = -genome[1] * mag; break; case 2: nux = genome[2] * mag; nuy = -genome[3] * mag; break; case 3: nux = genome[4] * mag; nuy = 0; break; case 4: nux = genome[5] * mag; nuy = -genome[6] * mag; break; case 5: nux = 0; nuy = -genome[7] * mag; break; case 6: nux = -genome[5] * mag; nuy = -genome[6] * mag; break; case 7: nux = -genome[4] * mag; nuy = 0; break; case 8: nux = -genome[2] * mag; nuy = -genome[3] * mag; break; default: // throw "BUG"; System.out.println("BUG " + br[stp]); break; } // Next branch. br[stp + 1] = br[stp] + 1; bs[stp + 1] = br[stp] - 1; if (br[stp + 1] >= 9) br[stp + 1] = 1; if (bs[stp + 1] == 0) bs[stp + 1] = 8; lx[stp + 1] = lx[stp] + nux; ly[stp + 1] = ly[stp] + nuy; l.addElement(new Line(lx[stp], ly[stp], lx[stp + 1], ly[stp + 1])); br[stp] = bs[stp]; bs[stp] = 0; if (++stp >= genome[8]) br[stp] = bs[stp] = 0; } } } // A BiomorphModel that changes smoothly with time. The way to think // of its manner of changing is to imagine a knob associated with each // of the biomorph's genes. The knobs move smoothly and slowly until // they reach a limit, at which point they begin to glide back the // other way. For best results, only one knob changes per tick of the // clock; but since the knobs are randomly selected, it sort of looks // like they're all moving at once. class BouncingBiomorphModel extends BiomorphModel implements Runnable { // Low and high limits for the knobs. private static final int HI = 5; private static final int LO = -HI; // We don't change the last gene's value (it's a limit on the // algorithm). This lets us easily talk about the other ones. protected static final int N1 = N - 1; private int knobSpeed []; // Knob speed (magnitude) and direction (sign). private Thread kickMe; // Kicker thread. private int delay; // Delay between clock ticks. private Object delayLock = new Object(); // Locks delay. private Object kickMeLock = new Object(); // Locks kickMe even when !kickMe. // Move a knob. private void perturb() { if (knobSpeed == null) // First time in here. { // Randomly choose speeds and directions for each knob. knobSpeed = new int [N]; for (int i = 0; i < N; ++i) { // Select a speed of -2, -1, 1, or 2. knobSpeed[i] = (int) (Math.random() * 2) + 1; if (Math.random() < 0.5) knobSpeed[i] = -knobSpeed[i]; } } // Randomly select a knob and try moving it in the same // direction and at the same speed as before. int idx = (int) (Math.random() * N); int willBe = genome[idx] + knobSpeed[idx]; int lo = (idx == 8) ? 5 : LO; int hi = (idx == 8) ? 9 : HI; if (willBe < lo) // Hit the low end, so bounce up. genome[idx] += (knobSpeed[idx] = (int) (Math.random() * 2) + 1); else if (willBe > hi) // Hit the high end, so bounce down. genome[idx] += (knobSpeed[idx] = (int) (Math.random() * 2) - 2); else // Common case: haven't reached either end. genome[idx] = willBe; } // Ctor. public BouncingBiomorphModel(int sz, int freq) { super(sz); setFrequency(Math.max(freq, 0)); } // Thread entry point. Draw me, notify the view, pause, twiddle a // knob, and repeat. public void run() { while (kickMe != null) { // Not the most efficient way, but the easiest way: synchronized (kickMeLock) { draw(); setChanged(); notifyObservers(); synchronized (delayLock) { if (delay == 0) // Let other threads run, but no more delay. kickMe.yield(); else if (delay > 0) { try { kickMe.sleep(delay); } catch (InterruptedException e) {} } } perturb(); } } } // Set # updates/sec. public void setFrequency(int freq) { synchronized (delayLock) { if (freq == 0) delay = 0; else if (freq > 0) delay = (int) (1000.0 / freq); } } // Start bouncing the biomorph. public void start() { synchronized (kickMeLock) { if (kickMe == null) kickMe = new Thread(this); kickMe.start(); } } // Stop bouncing the biomorph. public void stop() { synchronized (kickMeLock) { if (kickMe == null) return; kickMe.stop(); kickMe = null; } } } // Watch the biomorph model change, redrawing it whenever the model // changes. class BiomorphView extends Canvas implements Observer { private Image img; // The offscreen area where the drawing happens. private Graphics gImg; // The GC for img. private int w, h; // Width and height of drawing area. // Ctor. public BiomorphView(BouncingBiomorphModel model, int w_, int h_) { model.addObserver(this); w = w_; h = h_; resize(new Dimension(w, h)); } // Called back when the model changes. Draw it. public void update(Observable o, Object arg) { if (img == null) img = createImage(w, h); if (img == null) return; if (gImg == null) gImg = img.getGraphics(); if (gImg == null) return; BiomorphModel model = (BiomorphModel) o; Vector lines = model.getLines(); int size = lines.size(); gImg.setColor(Color.white); gImg.fillRect(0, 0, w, h); gImg.setColor(Color.black); gImg.drawRect(0, 0, w - 1, h - 1); for (int i = 0; i < size; ++i) ((Line) (lines.elementAt(i))).draw(gImg, w / 2, h / 2); repaint(); } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { if (img != null) g.drawImage(img, 0, 0, this); } } // The main Applet-derived class. public class BiomorphBounce extends Applet { private boolean initialized = false; // Guard multiple calls to init(). private static final int canvH = 256; // Canvas's height (in pixels). private static final int wantW = 256; // Applet's width (in pixels). private static final int wantH = canvH + 128; // Applet's height (in pixels). private BiomorphView view; // View component of MVC. private BouncingBiomorphModel model; // Model component of MVC. private Choice updateFreqChc; // Choice widget for update frequency. // Ctor. public BiomorphBounce() {} // One-time applet initialization. public void init() { // Guard against multiple calls to this method. if (initialized) return; initialized = true; // Gimme a model -- a *bouncy* model! model = new BouncingBiomorphModel(Math.min(wantW, canvH) / 4, 10); // Add the model's view. Panel p = new Panel(); p.add(view = new BiomorphView(model, wantW, canvH)); p.resize(view.size().width, view.size().height); add(p); // All this junk is to add the (labelled) choice widget. p = new Panel(); Label lbl = null; p.add(lbl = new Label("Updates/sec:")); updateFreqChc = new Choice(); String choices [] = { "1", "5", "10", "25", "50", "100", "200" }; for (int i = 0; i < choices.length; ++i) updateFreqChc.addItem(choices[i]); updateFreqChc.select(2); p.add(updateFreqChc); p.resize(Math.max(updateFreqChc.size().width, lbl.size().width), Math.max(updateFreqChc.size().height, lbl.size().height)); add(p); } // Something happened! public boolean action(Event e, Object arg) { if (e.target.equals(updateFreqChc)) { int freq = new Integer(updateFreqChc.getSelectedItem()).intValue(); model.setFrequency(freq); return true; } return false; } // Start/stop the applet. public void start() { model.start(); } public void stop() { model.stop(); } // Entry point when running as a standalone application. public static void main(String args []) { BiomorphFrame frame = new BiomorphFrame(wantW, wantH); } } // Frame class to hold BiomorphBounce applet when not running under a // Web browser. class BiomorphFrame extends Frame { // Ctor. public BiomorphFrame(int w, int h) { super("Bouncing Baby Biomorphs"); resize(w, h); BiomorphBounce applet = new BiomorphBounce(); //add(weasel, BorderLayout.CENTER); add(applet); applet.init(); applet.start(); show(); } // Old (Java 1.0-style) event handling. public boolean handleEvent(Event e, Object arg) { if (e.id == Event.WINDOW_DESTROY) System.exit(0); return super.handleEvent(e); } }