<?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: glassonion1</title>
    <description>The latest articles on DEV Community by glassonion1 (@glassonion1).</description>
    <link>https://dev.to/glassonion1</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%2F4161154%2F46ef9a0f-d9c6-44f7-a7a6-eba2cf05c348.png</url>
      <title>DEV Community: glassonion1</title>
      <link>https://dev.to/glassonion1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/glassonion1"/>
    <language>en</language>
    <item>
      <title>How to encode a won Minesweeper board in base64url</title>
      <dc:creator>glassonion1</dc:creator>
      <pubDate>Sun, 04 Oct 2026 09:48:52 +0000</pubDate>
      <link>https://dev.to/glassonion1/how-to-encode-a-won-minesweeper-board-in-base64url-4d2j</link>
      <guid>https://dev.to/glassonion1/how-to-encode-a-won-minesweeper-board-in-base64url-4d2j</guid>
      <description>&lt;p&gt;I built a Minesweeper that runs in the browser, and thought it would be nice if you&lt;br&gt;
could share your win screen as a URL and nothing else.&lt;/p&gt;

&lt;p&gt;What I ended up with is compact enough to be worth writing down. Here is the algorithm,&lt;br&gt;
with the implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  The format
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://9revolution9.com/games/minesweeper?b=0909&amp;amp;mf=kAAQogCAIAiAAHdg&amp;amp;t=125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That link is real. It opens a won 9×9 with eight flags on it, finished in 12.5 seconds.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;b&lt;/code&gt; is the level, which in Minesweeper is just a board size. Two digits for the width&lt;br&gt;
and two for the height. The three standard levels have been the same since the Windows&lt;br&gt;
version.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;level&lt;/th&gt;
&lt;th&gt;board&lt;/th&gt;
&lt;th&gt;mines&lt;/th&gt;
&lt;th&gt;&lt;code&gt;b&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;beginner&lt;/td&gt;
&lt;td&gt;9×9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0909&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;intermediate&lt;/td&gt;
&lt;td&gt;16×16&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1616&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expert&lt;/td&gt;
&lt;td&gt;30×16&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3016&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The mine count is not in &lt;code&gt;b&lt;/code&gt;. Counting the mine bits in &lt;code&gt;mf&lt;/code&gt; gives it, and that also&lt;br&gt;
leaves room for board sizes nobody has standardised.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;t&lt;/code&gt; is the time in tenths of a second.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mf&lt;/code&gt; is the board, packed six bits to a base64url character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ width × height bits ]  is this square a mine?   row-major, left to right
[ N bits              ]  was this mine flagged?   same order, mines only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;N&lt;/code&gt; is the number of mines. On expert that makes &lt;code&gt;mf&lt;/code&gt; 97 characters, and 115 for the&lt;br&gt;
whole query string.&lt;/p&gt;

&lt;p&gt;Nothing else travels. 3BV, the usual measure of how hard a board was, is counted from&lt;br&gt;
the mine layout, and 3BV/s is 3BV divided by the time. If the reader can work it out, it&lt;br&gt;
does not go in the URL.&lt;/p&gt;
&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Encoding is one pass over the bits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flagged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;// The mines, then the flags of the mined squares in the same order.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;mines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;flagged&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="nx"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;mines&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&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;let&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;bits&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="nx"&gt;BITS_PER_CHAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&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;0&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;let&lt;/span&gt; &lt;span class="nx"&gt;j&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;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;BITS_PER_CHAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bits&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="nx"&gt;j&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="nx"&gt;mf&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;ALPHABET&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="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;On the last group &lt;code&gt;bits[i + j]&lt;/code&gt; reads past the end and gives &lt;code&gt;undefined&lt;/code&gt;, which packs as&lt;br&gt;
a zero. That is the padding rule, so the tail needs no special case.&lt;/p&gt;

&lt;p&gt;Decoding reverses it. The first &lt;code&gt;width × height&lt;/code&gt; bits are the mines and the rest are the&lt;br&gt;
flags. Counting the set bits in the first part gives &lt;code&gt;N&lt;/code&gt;, so nothing has to carry it.&lt;br&gt;
Lining the flag bits up with the mines is the only fiddly part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decode&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Board&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="p"&gt;...&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flagBits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bits&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="nx"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;mineCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flagged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mines&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="nx"&gt;isMine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMine&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;flagBits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;next&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="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;next&lt;/code&gt; only moves when a mine turns up, so the two lists stay in step without either&lt;br&gt;
side carrying an index.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why the flags are one bit each
&lt;/h2&gt;

&lt;p&gt;The flags take one bit per mine rather than one bit per square. That is where the whole&lt;br&gt;
saving comes from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ width × height bits ]  is this square a mine?   row-major, left to right
[ N bits              ]  was this mine flagged?   same order, mines only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Storing two facts per square, a mine bit and a flag bit, costs 480 plus 480 bits on&lt;br&gt;
expert. At six bits per character that is 160 characters, before the time is added. Only&lt;br&gt;
99 of those squares hold a mine, so half the string describes empty ground.&lt;/p&gt;

&lt;p&gt;Minesweeper has a rule that makes most of those flag bits unnecessary.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A won board has every non-mine square opened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A flag stands on an unopened square. A flag on an empty square therefore means an&lt;br&gt;
unopened empty square, and that board is not won. On a won board, every flag is on a&lt;br&gt;
mine.&lt;/p&gt;

&lt;p&gt;The mines are already in the string, so the flags never have to say where they are. They&lt;br&gt;
answer yes or no for each mine in turn. That is 99 bits on expert instead of 480, and&lt;br&gt;
the board drops from 960 bits to 579, with no compression and no lookup table.&lt;/p&gt;

&lt;p&gt;The rule holds for a won board and nowhere else. A lost one can have flags on empty&lt;br&gt;
squares and needs the full bitmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Has somebody done this already?
&lt;/h2&gt;

&lt;p&gt;minesweeper.online, the best known Minesweeper site, puts boards in URLs too. Two things&lt;br&gt;
are different.&lt;/p&gt;

&lt;p&gt;It cannot put the flags back. It carries the mine layout and nothing else.&lt;/p&gt;

&lt;p&gt;It also packs less tightly. The same expert board takes 96 characters there and 80 here,&lt;br&gt;
or 97 once every flag is added.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is on GitHub
&lt;/h2&gt;

&lt;p&gt;I wrote the whole thing up as a spec, in case anybody else is building a Minesweeper and&lt;br&gt;
wants boards that travel. The reference implementation has no dependencies and comes to&lt;br&gt;
92 lines, counting the types, the comments and the validation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/glassonion1/minesweeper-board-format" rel="noopener noreferrer"&gt;glassonion1/minesweeper-board-format&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You can play it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://9revolution9.com/games/minesweeper" rel="noopener noreferrer"&gt;The Minesweeper is here.&lt;/a&gt; Win a game and it&lt;br&gt;
hands you one of these links. Open somebody else's and you get their board, their flags&lt;br&gt;
and their time, exactly as they left it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>showdev</category>
      <category>algorithms</category>
    </item>
  </channel>
</rss>
