<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title><![CDATA[ Cheshire Codes ]]></title>
        <description><![CDATA[ a BLOG FROG blog ]]></description>
        <link><![CDATA[ https://cheshire.codes/rss ]]></link>
        <language>en</language>
        <pubDate>Tue, 05 May 2026 11:54:56 +0000</pubDate>
        <atom:link href="https://cheshire.codes/rss" rel="self" type="application/rss+xml" />
        
                    <item>
                <title><![CDATA[2023 Year in Review: Favorite Programming Projects]]></title>
                <link>https://cheshire.codes/2023-projects-review</link>
                <description><![CDATA[Happy New Year! Looking back at 2023, I did a lot of programming projects but didn't keep up with this blog as I hoped. I thought now would be a good time to do a post looking at some of my favorite projects I did in 2023 that I haven't posted about yet. 

In no particular order... 

## TUI Music Player in Golang

<p style="text-align: center;">
<img src="https://i.imgur.com/upj9pTX.png" />
</p>

This little command-line music player will play all songs in the given directory, or in the current directory if none is given. It has options for repeat and shuffle, as well as skipping back and forth through the songs. It uses beep/faiface to play audio and charm.sh for the TUI. [Find the source code here!](https://github.com/cheryllium/music-player)

## Dice-rolling Discord bot

<p style="text-align: center;">
<img src="https://i.imgur.com/U7nhcI0.png" />
</p>

Named **Dicemancer**, this little bot can parse expressions containing dice notation, displaying the results of each roll with special emoji for critical rolls. It also supports creating macros for common rolls.

The bot's source code, along with instructions for adding it to your Discord server, can be found [here](https://github.com/cheryllium/diceroll). 

## Easy HTML/CSS Tutorial

More a writing project than a coding project, this tutorial was actually several years in the making! It's geared towards absolute beginners with the goal of making HTML/CSS as approachable as possible. I wanted to make a tutorial for people who get overwhelmed with other tutorials. It's also a very hands-on tutorial, as I believe the best way to learn to code is by trying things out and seeing for yourself what happens!

Check it out here: [https://easyhtmlcss.com/](https://easyhtmlcss.com)

## Crazy Key Suggester

<p style="text-align: center;">
<img src="https://i.imgur.com/H2xLXUq.png" />
</p>

My roommate and I were discussing theoretical keys one day, which lead to the idea for this mini joke project. A theoretical key is a musical key with at least one double-flat or double-sharp. I thought the concept could be extended to an arbitrary number and sharps or flats... so [Key Suggester](https://key-suggester.netlify.app/) was made. 

## Red-Blue 3D Pong

I was at one point in the year given a gift of red-blue 3D glasses, and was excited to discover that someone made an anaglyph library for P5! It was a lot of fun to put on my glasses and play this simple game.

[Check out the source code here.](https://github.com/cheryllium/3d-pong) To play the game, simply download the code, run a local server (`python -m http.server`) and open the HTML file.]]></description>
                <guid>https://cheshire.codes/2023-projects-review</guid>
                <pubDate>Sat, 06 Jan 2024 20:19:11 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[Using the Function Call Feature of ChatGPT API in Node.js]]></title>
                <link>https://cheshire.codes/chatgpt-function-call-api-nodejs</link>
                <description><![CDATA[I just finished prototyping a project that uses the ChatGPT API, and I used its function call feature to streamline my code. However, I didn't find much online about how to use this feature with the NodeJS openai library, so I thought I'd do a write up on that here.

The function call feature allows you to describe a function to ChatGPT, and get a JSON string in return with arguments which you can then use to call that function. Note that ChatGPT will not actually call the function itself as part of this API - instead it returns JSON which you need to then parse, validate, and then use to call the function yourself later in your code.

## The basics

This tutorial assumes you've set up your configuration already, which will look something like this: 

```javascript
const { Configuration, OpenAIApi } = require('openai')
const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
})
const openai = new OpenAIApi(configuration)
```

Now, you have an `openai` object which you can use to make the API calls. 

Now, to make an API call, you'll be making a call to `createChatCompletion()` which looks something like this:

```javascript
  const completion = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages: [
      {
        role: 'user',
        content: `Grade the following homework answer with a grade from 0 to 100. Add some comments on the student's work. Homework question: ${question} Homework answer: ${answer}`
      }
    ],
    functions: [
       // ... your functions will be described here!
    ]
 })
```

First, you'll specify the model, and then you'll specify the messages you want to send to ChatGPT. You can give it some instructions in this message about the function you want it to call. This tutorial will focus on the function part, so I won't go into further detail about the rest here.

For this simple example, imagine we are asking ChatGPT to act as a grader for a homework question and answer given by a student. We'll ask it to grade the assignment, and then return JSON that can be used to call the following function: 

```
const gradeAssignment = (grade, comments) => {
  // ... code that processes the grade and comments
}
```

## Describing functions

The next item, `functions`, should be an array where each element is an object describing a function that can be called. First, the object will contain a `name` and `description` of the function:

```javascript
   functions: [
     {
       name: "gradeAssignment",
       description: "Grades the homework assignment",
       parameters: {
         'type': 'object',
	 'properties': {
	   // ... 
	 }
       }
     }
   ]
```

The third item, `parameters`, is an object that describes what inputs the function takes. Function parameters are described using [JSON Schema](https://json-schema.org/understanding-json-schema/), and the top level must be type "object". One way to think about it is that the Schema describes an object whose properties are the function inputs. 

Our `gradeAssignment()` function takes two inputs, an integer `grade`, and a string `comments`. We use the JSON Schema Reference in the link above to find out how to represent these types. We list them under `properties` like this:

```
'properties': {
  'grade': {
    'type': 'integer',
    'description': 'The grade for the assignment, between 0 and 100'
  },
  'comments': {
    'type': 'string',
    'description': 'Additional comments on the student work'
  }
}
```

Our full code will now look like this:

```javascript
  const completion = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    messages: [
      {
        role: 'user',
        content: `Grade the following homework answer with a grade from 0 to 100. Add some comments on the student's work. Homework question: ${question} Homework answer: ${answer}`
      }
    ],
    functions: [
      {
        name: "gradeAssignment",
        description: "Grades the homework assignment",
        parameters: {
          'type': 'object',
          'properties': {
            'grade': {
              'type': 'integer',
              'description': 'The grade for the assignment, between 0 and 100'
            },
            'comments': {
              'type': 'string',
              'description': 'Additional comments on the student work'
            }	  
	  }
        }
      }
    ]
 })
```

As you can see, it gets very nested, but hopefully you can understand it since we've broken it down step by step.

## Getting the response

The code above asynchronously calls the API and gets the result in the variable `completion`. Now, it's relatively simple to use this result.

(The OpenAI API Reference describes the structure of the result you will get [here](https://platform.openai.com/docs/api-reference/making-requests), but that doesn't contain a function calling example. The function calling result is described by the code example [here](https://platform.openai.com/docs/guides/gpt/function-calling). This is a bit hard to understand, so I'll just explain what you need to do.)

First, get the completion response:

```
const completionResponse = completion.data.choices[0].message
```

Now, if `completionResponse.function_call` exists, then that means ChatGPT wanted to call a function. `completionResponse.function_call.arguments` will give you the arguments as a JSON string, which you'll want to parse using `JSON.parse()`.

```
if(completionResponse.function_call) {
  const arguments = JSON.parse(completionResponse.function_call.arguments)
}
```

Now, you can call the function like this:

```
gradeAssignment(arguments.grade, arguments.comments)
```

And that's it!

## Further notes

### Multiple functions

You can also give ChatGPT multiple functions to choose from. As mentioned above, `functions` in your request is an array describing all of the functions we can call. Additional functions will be described the same way, and simply added to this array.

When parsing the response, `completionResponse.function_call.name` will give you the name of the function it wants to call.

### Using without calling a function

As you may have figured out, this API feature can also be used to get a response from ChatGPT in an easy JSON format. You can use its returned JSON for anything - the function you describe to it does not actually have to exist in your code.]]></description>
                <guid>https://cheshire.codes/chatgpt-function-call-api-nodejs</guid>
                <pubDate>Sat, 29 Jul 2023 16:29:54 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[Mini Docker Gripe]]></title>
                <link>https://cheshire.codes/docker-gripe</link>
                <description><![CDATA[This has happened to me so many times, I'm writing a blog post just to vent about it. 

Today, [BLOG FROG's CI](https://gitlab.com/cheryllium/blog-frog/-/jobs) failed after I pushed an update. However, it wasn't due to the new code I pushed. To explain what happened, I'll have to give a brief background. 

The first step in BLOG FROG's CI is to build its docker image. BLOG FROG's Dockerfile does this in two steps: 

1. Using the **Composer 2.4** docker image, install dependencies. 
2. Build BLOG FROG's image from the **PHP 8.1 with Apache** image, copying dependencies from the previous step. 

The CI was failing on the first of these steps: installing dependencies with Composer 2.4. Looking more closely, one of the dependencies was giving this error: 

```
nette/schema v1.2.2 requires php >=7.1 <8.2 -> your php version (8.2.4) does not satisfy that requirement.
```

Apparently, at some point, the `composer:2.4` image updated to use PHP version 8.2 instead of 8.1, and this was causing a problem with my dependencies. Looking at the Dockerfile for `composer:2.4` confirmed that it's built off of `php:8-alpine`, which must have updated its version in the time between now and the last BLOG FROG update. 

This is not the first time I've had CI fail because of a base docker image changing software versions from under my feet. It's one of the gripes I have with docker, or more technically, how docker is used. It's for this reason that I think building your images off of `:latest` is an antipattern; if you do so, you risk things breaking when the version updates unexpectedly. This is why I try to pin my docker images to specific versions. 

However, in this case, pinning to a specific Composer version still introduced the same bug. My error was not pinning to **the specific PHP version I needed.** I should have realized that the Composer image would be pinned to PHP latest. 

To fix the issue, I copied the `composer:2.4` Dockerfile contents into my build step and changed the first line so that it builds from `php8.1-alpine` specifically. This fixed the issue and should hopefully prevent any more unexpected version mismatches moving forward.]]></description>
                <guid>https://cheshire.codes/docker-gripe</guid>
                <pubDate>Mon, 10 Apr 2023 02:44:16 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[Making a Simple Fantasy Desktop with No JavaScript]]></title>
                <link>https://cheshire.codes/fantasy-desktop-no-js</link>
                <description><![CDATA[This weekend, I set out to make a little Personal Home Page, and what better way than to use old-school PHP, HTML, and CSS to make something gimmicky and fun. I decided to make a page that looks kinda like a Windows desktop. And just for fun, I decided to do it with no JS, just to see how far I could get. 

## Taskbar and iFrames

I had already made a few simple HTML/PHP minigames on their own web pages, so I thought it would be fun to load them into iframes for the "windows" on the desktop. I used the `<details>` tag to represent both a window and its button in the taskbar. 

The `<details>` tag is a newer HTML tag with "click-to-open" functionality. If you write the following: 

```html
<details>
    <summary>Open me...</summary>
    <p>Hello world!</p>
</details>
```

You'll see that you can click what's inside `<summary>` to display the rest of the contents, as below: 

<details>
    <summary>Open me...</summary>
    <p>Hello world!</p>
</details>

Naturally, the `<summary>` tag will be used for the taskbar buttons, and the windows will be what they "open" and "close". The general format looks something like this: 

```html
<div id="os">
    <div id="taskbar">
        <details>
            <summary>Window Title</summary>
            <div class="window">
                ...
            </div>
        </details>
    </div>
</div>
```

To style this, I absolutely position all of the windows (`.window`) relative to the `#os` div, but leave the rest of the elements (including the `#taskbar`) unpositioned. Then, I use flexbox to align `#taskbar` to the bottom of `#os`. What results is that the `<summary>` elements remain aligned inside of `#taskbar` at the bottom of the screen, and the `.window` elements can be positioned freely within `#os`. 

Each "window" is simply another `<details>` element. I positioned the windows according to what I felt looked nice and well utilized the space. Sure, they don't have close buttons and you can't drag and drop them, but I still got pretty far forgoing JS!

## Radio Button Tabs

One of my windows is a fake web browser - there's no URL bar, but there will be tabs where it can load a couple external web pages, including this blog. I used radio buttons to mimic the functionality of browser tabs. 

There were three parts to this: 

1. The radio buttons (`<input>` elements)
2. The radio button labels (`<label>` elements)
3. The actual tabs (`.tab` elements containing the iframes)

The key is that all of these must be siblings, so that we can write CSS for the selected tab using `input:checked ~ .tab`. (The `~` here means "a sibling of" in CSS.)

Once this structure is in place, I added CSS to hide the radio buttons (we only want to see their labels), position the tabs on top of each other, hide tabs by default, and unhide the selected tab. With that, tabbing functionality was in place. The rest was just a little styling and polish to make it look good. 

## The Clock

I wanted to add a clock to the right-hand corner of the page just like a real desktop, but without using `setInterval()` or any JS. To do this, I created a `time.php` page containing a meta tag to auto-refresh on the minute. 

The following meta tag will cause a page to refresh itself every 30 seconds. The number of seconds to wait is determined by the `content` attribute. 

```html
<meta http-equiv="refresh" content="30">
```

To make it refresh on the minute, I used PHP to calculate how many seconds are left until the next minute, and use this for the `content` attribute. 

```html
<meta http-equiv="refresh" content="<?= 60 - date('s') ?>">
```

Then, I simply write out the time on the actual page. 

```php
<?php
    date_default_timezone_set('America/New_York');
    echo date('h:i A');
?>
```

Finally, I loaded `time.php` inside an iFrame in the "taskbar" on the actual home page. The contents of the iFrame refresh on the minute, forming a dynamic clock!

You can see the finished version, with all of its other bells and whistles, at [cheshire.gay](https://cheshire.gay). 

## AI Note

As an aside, **cheshire.gay** was one of my first projects where I used AI in its creation: 

- Stable Diffusion generated the background images for all of the themes except for the Webmaster theme. 
- ChatGPT generated all of the code for the guestbook page, including code for a secret admin page where I can delete guestbook entries. 
- I also used ChatGPT throughout as a Google alternative for general coding questions, or when I didn't feel like consulting documentation. 

It's really cool how AI can help you code and create, and I'm excited to see how the future with AI will look as a developer.]]></description>
                <guid>https://cheshire.codes/fantasy-desktop-no-js</guid>
                <pubDate>Sun, 09 Apr 2023 17:10:10 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[Disable Foreign Key Constraint Checks in MySQL DB]]></title>
                <link>https://cheshire.codes/disable-foreign-key-checks</link>
                <description><![CDATA[This is usually a terrible idea, but sometimes you want to do it. For example, say you want to just purge a lot of data without worrying about foreign key constraints, because the table restricting your delete is also going to be purged. 

You can disable foreign key checks with the following command: 

```
SET foreign_key_checks = 0;
```

Once you are done, be sure to re-enable them: 

```
SET foreign_key_checks = 1; 
```]]></description>
                <guid>https://cheshire.codes/disable-foreign-key-checks</guid>
                <pubDate>Wed, 29 Mar 2023 16:15:07 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[LESS Can&#039;t Use Hex Codes with Opacity]]></title>
                <link>https://cheshire.codes/less-alpha-mistake</link>
                <description><![CDATA[I recently wrote this line of CSS inside of a `.less` file....

```css
background: linear-gradient(#fff0, #fff);
```

...after first trying it out inside the browser inspector, where it worked as expected, rendering a gradient background from transparent fading into white. 

However, it did not work correctly after compiling the LESS file. It looks like it was getting compiled to: 

```css
background: linear-gradient(#ffffff 0, #ffffff);
```

Even writing `#ffffff00` would cause LESS to compile it to the less helpful `#ffffff 0`, which is... interesting. I ended up having to use `rgba()` as a workaround.

```css
background: linear-gradient(rgba(0,0,0,0), #fff); 
```

That worked.]]></description>
                <guid>https://cheshire.codes/less-alpha-mistake</guid>
                <pubDate>Thu, 09 Mar 2023 15:50:15 +0000</pubDate>
            </item>
                    <item>
                <title><![CDATA[Coding a Chaos Game Simulation to Generate Rainbow Fractals]]></title>
                <link>https://cheshire.codes/rainbow-fractals-with-the-chaos-game</link>
                <description><![CDATA[The chaos game is a neat way of generating fractals with just a polygon and a point - and a smattering of random chance. To play the game, first you start with polygon and a random initial point somewhere inside of that polygon. Then:

1. Pick a random **vertex** on the polygon.
1. Draw a point that is *some fraction* of the way in between your random point and the vertex you picked. 
1. Repeat the steps, but now using the new point you just drew. 

It turns out that if you repeat these steps over and over again, it will **sometimes** generate a fractal! Whether you get a fractal or not, and what that fractal looks like, depends on how many sides the polygon has as well as what *fraction* you chose.

Let's use **n** to refer to the number of sides in our polygon (making it an *n*-gon), and **r** to refer to the fraction we'll use to pick each point.

Technically, you can play this game with a paper, pencil, ruler, and die... but we are living in **the future** and can now make computers do it instead. So without further ado, let's dive in to the simulation.  

[Just want to play? Click here to skip the text and jump to the simulation.](#interactive-chaos-section)

## Playing the chaos game with JavaScript

To simulate this game, I used a JavaScript library called [p5.js](https://github.com/processing/p5.js/wiki/p5.js-overview) that gives us a simple framework for drawing graphics on an HTML canvas. However, I will focus more on the chaos game implementation in this blog post than on the specifics of p5.js. I find p5's functions are descriptively named, so it should still be easy enough to follow my source code. But if you'd like to learn more about p5, their official tutorials are a great place to start. 

### Generating the vertices

The first step in the game is to figure out the coordinates of our polygon's vertices. A polygon with **n** sides will have **n** vertices. To calculate them, we'll imagine these vertices as points evenly spaced around a circle. We'll use a bit of **trigonometry** to calculate the **x** and **y** coordinates of these points. 

Usually when traversing a circle, we start with the rightmost point and travel counter-clockwise. We can use **θ** (theta) to refer to the angle we've traveled from the starting point. If the center of the circle is the origin (0, 0), then point on it at angle **θ** will have the coordinates **(r \* cos(θ), r \* sin(θ))**, where **r** is the radius of the circle.

There are 2π radians in a circle, so if the polygon has **n** vertices, then each one will be spaced **2π/n** radians apart. So we'll simply calculate points for where **θ** is a multiple of **2π/n**. For the radius, we can use half of the canvas size. Our pseudocode, so far, looks like this: 

```js
theta = 0;
dTheta = TWO_PI / n;
for(i from 0 to n) {
    vertex = {
        x: (canvasSize / 2) * cos(theta), 
        y: (canvasSize / 2) * sin(theta),
    };
    drawPoint(vertex); 
    theta += dTheta; 
}
```

Two more notes: 

1. The calculation above assumes the circle is centered at (0, 0). But (0, 0) is actually the top left of the canvas, and our circle's real center is **(canvasSize / 2, canvasSize / 2)**. So we simply add **canvasSize / 2** to our calculations to translate the points. 
1. In p5's coordinate system, y-values increase downwards. Since our calculation is for a coordinate system where y-values increase upwards, we'll simply subtract our calculated value from the canvas height to get the correct coordinate. 

Our calculations now look like this: 

```js
    x: (canvasSize / 2) + (canvasSize / 2) * cos(theta), 
    y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * sin(theta)), 
```

You can use the widget below to visualize this calculation around the circle. 

<form id="interactive_trig_form" style="text-align:center;margin-bottom:5px">
    n: <input name="n" type="number" value="5" onChange="setNewN()" autocomplete="off" />
    <input type="button" onclick="drawNextStep()" value="Draw next step" />
    <input type="button" onclick="resetTrig()" value="Reset" />
    <br />
    <input type="checkbox" name="flipped" id="it_flipped" autocomplete="off" onchange="trigInteractiveVars.flipped=this.checked; resetTrig()" /> <label for="it_flipped">Swap sine and cosine</label>
</form>
<div id="interactive_trig" style="text-align:center"></div>

There is one more thing. Polygons drawn this way come out looking sideways, since we are starting at the right and not the top. Swapping the sine and cosine (using sine for x and cosine for y) will cause us to start from the top of the circle and move clockwise instead, giving us an upright-looking polygon. You can check the "Swap sine and cosine" checkbox in the simulation above to see this in action.

My final code (swapping sine and cosine) looks something like this: 

```js
vertices = []; 
dTheta = TWO_PI / n;

for(let i = 0; i < n; i++) {
    vertex = {
        x: (canvasSize / 2) + (canvasSize / 2) * sin(theta), 
        y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * cos(theta)), 
    };
    vertices.push(vertex);
    drawPoint(vertex);
    
    theta += dTheta; 
}
```

### From point to point we go!

To calculate the next point from the current one, I moved both the **x** and **y** values of the selected vertex some fraction of the distance between it and the chosen point: 

```js
nextPoint = {
    x: (currentPoint.x - randomVertex.x) * r + randomVertex.x, 
    y: (currentPoint.y - randomVertex.y) * r + randomVertex.y, 
}; 
```

Finally, just before I draw the point, I'll call `fill()` which sets the color that subsequent drawing operations will render with. 

```js
fill(
    nextPoint.x / canvasSize * 255, 
    nextPoint.y / canvasSize * 255, 
    255
);
```

`fill()` takes an HSB value, with each value being out of 255. Here, I've set the hue and saturation as percentages of 255 based on the point's location on the screen. This will produce a gorgeous rainbow gradient as the screen fills up. 

The final code excerpt looks like this: 

```js
randomVertex = vertices[Math.floor(Math.random() * vertices.length)]; 
nextPoint = {
    x: (currentPoint.x - randomVertex.x) * r + randomVertex.x, 
    y: (currentPoint.y - randomVertex.y) * r + randomVertex.y, 
}; 

fill(
    nextPoint.x / canvasSize * 255, 
    nextPoint.y / canvasSize * 255, 
    255
);

drawPoint(nextPoint); 
currentPoint = nextPoint; 
```

<a name="interactive-chaos-section"></a>
### The final simulation

Now for the fun part! Here's the final simulation I ended up with. Try giving it different values of **n** and **r** and see what happens. Some values of **r** you might try could be 1/3, 1/5, 3/8. 

The default values in the simulation, **n=3** and **r=1/2**, produce the well-known **Sierpinski triangle**. See what else you can discover as you play around!

<form id="interactive_chaos_form">
  <div style="text-align:center;margin-bottom:10px">
    n: <input type="number" value="3" name="n" autocomplete="off" />
    r: <input type="text" value="1/2" name="r" autocomplete="off" />
    <input type="button" onclick="setNewInputs()" value="Run with new inputs" />
  </div>
  <div id="interactive_chaos" style="text-align:center"></div>
  <div style="text-align:center">
    <input type="button" onclick="toggleDrawing()" value="Start or Stop Animation" />
  </div>
</form>

## Results

When you play around with the chaos game - especially by changing just one of the variables (**r** or **n**) at a time - you'll notice some interesting patterns emerge. Below, you can see what happens as you **decrease** the value of **r** for **n=3**, **n=5**, and **n=7**. Click the button to run the simulations!

<div style="text-align:center;margin-bottom:10px">
  <input type="button" onclick="drawExamples()" value="Show results" />
</div>

<div id="chaos_grid_n3" style="text-align:center"></div>

<div id="chaos_grid_n5" style="text-align:center"></div>

<div id="chaos_grid_n7" style="text-align:center"></div>

Cool, right? It's a gallery of rainbow snowflakes! I hope you enjoyed my blog post on an interesting way to generate fractals :) 

<script type="text/javascript">
 
 let trigInteractiveVars = {
     n: 5,
     flipped: false,
     vertices: [],
     count: 0,
     theta: 0,
     drawing: true,
 };
 function resetTrig() {
     trigInteractiveVars.vertices = [];
     trigInteractiveVars.count = 0;
     trigInteractiveVars.theta = 0;
     trigInteractiveVars.drawing = true; 
 }
 function drawNextStep() {
     trigInteractiveVars.drawing = true; 
 }
 function setNewN () {
     let formData = new FormData(document.getElementById('interactive_trig_form'));
     let newN = parseInt(formData.get('n'));
     if(newN >= 3) {
         trigInteractiveVars.n = newN; 
     }
     resetTrig(); 
 }
 const trigInteractive = ( p5 ) => {
     p5.setup = function() {
         p5.createCanvas(600, 200);
         p5.frameRate(30); 
     }

     p5.draw = function() {
         if(!trigInteractiveVars.drawing || trigInteractiveVars.count > trigInteractiveVars.n) {
             return; 
         }
         
         let canvasSize = 100;
         
         p5.stroke(60, 80, 150);
         p5.fill(255); 
         p5.rect(0, 0, 600, 200);

         p5.fill(60, 80, 150);
         p5.line(canvasSize/2, canvasSize/2, canvasSize/2, canvasSize*1.5);
         p5.line(canvasSize/2, canvasSize/2, canvasSize*1.5, canvasSize/2);
         p5.textSize(14);

         p5.textAlign(p5.CENTER);         
         p5.text(""+canvasSize, canvasSize/2, canvasSize*1.5 + 15);
         p5.text(""+canvasSize, canvasSize*1.5 + 15, canvasSize/2 + 5);
         p5.text("0", canvasSize/2 - 8, canvasSize/2 + 5); 

         p5.stroke(0); 
         p5.fill(255); 
         p5.circle(canvasSize, canvasSize, canvasSize);

         let dTheta = p5.TWO_PI / trigInteractiveVars.n;
         let theta = trigInteractiveVars.theta; 
         if(trigInteractiveVars.flipped) {
             vertex = {
                 x: (canvasSize / 2) + (canvasSize / 2) * p5.sin(theta) + 50,
                 y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * p5.cos(theta)) + 50,
             };             
         } else {
             vertex = {
                 x: (canvasSize / 2) + (canvasSize / 2) * p5.cos(theta) + 50,
                 y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * p5.sin(theta)) + 50,
             };
         }
         trigInteractiveVars.vertices.push(vertex); 

         p5.stroke(60, 80, 150);
         p5.fill(60, 80, 150);
         p5.line(vertex.x, vertex.y, canvasSize, canvasSize);

         p5.textAlign(p5.LEFT);
         let thetaText;
         if(trigInteractiveVars.count) {
             thetaText = trigInteractiveVars.count*2 + "/π"; 
         } else {
             thetaText = 0; 
         }
         let startY = 75;
         p5.text("θ = "+thetaText, 200, startY);
         if(trigInteractiveVars.flipped) {
             p5.text("x = 50 + 50 * sin("+thetaText+") = "+vertex.x.toFixed(2), 200, startY + 25);
             p5.text("y = 100 - (50 + 50 * cos("+thetaText+")) = "+vertex.y.toFixed(2), 200, startY + 50);
         } else {
             p5.text("x = 50 + 50 * cos("+thetaText+") = "+vertex.x.toFixed(2), 200, startY+25);
             p5.text("y = 100 - (50 + 50 * sin("+thetaText+")) = "+vertex.y.toFixed(2), 200, startY+50);
         }
         
         p5.fill(192, 255, 135);
         p5.circle(vertex.x, vertex.y, 4); 
         p5.circle(canvasSize, canvasSize, 4);

         p5.stroke(150, 60, 232);
         for(i = 0; i < trigInteractiveVars.vertices.length - 1; i++){
             p5.line(
                 trigInteractiveVars.vertices[i].x, trigInteractiveVars.vertices[i].y,
                 trigInteractiveVars.vertices[i+1].x, trigInteractiveVars.vertices[i+1].y, 
             );
         }
         
         trigInteractiveVars.theta += dTheta;
         trigInteractiveVars.count++;
         trigInteractiveVars.drawing = false; 
     }
 }

 let chaosVars = {
     initialized: false,
     drawing: false,
     drawingExamples: false, 
     n: 3,
     r: 1/2, 
 }
 
 function toggleDrawing () {
     chaosVars.drawing = !chaosVars.drawing; 
 }
 function drawExamples () {
     chaosVars.drawingExamples = true; 
 }
 function setNewInputs() {
     let formData = new FormData(document.getElementById('interactive_chaos_form'));
     chaosVars.n = parseInt(formData.get('n'));
     chaosVars.r = formData.get('r').split('/').reduce(function (numerator, denominator, i) {
         return numerator / (i ? denominator : 1)
     });

     chaosVars.initialized = false;
     chaosVars.drawing = true; 
 }
 const chaosSimulationInteractive = ( p5 ) => {
     const canvasSize = 500;
     
     let currentPoint;
     let vertices = [];

     function initializeGame() {
         p5.fill(0);
         p5.rect(0, 0, canvasSize, canvasSize);

         p5.fill(255); 
         currentPoint = {
             x: canvasSize / 2, 
             y: canvasSize / 2,
         };
         drawPoint(currentPoint); 

         vertices = []; 
         let dTheta = p5.TWO_PI/chaosVars.n; 
         let theta = 0;
         let vertex; 
         for(let i=0; i<chaosVars.n; i++) {
             vertex = {
                 x: (canvasSize / 2) + (canvasSize / 2) * p5.sin(theta),
                 y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * p5.cos(theta)),
             };
             vertices.push(vertex);
             drawPoint(vertex); 
             
             theta += dTheta; 
         }
     }

     p5.setup = function() {
         p5.createCanvas(canvasSize, canvasSize);

         p5.background(0);
         p5.frameRate(60);
         p5.noStroke();
         p5.colorMode(p5.HSB, 255); 
     };

     p5.draw = function() {
         let randomVertex, nextPoint;
         
         if(!chaosVars.drawing) {
             return; 
         }

         if(!chaosVars.initialized) {
             initializeGame();
             chaosVars.initialized = true; 
         }

         for(let i=0; i<20; i++) {
             randomVertex = vertices[Math.floor(Math.random() * vertices.length)];
             nextPoint = {
                 x: (currentPoint.x - randomVertex.x) * chaosVars.r + randomVertex.x,
                 y: (currentPoint.y - randomVertex.y) * chaosVars.r + randomVertex.y, 
             };

             p5.fill(
                 nextPoint.x / canvasSize * 255,
                 nextPoint.y / canvasSize * 255,
                 255
             ); 
             drawPoint(nextPoint);
             
             currentPoint = nextPoint;
         }
     };

     function drawPoint(point) {
         p5.rect(point.x-1, point.y-1, 2, 2);
     }
 };

 const chaosSimulation = (n, rText) => ( p5 ) => {
     let r = rText.split('/').reduce(function (numerator, denominator, i) {
         return numerator / (i ? denominator : 1)
     });
     let canvasSize = 200;
     
     let currentPoint;
     let vertices = [];

     let pointsDrawn = 0; 

     function initializeGame() {
         p5.fill(0);
         p5.rect(0, 0, canvasSize, canvasSize);

         p5.fill(255); 
         currentPoint = {
             x: canvasSize / 2, 
             y: canvasSize / 2,
         };
         drawPoint(currentPoint); 

         vertices = []; 
         let dTheta = p5.TWO_PI/n; 
         let theta = 0;
         let vertex; 
         for(let i=0; i<n; i++) {
             vertex = {
                 x: (canvasSize / 2) + (canvasSize / 2) * p5.sin(theta),
                 y: canvasSize - ((canvasSize / 2) + (canvasSize / 2) * p5.cos(theta)),
             };
             vertices.push(vertex);
             drawPoint(vertex); 
             
             theta += dTheta; 
         }
     }

     p5.setup = function() {
         p5.createCanvas(canvasSize, canvasSize + 70);

         p5.background(0);
         p5.frameRate(60);
         p5.noStroke();
         p5.colorMode(p5.HSB, 255);

         initializeGame();

         p5.textSize(16); 
         p5.textAlign(p5.CENTER); 
         p5.text("n="+n+", r="+rText, canvasSize / 2, canvasSize + 40);
     };

     p5.draw = function() {
         let randomVertex, nextPoint;

         if(!chaosVars.drawingExamples) {
             return; 
         }
         
         if(pointsDrawn > 10000) {
             return; 
         }
         
         randomVertex = vertices[Math.floor(Math.random() * vertices.length)];
         nextPoint = {
             x: (currentPoint.x - randomVertex.x) * r + randomVertex.x,
             y: (currentPoint.y - randomVertex.y) * r + randomVertex.y, 
         };

         p5.fill(
             nextPoint.x / canvasSize * 255,
             nextPoint.y / canvasSize * 255,
             255
         ); 
         drawPoint(nextPoint);
         
         currentPoint = nextPoint;
         pointsDrawn ++;
     };

     function drawPoint(point) {
         p5.rect(point.x-1, point.y-1, 2, 2);
     }
 };

 // Interactive trig
 new p5(trigInteractive, 'interactive_trig'); 
 
 // Interactive chaos
 new p5(chaosSimulationInteractive, 'interactive_chaos');

 // Chaos game simulations
 new p5(chaosSimulation(3, "1/2"), 'chaos_grid_n3');
 new p5(chaosSimulation(3, "1/3"), 'chaos_grid_n3');
 new p5(chaosSimulation(3, "1/5"), 'chaos_grid_n3');
 new p5(chaosSimulation(3, "1/8"), 'chaos_grid_n3'); 

 new p5(chaosSimulation(5, "1/2"), 'chaos_grid_n5');
 new p5(chaosSimulation(5, "1/3"), 'chaos_grid_n5');
 new p5(chaosSimulation(5, "1/5"), 'chaos_grid_n5');
 new p5(chaosSimulation(5, "1/8"), 'chaos_grid_n5'); 

 new p5(chaosSimulation(7, "1/2"), 'chaos_grid_n7');
 new p5(chaosSimulation(7, "1/3"), 'chaos_grid_n7');
 new p5(chaosSimulation(7, "1/5"), 'chaos_grid_n7');
 new p5(chaosSimulation(7, "1/8"), 'chaos_grid_n7'); 
 
</script>]]></description>
                <guid>https://cheshire.codes/rainbow-fractals-with-the-chaos-game</guid>
                <pubDate>Tue, 08 Nov 2022 06:28:42 +0000</pubDate>
            </item>
            </channel>
</rss>
