From 4b10bfdf425157bb9c59d18518f35ce176102768 Mon Sep 17 00:00:00 2001 From: Sebastian Sandqvist Date: Thu, 18 Jul 2024 18:53:39 -0700 Subject: [PATCH 1/5] set --- bun.lockb | Bin 20117 -> 20117 bytes src/menu.ts | 58 +++++++++++-------------- src/set.ts | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 34 deletions(-) create mode 100644 src/set.ts diff --git a/bun.lockb b/bun.lockb index 851eaf5c3778b36d34487836ff03aee1228d9de6..e06def0907dc395f6dd5eee90ceb36b637d25fc9 100755 GIT binary patch delta 316 zcmW;AD-42Q6hPtq!`Y;u;1gfWU@#huMj|vK(P%U}lZ8QpnT*WM%}6vFjm~5w5}n>t zzMY*oRdK2=v7uCy82;Z9*G=gRD|B0O&iF;ol#^rKk`C~OdRtD21)3c>8@!|4m9xbM z&U$io_{2qD&K_U59LPE12UoV71WQ~G7Sh2BI?j_Oo8zyh_IoDG&}*nW7f No8hb8xE}51;s?i@Q%L{- diff --git a/src/menu.ts b/src/menu.ts index c724376..108e5cb 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -1,21 +1,19 @@ -import { updateAndDraw as pongUpdateAndDraw } from "./pong"; -import { updateAndDraw as bulletHellUpdateAndDraw } from "./bullet-hell"; +import { updateAndDraw as pongUpdateAndDraw } from './pong'; +import { updateAndDraw as bulletHellUpdateAndDraw } from './bullet-hell'; +import { updateAndDraw as setUpdateAndDraw } from './set'; -const games = { - pong: pongUpdateAndDraw, - "bullet hell": bulletHellUpdateAndDraw, -}; -const gameNames = Object.keys(games); +const games = [ + ['pong', pongUpdateAndDraw], + ['bullet hell', bulletHellUpdateAndDraw], + ['set', setUpdateAndDraw], +] as const; -let game = null as null | keyof typeof games; +// let game = null as null | (typeof games)[number]; +let game = games[2] as null | (typeof games)[number]; let index = 0; let animatedIndex = 0; -export function updateAndDraw( - canvas: HTMLCanvasElement, - ctx: CanvasRenderingContext2D, - dt: number, -) { +export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, dt: number) { // UPDATE ///////////// if (!game) { @@ -41,10 +39,10 @@ export function updateAndDraw( const fontSize = 30; // draw the current game in the middle - ctx.fillStyle = "white"; + ctx.fillStyle = 'white'; ctx.font = `${fontSize}px Arial`; - ctx.textAlign = "center"; - const gameNames = Object.keys(games); + ctx.textAlign = 'center'; + const gameNames = games.map(([name]) => name); const canvasRect = canvas.getBoundingClientRect(); const numberToDrawAboveAndBelow = 4; const spacing = 10; @@ -52,21 +50,12 @@ export function updateAndDraw( const totalToDraw = numberToDrawAboveAndBelow * 2 + 1; const center = Math.floor(totalToDraw / 2); for (let i = 0; i < totalToDraw; i++) { - const gameIndex = modulusThatHandlesNegatives( - index + i - center, - gameNames.length, - ); + const gameIndex = modulusThatHandlesNegatives(index + i - center, gameNames.length); const animatedDifference = animatedIndex - index; const gameName = gameNames[gameIndex]; - const y = - canvasRect.height / 2 + - (i - center - animatedDifference) * (fontSize + spacing); + const y = canvasRect.height / 2 + (i - center - animatedDifference) * (fontSize + spacing); - const distFromCenter = Math.min( - 1, - Math.abs(i - center - animatedDifference) * - (1 / numberToDrawAboveAndBelow), - ); + const distFromCenter = Math.min(1, Math.abs(i - center - animatedDifference) * (1 / numberToDrawAboveAndBelow)); ctx.globalAlpha = 1 - distFromCenter ** 0.5; ctx.fillText(gameName, canvasRect.width / 2, y); ctx.globalAlpha = 1; @@ -74,7 +63,8 @@ export function updateAndDraw( return; } - games[game](canvas, ctx, dt); + const [_gameName, updateAndDraw] = game; + updateAndDraw(canvas, ctx, dt); } function modulusThatHandlesNegatives(n: number, m: number) { @@ -85,10 +75,10 @@ export function returnToMenu() { game = null; } -document.addEventListener("keydown", (e) => { - if (e.key === "ArrowDown") index++; - if (e.key === "ArrowUp") index--; - if (e.key === "Enter") { - game = gameNames[modulusThatHandlesNegatives(index, gameNames.length)]; +document.addEventListener('keydown', (e) => { + if (e.key === 'ArrowDown') index++; + if (e.key === 'ArrowUp') index--; + if (e.key === 'Enter') { + game = games[modulusThatHandlesNegatives(index, games.length)]; } }); diff --git a/src/set.ts b/src/set.ts new file mode 100644 index 0000000..89ffff3 --- /dev/null +++ b/src/set.ts @@ -0,0 +1,120 @@ +const gameResolution = { width: 400, height: 300 }; + +// attributes: +// - color: [red, green, blue] +// - fill: [solid, dashed, outline] +// - count: [1, 2, 3] +// - shape: [S, O, diamond] + +type Color = 'red' | 'green' | 'blue'; +type Fill = 'solid' | 'dashed' | 'outline'; +type Count = 1 | 2 | 3; + +export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, dt: number) { + // make letterboxed area + const gameArea = (() => { + const drawingRect = canvas.getBoundingClientRect(); + const drawingRectRatio = drawingRect.width / drawingRect.height; + const targetRatio = gameResolution.width / gameResolution.height; + const drawingRectWidth = drawingRectRatio > targetRatio ? drawingRect.height * targetRatio : drawingRect.width; + const drawingRectHeight = drawingRectRatio > targetRatio ? drawingRect.height : drawingRect.width / targetRatio; + return { + x: drawingRect.width / 2 - drawingRectWidth / 2, + y: drawingRect.height / 2 - drawingRectHeight / 2, + width: drawingRectWidth, + height: drawingRectHeight, + }; + })(); + + ctx.fillStyle = '#333'; + ctx.save(); + + ctx.translate(gameArea.x, gameArea.y); + ctx.scale(gameArea.width / gameResolution.width, gameArea.height / gameResolution.height); + ctx.fillRect(0, 0, gameResolution.width, gameResolution.height); + + drawO(ctx, 'red', 'solid', 3, 50); + drawO(ctx, 'blue', 'dashed', 2, 100); + drawO(ctx, 'green', 'outline', 1, 150); + + ctx.restore(); +} + +function drawS(ctx: CanvasRenderingContext2D) { + const s = { + curviness: 60, + width: 300, + height: 100, + strokeColor: 'blue', + fillColor: 'red', + }; + + ctx.beginPath(); + ctx.moveTo(s.width / 2, 0); + + ctx.bezierCurveTo( + s.width / 2 + s.curviness, + s.height / 4, + s.width / 2 - s.curviness, + (s.height / 4) * 3, + s.width / 2, + s.height, + ); + + ctx.moveTo(s.width / 2, s.height); + + ctx.bezierCurveTo( + s.width / 2 - s.curviness, + (s.height / 4) * 3, + s.width / 2 + s.curviness, + s.height / 4, + s.width / 2, + 0, + ); + + ctx.fillStyle = s.fillColor; + ctx.fill(); + + ctx.strokeStyle = s.strokeColor; + ctx.stroke(); +} + +// TODO: function to draw a card. +// then pass the card coords to the shape function. + +function drawO(ctx: CanvasRenderingContext2D, color: Color, fill: Fill, count: Count, y: number) { + const shapeWidth = 20; + const shapeHeight = 32; + for (let i = 0; i < count; i++) { + ctx.strokeStyle = color; + ctx.fillStyle = color; + ctx.beginPath(); + ctx.roundRect(20 + (shapeWidth + 10) * i, y, shapeWidth, shapeHeight, shapeHeight); + ctx.closePath(); + if (fill === 'outline') ctx.stroke(); + if (fill === 'solid') { + ctx.stroke(); + ctx.fill(); + } + if (fill === 'dashed') { + ctx.save(); + ctx.stroke(); + ctx.clip(); + const dashGap = 1.5; + const startX = 20 + (shapeWidth + 10) * i; + const endX = startX + shapeWidth; + const startY = y; + const endY = y + shapeHeight; + + for (let lineY = startY; lineY <= endY; lineY += 2 + dashGap) { + ctx.beginPath(); + ctx.moveTo(startX, lineY); + ctx.lineTo(Math.min(startX + 32, endX), lineY); + ctx.stroke(); + } + ctx.restore(); + } + } +} + +function drawDiamond() {} From 70738036967357f0fcc7bf02b957080af7e7d2da Mon Sep 17 00:00:00 2001 From: Sebastian Sandqvist Date: Thu, 18 Jul 2024 18:56:57 -0700 Subject: [PATCH 2/5] prettier --- src/menu.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/menu.ts b/src/menu.ts index 108e5cb..54b76b8 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -1,11 +1,11 @@ -import { updateAndDraw as pongUpdateAndDraw } from './pong'; -import { updateAndDraw as bulletHellUpdateAndDraw } from './bullet-hell'; -import { updateAndDraw as setUpdateAndDraw } from './set'; +import { updateAndDraw as pongUpdateAndDraw } from "./pong"; +import { updateAndDraw as bulletHellUpdateAndDraw } from "./bullet-hell"; +import { updateAndDraw as setUpdateAndDraw } from "./set"; const games = [ - ['pong', pongUpdateAndDraw], - ['bullet hell', bulletHellUpdateAndDraw], - ['set', setUpdateAndDraw], + ["pong", pongUpdateAndDraw], + ["bullet hell", bulletHellUpdateAndDraw], + ["set", setUpdateAndDraw], ] as const; // let game = null as null | (typeof games)[number]; @@ -39,9 +39,9 @@ export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingCon const fontSize = 30; // draw the current game in the middle - ctx.fillStyle = 'white'; + ctx.fillStyle = "white"; ctx.font = `${fontSize}px Arial`; - ctx.textAlign = 'center'; + ctx.textAlign = "center"; const gameNames = games.map(([name]) => name); const canvasRect = canvas.getBoundingClientRect(); const numberToDrawAboveAndBelow = 4; @@ -75,10 +75,10 @@ export function returnToMenu() { game = null; } -document.addEventListener('keydown', (e) => { - if (e.key === 'ArrowDown') index++; - if (e.key === 'ArrowUp') index--; - if (e.key === 'Enter') { +document.addEventListener("keydown", (e) => { + if (e.key === "ArrowDown") index++; + if (e.key === "ArrowUp") index--; + if (e.key === "Enter") { game = games[modulusThatHandlesNegatives(index, games.length)]; } }); From 855d52ddbd153e9db6dde91190650f035e651a15 Mon Sep 17 00:00:00 2001 From: Sebastian Sandqvist Date: Thu, 18 Jul 2024 18:57:50 -0700 Subject: [PATCH 3/5] prettier --- src/menu.ts | 21 +++++++++++++++---- src/set.ts | 59 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/menu.ts b/src/menu.ts index 54b76b8..b4faa07 100644 --- a/src/menu.ts +++ b/src/menu.ts @@ -13,7 +13,11 @@ let game = games[2] as null | (typeof games)[number]; let index = 0; let animatedIndex = 0; -export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, dt: number) { +export function updateAndDraw( + canvas: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + dt: number, +) { // UPDATE ///////////// if (!game) { @@ -50,12 +54,21 @@ export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingCon const totalToDraw = numberToDrawAboveAndBelow * 2 + 1; const center = Math.floor(totalToDraw / 2); for (let i = 0; i < totalToDraw; i++) { - const gameIndex = modulusThatHandlesNegatives(index + i - center, gameNames.length); + const gameIndex = modulusThatHandlesNegatives( + index + i - center, + gameNames.length, + ); const animatedDifference = animatedIndex - index; const gameName = gameNames[gameIndex]; - const y = canvasRect.height / 2 + (i - center - animatedDifference) * (fontSize + spacing); + const y = + canvasRect.height / 2 + + (i - center - animatedDifference) * (fontSize + spacing); - const distFromCenter = Math.min(1, Math.abs(i - center - animatedDifference) * (1 / numberToDrawAboveAndBelow)); + const distFromCenter = Math.min( + 1, + Math.abs(i - center - animatedDifference) * + (1 / numberToDrawAboveAndBelow), + ); ctx.globalAlpha = 1 - distFromCenter ** 0.5; ctx.fillText(gameName, canvasRect.width / 2, y); ctx.globalAlpha = 1; diff --git a/src/set.ts b/src/set.ts index 89ffff3..330f4f6 100644 --- a/src/set.ts +++ b/src/set.ts @@ -6,18 +6,28 @@ const gameResolution = { width: 400, height: 300 }; // - count: [1, 2, 3] // - shape: [S, O, diamond] -type Color = 'red' | 'green' | 'blue'; -type Fill = 'solid' | 'dashed' | 'outline'; +type Color = "red" | "green" | "blue"; +type Fill = "solid" | "dashed" | "outline"; type Count = 1 | 2 | 3; -export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, dt: number) { +export function updateAndDraw( + canvas: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + dt: number, +) { // make letterboxed area const gameArea = (() => { const drawingRect = canvas.getBoundingClientRect(); const drawingRectRatio = drawingRect.width / drawingRect.height; const targetRatio = gameResolution.width / gameResolution.height; - const drawingRectWidth = drawingRectRatio > targetRatio ? drawingRect.height * targetRatio : drawingRect.width; - const drawingRectHeight = drawingRectRatio > targetRatio ? drawingRect.height : drawingRect.width / targetRatio; + const drawingRectWidth = + drawingRectRatio > targetRatio + ? drawingRect.height * targetRatio + : drawingRect.width; + const drawingRectHeight = + drawingRectRatio > targetRatio + ? drawingRect.height + : drawingRect.width / targetRatio; return { x: drawingRect.width / 2 - drawingRectWidth / 2, y: drawingRect.height / 2 - drawingRectHeight / 2, @@ -26,16 +36,19 @@ export function updateAndDraw(canvas: HTMLCanvasElement, ctx: CanvasRenderingCon }; })(); - ctx.fillStyle = '#333'; + ctx.fillStyle = "#333"; ctx.save(); ctx.translate(gameArea.x, gameArea.y); - ctx.scale(gameArea.width / gameResolution.width, gameArea.height / gameResolution.height); + ctx.scale( + gameArea.width / gameResolution.width, + gameArea.height / gameResolution.height, + ); ctx.fillRect(0, 0, gameResolution.width, gameResolution.height); - drawO(ctx, 'red', 'solid', 3, 50); - drawO(ctx, 'blue', 'dashed', 2, 100); - drawO(ctx, 'green', 'outline', 1, 150); + drawO(ctx, "red", "solid", 3, 50); + drawO(ctx, "blue", "dashed", 2, 100); + drawO(ctx, "green", "outline", 1, 150); ctx.restore(); } @@ -45,8 +58,8 @@ function drawS(ctx: CanvasRenderingContext2D) { curviness: 60, width: 300, height: 100, - strokeColor: 'blue', - fillColor: 'red', + strokeColor: "blue", + fillColor: "red", }; ctx.beginPath(); @@ -82,21 +95,33 @@ function drawS(ctx: CanvasRenderingContext2D) { // TODO: function to draw a card. // then pass the card coords to the shape function. -function drawO(ctx: CanvasRenderingContext2D, color: Color, fill: Fill, count: Count, y: number) { +function drawO( + ctx: CanvasRenderingContext2D, + color: Color, + fill: Fill, + count: Count, + y: number, +) { const shapeWidth = 20; const shapeHeight = 32; for (let i = 0; i < count; i++) { ctx.strokeStyle = color; ctx.fillStyle = color; ctx.beginPath(); - ctx.roundRect(20 + (shapeWidth + 10) * i, y, shapeWidth, shapeHeight, shapeHeight); + ctx.roundRect( + 20 + (shapeWidth + 10) * i, + y, + shapeWidth, + shapeHeight, + shapeHeight, + ); ctx.closePath(); - if (fill === 'outline') ctx.stroke(); - if (fill === 'solid') { + if (fill === "outline") ctx.stroke(); + if (fill === "solid") { ctx.stroke(); ctx.fill(); } - if (fill === 'dashed') { + if (fill === "dashed") { ctx.save(); ctx.stroke(); ctx.clip(); From abe522f7757d1cf5ddba94aacd10618ecd7e9822 Mon Sep 17 00:00:00 2001 From: Sebastian Sandqvist Date: Fri, 19 Jul 2024 16:52:32 -0700 Subject: [PATCH 4/5] game logic --- src/set.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/set.ts b/src/set.ts index 330f4f6..e41a736 100644 --- a/src/set.ts +++ b/src/set.ts @@ -6,10 +6,38 @@ const gameResolution = { width: 400, height: 300 }; // - count: [1, 2, 3] // - shape: [S, O, diamond] +type Shape = "S" | "O" | "diamond"; type Color = "red" | "green" | "blue"; type Fill = "solid" | "dashed" | "outline"; type Count = 1 | 2 | 3; +type Card = { + color: Color; + fill: Fill; + count: Count; + shape: Shape; +}; + +function isSet(cards: [Card, Card, Card]) { + const allSameColor = new Set(cards.map((card) => card.color)).size === 1; + const allSameFill = new Set(cards.map((card) => card.fill)).size === 1; + const allSameCount = new Set(cards.map((card) => card.count)).size === 1; + const allSameShape = new Set(cards.map((card) => card.shape)).size === 1; + + // a set can also be made of 3 cards with all different values for a particular attribute + const allDifferentColor = new Set(cards.map((card) => card.color)).size === 3; + const allDifferentFill = new Set(cards.map((card) => card.fill)).size === 3; + const allDifferentCount = new Set(cards.map((card) => card.count)).size === 3; + const allDifferentShape = new Set(cards.map((card) => card.shape)).size === 3; + + return ( + (allSameColor || allDifferentColor) && + (allSameFill || allDifferentFill) && + (allSameCount || allDifferentCount) && + (allSameShape || allDifferentShape) + ); +} + export function updateAndDraw( canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, From 32a357929c1b64a98940510271e8f2d0600274a0 Mon Sep 17 00:00:00 2001 From: Sebastian Sandqvist Date: Fri, 19 Jul 2024 16:53:07 -0700 Subject: [PATCH 5/5] cooler version of that logic --- src/set.ts | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/set.ts b/src/set.ts index e41a736..ecbc814 100644 --- a/src/set.ts +++ b/src/set.ts @@ -19,22 +19,16 @@ type Card = { }; function isSet(cards: [Card, Card, Card]) { - const allSameColor = new Set(cards.map((card) => card.color)).size === 1; - const allSameFill = new Set(cards.map((card) => card.fill)).size === 1; - const allSameCount = new Set(cards.map((card) => card.count)).size === 1; - const allSameShape = new Set(cards.map((card) => card.shape)).size === 1; - - // a set can also be made of 3 cards with all different values for a particular attribute - const allDifferentColor = new Set(cards.map((card) => card.color)).size === 3; - const allDifferentFill = new Set(cards.map((card) => card.fill)).size === 3; - const allDifferentCount = new Set(cards.map((card) => card.count)).size === 3; - const allDifferentShape = new Set(cards.map((card) => card.shape)).size === 3; + const colors = new Set(cards.map((card) => card.color)); + const fills = new Set(cards.map((card) => card.fill)); + const counts = new Set(cards.map((card) => card.count)); + const shapes = new Set(cards.map((card) => card.shape)); return ( - (allSameColor || allDifferentColor) && - (allSameFill || allDifferentFill) && - (allSameCount || allDifferentCount) && - (allSameShape || allDifferentShape) + (colors.size === 1 || colors.size === 3) && + (fills.size === 1 || fills.size === 3) && + (counts.size === 1 || counts.size === 3) && + (shapes.size === 1 || shapes.size === 3) ); }