//
Copyright 2001 Ken Perlin
import java.awt.*;
import java.util.*;
import java.io.*;
import java.net.*;
public class Poetry extends BufferedApplet
{
public String in[] = {};
Hashtable words = null, pairs = null, trpls = null, tileMap;
Vector tiles = new Vector();
Vector drifts = new Vector();
int w, h, mx, my;
boolean isM = false, isT = false;
// START UP AND REGISTER A DRIFT BEHAVIOR
void add(PoetryDrift d) {
drifts.addElement(d);
d.start();
}
// BEFORE EXITING, STOP ALL DRIFT BEHAVIORS
public void stop() {
super.stop();
for (int i = 0 ; i < drifts.size() ; i++)
((PoetryDrift)drifts.elementAt(i)).stop();
}
PoetryEntry A = null;
int prevx, prevy;
// USER SELECTS AN ACTIVE TILE TO DRAG AROUND
public boolean mouseDown(Event E, int x, int y) {
if (x < 10 && y < 10) {
isT = true;
return true;
}
A = null;
for (int i = 0 ; i < tiles.size() ; i++) {
PoetryEntry T = (PoetryEntry)tiles.elementAt(i);
T.isActive = false;
if (x >= T.l() && x < T.r() && y >= T.t() && y < T.b())
A = T;
}
if (A == null)
isM = true;
else {
A.isActive = true;
A.isStuck = ! A.isStuck;
prevx = x;
prevy = y;
}
return true;
}
// USER LIFTS MOUSE BUTTON TO RELEASE THE ACTIVE TILE
public boolean mouseUp(Event e, int x, int y) {
if (isT)
isT = false;
else if (isM)
isM = false;
else {
A.isActive = false;
A = null;
}
return true;
}
// USER DRAGS THE ACTIVE TILE TO DESIRED LOCATION
public boolean mouseDrag(Event e, int x, int y) {
if (isM) {
mx = x;
my = y;
}
else {
A.x += x - prevx;
A.y += y - prevy;
prevx = x;
prevy = y;
}
return true;
}
PoetryEntry add(Hashtable h, String s) {
PoetryEntry P;
if ((P = (PoetryEntry)h.get(s)) == null) {
h.put(s, P = new PoetryEntry(this));
P.s = s;
}
P.count++;
return P;
}
int count = 0;
void read(String URLName) {
Vector tmp = new Vector();
String line;
try {
URL url = new URL(getCodeBase(), URLName);
try {
DataInputStream stream = new DataInputStream(url.openStream());
try {
while ((line = stream.readLine()) != null)
tmp.addElement(line);
} catch (Exception e) {System.out.println("reading: " + e);}
} catch (Exception e) {System.out.println(e);}
} catch (MalformedURLException e) {System.out.println(e);}
in = new String[tmp.size()];
for (int i = 0 ; i < in.length ; i++)
in[i] = (String)tmp.elementAt(i);
}
void parseWords(String in[]) {
String word;
words = new Hashtable();
pairs = new Hashtable();
trpls = new Hashtable();
Random R = new Random();
int nWords = 0;
String prev = "", prev2 = "";
for (int lineNo = 0 ; lineNo < in.length && in[lineNo] != null ; lineNo++) {
StringTokenizer st = new StringTokenizer(in[lineNo], " ,;./():`'?!-=\"");
while (st.hasMoreTokens()) {
word = st.nextToken().toLowerCase();
if (word.charAt(0) != '<') {
add(words, word);
if (prev.length() > 0)
add(pairs, prev + " " + word);
if (prev2.length() > 0)
add(trpls, prev2 + " " + prev + " " + word);
prev2 = prev;
prev = word;
nWords++;
}
}
}
tileMap = new Hashtable();
int density = nWords / 400;
for (Enumeration e = words.keys() ; e.hasMoreElements() ; ) {
word = (String)e.nextElement();
PoetryEntry W = (PoetryEntry)words.get(word);
int n = W.count / density;
if (n == 0)
continue;
n = 2 + (int)Math.sqrt(n);
int copies = n/3 + (Math.abs(R.nextInt()) % (2*n/3));
for (int i = 0 ; i < copies ; i++) {
PoetryEntry T = new PoetryEntry(this);
tiles.addElement(T);
TileClass V = getTileClass(word);
if (V == null)
tileMap.put(word, V = new TileClass());
V.addElement(T);
T.count = 1;
T.s = word;
T.x = w/4 + (Math.abs(R.nextInt()) % w)/2;
T.y = h/4 + (Math.abs(R.nextInt()) % h)/2;
T.w = 9*word.length()+6;
T.h = 17;
}
}
Object ob;
for (Enumeration e = pairs.keys() ; e.hasMoreElements() ; )
if (((PoetryEntry)pairs.get(ob = e.nextElement())).count < 2)
pairs.remove(ob);
for (Enumeration e = trpls.keys() ; e.hasMoreElements() ; )
if (((PoetryEntry)trpls.get(ob = e.nextElement())).count < 2)
trpls.remove(ob);
add(new WordsDrift(this, words));
add(new PairsDrift(this, pairs));
add(new TrplsDrift(this, trpls));
}
// USE A FIXED WIDTH FONT FOR LETTERING ON THE TILES
Font font = new Font("Courier", Font.BOLD, 15);
// COLOR FOR ACTIVE TILE
Color hiliteColor = new Color(1f,.8f,.7f);
Color bgColor = new Color(1f,1f,1f);
Color activeColor = new Color(1f,.5f,.5f);
Color stuckColor = new Color(1f,1f,.5f);
Color tileColor = new Color(.95f,.95f,.95f);
Color edgeColor = new Color(.8f,.8f,.8f);
double time = 0, t = 0, T = 0;
public void render(Graphics g) {
time += .03;
t = Math.sin(time);
T = t > 0 ? 1 : -1;
w = bounds().width;
h = bounds().height;
g.setColor(bgColor);
g.fillRect(0, 0, w, h);
if (words == null)
parseWords(in);
g.setFont(font);
for (int i = 0 ; i < tiles.size() ; i++) {
PoetryEntry T = (PoetryEntry)tiles.elementAt(i);
g.setColor(T.isActive ? activeColor : T.isStuck ? stuckColor : tileColor);
g.fillRect(T.ix(), T.iy(), T.w, T.h);
g.setColor(Color.black);
g.drawString(T.s, T.ix()+3, T.iy()+12);
g.fillRect(T.ix(), T.iy()+T.h, T.w, 1);
g.fillRect(T.ix()+T.w, T.iy(), 1, T.h+1);
g.setColor(edgeColor);
g.fillRect(T.ix(), T.iy(), T.w, 1);
g.fillRect(T.ix(), T.iy(), 1, T.h+1);
}
g.setColor(Color.pink);
g.fill3DRect(0,0,10,10,!isT);
}
TileClass getTileClass(String word) {
return (TileClass)tileMap.get(word);
}
}
class TileClass extends Vector
{
Random R = new Random();
PoetryEntry choose() {
PoetryEntry T = null;
int n;
for (n = 0 ; n < 10 && (T == null || T.isActive || T.isStuck) ; n++)
T = (PoetryEntry)elementAt(Math.abs(R.nextInt()) % size());
return (n < 10) ? T : null;
}
}
class PoetryEntry
{
Poetry parent;
int count = 0, w, h;
double x, y, dx = 0, dy = 0;
boolean isActive = false, isStuck = false;
int isFirst = 0, isLast = 0;
String s;
PoetryEntry(Poetry parent) { this.parent = parent; }
void move(double dx, double dy) {
if (!isActive && !isStuck) {
// MOVE THE TILE WITH A LOW PASS (SMOOTHING) FILTER
this.dx = (99*this.dx + dx)/100;
this.dy = (99*this.dy + dy)/100;
x += this.dx;
y += this.dy;
// IF TILE GOES OFF EDGE OF SCREEN, REPLACE IT AT SCREEN CENTER
double dX = x+w/2-parent.w/2;
double dY = y+h/2-parent.h/2;
if (Math.abs(dX) > parent.w/2 + w/2 || Math.abs(dY) > parent.h/2 + h/2) {
x = parent.w/2 - w/2;
y = parent.h/2 - h/2;
}
}
}
double round(double t) { return t < 0 ? Math.min(-.3,t) : Math.max(.3,t); }
int ix() { return (int) x; }
int iy() { return (int) y; }
double l() { return x; }
double r() { return x + w; }
double t() { return y; }
double b() { return y + h; }
void attract(PoetryEntry P, int n) {
double dx = P.l() - r();
double dy = P.y - y;
n = Math.min(n, 5);
double alpha = Math.min(1, n * n * n / (dx*dx + dy*dy + 1));
if (P.w < 100)
alpha *= P.w / 100.;
move ( alpha*dx/2, alpha*dy/2);
P.move(-alpha*dx/2, -alpha*dy/2);
}
}
class TrplsDrift extends PoetryDrift
{
Random R = new Random();
TrplsDrift(Poetry parent, Hashtable patterns) {
super(parent, patterns);
}
void move(String trpl) {
int sep1 = trpl.indexOf(" ");
String word1 = trpl.substring(0,sep1);
String word2 = trpl.substring(sep1+1);
int sep2 = word2.indexOf(" ");
String word3 = word2.substring(sep2+1);
word2 = word2.substring(0,sep2);
TileClass V1 = parent.getTileClass(word1);
TileClass V2 = parent.getTileClass(word2);
TileClass V3 = parent.getTileClass(word3);
if (V1 != null && V2 != null && V3 != null) {
PoetryEntry T1 = V1.choose();
PoetryEntry T2 = V2.choose();
PoetryEntry T3 = V3.choose();
if (T1 != null && T2 != null && T3 != null) {
PoetryEntry T = (PoetryEntry)patterns.get(trpl);
T1.attract(T2, T.count);
T2.attract(T3, T.count);
T3.attract(T1, T.count);
}
}
}
}
class PairsDrift extends PoetryDrift
{
Random R = new Random();
PairsDrift(Poetry parent, Hashtable patterns) {
super(parent, patterns);
}
void move(String pair) {
int sep = pair.indexOf(" ");
String word1 = pair.substring(0,sep);
String word2 = pair.substring(sep+1);
TileClass V1 = parent.getTileClass(word1);
TileClass V2 = parent.getTileClass(word2);
if (V1 != null && V2 != null) {
PoetryEntry T1 = V1.choose();
PoetryEntry T2 = V2.choose();
if (T1 != null && T2 != null) {
PoetryEntry P = (PoetryEntry)patterns.get(pair);
T1.attract(T2, P.count);
T2.attract(T1, P.count);
}
}
}
}
class WordsDrift extends PoetryDrift
{
Random R = new Random();
WordsDrift(Poetry parent, Hashtable patterns) {
super(parent, patterns);
}
void repel(PoetryEntry W, PoetryEntry V) {
if (W.l()-2 < V.r()+2 && V.l()-2 < W.r()+2 &&
W.t()-6 < V.b()+6 && V.t()-6 < W.b()+6) {
double ox = Math.min(V.r()-W.l()+ 4,W.r()-V.l()+ 4) / (W.w+V.w+4)*2;
double oy = Math.min(V.b()-W.t()+12,W.b()-V.t()+12) / (V.h+12);
double dx = 3*(W.x - V.x) * ox * ox * oy * oy;
double dy = 3*(W.y - V.y) * ox * ox * oy * oy;
if (W.w < 50) {
dx *= W.w / 50.;
dy *= W.w / 50.;
}
W.move(dx,dy);
V.move(-dx,-dy);
}
}
void snapTo(PoetryEntry W, PoetryEntry V) {
double Wx = W.r(), Wy = W.t(), Vx = V.l(), Vy = V.t();
double dx = Vx - Wx;
double dy = Vy - Wy;
double rr = dx*dx + dy*dy;
if (rr < 7) {
PoetryEntry P = (PoetryEntry)parent.pairs.get(W.s + " " + V.s);
if (P != null) {
dx *= Math.min(2,P.count);
dy *= Math.min(2,P.count);
W.move(dx/2,dy/2);
V.move(-dx/2,-dy/2);
}
}
}
void swirl(PoetryEntry W) {
double dx = (W.l() + W.r())/2 - w/2;
double dy = (W.t() + W.b())/2 - h/2;
double r = 10000 * Math.sqrt(dx * dx + dy * dy)/h + 5;
W.move(-4 * dx / r, -4 * dy / r);
int mx = parent.isM ? parent.mx : w/2;
int my = parent.isM ? parent.my : h/2;
dx = (W.l() + W.r())/2 - mx;
dy = (W.t() + W.b())/2 - my;
r = 1000 * Math.sqrt(dx * dx + dy * dy)/h;
if (parent.isM)
r /= 10;
W.move(-(dx/50 + dy/2)/r, -(dy/50 - dx/2)/r);
if (parent.isT)
W.move(10*dx/r, 10*dy/r);
}
void move(String word) {
TileClass V = parent.getTileClass(word);
PoetryEntry T1, T2;
if (V != null) {
if ((T1 = V.choose()) != null) {
swirl(T1);
for (int j = 0 ; j < parent.tiles.size() ; j++) {
T2 = (PoetryEntry)parent.tiles.elementAt(j);
repel(T1, T2);
if (! word.equals(T2.s))
snapTo(T1, T2);
else if (T1 != T2) {
double dx = T1.x-T2.x, dy = T1.y-T2.y, rr = dx*dx + dy*dy;
dx = 200 * dx / rr;
dy = 200 * dy / rr;
T1.move(dx, dy);
}
}
}
}
}
}
abstract class PoetryDrift extends Thread
{
Hashtable patterns;
Poetry parent;
int w, h;
PoetryDrift(Poetry parent, Hashtable patterns) {
this.parent = parent;
this.patterns = patterns;
}
abstract void move(String pattern);
void moveBegin() { }
void moveEnd() { }
public void run() {
try {
while (true) {
w = parent.bounds().width;
h = parent.bounds().height;
moveBegin();
for (Enumeration e = patterns.keys() ; e.hasMoreElements() ; )
move((String)e.nextElement());
moveEnd();
sleep(30);
}
}
catch(InterruptedException e){};
}
}