From a5b4d855e51f71165817122f8bc7bc9b11524d4e Mon Sep 17 00:00:00 2001 From: Stephen Blackheath Date: Fri, 13 Feb 2015 12:57:41 +1300 Subject: [PATCH] Zombicus example work in progress. --- book/zombicus/java/Animate.java | 108 ++++++++++++++++++++ book/zombicus/java/BitableHomoSapien.java | 50 +++++++++ book/zombicus/java/Characters.java | 119 ---------------------- book/zombicus/java/HomoSapien.java | 80 ++++++++------- book/zombicus/java/HomoZombicus.java | 112 +++++++++++++------- book/zombicus/java/Vector.java | 3 + book/zombicus/java/World.java | 9 +- book/zombicus/java/bite.java | 60 +++++++++++ book/zombicus/java/build.xml | 13 ++- book/zombicus/java/characters.java | 51 ++++++++++ 10 files changed, 406 insertions(+), 199 deletions(-) create mode 100644 book/zombicus/java/Animate.java create mode 100644 book/zombicus/java/BitableHomoSapien.java delete mode 100644 book/zombicus/java/Characters.java create mode 100644 book/zombicus/java/bite.java create mode 100644 book/zombicus/java/characters.java diff --git a/book/zombicus/java/Animate.java b/book/zombicus/java/Animate.java new file mode 100644 index 00000000..18bf463d --- /dev/null +++ b/book/zombicus/java/Animate.java @@ -0,0 +1,108 @@ +import javax.imageio.*; +import javax.swing.*; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Toolkit; +import java.awt.image.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import sodium.*; + +public class Animate extends JPanel { + private final double t0 = 0.0; + private final Dimension windowSize = new Dimension(600, 400); + private final BufferedImage sapienImgL; + private final BufferedImage sapienImgR; + private final BufferedImage zombicusImgL; + private final BufferedImage zombicusImgR; + private final BufferedImage coneImg; + private Cell> characters; + private CellSink clock; + private StreamSink sTick; + + public interface Animation { + public Cell> create( + double t0, Cell clock, Stream sTick, + Dimension screenSize); + } + + public Animate(Animation animation) + throws MalformedURLException, IOException + { + URL rootURL = new URL("file:."); + sapienImgL = ImageIO.read(new URL(rootURL, "../images/homo-sapien-left.png")); + sapienImgR = ImageIO.read(new URL(rootURL, "../images/homo-sapien-right.png")); + zombicusImgL = ImageIO.read(new URL(rootURL, "../images/homo-zombicus-left.png")); + zombicusImgR = ImageIO.read(new URL(rootURL, "../images/homo-zombicus-right.png")); + coneImg = ImageIO.read(new URL(rootURL, "../images/roadius-conium.png")); + Transaction.runVoid(() -> { + clock = new CellSink(t0); + sTick = new StreamSink(); + this.characters = animation.create(t0, clock, sTick, windowSize); + }); + } + + public void paintComponent(Graphics g) { + super.paintComponent(g); + Transaction.runVoid(() -> { + List chars = new ArrayList(characters.sample()); + chars.sort((a, b) -> a.pos.y == b.pos.y ? 0 : + a.pos.y < b.pos.y ? -1 : 1); + for (Character c : chars) { + if (c.type == CharacterType.SAPIEN) + if (c.velocity.dx < 0) + g.drawImage(sapienImgL, c.pos.x-30, c.pos.y-73, null); + else + g.drawImage(sapienImgR, c.pos.x-23, c.pos.y-73, null); + else + if (c.velocity.dx < 0) + g.drawImage(zombicusImgL, c.pos.x-39, c.pos.y-73, null); + else + g.drawImage(zombicusImgR, c.pos.x-23, c.pos.y-73, null); + } + }); + Toolkit.getDefaultToolkit().sync(); + } + + public Dimension getPreferredSize() { + return windowSize; + } + + public static void animate(String title, Animation animation) + { + try { + JFrame frame = new JFrame(title); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + Animate view = new Animate(animation); + frame.setContentPane(view); + frame.pack(); + frame.setVisible(true); + long t0 = System.currentTimeMillis(); + long tLast = t0; + while (true) { + long t = System.currentTimeMillis(); + long tIdeal = tLast + 20; + long toWait = tIdeal - t; + if (toWait > 0) + try { Thread.sleep(toWait); } catch (InterruptedException e) {} + view.clock.send((double)(tIdeal - t0) * 0.001); + view.sTick.send(Unit.UNIT); + view.repaint(0); + tLast = tIdeal; + } + } + catch (MalformedURLException e) { + e.printStackTrace(); + } + catch (IOException e) { + e.printStackTrace(); + } + } +} + diff --git a/book/zombicus/java/BitableHomoSapien.java b/book/zombicus/java/BitableHomoSapien.java new file mode 100644 index 00000000..fb3db34d --- /dev/null +++ b/book/zombicus/java/BitableHomoSapien.java @@ -0,0 +1,50 @@ +import java.awt.Point; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import sodium.*; + +public class BitableHomoSapien { + private static class All { + All(Character character, double t) { + this.character = character; + this.t = t; + } + Character character; + double t; + } + public BitableHomoSapien( + World world, + int self, + double tInit, + Point posInit, + Cell clock, + Stream sTick, + Stream sBite, + Cell> others) + { + HomoSapien h = new HomoSapien(world, self, tInit, posInit, clock, sTick); + Stream sBiteMe = sBite.filter(id -> id == self); + Cell all = Cell.lift( + (ch, t) -> new All(ch, t), + h.character, clock); + Stream sBecome = sBiteMe.snapshot( + all, + (id, a) -> new HomoZombicus( + world, self, + a.t, a.character.pos, + clock, + sTick, others + ) + ); + this.character = Cell.switchC( + sBecome.map(z -> z.character).hold(h.character) + ); + this.sBite = Cell.switchS( + sBecome.map(z -> z.sBite).hold(new Stream()) + ); + } + public final Cell character; + public final Stream sBite; +} + diff --git a/book/zombicus/java/Characters.java b/book/zombicus/java/Characters.java deleted file mode 100644 index 1d8ec5ef..00000000 --- a/book/zombicus/java/Characters.java +++ /dev/null @@ -1,119 +0,0 @@ -import javax.imageio.*; -import javax.swing.*; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Point; -import java.awt.Toolkit; -import java.awt.image.*; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import sodium.*; - -public class Characters extends JPanel { - private final BufferedImage sapienImgL; - private final BufferedImage sapienImgR; - private final BufferedImage zombicusImgL; - private final BufferedImage zombicusImgR; - private final BufferedImage coneImg; - private Cell> characters; - private CellSink clock; - private StreamSink sTick; - - public Characters() - throws MalformedURLException, IOException - { - URL rootURL = new URL("file:."); - sapienImgL = ImageIO.read(new URL(rootURL, "../images/homo-sapien-left.png")); - sapienImgR = ImageIO.read(new URL(rootURL, "../images/homo-sapien-right.png")); - zombicusImgL = ImageIO.read(new URL(rootURL, "../images/homo-zombicus-left.png")); - zombicusImgR = ImageIO.read(new URL(rootURL, "../images/homo-zombicus-right.png")); - coneImg = ImageIO.read(new URL(rootURL, "../images/roadius-conium.png")); - World world = new World(new Point(600, 400)); - Transaction.runVoid(() -> { - double t0 = 0.0; - clock = new CellSink(t0); - sTick = new StreamSink(); - List> chars = new ArrayList<>(); - CellLoop> others = new CellLoop<>(); - int id = 0; - for (int x = 100; x < 600; x += 100) - for (int y = 150; y < 400; y += 150) { - Point pos0 = new Point(x, y); - if (id != 3) - chars.add(HomoSapien.create(world, id, t0, pos0, clock, sTick)); - else - chars.add(HomoZombicus.create(world, id, t0, pos0, clock, sTick, others)); - id++; - } - Cell> others_ = new Cell<>(new ArrayList()); - for (Cell c : chars) { - others_ = Cell.lift( - (cc, l0) -> { - List l = new ArrayList(l0); - l.add(cc); - return l; - }, - c, others_); - } - this.characters = others_; - List emptyOthers = new ArrayList<>(); - Cell> othersFixed = others_.updates().hold(emptyOthers); - others.loop(othersFixed); - }); - } - - public void paintComponent(Graphics g) { - super.paintComponent(g); - Transaction.runVoid(() -> { - List chars = characters.sample(); - for (Character c : chars) { - if (c.type == CharacterType.SAPIEN) - if (c.velocity.dx < 0) - g.drawImage(sapienImgL, c.pos.x-30, c.pos.y-73, null); - else - g.drawImage(sapienImgR, c.pos.x-23, c.pos.y-73, null); - else - if (c.velocity.dx < 0) - g.drawImage(zombicusImgL, c.pos.x-39, c.pos.y-73, null); - else - g.drawImage(zombicusImgR, c.pos.x-23, c.pos.y-73, null); - } - }); - Toolkit.getDefaultToolkit().sync(); - } - - public Dimension getPreferredSize() { - return new Dimension(600, 400); - } - - public static void main(String[] args) - throws MalformedURLException, IOException - { - JFrame frame = new JFrame("Zombicus characters"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - Characters view = new Characters(); - frame.setContentPane(view); - frame.pack(); - frame.setVisible(true); - long t0 = System.currentTimeMillis(); - long tLast = t0; - while (true) { - long t = System.currentTimeMillis(); - long tIdeal = tLast + 20; - long toWait = tIdeal - t; - if (toWait > 0) - try { Thread.sleep(toWait); } catch (InterruptedException e) {} - System.out.println(tIdeal); - view.clock.send((double)(tIdeal - t0) * 0.001); - view.sTick.send(Unit.UNIT); - view.repaint(0); - tLast = tIdeal; - } - } -} - diff --git a/book/zombicus/java/HomoSapien.java b/book/zombicus/java/HomoSapien.java index 8c651613..ab056cdc 100644 --- a/book/zombicus/java/HomoSapien.java +++ b/book/zombicus/java/HomoSapien.java @@ -1,4 +1,5 @@ import java.awt.Point; +import java.util.List; import java.util.Optional; import java.util.Random; import sodium.*; @@ -7,29 +8,16 @@ public class HomoSapien { public static final double speed = 80.0; public static final double step = 0.02; - static class State { - State(World world, Random rng, double t0, Point orig) { - this.t0 = t0; - this.orig = orig; - this.period = rng.nextDouble() * 1 + 0.5; - for (int i = 0; i < 10; i++) { - double angle = rng.nextDouble() * Math.PI * 2; - velocity = new Vector(Math.sin(angle), Math.cos(angle)) - .mult(speed); - if (!world.hitsObstacle(positionAt(t0 + step*2))) - break; - } + private static class All { + All(State state, double t) { + this.state = state; + this.t = t; } - double t0; - Point orig; - double period; - Vector velocity; - Point positionAt(double t) { - return velocity.mult(t - t0).add(orig); - } - } + final State state; + final double t; + }; - public static Cell create( + public HomoSapien( World world, int self, double tInit, @@ -39,33 +27,51 @@ public static Cell create( { Random rng = new Random(); CellLoop state = new CellLoop<>(); - Cell> stateAndClock = Cell.lift( - (st, clk) -> new Tuple2(st, clk), + Cell all = Cell.lift( + (st, clk) -> new All(st, clk), state, clock); Stream sChange = Stream.filterOptional( - sTick.snapshot(stateAndClock, - (u, stclk) -> { - State st = stclk.a; - double t = stclk.b; - if (world.hitsObstacle(st.positionAt(t + step)) - || t - st.t0 >= st.period) + sTick.snapshot(all, + (u, a) -> { + if (world.hitsObstacle(a.state.positionAt(a.t + step)) + || a.t - a.state.t0 >= a.state.period) return Optional.of(Unit.UNIT); else return Optional.empty(); })); state.loop( - sChange.snapshot(stateAndClock, (u, stclk) -> { - State st = stclk.a; - double t = stclk.b; - return new State(world, rng, t, st.positionAt(t)); + sChange.snapshot(all, (u, a) -> { + return new State(world, rng, a.t, a.state.positionAt(a.t)); }).hold(new State(world, rng, tInit, posInit)) ); - return stateAndClock.map(stclk -> { - State st = stclk.a; - double t = stclk.b; + character = all.map(a -> { return new Character(self, CharacterType.SAPIEN, - st.positionAt(t), st.velocity); + a.state.positionAt(a.t), a.state.velocity); }); } + + public final Cell character; + + private static class State { + State(World world, Random rng, double t0, Point orig) { + this.t0 = t0; + this.orig = orig; + this.period = rng.nextDouble() * 1 + 0.5; + for (int i = 0; i < 10; i++) { + double angle = rng.nextDouble() * Math.PI * 2; + velocity = new Vector(Math.sin(angle), Math.cos(angle)) + .mult(speed); + if (!world.hitsObstacle(positionAt(t0 + step*2))) + break; + } + } + double t0; + Point orig; + double period; + Vector velocity; + Point positionAt(double t) { + return velocity.mult(t - t0).add(orig); + } + } } diff --git a/book/zombicus/java/HomoZombicus.java b/book/zombicus/java/HomoZombicus.java index 01f08ba4..89c3bce2 100644 --- a/book/zombicus/java/HomoZombicus.java +++ b/book/zombicus/java/HomoZombicus.java @@ -1,32 +1,93 @@ import java.awt.Point; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import sodium.*; public class HomoZombicus { - public static final double velocity = 80.0; + public HomoZombicus( + World world, + int self, + double tInit, + Point posInit, + Cell clock, + Stream sTick, + Cell> others) + { + CellLoop state = new CellLoop<>(); + List initOthers = new ArrayList(0); + Cell all = Cell.lift((st, t, o) -> new All(st, t, o), + state, clock, others); + Stream sChange = Stream.filterOptional( + sTick.snapshot(all, + (u, a) -> + a.t - a.state.t0 >= 0.2 + ? Optional.of(new State(a.t, a.state.positionAt(a.t), + self, a.others)) + : Optional.empty() + ) + ); + state.loop( + sChange.hold(new State(tInit, posInit, self, initOthers)) + ); + character = all.map(a -> new Character(self, + CharacterType.ZOMBICUS, + a.state.positionAt(a.t), a.state.velocity)); + sBite = Stream.filterOptional( + sTick.snapshot(all, + (u, a) -> { + Optional oVictim = a.state.nearest(self, + a.others, false); + if (oVictim.isPresent()) { + Character victim = oVictim.get(); + Point myPos = a.state.positionAt(a.t); + if (Vector.distance(victim.pos, myPos) < 10) + return Optional.of(victim.id); + } + return Optional.empty(); + } + )); + } + public final Cell character; + public final Stream sBite; + + public static final double speed = 50.0; + private static class State { + final double t0; + final Point orig; + final Vector velocity; - public static class State { State(double t0, Point orig, int self, List others) { this.t0 = t0; this.orig = orig; double bestDist = 0.0; - Vector bestVec = null; - for (Character o : others) - if (o.id != self) { - Vector vec = Vector.subtract(o.pos, orig); - double dist = vec.magnitude(); - if (bestVec == null || dist < bestDist) { + Optional oOther = nearest(self, others, true); + if (oOther.isPresent()) { + Character other = oOther.get(); + this.velocity = Vector.subtract(other.pos, orig) + .normalize().mult( + other.type == CharacterType.SAPIEN + ? speed : -speed + ); + } + else + this.velocity = new Vector(0,0); + } + + Optional nearest(int self, List others, boolean allTypes) { + double bestDist = 0.0; + Optional best = Optional.empty(); + for (Character ch : others) + if (ch.id != self && (allTypes || ch.type == CharacterType.SAPIEN)) { + double dist = Vector.distance(ch.pos, orig); + if (!best.isPresent() || dist < bestDist) { bestDist = dist; - bestVec = vec; + best = Optional.of(ch); } } - this.velocity = bestVec == null ? new Vector(0,0) - : bestVec.normalize(); + return best; } - double t0; - Point orig; - Vector velocity; + Point positionAt(double t) { return velocity.mult(t - t0).add(orig); } @@ -42,28 +103,5 @@ public static class All { double t; List others; }; - - public static Cell create( - World world, - int self, - double tInit, - Point posInit, - Cell clock, - Stream sTick, - Cell> others) - { - CellLoop state = new CellLoop<>(); - List initOthers = new ArrayList(0); - Cell all = Cell.lift((st, t, o) -> new All(st, t, o), - state, clock, others); - state.loop( - sTick.snapshot(all, - (u, a) -> new State(a.t, a.state.positionAt(a.t), self, a.others) - ).hold(new State(tInit, posInit, self, initOthers)) - ); - return all.map(a -> new Character(self, CharacterType.ZOMBICUS, - a.state.positionAt(a.t), - a.state.velocity)); - } } diff --git a/book/zombicus/java/Vector.java b/book/zombicus/java/Vector.java index b7cb8e51..c6f0dbbb 100644 --- a/book/zombicus/java/Vector.java +++ b/book/zombicus/java/Vector.java @@ -23,4 +23,7 @@ public Vector normalize() { public static Vector subtract(Point a, Point b) { return new Vector(a.x - b.x, a.y - b.y); } + public static double distance(Point a, Point b) { + return Vector.subtract(a, b).magnitude(); + } } diff --git a/book/zombicus/java/World.java b/book/zombicus/java/World.java index c4897d28..ea5d2844 100644 --- a/book/zombicus/java/World.java +++ b/book/zombicus/java/World.java @@ -1,16 +1,17 @@ +import java.awt.Dimension; import java.awt.Point; public class World { - public World(Point windowSize) { + public World(Dimension windowSize) { this.windowSize = windowSize; } - final Point windowSize; + final Dimension windowSize; public boolean hitsObstacle(Point pt) { - return pt.x < 31 || pt.x >= (windowSize.x - 31) || - pt.y < 73 || pt.y >= (windowSize.y - 23); + return pt.x < 31 || pt.x >= (windowSize.width - 31) || + pt.y < 73 || pt.y >= (windowSize.height - 23); } } diff --git a/book/zombicus/java/bite.java b/book/zombicus/java/bite.java new file mode 100644 index 00000000..3cb119c8 --- /dev/null +++ b/book/zombicus/java/bite.java @@ -0,0 +1,60 @@ +import java.awt.Dimension; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; +import sodium.*; + +public class bite { + public static void main(String[] args) + { + Animate.animate( + "Zombicus bite", + +(double t0, Cell clock, Stream sTick, + Dimension windowSize) -> { + World world = new World(windowSize); + List> chars = new ArrayList<>(); + List> sBites = new ArrayList<>(); + CellLoop> others = new CellLoop<>(); + StreamLoop sBite = new StreamLoop<>(); + int id = 0; + for (int x = 100; x < 600; x += 100) + for (int y = 150; y < 400; y += 150) { + Point pos0 = new Point(x, y); + if (id != 3) { + BitableHomoSapien h = new BitableHomoSapien(world, id, + t0, pos0, clock, sTick, + sBite, others); + chars.add(h.character); + sBites.add(h.sBite); + } + else { + HomoZombicus z = new HomoZombicus(world, id, t0, pos0, + clock, sTick, others); + chars.add(z.character); + sBites.add(z.sBite); + } + id++; + } + Cell> characters = new Cell<>(new ArrayList()); + for (Cell c : chars) { + characters = Cell.lift( + (cc, l0) -> { + List l = new ArrayList(l0); + l.add(cc); + return l; + }, + c, characters); + } + Stream sBite_ = new Stream(); + for (Stream sb : sBites) + sBite_ = sBite.merge(sb); + sBite.loop(sBite_); + others.loop(characters.updates().hold(new ArrayList<>())); + return characters; +} + + ); + } +} + diff --git a/book/zombicus/java/build.xml b/book/zombicus/java/build.xml index d92e68e0..6ad3bbaa 100644 --- a/book/zombicus/java/build.xml +++ b/book/zombicus/java/build.xml @@ -14,8 +14,17 @@ - - + + + + + + + + + + + diff --git a/book/zombicus/java/characters.java b/book/zombicus/java/characters.java new file mode 100644 index 00000000..a019a83c --- /dev/null +++ b/book/zombicus/java/characters.java @@ -0,0 +1,51 @@ +import java.awt.Dimension; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; +import sodium.*; + +public class characters { + public static void main(String[] args) + { + Animate.animate( + "Zombicus characters", + +(double t0, Cell clock, Stream sTick, + Dimension windowSize) -> { + World world = new World(windowSize); + List> chars = new ArrayList<>(); + CellLoop> others = new CellLoop<>(); + int id = 0; + for (int x = 100; x < 600; x += 100) + for (int y = 150; y < 400; y += 150) { + Point pos0 = new Point(x, y); + if (id != 3 && id != 6 && id != 7) { + HomoSapien h = new HomoSapien(world, id, t0, pos0, + clock, sTick); + chars.add(h.character); + } + else { + HomoZombicus z = new HomoZombicus(world, id, t0, pos0, + clock, sTick, others); + chars.add(z.character); + } + id++; + } + Cell> characters = new Cell<>(new ArrayList()); + for (Cell c : chars) { + characters = Cell.lift( + (cc, l0) -> { + List l = new ArrayList(l0); + l.add(cc); + return l; + }, + c, characters); + } + others.loop(characters.updates().hold(new ArrayList<>())); + return characters; +} + + ); + } +} +