Reactive
1 What is This Assignment’s Purpose?
We want you to get practice with writing reactive programs. We expose you to a
relatively simple reactive programming interface, but using a style (based on
functional programming) that is quite similar to that advocated in modern
programming frameworks like React—
We also want you to have some fun!
2 Theme Song
Among Us Drip Theme Song Original (Among Us Trap Remix / Amogus Meme Music) by leonz
3 Starter
There is no starter file! Use CPO in all its barren but fertile glory.
4 Assignment
In this course, all the other assignments have well-defined end-points. This one doesn’t.
You have seen the Pyret reactive programming framework (using reactors) in class. Below we provide another: Alternative: Sprites. You can use either one for your program.
Your task is to write a program that you find interesting. You can take inspiration from anything: video games, physics simulations, modeling biological phenomena, social networks, cruise controllers, traffic simulations, psychology experiments, 2048, Wordle, Wordle bots, or whatever else strikes your fancy.
In pursuit of (usually visual) output, though, you shouldn’t sacrifice code quality. We will expect to see that you have written quality code following the guidelines established in this course, which includes testing your functions as well as your reactors! (That should include testing the reactor state after events like keypresses.)
Especially when writing games, it’s easy to get caught up in piling on complexity. No matter what causes this, remember to step back and refactor your code to be clean. Write the kind of code that you would want to review! If you need motivation, keep in mind that we won’t just be awed by the coolness of your output: the harder you make it for us to review your code, the poorer your grade will be.
Have fun. And tell us what’s neat about it.
5 Alternative: Sprites
This part is entirely experimental. There is no documentation beyond what we provide below, though you are welcome to read the source.
In reactors, you have a single value that records the entire state of the program. Every handler function consumes it and produces a possibly updated version of it.
While in principle this is sufficient, in practice, for some programs, it can be a nuisance. If the reactive program has lots of little entities that each have their own local state, it’s annoying to “find” it in the single state value and even more so to re-assemble that entire state to update it. The same can be true when drawing an entity.
Therefore, we have created an alternate reactive programming library based on sprites.The sprite library was created in collaboration with Artem Agvanian. Sprites are individual entities that have their own local notion of state, display, and so on. Underneath, our sprite library merely stitches together the state of each individual sprite into a larger data structure, so it’s still built atop reactors. But it gives you a more comfortable programming interface for programs that would benefit from that kind of localization.
You are welcome to use the sprite library instead, if your design would better fit this structure.
include shared-gdrive("sprite-lib", "1oXiADjdC5WMA_iljrNdg7Dvo2mJC73_N")
mk-sprite, a constructor for a single Sprite (see details below) that adds the sprite to the current list of sprites
go-sprites :: Number, Number, Number -> List<Sprite>, which takes a width, height, and inverse-frequency of updates, and begins animating the sprites
reset-sprites :: -> Nothing, which resets the list of sprites to forget all previously-defined sprites
posn, a two-dimensional point of type Posn
make-sprites-reactor :: Number, Number, Number -> Reactor<List<Sprites>>, which consumes a width, height, and the inverse-frequency of updates, and returns a reactor of the sprites—
this is useful for testing the sprite ensemble! find-sprite-named :: String, List<Sprite> -> Sprite, which finds other sprites—
useful for reading their properties in case of, e.g., a collision
Name |
| Purpose |
| Value Type |
"name" |
| a name to use for finding |
| String |
"init" |
| initial state |
| T |
"new-state" |
| state update function |
| T, World -> T |
"loc" |
| initial location |
| Posn |
"new-loc" |
| location update function |
| T, Posn, World -> Posn |
"rot" |
| initial rotation |
| Number |
"new-rot" |
| rotation update function |
| T, Posn, Number, World -> Number |
"image" |
| image to draw |
| Image |
"to-draw" |
| drawing function |
| T, World -> Image |
"collision-threshold" |
| distance for collision |
| Number |
"on-collision" |
| collision handling function |
| Sprite, Sprite, World -> Sprite |
"on-key" |
| key handler |
| String, Sprite, World -> Sprite |
include reactors
include shared-gdrive("sprite-lib", "1oXiADjdC5WMA_iljrNdg7Dvo2mJC73_N")
[mk-sprite:]
go-sprites(500, 500, 0.025)
Every Sprite also has a get and update method. The get method takes a key, which is one of the strings in the leftmost column above, and returns a value of the rightmost column’s type. The update method takes a key (again, one of the above strings) and a value of that key’s type, and returns an updated Sprite.
If you want a few illustrative examples of the Sprites library, see this file. This defines a collection of demo functions: d0, d1, and so on. Each takes no arguments, so run it, e.g., d0(). Read the comments above each that tell you what each demo does.