From 2048bf28d6b8e7636bcde0e9800c3099f85a5a13 Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Thu, 29 Oct 2020 08:55:55 +0530 Subject: [PATCH 1/6] Started working on toothpick Signed-off-by: Lalit Umbarkar --- README.md | 5 +++-- toothpick/index.html | 51 ++++++++++++++++++++++++++++++++++++++++++ toothpick/main.css | 41 +++++++++++++++++++++++++++++++++ toothpick/toothpick.js | 35 +++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 toothpick/index.html create mode 100644 toothpick/main.css create mode 100644 toothpick/toothpick.js diff --git a/README.md b/README.md index acd67fc..72ab413 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ This repository contains visualization of various [Numberphile](https://www.yout ## Projects - 1. Trapped Knight - [Video](https://www.youtube.com/watch?v=RGQe8waGJ4w) - [Illustration](./jumping-knight) - 2. Prime Spirals - [Video](https://www.youtube.com/watch?v=iFuR97YcSLM) - [Illustration](./prime-spirals) + 1. Trapped Knight - [Video](https://www.youtube.com/watch?v=RGQe8waGJ4w){:target="_blank"} - [Illustration](./jumping-knight) + 2. Prime Spirals - [Video](https://www.youtube.com/watch?v=iFuR97YcSLM){:target="_blank"} - [Illustration](./prime-spirals) + 3. Terrific Toothpick Patterns - [Video](https://www.youtube.com/watch?v=_UtCli1SgjI){:target="_blank"} - [Illustration](./toothpick) ## Contribute diff --git a/toothpick/index.html b/toothpick/index.html new file mode 100644 index 0000000..cedfcbe --- /dev/null +++ b/toothpick/index.html @@ -0,0 +1,51 @@ + + + + + + + Numberphilephile - Terrific Toothpick Patterns + + + + + + + + + + +
+
+
+ +
+
+

+ Run Options +

+
+ + + +
+
+
+
+
+
+
+
+
+ + + + + diff --git a/toothpick/main.css b/toothpick/main.css new file mode 100644 index 0000000..507ec58 --- /dev/null +++ b/toothpick/main.css @@ -0,0 +1,41 @@ +body { + padding-top: 50px; + background-color: #4b5d67; +} + +.block { + background: #feffc4; + align-items: center; + color: #FFFFFFE6; +} + +.block.dotted { + border: 1px dotted #4b5d67; +} + +.rowBlock { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(1px, 1fr)); + text-align: center; +} + +.root-grid { + display: grid; + width: 100%; +} + +.block.black { + background-color: #1b1c1d; +} + +.block.red { + background-color: #db2828; +} + +.block.grey { + background-color: #767676; +} + +.block.olive { + background-color: #b5cc18; +} diff --git a/toothpick/toothpick.js b/toothpick/toothpick.js new file mode 100644 index 0000000..5e1b5ee --- /dev/null +++ b/toothpick/toothpick.js @@ -0,0 +1,35 @@ +/** + * Created By : Lalit Umbarkar + * Created On : 11/10/20 + */ + +const toothpick = (function () { + + const getHTMLFromGrid = () => { + return "
" + "
"; + }; + + const resetGrid = () => { + toothpick.render(); + }; + + const registerListeners = () => { + }; + + return { + init: () => { + registerListeners(); + toothpick.render(); + }, + render: () => { + document.getElementById("root").innerHTML = getHTMLFromGrid(); + let rootEle = document.getElementsByClassName("root-grid")[0]; + rootEle.setAttribute("style", "height: " + rootEle.clientWidth + "px"); + }, + }; + +})(); + + + + From 64403c602f1311595da29b7547e70856f9d3fbd5 Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Fri, 30 Oct 2020 14:09:59 +0530 Subject: [PATCH 2/6] Got basic structuring working Signed-off-by: Lalit Umbarkar --- toothpick/index.html | 4 +- toothpick/toothpick.js | 94 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/toothpick/index.html b/toothpick/index.html index cedfcbe..ae8470a 100644 --- a/toothpick/index.html +++ b/toothpick/index.html @@ -40,7 +40,9 @@

-
+
+ +
diff --git a/toothpick/toothpick.js b/toothpick/toothpick.js index 5e1b5ee..6e99d5e 100644 --- a/toothpick/toothpick.js +++ b/toothpick/toothpick.js @@ -5,26 +5,100 @@ const toothpick = (function () { - const getHTMLFromGrid = () => { - return "
" + "
"; + const oliveColor = "#b5cc18", blackColor = "#1b1c1d", greyColor = "#767676", pickWidth = 1, pickHeight = 100; + let ctx, visitedPicks = [], latestVertical = [], latestHorizontal = []; + + const addEdgeToothpicks = () => { + + let halfDiff = pickHeight / 2, + previousVerticals = [...latestVertical], + previousHorizontal = [...latestHorizontal]; + latestVertical = []; + latestHorizontal = []; + while (previousVerticals.length) { + let eachPick = previousVerticals.pop(), + ex = eachPick[0], ey = eachPick[1], + abovePick = [ex, ey - halfDiff], + belowPick = [ex, ey + halfDiff]; + if (visitedPicks.indexOf(abovePick) === -1) + drawHorizontalToothpick(...abovePick); + if (visitedPicks.indexOf(belowPick) === -1) + drawHorizontalToothpick(...belowPick); + } + while (previousHorizontal.length) { + let eachPick = previousHorizontal.pop(), + ex = eachPick[0], ey = eachPick[1], + leftPick = [ex - halfDiff, ey], + rightPick = [ex + halfDiff, ey]; + if (visitedPicks.indexOf(leftPick) === -1) + drawVerticalToothpick(...leftPick); + if (visitedPicks.indexOf(rightPick) === -1) + drawVerticalToothpick(...rightPick); + } + }; + + const getColorInd = (x, y) => { + let imageData = ctx.getImageData(x, y, 1, 1).data; + return "#" + ("000000" + rgbToHex(imageData[0], imageData[1], imageData[2])).slice(-6); + } + + const rgbToHex = (r, g, b) => { + if (r > 255 || g > 255 || b > 255) + throw "Invalid color component"; + return ((r << 16) | (g << 8) | b).toString(16); + } + + const drawVerticalToothpick = (cx, cy) => { + ctx.fillStyle = oliveColor; + console.log("vc", cx, cy, pickWidth, pickHeight,); + console.log("color", getColorInd(cx - pickWidth, cy), getColorInd(cx + pickWidth, cy)); + ctx.fillRect(cx, cy - (pickHeight / 2), pickWidth, pickHeight); + latestVertical.push([cx, cy]); + visitedPicks.push([cx, cy]); }; - const resetGrid = () => { - toothpick.render(); + const drawHorizontalToothpick = (cx, cy) => { + ctx.fillStyle = greyColor; + console.log("hc", cx, cy, pickWidth, pickHeight); + console.log("color", getColorInd(cx, cy - pickWidth), getColorInd(cx, cy + pickWidth)); + ctx.fillRect(cx - (pickHeight / 2), cy, pickHeight, pickWidth); + latestHorizontal.push([cx, cy]); + visitedPicks.push([cx, cy]); + }; + + const resetAndKeepOneToothpick = () => { + if (!ctx) { + console.error("Context not setup"); + return; + } + + visitedPicks = []; + latestVertical = []; + latestHorizontal = []; + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + ctx.fillStyle = blackColor; + ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + drawVerticalToothpick(ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2); }; const registerListeners = () => { + document.getElementById("reset-grid").onclick = resetAndKeepOneToothpick; + document.getElementById("next-trigger").onclick = addEdgeToothpicks; }; return { init: () => { registerListeners(); - toothpick.render(); - }, - render: () => { - document.getElementById("root").innerHTML = getHTMLFromGrid(); - let rootEle = document.getElementsByClassName("root-grid")[0]; - rootEle.setAttribute("style", "height: " + rootEle.clientWidth + "px"); + // let rootEle = document.getElementsByClassName("root-grid")[0]; + // rootEle.setAttribute("style", "height: " + rootEle.clientWidth + "px"); + const canvas = document.getElementById("root"); + let cH = canvas.parentElement.clientHeight, cW = canvas.parentElement.clientWidth, + smallerDimension = cW > cH ? cW : cH; + canvas.setAttribute("height", smallerDimension + "px"); + canvas.setAttribute("width", smallerDimension + "px"); + if (canvas.getContext) + ctx = canvas.getContext('2d'); + resetAndKeepOneToothpick(); }, }; From 394259ce1ccef34b1a789e50efba2a33105ace5e Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Fri, 30 Oct 2020 14:18:04 +0530 Subject: [PATCH 3/6] Minor refactoring Signed-off-by: Lalit Umbarkar --- toothpick/toothpick.js | 82 ++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/toothpick/toothpick.js b/toothpick/toothpick.js index 6e99d5e..4337b7b 100644 --- a/toothpick/toothpick.js +++ b/toothpick/toothpick.js @@ -3,14 +3,47 @@ * Created On : 11/10/20 */ -const toothpick = (function () { +const Utl = (function () { const oliveColor = "#b5cc18", blackColor = "#1b1c1d", greyColor = "#767676", pickWidth = 1, pickHeight = 100; + + const rgbToHex = (r, g, b) => { + if (r > 255 || g > 255 || b > 255) + throw "Invalid color component"; + return ((r << 16) | (g << 8) | b).toString(16); + } + + return { + pickWidth, pickHeight, + getColorInd: (ctx, x, y) => { + let imageData = ctx.getImageData(x, y, 1, 1).data; + return "#" + ("000000" + rgbToHex(imageData[0], imageData[1], imageData[2])).slice(-6); + }, + clearCanvas: (ctx) => { + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + ctx.fillStyle = blackColor; + ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + }, + drawVerticalToothpick: (ctx, cx, cy, latest) => { + ctx.fillStyle = oliveColor; + ctx.fillRect(cx, cy - (pickHeight / 2), pickWidth, pickHeight); + latest.push([cx, cy]); + }, + drawHorizontalToothpick: (ctx, cx, cy, latest) => { + ctx.fillStyle = greyColor; + ctx.fillRect(cx - (pickHeight / 2), cy, pickHeight, pickWidth); + latest.push([cx, cy]); + } + } +})(); + +const toothpick = (function () { + let ctx, visitedPicks = [], latestVertical = [], latestHorizontal = []; const addEdgeToothpicks = () => { - let halfDiff = pickHeight / 2, + let halfDiff = Utl.pickHeight / 2, previousVerticals = [...latestVertical], previousHorizontal = [...latestHorizontal]; latestVertical = []; @@ -20,52 +53,25 @@ const toothpick = (function () { ex = eachPick[0], ey = eachPick[1], abovePick = [ex, ey - halfDiff], belowPick = [ex, ey + halfDiff]; + // console.log("color", cHelper.getColorInd(ctx, cx - pickWidth, cy), cHelper.getColorInd(ctx, cx + pickWidth, cy)); if (visitedPicks.indexOf(abovePick) === -1) - drawHorizontalToothpick(...abovePick); + Utl.drawHorizontalToothpick(ctx, ...abovePick, latestHorizontal); if (visitedPicks.indexOf(belowPick) === -1) - drawHorizontalToothpick(...belowPick); + Utl.drawHorizontalToothpick(ctx, ...belowPick, latestHorizontal); } while (previousHorizontal.length) { let eachPick = previousHorizontal.pop(), ex = eachPick[0], ey = eachPick[1], leftPick = [ex - halfDiff, ey], rightPick = [ex + halfDiff, ey]; + // console.log("color", cHelper.getColorInd(ctx, cx - pickWidth, cy), cHelper.getColorInd(ctx, cx + pickWidth, cy)); if (visitedPicks.indexOf(leftPick) === -1) - drawVerticalToothpick(...leftPick); + Utl.drawVerticalToothpick(ctx, ...leftPick, latestVertical); if (visitedPicks.indexOf(rightPick) === -1) - drawVerticalToothpick(...rightPick); + Utl.drawVerticalToothpick(ctx, ...rightPick, latestVertical); } }; - const getColorInd = (x, y) => { - let imageData = ctx.getImageData(x, y, 1, 1).data; - return "#" + ("000000" + rgbToHex(imageData[0], imageData[1], imageData[2])).slice(-6); - } - - const rgbToHex = (r, g, b) => { - if (r > 255 || g > 255 || b > 255) - throw "Invalid color component"; - return ((r << 16) | (g << 8) | b).toString(16); - } - - const drawVerticalToothpick = (cx, cy) => { - ctx.fillStyle = oliveColor; - console.log("vc", cx, cy, pickWidth, pickHeight,); - console.log("color", getColorInd(cx - pickWidth, cy), getColorInd(cx + pickWidth, cy)); - ctx.fillRect(cx, cy - (pickHeight / 2), pickWidth, pickHeight); - latestVertical.push([cx, cy]); - visitedPicks.push([cx, cy]); - }; - - const drawHorizontalToothpick = (cx, cy) => { - ctx.fillStyle = greyColor; - console.log("hc", cx, cy, pickWidth, pickHeight); - console.log("color", getColorInd(cx, cy - pickWidth), getColorInd(cx, cy + pickWidth)); - ctx.fillRect(cx - (pickHeight / 2), cy, pickHeight, pickWidth); - latestHorizontal.push([cx, cy]); - visitedPicks.push([cx, cy]); - }; - const resetAndKeepOneToothpick = () => { if (!ctx) { console.error("Context not setup"); @@ -75,10 +81,8 @@ const toothpick = (function () { visitedPicks = []; latestVertical = []; latestHorizontal = []; - ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); - ctx.fillStyle = blackColor; - ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); - drawVerticalToothpick(ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2); + Utl.clearCanvas(ctx); + Utl.drawVerticalToothpick(ctx, ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2, latestVertical); }; const registerListeners = () => { From 5789a6f771cab876a0ab417a6df9c13fd6b60db4 Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Fri, 30 Oct 2020 14:30:20 +0530 Subject: [PATCH 4/6] Fix bug related to duplicate toothpicks Signed-off-by: Lalit Umbarkar --- toothpick/toothpick.js | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/toothpick/toothpick.js b/toothpick/toothpick.js index 4337b7b..2f1bc15 100644 --- a/toothpick/toothpick.js +++ b/toothpick/toothpick.js @@ -5,7 +5,7 @@ const Utl = (function () { - const oliveColor = "#b5cc18", blackColor = "#1b1c1d", greyColor = "#767676", pickWidth = 1, pickHeight = 100; + const oliveColor = "#b5cc18", blackColor = "#1b1c1d", greyColor = "#767676", pickWidth = 10, pickHeight = 100; const rgbToHex = (r, g, b) => { if (r > 255 || g > 255 || b > 255) @@ -14,6 +14,7 @@ const Utl = (function () { } return { + blackColor, oliveColor, greyColor, pickWidth, pickHeight, getColorInd: (ctx, x, y) => { let imageData = ctx.getImageData(x, y, 1, 1).data; @@ -39,37 +40,34 @@ const Utl = (function () { const toothpick = (function () { - let ctx, visitedPicks = [], latestVertical = [], latestHorizontal = []; + let ctx, latestVertical = [], latestHorizontal = []; const addEdgeToothpicks = () => { let halfDiff = Utl.pickHeight / 2, - previousVerticals = [...latestVertical], - previousHorizontal = [...latestHorizontal]; - latestVertical = []; - latestHorizontal = []; - while (previousVerticals.length) { - let eachPick = previousVerticals.pop(), + upcomingVerticals = [], upcomingHorizontals = []; + while (latestVertical.length) { + let eachPick = latestVertical.pop(), ex = eachPick[0], ey = eachPick[1], abovePick = [ex, ey - halfDiff], belowPick = [ex, ey + halfDiff]; - // console.log("color", cHelper.getColorInd(ctx, cx - pickWidth, cy), cHelper.getColorInd(ctx, cx + pickWidth, cy)); - if (visitedPicks.indexOf(abovePick) === -1) - Utl.drawHorizontalToothpick(ctx, ...abovePick, latestHorizontal); - if (visitedPicks.indexOf(belowPick) === -1) - Utl.drawHorizontalToothpick(ctx, ...belowPick, latestHorizontal); + if (Utl.getColorInd(ctx, ex, ey - halfDiff - Utl.pickWidth) === Utl.blackColor) + Utl.drawHorizontalToothpick(ctx, ...abovePick, upcomingHorizontals); + if (Utl.getColorInd(ctx, ex, ey + halfDiff + Utl.pickWidth) === Utl.blackColor) + Utl.drawHorizontalToothpick(ctx, ...belowPick, upcomingHorizontals); } - while (previousHorizontal.length) { - let eachPick = previousHorizontal.pop(), + while (latestHorizontal.length) { + let eachPick = latestHorizontal.pop(), ex = eachPick[0], ey = eachPick[1], leftPick = [ex - halfDiff, ey], rightPick = [ex + halfDiff, ey]; - // console.log("color", cHelper.getColorInd(ctx, cx - pickWidth, cy), cHelper.getColorInd(ctx, cx + pickWidth, cy)); - if (visitedPicks.indexOf(leftPick) === -1) - Utl.drawVerticalToothpick(ctx, ...leftPick, latestVertical); - if (visitedPicks.indexOf(rightPick) === -1) - Utl.drawVerticalToothpick(ctx, ...rightPick, latestVertical); + if (Utl.getColorInd(ctx, ex - halfDiff - Utl.pickWidth, ey) === Utl.blackColor) + Utl.drawVerticalToothpick(ctx, ...leftPick, upcomingVerticals); + if (Utl.getColorInd(ctx, ex + halfDiff + Utl.pickWidth, ey) === Utl.blackColor) + Utl.drawVerticalToothpick(ctx, ...rightPick, upcomingVerticals); } + latestHorizontal = upcomingHorizontals; + latestVertical = upcomingVerticals; }; const resetAndKeepOneToothpick = () => { @@ -78,7 +76,6 @@ const toothpick = (function () { return; } - visitedPicks = []; latestVertical = []; latestHorizontal = []; Utl.clearCanvas(ctx); From 1340f11c66dcd62bf75f38020a8a53fff3169fcb Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Fri, 30 Oct 2020 14:38:51 +0530 Subject: [PATCH 5/6] Shows latest toothpicks in highlighted color Signed-off-by: Lalit Umbarkar --- toothpick/toothpick.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/toothpick/toothpick.js b/toothpick/toothpick.js index 2f1bc15..0faac16 100644 --- a/toothpick/toothpick.js +++ b/toothpick/toothpick.js @@ -25,13 +25,13 @@ const Utl = (function () { ctx.fillStyle = blackColor; ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); }, - drawVerticalToothpick: (ctx, cx, cy, latest) => { - ctx.fillStyle = oliveColor; + drawVerticalToothpick: (ctx, cx, cy, color, latest = []) => { + ctx.fillStyle = color; ctx.fillRect(cx, cy - (pickHeight / 2), pickWidth, pickHeight); latest.push([cx, cy]); }, - drawHorizontalToothpick: (ctx, cx, cy, latest) => { - ctx.fillStyle = greyColor; + drawHorizontalToothpick: (ctx, cx, cy, color, latest = []) => { + ctx.fillStyle = color; ctx.fillRect(cx - (pickHeight / 2), cy, pickHeight, pickWidth); latest.push([cx, cy]); } @@ -51,20 +51,22 @@ const toothpick = (function () { ex = eachPick[0], ey = eachPick[1], abovePick = [ex, ey - halfDiff], belowPick = [ex, ey + halfDiff]; + Utl.drawVerticalToothpick(ctx, ...eachPick, Utl.greyColor); if (Utl.getColorInd(ctx, ex, ey - halfDiff - Utl.pickWidth) === Utl.blackColor) - Utl.drawHorizontalToothpick(ctx, ...abovePick, upcomingHorizontals); + Utl.drawHorizontalToothpick(ctx, ...abovePick, Utl.oliveColor, upcomingHorizontals); if (Utl.getColorInd(ctx, ex, ey + halfDiff + Utl.pickWidth) === Utl.blackColor) - Utl.drawHorizontalToothpick(ctx, ...belowPick, upcomingHorizontals); + Utl.drawHorizontalToothpick(ctx, ...belowPick, Utl.oliveColor, upcomingHorizontals); } while (latestHorizontal.length) { let eachPick = latestHorizontal.pop(), ex = eachPick[0], ey = eachPick[1], leftPick = [ex - halfDiff, ey], rightPick = [ex + halfDiff, ey]; + Utl.drawHorizontalToothpick(ctx, ...eachPick, Utl.greyColor); if (Utl.getColorInd(ctx, ex - halfDiff - Utl.pickWidth, ey) === Utl.blackColor) - Utl.drawVerticalToothpick(ctx, ...leftPick, upcomingVerticals); + Utl.drawVerticalToothpick(ctx, ...leftPick, Utl.oliveColor, upcomingVerticals); if (Utl.getColorInd(ctx, ex + halfDiff + Utl.pickWidth, ey) === Utl.blackColor) - Utl.drawVerticalToothpick(ctx, ...rightPick, upcomingVerticals); + Utl.drawVerticalToothpick(ctx, ...rightPick, Utl.oliveColor, upcomingVerticals); } latestHorizontal = upcomingHorizontals; latestVertical = upcomingVerticals; @@ -76,10 +78,11 @@ const toothpick = (function () { return; } + let canvasCenter = [ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2]; latestVertical = []; latestHorizontal = []; Utl.clearCanvas(ctx); - Utl.drawVerticalToothpick(ctx, ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2, latestVertical); + Utl.drawVerticalToothpick(ctx, ...canvasCenter, Utl.oliveColor, latestVertical); }; const registerListeners = () => { From 51577cc30df068c431d8a8bdd27d49d40cdcb602 Mon Sep 17 00:00:00 2001 From: Lalit Umbarkar Date: Fri, 30 Oct 2020 14:44:28 +0530 Subject: [PATCH 6/6] Reverted change related to opening in new tab Signed-off-by: Lalit Umbarkar --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 72ab413..005bf4e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ This repository contains visualization of various [Numberphile](https://www.yout ## Projects - 1. Trapped Knight - [Video](https://www.youtube.com/watch?v=RGQe8waGJ4w){:target="_blank"} - [Illustration](./jumping-knight) - 2. Prime Spirals - [Video](https://www.youtube.com/watch?v=iFuR97YcSLM){:target="_blank"} - [Illustration](./prime-spirals) - 3. Terrific Toothpick Patterns - [Video](https://www.youtube.com/watch?v=_UtCli1SgjI){:target="_blank"} - [Illustration](./toothpick) + 1. Trapped Knight - [Video](https://www.youtube.com/watch?v=RGQe8waGJ4w) - [Illustration](./jumping-knight) + 2. Prime Spirals - [Video](https://www.youtube.com/watch?v=iFuR97YcSLM) - [Illustration](./prime-spirals) + 3. Terrific Toothpick Patterns - [Video](https://www.youtube.com/watch?v=_UtCli1SgjI) - [Illustration](./toothpick) ## Contribute