home *** CD-ROM | disk | FTP | other *** search
Java Source | 1996-08-14 | 18.5 KB | 618 lines |
- // -*- c++ -*-
- //
- // Copyright (c) 1996 Dmitri Bassarab
- //
- // Permission to use, copy, hack, and distribute this software and its
- // documentation for NON-COMMERCIAL purposes and without fee is hereby
- // granted provided that this copyright notice appears in all copies.
- //
- import java.awt.*;
- import java.awt.image.*;
- import java.applet.Applet;
- import java.util.Vector;
- import java.util.Random;
-
- //
- // Non-flicker applet.
- //
- public class Tubes extends Applet
- {
- final static Color colors[] = {
- Color.black,
- Color.red,
- Color.yellow,
- Color.green,
- Color.cyan,
- Color.blue,
- Color.magenta,
- Color.white };
-
- private Image _image = null;
- private Graphics _graphics;
- private Dimension _size;
- private Help _help;
- private Puzzle _puzzle;
- private int _level = 4;
-
- public void init()
- {
- setLayout(new BorderLayout());
- Panel panel = new Panel();
- panel.setLayout(new GridLayout(1, 0));
- panel.add(new Button("Goal"));
- panel.add(new Button("Random"));
- panel.add(new Button("Help"));
- add("North", panel);
- panel = new Panel();
- panel.setLayout(new GridLayout(1, 0));
- panel.add(new Button("Left"));
- panel.add(new Button("Flip"));
- panel.add(new Button("Right"));
- add("South", panel);
- _help = new Help(this, _level);
- _puzzle = new Puzzle(this, _level + 2);
- }
- public void stop() { if(_help != null) _help.hide(); }
- public boolean action(Event e, Object o)
- {
- if(o.equals("Help")){
- _help.show();
- return true;
- } else if(o.equals("Goal"))
- _puzzle.goal();
- else if(o.equals("Random"))
- _puzzle.randomize();
- else if(o.equals("Left"))
- _puzzle.left();
- else if(o.equals("Flip"))
- _puzzle.flip();
- else if(o.equals("Right"))
- _puzzle.right();
- repaint();
- return true;
- }
- public boolean mouseDown(Event e, int x, int y)
- {
- if(x < _size.width / 3)
- _puzzle.left();
- else if(x < _size.width * 2 / 3)
- _puzzle.flip();
- else
- _puzzle.right();
- repaint();
- return true;
- }
- public void update(Graphics g) { paint(g); }
- public void paint(Graphics g)
- {
- showStatus(_puzzle.goal_p()? " G O A L !": "");
- if(_image == null ||
- _size.width != size().width || _size.height != size().height){
- _size = new Dimension(size());
- _image = createImage(_size.width, _size.height);
- _graphics = _image.getGraphics();
- }
- _graphics.setColor(Color.black);
- _graphics.fillRect(0, 0, _size.width, _size.height);
- _graphics.translate(_size.width / 2, _size.height / 2);
- _puzzle.draw(_graphics);
- _graphics.translate(-_size.width / 2, -_size.height / 2);
- g.drawImage(_image, 0, 0, null);
- }
- Image makeImage(int w, int h)
- {
- Image i = createImage(w, h);
- Graphics g = i.getGraphics();
- g.setColor(Hollow.color);
- g.fillRect(0, 0, w, h);
- return i;
- }
- Image hollowImage(Image i)
- {
- return createImage(new FilteredImageSource(i.getSource(), new Hollow()));
- }
- static void drawImageCentered(Graphics g, Image i, int x, int y)
- { g.drawImage(i, x - i.getWidth(null)/2, y - i.getHeight(null)/2, null); }
- void setLevel(int level)
- {
- if(_level == level)
- return;
- _puzzle.setSize((_level = level) + 2);
- repaint();
- }
- }
-
- //
- // Read it! It may ...
- //
- class Help extends Frame
- {
- private static String _contents[] = {
- "Desiderata:",
- " Once upon a time we got a 6-Tubes Puzzle for",
- " an exercise in Artificial Intelligence. Then",
- " I implemented a graphical simulator of the",
- " puzzle (originally with Open GL), which helped",
- " me a lot in figuring out an appropriate",
- " heuristic for solution from an arbitrary",
- " initial state. Subsequently, I rewrote the",
- " puzzle as an applet with Java. It's now open",
- " to the general public, so you can play it in",
- " your spare time. Enjoy!",
- "",
- "Topology:",
- " The original 3D game consists of two sets of",
- " tubes, the upper and the lower, each having 6",
- " tubes arranged in a circle according to their",
- " sizes. The two sets are combined together in",
- " such way that one set can be rotated",
- " relatively to the other. The proposed",
- " implementation simplifies the topology by",
- " stretching the circle into the line and",
- " allowing circular shift of the upper set of",
- " tubes.",
- "",
- "Goal:",
- " The tubes contain 21 colored beads. There is",
- " one red bead, two yellow beads, ..., and",
- " finally, 6 magenta beads. The number of beads",
- " of each color corresponds the capacity of the",
- " tube of same color. The goal of the game is to",
- " put all the beads into corresponding tubes.",
- " As you play, monitor the status line of your",
- " browser/appletviewer; it'll inform you",
- " whenever the current puzzle state is the goal",
- " state.",
- "",
- "Controls:",
- " In order to reach the goal state from an",
- " arbitrary initial bead distribution the",
- " following three operations are allowed:",
- "",
- " o FLIP: The two sets are flipped so that the",
- " lower set becomes the upper one and",
- " vice versa. As a result of this",
- " operation the beads fall from the",
- " tubes of the new upper set into the",
- " corresponding tubes of the new lower",
- " set;",
- "",
- " o RIGHT: The upper set is circularly rotated",
- " once to the right. As a result, beads",
- " from the upper set of tubes might",
- " fall into the tubes of lower set;",
- "",
- " o LEFT: Same as RIGHT but in the other",
- " direction;",
- "",
- " Buttons with these names are located at the",
- " bottom of the applet. Moreover, the buttons",
- " divide the applet into three equally sized",
- " areas, so clicking in an appropriate area",
- " performs the same action as the button",
- " below. The buttons located over the applet are",
- " self-explanatory. The HELP frame you are",
- " currently reading, contains the LEVEL widget",
- " which allows to change the skill level of the",
- " puzzle (the number of tubes).",
- null };
-
- private static String _levelNames[] = {
- "I'm too young to die",
- "Hey, not too rough",
- "Hurt me plenty",
- "Ultra-Violence",
- "Nightmare!",
- null };
- private Tubes _applet;
-
- Help(Tubes applet, int level)
- {
- super("Puzzle Help");
- _applet = applet;
- add("West", new Panel());
- add("East", new Panel());
- Panel panel = new Panel();
- add("South", panel);
- panel.add(new Button("Hide"));
- panel = new Panel();
- add("North", panel);
- panel.add(new Label("Level:"));
- Choice choice = new Choice();
- panel.add(choice);
- for(int i = 0; _levelNames[i] != null; i++)
- choice.addItem(_levelNames[i]);
- choice.select(level);
- TextArea text = new TextArea(20, 40);
- text.setEditable(false);
- for(int i = 0; _contents[i] != null; i++)
- text.appendText(" " + _contents[i] + "\n");
- add("Center", text);
- pack();
- }
- public boolean action(Event e, Object o)
- {
- if("Hide".equals(o)){
- hide();
- return true;
- }
- for(int i = 0; _levelNames[i] != null; i++)
- if(_levelNames[i].equals(o)){
- _applet.setLevel(i);
- return true;
- }
- return super.action(e, o);
- }
- }
-
- //
- // Puzzle itself. Contains two set of slots: "_upper" & "_lower".
- //
- class Puzzle
- {
- private Tubes _applet;
- private int _diameter = 48;
- private int _height = 6;
- private int _margin = 12;
- private int _size = 0;
- private Color _color = Tubes.colors[7];
- private Image _supportImage; // slot holder: 2 margins & _size segments
- private Image _marginImage;
- private Image _segmentImage;
- private Queue _upper;
- private Queue _lower;
-
- Puzzle(Tubes applet, int size)
- {
- _applet = applet;
- _marginImage = makeBox
- (_margin, _diameter + 2 * _margin, _height, false, 0);
- _segmentImage = makeBox
- (_diameter, _diameter + 2 * _margin, _height, true, _diameter / 2);
- new Bead(_applet, _diameter / 2);
- setSize(size);
- }
- private Image makeSupport()
- {
- Rectangle b = (new IsoPoint(0, 0)).getBox
- (IsoPoint.XY,_size * _diameter + 2 * _margin, _diameter + 2 * _margin);
- Image i = _applet.makeImage(b.width, b.height += 2 * _height);
- IsoPoint p = new IsoPoint(b.width / 2, b.height / 2);
- p.isoMove(_diameter * _size / 2 + _margin / 2, 0, 0);
- Graphics g = i.getGraphics();
- Tubes.drawImageCentered(g, _marginImage, p.x, p.y);
- p.isoMove((-_diameter - _margin) / 2, 0, 0);
- for(int j = 0; j < _size; j++, p.isoMove(-_diameter, 0, 0))
- Tubes.drawImageCentered(g, _segmentImage, p.x, p.y);
- p.isoMove((_diameter - _margin) / 2, 0, 0);
- Tubes.drawImageCentered(g, _marginImage, p.x, p.y);
- return _applet.hollowImage(i);
- }
- private Image makeBox(int w, int l, int h, boolean withHole, int r)
- {
- Rectangle b = (new IsoPoint(0, 0)).getBox(IsoPoint.XY, w, l);
- Image i = _applet.makeImage(b.width, b.height += 2 * h);
- Graphics g = i.getGraphics();
- IsoPoint p = new IsoPoint(b.width / 2, b.height / 2);
- p.isoMove(0, 0, -h / 2);
- Tubes.drawImageCentered
- (g, makeCover(_color.darker(), w, l, withHole, r), p.x, p.y);
- p.isoMove(-w / 2, -l / 2, 0);
- Polygon perimeter = p.createIsoRectangle(p.XY, w, l);
- g.setColor(_color.darker());
- p.move(perimeter.xpoints[2], perimeter.ypoints[2]);
- g.fillPolygon(p.createIsoRectangle(p.XZ, -w, h));
- g.fillPolygon(p.createIsoRectangle(p.YZ, -l, h));
- p.move(perimeter.xpoints[0], perimeter.ypoints[0]);
- g.fillPolygon(p.createIsoRectangle(p.XZ, w, h));
- g.setColor(_color.darker().darker());
- g.fillPolygon(p.createIsoRectangle(p.YZ, l, h));
- p.move(b.width / 2, b.height / 2);
- p.isoMove(0, 0, h / 2);
- Tubes.drawImageCentered
- (g, makeCover(_color, w, l, withHole, r), p.x, p.y);
- return _applet.hollowImage(i);
- }
- private Image makeCover(Color c, int w, int l, boolean withHole, int r)
- {
- Rectangle b = (new IsoPoint(0, 0)).getBox(IsoPoint.XY, w, l);
- Image i = _applet.makeImage(b.width, b.height);
- Graphics g = i.getGraphics();
- g.setColor(c);
- IsoPoint p = new IsoPoint(b.width / 2, b.height / 2);
- p.isoMove(-w / 2, -l / 2, 0);
- g.fillPolygon(p.createIsoRectangle(p.XY, w, l));
- if(withHole){
- p.isoMove(w / 2, l / 2, 0);
- g.setColor(Hollow.color);
- p.fillIsoCircle(g, r);
- }
- return _applet.hollowImage(i);
- }
- void draw(Graphics g)
- {
- IsoPoint o = new IsoPoint(0, 0);
- o.isoMove(_diameter * (_size - 1) / 2, 0, 0);
- IsoPoint p = new IsoPoint(o.x, o.y);
- for(int i = _size - 1; 0 <= i; i--, p.isoMove(-_diameter, 0, 0))
- ((Slot) _lower.at(i)).draw(g, p, -_height / 2, _diameter / 2);
- Tubes.drawImageCentered(g, _supportImage, 0, 0);
- p.move(o.x, o.y);
- for(int i = _size - 1; 0 <= i; i--, p.isoMove(-_diameter, 0, 0))
- ((Slot) _upper.at(i)).draw(g, p, _height / 2, _diameter / 2);
- }
- void setSize(int size)
- {
- if(_size == size)
- return;
- _size = size;
- _supportImage = makeSupport();
- goal();
- }
- void goal()
- {
- _upper = new Queue(_size + 1);
- _lower = new Queue(_size + 1);
- for (int i = 0; i < _size; i++) {
- _upper.append(new Slot(i + 1));
- _lower.append(new Slot(i + 1));
- for(int j = 0; j <=i; j++)
- ((Slot) _lower.at(i)).prepend(new Bead(i + 1));
- }
- }
- void left()
- {
- _upper.append(_upper.getFirst());
- gravity();
- }
- void right()
- {
- _upper.prepend(_upper.getLast());
- gravity();
- }
- void flip()
- {
- Queue t = _upper; _upper = _lower; _lower = t;
- gravity();
- }
- void gravity() // 9.81 m/c^2
- {
- for (int i = 0; i < _size; i++) {
- Slot u = (Slot)_upper.at(i);
- Slot l = (Slot)_lower.at(i);
- while (!u.isEmpty() && !l.isFull())
- l.prepend(u.getFirst());
- }
- }
- void randomize()
- {
- goal();
- Random r = new Random();
- do
- for(int i = 0; i < _size * _size; i++){
- int times = r.nextInt() % _size;
- for(int j = 0; j < times; j++)
- right();
- times = r.nextInt() % 2;
- for(int j = 0; j < times; j++)
- flip();
- times = r.nextInt() % _size;
- for(int j = 0; j < times; j++)
- right();
- }
- while(goal_p());
- }
- boolean goal_p() // predicate
- {
- for(int i = 0; i < _size; i++){
- Slot l = (Slot)_lower.at(i);
- if(l.size() != l.getColor())
- return false;
- for(int j = 0; j < l.size(); j++)
- if(l.getColor() != ((Bead) l.at(j)).getColor())
- return false;
- }
- return true;
- }
- }
-
- //
- // Aka tube. Maximal capacity: "_color" beads.
- //
- class Slot extends Queue
- {
- private int _color;
-
- Slot(int color)
- {
- super(color);
- _color = color;
- }
- boolean isFull() { return size() == _color; }
- int getColor() { return _color; }
- void drawCage(Graphics g, IsoPoint o, int h, int r, boolean back)
- {
- g.setColor(back? Tubes.colors[_color].darker(): Tubes.colors[_color]);
- IsoPoint p = new IsoPoint(o.x, o.y);
- p.isoMove(0, 0, h);
- p.drawHalfIsoCircle(g, r, back);
- int s = (h < 0)? -1: 1;
- p.drawHalfIsoCylinder(g, s * (2 * r * _color - Math.abs(h)), r, back);
- p.move(o.x, o.y);
- p.isoMove(0, 0, s * 2 * r);
- for(int i = 0; i < _color; i++, p.isoMove(0, 0, s * 2 * r))
- p.drawHalfIsoCircle(g, r, back);
- }
- void draw(Graphics g, IsoPoint o, int h, int r)
- {
- drawCage(g, o, h, r, true);
- IsoPoint p = new IsoPoint(o.x, o.y);
- p.isoMove(0, 0, (h < 0)? -r - 2 * r * (_color - size()): r);
- for(int i = 0; i < size(); i++, p.isoMove(0, 0, (h < 0)? -2 * r: 2 * r))
- ((Bead) at(i)).draw(g, p.x, p.y);
- drawCage(g, o, h, r, false);
- }
- }
-
- //
- // Ambient/diffuse/specular light model. Viewer: (0, 0, 1).
- // White light: (1, 1, 1). Material shininess: 16.
- //
- class Bead
- {
- private static Image[] _image;
- private int _color;
-
- Bead(Tubes applet, int radius) // once invoked constructor
- {
- int r = radius - 1;
- double cosLN[] = new double[(2 * r + 1) * (2 * r + 1)];
- double cosRV[] = new double[(2 * r + 1) * (2 * r + 1)];
- double sqrt3 = Math.sqrt(3);
- int index = 0;
- for(int y = 0; y <= 2 * r; y++){
- for(int x = 0; x <= 2 * r; x++, index++){
- cosLN[index] = cosRV[index] = 2;
- long d = Math.round(Math.sqrt((x-r) * (x-r) + (y-r) * (y-r)));
- if(d <= r){
- double z = Math.sqrt(r * r - d * d);
- double R = Math.sqrt(z * z + (x-r) * (x-r) + (y-r) * (y-r));
- cosLN[index] = ((x-r) - (y-r) + z) / R / sqrt3;
- if(cosLN[index] > 0)
- cosRV[index]
- = 1 - Math.pow(2 * z * cosLN[index] / R - 1/sqrt3, 16);
- cosLN[index] = (cosLN[index] + 1) / 2;
- }
- }
- }
- _image = new Image[7];
- for(int c = 0; c < 7; c++){
- int pixels[] = new int[(2 * r + 1) * (2 * r + 1)];
- index = 0;
- for(int y = 0; y <= 2 * r; y++){
- for(int x = 0; x <= 2 * r; x++, index++){
- pixels[index] = 0;
- if(cosLN[index] < 2) {
- Color color = blend(cosLN[index], Tubes.colors[c], Color.black);
- if(cosRV[index] < 2)
- color = blend(cosRV[index], color, Color.white);
- pixels[index] = color.getRGB();
- }
- }
- }
- _image[c] = applet.createImage
- (new MemoryImageSource(2 * r + 1, 2 * r + 1, pixels, 0, 2 * r + 1));
- }
- }
- Bead(int color) { _color = color; }
- int getColor() { return _color; }
- void draw(Graphics g, int x, int y)
- { Tubes.drawImageCentered(g, _image[_color], x, y); }
- private Color blend(double factor, Color color, Color light)
- {
- return new Color
- ((int)(factor * color.getRed() + (1 - factor) * light.getRed()),
- (int)(factor * color.getGreen() + (1 - factor) * light.getGreen()),
- (int)(factor * color.getBlue() + (1 - factor) * light.getBlue()));
- }
- }
-
- //
- // Wraper for java.util.Vector;
- //
- class Queue extends Vector
- {
- Queue(int size) { super(size); }
- void append(Object object) { addElement(object); }
- void prepend(Object object) { insertElementAt(object, 0); }
- Object at(int i) { return elementAt(i); }
- Object getFirst()
- {
- Object object = firstElement();
- removeElementAt(0);
- return object;
- }
- Object getLast()
- {
- Object object = lastElement();
- removeElementAt(size() - 1);
- return object;
- }
- }
-
- //
- // Encapsulates all pseudo-iso drawings.
- //
- class IsoPoint extends Point
- {
- final static int XY = 1, XZ = 2, YZ = 3;
-
- IsoPoint(int x, int y) { super(x, y); }
- void isoMove(int dX, int dY, int dZ)
- {
- translate( dX * 3 / 4, -dX / 6);
- translate(-dY * 3 / 4, -dY / 6);
- translate(0, -dZ);
- }
- Polygon createIsoRectangle(int plane, int w, int h)
- {
- Polygon it = new Polygon();
- it.addPoint(x, y);
- IsoPoint p = new IsoPoint(x, y);
- if(plane == IsoPoint.XZ)
- p.isoMove(0, 0, h);
- else
- p.isoMove(0, (plane == IsoPoint.XY)? h: w, 0);
- it.addPoint(p.x, p.y);
- if(plane == IsoPoint.YZ)
- p.isoMove(0, 0, h);
- else
- p.isoMove(w, 0, 0);
- it.addPoint(p.x, p.y);
- if(plane == IsoPoint.XZ)
- p.isoMove(0, 0, -h);
- else
- p.isoMove(0, (plane == IsoPoint.XY)? -h: -w, 0);
- it.addPoint(p.x, p.y);
- return it;
- }
- Rectangle getBox(int plane, int w, int h)
- { return createIsoRectangle(plane, w, h).getBoundingBox(); }
- void fillIsoCircle(Graphics g, int r)
- { g.fillOval(x - r, y - r / 4, 2 * r, 2 * r / 4); }
- void drawHalfIsoCircle(Graphics g, int r, boolean back)
- { g.drawArc(x - r, y - r / 4, 2 * r, 2 * r / 4, 0, (back)? 180: -180); }
- void drawHalfIsoCylinder(Graphics g, int h, int r, boolean back)
- {
- IsoPoint p = new IsoPoint(x, y);
- int s = back? -1: 1;
- p.isoMove(-s * r, 0, 0);
- g.drawLine(p.x, p.y, p.x, p.y - h);
- p.move(x, y);
- p.translate(0, s * r / 4);
- g.drawLine(p.x, p.y, p.x, p.y - h);
- p.move(x, y);
- p.isoMove(0, -s * r, 0);
- g.drawLine(p.x, p.y, p.x, p.y - h);
- p.move(x, y);
- p.translate(s * r, 0);
- g.drawLine(p.x, p.y, p.x, p.y - h);
- }
- }
-
- //
- // Replaces Hollow.color'ed pixel with transparent one.
- // There is a bug/feature in JDK for WinNT: "argb" parameter
- // contains wrong color for off-screen drawable images.
- // Should I submit the bug?
- //
- class Hollow extends RGBImageFilter
- {
- private static int _color = 0xff000000;
- static Color color = new Color(_color);
-
- Hollow() { canFilterIndexColorModel = true; }
- public int filterRGB(int x, int y, int argb)
- { return (argb == _color)? 0x00000000: argb; }
- }
-