<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max Miracolo</title>
    <description>The latest articles on DEV Community by Max Miracolo (@mcolo).</description>
    <link>https://dev.to/mcolo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F78403%2F8baad678-5690-45c8-8a59-51f8a6141c3f.png</url>
      <title>DEV Community: Max Miracolo</title>
      <link>https://dev.to/mcolo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mcolo"/>
    <language>en</language>
    <item>
      <title>The Logic Behind Tile Swaps: Lessons from Connections and Starcrossed</title>
      <dc:creator>Max Miracolo</dc:creator>
      <pubDate>Tue, 25 Mar 2025 16:25:29 +0000</pubDate>
      <link>https://dev.to/mcolo/the-logic-behind-tile-swaps-lessons-from-connections-and-starcrossed-92n</link>
      <guid>https://dev.to/mcolo/the-logic-behind-tile-swaps-lessons-from-connections-and-starcrossed-92n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2e0euyuamskozwcigjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2e0euyuamskozwcigjq.png" alt="A screenshot of the New York Times puzzle Connections, the grid has 16 tiles, four of which are highlighted – MICE, OXEN, DICE, and LICE" width="500" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Assuming you already know how &lt;a href="https://www.nytimes.com/games/connections" rel="noopener noreferrer"&gt;Connections&lt;/a&gt; works, given the above scenario, which tiles from the top row will swap with which correctly selected tiles when the animation happens? There are a lot of options to pair them up. Will &lt;code&gt;MICE&lt;/code&gt; swap with &lt;code&gt;KINDER&lt;/code&gt;, &lt;code&gt;SINGULAR&lt;/code&gt; or &lt;code&gt;DIE&lt;/code&gt;? Will &lt;code&gt;DICE&lt;/code&gt; move, or stay put? Think about it for a second and guess, read on to see the answer.&lt;/p&gt;




&lt;p&gt;These are questions I found myself asking while playing connections one day, and it came in handy when I started building &lt;a href="https://starcrossed.today" rel="noopener noreferrer"&gt;starcrossed&lt;/a&gt;. It’s a daily movie puzzle game inspired by games like Connections, and one aspect of Connections I set out to emulate was the animation of the tile swaps. Learning how they worked was a vital step, and it turned out to be way more intriguing than I had expected. On that note, back to the initial prompt. Here’s how the animation plays out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t8dnfidxy3geic62p0u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t8dnfidxy3geic62p0u.gif" alt="An animated gif of the same Connections puzzle as before, the selected tiles OXEN, DICE, LICE, and MICE move to the top row in that order." width="500" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We end up with the following swaps: &lt;code&gt;KINDER&lt;/code&gt;↔&lt;code&gt;OXEN&lt;/code&gt;, &lt;code&gt;SINGULAR&lt;/code&gt;↔&lt;code&gt;LICE&lt;/code&gt;, and &lt;code&gt;DIE&lt;/code&gt;↔&lt;code&gt;MICE&lt;/code&gt;, and &lt;code&gt;DICE&lt;/code&gt; stayed put. Did you guess correctly? These pairs may seem arbitrary, but there is indeed a methodology for determining them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hierarchical
&lt;/h2&gt;

&lt;p&gt;Connections employs what I would call a &lt;em&gt;hierarchical&lt;/em&gt; approach. To illustrate this I’ve recreated the grid from above with the tiles labeled 1-16, and slowed down the animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffur0jwuz9g4lgop8a233.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffur0jwuz9g4lgop8a233.gif" alt="An animated gif of a four by four grid of 16 tiles, numbered 1 through 16. The grid recreates the connections grid from before, and it animates the selected tiles (2,5,8,13) moving to the top row in the order of (5,2,8,13)" width="350" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The four selected tiles &lt;code&gt;2&lt;/code&gt; &lt;em&gt;,&lt;/em&gt; &lt;code&gt;5&lt;/code&gt;&lt;em&gt;,&lt;/em&gt; &lt;code&gt;8&lt;/code&gt;&lt;em&gt;,&lt;/em&gt; and &lt;code&gt;13&lt;/code&gt; need to end up in the first row. Since &lt;code&gt;2&lt;/code&gt; is already in the first row, that leaves &lt;code&gt;1&lt;/code&gt;&lt;em&gt;,&lt;/em&gt; &lt;code&gt;3&lt;/code&gt;, and &lt;code&gt;4&lt;/code&gt; to swap. There are six different combinations for pairing these six tiles, and if the fourth tile also needs to be swapped then that number jumps to 24 combinations. Therefore a decision needs to be made, or a rule needs to be established to decide how the pairs are formed. In the case of Connections, the tiles are swapped based on their ordering, or hierarchy. Tiles higher up, and to the left, take precedence. Or referencing our new grid, tiles with lower numbers get paired up first. This hierarchy becomes even clearer when the tiles are arranged in a single row.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7q5lgqaji9l5gx0k5h2n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7q5lgqaji9l5gx0k5h2n.gif" alt="An animated gif of a a single row of 16 tiles, numbered 1 through 16. The animation sequentially highlights the pairs of tiles that will be swapped." width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply move from left to right to determine the order for selecting pairs. First &lt;code&gt;1&lt;/code&gt;↔&lt;code&gt;5&lt;/code&gt;, then &lt;code&gt;3&lt;/code&gt;↔&lt;code&gt;8&lt;/code&gt;, and finally &lt;code&gt;4&lt;/code&gt;↔&lt;code&gt;13&lt;/code&gt;. Again, &lt;code&gt;2&lt;/code&gt; stays put because it’s already one of the first four tiles.&lt;/p&gt;

&lt;p&gt;While this approach certainly makes sense, it isn’t the only method to determine which tiles swap. One potential drawback of the hierarchical approach is that there may be more commotion, or a busier animation due to tiles moving farther than they need to. This brings us to the next approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nearest Neighbor
&lt;/h2&gt;

&lt;p&gt;The goal here is to have the tiles move as little distance as possible, and coincidentally, overlap as little as possible. As you can see, with the same selected tiles from the previous examples, there’s less movement in this animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhz43ekjavkb15pztqddn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhz43ekjavkb15pztqddn.gif" alt="An animated gif of a four by four grid of 16 tiles, numbered 1 through 16. The grid recreates the connections grid from before, and it animates the selected tiles (2,5,8,13) moving to the top row in the order of (5,2,13,8)" width="350" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, it happens to be quite close to the hierarchical approach, but instead of &lt;code&gt;3&lt;/code&gt;↔&lt;code&gt;8&lt;/code&gt; swapping, &lt;code&gt;3&lt;/code&gt;↔&lt;code&gt;13&lt;/code&gt; swap because &lt;code&gt;4&lt;/code&gt;↔&lt;code&gt;8&lt;/code&gt; are closer together. However, in some cases the difference can be quite dramatic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkiqix62xvh92tik4mk9z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkiqix62xvh92tik4mk9z.gif" alt="An animated gif of two four by four grids of 16 tiles, numbered 1 through 16. The grid on the left animates the selected tiles (8,11,13,14) moving to the top row in the order of (8,11,13,14). The grid on the right animates the selected tiles (8,11,13,14) moving to the top row in the order of (13,14,11,8)" width="650" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nearest neighbor is a more subtle approach and I’m even contemplating updating starcrossed to use it. For now, starcrossed will keep its current approach, which I will call &lt;em&gt;user-driven.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  User-Driven
&lt;/h2&gt;

&lt;p&gt;If you think about it, an order has already been established before the swap occurs. We can follow the sequence in which the user selected the tiles, and then have that be the order for how the tiles should land in the top row. Below is a visualization of this approach, again using the same selected tiles as the previous examples, now assuming the user selected the tiles in a specific order: &lt;code&gt;2&lt;/code&gt; → &lt;code&gt;13&lt;/code&gt; → &lt;code&gt;8&lt;/code&gt; → &lt;code&gt;5&lt;/code&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0uu5d69fkv7hdak4hww.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0uu5d69fkv7hdak4hww.gif" alt="An animated gif of a four by four grid of 16 tiles, numbered 1 through 16. The grid recreates the connections grid from before, and it animates the selected tiles (2,5,8,13) moving to the top row in the order of (2,13,8,5)" width="350" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where nearest neighbor dialed back the commotion from the hierarchical approach, this can result in &lt;em&gt;more&lt;/em&gt; commotion, which may be desirable! This is the approach I chose for starcrossed, with a caveat: no tiles are swapped. The tiles in the first row remain in place and are covered by the user-selected tiles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuwwesl56a5hjzmvibd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuwwesl56a5hjzmvibd.gif" alt="An animated gif of a four by four grid of 16 tiles, numbered 1 through 16. The grid recreates the connections grid from before, and it animates the selected tiles (2,5,8,13) moving to the top row in the order of (5,2,13,8). This time no tiles are swapped." width="350" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a look at this approach in action with starcrossed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1742323592521%2F3960fc3a-d3c8-4b16-9c48-813b005358c9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1742323592521%2F3960fc3a-d3c8-4b16-9c48-813b005358c9.webp" alt="An animated gif of the puzzle game starcrossed. Four headshots are selected and move to the top row, and then the category is revealed." width="466" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, the tiles were selected in the following order:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Hugh Jackman&lt;/code&gt; → &lt;code&gt;Russell Crowe&lt;/code&gt; → &lt;code&gt;Anne Hathaway&lt;/code&gt; → &lt;code&gt;Amanda Seyfried&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One reason I chose this approach was to create consistency. The tiles selected by the user maintain this order as they move to the top row, and that order is maintained again when the names are listed in the revealed category (green box). I also found that by not swapping the tiles with the user-driven approach it was a happy medium in terms of animation busyness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embrace the Chaos
&lt;/h2&gt;

&lt;p&gt;A fourth option would be for the animation to have as much commotion as possible. Having more or less commotion isn’t strictly a good or bad thing. It largely depends on the use case — perhaps more commotion is desired to evoke a feeling of high energy and excitement.&lt;/p&gt;

&lt;p&gt;In my mind, there are essentially two ways to implement this more chaotic approach. One would be to find the opposite of the nearest neighbor (farthest neighbor), and the other would be to choose pairs at random each time the animation will run. Of course, choosing pairs at random could result in the same combination as nearest neighbor, but the randomness between each round of swaps could still evoke a feeling of novelty and excitement.&lt;/p&gt;




&lt;p&gt;Each approach has it’s pros and cons and choosing one ultimately depends on the experience you want to create. Whether it’s the structured logic of the hierarchical method, the subtle efficiency of nearest neighbor, the personalized feel of user-driven swaps, or the lively unpredictability of a chaotic approach, each method brings something unique. For &lt;a href="https://starcrossed.today" rel="noopener noreferrer"&gt;starcrossed&lt;/a&gt;, I opted for consistency and excitement, but experimenting with different styles has been a fascinating process. If nothing else, I hope this overview has given you a new appreciation for the small details that shape how puzzles come to life on screen.&lt;/p&gt;

</description>
      <category>design</category>
      <category>ux</category>
      <category>ui</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built an Affordable, Efficient Architecture for a Daily Movie Puzzle Game</title>
      <dc:creator>Max Miracolo</dc:creator>
      <pubDate>Wed, 18 Dec 2024 16:14:30 +0000</pubDate>
      <link>https://dev.to/mcolo/how-i-built-an-affordable-efficient-architecture-for-a-daily-movie-puzzle-game-4gpm</link>
      <guid>https://dev.to/mcolo/how-i-built-an-affordable-efficient-architecture-for-a-daily-movie-puzzle-game-4gpm</guid>
      <description>&lt;p&gt;I’ve been building a daily movie puzzle game called &lt;a href="https://starcrossed.today" rel="noopener noreferrer"&gt;starcrossed&lt;/a&gt;, and I’ve managed to set it up and maintain it at essentially no cost. We’re currently seeing about 500 daily users and growing. While things might change if traffic were to surge exponentially, it’s been cool to see how affordable creating and maintaining a project like this can be.&lt;/p&gt;

&lt;p&gt;As a software engineer today, it’s easy to feel overwhelmed by the ever-growing selection of tools and technologies. But we’re also fortunate enough to have a lot of free or low-cost services that make projects like this possible.&lt;/p&gt;

&lt;p&gt;Here’s a list of most of the tools I’ve used to build starcrossed thus far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Vercel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS S3, Lambda, API Gateway, and Cloudwatch&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloudflare&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web API’s for Local Storage and IndexedDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NextJS, TailwindCSS, Iconmonstr&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TMDB&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Vercel&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I’ve come to greatly appreciate Vercel, their free Hobby plan is an excellent starting point—but don’t let the plan name deceive you, it comes with a fairly robust set of features. Check out their &lt;a href="https://vercel.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; to see more.&lt;/p&gt;

&lt;p&gt;When you create a project Vercel provides you with a free domain (e.g., &lt;code&gt;*.vercel.app&lt;/code&gt;), but you can easily bring your own domain if you prefer. I purchased mine through Porkbun for only $3 (the most expensive purchase of this project yet).&lt;/p&gt;

&lt;p&gt;While this isn’t an exhaustive review of Vercel, I’m sure there are plenty of those already, I will mention a few of my favorite features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployments and Instant Rollbacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you connect your Github project to Vercel, merges and pushes into your main branch will trigger an automatic production deployment. When you create a pull request, that branch gets its own designated preview URL (limited to one branch URL on hobby). I’ve used this feature frequently to test pull requests by sharing the branch-specific URL with my QA team (read: my friends).&lt;/p&gt;

&lt;p&gt;While this alone is powerful, the ability to initiate instant rollbacks may be even more valuable. If you merge a problematic pull request, you can roll back to a previous deployment instantly with just a couple of clicks. Not that I would know anything about needing to do that… 😅.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4wwscui6nt77fxu3zj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4wwscui6nt77fxu3zj8.png" alt="A screenshot of the Vercel deployments page, showing a list of recent Production deployments. There is a pop-out menu opened for one of the deployments showing the Instant Rollback option." width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With just a couple lines of code, you can have a no-cost analytics tool that doesn’t rely on cookies. You get 2500 events per month on the free plan, which when starting out is pretty handy. I haven’t upgraded to Pro for the 25k events per month because I’ve found a workaround with Cloudflare’s analytics tools (more details below in the Cloudflare section).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Vercel offers flexible &lt;a href="https://vercel.com/docs/infrastructure/data-cache" rel="noopener noreferrer"&gt;cache management&lt;/a&gt; and it becomes even more powerful when paired with a framework like NextJS. For starcrossed, the caching strategy is rather simple—the puzzle file fetch response is cached after the first request. Each puzzle is stored as a single JSON file, and since the same file is used by everyone for an entire day, it can be cached immediately. This approach not only improves performance but also keeps my AWS bill at zero almost every month since so few requests actually make it to AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;AWS&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Speaking of AWS, below is a diagram of the simple architecture in use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi1u4ly5zze4nqml5iey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi1u4ly5zze4nqml5iey.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;API Gateway handles requests to pull the JSON puzzle files that are stored in the S3 bucket. I followed &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-s3.html" rel="noopener noreferrer"&gt;this pattern&lt;/a&gt; provided by AWS. To ensure requests are legitimate, I use a Lambda Authorizer function. The S3 bucket is setup with replication and fail-over in us-west-2. The API gateway is configured with two stages: &lt;strong&gt;dev&lt;/strong&gt; and &lt;strong&gt;prod&lt;/strong&gt;. Cloudwatch log groups are set up for both the lambda function, and the two API Gateway stages.&lt;/p&gt;

&lt;p&gt;The only month I’ve incurred any charges was September, and it was only $0.14:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;$0.12 for API Gateway&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;$0.02 for S3&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have yet to dive into why this was the only month I was charged, so I unfortunately can’t provide any more details at this time. I also have a billing alarm set to prevent my account from blowing up, so I haven’t been in a hurry to investigate it any further.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cloudflare&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I chose to use Cloudflare as my DNS provider primarily for its free analytics tooling. Curiously, the traffic numbers reported are slightly higher than Vercel, and sometimes the numbers on Cloudflare don’t add up correctly (see screenshots below). I’m not concerned about traffic on a granular level so I haven’t been bothered enough to do more research. Besides, the numbers are still useful for viewing overall trends.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25fetz7b3wuiha59pppy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F25fetz7b3wuiha59pppy.png" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a look at our 30 day unique visitor chart. The Total Unique Visitors is stated as 10.76k, however if you add up each day individually you get 15.94k, quite a large discrepancy. Let me know if you have insight as to why the numbers differ.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6h0jl8kj4190b9q48ro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft6h0jl8kj4190b9q48ro.png" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same thing with the 7 day chart, if you add up these 7 values you get 3.81k. Also, on Thursday December 12th we had a spike in traffic, Cloudflare lists it as 765 unique visitors from 7PM on the 11th to 7PM on the 12th. Vercel, for that same time frame counts up to 709. Not worrying, but an interesting observation.(I made sure to check that both services were using the same time zone, which they are).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Local Storage and IndexedDB Web API’s&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To avoid the complexity and cost of setting up user authorization and a database, I opted to make use of existing web API’s to handle much of the same functionality. Of course the primary drawback is that our users’ data is more vulnerable to permanent deletion (accidental or not).&lt;/p&gt;

&lt;p&gt;Local Storage is used to store data for in-progress games—such as your previous guesses, found answers, and currently selected tiles. I also chose to use IndexedDB to allow for a clear separation of intent: Local Storage stores in-progress game data, while IndexedDB stores stats and data for previously completed games. Plus IndexedDB has a significantly larger storage capacity than Local Storage. If starcrossed continues to grow and the game sticks around for a while, then the data could potentially pile up for our more active users. Futhermore, having long-term data stored in IndexedDB will facilitate incorporating upcoming features like lifetime statistics and a puzzle archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;NextJS, TailwindCSS, Iconmonstr&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are a lot of options for frameworks, but I chose familiar ones that enable me to build quicker. Also they’re each free, and well made, so I had to give them a shout-out.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;TMDB&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Not a software or architecture related tool, but TMDB (The Movie Database) is a community-run alternative to IMDb that is free for non-commercial purposes. They have an extroadinarily large data set of Actors, Movies, and everything in between.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;In conclusion…&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Building a daily movie puzzle game like starcrossed has been an exciting challenge, and using low-cost solutions for the architecture has allowed me to keep things simple and affordable. By leveraging free tools like Vercel for hosting, Cloudflare for DNS and analytics, AWS for API requests and storage, and standard Web API’s I've been able to create a reliable, efficient system while barely spending a dime.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vercel</category>
      <category>aws</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
