<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged gamedevelopment</title>
<link>https://coderlegion.com/tag/gamedevelopment</link>
<description>Powered by Question2Answer</description>
<item>
<title>Rend Infestation Devlog #3 - UX is hard</title>
<link>https://coderlegion.com/33212/rend-infestation-devlog-3-ux-is-hard</link>
<description>&lt;p&gt;The main focus of this devlog was to show the new UX flow for weapon modifications.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/33212/rend-infestation-devlog-3-ux-is-hard</guid>
<pubDate>Sat, 31 Oct 2026 17:00:00 +0000</pubDate>
</item>
<item>
<title>Rend Infestation Devlog #2 - Death by thousand papercuts</title>
<link>https://coderlegion.com/32002/rend-infestation-devlog-2-death-by-thousand-papercuts</link>
<description>&lt;p&gt;Second status update on my upcoming game Rend Infestation, slowly getting back into the rhythm of development.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/32002/rend-infestation-devlog-2-death-by-thousand-papercuts</guid>
<pubDate>Wed, 21 Oct 2026 20:54:31 +0000</pubDate>
</item>
<item>
<title>Landscape Layout &amp; Terracing | UE5 Devlog</title>
<link>https://coderlegion.com/30519/landscape-layout-terracing-ue5-devlog</link>
<description>&lt;p&gt;In this devlog, I showcase the landscape creation and terracing process for Kazuhara resort in Unreal Engine 5. Using Landscape Mode’s Sculpt, Flatten, and Smooth tools, I shape the terrain, create distinct terraces, and soften the transitions between elevations.&lt;/p&gt;
&lt;p&gt;The focus is on building a foundation for the resort, balancing usable flat areas with the natural slopes of the surrounding landscape. It’s a look at the work behind shaping the environment before the finer details bring it to life.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/30519/landscape-layout-terracing-ue5-devlog</guid>
<pubDate>Fri, 09 Oct 2026 20:47:09 +0000</pubDate>
</item>
<item>
<title>Rend Infestation Devlog #1 - A new start</title>
<link>https://coderlegion.com/29764/rend-infestation-devlog-1-a-new-start</link>
<description>&lt;p&gt;Getting back into game development by resuming works on a project I started earlier this year - a top-down twin-stick shooter called Rend Infestation.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/29764/rend-infestation-devlog-1-a-new-start</guid>
<pubDate>Sun, 04 Oct 2026 21:12:49 +0000</pubDate>
</item>
<item>
<title>Sprint &amp; Stamina | UE5 Devlog</title>
<link>https://coderlegion.com/29680/sprint-stamina-ue5-devlog</link>
<description>&lt;p&gt;This devlog showcases my Blueprint-based sprint and stamina system and its integration with the player’s health and thirst.&lt;/p&gt;
&lt;p&gt;Hydration decreases at a normal rate while walking. Sprinting consumes stamina and accelerates hydration loss, giving faster movement an additional resource cost. Once stamina is depleted, the extra hydration drain from sprinting stops, while the normal drain continues. If hydration reaches zero, the player begins losing health.&lt;/p&gt;
&lt;p&gt;These connected systems make resource management part of traversal. Players need to balance speed with their physical condition, using drinks to restore hydration and medkits to recover health. The UI tracks each stat and highlights critical levels, providing clear feedback as resources run low.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/29680/sprint-stamina-ue5-devlog</guid>
<pubDate>Sun, 04 Oct 2026 02:01:31 +0000</pubDate>
</item>
<item>
<title>DevLog Life of Alex - Inventory, Interaction, Pick-up, Use and Drop Systems</title>
<link>https://coderlegion.com/28929/devlog-life-of-alex-inventory-interaction-pick-up-use-and-drop-systems</link>
<description>&lt;p&gt;Hi everyone, and welcome!&lt;/p&gt;
&lt;p&gt;Today I want to give you a first look at one of the systems I’ve been working on for my game: the inventory system.&lt;/p&gt;
&lt;p&gt;The game is still very early in development, so everything you’re about to see is a work in progress and will continue to change as the project grows.&lt;/p&gt;
&lt;p&gt;For this showcase, I’ll walk you through how the inventory currently works, from picking up and storing items to selecting them, viewing their information, and interacting with them.&lt;/p&gt;
&lt;p&gt;I’ll also show you some of the systems connected to the inventory, including consumable items and player stats.&lt;/p&gt;
&lt;p&gt;There’s still plenty I want to improve and expand, especially when it comes to the visuals and overall presentation, but the core functionality is already taking shape.&lt;/p&gt;
&lt;p&gt;So, with that said, let’s jump into the game and take a look at the inventory system.&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/28929/devlog-life-of-alex-inventory-interaction-pick-up-use-and-drop-systems</guid>
<pubDate>Mon, 28 Sep 2026 13:07:31 +0000</pubDate>
</item>
<item>
<title>Building a Procedural Murder-Mystery Game That Still Has to Make Sense</title>
<link>https://coderlegion.com/27786/building-a-procedural-murder-mystery-game-that-still-has-to-make-sense</link>
<description>&lt;p&gt;&lt;strong&gt;Building Casefile: Sixty Seconds — From Murder-Mystery Prototype to Launch Sprint&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve been spending a lot of my development time lately on a new Vasken Interactive Studios project called Casefile: Sixty Seconds, and it’s finally starting to feel less like a prototype and more like an actual game.&lt;/p&gt;
&lt;p&gt;The basic idea is a procedural mystery game built around a simple loop:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Briefing → Investigation → Accusation → Results&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Players examine suspects and evidence, look for contradictions, and decide who is responsible. The goal was to make cases short enough to play in a few minutes without turning the deduction itself into guesswork.&lt;/p&gt;
&lt;p&gt;One of the biggest development challenges has been procedural generation. Randomizing names, suspects, evidence, motives, and relationships is easy enough. Randomizing them while still producing a case that is logically solvable is another matter entirely.&lt;/p&gt;
&lt;p&gt;So the generator now performs structural validation before a case reaches the player. I recently ran 1,000 generated-case tests, and all 1,000 passed validation. That was one of those development milestones that doesn’t look flashy in a screenshot, but it felt pretty damn good.&lt;/p&gt;
&lt;p&gt;The current build includes:&lt;/p&gt;
&lt;p&gt;Three handcrafted Pinecrest cases alongside procedural Dispatch cases&lt;br&gt;
Six difficulty levels, from Rookie through Black File&lt;br&gt;
XP, streaks, case archives, personal scores, and Clue Tokens&lt;br&gt;
Daily Case and leaderboard-oriented competition systems&lt;br&gt;
A Case Rush mode where the clock actually becomes part of the pressure&lt;br&gt;
A Supply Desk with a small launch-focused purchase catalog rather than a giant monetization ladder&lt;br&gt;
A progression system that lets players unlock higher difficulty tiers through play, with an optional Investigator Access Pack&lt;/p&gt;
&lt;p&gt;The monetization philosophy has been another interesting design problem. I don’t want the game built around constantly interrupting the player or selling them the answer. The current plan is limited to optional rewarded ads, controlled interstitials between cases, Remove Ads, a small Clue Token pack, and the $9.99 Investigator Access Pack.&lt;/p&gt;
&lt;p&gt;Most of the core game systems are now working. The remaining launch work is the less glamorous but absolutely necessary stuff: production art, tutorial polish, audio and feedback, mobile layout cleanup, Android packaging, Google Play Billing, leaderboards, privacy/compliance work, and final QA.&lt;/p&gt;
&lt;p&gt;That means I’m currently in the dangerous stage of development where the game is functional enough to make you think, “I’m almost done,” while the launch checklist quietly laughs at you from the corner.&lt;/p&gt;
&lt;p&gt;Still, seeing the investigation loop, procedural generation, progression, scoring, and case systems finally working together has been a great milestone.&lt;/p&gt;
&lt;p&gt;Casefile: Sixty Seconds started as a fairly small idea: three suspects, a handful of clues, one contradiction, make the accusation.&lt;/p&gt;
&lt;p&gt;It has turned into a considerably deeper system than I originally expected.&lt;/p&gt;
&lt;p&gt;And apparently I have not yet learned my lesson about the phrase “This should be a simple app.”&lt;/p&gt;
&lt;p&gt;— Wendell Wilhelm&lt;br&gt;
Founder, Vasken Interactive Studios LLC&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27786/building-a-procedural-murder-mystery-game-that-still-has-to-make-sense</guid>
<pubDate>Sun, 20 Sep 2026 03:50:17 +0000</pubDate>
</item>
<item>
<title>JavaScript Essentials: How to Use .includes() for Array Searches</title>
<link>https://coderlegion.com/6682/javascript-essentials-how-to-use-includes-for-array-searches</link>
<description>&lt;h5 id=&quot;in-this-article-we-explore-javascript-s-includes-method-for-searching-arrays-including-practical-implementations-in-game-development-you-ll-learn-coding-techniques-to-enhance-your-own-projects-&quot;&gt;In this article, we explore JavaScript's .includes() method for searching arrays, including practical implementations in game development. You'll learn coding techniques to enhance your own projects!&lt;/h5&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1760978852671/998b929e-2e5a-4a05-a78b-ad394424dfc5.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-includes-&quot;&gt;.includes()&lt;/h4&gt;
&lt;p&gt;What is .includes()? &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes&quot;&gt;Array.prototype.includes()&lt;/a&gt; is a built-in JavaScript array method that checks if an array contains a specific element. It returns true if the element is found, and false otherwise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Syntax:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;element:&lt;/strong&gt; The value to search for.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;f&lt;strong&gt;romIndex (optional)&lt;/strong&gt;: The position in the array to start searching.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;array.includes(element, fromIndex)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-you-can-do-with-includes-&quot;&gt;What You Can Do With .includes()&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;.includes()&lt;/code&gt; method is excellent for checking if an array contains a specific value—it even works with emojis! For a simple demonstration, here’s how I use &lt;code&gt;.includes()&lt;/code&gt; in my Pac-Man project to check for particular fruits. As you can see, the apple returns &lt;code&gt;true&lt;/code&gt; because it is included in the array (in fact, there are two apples), and the watermelon returns &lt;code&gt;false&lt;/code&gt; because it is not in the &lt;code&gt;fruitBonus&lt;/code&gt; array.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Example from my Pac-Man project
const fruitBonus = ['','','','','','','','','','',''];

console.log(fruitBonus.includes('')); // true
console.log(fruitBonus.includes('')); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;⚠ &lt;strong&gt;Important:&lt;/strong&gt; There are limitations to using &lt;code&gt;.includes()&lt;/code&gt;. While it works perfectly for primitive values like numbers, strings, and emojis, it does not perform deep checks for nested arrays or objects. Using &lt;code&gt;.includes()&lt;/code&gt; with arrays or objects will only return &lt;code&gt;true&lt;/code&gt; if the reference matches exactly—not if the contents are the same. You’ll need a different approach for deep comparisons, such as using &lt;code&gt;.some()&lt;/code&gt; with a custom comparison function.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;putting-includes-to-good-use&quot;&gt;Putting .includes() to Good Use&lt;/h4&gt;
&lt;p&gt;I successfully used &lt;code&gt;.includes()&lt;/code&gt; to improve ghost movement in my JavaScript Pac-Man game! I created an array containing all the intersection indices on the game board, and used &lt;code&gt;.includes()&lt;/code&gt; to detect when a ghost enters an intersection. This allows the ghost to change direction at the correct spots, just like in the original arcade game.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The intersectionIndices array:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const intersectionIndices = [
    34, 49,
    141, 146, 149, 152, 155, 158, 161, 166,
    230, 245,
    320, 323,
    377, 378,
    398, 401, 410, 413,
    485, 494,
    566, 569, 578, 581,
    650, 653, 656, 659, 662, 665,
    731, 752,
    824, 827
];
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;adding-the-intersections&quot;&gt;Adding the Intersections&lt;/h4&gt;
&lt;p&gt;During board construction, squares whose index is listed in &lt;code&gt;intersectionIndices&lt;/code&gt; are assigned the 'intersection' class. This visually and logically marks those squares as intersections for game logic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;
export function buildTheBoard() {
  for(let i = 0; i &amp;lt; 868; i++) {
    let div = document.createElement(&quot;div&quot;);
    gameGrid.appendChild(div);  
    squares.push(div);
    squares[i].classList.add('game-board-square');  

    if (intersectionIndices.includes(i)) {
      squares[i].classList.add('intersection');
    }    
  }    
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Below, you can see every intersection included in the &lt;code&gt;intersectionIndices&lt;/code&gt; array, courtesy of the ‘intersection‘ class:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1760563986547/b889272b-569e-4063-8b6c-f2a7e635e9f1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;helper-intersection-function&quot;&gt;Helper Intersection function&lt;/h4&gt;
&lt;p&gt;Here is a simple helper function, used for game logic, that takes an index as a parameter, uses &lt;code&gt;.includes()&lt;/code&gt;, and returns &lt;code&gt;true&lt;/code&gt; if the specified index is an intersection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Helper function to check if a given index is an intersection
function isIntersection(index) {
  return intersectionIndices.includes(index);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;improved-ghost-movement-using-includes-&quot;&gt;Improved Ghost Movement using .includes()&lt;/h4&gt;
&lt;p&gt;Now that I have a function that detects intersections, I can use it to see if a ghost’s current index includes one. Once a ghost enters an intersection, I trigger a direction change—making the ghosts smarter!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How It Works: Each ghost moves automatically on a timer. At every step, the code checks if the ghost is on an intersection square (using isIntersection and .includes()). If the ghost is at an intersection:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The code figures out which directions are possible (not blocked by walls or other ghosts).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It avoids making the ghost immediately turn around (no U-turns unless stuck).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It randomly picks one of the valid directions and updates the ghost’s movement.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Ghost movement logic uses isIntersection to trigger intersection behavior
export function moveGhost(ghost) {
  const directions = [-1, 1, 28, -28];
  let direction = ghost.direction || directions[Math.floor(Math.random() * directions.length)];
  
  ghost.timerId = setInterval(function() {
    // ...existing movement logic...

    // At intersections, choose a new direction
    if (isIntersection(ghost.currentIndex)) {
      const reverseDir = ghost.previousIndex - ghost.currentIndex;
      let validDirections = directions.filter(dir =&amp;gt; {
        if (dir === reverseDir) return false;
        const nextIndex = ghost.currentIndex + dir;
        return (
          !squares[nextIndex].classList.contains('ghost') &amp;amp;&amp;amp;
          !squares[nextIndex].classList.contains('lairText') &amp;amp;&amp;amp;
          !squares[nextIndex].classList.contains('wall') &amp;amp;&amp;amp;
          !(dir === 28 &amp;amp;&amp;amp; squares[nextIndex].classList.contains('lairWall')) &amp;amp;&amp;amp;
          (nextIndex !== 375) &amp;amp;&amp;amp;
          (nextIndex !== 380)
        );
      });

      if (validDirections.length === 0) {
        validDirections = [reverseDir];
      }
      
      if (validDirections.length &amp;gt; 0) {
        direction = validDirections[Math.floor(Math.random() * validDirections.length)];
        ghost.direction = direction;
      }
    }

    // ...rest of movement logic...
  }, ghost.speed);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;By checking for intersections with JavaScript’s .includes(), I ensure ghosts only change direction at the right spots. This brings my project closer to the original arcade experience and creates more authentic, challenging gameplay!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1760980644997/fe2c9dee-0b78-4e55-8d1c-92302a40bf34.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;js-pac-man-project-links-&quot;&gt;JS Pac-Man Project Links:&lt;/h4&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pac-man-javascript-laroccade.netlify.app/&quot;&gt;Link to the deployed project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MichaelLarocca/pac-man-javascript-laroccade&quot;&gt;Link to GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/users/MichaelLarocca/projects/1&quot;&gt;Link to the sprint board&lt;/a&gt;&lt;/p&gt;
&lt;h6 id=&quot;note-since-i-m-following-team-development-practices-and-pushing-updates-to-the-develop-branch-some-of-the-changes-i-describe-in-this-article-may-not-be-live-in-the-deployed-version-of-the-game-yet-&quot;&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Since I’m following team development practices and pushing updates to the develop branch, some of the changes I describe in this article may not be live in the deployed version of the game yet.&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;my-other-related-articles&quot;&gt;My other related articles&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/10-25/javascript-array-iteration-and-dom-manipulation-in-game-development/&quot;&gt;JavaScript: Array Iteration and DOM Manipulation in Game Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/10-25/javascript-game-timing-how-independent-intervals-can-cause-missed-collisions/&quot;&gt;JavaScript Game Timing: How Independent Intervals Can Cause Missed Collisions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/09-25/what-is-state-in-javascript-and-how-to-use-it-in-your-projects/&quot;&gt;What Is “State” in JavaScript, and How to Use It in Your Projects&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/08-25/mastering-javascript-the-power-of-foreach-and-ternary-operators/&quot;&gt;Mastering JavaScript: The Power of forEach and Ternary Operators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/06-25/javascript-understanding-the-set-and-clear-interval-methods/&quot;&gt;JavaScript: Understanding the Set and Clear Interval Methods&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168177634/1ff578a9-5d03-4800-9c0c-9092e4e60c4f.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;be-sure-to-listen-to-the-html-all-the-things-podcast-&quot;&gt;&lt;strong&gt;Be sure to listen to the HTML All The Things Podcast!&lt;/strong&gt;&lt;/h4&gt;
&lt;h5 id=&quot;i-also-write-articles-for-the-html-all-the-things-podcast-which-you-can-read-on-their-website-https-www-htmlallthethings-com-&quot;&gt;&lt;em&gt;I also write articles for the HTML All The Things Podcast, which you can read on their website:&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.htmlallthethings.com/&quot;&gt;&lt;em&gt;https://www.htmlallthethings.com/&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/h5&gt;
&lt;h5 id=&quot;be-sure-to-check-out-html-all-the-things-on-socials-&quot;&gt;&lt;strong&gt;Be sure to check out HTML All The Things on socials!&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/htmleverything&quot;&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/company/html-all-the-things/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tiktok.com/@htmlallthethings&quot;&gt;TikTok&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/htmlallthethings/&quot;&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;affiliate-amp-discount-links-&quot;&gt;Affiliate &amp;amp; Discount Links!&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168473785/c85789c9-1594-4000-aa2f-43e842f9825f.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With CodeMonkey, learning can be all fun and games!&lt;/strong&gt; CodeMonkey transforms education into an engaging experience, enabling children to evolve from tech consumers to creators. Use CodeMonkey's &lt;strong&gt;FREE trial&lt;/strong&gt; to unlock the incredible potential of young tech creators!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;With a structured learning path tailored for various age groups, kids progress from block coding to more advanced topics like data science and artificial intelligence, using languages such as CoffeeScript and Python. The platform includes features for parents and teachers to track progress, making integrating coding into home and classroom settings easy.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Through fun games, hands-on projects, and community interaction, CodeMonkey helps young learners build teamwork skills and receive recognition for their achievements. It fosters a love for coding and prepares children for future career opportunities in an ever-evolving tech landscape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;To learn more about CodeMonkey, you can read my detailed&lt;/em&gt;&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/02-25/inspiring-young-coders-how-codemonkey-turns-kids-into-tech-creators/&quot;&gt;&lt;strong&gt;&lt;em&gt;review article&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&lt;em&gt;!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affiliate Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919057/12259&quot;&gt;Sign Up for Parents&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919060/12259&quot;&gt;Sign Up for Teachers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740169191630/ac8c5360-d1c5-4c85-8504-18b9c755841e.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;advance-your-career-with-a-20-discount-on-scrimba-pro-using-this-affiliate-link-&quot;&gt;Advance your career with a 20% discount on Scrimba Pro using this &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;affiliate link&lt;/a&gt;!&lt;/h4&gt;
&lt;p&gt;Become a hireable developer with Scrimba Pro! Discover a world of coding knowledge with full access to all courses, hands-on projects, and a vibrant community. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2021/06-21/06-07-21/&quot;&gt;read my article&lt;/a&gt; to learn more about my exceptional experiences with Scrimba and how it helps many become confident, well-prepared web developers!&lt;/p&gt;
&lt;h6 id=&quot;important-this-discount-is-for-new-accounts-only-if-a-higher-discount-is-currently-available-it-will-be-applied-automatically-&quot;&gt;&lt;strong&gt;&lt;em&gt;Important:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This discount is for new accounts only. If a higher discount is currently available, it will be applied automatically.&lt;/em&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;How to Claim Your Discount:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;the link&lt;/a&gt; to explore the new Scrimba 2.0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upgrade to Pro; the 20% discount will automatically apply.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id=&quot;disclosure-this-article-contains-affiliate-links-i-will-earn-a-commission-from-any-purchases-made-through-these-links-at-no-extra-cost-to-you-your-support-helps-me-continue-creating-valuable-content-thank-you-&quot;&gt;&lt;strong&gt;&lt;em&gt;Disclosure:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This article contains affiliate links. I will earn a commission from any purchases made through these links at no extra cost to you. Your support helps me continue creating valuable content. Thank you!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;The JavaScript &lt;code&gt;.includes()&lt;/code&gt; method checks whether an array contains a specific element, returning &lt;code&gt;true&lt;/code&gt; if it does and &lt;code&gt;false&lt;/code&gt; if it doesn't. It’s great for verifying if an array has a particular value, but it does have limitations. While it works well for simple values like numbers, strings, or (in my project’s case) emojis, it doesn’t check inside nested arrays or objects.&lt;/p&gt;
&lt;p&gt;By utilizing &lt;code&gt;.includes()&lt;/code&gt; in the function that controls the ghosts in my Pac-Man game, I can determine when a ghost’s current index enters an intersection and trigger a direction change. This makes the ghosts smarter and brings my project closer to the original arcade experience!&lt;/p&gt;
&lt;p&gt;Just as I used &lt;code&gt;.includes()&lt;/code&gt; with an array of intersections to improve game logic, you can also find creative ways to combine this method with your own data structures. Explore how &lt;code&gt;.includes()&lt;/code&gt; can simplify checks and enhance features in your projects—experiment and see what smart solutions you can build!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Let’s connect! I’m active on&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/michaeljudelarocca/&quot;&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/MikeJudeLarocca&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*WkkMi-AgjubeODf8.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h6 id=&quot;do-you-now-feel-confident-using-javascript-s-includes-for-array-searches-have-you-applied-similar-techniques-in-your-projects-please-share-the-article-and-comment-&quot;&gt;&lt;em&gt;Do you now feel confident using JavaScript's .includes() for array searches? Have you applied similar techniques in your projects? Please share the article and comment!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
</description>
<category>JavaScript</category>
<guid isPermaLink="true">https://coderlegion.com/6682/javascript-essentials-how-to-use-includes-for-array-searches</guid>
<pubDate>Mon, 20 Oct 2025 19:37:27 +0000</pubDate>
</item>
<item>
<title>CSS Pseudo-Elements: Enhance Your Projects with Style!</title>
<link>https://coderlegion.com/5783/css-pseudo-elements-enhance-your-projects-with-style</link>
<description>&lt;h5 id=&quot;this-article-explores-css-pseudo-elements-which-enable-easy-styling-and-content-modification-without-additional-html-elements-and-how-i-used-them-to-create-eyes-for-the-ghosts-in-my-js-pac-man-game-&quot;&gt;This article explores CSS pseudo-elements, which enable easy styling and content modification without additional HTML elements, and how I used them to create eyes for the ghosts in my JS Pac-Man game!&lt;/h5&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1758911098388/a64c1c00-ce16-4fe5-ac91-e7960708e155.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;css-before-amp-after-pseudo-elements&quot;&gt;CSS Before &amp;amp; After Pseudo-Elements&lt;/h4&gt;
&lt;p&gt;CSS pseudo-elements allow you to insert content before or after an element's actual content without adding extra HTML elements. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/::before&quot;&gt;::before&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/::after&quot;&gt;::after&lt;/a&gt; pseudo-elements are popular and can help you style and modify content easily.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some of the other commonly used pseudo-elements include:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;::first-line&lt;/code&gt;: Applies styles to the first line of a block-level element.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;::first-letter&lt;/code&gt;: Styles the first letter of a block-level element.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;::selection&lt;/code&gt;: Styles the portion of an element that is selected by the user.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;::placeholder&lt;/code&gt;: Styles the placeholder text in an input or textarea element.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;::marker&lt;/code&gt;: Styles the marker box of a list item, such as bullets or numbers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;⚠ &lt;strong&gt;Important:&lt;/strong&gt; &lt;em&gt;Pseudo-elements can't be targeted in JavaScript as they aren't part of the DOM. They serve decorative purposes, but you can indirectly manipulate their styles by altering their parent elements' styles or modifying CSS rules dynamically.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-is-this-a-valuable-coding-concept-&quot;&gt;Why Is This a Valuable Coding Concept?&lt;/h4&gt;
&lt;p&gt;Understanding and utilizing CSS pseudo-elements is valuable because they enhance styling capabilities by allowing you to add decorative content and style specific parts of an element without altering the HTML structure. This leads to improved code maintainability, as it keeps HTML clean and semantic, reducing the need for additional markup.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1758581894926/5d0a7df6-97c0-48c6-bcd0-4b056cc8d178.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;js-pac-man-project-links--1&quot;&gt;JS Pac-Man Project Links:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pac-man-javascript-laroccade.netlify.app/&quot;&gt;Link to the deployed projec&lt;/a&gt;t&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MichaelLarocca/pac-man-javascript-laroccade&quot;&gt;Link to GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/users/MichaelLarocca/projects/1&quot;&gt;Link to the sprint board&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id=&quot;note-since-i-m-following-team-development-practices-and-pushing-updates-to-the-develop-branch-some-of-the-changes-i-describe-in-this-article-may-not-be-live-in-the-deployed-version-of-the-game-yet--1&quot;&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Since I’m following team development practices and pushing updates to the develop branch, some of the changes I describe in this article may not be live in the deployed version of the game yet.&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;ghost-s-eye-direction&quot;&gt;Ghost’s Eye Direction&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;This article explains how I used CSS pseudo-elements to add eyes to the ghosts in my JavaScript Pac-Man game and simulate their eye movements!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For my JavaScript Pac-Man game, I created each ghost's eye using the before and after CSS pseudo-elements: the before pseudo-element for the left eye and the after pseudo-element for the right eye. Each eye is simply a boxed div with a 50% border radius, a black background, and a thick white border.&lt;/p&gt;
&lt;p&gt;Taking creative liberty, I reposition the eyes to create the effect, differentiating it from the original arcade, where the pupils move. In my game, the left and right eyes remain grouped, and I slightly shift them in the direction they are moving. While in action, I believe it is pretty convincing!&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://codepen.io/Michael_Larocca/pen/YzNqBWj?editors=1100&quot;&gt;Link to the CodePen project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1758836601263/dd3e80dd-55f0-4672-8383-f7e92870507a.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, I created a main ghost class. Using a black background, I rounded out the ghost’s head with the top left and right border radius, and I used a clip-path to create the jagged bottom of the ghost.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.ghost {
  border: 1px solid white;
  background-color: black;
  position: relative;
  width: 200px;
  height: 200px;
  margin:10px;
  border-top-left-radius: 50%;
  border-top-right-radius: 50%;
  clip-path: polygon(50% 0%, 100% 0, 100% 100%, 81% 91%, 66% 100%, 49% 91%, 31% 100%, 15% 91%, 0 100%, 0 0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I create the ghost’s left eye using the &lt;code&gt;::before&lt;/code&gt; CSS pseudo-element. I create a box and use a thick white border with a black background, rounding it out with a 50% border radius to complete the effect. The eye is positioned absolutely, sitting 100 pixels from the bottom and slightly shifted to the right with a margin, giving it the appearance of a floating eye on the ghost.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.ghost::before {
  border: 12px solid white;
  border-radius: 50%;
  background-color: black;
  content: &quot;&quot;;
  width: 30px;
  height: 30px;
  position: absolute;
  bottom: 100px;
  margin: 0 0 0 50px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the ghost's right eye, the &lt;code&gt;::after&lt;/code&gt; CSS pseudo-element is used. It mirrors the left eye by creating another circular shape with a thick white border and a black background, using a 50% border radius for the round look. This eye is also positioned absolutely, 100 pixels from the bottom, but it's shifted further to the right with a margin, making it appear as the ghost's second floating eye.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.ghost::after {
  border: 12px solid white;
  border-radius: 50%;
  background-color: black;
  content: &quot;&quot;;
  width: 30px;
  height: 30px;
  position: absolute;
  bottom: 100px;
  margin: 0 0 0 100px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;ghosts-colors&quot;&gt;Ghosts’ Colors&lt;/h4&gt;
&lt;p&gt;I created a separate class for each ghost color so that I can easily use them in conjunction with the main ghost class.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Ghost colors */
.ghost-red {
  background-color: rgb(255,12,14);
}
.ghost-pink {
  background-color: rgb(255,165,166);
}
.ghost-blue {
  background-color: rgb(147,255,255);
}
.ghost-orange {
  background-color: rgb(253,123,13);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;scared-ghost-color&quot;&gt;Scared Ghost Color&lt;/h4&gt;
&lt;p&gt;For a scared ghost, I changed the color of the ghost to blue and the eyes to white.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Scared */
.scared {
 background-color: blue;
}

.scared::before,
.scared::after {
  background-color: white;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;combining-ghost-classes&quot;&gt;Combining Ghost Classes&lt;/h4&gt;
&lt;p&gt;Now that there are classes for a main ghost, a ghost color, and a scared ghost, I can combine them to create a single ghost with the ability to change its eye direction!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;div class=&quot;flex&quot;&amp;gt;
  &amp;lt;div class=&quot;ghost ghost-red ghost-look-left&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;ghost ghost-blue ghost-look-up&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;ghost ghost-pink ghost-look-down&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;ghost ghost-orange ghost-look-right&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;ghost scared&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;ghost&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;javascript-ghost-eye-direction&quot;&gt;JavaScript Ghost Eye Direction&lt;/h4&gt;
&lt;p&gt;In my &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MichaelLarocca/pac-man-javascript-laroccade&quot;&gt;JavaScript Pac-Man game&lt;/a&gt;, I created a Ghost class that includes an eyes property and other properties essential for the ghost's functionality.&lt;/p&gt;
&lt;p&gt;Within the function that moves the ghosts, there is code to determine a valid direction—left, right, down, or up—and assign it. Once a direction is assigned, I update the CSS ghost's eye direction class by removing the current class and applying the new one.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;      // Eye direction
      if (direction === -1) {
        squares[ghost.currentIndex].classList.remove(ghost.eyes);
        ghost.eyes = `ghost-look-left-${eyeSize}`;
      } else if (direction === 1) {
        squares[ghost.currentIndex].classList.remove(ghost.eyes);
        ghost.eyes = `ghost-look-right-${eyeSize}`;
      } else if (direction === 28) {
        squares[ghost.currentIndex].classList.remove(ghost.eyes);
        ghost.eyes = `ghost-look-down-${eyeSize}`;
      } else if (direction === -28) {
        squares[ghost.currentIndex].classList.remove(ghost.eyes);
        ghost.eyes = `ghost-look-up-${eyeSize}`;
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;To make trial-and-error creation easier, I initially styled all the ghosts' eye directions on&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://codepen.io/Michael_Larocca/pen/YzNqBWj&quot;&gt;&lt;em&gt;CodePen&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, using a ghost size of 200px for both width and height. In my game, the size of the ghost is either 20px or 10px in width and height, depending on full-screen or mobile view. With GitHub's CoPilot, I was able to easily resize the CSS values by letting the AI handle the calculations.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;my-other-related-articles-1&quot;&gt;My other related articles&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/09-25/what-is-state-in-javascript-and-how-to-use-it-in-your-projects/&quot;&gt;What Is “State” in JavaScript, and How to Use It in Your Projects&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2022/03-22/Ania_Kubow-Tetris/&quot;&gt;Ania Kubow: Building Tetris with JavaScript&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.htmlallthethings.com/blog-posts/mastering-javascript-fundamentals-unleashing-your-framework-readiness&quot;&gt;Mastering JavaScript Fundamentals: Unleashing Your Framework Readiness&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/08-25/mastering-javascript-the-power-of-foreach-and-ternary-operators/&quot;&gt;Mastering JavaScript: The Power of forEach and Ternary Operators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/06-25/javascript-understanding-the-set-and-clear-interval-methods/&quot;&gt;JavaScript: Understanding the Set and Clear Interval Methods&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168177634/1ff578a9-5d03-4800-9c0c-9092e4e60c4f.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;be-sure-to-listen-to-the-html-all-the-things-podcast--1&quot;&gt;&lt;strong&gt;Be sure to listen to the HTML All The Things Podcast!&lt;/strong&gt;&lt;/h4&gt;
&lt;h5 id=&quot;i-also-write-articles-for-the-html-all-the-things-podcast-which-you-can-read-on-their-website-https-www-htmlallthethings-com--1&quot;&gt;&lt;em&gt;I also write articles for the HTML All The Things Podcast, which you can read on their website:&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.htmlallthethings.com/&quot;&gt;&lt;em&gt;https://www.htmlallthethings.com/&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/h5&gt;
&lt;h5 id=&quot;be-sure-to-check-out-html-all-the-things-on-socials--1&quot;&gt;&lt;strong&gt;Be sure to check out HTML All The Things on socials!&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/htmleverything&quot;&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/company/html-all-the-things/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tiktok.com/@htmlallthethings&quot;&gt;TikTok&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/htmlallthethings/&quot;&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;affiliate-amp-discount-links--1&quot;&gt;Affiliate &amp;amp; Discount Links!&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168473785/c85789c9-1594-4000-aa2f-43e842f9825f.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With CodeMonkey, learning can be all fun and games!&lt;/strong&gt; CodeMonkey transforms education into an engaging experience, enabling children to evolve from tech consumers to creators. Use CodeMonkey's &lt;strong&gt;FREE trial&lt;/strong&gt; to unlock the incredible potential of young tech creators!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;With a structured learning path tailored for various age groups, kids progress from block coding to more advanced topics like data science and artificial intelligence, using languages such as CoffeeScript and Python. The platform includes features for parents and teachers to track progress, making integrating coding into home and classroom settings easy.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Through fun games, hands-on projects, and community interaction, CodeMonkey helps young learners build teamwork skills and receive recognition for their achievements. It fosters a love for coding and prepares children for future career opportunities in an ever-evolving tech landscape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;To learn more about CodeMonkey, you can read my detailed&lt;/em&gt;&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/02-25/inspiring-young-coders-how-codemonkey-turns-kids-into-tech-creators/&quot;&gt;&lt;strong&gt;&lt;em&gt;review article&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&lt;em&gt;!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affiliate Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919057/12259&quot;&gt;Sign Up for Parents&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919060/12259&quot;&gt;Sign Up for Teachers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740169191630/ac8c5360-d1c5-4c85-8504-18b9c755841e.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;advance-your-career-with-a-20-discount-on-scrimba-pro-using-this-affiliate-link--1&quot;&gt;Advance your career with a 20% discount on Scrimba Pro using this &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;affiliate link&lt;/a&gt;!&lt;/h4&gt;
&lt;p&gt;Become a hireable developer with Scrimba Pro! Discover a world of coding knowledge with full access to all courses, hands-on projects, and a vibrant community. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2021/06-21/06-07-21/&quot;&gt;read my article&lt;/a&gt; to learn more about my exceptional experiences with Scrimba and how it helps many become confident, well-prepared web developers!&lt;/p&gt;
&lt;h6 id=&quot;important-this-discount-is-for-new-accounts-only-if-a-higher-discount-is-currently-available-it-will-be-applied-automatically--1&quot;&gt;&lt;strong&gt;&lt;em&gt;Important:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This discount is for new accounts only. If a higher discount is currently available, it will be applied automatically.&lt;/em&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;How to Claim Your Discount:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;the link&lt;/a&gt; to explore the new Scrimba 2.0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upgrade to Pro; the 20% discount will automatically apply.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id=&quot;disclosure-this-article-contains-affiliate-links-i-will-earn-a-commission-from-any-purchases-made-through-these-links-at-no-extra-cost-to-you-your-support-helps-me-continue-creating-valuable-content-thank-you--1&quot;&gt;&lt;strong&gt;&lt;em&gt;Disclosure:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This article contains affiliate links. I will earn a commission from any purchases made through these links at no extra cost to you. Your support helps me continue creating valuable content. Thank you!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;CSS pseudo-elements are great for styling projects without the need for additional HTML. You can easily style a first letter or line with pseudo-elements, markers like bullets, placeholder text, entire sections, and more. I used the before and after pseudo-elements for enhanced styling in my project.&lt;/p&gt;
&lt;p&gt;Additional benefits include improved code maintainability by keeping HTML clean and semantic, performance optimization as the browser efficiently renders them, the ability to create responsive designs that adapt to various screen sizes, and the potential for dynamic visual effects through CSS transitions and animations.&lt;/p&gt;
&lt;p&gt;In my Pac-Man game, I stylistically designed pseudo-element classes to represent the eyes of the ghosts looking in the left, right, up, and down directions. Using JavaScript, I change the direction of the ghosts' eyes based on their movement, producing a realistic effect that makes it seem like their eyes are looking in the direction they are moving.&lt;/p&gt;
&lt;p&gt;Can you think of other unique styling ideas for your projects that utilize pseudo-elements? Experiment with them to unleash your creativity and transform your designs into something remarkable!  &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Let’s connect! I’m active on&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/michaeljudelarocca/&quot;&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/MikeJudeLarocca&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*WkkMi-AgjubeODf8.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h6 id=&quot;do-you-now-feel-confident-in-using-css-pseudo-elements-to-enhance-your-projects-have-you-tried-creating-unique-styles-with-them-in-your-own-work-please-share-the-article-and-comment-&quot;&gt;&lt;em&gt;Do you now feel confident in using CSS pseudo-elements to enhance your projects? Have you tried creating unique styles with them in your own work? Please share the article and comment!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
</description>
<category>css</category>
<guid isPermaLink="true">https://coderlegion.com/5783/css-pseudo-elements-enhance-your-projects-with-style</guid>
<pubDate>Mon, 29 Sep 2025 22:22:09 +0000</pubDate>
</item>
<item>
<title>What Is “State” in JavaScript, and How to Use It in Your Projects</title>
<link>https://coderlegion.com/5522/what-is-state-in-javascript-and-how-to-use-it-in-your-projects</link>
<description>&lt;h5 id=&quot;state-in-javascript-refers-to-the-underlying-data-that-represents-your-application-s-current-condition-this-article-covers-how-i-implemented-state-in-a-real-world-scenario-my-pac-man-game-project-&quot;&gt;State in JavaScript refers to the underlying data that represents your application's current condition. This article covers how I implemented state in a real-world scenario, my Pac-Man game project!&lt;/h5&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1758651183878/5916bda6-93f9-4d5d-b89d-ee55f96c4a9f.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;state-in-javascript&quot;&gt;State in JavaScript&lt;/h4&gt;
&lt;p&gt;State in JavaScript refers to the underlying data that represents the current condition of your application or game. It tracks various aspects such as player position, scores, and elements on the board. In game development, maintaining a separate state array helps ensure that you always know the true status of the game board, regardless of what is visually displayed on the screen. This approach is advantageous when multiple elements can change a square simultaneously, as it provides a reliable way to manage game logic independently from the DOM.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;why-is-this-a-valuable-coding-concept--1&quot;&gt;Why Is This a Valuable Coding Concept?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separation of concerns:&lt;/strong&gt; Logic (state) is separate from presentation (DOM).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Predictability:&lt;/strong&gt; You always know the “truth” of your game.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; This pattern works for more complex games and apps!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1758581894926/5d0a7df6-97c0-48c6-bcd0-4b056cc8d178.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;js-pac-man-project-links--2&quot;&gt;JS Pac-Man Project Links:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pac-man-javascript-laroccade.netlify.app/&quot;&gt;Link to the deployed project&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MichaelLarocca/pac-man-javascript-laroccade&quot;&gt;Link to GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/users/MichaelLarocca/projects/1&quot;&gt;Link to the sprint board&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id=&quot;disclaimer-since-i-m-following-team-development-practices-and-pushing-updates-to-the-develop-branch-some-of-the-changes-i-describe-in-this-article-may-not-be-live-in-the-deployed-version-of-the-game-yet-if-you-try-out-the-game-and-notice-differences-it-s-because-new-features-and-fixes-are-still-being-tested-before-release-&quot;&gt;&lt;strong&gt;&lt;em&gt;Disclaimer:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Since I’m following team development practices and pushing updates to the develop branch, some of the changes I describe in this article may not be live in the deployed version of the game yet. If you try out the game and notice differences, it’s because new features and fixes are still being tested before release.&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;managing-pellet-visibility-and-ghost-interaction&quot;&gt;Managing Pellet Visibility and Ghost Interaction&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;This article focuses on adding JavaScript state management for the pellets in my Pac-Man game project, for improved and more reliable gameplay.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For my JavaScript Pac-Man game, CSS classes are used for the pellets. To create the visual effect of a pellet, I made the background of the 20×20px square white and transformed the scale to 0.2 the size of the square (it’s 10×10px on mobile view).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Transform scale only affects how the content appears, not the div’s box size or position in the grid.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.pellet {
  background-color: whitesmoke;
  transform: scale(0.2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When Pac-Man enters a div containing a pellet class, I remove it, and update the score and other logic accordingly, achieving the desired effect. However, using the transform scale to shrink the size of the pellet comes at a cost; it also shrinks anything else sharing the div to 0.2 of the original size! Since Pac-Man always eats the pellet, I avoid the shrinkage issue. But when the ghosts pass through the divs containing pellets, they shrink.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My solution to the ghosts' shrinking due to using the CSS transform scale&lt;/strong&gt; &lt;strong&gt;was to remove the pellet class when they entered the div and then return it when they left it. And it worked! Kind of. While playing the game, the ghosts were inconsistent in putting the pellet class back and added it to empty game cells where it did not belong.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So, I devised a new approach: I would create a variable that held all of the information needed from the cell a ghost enters, and then use the variable’s information to put it back when the ghost exits it. I described my solution to GitHub CoPilot, expecting to be commended for my brilliant idea and then for CoPilot to assist me in implementing the code. Instead, CoPilot offered another solution and introduced me to state management in JavaScript, which is used for game development.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;state-in-my-project&quot;&gt;State in My Project&lt;/h4&gt;
&lt;p&gt;I learned that “state” in JavaScript game development is used for tracking player position, scores, what’s on the board, and, in my case, pellets, among other things; previously, I thought state management was just for React and other frameworks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CoPilot explains that the DOM (your HTML elements and their classes) shows what’s on the screen, but it’s not always reliable for tracking game logic, especially in my case, when multiple things (like ghosts and Pac-Man) can change a square simultaneously. By maintaining a separate array (the “state array”) that contains what each square holds, we now always know the “truth” about the game board, regardless of what’s seen visually.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;implementing-state-management&quot;&gt;Implementing State Management&lt;/h4&gt;
&lt;p&gt;In my project, the &lt;code&gt;setGameBoard&lt;/code&gt;function acts as an overlay to set up the Pac-Man game board. This blueprint for the game board contains numbers for different squares, like a wall, pellet, power pellet, empty space, or Pac-Man’s starting position. When setting up, the code iterates through each square, examines the overlay value, and adds the appropriate visual class.&lt;/p&gt;
&lt;p&gt;I amended the &lt;code&gt;setGameBoard&lt;/code&gt; function to include additional code that also updates the newly added &lt;code&gt;pelletState&lt;/code&gt; array accordingly, when finding both pellets and power pellets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Inside the setGameBoard function, the code uses the pelletState array to track what each square on the board contains:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For each square, it checks the overlayArray to see if that square should have a power pellet (2), a regular pellet (3), or neither.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the square should have a power pellet, it sets pelletState\[i\] = 'powerPellet'.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the square should have a regular pellet, it sets pelletState\[i\] = 'pellet'.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the square should have neither, it sets pelletState\[i\] = null.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Now, the pelletState array always holds the true state of every square—regardless of what’s currently visible on the screen.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const pelletState = [];

export function setGameBoard() { 
  // ...additional setup code...

  for (let i = 0; i &amp;lt; squares.length; i++) {
    // ...remove classes, reset square...

    // Pellet state logic (only one of these will be true per square)
    if (overlayArray[i] === 2) {
      squares[i].classList.add('powerPellet');
      pelletState[i] = 'powerPellet'; // Track state
    } else if (overlayArray[i] === 3) {
      squares[i].classList.add('pellet');
      pelletState[i] = 'pellet'; // Track state
    } else {
      pelletState[i] = null; // Track state
    }

    // ...other board setup logic...
  }

  // ...ghost setup and other code...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;updating-pellet-state-with-pac-man&quot;&gt;Updating Pellet State With Pac-Man&lt;/h4&gt;
&lt;p&gt;In the function that controls Pac-Man, I added code that updates the &lt;code&gt;pelletState&lt;/code&gt; array right after the code that removes the pellet and power pellet classes when he eats them. Now, when Pac-Man eats a pellet or power pellet, the code not only removes the visual CSS class from the board, but also updates the &lt;code&gt;pelletState&lt;/code&gt; array for that square. This ensures the game’s internal state always matches what’s actually on the board, making the logic more reliable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function control(x) {
  // ...determine steps and movement logic...

  for (let i = 0; i &amp;lt; steps; i++) {
    // ...Pac-Man movement code...

    // Pellet state logic: update state when Pac-Man eats a pellet
    if (squares[pacmanCurrentIndex].classList.contains('pellet')) {
      playPacManEatingPelletsSound();
      squares[pacmanCurrentIndex].classList.remove('pellet');
      pelletState[pacmanCurrentIndex] = null; // Update state: pellet is now gone
      counterPelet += 1;
      score += 10;
    } else {
      stopPacManEatingPelletsSound();
    }

    // Pellet state logic: update state when Pac-Man eats a power pellet
    if (squares[pacmanCurrentIndex].classList.contains('powerPellet')) {
      squares[pacmanCurrentIndex].classList.remove('powerPellet');
      pelletState[pacmanCurrentIndex] = null; // Update state: power pellet is now gone
      score += 50;
      // ...power pellet effects...
    }

    // ...bonus fruit, score, and UI updates...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;updating-pellet-state-with-ghosts&quot;&gt;Updating Pellet State With Ghosts&lt;/h4&gt;
&lt;p&gt;When a ghost moves around the board, it passes over squares containing pellets and power pellets. The updated code checks the pellet state array every time a ghost moves to keep the game visuals and logic accurate. If the ghost is about to enter a square that should have a pellet or power pellet (according to the state array), it temporarily removes the visual pellet (CSS class) from that square so the ghost appears on top. But, since the ghosts never eat the pellets or power pellets, they always remain in the &lt;code&gt;pelletState&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;Then, when the ghost leaves its previous square, the updated code checks the &lt;code&gt;pelletState&lt;/code&gt; array again. If that square is supposed to have a pellet or power pellet, it restores the visual pellet (CSS class) so it reappears after the ghost has moved away. This way, the game always knows which squares actually have pellets, and the visuals stay in sync with the game’s internal state—even as ghosts move around the board, fixing the pellet misplacement caused by the ghosts’ glitch!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function moveGhost(ghost) {
  // ...setup and movement logic...

  ghost.timerId = setInterval(function() {
    // ...direction and movement checks...

    if (foundValid) {
      // ...eye direction logic...

      // Calculate next index
      const nextIndex = ghost.currentIndex + direction;

      // Pellet state logic: update pellet visuals based on state
      // Remove pellet/powerPellet class from the new square if needed
      if (pelletState[nextIndex] === 'pellet') {
        squares[nextIndex].classList.remove('pellet');
      }
      if (pelletState[nextIndex] === 'powerPellet') {
        squares[nextIndex].classList.remove('powerPellet');
      }

      // Restore pellet/powerPellet class to the previous square if needed
      squares[ghost.currentIndex].classList.remove('pellet', 'powerPellet');
      if (pelletState[ghost.currentIndex] === 'pellet') {
        squares[ghost.currentIndex].classList.add('pellet');
      }
      if (pelletState[ghost.currentIndex] === 'powerPellet') {
        squares[ghost.currentIndex].classList.add('powerPellet');
      }

      // ...move ghost, tunnel wrap, and add ghost to new square...
    }

    // ...scared state, collision, and other logic...
  }, ghost.speed);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;my-other-related-articles-2&quot;&gt;My other related articles&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2022/03-22/Ania_Kubow-Tetris/&quot;&gt;Ania Kubow: Building Tetris with JavaScript&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.htmlallthethings.com/blog-posts/mastering-javascript-fundamentals-unleashing-your-framework-readiness&quot;&gt;Mastering JavaScript Fundamentals: Unleashing Your Framework Readiness&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/08-25/mastering-javascript-the-power-of-foreach-and-ternary-operators/&quot;&gt;Mastering JavaScript: The Power of forEach and Ternary Operators&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/06-25/javascript-understanding-the-set-and-clear-interval-methods/&quot;&gt;JavaScript: Understanding the Set and Clear Interval Methods&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168177634/1ff578a9-5d03-4800-9c0c-9092e4e60c4f.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;be-sure-to-listen-to-the-html-all-the-things-podcast--2&quot;&gt;&lt;strong&gt;Be sure to listen to the HTML All The Things Podcast!&lt;/strong&gt;&lt;/h4&gt;
&lt;h5 id=&quot;i-also-write-articles-for-the-html-all-the-things-podcast-which-you-can-read-on-their-website-https-www-htmlallthethings-com--2&quot;&gt;&lt;em&gt;I also write articles for the HTML All The Things Podcast, which you can read on their website:&lt;/em&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.htmlallthethings.com/&quot;&gt;&lt;em&gt;https://www.htmlallthethings.com/&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/h5&gt;
&lt;h5 id=&quot;be-sure-to-check-out-html-all-the-things-on-socials--2&quot;&gt;&lt;strong&gt;Be sure to check out HTML All The Things on socials!&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/htmleverything&quot;&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/company/html-all-the-things/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.tiktok.com/@htmlallthethings&quot;&gt;TikTok&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/htmlallthethings/&quot;&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;affiliate-amp-discount-links--2&quot;&gt;Affiliate &amp;amp; Discount Links!&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740168473785/c85789c9-1594-4000-aa2f-43e842f9825f.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With CodeMonkey, learning can be all fun and games!&lt;/strong&gt; CodeMonkey transforms education into an engaging experience, enabling children to evolve from tech consumers to creators. Use CodeMonkey's &lt;strong&gt;FREE trial&lt;/strong&gt; to unlock the incredible potential of young tech creators!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;With a structured learning path tailored for various age groups, kids progress from block coding to more advanced topics like data science and artificial intelligence, using languages such as CoffeeScript and Python. The platform includes features for parents and teachers to track progress, making integrating coding into home and classroom settings easy.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Through fun games, hands-on projects, and community interaction, CodeMonkey helps young learners build teamwork skills and receive recognition for their achievements. It fosters a love for coding and prepares children for future career opportunities in an ever-evolving tech landscape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;To learn more about CodeMonkey, you can read my detailed&lt;/em&gt;&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2025/02-25/inspiring-young-coders-how-codemonkey-turns-kids-into-tech-creators/&quot;&gt;&lt;strong&gt;&lt;em&gt;review article&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&lt;em&gt;!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Affiliate Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919057/12259&quot;&gt;Sign Up for Parents&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://codemonkey.sjv.io/c/5987452/919060/12259&quot;&gt;Sign Up for Teachers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1740169191630/ac8c5360-d1c5-4c85-8504-18b9c755841e.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;advance-your-career-with-a-20-discount-on-scrimba-pro-using-this-affiliate-link--2&quot;&gt;Advance your career with a 20% discount on Scrimba Pro using this &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;affiliate link&lt;/a&gt;!&lt;/h4&gt;
&lt;p&gt;Become a hireable developer with Scrimba Pro! Discover a world of coding knowledge with full access to all courses, hands-on projects, and a vibrant community. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://selftaughttxg.com/2021/06-21/06-07-21/&quot;&gt;read my article&lt;/a&gt; to learn more about my exceptional experiences with Scrimba and how it helps many become confident, well-prepared web developers!&lt;/p&gt;
&lt;h6 id=&quot;important-this-discount-is-for-new-accounts-only-if-a-higher-discount-is-currently-available-it-will-be-applied-automatically--2&quot;&gt;&lt;strong&gt;&lt;em&gt;Important:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This discount is for new accounts only. If a higher discount is currently available, it will be applied automatically.&lt;/em&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;How to Claim Your Discount:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrimba.com/?via=MichaelLarocca&quot;&gt;the link&lt;/a&gt; to explore the new Scrimba 2.0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new account.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upgrade to Pro; the 20% discount will automatically apply.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id=&quot;disclosure-this-article-contains-affiliate-links-i-will-earn-a-commission-from-any-purchases-made-through-these-links-at-no-extra-cost-to-you-your-support-helps-me-continue-creating-valuable-content-thank-you--2&quot;&gt;&lt;strong&gt;&lt;em&gt;Disclosure:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;This article contains affiliate links. I will earn a commission from any purchases made through these links at no extra cost to you. Your support helps me continue creating valuable content. Thank you!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;State in JavaScript is data that represents the current condition of your program. In games like the Pac-Man game I am working on, state is often used to track player position, scores, and what’s on the board, among other things. Unlike React, where state is managed with hooks or class properties, in plain JavaScript, state is used in variables, arrays, or objects.&lt;/p&gt;
&lt;p&gt;Before implementing state in my project, I relied on the DOM for my game logic, which proved unstable. Specifically, when Pac-Man and the ghosts enter a div containing a pellet, I use JavaScript’s contains() method to check if it has a pellet class and then handle the logic. Since changing my approach to handling the logic using state, my game has become much more stable, and I intend to continue refactoring my code to use state throughout the project.&lt;/p&gt;
&lt;p&gt;The state management approach provides your project and games with a single source of truth. While the DOM merely displays what's happening on the screen, the actual data resides in your state. By using state management for your logic instead of relying on the visual DOM, you can significantly enhance the stability and performance of your games!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Let’s connect! I’m active on&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/michaeljudelarocca/&quot;&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://twitter.com/MikeJudeLarocca&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:700/0*WkkMi-AgjubeODf8.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h6 id=&quot;have-you-gained-a-better-understanding-of-state-management-in-javascript-games-are-you-ready-to-implement-these-techniques-in-your-own-projects-please-share-the-article-and-comment-&quot;&gt;&lt;em&gt;Have you gained a better understanding of state management in JavaScript games? Are you ready to implement these techniques in your own projects? Please share the article and comment!&lt;/em&gt;&lt;/h6&gt;
&lt;hr&gt;
</description>
<category>JavaScript</category>
<guid isPermaLink="true">https://coderlegion.com/5522/what-is-state-in-javascript-and-how-to-use-it-in-your-projects</guid>
<pubDate>Tue, 23 Sep 2025 21:02:52 +0000</pubDate>
</item>
<item>
<title>Make In A Day: Hovertank One</title>
<link>https://coderlegion.com/4793/make-in-a-day-hovertank-one</link>
<description>&lt;p&gt;Hovertank One was the predecessor of DOOM, Wolfenstein, and Quake, by id software. This will probably be the only 3D game in this series, and even then, this is not true 3D. The challenge is to make this in one day, so first let's create our spec. I'll be using Golang and &lt;a rel=&quot;nofollow&quot; href=&quot;https://ebitengine.org/&quot;&gt;Ebitengine&lt;/a&gt;. I'll also be using my own UI code. All other code will be written within on day, and hopefully you can do the same. Choose your own language, game engine, whatever, the idea of the code should be the same.&lt;/p&gt;
&lt;h3 id=&quot;shortlist&quot;&gt;Shortlist&lt;/h3&gt;
&lt;p&gt;Here's a small checklist for what we have to implement in the game.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The ceiling (or sky) is black&lt;/li&gt;
&lt;li&gt;The floor is grey&lt;/li&gt;
&lt;li&gt;The world is grid based&lt;/li&gt;
&lt;li&gt;Lose screen&lt;/li&gt;
&lt;li&gt;Win screen&lt;/li&gt;
&lt;li&gt;Reset logic&lt;/li&gt;
&lt;li&gt;Colored walls&lt;/li&gt;
&lt;li&gt;Different shading depending on wall direction&lt;/li&gt;
&lt;li&gt;Sprite drawing&lt;/li&gt;
&lt;li&gt;Enemy logic&lt;/li&gt;
&lt;li&gt;Shooting logic&lt;/li&gt;
&lt;li&gt;Player movement&lt;/li&gt;
&lt;li&gt;Civilians and civilian logic&lt;/li&gt;
&lt;li&gt;Minimap&lt;/li&gt;
&lt;li&gt;Collision&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;first-steps&quot;&gt;First Steps&lt;/h3&gt;
&lt;p&gt;We will first define our tiles, such as empty space, walls, and spawn locations, which will be replaced with empty space.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Tile struct {
color color.RGBA
solid bool
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I've also created a grid using the tile codes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (g *Game) loadGrid(rawGrid [][]int) {
g.grid = make([][]Tile, len(rawGrid))

for r := range len(rawGrid) {
g.grid[r] = make([]Tile, len(rawGrid[r]))

for c := range len(rawGrid[r]) {
switch TileCode(rawGrid[r][c]) {
case TC_EMPTY:
g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
case TC_WHITE_WALL:
g.grid[r][c] = Tile{color.RGBA{255, 255, 255, 255}, true}
case TC_RED_WALL:
g.grid[r][c] = Tile{color.RGBA{255, 0, 0, 255}, true}
case TC_GREEN_WALL:
g.grid[r][c] = Tile{color.RGBA{0, 255, 0, 255}, true}
case TC_BLUE_WALL:
g.grid[r][c] = Tile{color.RGBA{0, 0, 255, 255}, true}

// Entities
case TC_SPAWN:
g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
case TC_CIVIL:
g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
case TC_ENEMY:
g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
}
}
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To see if this is correct, we'll make a minimap for the bottom right.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (g *Game) drawMinimap(screen *ebiten.Image) {
// Calculate how big we should make each minimap tile
tileWidth := float32(g.minimap.Bounds().Dx()) / float32(len(g.grid[0]))
tileHeight := float32(g.minimap.Bounds().Dy()) / float32(len(g.grid))

// Draw each tile
for r := range len(g.grid) {
for c := range len(g.grid[r]) {
vector.DrawFilledRect(g.minimap, float32(c)*tileWidth, float32(r)*tileHeight, tileWidth, tileHeight, g.grid[r][c].color, false)
}
}

op := ebiten.DrawImageOptions{}
op.GeoM.Translate(SCREEN_WIDTH-float64(g.minimap.Bounds().Dx()), SCREEN_HEIGHT-float64(g.minimap.Bounds().Dy()))
screen.DrawImage(g.minimap, &amp;amp;op)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/smszcviuzvh46hebsnou.png&quot; alt=&quot;Minimap&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, if we want to start being able to see the world, we need to place the player in it, so let's do that, and spawn them in, and draw them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Player struct {
x, y float64
w, h float64
angle float64

shotCooldown int
}

case TC_SPAWN:
    g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
    g.player = &amp;amp;Player{
        x: float64(c)+0.5,
        y: float64(r)+0.5,
        w: 0.5,
        h: 0.5,
    }

// Draw the player
vector.DrawFilledRect(
    g.minimap,
    float32(g.player.x-g.player.w/2)*tileWidth,
    float32(g.player.y-g.player.h/2)*tileHeight,
    float32(g.player.w)*tileWidth,
    float32(g.player.h)*tileHeight,
    color.RGBA{255, 255, 0, 255},
    false,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yx7eg8annm8r26f8q7m9.png&quot; alt=&quot;A dot!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now we may as well place the enemies. But first, we must define what an enemy is, load them, and draw them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Enemy struct {
x, y float64
w, h float64
img  *ebiten.Image
}

// Loading the enemy
case TC_ENEMY:
    g.grid[r][c] = Tile{color.RGBA{0, 0, 0, 255}, false}
    g.enemies = append(g.enemies, &amp;amp;Enemy{
        x: float64(c)+0.5,
        y: float64(r)+0.5,
        w: 0.5,
        h: 0.5,
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after repeating this for civilians, we get this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrrfxbsl0qcdr4brcbjd.png&quot; alt=&quot;Civilian on minimap&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let's start the raycasting, which will allow us to view the 2D world as 3D. The player will cast rays across their field of view. We use the distance the ray travels to draw a vertical line on screen. We can also use the wall's color. First, we have the mathematics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbxcu0lp9o5rqejt0fuf.png&quot; alt=&quot;Ray maths&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is a diagram of our player projecting, and where the screen is in 2D space. One thing we must use is the field of view and width of the screen to get the distance between the player and the screen center. This value will be useful later, but let's define it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jhhpeg3imzuz3bmhce7i.png&quot; alt=&quot;Screen distance&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you know you trigonometry, you'll notice that the tangent of half the field of view is equal to half the screen width over our magic value.&lt;br&gt;
Therefore:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tan(FOV/2) = (SCREEN_WIDTH/2) / SCREEN_DIS
SCREEN_DIS = (SCREEN_WIDTH/2) / tan(FOV/2) // Multiply both sides by SCREEN_DIS and Divide both sides by tan(FOV/2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfect! That's step one done. Now we must cast rays evenly across the screen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jiz03pjwkouhwlkg63tk.png&quot; alt=&quot;Ray angle&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this image, we know &lt;code&gt;i&lt;/code&gt;, and SD (SCREEN_DIS). &lt;code&gt;i&lt;/code&gt; will loop over all pixels on the screen, starting from the far left pixel column, to the far right. We just need to calculate &lt;code&gt;a&lt;/code&gt;, and luckily for us, we've use similar trigonometry just before.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tan(a) = i / SCREEN_DIS
a = atan(i / SCREEN_DIS) // Apply inverse tangent to both sides
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So now we know the angle of each ray we need to shoot. With each ray, we just get the distance and color of the wall we hit, and draw a line based on this information.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (g *Game) drawWorld(screen *ebiten.Image) {
const FOV = 90
const HALF_FOV = float64(FOV&amp;gt;&amp;gt;1)
const HALF_SCREEN_WIDTH = float64(SCREEN_WIDTH&amp;gt;&amp;gt;1)
const HALF_SCREEN_HEIGHT = float64(SCREEN_HEIGHT&amp;gt;&amp;gt;1)

screenDis := HALF_SCREEN_WIDTH / math.Tan(HALF_FOV)

for i := -HALF_SCREEN_WIDTH; i &amp;lt; HALF_SCREEN_WIDTH; i++ {
a := math.Atan(float64(i)/screenDis) + g.player.angle
dis, clr := g.castRay(a)

// Didn't hit anything
if dis == -1 {
continue
}

vector.DrawFilledRect(screen, float32(i+HALF_SCREEN_WIDTH), 10, 1, 100, clr, false)
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We'll add the height calculation later, but now we're ready to cast rays! We cast 2 on each iteration, one for horizontal grid lines, and one for vertical, starting with horizontal. We need the start position of the ray, and how far it travels on each loop iteration. First, the ratio between x and y movemeny.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;ratio := math.Cos(a) / math.Sin(a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we need to find the distance in the y coordinate from our player's position, and the next horizontal line.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;iter := 0
if a == 0 || a == math.Pi { // Perfectly parallel
    // Don't even try
    iter = VIEW_DIS
} else if a &amp;gt; math.Pi { // Looking upwards
    ratio := math.Cos(a) / math.Sin(a)
    sy := float64(int(g.player.y))
    sx := g.player.x + (sy-g.player.y)*ratio
    dy := -1.0
    dx := dy * ratio
} else { // Looking downwards
    ratio := math.Cos(a) / math.Sin(a)
    sy := float64(int(g.player.y))+1
    sx := g.player.x + (sy-g.player.y)*ratio
    dy := 1.0
    dx := dy * ratio
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;iter&lt;/code&gt; will be our counter to see how many times we move the ray forward. In both viewing cases, we first calculate the ratio between moving vertically and horizontally. We round up or down bases on which way we're looking, giving us our starting position. We then get the distance to the next grid line (always 1) and get the correct ratio for x movement.&lt;/p&gt;
&lt;p&gt;We want to simply do 3 thing on each loop iteration.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are we out of bounds?&lt;/li&gt;
&lt;li&gt;Have we hit a wall?&lt;/li&gt;
&lt;li&gt;Move on.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;for iter &amp;lt; VIEW_DIS {
    // Out of bounds?
    if sx &amp;lt; 0 ||
        sy &amp;lt; 0 ||
        sy &amp;gt; float64(len(g.grid)) ||
        sx &amp;gt; float64(len(g.grid[0])) {
        break
    }

    // Check grid collisions
    hit, clr := g.getGridCollision(sx, sy)
    if hit {
        horColor = clr
        horHit = true
        disX := sx-g.player.x
        disY := sy-g.player.y
        horDis = math.Sqrt(disX*disX + disY*disY)
        break
    }

    // Move on
    sx += dx
    sy += dy
    iter++
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We copy the above for vertical check. Now, we just return the nearest collision.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Get correct data
if !horHit &amp;amp;&amp;amp; !verHit {
    return -1, color.RGBA{0, 0, 0, 0}
}

if !horHit || verDis &amp;lt; horDis {
    return verDis, verColor
}

return horDis, horColor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ilr65ahldhaixkfde25.png&quot; alt=&quot;Random white lines&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmqw1npvmceaj9viwrk7.png&quot; alt=&quot;Colored walls&quot;&gt;&lt;/p&gt;
&lt;p&gt;As you can see, we get the color of the wall we're looking at. Now we just have to use the distance to the wall to calculate the visual height.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;height := float32(WALL_HEIGHT / dis * screenDis )
vector.DrawFilledRect(screen, float32(i+HALF_SCREEN_WIDTH), float32(HALF_SCREEN_HEIGHT)-height/2, 1, height, clr, false)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2t9oc32ft8bvk7d9gp9.png&quot; alt=&quot;Real walls&quot;&gt;&lt;/p&gt;
&lt;p&gt;It would be way cooler if we could move the player ourselves, so let's write that now.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (p *Player) update(g *Game) {
if ebiten.IsKeyPressed(ebiten.KeyA) {
p.angle -= 0.04
}

if ebiten.IsKeyPressed(ebiten.KeyD) {
p.angle += 0.04
}

if ebiten.IsKeyPressed(ebiten.KeyW) {
dx := math.Cos(p.angle) * 0.1
dy := math.Sin(p.angle) * 0.1
p.x += dx
p.y += dy
}

if ebiten.IsKeyPressed(ebiten.KeyS) {
dx := math.Cos(p.angle) * 0.1
dy := math.Sin(p.angle) * 0.1
p.x -= dx
p.y -= dy
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jrosgl30xhj17dhckcm.png&quot; alt=&quot;Nice walls&quot;&gt;&lt;/p&gt;
&lt;p&gt;We've got a warping issue here, as a flat wall should appear, well, flat. The reason for this is because the rays furthest from the center will have to travel further to hit this wall. Therefore, we need to help out these rays so that our visuals think they hit sooner.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;a = g.player.angle - a
dis *= math.Cos(a)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3quwbet5n4adqrlmgbw.png&quot; alt=&quot;Shaded walls&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now for the civilians. Our civilians are going to be angry blobs. Let's loop over all civilians, and draw an image where they are.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (g *Game) drawEntities(screen *ebiten.Image) {
const FOV = math.Pi/2
const HALF_FOV = float64(FOV/2)
const HALF_SCREEN_WIDTH = float64(SCREEN_WIDTH&amp;gt;&amp;gt;1)

screenDis := HALF_SCREEN_WIDTH / math.Tan(HALF_FOV)

for _, civil := range g.civils {
dx := civil.x - g.player.x
dy := civil.y - g.player.y
dis := math.Sqrt(dx*dx + dy*dy)

// Relative angle to civil
a := math.Acos(dx/dis)
if dy &amp;lt; 0 {
a = -a
}
a -= g.player.angle
if a &amp;gt; HALF_FOV &amp;amp;&amp;amp; a &amp;lt; math.Pi*2-HALF_FOV {
// Civil isn't in our view
continue
}

// What pixel column the center of the enemy will be in
i := math.Tan(a) * screenDis + HALF_SCREEN_WIDTH

dis *= math.Cos(a)
height := CIVIL_HEIGHT * screenDis / dis
maxHeight := WALL_HEIGHT * screenDis / dis
op := ebiten.DrawImageOptions{}
op.GeoM.Scale(height/float64(imgH), height/float64(imgH))
op.GeoM.Translate(i-float64(height/2), HALF_SCREEN_HEIGHT+maxHeight/2-height)
screen.DrawImage(img, &amp;amp;op)
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All we're doing here is getting the angle to the civilian, checking they're in our view, then drawing an image where they should be. Etra logic is required to place the civilian on the floor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64dahnlv2mjbwu8lze10.png&quot; alt=&quot;Look, an enemy!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Looks good! But the one on the left is in the wall, so we must fix occlusion. We find the distance to the walls, then just check if the enemy is behind them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Z buffer definition
zBuffer := [SCREEN_WIDTH]float64{}

// On each ray case
zBuffer[int(i+HALF_SCREEN_WIDTH)] = dis

// Passing info between drawing functions
zBuffer := g.drawWorld(screen)
g.drawEntities(screen, zBuffer)

// Occlusion
if zBuffer[int(i)] &amp;lt; dis &amp;amp;&amp;amp; zBuffer[int(i)] != -1 {
    continue
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works quite well, and will work for our make in a day case. Now we must be able to save these civilians!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (p *Player) saveCivils(g *Game) {
for i := 0; i &amp;lt; len(g.civils); i++ {
civil := g.civils[i]
dx := civil.x - p.x
dy := civil.y - p.y
dis := math.Sqrt(dx*dx + dy*dy)
if dis &amp;lt; 1 {
// Save the civil!
g.civils = slices.Delete(g.civils, i, i+1)
i--
}
}
}

if len(g.civils) == 0 {
    g.phase = WIN
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as we get close enough to a civilian, we delete them. Then in the game's update we can simply check if there are any civilians left. And this works!&lt;/p&gt;
&lt;p&gt;After generalising sprite drawing code, we can &lt;em&gt;easily&lt;/em&gt; draw enemies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (g *Game) drawEntities(screen *ebiten.Image, zBuffer [SCREEN_WIDTH]float64) {
const CIVIL_HEIGHT = 0.4
const ENEMY_HEIGHT = 0.8

civilImg := g.vis.GetImage(&quot;civil&quot;)
for _, civil := range g.civils {
g.drawSprite(screen, civilImg, CIVIL_HEIGHT, civil.x, civil.y, zBuffer)
}

enemyImg := g.vis.GetImage(&quot;enemy&quot;)
for _, enemy := range g.enemies {
g.drawSprite(screen, enemyImg, ENEMY_HEIGHT, enemy.x, enemy.y, zBuffer)
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can use similar code used for saving civilians to check if we've lost&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (p *Player) checkKilled(g *Game) {
for i := 0; i &amp;lt; len(g.enemies); i++ {
enemy := g.enemies[i]
dx := enemy.x - p.x
dy := enemy.y - p.y
dis := math.Sqrt(dx*dx + dy*dy)
if dis &amp;lt; 0.3 {
// We die!
g.phase = LOSE
}
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice, but now we need defense! We can case a drawing ray, ignore color, and use it for the shot angle and distance, to find if we hit any enemies.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (p *Player) shoot(g *Game) {
maxDis, _ := g.castRay(p.angle)
if maxDis == -1 {
maxDis = 99999.9
}

const FOV = math.Pi/2
const HALF_FOV = float64(FOV/2)

for i := 0; i &amp;lt; len(g.enemies); i++ {
enemy := g.enemies[i]
dx := enemy.x - g.player.x
dy := enemy.y - g.player.y
dis := math.Sqrt(dx*dx + dy*dy)

if dis &amp;gt; maxDis {
continue
}

// Relative angle to enemy
a := math.Acos(dx/dis)
if dy &amp;lt; 0 {
a = -a
}
a -= g.player.angle
a = boundAngle(a)
if a &amp;gt; HALF_FOV &amp;amp;&amp;amp; a &amp;lt; math.Pi*2-HALF_FOV {
// Enemy isn't in our view
continue
}

// Horizontal offset (0 means we hit the enemies center)
offset := math.Abs(math.Tan(a) * dis)

if offset &amp;gt; 0.3 {
continue
}

// We hit the enemy!
g.enemies = slices.Delete(g.enemies, i, i+1)
i--
}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This mashing of code uses the offset to determine a hit. An offset of 0 means a perfect hit, with 1 meaning you were a whole tile off. Let's get the enemy to chase the player, so we'll use Dijkstra's to find them first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func (e *Enemy) update(g *Game) {
// Try to move to next position
if e.x != e.nextX {
if math.Abs(e.nextX - e.x) &amp;lt; 0.01 {
e.x = e.nextX
} else if e.nextX &amp;gt; e.x {
e.x += 0.01
} else {
e.x -= 0.01
}
return
}

if e.y != e.nextY {
if math.Abs(e.nextY - e.y) &amp;lt; 0.01 {
e.y = e.nextY
} else if e.nextY &amp;gt; e.y {
e.y += 0.01
} else {
e.y -= 0.01
}
return
}

startX := int(e.x)
startY := int(e.y)
endX := int(g.player.x)
endY := int(g.player.y)

type SpaceInfo struct {
visited bool
dis int
parentX, parentY int
}

type Pos struct {
x, y int
parentX, parentY int
}

spaceInfo := make([][]SpaceInfo, len(g.grid))
for r := range len(g.grid) {
spaceInfo[r] = make([]SpaceInfo, len(g.grid[0]))
for c := range len(g.grid) {
spaceInfo[r][c].dis = 99999
}
}

spaceInfo[startY][startX].dis = 0
spaceInfo[startY][startX].parentX = startX
spaceInfo[startY][startX].parentY = startY

queue := []Pos{{startX, startY, startX, startY}}

var pos Pos

for len(queue) != 0 {
pos = queue[0]
queue = queue[1:]

if pos.x &amp;lt; 0 || pos.y &amp;lt; 0 || pos.y &amp;gt;= len(g.grid) || pos.x &amp;gt;= len(g.grid[0]) {
continue
}

if g.grid[pos.y][pos.x].solid {
continue
}

if pos.x == endX &amp;amp;&amp;amp; pos.y == endY {
break
}

newDis := getMovementDistance(startX, startY, pos.x, pos.y)

if spaceInfo[pos.y][pos.x].visited {
if spaceInfo[pos.y][pos.x].dis &amp;lt;= newDis {
continue
}
}

spaceInfo[pos.y][pos.x].visited = true
spaceInfo[pos.y][pos.x].dis = newDis
spaceInfo[pos.y][pos.x].parentX = pos.parentX
spaceInfo[pos.y][pos.x].parentY = pos.parentY

queue = append(queue, Pos{pos.x-1, pos.y, pos.x, pos.y})
queue = append(queue, Pos{pos.x+1, pos.y, pos.x, pos.y})
queue = append(queue, Pos{pos.x, pos.y-1, pos.x, pos.y})
queue = append(queue, Pos{pos.x, pos.y+1, pos.x, pos.y})
}

// Couldn't find path
if pos.x != endX || pos.y != endY {
return
}

curTile := spaceInfo[pos.parentY][pos.parentX]
nextTile := spaceInfo[curTile.parentY][curTile.parentX]

for curTile.parentX != startX || curTile.parentY != startY {
pos.x = curTile.parentX
pos.y = curTile.parentY
curTile = nextTile
nextTile = spaceInfo[curTile.parentY][curTile.parentX]
}

// Now we have the position of the next tile, set the enemy there
e.nextX = float64(pos.x)+0.25
e.nextY = float64(pos.y)+0.25
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just a bit of code... Either way, it works! Now we have a fairly playable game. For collision, we can just check that the player isn't trying to move into a wall.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Player update
newX := p.x
newY := p.y

if ebiten.IsKeyPressed(ebiten.KeyW) {
    dx := math.Cos(p.angle) * 0.05
    dy := math.Sin(p.angle) * 0.05
    newX += dx
    newY += dy
}

if ebiten.IsKeyPressed(ebiten.KeyS) {
    dx := math.Cos(p.angle) * 0.05
    dy := math.Sin(p.angle) * 0.05
    newX -= dx
    newY -= dy
}

if !g.checkCollide(newX, newY, p.w, p.h) {
    p.x = newX
    p.y = newY
}

func (g *Game) checkCollide(x, y, w, h float64) bool {
c := int(x)
r := int(y)
t := g.getTileAtPos(float64(c), float64(r))
if t != nil {
if t.solid {
return true
}
}

c = int(x+w)
r = int(y)
t = g.getTileAtPos(float64(c), float64(r))
if t != nil {
if t.solid {
return true
}
}

c = int(x)
r = int(y+h)
t = g.getTileAtPos(float64(c), float64(r))
if t != nil {
if t.solid {
return true
}
}

c = int(x+w)
r = int(y+h)
t = g.getTileAtPos(float64(c), float64(r))
if t != nil {
if t.solid {
return true
}
}

return false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it, the last check on the checklist.&lt;/p&gt;
&lt;h3 id=&quot;done&quot;&gt;Done&lt;/h3&gt;
&lt;p&gt;Wow, I decided to come back with a tougher one here, and it was quite fun.  Hopefully from this you understand the rendering we used, and would be able to tinker with the logic yourself. Which leads me to &lt;em&gt;your&lt;/em&gt; challenges.&lt;/p&gt;
&lt;h3 id=&quot;your-next-steps&quot;&gt;Your Next Steps&lt;/h3&gt;
&lt;p&gt;You've go the code &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Chig-Beef/MakeInADay&quot;&gt;here&lt;/a&gt; for you to look at and modify, so here are some ideas for you.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Walls can be &lt;em&gt;any&lt;/em&gt; color&lt;/li&gt;
&lt;li&gt;Add different enemy types&lt;/li&gt;
&lt;li&gt;Walls with different heights&lt;/li&gt;
&lt;li&gt;Textured walls&lt;/li&gt;
&lt;li&gt;Jumping&lt;/li&gt;
&lt;li&gt;Non-perpendicular walls&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I haven't had the spare day to make one of these in a while, so hopefully I get another one soon, any suggestions would help!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/4793/make-in-a-day-hovertank-one</guid>
<pubDate>Mon, 18 Aug 2025 11:08:26 +0000</pubDate>
</item>
<item>
<title>The Pygame Framework I Didn’t Plan: Building Tic-Tac-Toe with Asyncio and Events</title>
<link>https://coderlegion.com/3729/the-pygame-framework-i-didnt-plan-building-tic-tac-toe-with-asyncio-and-events</link>
<description>&lt;p&gt;Over the last weekend, I spent some time mucking around with &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pygame.org/news&quot;&gt;Pygame&lt;/a&gt;. Despite facing some challenges along the way, I ended up with a working prototype. There was neither design nor specification drafted for the project. Nonetheless, mid-development, a reusable design emerged, and we discussed that in last week’s article. Following that discovery, we will explore the implementation this week and answer the question ‚ “Can we make a framework out of it?”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*EFBQUlLgruEaJ-xBPLUYCQ.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Cute illustration from Copilot&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-unexpected-framework-from-pygame-hacks-to-a-reusable-design&quot;&gt;The Unexpected Framework: From Pygame Hacks to a Reusable Design&lt;/h4&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/understanding-awaitables-coroutines-tasks-and-futures-in-python-gk7&quot;&gt;Understanding Awaitables: Coroutines, Tasks, and Futures in Python&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As a brief recap to the &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/my-pygame-evolution-embracing-asyncio-and-immutability-for-scalable-design-g55&quot;&gt;last article&lt;/a&gt;, we know how Pygame is designed to be primitive, and lacks inherent structure. Building a Pygame application would therefore require a developer to begin by writing the main application loop. On the other hand, this also presents an opportunity to design a bespoke structure for graphical application development. By refactoring our work from last week, we will witness how a framework emerges, while adhering the original design goal to stay immutable and reliable.&lt;/p&gt;
&lt;p&gt;Adapting the synchronous main application loop to AsyncIO took quite some effort. Despite the complexity involved, it was worth it because it broke the tight coupling of display update and event dispatching loop. Previously, events were only fetched once per display update cycle. If the application was written in a pure event driven manner, delegating work into multiple events would introduce latency.&lt;/p&gt;
&lt;p&gt;Now that both event dispatching and display update run independently, ensuring synchronization of application states between the two components is also crucial. Immutability was therefore introduced such that application data can only be changed through publishing changes to relevant queues.&lt;/p&gt;
&lt;p&gt;With all the work laid out, this opens up an interesting potential, allowing the orchestration code for event dispatching and display updates to be split from the game logic. Application setup work, such as event handler registrations and the initialization of application-specific data, is the primary area that differs from one application to another.&lt;/p&gt;
&lt;p&gt;Sounds like a plan, let’s split it out, and complete the tic-tac-toe game we started.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*ZM4TOBEllAcdECvK&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Daniel McCullough on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;building-the-core-immutable-state-and-asynchronous-orchestration&quot;&gt;Building the Core: Immutable State and Asynchronous Orchestration&lt;/h4&gt;
&lt;p&gt;It shouldn’t be difficult, right? Given the design goal mentioned earlier, the splitting work was indeed not too difficult. On the other hand, keeping track of the declared dataclasses is another story. Regardless, we will uncover an architectural pattern that forms the framework core through some refactoring work. Some minor revisions will be made to the state management, as well as the main application loop. All this work yields a reusable framework for simple graphical applications.&lt;/p&gt;
&lt;p&gt;Previously we were creating multiple queues for different fields in our Application dataclass. Having multiple queues serving similar purpose to update values is fine, though I personally find it wasteful. Why not just consolidate them?&lt;/p&gt;
&lt;p&gt;And I did exactly that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*pbPfF-uj2A66b_aR&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by USGS on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Firstly, we start by introducing an Enum to represent the field we intend to update&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ApplicationDataField(Enum):
    STATE = auto()
    ELEMENTS = auto()
    EVENTS = auto()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Secondly, we update the DeltaOperation to always require the field we intend to update&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass
class DeltaOperation(ABC):
    field: ApplicationDataField
    item: Any
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we need to update the application_refresh function, which is the only place where the Application object is updated throughout the framework.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def application_refresh(application: Application) -&amp;gt; Application:
    result = application

    with suppress(asyncio.queues.QueueEmpty):
        while delta := application.delta_data.get_nowait():
            field = application_get_field(application, delta.field)

            match delta:
                case DeltaAdd():
                    result = replace(
                        result,
                        **{field: getattr(result, field) + (delta.item,)},
                    )

                case DeltaUpdate():
                    result = replace(
                        result,
                        **{
                            field: tuple(
                                delta.new if item == delta.item else item
                                for item in getattr(result, field)
                            )
                        },
                    )

                case DeltaDelete():
                    result = replace(
                        result,
                        **{
                            field: tuple(
                                item
                                for item in getattr(result, field)
                                if not item == delta.item
                            )
                        },
                    )

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actual implementation is a little bit longer, but I truncated for brevity. Logically, the current version is rather similar to the previous version, though it only consumes from one single queue (application.delta_data) to update the states in an Application object.&lt;/p&gt;
&lt;p&gt;After the minor refactoring to the state management, we shift our attention to the main application loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def main_loop(
    application_setup: Awaitable[Application],
) -&amp;gt; None:
    pygame.init()

    application = await application_setup

    pygame.event.post(pygame.event.Event(SystemEvent.INIT.value))

    tasks = []

    tasks.append(
        asyncio.create_task(
            display_update(
                application.delta_screen,
                application.clock,
            )
        )
    )
    tasks.append(
        asyncio.create_task(events_process(application))
    )

    await application.exit_event.wait()

    pygame.quit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is almost similar to the original run function in our original code, with just 2 small changes. Remember I mentioned about wanting a window.onload like system events? Here we are emitting an INIT event before we start the display update and event dispatching loops. Additionally, setup code is passed in as &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/p/ea7ed54ec705/edit&quot;&gt;an&lt;/a&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/p/ea7ed54ec705/edit&quot;&gt;Awaitable object&lt;/a&gt;, that is only scheduled to run after pygame.init().&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/understanding-awaitables-coroutines-tasks-and-futures-in-python-gk7&quot;&gt;Understanding Awaitables: Coroutines, Tasks, and Futures in Python&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That’s practically it, though extracting them out to another module, is a pain due to how these functions reference numerous dataclasses and other helpers. Alongside these changes, I also added some helpers to deal with in-game state management. Spiritually, the design of these helper functions loosely follows &lt;a rel=&quot;nofollow&quot; href=&quot;https://redux.js.org/&quot;&gt;Redux&lt;/a&gt;, though the implementation is a lot simpler in my implementation. We shall see them in action when we go through the game implementation next.&lt;/p&gt;
&lt;h4 id=&quot;framework-in-action-bringing-tic-tac-toe-to-life&quot;&gt;Framework in Action: Bringing Tic-Tac-Toe to Life&lt;/h4&gt;
&lt;p&gt;Framework is now done, what’s next?&lt;/p&gt;
&lt;p&gt;What’s a better way to test the framework, than building a game for real? Now that we delegate the orchestration work to another module, we can now focus purely on the game logic. Throughout the discussion, we can observe how the framework design enables building of a simple game with just dispatching events and implementing the handlers. Game state management as mentioned earlier will be discussed as well.&lt;/p&gt;
&lt;p&gt;Firstly, let’s set-up the game:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def setup() -&amp;gt; Application:
    application = Application(screen=pygame.display.set_mode((300, 300)))
    application = await add_event_listeners(
        application,
        (
            (SystemEvent.INIT.value, handle_init),
            (CustomEvent.RESET.value, handle_reset),
            (CustomEvent.MAP_UPDATE.value, handle_update),
            (CustomEvent.JUDGE.value, handle_judge),
        ),
    )

    return application
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A new helper add_event_listeners was written for adding multiple event listeners, though they work similarly as the add_event_listener we discussed last week. In case you are wondering at this point, yes, this is all we needed to implement our tic-tac-toe game. Since we split out the orchestration code to another module, our run function now becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from core import main_loop

async def run() -&amp;gt; None:
    await main_loop(setup())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember main_loop() takes an Awaitable as argument? This is the reason why setup() was written as a coroutine function, and passed into main_loop function without the await keyword.&lt;/p&gt;
&lt;p&gt;Before we start the display update and event dispatching cycles, let’s initialize the game by drawing relevant elements to the application screen object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_init(
    _event: pygame.event.Event, target: Application, logger: BoundLogger, **detail: Any
):
    for row, col in product(range(3), range(3)):
        element = await box_draw(target, row, col, 100)
        element = await add_event_listener(
            element,
            pygame.MOUSEBUTTONDOWN,
            handle_box_click,
        )
        await target.delta_data.put(DeltaAdd(ApplicationDataField.ELEMENTS, element))
        await screen_update(target, element) # type: ignore

    pygame.event.post(pygame.event.Event(CustomEvent.RESET.value))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;During the initialization phase, the handler draws the 3 x 3 tiles to the screen via box_draw, and then registers a mouse click event to each of them. Each tile is then sent to 2 queues, one to populate the application.elements tuple (via target.delta_data.put), and another to update the display (via screen_update). Once done, the handler invokes the RESET event to start a new game.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_reset(
    _event: pygame.event.Event, target: Application, logger: BoundLogger, **detail: Any
):
    await state_merge(target, &quot;board&quot;, winner=None, state=GameState.RING, board=None)

    for element in target.elements.values():
        delta = await box_redraw(target, (255, 255, 255), element, Symbol.EMPTY) # type: ignore
        await target.delta_data.put(
            DeltaUpdate(ApplicationDataField.ELEMENTS, element, delta)
        )
        await screen_update(target, delta)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the game is resetting, it first reinitializes the game state via state_merge.The helper sends a DeltaOperation to update application.state to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;board&quot;: {
        &quot;winner&quot;: None,
        &quot;state&quot;: GameState.END,
        &quot;board&quot;: None
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Elements in the game board also get redrawn (via box_redraw) to ensure they do not contain a ring or cross symbol. As we did earlier, all of them are then sent to the queues for application and display update.&lt;/p&gt;
&lt;p&gt;Our game is now accepting input. And once a click is registered, the registered event handler is called.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def handle_box_click(
    _event: pygame.event.Event,
    target: Box,
    application: Application,
    logger: BoundLogger,
    **detail: Any,
) -&amp;gt; None:
    element = None

    match state_get(application, &quot;board&quot;).get(&quot;state&quot;):
        case GameState.END:
            await logger.ainfo(&quot;END&quot;)
            pygame.event.post(pygame.event.Event(CustomEvent.RESET.value))

        case GameState.TIE:
            await logger.ainfo(&quot;TIE&quot;)
            pygame.event.post(pygame.event.Event(CustomEvent.RESET.value))

        case GameState.RING if target.value == Symbol.EMPTY:
            element = await box_update(
                application,
                target,
                Symbol.RING,
            )
            await state_merge(application, &quot;board&quot;, state=GameState.CROSS)

        case GameState.CROSS if target.value == Symbol.EMPTY:
            element = await box_update(
                application,
                target,
                Symbol.CROSS,
            )
            await state_merge(application, &quot;board&quot;, state=GameState.RING)

        case _:
            await logger.aerror(&quot;Invalid click&quot;)

    if element:
        pygame.event.post(pygame.event.Event(CustomEvent.MAP_UPDATE.value))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Click events are handled differently, depending on the state of the board. If there is no valid moves available, in the events of an ended or tie game, then we log a message, and emit a RESET event. Otherwise, we update the clicked element to the symbol corresponding to the current state of the board, followed by a board state change for the next symbol. Lastly, we trigger a MAP_UPDATE event to update the current mapping of the board for judging.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass
class BoardMap:
    mapping: dict[tuple[int, int], Symbol]

async def handle_update(
    _event: pygame.event.Event, target: Application, logger: BoundLogger, **detail: Any
):
    await state_merge(
        target,
        &quot;board&quot;,
        map=BoardMap(
            {
                (item.column, item.row): item.value
                for item in target.elements.values()
                if isinstance(item, Box)
            }
        ),
    )

    pygame.event.post(pygame.event.Event(CustomEvent.JUDGE.value))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BoardMap is just a dataclass keeping the current state of the board, what symbol is placed at each tile. As soon as it is updated, a JUDGE event is triggered to evaluate the game.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WIN_LINES = [
    # Rows
    {(0, 0), (0, 1), (0, 2)},
    {(1, 0), (1, 1), (1, 2)},
    {(2, 0), (2, 1), (2, 2)},
    # Columns
    {(0, 0), (1, 0), (2, 0)},
    {(0, 1), (1, 1), (2, 1)},
    {(0, 2), (1, 2), (2, 2)},
    # Diagonals
    {(0, 0), (1, 1), (2, 2)},
    {(0, 2), (1, 1), (2, 0)},
]

async def handle_judge(
    _event: pygame.event.Event, target: Application, logger: BoundLogger, **detail: Any
):
    if not (bmap := state_get(target, &quot;board&quot;).get(&quot;map&quot;)):
        return

    tiles, winner = (), None

    for symbol in (Symbol.RING, Symbol.CROSS):
        places = {coor for coor, item in bmap.mapping.items() if symbol == item}

        for line in WIN_LINES:
            if line.issubset(places):
                winner = symbol
                tiles = line
                break

        if winner:
            break

    if winner:
        await logger.ainfo(&quot;Winner is found&quot;, winner=winner)
        await state_merge(target, &quot;board&quot;, winner=winner, state=GameState.END)

        for element in target.elements.values():
            if (element.column, element.row) in tiles: # type: ignore
                delta = await box_redraw(target, (255, 255, 0), element, winner) # type: ignore
                await target.delta_data.put(
                    DeltaUpdate(ApplicationDataField.ELEMENTS, element, delta)
                )
                await screen_update(
                    target,
                    delta, # type: ignore
                )
    elif (
        len([symbol for _, symbol in bmap.mapping.items() if symbol == Symbol.EMPTY])
        == 0
    ):
        await logger.ainfo(&quot;End with a tie&quot;)
        await state_merge(target, &quot;board&quot;, state=GameState.TIE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not the most robust code I would proudly print on a t-shirt and parade with it. Essentially, it judges the game to find the winning line and symbol. Redrawing the winning tiles will take place whenever applicable. Besides that, the relevant state of the board is also queued for update (either an END or TIE) whenever needed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*9hZL4Bm3asPWiieXpsof8Q.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;By utilizing events and state_merge strategically, the simple game of tic-tac-toe is implemented. All the event dispatching work and display update are completely handled by the framework itself. All we needed to do, when changes occur, is to submit the change to the relevant queue. While there are certainly potential improvements to be made, as a weekend project I deem it good enough.&lt;/p&gt;
&lt;h4 id=&quot;beyond-tic-tac-toe-lessons-learned-and-what-s-next&quot;&gt;Beyond Tic-Tac-Toe: Lessons Learned and What’s Next&lt;/h4&gt;
&lt;p&gt;Can we build more things with it?&lt;/p&gt;
&lt;p&gt;Short answer to the question is yes, but I don’t see much point in doing so for most people. Instead, we should reflect on what we learned so far. What is the purpose of the project? What have we achieved so far? What do we do with this piece of code?&lt;/p&gt;
&lt;p&gt;Extracting the orchestration code into a framework is a totally unexpected outcome. Yet, I am happy with the turns of events leading to what we have now. With some polish, the resulting framework should be able to be applied for other simple graphical applications. Personally, I am quite curious on seeing how the framework scales with more elements on screen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*fEbkPFllr5iRzZ_h&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Dan Freeman on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As an experimental project, it certainly is quite rough on the edges. Much thought and polish are still needed in the framework API design for consistency. There are also some experimental code snippets that are not covered in the article as they are irrelevant to the game featured this week. Some of the most interesting helpers I can’t wait to try out are the functions inspired by setTimeout and element.dispatchEvent().&lt;/p&gt;
&lt;p&gt;Perhaps a re-implementation of &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/code-rains-revelation-embracing-existence-before-perfection-60f5c641963a&quot;&gt;the code rain animation a-la the Matrix&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/code-rains-revelation-embracing-existence-before-perfection-2mnc&quot;&gt;Code Rain’s Revelation: Embracing Existence Before Perfection&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, back to the original question asked earlier, at the beginning of this section. The framework is proven capable of building a simple game, as shown in this article. Despite that, do go for a proper game engine like Godot for serious projects. Treat the work presented here as a personal passion project, though I am open for feedback and collaboration. Seriously, this exploration makes me appreciate how game engines do all the heavy-lifting work.&lt;/p&gt;
&lt;p&gt;Finally, thanks for reading this far. If you are interested in checking out the project, it is still hosted on GitHub at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/pygame-ttt/&quot;&gt;the same repository&lt;/a&gt;. Though I cannot commit to a fixed publication schedule due to my new job, I shall try writing again soon.&lt;/p&gt;
&lt;p&gt;A quick note on how this article came together. While Gemini helped with the drafting and refining of the language, all the core ideas, the technical insights, and every line of code you’ve seen are entirely mine. This project truly reflects my personal journey and passion for game development. If you enjoyed this deep dive and want to follow more of my explorations into coding and game development, consider subscribing or following me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/&quot;&gt;Medium&lt;/a&gt;. Your support means a lot!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/3729/the-pygame-framework-i-didnt-plan-building-tic-tac-toe-with-asyncio-and-events</guid>
<pubDate>Sat, 05 Jul 2025 13:46:49 +0000</pubDate>
</item>
<item>
<title>My Pygame Evolution: Embracing Asyncio and Immutability for Scalable Design</title>
<link>https://coderlegion.com/3469/my-pygame-evolution-embracing-asyncio-and-immutability-for-scalable-design</link>
<description>&lt;p&gt;Maintaining a blog is a lot of work, especially when it comes to finding new ideas. Over the weekend, I explored &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pygame.org/news&quot;&gt;Pygame&lt;/a&gt; by building a simple game. I was very humbled by the experience throughout the build. Aha, this exploration turned out to be a worthy idea for a new article! Read on to relive the struggles involved while building a graphical game via Pygame together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/1*cdjhvr9v3dw_IwtLW6GqXw.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;A cute illustration on the topic, by Microsoft Copilot&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-tic-tac-toe-catalyst-unveiling-pygame-s-low-level-nature&quot;&gt;The Tic-Tac-Toe Catalyst: Unveiling Pygame’s Low-Level Nature&lt;/h4&gt;
&lt;p&gt;Over the weekend, in the EngineersMY chatroom, a friend shared his work in a recent game jam held in his workplace. He built the project with &lt;a rel=&quot;nofollow&quot; href=&quot;https://p5js.org/&quot;&gt;p5.js&lt;/a&gt;, piqued my interest. Eventually I decided to proceed with Pygame, and that decision led me to hours of exploration and perhaps the expense of a few hair follicles. Despite that, the lower-level library nudged me to build a custom architectural design that can potentially evolve into a proper framework.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*eNY9cVdgE87-J49_&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Ryan Snaadt on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Discussions about my friend’s project reminded me of a less than ideal job application that happened around late last year. The offered package was unattractive, and I was expected to work on graphical animation alongside full-stack development. My submitted video processing pipeline work received satisfactory feedback, though the hiring manager came back with another assignment. He requested a new task: to build a graphical application with p5.js, and it was also when I formally ended the application. That mention of p5.js was the first time in a formal setting.&lt;/p&gt;
&lt;p&gt;Interestingly, I was still given a job offer despite all that, which I ultimately rejected.&lt;/p&gt;
&lt;p&gt;Fast forward to the weekend, I consulted with my AI assistant and started my research about &lt;a rel=&quot;nofollow&quot; href=&quot;https://processing.org/&quot;&gt;Processing&lt;/a&gt;, a project where p5.js was based on. Apparently there are ports for Python, though the official processing.py only works with Jython. All other alternatives either relied on the Java package or required special libraries. Consequently, I stumbled upon Pygame after a series of aimless searches.&lt;/p&gt;
&lt;p&gt;Unlike Processing, Pygame felt like a lower level library for building graphical applications. Partially inspired by the discussion on the game jam, but mostly out of boredom, I decided to give it a try. The realization of how low-level Pygame is came when I was figuring out the specification and scope of my over-ambitious initial plan. At the time, my main loop was just barely running.&lt;/p&gt;
&lt;p&gt;As a project to explore graphical application building, I eventually decided to limit the scope further to start small. &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Tic-tac-toe&quot;&gt;Tic-tac-toe&lt;/a&gt; seemed like a good idea, and I should be able to build a reasonably functional version over a weekend. How wrong I was, as I am still doing some last minute changes as of writing this article…&lt;/p&gt;
&lt;h4 id=&quot;building-a-reactive-core-event-driven-design-in-pygame&quot;&gt;Building a Reactive Core: Event-Driven Design in Pygame&lt;/h4&gt;
&lt;p&gt;Pygame is synchronous, and most of the objects and operations are not thread-safe. Due to the interactive nature of graphical applications, they often require a carefully planned strategy to achieve concurrency. AsyncIO is an obvious candidate for the task, though getting it to work with Pygame, requires some thought. Much of the implementation takes a cue from Javascript, especially in the event dispatching department. Immutability of certain components is also crucial, though mainly to keep me sane.&lt;/p&gt;
&lt;p&gt;Let’s start with the main loop, essentially we want something like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def run() -&amp;gt; None:
    pygame.init()

    continue_to_run = True
    clock = pygame.time.Clock()

    pygame.display.set_mode((300, 300))

    while continue_to_run:
        clock.tick(60)

        continue_to_run = event_process(pygame.event.get())

        pygame.display.flip()

    pygame.quit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A potential problem I see with this arrangement is that, both handle_events and pygame.display.flip() are inevitably blocking. If any of them stalled for a prolonged period, we will miss the target frame rate. Ideally, we want something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def run():
    pygame.init()

    exit_event = asyncio.Event()

    pygame.display.set_mode((300, 300))

    asyncio.create_task(display_update())
    asyncio.create_task(event_process(exit_event))

    await exit_event.wait()

    pygame.quit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*Q7gEPaYVeZW316KJ&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Acton Crawford on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A window popped up, and my event handling work seemed functional. Yet, sometimes when the application exits I get a SIGSEGV segmentation fault when exiting the program. After some research on the error, I found out that Pygame is not thread-safe, hence I should really avoid asyncio.to_thread. This means the following code, delegating the blocking display.flip() call to another thread, is invalid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def display_update():
    clock = pygame.time.Clock()

    while True:
        clock.tick(60)

        await asyncio.to_thread(pygame.display.flip)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that discovery, I settled with the original main loop convention for a while, and only moved the event dispatching work to a coroutine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def run() -&amp;gt; None:
    pygame.init()

    clock = pygame.time.Clock()
    exit_event = asyncio.Event()

    pygame.display.set_mode((300, 300))

    while not exit_event.is_set():
        clock.tick(60)

        asyncio.create_task(event_process(pygame.event.get(), exit_event))

        pygame.display.flip()

    pygame.quit()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, due to the synchronous blocking nature of Pygame, the event_process tasks do not have a chance to run, though scheduled successfully. As my coding companion, the chatbot suggested adding asyncio.sleep(0) into the loop. According to &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.python.org/3/library/asyncio-task.html#sleeping&quot;&gt;the documentation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Setting the delay to 0 provides an optimized path to allow other tasks to run. This can be used by long-running functions to avoid blocking the event loop for the full duration of the function call.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adding the statement to the end of the loop allows the application to finally respond to events.&lt;/p&gt;
&lt;p&gt;Previously, I mentioned I performed a last-minute update while writing this article. With this knowledge, we can apply the asyncio.sleep(0) fix to both display_update and event_process, enabling AsyncIO to run the scheduled tasks. In turn, this allows the refactoring of the huge main loop shown in the first snippet into the second snippet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*S7jVmr9jJnjbRz-x&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Scott Rodgerson on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Before diving into the event dispatching part of the design, let’s talk about how I manage shared data. Certain components need to be shared across different parts of the application. Graphical applications are extremely interactive, hence I would need to respond to them concurrently. In this case, interactivity poses a new challenge — keeping track of changes of these shared objects.&lt;/p&gt;
&lt;p&gt;Much of the design came from my experience with development work in JavaScript. Within a JavaScript application, it is possible to dispatch and subscribe to events almost everywhere, and changes can also be made to a page whenever we see fit. Keeping a reference while staying aware of these changes is a nightmare, and it is extremely unfriendly to my hair follicle.&lt;/p&gt;
&lt;p&gt;Grouping them together into one dataclass, which serves as a registry is usually my first step. In this case, I prefer them to stay immutable, as shown below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass(frozen=True)
class Application:
    screen: pygame.Surface = pygame.Surface((0, 0))
    elements: tuple[Element, ...] = tuple()
    state: GameState = GameState.INIT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The three objects consist of a pygame.Surface object for the main application window, an element tuple holding all the interactive widgets in the surface, and an Enum holding the current game state. Events are dispatched to be executed concurrently, so each execution is completely isolated from one another. Therefore, we need to synchronize the changes made to these shared objects to avoid race conditions.&lt;/p&gt;
&lt;p&gt;Aiming to maintain the simplicity inspired by functional programs, I started by marking the dataclass as frozen, as shown in the snippet. Changes to the elements and state objects are deferred to two queues I introduced to the registry dataclass.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@dataclass(frozen=True)
class Application:
    ...

    state_update: asyncio.Queue[GameState] = asyncio.Queue()
    element_delta: asyncio.Queue[DeltaOperation] = asyncio.Queue()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Making the dataclass immutable would require a regeneration of application registry for every batch of events.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def events_process(application: Application):
    while True:
        application = await application_refresh(application)

        await events_dispatch(
            application,
            pygame.event.get(),
        )
        
        await asyncio.sleep(0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever an event handler makes a change to the game state or manipulates the widgets on screen, the changes are pushed to the queue instead. Then the queue is consumed, to update the corresponding object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from dataclasses import replace

async def application_refresh(application: Application) -&amp;gt; Application:
    with suppress(asyncio.queues.QueueEmpty):
        elements = application.elements

        while delta := application.element_delta.get_nowait():
            match delta:
                case ElementAdd():
                    elements += (delta.item,)

                case ElementUpdate():
                    elements = tuple(
                        delta.new if element == delta.item else element
                        for element in elements
                    )

                case ElementDelete():
                    elements = tuple(
                        element for element in elements if not element == delta.item
                    )

    with suppress(asyncio.queues.QueueEmpty):
        state = application.state

        while state_new := application.state_update.get_nowait():
            state = state_new

    return replace(
        application,
        elements=elements,
        state=state
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrapping the widget element with an ElementAdd, an ElementUpdate and an ElementDelete is just a way to provide metadata to indicate the manipulation type. On the other hand, the replace function, provided by the dataclasses module, is just a helpful shorthand in this case. Alternatively, we can recreate the Application object as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    return Application(
        application.screen,
        elements,
        state,
        application.state_update,
        application.element_delta
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Moving on to the event dispatching part.&lt;/p&gt;
&lt;p&gt;Due to my inexperience, I didn’t spent too much time drafting a design or a plan when I worked on the game. Still, as I went deeper I realized I really needed to start structuring the event handling code. Keeping that in mind, I continued with the build until I managed to get a bare minimum version running.&lt;/p&gt;
&lt;p&gt;Drawing inspiration from JavaScript, I started my refactoring by revising the event handler registration. Directly inspired by the document.addEventListener method, my interpretation of it in this setting took this form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def add_event_listener(target: Eventable, kind: int, handler: Callable[..., None]) -&amp;gt; Eventable:
    event = Event(kind, handler)
    result = target

    match target:
        case Application():
            await target.event_delta.put(DeltaAdd(event))

        case Element():
            result = replace(target, events=target.events + (event,))

        case _:
            raise Exception(&quot;Unhandled event registration&quot;)

    return result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Besides the widgets in the application window, the application itself can also subscribe to an event. For that, I added a tuple of events as well as an event_delta queue to the Application dataclass. This function always returns either the application registry, or the recreated element. Publishing the recreated element to the element_delta queue is handled by the caller.&lt;/p&gt;
&lt;p&gt;Unlike the display_update function, there is no clock.tick() function to regulate the timing of execution in the event_process function shown previously. After finishing scheduling the current batch, we want to immediately start fetching and processing a new batch of events as soon as possible. The current dispatching function is rather straightforward, as it just schedules the registered handler by cycling through the events tuple in the application registry as well as each of the elements.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def events_dispatch(
    application: Application,
    events: Sequence[pygame.event.Event]
) -&amp;gt; None:
    for event in events:
        match event.type:
            case pygame.QUIT:
                application.exit_event.set()

            case pygame.MOUSEBUTTONDOWN:
                for element in application.elements:
                    for ev in element.events:
                        if not ev.kind == event.type:
                            continue

                        if element.position.collidepoint(mouse_x, mouse_y):
                            asyncio.create_task(
                                ev.handler(ev, element, application)
                            )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basically these is all the code mainly for handling the orchestration work; it contains no game logic. Like how I was working on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/bigmeow_bot&quot;&gt;BigMeow&lt;/a&gt;, a structure emerges when I work more on the project. When I work on a personal project, I also tend to avoid writing object-oriented code as much as possible, and I am glad how well the current revision turns out.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*dQK9UzM3Ixvv0AoZ&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by BoliviaInteligente on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Being a complete newbie to Pygame, Gemini proved helpful yet again. When I talked about the project with a friend, he jokingly asked if I vibe-coded it. However, the whole experience was not unlike &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/the-quest-continues-porting-the-word-game-with-asyncssh-111n&quot;&gt;how I ported the LLM word game to AsyncSSH&lt;/a&gt;, where Gemini assisted by helping me to navigate the documentation. Granted, the code example is usually good enough, but it helps to know how and why things are written in a certain way.&lt;/p&gt;
&lt;h4 id=&quot;lessons-learned-and-horizons-gained-my-pygame-journey-continues&quot;&gt;Lessons Learned and Horizons Gained: My Pygame Journey Continues&lt;/h4&gt;
&lt;p&gt;Refactoring work started right after I was more or less done with handling mouse click and triggered a change to game state. As the code gets cleaned up, it occurs to me that extracting the game logic into a separate module becomes trivial. To my surprise, after getting Gemini to review the code, I was told the separation would yield a mini framework for similar projects.&lt;/p&gt;
&lt;p&gt;Not knowing where I could get feedback, I turned to Gemini to review my code, and we chatted about how this project could be extended. That follow-up discussion was a very enlightening session, apart from the revelation of an emerging framework. Presumably, the Application dataclass resembles an Entity-Component-System. This was definitely a concept worth adding to my learning backlog.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/max/1024/0*edohuBuN2z2BWAyp&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;em&gt;Photo by Glenn Carstens-Peters on Unsplash&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If time permits, I would like to work on the separation and complete the game. Curiosity is definitely the biggest motivation behind the effort, rather than publishing the potentially bad framework. New system events like &lt;code&gt;window.onload&lt;/code&gt; and &lt;code&gt;window.onclose&lt;/code&gt; equivalents may also be implemented to better simulate my web development workflow. Follow &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Jeffrey04/pygame-ttt&quot;&gt;the project on GitHub &lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;p&gt;Unfortunately, I will not have as much free time for a while. On the flip-side, I no longer have to entertain needy and exploitative hiring managers too. With my incoming new job offer, I will adjust the publishing frequency for the coming weeks. I would also like to take this opportunity to thank everyone offering encouragement and help throughout this trying period. &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/jeffrey04/beyond-the-code-a-lunar-new-year-reflection-on-career-recovery-and-new-beginnings-l77&quot;&gt;Spring does come, after winter&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To ensure this post is as clear and readable as possible, I leveraged Gemini as my editorial assistant. Gemini also provided complementary information to Pygame’s official documentation, helping to clarify complex concepts. However, please note that the voice and all code presented within this article are entirely my own. If you found this article helpful or insightful, consider following me on &lt;a rel=&quot;nofollow&quot; href=&quot;https://kitfucoda.medium.com/&quot;&gt;Medium&lt;/a&gt; and subscribing for more content like this.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/3469/my-pygame-evolution-embracing-asyncio-and-immutability-for-scalable-design</guid>
<pubDate>Tue, 24 Jun 2025 19:00:07 +0000</pubDate>
</item>
</channel>
</rss>