<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arelvox</title>
    <description>The latest articles on DEV Community by Arelvox (@arelvox).</description>
    <link>https://dev.to/arelvox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4204802%2F2cf0d6f8-6ca7-4313-942b-d5126ea91231.png</url>
      <title>DEV Community: Arelvox</title>
      <link>https://dev.to/arelvox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arelvox"/>
    <language>en</language>
    <item>
      <title>e.key vs e.code: Why Your WASD Controls Break on AZERTY (and How I Built a Keyboard Tester)</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/arelvox/ekey-vs-ecode-why-your-wasd-controls-break-on-azerty-and-how-i-built-a-keyboard-tester-c50</link>
      <guid>https://dev.to/arelvox/ekey-vs-ecode-why-your-wasd-controls-break-on-azerty-and-how-i-built-a-keyboard-tester-c50</guid>
      <description>&lt;p&gt;If you have ever built keyboard shortcuts or a game in the browser, you have probably used &lt;code&gt;e.key&lt;/code&gt;. Then someone with a French or German keyboard tells you your WASD controls are broken. The fix is one property you may not be using: &lt;code&gt;e.code&lt;/code&gt;. Here is the difference, and what I learned building a keyboard tester.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://arelvox.com/keyboard-tester/" rel="noopener noreferrer"&gt;arelvox.com/keyboard-tester&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  key vs code vs keyCode
&lt;/h2&gt;

&lt;p&gt;Press the key to the right of Tab on three different layouts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layout&lt;/th&gt;
&lt;th&gt;&lt;code&gt;e.key&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;e.code&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;e.keyCode&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;US (QWERTY)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeyQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;French (AZERTY)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeyQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;German (QWERTZ)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;KeyQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;e.key&lt;/code&gt;&lt;/strong&gt; is the &lt;em&gt;character&lt;/em&gt; the key produces. It depends on the layout, Shift, Caps Lock and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;e.code&lt;/code&gt;&lt;/strong&gt; is the &lt;em&gt;physical position&lt;/em&gt; of the key, named after the US layout. It never changes with the layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;e.keyCode&lt;/code&gt;&lt;/strong&gt; is deprecated and inconsistent between browsers. Avoid it in new code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rule of thumb
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Typing text, shortcuts that show a letter in the UI (like Ctrl+S): use &lt;strong&gt;&lt;code&gt;e.key&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Game controls and anything about &lt;em&gt;where&lt;/em&gt; the key is (WASD, a keyboard tester): use &lt;strong&gt;&lt;code&gt;e.code&lt;/code&gt;&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;KeyW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;moveUp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// works on AZERTY too (it is the Z key there)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a keyboard tester
&lt;/h2&gt;

&lt;p&gt;A keyboard tester is a nice exercise because it forces you to use &lt;code&gt;e.code&lt;/code&gt;. Each key on the virtual keyboard has its code as a data attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ROWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Esc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;F1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;F1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Backquote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;`&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Digit1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Backspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Backspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
  &lt;span class="c1"&gt;// [code, label, width]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On &lt;code&gt;keydown&lt;/code&gt;, I look up the element by code and mark it as tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// stop Tab, Space, arrows from scrolling&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-c="&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key done down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-c="&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gotchas I hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Left and right modifiers are different codes.&lt;/strong&gt; &lt;code&gt;ShiftLeft&lt;/code&gt; and &lt;code&gt;ShiftRight&lt;/code&gt;, &lt;code&gt;ControlLeft&lt;/code&gt; and &lt;code&gt;ControlRight&lt;/code&gt;, &lt;code&gt;AltLeft&lt;/code&gt; and &lt;code&gt;AltRight&lt;/code&gt;. Great for a tester, but remember it if you check &lt;code&gt;e.code === 'ShiftLeft'&lt;/code&gt; and the user presses the right one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Some keys never reach the page.&lt;/strong&gt; &lt;code&gt;Fn&lt;/code&gt; is handled inside the keyboard itself, so no website can detect it. Print Screen, media keys and some Windows key combinations are often eaten by the operating system. A tester should say so, or people think their key is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Stuck "pressed" keys after Alt+Tab.&lt;/strong&gt; If the window loses focus while a key is down, you never get the &lt;code&gt;keyup&lt;/code&gt;. Clear the pressed state on &lt;code&gt;blur&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.key.down&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Do not block F5 and F12.&lt;/strong&gt; Calling &lt;code&gt;preventDefault()&lt;/code&gt; on everything also blocks refresh and dev tools, which annoys people. Let those through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Ghosting is a hardware limit.&lt;/strong&gt; If you press several keys at once and some never fire, that is the keyboard's rollover limit, not a bug in your code. Cheap keyboards often fail on combinations like A+S+D+F. It is a fun thing to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Press every key and see which ones work: &lt;strong&gt;&lt;a href="https://arelvox.com/keyboard-tester/" rel="noopener noreferrer"&gt;Keyboard tester →&lt;/a&gt;&lt;/strong&gt;. It shows &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;keyCode&lt;/code&gt; live, so it is also handy when you are debugging shortcuts.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>gamedev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Build a Microphone Level Meter in JavaScript with the Web Audio API</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Mon, 02 Nov 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/arelvox/build-a-microphone-level-meter-in-javascript-with-the-web-audio-api-4p49</link>
      <guid>https://dev.to/arelvox/build-a-microphone-level-meter-in-javascript-with-the-web-audio-api-4p49</guid>
      <description>&lt;p&gt;Every video call app has a little bar that jumps when you talk. It looks like magic, but the browser gives you everything you need in about 15 lines of JavaScript with the Web Audio API. Here is how a microphone level meter works, plus the error handling that makes it usable for real people.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://arelvox.com/mic-test/" rel="noopener noreferrer"&gt;arelvox.com/mic-test&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ask for the microphone
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUserMedia&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser shows its permission prompt. Note that &lt;code&gt;getUserMedia&lt;/code&gt; only works on HTTPS (or &lt;code&gt;localhost&lt;/code&gt;), so test it on a secure page.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Connect it to an AnalyserNode
&lt;/h2&gt;

&lt;p&gt;You do not need to record anything. An &lt;code&gt;AnalyserNode&lt;/code&gt; gives you a live snapshot of the waveform on every frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AudioContext&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkitAudioContext&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMediaStreamSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;an&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAnalyser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fftSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// note: NOT connected to ac.destination, so no echo&lt;/span&gt;
  &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not connecting the analyser to &lt;code&gt;ac.destination&lt;/code&gt; matters: if you do, people hear themselves with a delay, which is horrible.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Turn the waveform into a volume level
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;getByteTimeDomainData&lt;/code&gt; fills an array with values from 0 to 255, where 128 is silence. The usual way to get "loudness" is the RMS (root mean square):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fftSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByteTimeDomainData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// -1 .. 1&lt;/span&gt;
    &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 0 .. ~1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normal speech gives a small RMS (around 0.05 to 0.2), so I multiply by 300 and clamp to 100 to get a meter that actually moves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Draw the waveform
&lt;/h2&gt;

&lt;p&gt;The same buffer draws a nice line on a canvas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;W&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;moveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lineTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it from &lt;code&gt;requestAnimationFrame&lt;/code&gt; and you have a live oscilloscope.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The part that actually helps users: errors
&lt;/h2&gt;

&lt;p&gt;Most people who search "mic test" have a problem. The error name from &lt;code&gt;getUserMedia&lt;/code&gt; tells you what to say:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;err.name&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;What to tell the user&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NotAllowedError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permission blocked&lt;/td&gt;
&lt;td&gt;Click the lock icon in the address bar and allow the microphone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NotFoundError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No input device&lt;/td&gt;
&lt;td&gt;Plug in a mic or headset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NotReadableError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device busy&lt;/td&gt;
&lt;td&gt;Close Zoom, Teams or Discord and try again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A friendly message for each case turns a confusing failure into a fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Let people choose the microphone
&lt;/h2&gt;

&lt;p&gt;Once permission is granted, &lt;code&gt;enumerateDevices()&lt;/code&gt; returns real device labels, so you can show a dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mediaDevices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enumerateDevices&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audioinput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// build &amp;lt;option value="deviceId"&amp;gt;label&amp;lt;/option&amp;gt; for each&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart with &lt;code&gt;{ audio: { deviceId: { exact: id } } }&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Clean up
&lt;/h2&gt;

&lt;p&gt;Always stop the tracks when the user clicks Stop or leaves, or the browser keeps showing the red "recording" dot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTracks&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The full version, with the waveform, peak level and device picker, is here: &lt;strong&gt;&lt;a href="https://arelvox.com/mic-test/" rel="noopener noreferrer"&gt;Microphone test →&lt;/a&gt;&lt;/strong&gt;. Nothing is recorded or uploaded; it all runs in the browser.&lt;/p&gt;

&lt;p&gt;I also made a &lt;a href="https://arelvox.com/webcam-test/" rel="noopener noreferrer"&gt;webcam test&lt;/a&gt; with the same idea, which reads the real resolution and measures FPS with &lt;code&gt;requestVideoFrameCallback&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>audio</category>
    </item>
    <item>
      <title>How Daily Puzzles Give Everyone the Same Answer Without a Server (Seeded RNG in JS)</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Sat, 31 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/arelvox/how-daily-puzzles-give-everyone-the-same-answer-without-a-server-seeded-rng-in-js-4pah</link>
      <guid>https://dev.to/arelvox/how-daily-puzzles-give-everyone-the-same-answer-without-a-server-seeded-rng-in-js-4pah</guid>
      <description>&lt;p&gt;&lt;a href="https://arelvox.com/crack-the-code/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0iht6vjvblvspkmef74z.png" alt="Crack the Code, a daily number puzzle" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Daily puzzles like Wordle have a neat trick: everyone in the world gets the same answer, yet there is no server deciding it. I used the same idea for &lt;strong&gt;Crack the Code&lt;/strong&gt;, a daily number puzzle where you guess a secret 4-digit code in six tries. Here are the three pieces that make it work, all in plain JavaScript.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Today's puzzle:&lt;/strong&gt; &lt;a href="https://arelvox.com/crack-the-code/" rel="noopener noreferrer"&gt;arelvox.com/crack-the-code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Same code for everyone, no backend
&lt;/h2&gt;

&lt;p&gt;The trick is a &lt;strong&gt;seeded random number generator&lt;/strong&gt;. If everyone uses the same seed, everyone gets the same "random" numbers. The seed is just the day number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;EPOCH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// puzzle #1&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dayNum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFullYear&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMonth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;EPOCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that it builds the date from the &lt;em&gt;local&lt;/em&gt; year, month and day. That way the new puzzle arrives at midnight in your own time zone, not at midnight UTC.&lt;/p&gt;

&lt;p&gt;For the generator itself, a tiny linear congruential generator is enough. It is not secure, but a puzzle doesn't need that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;seeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1664525&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1013904223&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;makeCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;rnd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;makeCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;seeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;dayNum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;7919&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiplying by a prime and adding an offset stops neighbouring days from producing similar first numbers. &lt;code&gt;Math.random()&lt;/code&gt; could never do this, because you cannot seed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Scoring guesses when digits repeat
&lt;/h2&gt;

&lt;p&gt;Codes can contain the same digit twice, like &lt;code&gt;0277&lt;/code&gt;. That is where a naive scorer breaks. If the code is &lt;code&gt;0277&lt;/code&gt; and you guess &lt;code&gt;7777&lt;/code&gt;, how many yellows should you get? The answer is &lt;strong&gt;zero&lt;/strong&gt;: two sevens are green, and there are no sevens left over for the other positions.&lt;/p&gt;

&lt;p&gt;The fix is two passes. First mark the exact matches and count what is left over in the code. Then hand out yellows only from that leftover pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// pass 1: greens, and count unmatched digits in the code&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// pass 2: yellows, only while that digit is still available&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same algorithm Wordle uses for repeated letters.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Share the result without spoiling it
&lt;/h2&gt;

&lt;p&gt;The share button turns your guesses into colored squares, so friends see &lt;em&gt;how&lt;/em&gt; you did but not the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🟩&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🟨&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⬛&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Crack the Code #&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;day&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/6&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;share&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The problem: it was too easy
&lt;/h2&gt;

&lt;p&gt;People finished the daily code almost every time, then left. Ten thousand possible codes sounds like a lot, but with six tries and good feedback, a sensible strategy (try &lt;code&gt;1234&lt;/code&gt;, then &lt;code&gt;5678&lt;/code&gt;) solves most days in four.&lt;/p&gt;

&lt;p&gt;So I added a &lt;strong&gt;Time Attack&lt;/strong&gt; mode for players who want more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You start with 90 seconds. Each cracked code &lt;strong&gt;adds time&lt;/strong&gt;, more if you used fewer tries.&lt;/li&gt;
&lt;li&gt;A failed code costs 15 seconds.&lt;/li&gt;
&lt;li&gt;Tries drop from 6 to 4 as you level up, and codes grow to 5 and then 6 digits.&lt;/li&gt;
&lt;li&gt;Fast solves build a combo multiplier up to x5.&lt;/li&gt;
&lt;li&gt;Every third code earns a power-up: reveal a digit, remove digits that aren't in the code, or freeze the clock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The daily puzzle is still the hook, but the clock is what keeps people playing "just one more".&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;What's your best strategy for the first two guesses? Let me know in the comments, and see how many codes you can crack before the clock runs out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arelvox.com/crack-the-code/" rel="noopener noreferrer"&gt;Play Crack the Code →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are more free browser games on the same site, including a &lt;a href="https://arelvox.com/sky-stack/" rel="noopener noreferrer"&gt;one-tap stacking game&lt;/a&gt;: &lt;a href="https://arelvox.com/games/" rel="noopener noreferrer"&gt;arelvox.com/games&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Automate Tasks on Linux with Cron Jobs: A Beginner’s Guide</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Thu, 29 Oct 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/arelvox/how-to-automate-tasks-on-linux-with-cron-jobs-a-beginners-guide-52ll</link>
      <guid>https://dev.to/arelvox/how-to-automate-tasks-on-linux-with-cron-jobs-a-beginners-guide-52ll</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arelvox.com/how-to-automate-tasks-on-linux-with-cron-jobs-a-beginners-guide/" rel="noopener noreferrer"&gt;Arelvox&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Do you find yourself running the same commands every day? Backing up files, cleaning temporary folders, or restarting a service? With cron jobs on &lt;a href="https://arelvox.com/essential-linux-commands-every-beginner-should-know/" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;, you can schedule all of that to run automatically, without lifting a finger. In this guide, you’ll learn what cron is, how to read its syntax, and how to create your first scheduled tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Cron Jobs?
&lt;/h2&gt;

&lt;p&gt;Cron is a background service (a &lt;em&gt;daemon&lt;/em&gt;) that runs commands at times you define. Each user has a file called a &lt;strong&gt;crontab&lt;/strong&gt; (cron table) that lists the tasks to run and when. Once a task is in your crontab, Linux takes care of the rest, even if you’re not logged in.&lt;/p&gt;

&lt;p&gt;Cron is available on practically every Linux distribution, including Raspberry Pi OS, Ubuntu, and Debian, which makes it perfect for &lt;a href="https://arelvox.com/turn-a-raspberry-pi-into-a-home-server-a-beginners-step-by-step-guide/" rel="noopener noreferrer"&gt;home servers&lt;/a&gt; and small projects.&lt;/p&gt;

&lt;p&gt;Each scheduled task in your crontab is called a cron job, and you can create as many cron jobs as you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Run Nightly Backups with Cron Jobs
&lt;/h2&gt;

&lt;p&gt;To edit your personal crontab, open a terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time, you may be asked to choose a text editor. If you’re a beginner, &lt;strong&gt;nano&lt;/strong&gt; is the easiest option.&lt;/p&gt;

&lt;p&gt;To see the tasks you already have scheduled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where all your cron jobs live, so you’ll come back to this command often.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron Jobs Syntax Explained
&lt;/h2&gt;

&lt;p&gt;Every line in a crontab has five time fields followed by the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * command_to_run
│ │ │ │ │
│ │ │ │ └── Day of the week (0-7, Sunday is 0 or 7)
│ │ │ └──── Month (1-12)
│ │ └────── Day of the month (1-31)
│ └──────── Hour (0-23)
└────────── Minute (0-59)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An asterisk (&lt;code&gt;*&lt;/code&gt;) means “every”. Here are some examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Schedule&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;* * * * *&lt;/td&gt;
&lt;td&gt;Every minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0 * * * *&lt;/td&gt;
&lt;td&gt;Every hour, on the hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30 2 * * *&lt;/td&gt;
&lt;td&gt;Every day at 2:30 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0 9 * * 1&lt;/td&gt;
&lt;td&gt;Every Monday at 9:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0 0 1 * *&lt;/td&gt;
&lt;td&gt;The first day of every month at midnight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;*/10 * * * *&lt;/td&gt;
&lt;td&gt;Every 10 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cron also supports handy shortcuts such as &lt;code&gt;@reboot&lt;/code&gt; (run once at startup), &lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, and &lt;code&gt;@monthly&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you understand these five fields, you can schedule any of your cron jobs in seconds.&lt;/p&gt;

&lt;p&gt;For the full technical reference, see the official &lt;strong&gt;&lt;a href="https://man7.org/linux/man-pages/man5/crontab.5.html" rel="noopener noreferrer"&gt;crontab manual page&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1: Run Nightly Backups with Cron Jobs
&lt;/h2&gt;

&lt;p&gt;Backups are one of the most popular cron jobs, and also one of the easiest to set up. Imagine you have a script called &lt;code&gt;backup.sh&lt;/code&gt; in your home folder. To run it every day at 3:00 AM, add this line to your crontab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 3 * * * /home/user/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to make the script executable first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /home/user/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Example 2: Save the Output to a Log File
&lt;/h2&gt;

&lt;p&gt;By default, cron doesn’t show you what a command printed. To keep a record of the results (and any errors), redirect the output to a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 3 * * * /home/user/backup.sh &amp;gt;&amp;gt; /home/user/backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; appends the output to the log, and &lt;code&gt;2&amp;gt;&amp;amp;1&lt;/code&gt; sends error messages to the same place. This is the single most useful habit when debugging cron jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 3: Run a Command at Startup
&lt;/h2&gt;

&lt;p&gt;Want something to start every time your &lt;a href="https://arelvox.com/turn-a-raspberry-pi-into-a-home-server-a-beginners-step-by-step-guide/" rel="noopener noreferrer"&gt;Raspberry Pi&lt;/a&gt; boots? Use &lt;code&gt;@reboot&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;@&lt;span class="n"&gt;reboot&lt;/span&gt; /&lt;span class="n"&gt;home&lt;/span&gt;/&lt;span class="n"&gt;user&lt;/span&gt;/&lt;span class="n"&gt;start_server&lt;/span&gt;.&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Startup tasks like this are some of the most useful cron jobs for a home server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 4: Clean Temporary Files Weekly
&lt;/h2&gt;

&lt;p&gt;This job deletes files older than 7 days from a temporary folder every Sunday at 4:00 AM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 4 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 0 find /home/user/tmp &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always test a &lt;code&gt;find ... -delete&lt;/code&gt; command without &lt;code&gt;-delete&lt;/code&gt; first, so you can check which files it would remove.&lt;/p&gt;

&lt;p&gt;Maintenance tasks like this are ideal cron jobs, since you set them once and forget about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Cron Jobs Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Using relative paths.&lt;/strong&gt; Cron runs with a minimal environment, so always use full paths (&lt;code&gt;/home/user/script.sh&lt;/code&gt;, not &lt;code&gt;./script.sh&lt;/code&gt;). You can find the full path of a program with &lt;code&gt;which python3&lt;/code&gt;, for example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Forgetting to make the script executable.&lt;/strong&gt; If the script lacks permissions, cron will silently fail. Use &lt;code&gt;chmod +x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Wrong time zone.&lt;/strong&gt; Cron uses the system’s time zone. Check it with the &lt;code&gt;date&lt;/code&gt; command if tasks run at unexpected hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Not checking the logs.&lt;/strong&gt; If a job doesn’t work, look at the system log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;CRON /var/log/syslog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On some systems you can use &lt;code&gt;journalctl -u cron&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Editing the wrong crontab.&lt;/strong&gt; &lt;code&gt;crontab -e&lt;/code&gt; edits &lt;em&gt;your&lt;/em&gt; user’s crontab. System-wide tasks that need root privileges belong in &lt;code&gt;sudo crontab -e&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Helpful Tool: Crontab Guru
&lt;/h2&gt;

&lt;p&gt;If the syntax feels confusing, the website &lt;strong&gt;&lt;a href="https://crontab.guru" rel="noopener noreferrer"&gt;crontab.guru&lt;/a&gt;&lt;/strong&gt; translates any cron expression into plain English. Paste in &lt;code&gt;*/15 9-17 * * 1-5&lt;/code&gt; and it will tell you: “Every 15 minutes, between 9:00 and 17:59, Monday through Friday.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron vs. Systemd Timers
&lt;/h2&gt;

&lt;p&gt;Modern Linux systems also offer &lt;strong&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html" rel="noopener noreferrer"&gt;systemd timers&lt;/a&gt;&lt;/strong&gt;, which are more flexible and integrate with system logs. However, cron is simpler, works everywhere, and cron jobs are more than enough for most personal projects. Start with cron, and explore timers later if you need more control.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj5if3swhgoo4ubd9ikv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzj5if3swhgoo4ubd9ikv.jpg" alt="Server and clock representing automated cron jobs on Linux" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; a script must be executable before cron can run it. Use our free &lt;a href="https://arelvox.com/chmod-calculator/" rel="noopener noreferrer"&gt;chmod calculator&lt;/a&gt; to pick the right permissions, such as 755 for most scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Cron jobs are one of the simplest ways to save time on Linux. With a basic understanding of the five time fields, full paths, and log redirection, you can automate backups, maintenance, and monitoring in just a few minutes.&lt;/p&gt;

&lt;p&gt;Try creating your first cron job today: schedule a script that writes the date to a file every minute, check that it works, and then build from there.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question or a cron job you’d like us to cover? Let us know through the Contact page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep reading:&lt;/strong&gt; build the commands your jobs run with our free &lt;a href="https://arelvox.com/linux-command-generator/" rel="noopener noreferrer"&gt;Linux command generator&lt;/a&gt;, and if you run scheduled tasks on a Pi, see how to &lt;a href="https://arelvox.com/pi-hole-raspberry-pi-block-ads/" rel="noopener noreferrer"&gt;block ads on your whole network with Pi-hole&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Build your crontab lines faster with the free &lt;a href="https://arelvox.com/cron-expression-generator/" rel="noopener noreferrer"&gt;cron expression generator&lt;/a&gt; on Arelvox.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Check Your Monitor Refresh Rate (Hz) in the Browser with JavaScript</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Tue, 27 Oct 2026 23:19:25 +0000</pubDate>
      <link>https://dev.to/arelvox/how-to-check-your-monitor-refresh-rate-hz-in-the-browser-with-javascript-5377</link>
      <guid>https://dev.to/arelvox/how-to-check-your-monitor-refresh-rate-hz-in-the-browser-with-javascript-5377</guid>
      <description>&lt;p&gt;Is your 144 Hz monitor really running at 144 Hz? A surprising number of people find out it has been stuck at 60 Hz for months because of a cable or a Windows setting. You can check it from a web page with about 20 lines of JavaScript. Here is how, including the two details that make the number trustworthy.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Try it first:&lt;/strong&gt; &lt;a href="https://arelvox.com/refresh-rate-test/" rel="noopener noreferrer"&gt;arelvox.com/refresh-rate-test&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: count frames with requestAnimationFrame
&lt;/h2&gt;

&lt;p&gt;The browser calls &lt;code&gt;requestAnimationFrame&lt;/code&gt; once per screen refresh. If you record the time between calls, you get the frame time, and &lt;code&gt;1000 / frameTime&lt;/code&gt; is the refresh rate in hertz.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ms between two frames&lt;/span&gt;
  &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 60 Hz screen the samples hover around 16.7 ms, on 144 Hz around 6.9 ms, and on 240 Hz around 4.2 ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detail 1: use the median, not the average
&lt;/h2&gt;

&lt;p&gt;If you simply average the samples, you get numbers like 57 Hz on a perfectly normal 60 Hz laptop. The reason is &lt;strong&gt;dropped frames&lt;/strong&gt;. When the browser is busy (garbage collection, a heavy tab, a font loading), one frame takes 33 ms instead of 16.7 ms. A few of those pull the average down a lot.&lt;/p&gt;

&lt;p&gt;The median ignores them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// skip the first frames while the page settles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also skip the first 10 samples, because the first frames after page load are often irregular.&lt;/p&gt;

&lt;p&gt;A bonus: once you have the median, counting dropped frames is trivial. Any frame that takes more than about 1.7× the median was dropped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;drops&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Detail 2: snap to real refresh rates
&lt;/h2&gt;

&lt;p&gt;The measured value is never perfectly round. You get 59.94 Hz (a real NTSC-style rate on many laptops), 143.8 Hz, and so on. People expect to see "144 Hz", so I snap to the nearest common rate if it is within 6%:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;COMMON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;144&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;165&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;170&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nearest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;COMMON&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;COMMON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;hz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;hz&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;hz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;hz&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I still show the raw measurement next to it ("about 60 Hz, measured 59.9 Hz"), so nothing is hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha: background tabs
&lt;/h2&gt;

&lt;p&gt;Browsers throttle &lt;code&gt;requestAnimationFrame&lt;/code&gt; in background tabs, sometimes down to 1 frame per second or none at all. My first deploy showed &lt;strong&gt;"NaN Hz"&lt;/strong&gt; to anyone who opened the page in a new background tab: after 2.5 seconds there were not enough samples, and the median of an empty array is &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix is to wait until there are enough samples, and to restart the measurement when the tab becomes visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;visibilitychange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The ternary instead of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; is because the code runs inside a WordPress block, and WordPress mangles &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; in inline scripts.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the difference visible
&lt;/h2&gt;

&lt;p&gt;A number is nice, but people want to &lt;em&gt;see&lt;/em&gt; smoothness. The page has three squares moving at the same speed: one updates every frame, one at 30 FPS and one at 15 FPS. On a 144 Hz screen the difference between the first two is obvious, and it is a good way to convince yourself that the monitor setting actually changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  If your screen shows 60 Hz but should not
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; Settings → System → Display → Advanced display → Choose a refresh rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS:&lt;/strong&gt; System Settings → Displays → Refresh rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cable:&lt;/strong&gt; old HDMI cables often cap you at 60 Hz. DisplayPort is the safe choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laptops on battery&lt;/strong&gt; may drop to 60 Hz to save power.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;What does your screen report? I'd love to hear about odd results, especially on variable refresh rate displays or phones, where the browser may run at a lower rate than the panel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arelvox.com/refresh-rate-test/" rel="noopener noreferrer"&gt;Run the refresh rate test →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are a few more small browser tests on the same site if you are setting up new hardware: a &lt;a href="https://arelvox.com/keyboard-tester/" rel="noopener noreferrer"&gt;keyboard tester&lt;/a&gt;, a &lt;a href="https://arelvox.com/dead-pixel-test/" rel="noopener noreferrer"&gt;dead pixel test&lt;/a&gt;, a &lt;a href="https://arelvox.com/mic-test/" rel="noopener noreferrer"&gt;mic test&lt;/a&gt; and a &lt;a href="https://arelvox.com/webcam-test/" rel="noopener noreferrer"&gt;webcam test&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>hardware</category>
    </item>
    <item>
      <title>I Built 2048 in Vanilla JS: Fixing the Double-Merge Bug, Smooth Slides and Undo</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:07:47 +0000</pubDate>
      <link>https://dev.to/arelvox/i-built-2048-in-vanilla-js-fixing-the-double-merge-bug-smooth-slides-and-undo-3gbo</link>
      <guid>https://dev.to/arelvox/i-built-2048-in-vanilla-js-fixing-the-double-merge-bug-smooth-slides-and-undo-3gbo</guid>
      <description>&lt;p&gt;2048 is the classic "build it in a weekend" game. The rules fit in one sentence, yet there are a few traps that almost every 2048 clone falls into. Here is how I handled them in plain JavaScript, with no framework and no canvas, just DOM tiles and CSS transforms.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Play the result:&lt;/strong&gt; &lt;a href="https://arelvox.com/2048-game/" rel="noopener noreferrer"&gt;arelvox.com/2048-game&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The double-merge bug
&lt;/h2&gt;

&lt;p&gt;Take the row &lt;code&gt;[2, 2, 4, _]&lt;/code&gt; and swipe left. The correct result is &lt;code&gt;[4, 4, _, _]&lt;/code&gt;. A naive loop gives &lt;code&gt;[8, _, _, _]&lt;/code&gt;, because the new 4 merges again with the existing 4 in the same move.&lt;/p&gt;

&lt;p&gt;The rule is simple: &lt;strong&gt;a tile can only merge once per move&lt;/strong&gt;. I give every tile an id and remember which ids already merged during this move:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mergedIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;mergedIds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;mergedIds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// this tile is done for this move&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Yes, that is a ternary instead of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;. The game runs inside a WordPress block, and WordPress mangles &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; in inline scripts.)&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Process tiles from the far side
&lt;/h2&gt;

&lt;p&gt;The second bug is about order. If you swipe right but loop over columns from left to right, tiles bump into neighbours that have not moved yet. The fix is to iterate starting from the edge you are moving towards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// up, right, down, left&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// moving down: start at the bottom&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// moving right: start at the right&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tile then slides cell by cell until it hits the wall, a different tile, or a tile it can merge with.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Smooth animations with stable ids
&lt;/h2&gt;

&lt;p&gt;If you rebuild the board on every move, tiles teleport instead of sliding. The fix is to never recreate a tile element. Each tile keeps its &lt;code&gt;id&lt;/code&gt;, the DOM node keeps a &lt;code&gt;data-id&lt;/code&gt;, and moving a tile only changes its CSS transform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;translate(calc(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;% + &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px), calc(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;% + &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px))&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;transition: transform .11s&lt;/code&gt; in the CSS, the browser animates the slide for free. For merges, the tile that disappears slides into its target first and is removed 120 ms later, so you actually see the two tiles meet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* target position */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New tiles get a small "pop" keyframe animation and merged tiles a quick scale bounce.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Undo is just a stack of snapshots
&lt;/h2&gt;

&lt;p&gt;Undo sounds hard, but the state is tiny: a 4x4 grid and a score. Before each move I push a deep copy, and undo pops it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;undo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;undos&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;undos&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlimited undo makes the game pointless, so you get &lt;strong&gt;three per game&lt;/strong&gt;. That turned out to be a nice design choice: players save them for real mistakes, and it adds a small layer of strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Autosave with localStorage
&lt;/h2&gt;

&lt;p&gt;People play 2048 in short breaks, then close the tab. Losing a 1024 tile to a closed tab is painful, so the game saves after every move:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avx-2048-state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;undos&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;try/catch&lt;/code&gt; matters: Safari private mode and some embedded browsers throw on &lt;code&gt;localStorage&lt;/code&gt; access.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Checking for game over
&lt;/h2&gt;

&lt;p&gt;The board is only lost when it is full &lt;strong&gt;and&lt;/strong&gt; no two neighbours match. You only need to check right and down for each cell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canMove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;What is your highest tile? Anything above 2048 is already very good. If you are stuck around 512, I also wrote a short guide on &lt;a href="https://arelvox.com/how-to-win-2048/" rel="noopener noreferrer"&gt;the corner strategy&lt;/a&gt;, which is how most people get past it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arelvox.com/2048-game/" rel="noopener noreferrer"&gt;Play 2048 →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to Set Up Pi-hole on a Raspberry Pi to Block Ads</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Tue, 27 Oct 2026 08:00:00 +0000</pubDate>
      <link>https://dev.to/arelvox/how-to-set-up-pi-hole-on-a-raspberry-pi-to-block-ads-4bli</link>
      <guid>https://dev.to/arelvox/how-to-set-up-pi-hole-on-a-raspberry-pi-to-block-ads-4bli</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arelvox.com/pi-hole-raspberry-pi-block-ads/" rel="noopener noreferrer"&gt;Arelvox&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ads and trackers slow down your browsing, waste your data, and follow you from site to site. Pi-hole is a free tool that blocks them for every device on your home network at once, including phones, smart TVs, and laptops. In this guide, you’ll learn how to install it on a Raspberry Pi, connect your network, and keep everything working well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Pi-hole?
&lt;/h2&gt;

&lt;p&gt;Pi-hole is a network-wide ad blocker that works as a DNS sinkhole. Whenever a device asks for a website address, it checks the request against a list of known advertising and tracking domains. If the domain is on the list, the request is refused, so the ad never loads. Otherwise, it passes the request along to a normal DNS server.&lt;/p&gt;

&lt;p&gt;If you want a refresher on how DNS works, read our guide to &lt;a href="https://arelvox.com/the-ultimate-guide-to-your-home-network-ips-routers-and-dns/" rel="noopener noreferrer"&gt;understanding your home network&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need Before You Start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Raspberry Pi. Almost any model works, even a Raspberry Pi Zero 2 W, because the software uses very few resources.&lt;/li&gt;
&lt;li&gt;A microSD card (8 GB or more) and a power supply.&lt;/li&gt;
&lt;li&gt;An Ethernet cable. Wi-Fi works, but a cable is more reliable for a device that your whole network depends on.&lt;/li&gt;
&lt;li&gt;Access to your router’s settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Prepare Your Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;First, install &lt;strong&gt;Raspberry Pi OS Lite&lt;/strong&gt; with Raspberry Pi Imager. In the advanced settings, enable SSH and set your username and password. Then boot the Pi, connect to it over SSH, and update the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt full-upgrade -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, give the Pi a &lt;strong&gt;fixed IP address&lt;/strong&gt;. Since your devices will send all their DNS requests to this address, it must never change. The easiest way is to create a DHCP reservation in your router, which always assigns the same IP to your Pi’s MAC address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Install Pi-hole
&lt;/h2&gt;

&lt;p&gt;The project provides an official installer. Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://install.pi-hole.net | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer walks you through a few screens. In most cases, you can keep the defaults, but pay attention to these choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Upstream DNS provider:&lt;/strong&gt; Choose one you trust, such as Cloudflare or Quad9.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blocklists:&lt;/strong&gt; Keep the default list to start with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web admin interface:&lt;/strong&gt; Leave it enabled, because it makes managing Pi-hole much easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you prefer to read a script before running it, download it first and review it. This is a good habit with any installer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Set Your Admin Password
&lt;/h2&gt;

&lt;p&gt;When the installation finishes, set a password for the web dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pihole setpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On older versions, the command is &lt;code&gt;pihole -a -p&lt;/code&gt; instead. Afterward, open &lt;code&gt;http://pi.hole/admin&lt;/code&gt; in your browser, or use the Pi’s IP address followed by &lt;code&gt;/admin&lt;/code&gt;, and log in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Connect Your Network to Pi-hole
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft8sal5u7lxzefog2vwvu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft8sal5u7lxzefog2vwvu.jpg" alt="Home router connected to a Raspberry Pi running Pi-hole" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it’s running, but your devices don’t use it yet. You have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Change the DNS server in your router.&lt;/strong&gt; Set the router’s DNS (or DHCP DNS) to your Pi’s IP address. As a result, every device on the network uses Pi-hole automatically. This is the best option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change the DNS on individual devices.&lt;/strong&gt; This works if your router doesn’t let you change DNS, but you have to repeat it on every device.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After saving, reconnect a device to the network or restart it, so it picks up the new settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Check That Everything Works
&lt;/h2&gt;

&lt;p&gt;Open any website and look at the dashboard. You should see queries appear in real time, along with the percentage of blocked requests. For a quick test, visit a page with many ads and compare it with your usual experience.&lt;/p&gt;

&lt;p&gt;You can also test from a terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nslookup doubleclick.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s blocking the domain, the answer comes back as &lt;code&gt;0.0.0.0&lt;/code&gt; or similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping Pi-hole Up to Date
&lt;/h2&gt;

&lt;p&gt;Every so often, update the software and the blocklists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pihole -up
pihole -g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first command updates Pi-hole itself, and the second refreshes the blocklists (this process is called &lt;em&gt;gravity&lt;/em&gt;). Moreover, you can add more blocklists from the web interface under &lt;strong&gt;Adlists&lt;/strong&gt; if you want stronger blocking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Pi-hole Cannot Do
&lt;/h2&gt;

&lt;p&gt;However, Pi-hole is not magic. Keep these limits in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It cannot block ads that come from the same domain as the content, such as most YouTube ads.&lt;/li&gt;
&lt;li&gt;Some websites break if you block too much, so you may need to &lt;strong&gt;allowlist&lt;/strong&gt; certain domains.&lt;/li&gt;
&lt;li&gt;If your Pi goes offline, devices that depend only on it lose DNS and appear to have no internet. Therefore, many people set a secondary DNS server in the router as a fallback, although this lets some ads through.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use a strong password for the dashboard and for the Pi itself.&lt;/li&gt;
&lt;li&gt;Never expose the Pi-hole admin page to the internet.&lt;/li&gt;
&lt;li&gt;Keep Raspberry Pi OS updated. Our guide to &lt;a href="https://arelvox.com/how-to-secure-your-home-network-a-practical-cybersecurity-guide-for-beginners/" rel="noopener noreferrer"&gt;securing your home network&lt;/a&gt; explains more habits like this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: Enjoy a Cleaner Network with Pi-hole
&lt;/h2&gt;

&lt;p&gt;Pi-hole is one of the best first projects for a Raspberry Pi. It costs almost nothing, takes less than an hour to set up, and makes every device on your network a little faster and more private. Now that you have it running, try adding extra blocklists and watch the dashboard to see how many requests it blocks every day.&lt;/p&gt;

&lt;p&gt;For the official documentation, visit the &lt;a href="https://pi-hole.net" rel="noopener noreferrer"&gt;Pi-hole website&lt;/a&gt; and its &lt;a href="https://docs.pi-hole.net" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do you have a question about Pi-hole? Let us know through the Contact page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep reading:&lt;/strong&gt; new to the Pi? Start with &lt;a href="https://arelvox.com/turn-a-raspberry-pi-into-a-home-server-a-beginners-step-by-step-guide/" rel="noopener noreferrer"&gt;how to turn a Raspberry Pi into a home server&lt;/a&gt;, and use our free &lt;a href="https://arelvox.com/subnet-calculator/" rel="noopener noreferrer"&gt;subnet calculator&lt;/a&gt; to check your network range before you give Pi-hole a fixed address.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Planning your home network? Try the free &lt;a href="https://arelvox.com/subnet-calculator/" rel="noopener noreferrer"&gt;IP subnet calculator&lt;/a&gt; on Arelvox.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>selfhosted</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>PHP2Shell (CVE-2026-109618): How to Check if Your PHP Server Is Vulnerable and Patch It</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Mon, 26 Oct 2026 23:45:48 +0000</pubDate>
      <link>https://dev.to/arelvox/php2shell-cve-2026-109618-how-to-check-if-your-php-server-is-vulnerable-and-patch-it-22b8</link>
      <guid>https://dev.to/arelvox/php2shell-cve-2026-109618-how-to-check-if-your-php-server-is-vulnerable-and-patch-it-22b8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;PHP2Shell in short:&lt;/strong&gt; if your server runs PHP 8.2–8.5, update to &lt;strong&gt;8.2.35, 8.3.36, 8.4.27 or 8.5.12&lt;/strong&gt; (or newer) and restart PHP. If you run PHP 8.1 or older, you need a patched package from your distro or host, or an upgrade. No setting fully protects you. &lt;em&gt;Last updated: October 27, 2026.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;PHP2Shell&lt;/strong&gt; is the name given to two critical vulnerabilities in PHP itself, &lt;strong&gt;CVE-2026-109618&lt;/strong&gt; and &lt;strong&gt;CVE-2026-109619&lt;/strong&gt;, which PHP fixed on October 21, 2026. They let an attacker run code on a web server with a single unauthenticated HTTP request, before your application code even starts. Because PHP powers WordPress and a large share of the web, this is one of the most serious PHP bugs in years. Here is what it is, how to check if you are affected and exactly how to patch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0zk1t7qo2l8jxgn28sc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0zk1t7qo2l8jxgn28sc.jpg" alt="PHP2Shell vulnerability CVE-2026-109618 and CVE-2026-109619 patch guide" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is PHP2Shell?
&lt;/h2&gt;

&lt;p&gt;PHP2Shell covers two separate bugs in the PHP interpreter. Both score &lt;strong&gt;9.2 on CVSS v4.0&lt;/strong&gt; and both can lead to remote code execution with the privileges of the web server user, such as &lt;code&gt;www-data&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109618 (memory corruption):&lt;/strong&gt; when a request makes PHP run out of memory while it is still setting up, part of an internal hash table stays half-built. The cleanup code then treats it as valid, which an attacker can abuse to call a function of their choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109619 (use-after-free):&lt;/strong&gt; some worker-wide state can survive the request that created it. A later request handled by the same PHP worker can then use a stale pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dangerous part is &lt;em&gt;where&lt;/em&gt; it happens. The failure occurs while PHP parses the incoming request, so login pages, firewalls in your app, plugins and frameworks never get a chance to stop it. In other words, keeping WordPress and your plugins up to date does &lt;strong&gt;not&lt;/strong&gt; fix PHP2Shell. Only updating PHP does.&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP2Shell Timeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;October 17, 2026:&lt;/strong&gt; AWS reported exploitation attempts four days before the public disclosure, according to Datadog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 20:&lt;/strong&gt; details leaked on social media ahead of the planned October 22 disclosure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 21:&lt;/strong&gt; PHP released emergency fixes and published its advisories. watchTowr reported exploitation in its honeypots the same day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 22–23:&lt;/strong&gt; public proof-of-concept exploits appeared. Security vendors added scanner checks and detections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update (October 27):&lt;/strong&gt; attacks have grown fast. Cloudflare reported PHP2Shell attempts rising from tens of thousands within 45 minutes of the disclosure to millions by early October 22, and Sophos and Arctic Wolf saw attempts to install a Mirai botnet variant. Sophos said there was no evidence of a successful compromise. Some researchers have also claimed the emergency patch is incomplete, which PHP disputes, so keep watching for the next PHP release.&lt;/p&gt;

&lt;p&gt;Not every vendor sees the same PHP2Shell picture. Rapid7 says the public exploits only work against specific combinations of operating system, PHP version and web server, and that it had not confirmed in-the-wild exploitation itself. That difficulty buys you a little time, but every vendor agrees on the advice: patch now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which PHP Versions Are Affected by PHP2Shell?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PHP branch&lt;/th&gt;
&lt;th&gt;Affected&lt;/th&gt;
&lt;th&gt;Fixed in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;before 8.5.12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.5.12&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;before 8.4.27&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.4.27&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;before 8.3.36&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.3.36&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;before 8.2.35&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.2.35&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.1, 8.0, 7.4&lt;/td&gt;
&lt;td&gt;all versions&lt;/td&gt;
&lt;td&gt;no official release; source backports, distro/LTS packages, or upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.3 and older (incl. PHP 5)&lt;/td&gt;
&lt;td&gt;all versions&lt;/td&gt;
&lt;td&gt;no fix, upgrade to a supported branch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every way of running PHP for the web falls in scope: &lt;strong&gt;Apache mod_php, PHP-FPM, php-cgi, LiteSpeed and FrankenPHP&lt;/strong&gt;. The PHP team rates mod_php as the highest risk. One important detail: Linux distributions often backport security fixes without changing the version number, so an “old” version on Debian, Ubuntu or RHEL may already include the fix. Always check your vendor advisory, not just the number.&lt;/p&gt;

&lt;p&gt;The scale is huge. According to CybelAngel, citing Shodan data from October 20, about &lt;strong&gt;2.24 million&lt;/strong&gt; internet-facing hosts advertise PHP, and 59% of those that show a version run a branch with no official fix. For WordPress, 35.1% of sites run an unsupported PHP version, according to WordPress.org statistics they cite.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Check Your PHP Version for PHP2Shell
&lt;/h2&gt;

&lt;h3&gt;
  
  
  On a server with SSH
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find out whether PHP2Shell affects you, check the PHP that serves your website. Careful: the command-line PHP is not always the same binary your website uses. To see what the web server actually runs, check PHP-FPM and installed packages too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PHP-FPM version (Debian/Ubuntu)&lt;/span&gt;
php-fpm8.3 &lt;span class="nt"&gt;-v&lt;/span&gt;

&lt;span class="c"&gt;# Installed PHP packages&lt;/span&gt;
dpkg &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;php        &lt;span class="c"&gt;# Debian/Ubuntu&lt;/span&gt;
rpm &lt;span class="nt"&gt;-qa&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;php        &lt;span class="c"&gt;# RHEL/AlmaLinux/Rocky&lt;/span&gt;

&lt;span class="c"&gt;# Docker image&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;container&amp;gt; php &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  On WordPress
&lt;/h3&gt;

&lt;p&gt;In the WordPress dashboard go to &lt;strong&gt;Tools → Site Health → Info → Server&lt;/strong&gt;. The &lt;em&gt;PHP version&lt;/em&gt; row shows exactly what your site uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  On shared hosting
&lt;/h3&gt;

&lt;p&gt;Open your hosting panel and look for the PHP version or PHP configuration section (for example in cPanel’s &lt;em&gt;MultiPHP Manager&lt;/em&gt; or &lt;em&gt;Select PHP Version&lt;/em&gt;, or the PHP Configuration page in Hostinger’s hPanel). On shared hosting, though, the host controls the actual PHP build, so the most reliable check is to ask support whether the PHP2Shell fixes are applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Patch PHP2Shell
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Debian and Ubuntu
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-upgrade&lt;/span&gt; &lt;span class="s1"&gt;'php*'&lt;/span&gt;

&lt;span class="c"&gt;# restart PHP so the fix takes effect&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php8.3-fpm     &lt;span class="c"&gt;# match your version&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2        &lt;span class="c"&gt;# if you use mod_php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  RHEL, AlmaLinux, Rocky, Fedora
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf upgrade &lt;span class="s1"&gt;'php*'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php-fpm httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use the Remi repository, note that Rapid7 reported Remi RPMs were not expected to receive the update on release day, and PHP advised switching to distribution packages or building from source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;p&gt;Pull the latest official image for your branch, for example &lt;code&gt;php:8.3-fpm&lt;/code&gt;, rebuild your own images on top of it and redeploy. An old image keeps the old PHP no matter what you install inside your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared and managed WordPress hosting
&lt;/h3&gt;

&lt;p&gt;Here the host patches PHP2Shell, not you. Some, such as SiteGround, say they applied mitigations in advance and the official fixes on October 21. If you are on PHP 7.4 or 8.0/8.1, switch to PHP 8.3 or newer in your panel (after testing your site), and ask your host to confirm in writing that the fix is in place. Take a fresh backup first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not forget to restart.&lt;/strong&gt; PHP-FPM and Apache keep the old code in memory until their workers restart. Updating the package without a restart leaves you vulnerable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temporary PHP2Shell Mitigations If You Cannot Patch Yet
&lt;/h2&gt;

&lt;p&gt;PHP and the security vendors are clear that no setting fully fixes PHP2Shell. These steps only reduce the risk until you can update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;session.upload_progress.enabled = 0&lt;/code&gt; in &lt;code&gt;php.ini&lt;/code&gt; or the FPM pool. If you set it with &lt;code&gt;ini_set()&lt;/code&gt; or &lt;code&gt;.user.ini&lt;/code&gt;, it comes too late.&lt;/li&gt;
&lt;li&gt;On PHP 8.5, also set &lt;code&gt;fatal_error_backtraces = 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For CVE-2026-109618, PHP’s guidance is &lt;code&gt;memory_limit = -1&lt;/code&gt; set by the administrator, combined with strict request-size limits at the proxy and container memory limits. Do not use &lt;code&gt;RLIMIT_AS&lt;/code&gt; or &lt;code&gt;RLIMIT_DATA&lt;/code&gt;, which can trigger the bug.&lt;/li&gt;
&lt;li&gt;Lower &lt;code&gt;post_max_size&lt;/code&gt; (for example 8M, or 32M if you need uploads) and limit body size, number of fields and nesting depth at your reverse proxy or WAF. Block multipart requests on apps that do not use uploads.&lt;/li&gt;
&lt;li&gt;Restrict internet access to any PHP system you cannot patch right away.&lt;/li&gt;
&lt;li&gt;Restart all PHP workers after every change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hiding errors with &lt;code&gt;display_errors&lt;/code&gt; or &lt;code&gt;@&lt;/code&gt; will not help, and an empty error log does not prove you are safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Signs of a PHP2Shell Attack on Your Server
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bursts of fatal errors like &lt;em&gt;“Allowed memory size of … bytes exhausted … in Unknown on line 0”&lt;/em&gt;. “Unknown on line 0” means the failure happened before any script ran.&lt;/li&gt;
&lt;li&gt;Apache log lines such as &lt;code&gt;AH00052: child pid … exit signal Segmentation fault (11)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Multipart POST requests with &lt;code&gt;PHPSESSID&lt;/code&gt; or &lt;code&gt;PHP_SESSION_UPLOAD_PROGRESS&lt;/code&gt; fields, deeply nested field names, or upload filenames containing &lt;code&gt;&amp;lt;?php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Very large form bodies, or uploads that declare a huge size and never finish.&lt;/li&gt;
&lt;li&gt;The strongest sign: your web server process (for example &lt;code&gt;apache2&lt;/code&gt; or &lt;code&gt;php-fpm&lt;/code&gt; as &lt;code&gt;www-data&lt;/code&gt;) starting a shell such as &lt;code&gt;sh -c&lt;/code&gt;, followed by an outbound connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that a successful attack does not have to crash anything. In Datadog’s lab the exploit returned a normal HTTP 200. If you see these signs, treat the server as compromised: isolate it, patch, rotate credentials and look for web shells and new cron jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Third PHP Bug: CVE-2026-115560
&lt;/h2&gt;

&lt;p&gt;On October 23 the PHP Foundation published another advisory, CVE-2026-115560. It only affects apps that switch the session serializer at runtime, and there is no patch yet; the fix comes with the next regular PHP release. The mitigation is the same &lt;code&gt;session.upload_progress.enabled = 0&lt;/code&gt; setting listed above. Read our full guide: &lt;a href="https://arelvox.com/cve-2026-115560-php/" rel="noopener noreferrer"&gt;CVE-2026-115560 explained and how to mitigate it&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does updating WordPress protect me from PHP2Shell?
&lt;/h3&gt;

&lt;p&gt;No. PHP2Shell is a bug in PHP itself, so WordPress, theme and plugin updates do not fix it. You need a patched PHP version from your host or your server packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the PHP CLI affected?
&lt;/h3&gt;

&lt;p&gt;The attacks target PHP while it handles web requests, through mod_php, PHP-FPM, CGI, LiteSpeed or FrankenPHP. Command-line scripts are not the main risk, but update the whole PHP installation anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  I run PHP 7.4. What should I do?
&lt;/h3&gt;

&lt;p&gt;PHP 7.4 has no official release with the fix. Use a patched package from your Linux distribution or LTS vendor if one exists, or upgrade to PHP 8.3 or newer, which is the better long-term choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a WAF protect me?
&lt;/h3&gt;

&lt;p&gt;It can block known attack patterns, but a WAF must see the request exactly as PHP parses it, and chunked or truncated bodies can slip through. Treat a WAF as an extra layer, not a fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I know if my host has patched?
&lt;/h3&gt;

&lt;p&gt;Check the exact PHP version in WordPress Site Health or your panel and compare it with the table above, then ask support to confirm. Remember that distro builds can include the fix without a new version number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://securitylabs.datadoghq.com/articles/php2shell-critical-vulnerabilities-in-php/" rel="noopener noreferrer"&gt;Datadog Security Labs: PHP2Shell technical analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://watchtowr.com/intelligence/php-cve-2026-109618-faq/" rel="noopener noreferrer"&gt;watchTowr: PHP2Shell FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rapid7.com/blog/post/etr-cve-2026-109618-and-cve-2026-109619-critical-unauthenticated-remote-code-execution-in-php/" rel="noopener noreferrer"&gt;Rapid7: CVE-2026-109618 and CVE-2026-109619&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert.europa.eu/publications/security-advisories/2026-018" rel="noopener noreferrer"&gt;CERT-EU advisory 2026-018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cybelangel.com/blog/critical-php-rce-vulnerabilities-cve-2026-109618-and-cve-2026-109619/" rel="noopener noreferrer"&gt;CybelAngel: exposure numbers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Keep your server safe:&lt;/strong&gt; read our guide on &lt;a href="https://arelvox.com/how-to-secure-your-home-network-a-practical-cybersecurity-guide-for-beginners/" rel="noopener noreferrer"&gt;securing your network&lt;/a&gt;, brush up on &lt;a href="https://arelvox.com/essential-linux-commands-every-beginner-should-know/" rel="noopener noreferrer"&gt;essential Linux commands&lt;/a&gt;, and set up automatic tasks with our &lt;a href="https://arelvox.com/cron-expression-generator/" rel="noopener noreferrer"&gt;cron expression generator&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arelvox.com/php2shell-cve-2026-109618-cve-2026-109619/" rel="noopener noreferrer"&gt;Arelvox&lt;/a&gt;, where the guide is kept up to date as new patches and attack data come out. You can also &lt;a href="https://arelvox.com/" rel="noopener noreferrer"&gt;get free security alerts&lt;/a&gt; by email.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>webdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>I Built a One-Tap Stacking Game in Vanilla JS: The Tiny Details That Make It Addictive</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Sun, 25 Oct 2026 02:21:52 +0000</pubDate>
      <link>https://dev.to/arelvox/i-built-a-one-tap-stacking-game-in-vanilla-js-the-tiny-details-that-make-it-addictive-1jpi</link>
      <guid>https://dev.to/arelvox/i-built-a-one-tap-stacking-game-in-vanilla-js-the-tiny-details-that-make-it-addictive-1jpi</guid>
      <description>&lt;p&gt;&lt;a href="https://arelvox.com/sky-stack/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frtqdxl70jhybarmlr4se.jpg" alt="Sky Stack, a one-tap stacking game" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One-tap games look trivial. You tap, a block drops, done. But when I built &lt;strong&gt;Sky Stack&lt;/strong&gt;, a small stacking game in plain JavaScript and canvas, the fun turned out to depend on a few tiny decisions. Here is how it works and what made it feel good to play.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Play it first (30 seconds):&lt;/strong&gt; &lt;a href="https://arelvox.com/sky-stack/" rel="noopener noreferrer"&gt;arelvox.com/sky-stack&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole game in one idea
&lt;/h2&gt;

&lt;p&gt;The tower is an array of blocks, and each block is only an &lt;code&gt;x&lt;/code&gt; position and a width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;     &lt;span class="c1"&gt;// the base&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;     &lt;span class="c1"&gt;// the block sliding across the screen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every frame the current block moves left or right and bounces at the edges. When you tap, you compare it with the top of the tower.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core: intersecting two ranges
&lt;/h2&gt;

&lt;p&gt;Dropping a block is just the overlap of two 1D ranges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;topBlock&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;overlap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overlap&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;gameOver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// missed the tower completely&lt;/span&gt;

  &lt;span class="c1"&gt;// the part that hangs over the edge becomes a falling piece&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;cutX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;falls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cutX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;yOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;overlap&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;nextBlock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire game logic. Everything else is about feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. A "perfect" zone that forgives you
&lt;/h2&gt;

&lt;p&gt;Pixel-perfect drops are almost impossible on a phone, so anything within &lt;strong&gt;5 pixels&lt;/strong&gt; counts as perfect. A perfect drop cuts nothing off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combo&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;grow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;combo&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// reward streaks&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;grow&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;grow&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;grow&lt;/code&gt; line matters most. In most stacking games your tower only shrinks, so every run feels like a slow loss. Here, from the third perfect drop in a row, the block &lt;strong&gt;grows back&lt;/strong&gt; a little. Suddenly a bad drop is something you can recover from, and that is what makes people tap "play again".&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Sound that climbs with your combo
&lt;/h2&gt;

&lt;p&gt;No audio files, just the Web Audio API. Each perfect drop plays a note one step higher than the last:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOscillator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createGain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;triangle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frequency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValueAtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exponentialRampToValueAtTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dur&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;tone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;440&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;combo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mf"&gt;0.18&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// roughly one whole tone per step&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You &lt;em&gt;hear&lt;/em&gt; your streak building before you look at the score. On a miss, a dull low note plays and the pitch resets. It costs about ten lines and changes the game a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Juice: falling pieces, particles, a little shake
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The piece you cut off keeps falling and rotating instead of disappearing.&lt;/li&gt;
&lt;li&gt;A perfect drop throws white particles from the edges.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;navigator.vibrate(15)&lt;/code&gt; gives a tiny buzz on phones that support it.&lt;/li&gt;
&lt;li&gt;The camera eases upward as the tower grows, so you never see a hard jump.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this changes the rules, but without it the game felt dead in testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A restart bug I almost shipped
&lt;/h2&gt;

&lt;p&gt;The first version had a nasty bug: the tap that &lt;em&gt;ended&lt;/em&gt; the game also counted as a tap on the "play again" overlay, so a new run started instantly and players never saw their score. The fix was to keep the two states strictly apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onTap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;playing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;over&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// ignore taps while the game-over screen is up&lt;/span&gt;
  &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlay has its own handler that only restarts once the game is really over. Keyboard Space and Enter only restart when the overlay is visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. It has to fit anywhere
&lt;/h2&gt;

&lt;p&gt;The game is a single self-contained block of HTML, CSS and JS. No framework, no build step, no external requests. It runs inside a WordPress "Custom HTML" block. One tip if you ever do the same: WordPress can mangle &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; inside inline scripts, so the code uses ternaries instead. Annoying, but it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it and beat my score
&lt;/h2&gt;

&lt;p&gt;Drop your best score in the comments. Anything over 50 is already good, and 100+ means your timing is seriously sharp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arelvox.com/sky-stack/" rel="noopener noreferrer"&gt;Play Sky Stack →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you like small browser games, there are a few more on the same site: a daily number puzzle with a &lt;a href="https://arelvox.com/crack-the-code/" rel="noopener noreferrer"&gt;Time Attack mode&lt;/a&gt; and a &lt;a href="https://arelvox.com/bug-hunt-typing-game/" rel="noopener noreferrer"&gt;typing game with boss bugs&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Calculate Subnets by Hand: A Step-by-Step Guide</title>
      <dc:creator>Arelvox</dc:creator>
      <pubDate>Sat, 24 Oct 2026 19:40:57 +0000</pubDate>
      <link>https://dev.to/arelvox/how-to-calculate-subnets-by-hand-a-step-by-step-guide-3eh8</link>
      <guid>https://dev.to/arelvox/how-to-calculate-subnets-by-hand-a-step-by-step-guide-3eh8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://arelvox.com/how-to-calculate-subnets/" rel="noopener noreferrer"&gt;Arelvox&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Knowing how to calculate subnets is one of the most useful networking skills you can learn. It comes up in certification exams, in home labs and whenever you plan a network for an office. The good news is that you do not need complex binary math. With one simple trick, you can find the network address, broadcast address and host range of any IPv4 address in under a minute.&lt;/p&gt;

&lt;p&gt;In this guide you will learn how to calculate subnets by hand, step by step, with real examples. When you finish, you can check every answer with our free &lt;a href="https://arelvox.com/subnet-calculator/" rel="noopener noreferrer"&gt;subnet calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgh0xme4bkb9uzfar6gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhgh0xme4bkb9uzfar6gb.png" alt="How to calculate subnets by hand: network, broadcast and host range for 192.168.10.75/26" width="799" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Need to Know First
&lt;/h2&gt;

&lt;p&gt;Before you learn how to calculate subnets, you only need a few basic ideas. Every IPv4 address has 32 bits, split into four groups of 8 bits called octets. A subnet mask, or a prefix like /24, tells you how many of those bits belong to the network. The remaining bits identify individual devices, called hosts.&lt;/p&gt;

&lt;p&gt;For example, in 192.168.1.10/24 the first 24 bits are the network part and the last 8 bits are the host part. If these ideas are new to you, first read our guide to &lt;a href="https://arelvox.com/the-ultimate-guide-to-your-home-network-ips-routers-and-dns/" rel="noopener noreferrer"&gt;IP addresses, routers and DNS&lt;/a&gt;, and then come back here.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prefix&lt;/th&gt;
&lt;th&gt;Subnet mask&lt;/th&gt;
&lt;th&gt;Block size&lt;/th&gt;
&lt;th&gt;Usable hosts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;255.255.255.0&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;254&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/25&lt;/td&gt;
&lt;td&gt;255.255.255.128&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/26&lt;/td&gt;
&lt;td&gt;255.255.255.192&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/27&lt;/td&gt;
&lt;td&gt;255.255.255.224&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/28&lt;/td&gt;
&lt;td&gt;255.255.255.240&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/29&lt;/td&gt;
&lt;td&gt;255.255.255.248&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/30&lt;/td&gt;
&lt;td&gt;255.255.255.252&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Magic Number Method
&lt;/h2&gt;

&lt;p&gt;The fastest way to calculate subnets by hand is the &lt;strong&gt;magic number&lt;/strong&gt;, also called the block size. Subnets always start at multiples of this number, so once you know it, everything else falls into place.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the &lt;strong&gt;interesting octet&lt;/strong&gt;: the first octet of the mask that is not 255.&lt;/li&gt;
&lt;li&gt;Calculate the magic number: &lt;strong&gt;256 minus the mask value&lt;/strong&gt; in that octet.&lt;/li&gt;
&lt;li&gt;List the multiples of the magic number. The &lt;strong&gt;network address&lt;/strong&gt; is the multiple that is equal to or just below your IP.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;broadcast address&lt;/strong&gt; is the next multiple minus one.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;usable hosts&lt;/strong&gt; are everything between the network and broadcast addresses.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Example 1: 192.168.10.75/26
&lt;/h2&gt;

&lt;p&gt;Let us see how to calculate subnets with a common exam question. First, convert /26 into a mask: 255.255.255.192. The interesting octet is the fourth one, because it is the first that is not 255.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Magic number: 256 − 192 = &lt;strong&gt;64&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Multiples of 64: 0, 64, 128, 192. Our IP ends in 75, so it sits between 64 and 128.&lt;/li&gt;
&lt;li&gt;Network address: &lt;strong&gt;192.168.10.64&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Broadcast address: 128 − 1 = &lt;strong&gt;192.168.10.127&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Usable host range: &lt;strong&gt;192.168.10.65 to 192.168.10.126&lt;/strong&gt;, which is 62 hosts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the whole calculation. As a result, you can solve most subnetting questions without writing a single binary number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2: 172.16.45.200/20
&lt;/h2&gt;

&lt;p&gt;This second example shows how to calculate subnets when the interesting octet is not the last one. A /20 prefix gives the mask 255.255.240.0, so this time the interesting octet is the third.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Magic number: 256 − 240 = &lt;strong&gt;16&lt;/strong&gt;, applied to the third octet.&lt;/li&gt;
&lt;li&gt;Multiples of 16: 0, 16, 32, 48. The third octet is 45, so it falls between 32 and 48.&lt;/li&gt;
&lt;li&gt;Network address: &lt;strong&gt;172.16.32.0&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Broadcast address: &lt;strong&gt;172.16.47.255&lt;/strong&gt;, because the fourth octet is all ones.&lt;/li&gt;
&lt;li&gt;Usable hosts: 172.16.32.1 to 172.16.47.254, or 4,094 addresses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that the fourth octet is 0 for the network and 255 for the broadcast. This happens whenever the interesting octet is not the last one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Split a Network into Subnets
&lt;/h2&gt;

&lt;p&gt;Knowing how to calculate subnets also lets you do the opposite: take one network and divide it into smaller ones. To do that, you borrow host bits. Each borrowed bit doubles the number of subnets and halves their size.&lt;/p&gt;

&lt;p&gt;For example, to split 192.168.1.0/24 into four subnets, you borrow 2 bits, because 2² = 4. The new prefix is /26, with a magic number of 64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.0/26     hosts .1   to .62
192.168.1.64/26    hosts .65  to .126
192.168.1.128/26   hosts .129 to .190
192.168.1.192/26   hosts .193 to .254
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, if you start from the number of hosts you need, choose the smallest subnet that fits. Remember that you lose two addresses in every subnet, one for the network and one for the broadcast. For 50 devices you need a /26 (62 usable hosts), and for 500 devices you need a /23 (510 usable hosts).&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes When You Calculate Subnets
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting the two reserved addresses.&lt;/strong&gt; A /26 has 64 addresses but only 62 usable hosts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Using the wrong octet.&lt;/strong&gt; Always apply the magic number to the first octet of the mask that is not 255.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixing up the broadcast address.&lt;/strong&gt; It is the next network address minus one, not the next network itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring the special cases.&lt;/strong&gt; A /31 is used for point-to-point links and has two usable addresses, as described in &lt;a href="https://datatracker.ietf.org/doc/html/rfc3021" rel="noopener noreferrer"&gt;RFC 3021&lt;/a&gt;, while a /32 is a single host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best way to avoid these mistakes is practice. Pick random addresses, calculate the subnets by hand and then confirm your results with the &lt;a href="https://arelvox.com/subnet-calculator/" rel="noopener noreferrer"&gt;subnet calculator&lt;/a&gt;. After ten or twenty examples, the method becomes automatic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice Questions
&lt;/h2&gt;

&lt;p&gt;Try these on your own before checking the answers with the calculator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;10.0.5.130/25: what is the network address?&lt;/li&gt;
&lt;li&gt;192.168.50.17/28: what is the broadcast address?&lt;/li&gt;
&lt;li&gt;172.20.100.9/22: how many usable hosts are there?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Answers: 10.0.5.128, 192.168.50.31 and 1,022 hosts. If you got all three right, you now know how to calculate subnets by hand. For the full background on CIDR prefixes, the &lt;a href="https://en.wikipedia.org/wiki/Subnetwork" rel="noopener noreferrer"&gt;Wikipedia article on subnetworks&lt;/a&gt; is a good next read.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ: How to Calculate Subnets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the easiest way to calculate subnets?
&lt;/h3&gt;

&lt;p&gt;The easiest way is the magic number method: subtract the interesting mask octet from 256 and list its multiples. The network address is the multiple at or below your IP, and the broadcast is the next multiple minus one.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many hosts are in a /26 subnet?
&lt;/h3&gt;

&lt;p&gt;A /26 subnet has 64 addresses and 62 usable hosts, because the network address and the broadcast address cannot be assigned to devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to know binary to calculate subnets?
&lt;/h3&gt;

&lt;p&gt;No. Binary helps you understand why the method works, but for day-to-day subnetting and most exam questions the magic number method is faster and less error-prone.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to check your answers? Try the free &lt;a href="https://arelvox.com/subnet-calculator/" rel="noopener noreferrer"&gt;IP subnet calculator&lt;/a&gt; on Arelvox.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
