markrbower (11) [Avatar] Offline
#1
Am enjoying the book, and totally get the idea that the authors want users to learn a new programming paradigm. Just wish there more examples to try things out. Am trying to get Exercise 7.5.3 (have humans move towards mouse click locations) to run, but am hitting loads of roadblocks. Conceptually, it seems simple: Use a MouseAdaptor to acquire the mouse click locations (ev.getPoint) and then send that to the humans to calculate the angle from their current location to the location of the click. Making this work with FRP is a horse of another color. Best I can tell, I’m supposed to make a cell to hold the location of the click, a stream to take a snapshot of the Cell and convey the point, and have some way for humans to download the Point from that stream, and have all of this running in some kind of loop (like bites) so that it updates the scene. I made a copy (“bird.java”) of an existing main program (“dynamic.java”) and then have modified Animate.java (the MouseAdaptor provided by the authors), HomoSapiens.java (to get the stream into humans), and BitableHomoSapiens.java (to try to download the click location and change the trajectory). I would post the code, but problems abound: the only thing that compiles in the MouseAdaptor is a StreamLoop<Set<Point>> and it is not clear how to download the Point value from a StreamLoop<Set<Point>> object.

This seems like it should be a pretty simple (and fun!) thing to do, but that might be causing those who already know Sodium and FRP to forget how difficult it is to learn a new paradigm. Working examples online (I’ve looked, but can’t find any) would be helpful.
Stephen Blackheath (114) [Avatar] Offline
#2
Hi!

I'm a bit confused by what you wrote, but if you post what you've got already I'll take a look. I'll assume you want clues rather than a complete answer (though I haven't actually done this exercise).



Steve
markrbower (11) [Avatar] Offline
#3
Thank you for responding. Here is the relevant code, which is cut from three different programs. My thought was to copy the pattern of sBite, so I’m sure that added a lot of unnecessary code. My guess is that someone who knows how to do this (you) will be able to change just 3 or 4 lines; conceptually, it just seems like it should be easy.

bird.java (copy of dynamic.java)

static class State {
State() {

this.sTweets = new HashMap<>();
}
State(int nextID, Map<Integer, Cell<Character>> chars,
Map<Integer, Stream<Integer>> sBites,
Map<Integer, Stream<Integer>> sDestroys,
Map<Point, Stream<Point>> sTweets) {

this.sTweets = sTweets;
}

final Map<Point, Stream<Point>> sTweets;


State add(Cell<Character> chr, Stream<Integer> sBite,
Stream<Integer> sDestroy) {

return new State(nextID+1, chars, sBites, sDestroys, sTweets);
}



Animate.java:

private StreamLoop<Set<Point>> sTweet;

public Animate(Animation animation, List<Polygon> obstacles, StreamLoop<Set<Point>> sT)

addMouseListener(new MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent ev) {
cTweet = new Cell<Point>(ev.getPoint());
sTweet.snapshot(cTweet);
}
});



BitableHomoSapiens.java

public class BitableHomoSapiens {
public BitableHomoSapiens(
World world,
int self,
Point posInit,
Cell<Double> time,
Stream<Unit> sTick,
Stream<Set<Integer>> sBite,
Cell<List<Character>> scene,
StreamLoop<Set<Point>> sT)
{
final StreamLoop<Set<Point>> sTweet = sT;
...
Should I add something like “this.Bite…” at the end of the constructor?

As you can see, I have no idea how to proceed. None of the widgets examples or Chapter 2 patterns had anything like this in them. Ideally, I would just add a Stream to the MouseAdapter and read it in BitableHomoSapiens to extract the Point values, but it is not clear how to "connect the plumbing."

Any advice or clues would be appreciated.
Mark



Stephen Blackheath (114) [Avatar] Offline
#4
Hi Mark,

How about we declare it like this in Animate.java:

CellSink<Optional<Point>> tweet = new Cellsink<>(Optional.empty());

It would have a value when the mouse is down and no value when it's released:

public void mousePressed(java.awt.event.MouseEvent ev) {
tweet.send(Optional.of(ev.getPoint()));
}

public void mouseReleased(java.awt.event.MouseEvent ev) {
tweet.send(Optional.empty());
}

When you pass it through the code, use the type Cell<Optional<Point>> rather than CellSink. Try that and let me know if you're still stuck somewhere.


Steve
markrbower (11) [Avatar] Offline
#5
That worked! I made the changes you suggested to Animate, passed the variable through animate.create(), then through CreateCharacters(), then through BitableHomoSapiens, and then added the following to the HomoSapiens.Trajectory constructor:
Optional<Point> tp = tweet.sample();
if ( tp.isPresent() ) {
	Point pt = tp.get();
	System.out.println( "Last click: " + pt.getX() + " " + pt.getY() );
}

I will try to make it "pretty" and then post some sample code for others.

It all still seems a bit like voodoo with all of the mysterious type changes ("It's a CellSink, but pass it as a Cell..."), but perhaps it will make more sense as I read on.

Thanks, again!
Mark




Stephen Blackheath (114) [Avatar] Offline
#6
Hi Mark,

Great!

That's all legitimate FRP, but it would be more 'normal' and might help your understanding to pass the sampled value (tp) to the Trajectory constructor rather than 'tweet'. That way it's really clear from reading the code that tweet.sample() is being called in the context of a particular map() or snapshot() invocation.

CellSink exposes the ability to write into a Cell. This can only be used on the interface from the 'real world' to FRP land. Once inside FRP land, 'tweet' must be strictly treated as a Cell. So, you downcast it to Cell and then it's safe from anyone breaking the rules of FRP.


Steve
Kostadin Markov (17) [Avatar] Offline
#7
Hello.
Here's some implementation that I think should reach the exercise goal:
Mouse location is tracked by the mouse cell and clicks(tweets) are tracked by the sClick stream. In case of dragging, I refresh the mouse location on releasing(not sure if needed).
In Animate.java I add these:
//other code omitted

private CellSink<Point> mouse;
private StreamSink<Unit> sClick;
public interface Animation
    {
        public Cell<List<Character>> create(Cell<Double> time, Stream<Unit> sTick, Dimension screenSize,
                                            Cell<Point> mouse, Stream<Unit> sClick);
    }

//other code omitted

scene = animation.create(time, sTick, windowSize, mouse, sClick);

//other code omitted

addMouseListener(new MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent ev) {
                System.out.println("mouse pressed:" + ev.getX() + "," + ev.getY());
                sClick.send(Unit.UNIT);
            }
            public void mouseReleased(java.awt.event.MouseEvent ev) {
                System.out.println("mouse released:" + ev.getX() + "," + ev.getY());
                mouse.send(new Point(ev.getX(), ev.getY()));
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseDragged(MouseEvent e) {

            }

            public void mouseMoved(MouseEvent ev) {
                System.out.println("mouse moved:" + ev.getX() + ", " + ev.getY());
                mouse.send(new Point(ev.getX(), ev.getY()));
            }
        });

//other code omitted


I have a 'nearest human'-monitor. This is the same logic that the zombies use, but spanning across all of the humans. I called it butterflyhint.java:
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import nz.sodium.*;

public class ButterflyHint
{
    public ButterflyHint(Stream<Unit> sTick, Cell<Point> mouse, Stream<Unit> sClick, Cell<List<Character>> scene)
    {
        class State {
            State(Point orig) {
                this.orig = orig;
            }

            Optional<Character> nearest(List<Character> scene) {
                double bestDist = 0.0;
                Optional<Character> best = Optional.empty();
                for (Character ch : scene)
                {
                    double dist = Vector.distance(ch.pos, orig);
                    if (!best.isPresent() || dist < bestDist) {
                        bestDist = dist;
                        best = Optional.of(ch);
                    }
                }
                return best;
            }

            Optional<Character> nearestSapiens(List<Character> scene) {
                List<Character> sapiens = new ArrayList<>();
                for (Character ch : scene)
                    if (ch.type == CharacterType.SAPIENS)
                        sapiens.add(ch);
                return nearest(sapiens);
            }

            final Point orig;
        }

        Cell<State> state = sTick.snapshot(mouse, (u, m) -> new State(m)).hold(new State(mouse.sample()));
        humanInNeed = Stream.filterOptional(sClick.snapshot(state, scene, (u, st, sc) -> st.nearestSapiens(sc)));
    }

    public final Stream<Character> humanInNeed;
}



In butterfly.java(dynamic/bird.java) I utilize the mouse inputs(passed over from Animate.animate() to CreateCharacters()) to monitor the scene and sent hint to the nearest human through a stream of points:
//other code omitted

//monitor nearest humans
            ButterflyHint bh = new ButterflyHint(sTick, mouse, sClick, scene);
//ADD
            Stream<Lambda1<State, State>> sAdd =
                    //every six seconds
                    periodicTimer(time, sTick, 6.0)
                            .map(u ->
                                    st -> {
                                        StreamLoop<Point> sSentHint = new StreamLoop<>();
                                        //add a bitable human
                                        BitableHomoSapiens h = new BitableHomoSapiens(
                                                world, st.nextID, center, time, sTick,
                                                sBite, scene, sSentHint);
                                        //sent hint to the human, if they're the neareast
                                        sSentHint.loop(Stream.filterOptional(bh.humanInNeed.snapshot(h.character, mouse, (hin, ch, m) -> {
                                            if(hin.id == ch.id)
                                                return Optional.<Point>of(m);
                                            return Optional.<Point>empty();
                                        })));
                                        //might turn into zombie, thus needs destroy logic
                                        return st.add(h.character, h.sBite,
                                                fallDownHole(st.nextID, sTick, h.character, world));
                                    });

//other code omitted


The BitableHomoSapiens should conduct the hint input:
//other code omitted
,
            Stream<Point> sHint)
    {
        HomoSapiens h = new HomoSapiens(world, self, posInit, time, sTick, sHint);
//other code omitted


Finally, in HomoSapiens.java make humans respect hints:
import java.awt.Point;
import java.util.List;
import java.util.Optional;
import java.util.Random;

import nz.sodium.*;

public class HomoSapiens
{
    public HomoSapiens(World world, int self, Point posInt, Cell<Double> time, Stream<Unit> sTick)
    {
        this(world, self, posInt, time, sTick, new Stream<Point>());
    }

    public HomoSapiens(World world, int self, Point posInt, Cell<Double> time, Stream<Unit> sTick, Stream<Point> sHint)
    {
        final double speed = 40.0;
        final double step = 0.02;

        class Trajectory {
            Trajectory(Random rnd, double t0, Point orig, Optional<Point> hintPoint) {
                this.t0 = t0;
                this.orig = orig;
                //no matter what, reset distraction interval
                this.period = rnd.nextDouble() + 1.5;
                for(int i = 0 ; i < 10 ; i++) {
                    if(hintPoint.isPresent()) {
                        //TODO: use dedicated output cell/stream to depict
                        //a human that had received a hint to change their velocity
                        velocity = Vector.substract(hintPoint.get(), orig).normalize().mult(speed);
                    }
                    else {
                        double angle = rnd.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);
            }
        }

        Random rnd = new Random();

        CellLoop<Trajectory> traj = new CellLoop<>();

        Stream<Optional<Point>> sHintSent = sHint.map(p -> Optional.<Point>of(p));
        Stream<Unit> sChange = Stream.filterOptional(
                sTick.snapshot(traj,
                (u, traj_) -> {
                    double t = time.sample();
                    return world.hitsObstacle(traj_.positionAt(t + step)) ||
                            t - traj_.t0 >= traj_.period
                            ? Optional.of(Unit.UNIT)
                            : Optional.<Unit>empty();
                }));
        Stream<Optional<Point>> sChanges = sChange.map(u -> Optional.<Point>empty());

        Stream<Optional<Point>> sTrajectory = sHintSent.orElse(sChanges);

        traj.loop(
            sTrajectory.snapshot(traj, (op, traj_) ->
                new Trajectory(rnd, time.sample(), traj_.positionAt(time.sample()), op))
            .hold(new Trajectory(rnd, time.sample(), posInt, Optional.<Point>empty()))
        );

        character = traj.lift(time, (traj_, t) ->
            new Character(self, CharacterType.SAPIENS, traj_.positionAt(t), traj_.velocity)
        );
    }

    public final Cell<Character> character;
}




I was wondering how could I alternate that behavior utilizing the switch-primitive?! Will add my thoughts on that later smilie
Edit:
Had I want to use switch on the character-cell, I'd have to deal with resetting the trajectory. In fact, as it is written now, the character is a lift on trajectory, so the switch should be within trajectory itself. So maybe I need two kinds of trajectories, behaving differently.
I just need to practice switch, which is the topic of this chapter, right!?

And one other thought - had anyone enhanced the output - I have some ideas, but not that 'tasty' as reasoning on FRP-logic?!
Stephen Blackheath (114) [Avatar] Offline
#8
Hi Kostadin,

Beautiful!

One way to use switch in this code would be first to have normal code based on sChange that ignores the tweet scenario:

Stream<Trajectory> sNormal = sChange.snapshot(traj, (u, traj_) ->
        new Trajectory(rng, time.sample(), traj_.positionAt(time.sample()),
        Optional.<Point>empty())
    );


Then have the mouse click (sHint) "generate some code" to handle the tweet situation. In this example, it does something else for 2 seconds.

Stream<Tuple2<Cell<Trajectory>, Stream<Unit>>> sTweet =
    sHint.snapshot(traj, (pt, traj_) -> {
        // time out after 2 seconds
        Double timeout = time.sample() + 2;
        Stream<Unit> sTimeout = Stream.filterOptional(
                sTick.snapshot(time, (u, t) ->
                      t >= timeout ? Optional.of(Unit.UNIT)
                                   : Optional.<Unit>empty())
            ).once();
        // This is simple but you can make the behaviour as complex as you like here
        Cell<Trajectory> tweetTraj = new Cell(
            new Trajectory(rng, time.sample(), traj_.positionAt(time.sample()),
                Optional.of(pt)));
        return new Tuple2<Cell<Trajectory>, Stream<Double>>(tweetTraj, sTimeout);
    });
Stream<Cell<Trajectory>> sStartTweet = sTweet.map(t -> t.a);
Stream<Unit> sEndTweet = Cell.switchS(sTweet.map(t -> t.b)).hold(new Stream<Unit>()));
Cell<Trajectory> normal =
    // Capture traj at end of tweet so there's no jump from tweet to normal behaviour
    sEndTweet.snapshot(traj)
        .or_else(sNormal)
        .hold(new Trajectory(rng, time.sample(), posInit))
traj.loop(
    Cell.switchC(
        sStartTweet
            .or_else(sEndTweet.snapshot(normal))
            .hold(normal))
);



Steve
Kostadin Markov (17) [Avatar] Offline
#9
Great! This adds more streams and cells to interact with(which should give more options to the developer(s) in the long run, I guess). Apart form demonstrating the two switches, it has once() and uses tuples. I havn't tried it out, but for those that might go for it, there's a mistyped ending stream, missed the optional empty point in the last constructor of trajectory and misspelled orElse() in the java's Sodium.
As for the complexity of sTweet definition, it'd be reasonable to follow up until the actual tweet location(like real-time strategy games do).

Best regards,
Kostadin