From 5b8b3abcba8746502014266583907421b71a330b Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Thu, 8 Feb 2024 18:36:10 -0700 Subject: [PATCH] add hungry partner function --- script.md | 33 +++--- src/components/function_box.tsx | 182 ++++++++++++++++++-------------- src/project.meta | 2 +- src/scenes/first_box.tsx | 23 ++-- src/scenes/hungry_partner.meta | 5 + src/scenes/hungry_partner.tsx | 108 +++++++++++++++++++ src/scenes/index.ts | 14 +-- 7 files changed, 250 insertions(+), 117 deletions(-) create mode 100644 src/scenes/hungry_partner.meta create mode 100644 src/scenes/hungry_partner.tsx diff --git a/script.md b/script.md index e18b46f..d1ab35d 100644 --- a/script.md +++ b/script.md @@ -41,38 +41,29 @@ Yes, hmmm... so, do you really think this is so? > yes ``` -well that's not super helpful +well that's not super helpful, looks like the "AI repeating itself" notion is nothing modern. -It looks like we have a black box here that came out of nowhere. What does it do? +On our journey to get a valentine, looks like we have stumbled across a black box. What does it do? Well... we could start shoving random numbers in it and see what happens. -When we feed it a one and put it at this arrow, it returns one. When we feed it a two, it returns one. A three, two. A four, three. And so forth. -No matter how many times we put a number in it gives me the same answer; we're all in love with it already. +hmm... [-1, 2]? okay, 1. [3, 4]? okay, 7. what if we try [-1, 2] again, see if it meets our criteria to our valentine. amazing! -So we're ready to start getting to know this so called "function". Where could our curiosity take us? +No matter how many times we put two numbers in it gives me the same answer; we're in love with it already! -Let's look at what's behind this black box. +So we're ready to start getting to know this black box. Let's look at what's behind this black box. -Ah. A simple fibonacci function. +Now where could our curiosity take us? -```python -def fib(n): - if (n <= 2): - return 1 - return fib(n - 1) + fib(n - 2) -``` +Ah. A simple add function. -We're in love with its predictability, the assurance that no matter what we give as input, the function behaves in a consistent manner. +We're in love with its predictability, the assurance that no matter what we give as input or how many times it's given, the function behaves in a consistent manner. -But let's imagine, for a moment, a different kind of relationship. One where actions outside the relationship influence your partner's responses. +But let's imagine, for a moment, a different kind of relationship. One where actions outside the relationship influences responses. -Imagine asking your partner about what food they want to eat. But instead of a straightforward answer based on your question alone and some state (i.e. hunger levels, craving ratios, etc), the function's response is influenced by other factors; the day of the week, and the state of the day or week prior. +Imagine asking your partner about what food they want to eat. But instead of a straightforward answer based on your question alone and some state (i.e. hunger levels, craving ratios, etc), the function's response is influenced by other factors; the day of the week, and the state of the day or week prior. We can simulate this: (go through some ratios and outputs). -```python -def random_number(): - return 4 # picked randomly -``` +Let's see what causes this in this black box we don't love; don't pay attention to the implementation (there's some stuff we haven't talked about yet), but check out this line (MATH.RANDOM). This is a side effect; an unpredictible effect on the output. -This unpredictability is what side effects introduce into our programming relationships. Where output is not just determined by its input but also by the state of the outside world, or the system at the time of execution. Suddenly, the predictability we cherished is compromised. (we'll talk about this more later) +Side effects introduce unpredictability into our programming relationships. Where output is not just determined by its input but also by the state of the outside world, or the system at the time of execution. Suddenly, the predictability we cherished is compromised. (we'll go much more in depth about this more later) So let's take our love of this function and begin studying it. diff --git a/src/components/function_box.tsx b/src/components/function_box.tsx index e046d63..43d3985 100644 --- a/src/components/function_box.tsx +++ b/src/components/function_box.tsx @@ -1,31 +1,10 @@ -import { - Img, - Rect, - Node, - Video, - makeScene2D, - Txt, - Line, - LineSegment, - NodeProps, -} from "@motion-canvas/2d"; -import { - Direction, - beginSlide, - createRef, - map, - slideTransition, - tween, - all, - waitFor, - range, - makeRef, -} from "@motion-canvas/core"; +import { Rect, Node, Txt, Line, NodeProps } from "@motion-canvas/2d"; +import { createRef, all, range, makeRef } from "@motion-canvas/core"; import { CodeBlock } from "@motion-canvas/2d/lib/components/CodeBlock"; import { theme } from "../theme"; -import * as ts from "typescript"; +import { transpile } from "typescript"; export interface FunctionBoxProps extends NodeProps { source?: string; @@ -33,6 +12,9 @@ export interface FunctionBoxProps extends NodeProps { padding?: number; delta?: number; + inputFontSize?: number; + outputFontSize?: number; + workingText?: string; idlingText?: string; @@ -43,18 +25,6 @@ export interface FunctionBoxProps extends NodeProps { type FunctionArgs = { node?: Node; val: any }[]; -/* - - - - */ - export class FunctionBox extends Node { private readonly source: string; private readonly workingText: string; @@ -72,11 +42,14 @@ export class FunctionBox extends Node { private readonly inputSegments: Line[] = []; private readonly inputs: Rect[] = []; private readonly outputSegment = createRef(); - private readonly output = createRef(); + private readonly output = createRef(); private readonly child = createRef(); private readonly isChild: boolean; + private readonly inputFontSize: number; + private readonly outputFontSize: number; + private currentArgs: FunctionArgs = []; public constructor(props?: FunctionBoxProps) { @@ -91,7 +64,7 @@ export class FunctionBox extends Node { } else { this.source = props?.source ?? `(x: number): number => x + 2`; - const functionCode = ts.transpile(this.source); + const functionCode = transpile(this.source); this.function = eval(functionCode); } @@ -103,6 +76,9 @@ export class FunctionBox extends Node { this.isChild = props?.isChild ?? false; + this.outputFontSize = props?.outputFontSize ?? 30; + this.inputFontSize = props?.inputFontSize ?? 30; + this.add( {this.idlingText} + + + @@ -163,6 +148,7 @@ export class FunctionBox extends Node { ref={this.output} justifyContent={"end"} opacity={1} + fontSize={this.outputFontSize} > @@ -173,22 +159,25 @@ export class FunctionBox extends Node { public *resetInput(duration: number) { yield* all( ...this.inputs.map((x) => - all( - x.opacity(0, duration), - x.height(0, duration), - x.width(0, duration), - ), + all(x.opacity(0, duration), x.fontSize(0, duration)), + ), + ...this.inputSegments.map((segment) => + all(segment.points([], duration), segment.opacity(1, duration)), ), - ...this.inputSegments.map((segment) => segment.points([], duration)), ); + + this.inputs.forEach((x) => x.removeChildren()); } public *resetOutput(duration: number) { yield* all( this.output().opacity(0, duration), + this.output().fontSize(0, duration), this.outputSegment().points([], duration), + this.outputSegment().opacity(0, duration), ); - yield this.output().removeChildren(); + + this.output().removeChildren(); } public *reset(duration: number) { @@ -204,7 +193,7 @@ export class FunctionBox extends Node { input.removeChildren(); input.add( args[i].node ?? ( - + {args[i].val.toString()} ), @@ -222,68 +211,99 @@ export class FunctionBox extends Node { ), ...this.inputs.map((input) => all( - input.height(40, duration), - input.width(40, duration), input.opacity(1, duration), + input.fontSize(this.inputFontSize, duration), ), ), ); } public *propogateInput(duration: number) { - const opacityChangeDuration = 0.1; - yield* all( ...this.inputSegments.map((segment) => - segment.opacity(0.2, opacityChangeDuration), + all(segment.opacity(0.2, duration), segment.points([], duration)), ), ); - yield* all( - ...this.inputSegments.map((segment) => segment.points([], duration)), - ); - - yield* all( - ...this.inputSegments.map((segment) => - segment.opacity(1, opacityChangeDuration), - ), - ...this.inputs.map((input) => input.opacity(0, opacityChangeDuration)), + this.resetInput(duration), this.boxMoji().text(this.workingText, duration), ); } public *propogateOutput(duration: number) { - const output = this.function(...this.currentArgs.map((input) => input.val)); - if (typeof output === "function") { - this.output().add( - , - ); - } else { - this.output().add( - - {output.toString()} - , - ); - } + yield* all( + this.boxMoji().text(this.idlingText, duration), + this.child()?.opacity(0.2, duration), + this.output().opacity(0.2, duration), + this.outputSegment().opacity(0, duration), + ); - yield* this.boxMoji().text(this.idlingText, duration); + const output = this.function(...this.currentArgs.map((input) => input.val)); + + switch (typeof output) { + case "function": + yield this.output().add( + , + ); + break; + case "number": + case "string": + yield this.output().add( + + {output.toString()} + , + ); + break; + default: + yield this.output().add( + , + ); + } yield* all( this.outputSegment().points( [ - { x: -this.delta, y: 0 }, + { x: 0, y: 0 }, { x: this.padding, y: 0 }, ], duration, ), + this.outputSegment().opacity(1, duration), + this.output().fontSize(this.outputFontSize, duration), this.child()?.opacity(1, duration), this.output().opacity(1, duration), - this.outputSegment().opacity(1, duration), ); } + + public *showCode(duration: number) { + yield* all( + this.boxMoji().text("", duration), + this.boxMoji().opacity(0, duration), + this.block().fontSize(30, duration), + this.node().opacity(1, duration), + ); + } + + public *hideCode(duration: number) { + yield* this.boxMoji().text(this.idlingText, 0); + yield* all( + this.block().fontSize(0, duration), + this.node().opacity(0, duration), + this.boxMoji().opacity(1, duration), + ); + } + + public getChild() { + return this.child; + } } diff --git a/src/project.meta b/src/project.meta index 9093235..7c59eb3 100644 --- a/src/project.meta +++ b/src/project.meta @@ -25,7 +25,7 @@ "options": { "fileType": "image/png", "quality": 100, - "groupByScene": false + "groupByScene": true } } } diff --git a/src/scenes/first_box.tsx b/src/scenes/first_box.tsx index b3957af..31ee631 100644 --- a/src/scenes/first_box.tsx +++ b/src/scenes/first_box.tsx @@ -4,8 +4,6 @@ import { beginSlide, createRef, slideTransition, - all, - waitFor, } from "@motion-canvas/core"; import { FunctionBox } from "../components/function_box"; @@ -23,14 +21,15 @@ export default makeScene2D(function* (view) { yield* beginSlide("Black Box"); - for (const [a, b] of [ - [-1, 2], + for (const [[a, b], i] of [ + [1, 2], [3, 4], - [5, 6], - ] as [number, number][]) { - const inputId = "(" + [a, b].join(",") + ")"; + [1, 2], + [1, 2], + ].map((x, i) => [x, i]) as [[number, number], number][]) { + const inputId = "(" + [a, b, i].join(",") + ")"; - yield* all(functionBox().reset(0.25)); + yield* functionBox().reset(0.5); yield* functionBox().setInputs([{ val: a }, { val: b }], 0.5); yield* beginSlide("Add Inputs " + inputId); @@ -41,4 +40,12 @@ export default makeScene2D(function* (view) { yield* functionBox().propogateOutput(0.5); yield* beginSlide("Propogate Outputs of " + inputId); } + + yield* functionBox().reset(0.5); + + yield* functionBox().showCode(0.85); + yield* beginSlide("Show Code"); + + yield* functionBox().hideCode(0.85); + yield* beginSlide("Hide Code"); }); diff --git a/src/scenes/hungry_partner.meta b/src/scenes/hungry_partner.meta new file mode 100644 index 0000000..cec20cc --- /dev/null +++ b/src/scenes/hungry_partner.meta @@ -0,0 +1,5 @@ +{ + "version": 0, + "timeEvents": [], + "seed": 3141613664 +} \ No newline at end of file diff --git a/src/scenes/hungry_partner.tsx b/src/scenes/hungry_partner.tsx new file mode 100644 index 0000000..696870e --- /dev/null +++ b/src/scenes/hungry_partner.tsx @@ -0,0 +1,108 @@ +import { Node, Txt, makeScene2D } from "@motion-canvas/2d"; +import { + Direction, + beginSlide, + createRef, + slideTransition, +} from "@motion-canvas/core"; + +import { FunctionBox } from "../components/function_box"; +import { theme } from "../theme"; + +const hungryValentine = `( + savoryCravingRatio: number, sweetCravingRatio: number, + acidityCravingRatio: number, spiceCravingRatio: number, +): string => { + const foods = { + "🍕": {savory: 0.8, sweet: 0.1, acidity: 0.5, spice: 0.5}, + "🧁": {savory: 0.2, sweet: 0.9, acidity: 0.1, spice: 0.1}, + "🍋🍰": {savory: 0.1, sweet: 0.8, acidity: 0.9, spice: 0.1}, + "🌮🔥": {savory: 0.6, sweet: 0.2, acidity: 0.5, spice: 0.8}, + }; + + const weight = (foodProfile: Record) => + foodProfile["savory"] * savoryCravingRatio + + foodProfile["sweet"] * sweetCravingRatio + + foodProfile["acidity"] * acidityCravingRatio + + foodProfile["spice"] * spiceCravingRatio; + + const bestFood = Object.keys(foods).reduce((a, b) => + weight(foods[a]) > weight(foods[b]) ? a : b + ); + + const foodNames = Array.from(Object.keys(foods)); + const shouldChooseRandom = Math.random() > 0.4; // side effect + return shouldChooseRandom + ? foodNames[Math.floor(Math.random() * foodNames.length)] + : bestFood; +};`; + +export default makeScene2D(function* (view) { + const functionBox = createRef(); + + view.add( + , + ); + + yield* slideTransition(Direction.Right); + + yield* beginSlide("Get Best Food For Partner"); + + const order = ["savory", "sweet", "acidic", "spice"]; + + for (const [[a, b, c, d], i] of [ + [0.7, 0.1, 0.4, 0.1], + [0.7, 0.1, 0.4, 0.1], + [0.7, 0.1, 0.4, 0.1], + [0.7, 0.1, 0.4, 0.1], + ].map((x, i) => [x, i]) as [[number, number, number, number], number][]) { + const inputId = "(" + [a, b, c, d, i].join(",") + ")"; + + yield* functionBox().reset(0.5); + yield* functionBox().setInputs( + [a, b, c, d].map((ratio, i) => ({ + val: ratio, + node: ( + + {order[i]}:{" "} + 0.6 + ? theme.red.hex + : ratio < 0.3 + ? theme.green.hex + : theme.lavender.hex + } + > + {ratio.toString()} + + + ), + })), + 0.5, + ); + + yield* beginSlide("Add Inputs " + inputId); + + yield* functionBox().propogateInput(0.5); + yield* beginSlide("Propogate Inputs " + inputId); + + yield* functionBox().propogateOutput(0.5); + yield* beginSlide("Propogate Outputs of " + inputId); + } + + yield* functionBox().reset(0.5); + + yield* functionBox().showCode(0.85); + yield* beginSlide("Show Code"); + + yield* functionBox().hideCode(0.85); + yield* beginSlide("Hide Code"); +}); diff --git a/src/scenes/index.ts b/src/scenes/index.ts index 56fda2b..d21da3b 100644 --- a/src/scenes/index.ts +++ b/src/scenes/index.ts @@ -4,12 +4,14 @@ import partone from "./partone?scene"; import flirtingwithfunctions from "./flirtingwithfunctions?scene"; import doctor from "./doctor?scene"; import first_box from "./first_box?scene"; +import hungry_partner from "./hungry_partner?scene"; export const scenes = [ - title, - me, - partone, - flirtingwithfunctions, - doctor, - first_box, + // title, + // me, + // partone, + // flirtingwithfunctions, + // doctor, + // first_box, + hungry_partner, ];