/* * CellAuto1d is a basic linear cellular automata applet. * 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. */ // A more or less standard applet to display linear cellular automata. import java.awt.*; import java.applet.*; // Model a 1-D CA. class Ca1dModel { private static final int nStates = 4; // 8 is also OK, but 4 looks better. private static final int ruleLen = nStates * 3; private byte [] states; // State of each element, [0..nStates). private int len; // Length of states[]. private byte [] rule; // Maps three (adjacent) states to a new state. private Object ruleLock = new Object(); private Object statesLock = new Object(); // Common code for both scrambleState() and scrambleRule(). private byte [] scrambleAux(int len) { byte [] a = new byte [len]; for (int i = 0; i < len; ++i) a[i] = (byte) (Math.random() * nStates); return a; } // Ctor. public Ca1dModel(int l) { // This is just so we can synchronize on them. states = new byte [1]; rule = new byte [1]; len = l; scrambleState(); scrambleRule(); } // Randomly scramble the state but not the rule. public void scrambleState() { synchronized (statesLock) { states = scrambleAux(len); } } // Randomly scramble the rule but not the state. public void scrambleRule() { synchronized (ruleLock) { rule = scrambleAux(ruleLen); } } // Apply the current rule to the current state, returning the new // (read-only!) state. public byte [] tick() { synchronized (ruleLock) { synchronized (statesLock) { byte [] newStates = new byte [len]; int len1 = len - 1; // Do the two endpoints first, so that we can then // do all the points in between without any // special-case code. newStates[0] = rule[states[len1] + states[0] + states[1]]; newStates[len1] = rule[states[len1 - 1] + states[len1] + states[0]]; for (int i = 1; i < len1; ++i) newStates[i] = rule[states[i - 1] + states[i] + states[i + 1]]; states = newStates; return states; } } } } // The main canvas where the action is. It runs itself in its own // thread and can be stopped or started without losing its state. class Ca1dUpdater extends Canvas implements Runnable { private int perSec; // # updates/sec. private Ca1dModel model; // Model that calculates updates. private Image img; // Offscreen buffer to hold drawing results. private Thread kickMe; // Thread that runs me. private int y = 0; // Line of img we're drawing now (wraps around). // Map state value (from model) to color. More than 8 colors is // just ugly -- in fact, more than 4 is pretty ugly. private static final Color colors [] = { Color.black, Color.white, Color.red, Color.green, Color.blue, Color.cyan, Color.orange, Color.gray }; // Ctor. public Ca1dUpdater(int ps, Ca1dModel m) { perSec = ps; model = m; } // AWT update callback. public void update(Graphics g) { paint(g); } // AWT paint callback. All the drawing takes place in the // offscreen Image img, which I just blit onto myself. public void paint(Graphics g) { if (img != null) g.drawImage(img, 0, 0, this); } // Thread entry point. This does the real work, periodically public void run() { // Create the offscreen image and get a graphics context for it. Graphics g = null; while (true) { if (img == null) img = createImage(size().width, size().height); if (img != null) { g = img.getGraphics(); if (g != null) break; } // Couldn't get one or both; let any other threads run // and then try again. if (kickMe == null) return; try { kickMe.sleep(100); } catch(InterruptedException e) { } } while (true) { byte [] states = model.tick(); for (int i = 0; i < states.length; ++i) { g.setColor(colors[states[i]]); g.drawLine(i, y, i, y); } if (++y >= size().height) y = 0; repaint(); // If perSec == 0, then run at top speed. Else sleep. if (kickMe == null) return; if (perSec == 0) kickMe.yield(); else if (perSec < 0) perSec = 1; if (perSec > 0) try { kickMe.sleep(1000 / perSec); } catch(InterruptedException e) {} } } // Set desired # updates per second. public void setFrequency(int ps) { perSec = ps; } // Start running, if we're not running already. public void start() { if (kickMe == null) kickMe = new Thread(this); kickMe.start(); } // Stop running if we've started. public void stop() { if (kickMe == null) return; kickMe.stop(); kickMe = null; } // Clear and start painting from the top. public void reset() { y = 0; Graphics g = (img == null) ? null : img.getGraphics(); if (g != null) g.clearRect(0, 0, size().width, size().height); } } // The main applet class. public class CellAuto1d extends Applet { private boolean initialized = false; // Guard redundant calls to init(). private Ca1dModel model; // The CA. private Ca1dUpdater canvas; // The view onto the CA. private Button scrambleRuleBtn; // "Scramble Rule" button. private Button scrambleDataBtn; // "Scramble State" button. private Button scrambleBothBtn; // "Scramble Both" button. private Choice updateFreqChc; // "Updates/sec" choice widget. // Desired (width, height) of the applet and height of the canvas. private static final int canvH = 128; private static final int wantW = 256; private static final int wantH = 128 + canvH; // Set the update frequency based on the choice widget's setting. private void setFrequency() { if ((canvas == null) || (updateFreqChc == null)) return; // Should never happen. int freq = 0; try { freq = new Integer(updateFreqChc.getSelectedItem()).intValue(); } catch (NumberFormatException e) { // It's just the "Zoom!" item. Let freq remain 0. } if (freq >= 0) canvas.setFrequency(freq); } // Ctor. public CellAuto1d() {} // Called by the browser to perform one-time initialization. public void init() { // Gracefully handle multiple calls to this method. if (initialized) return; initialized = true; model = new Ca1dModel(wantW); // First row (just the canvas itself). int rowHeight = 0; Panel p = new Panel(); canvas = new Ca1dUpdater(0, model); canvas.resize(new Dimension(wantW, canvH)); p.add(canvas); rowHeight = Math.max(rowHeight, canvas.size().height); p.resize(new Dimension(wantW, rowHeight)); add(p); // Second row. rowHeight = 0; p = new Panel(); p.add(scrambleRuleBtn = new Button("Scramble Rule")); rowHeight = Math.max(rowHeight, scrambleRuleBtn.size().height); p.add(scrambleDataBtn = new Button("Scramble Data")); rowHeight = Math.max(rowHeight, scrambleDataBtn.size().height); p.resize(new Dimension(wantW, rowHeight)); add(p); // Third row. rowHeight = 0; p = new Panel(); p.add(scrambleBothBtn = new Button("Scramble Both (And Clear)")); rowHeight = Math.max(rowHeight, scrambleBothBtn.size().height); p.resize(new Dimension(wantW, rowHeight)); add(p); // Third row. rowHeight = 0; p = new Panel(); Label lbl = null; p.add(lbl = new Label("Updates/sec:")); rowHeight = Math.max(rowHeight, lbl.size().height); String [] choices = { "1", "5", "10", "25", "50", "100", "200", "Zoom!" }; p.add(updateFreqChc = new Choice()); for (int i = 0; i < choices.length; ++i) updateFreqChc.addItem(choices[i]); updateFreqChc.select(2); rowHeight = Math.max(rowHeight, updateFreqChc.size().height); p.resize(new Dimension(wantW, rowHeight)); add(p); } // Start calculating. public void start() { setFrequency(); canvas.start(); } // Whoa! public void stop() { canvas.stop(); } // GUI events. public boolean action(Event e, Object arg) { if (e.target.equals(scrambleRuleBtn)) { model.scrambleRule(); return true; } if (e.target.equals(scrambleDataBtn)) { model.scrambleState(); return true; } if (e.target.equals(scrambleBothBtn)) { model.scrambleRule(); model.scrambleState(); canvas.reset(); return true; } if (e.target.equals(updateFreqChc)) { setFrequency(); return true; } return false; } // In case we're not running in in a Web browser. public static void main(String args[]) { CellAuto1dFrame frame = new CellAuto1dFrame(wantW, wantH); } } // Only used when the CellAuto applet is not running in a Web browser. class CellAuto1dFrame extends Frame { // Ctor. public CellAuto1dFrame(int w, int h) { super("1-D Cellular Automata"); // setSize(w, h); resize(w, h); CellAuto1d applet = new CellAuto1d(); // add(applet, BorderLayout.CENTER); add(applet); applet.init(); applet.start(); // setVisible(true); 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); } }