add hungry partner function

This commit is contained in:
Elizabeth Hunt 2024-02-08 18:36:10 -07:00
parent c18b81b2f2
commit 5b8b3abcba
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
7 changed files with 250 additions and 117 deletions

View File

@ -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.

View File

@ -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 }[];
/*
<Node ref={this.node} opacity={0}>
<CodeBlock
fontFamily={theme.font}
language="typescript"
ref={this.block}
fontSize={1}
code={this.source}
></CodeBlock>
</Node>
*/
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<Line>();
private readonly output = createRef<Node>();
private readonly output = createRef<Rect>();
private readonly child = createRef<FunctionBox>();
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(
<Rect
opacity={this.opacity}
@ -115,6 +91,7 @@ export class FunctionBox extends Node {
<Rect direction={"row"} alignItems={"center"} gap={10}>
<Rect
direction={"row"}
fontSize={this.inputFontSize}
ref={makeRef(this.inputs, i)}
justifyContent={"end"}
opacity={1}
@ -141,11 +118,19 @@ export class FunctionBox extends Node {
padding={60}
direction={"row"}
height={"100%"}
gap={40}
>
<Txt fontFamily={theme.font} fill={theme.text.hex} ref={this.boxMoji}>
{this.idlingText}
</Txt>
<Node ref={this.node} opacity={0}>
<CodeBlock
fontFamily={theme.font}
language="typescript"
ref={this.block}
fontSize={0}
code={this.source}
></CodeBlock>
</Node>
</Rect>
<Rect direction={"column"} height={"100%"} alignItems={"end"}>
@ -163,6 +148,7 @@ export class FunctionBox extends Node {
ref={this.output}
justifyContent={"end"}
opacity={1}
fontSize={this.outputFontSize}
></Rect>
</Rect>
</Rect>
@ -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 ?? (
<Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
<Txt fontFamily={theme.font} fill={theme.text.hex}>
{args[i].val.toString()}
</Txt>
),
@ -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(
<FunctionBox
opacity={0}
isChild={true}
ref={this.child}
fn={output}
></FunctionBox>,
);
} else {
this.output().add(
<Txt fontFamily={theme.font} fontSize={30} fill={theme.text.hex}>
{output.toString()}
</Txt>,
);
}
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(
<FunctionBox
opacity={0}
isChild={true}
ref={this.child}
fn={output}
></FunctionBox>,
);
break;
case "number":
case "string":
yield this.output().add(
<Txt fontFamily={theme.font} fill={theme.text.hex}>
{output.toString()}
</Txt>,
);
break;
default:
yield this.output().add(
<CodeBlock
fontFamily={theme.font}
language="typescript"
fontSize={15}
code={JSON.stringify(output, null, 2)}
></CodeBlock>,
);
}
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;
}
}

View File

@ -25,7 +25,7 @@
"options": {
"fileType": "image/png",
"quality": 100,
"groupByScene": false
"groupByScene": true
}
}
}

View File

@ -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");
});

View File

@ -0,0 +1,5 @@
{
"version": 0,
"timeEvents": [],
"seed": 3141613664
}

View File

@ -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<string, number>) =>
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<FunctionBox>();
view.add(
<FunctionBox
arity={4}
idlingText={"😴"}
workingText={"😋💭"}
source={hungryValentine}
ref={functionBox}
/>,
);
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: (
<Txt fontFamily={theme.font} fill={theme.text.hex}>
{order[i]}:{" "}
<Txt
fontFamily={theme.font}
fill={
ratio > 0.6
? theme.red.hex
: ratio < 0.3
? theme.green.hex
: theme.lavender.hex
}
>
{ratio.toString()}
</Txt>
</Txt>
),
})),
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");
});

View File

@ -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,
];