tag:blogger.com,1999:blog-50523872024-03-17T16:13:55.286-07:00Blobs in Gameshttps://www.redblobgames.com/ is where I post interactive articles about math and computer science topics, mostly related to computer game development. On this blog I talk about how I make these articles.Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.comBlogger264125tag:blogger.com,1999:blog-5052387.post-88452102885840448922023-12-31T16:14:00.000-08:002023-12-31T16:15:59.570-08:00What I did in 2023<p>
It's time for my annual self review. <a href="https://simblob.blogspot.com/2022/12/what-i-did-in-2022.html">In last year's review</a> I said I didn't have clear goals for 2023, and I would probably just do whatever was interesting. I did a bunch of interesting things. Unlike last year, I didn't spend much time on art or science. The first part of the year I was learning about drag & drop on the web. Then I worked on map generation. and the last part of the year were productive, but <a href="https://simblob.blogspot.com/2023/07/experiments-and-motivation.html">I felt kind of aimless in the middle</a>.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/circle-drawing/blog/layers-diagram.png" alt="Diagram showing how I make interactive diagrams" />
</figure>
<a name='more'></a>
<h3>New pages</h3>
<p>
I wrote only two full articles, each taking over a month to write. Both are for the small audience of people making interactive tutorials. Neither is for the large audience of people making games.
</p>
<ol class="org-ol">
<li>Many years ago I wrote my interactive tutorials with d3.js. I now use vue.js for some of them. In 2018 I had written a page that showed how I used d3.js to make my line-drawing page. This year I wrote a page <a href="https://simblob.blogspot.com/2023/01/making-of-circle-drawing.html">showing how I used vue.js</a> to make the circle-drawing page. It's an <em>interactive tutorial about making interactive tutorials</em>.</li>
<li>While writing the making-of circle-drawing page, I realized I had been "<a href="https://en.wikipedia.org/wiki/Cargo_cult_programming">cargo culting</a>" the mouse event handling code. In particular, I had been using <code>preventDefault()</code> and <code>stopPropagation()</code> all the time, instead of using them because I had a specific need for them. I decided to take a week to learn this, and discovered it was a deep rabbit hole that took <em>months</em> to figure out! <a href="https://simblob.blogspot.com/2023/02/making-of-draggable-objects.html">I wrote up my recommendations</a>.</li>
</ol>
<p>
In writing the "making of" circle-drawing page, I discovered several places where I could improve the circle-drawing page. And in writing the "making of" event-handling page, I discovered lots of things I could improve on my existing pages, including making them more consistent and more mobile-friendly. I'm <em>very</em> happy with both of these projects.
</p>
<p>
While learning about event handling, I made various test pages (<a href="https://www.redblobgames.com/x/2251-draggable/">one</a>, <a href="https://www.redblobgames.com/x/2305-passive-events/">two</a>, <a href="https://www.redblobgames.com/making-of/draggable/targets.html">three</a>), and the scenarios got increasingly more ridiculous as I went on:
</p>
<ul class="org-ul">
<li>what if you drag to move an object but the page uses drag to scroll</li>
<li>what if you switch to another window in the middle of dragging</li>
<li>what if you drag an object while you've selected text, which is also draggable</li>
<li>what happens if you rotate your phone while dragging</li>
<li>what if you drag with one finger but then use two other fingers to pinch to zoom</li>
<li>what happens if you hold down the left mouse button, then the right, then let go of the left</li>
<li>what if you plug in two mice and start dragging with the first but let go with the second</li>
<li>what if you set up an ipad as a second screen on a mac and you use the stylus to drag from the ipad to the mac screen</li>
<li>what if you start a drag operation and put the computer to sleep in the middle</li>
</ul>
<p>
Event handling is tricky! I didn't try to solve <em>all</em> of these problems, but I tried to handle common cases.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/draggable/build/diagram-state-and-event-handlers.svg" alt="Diagram showing how the event handler and state handlers are related" class="org-svg" />
</figure>
<p>
I really like the way I designed the event handling code. In popular libraries like <a href="https://api.jqueryui.com/draggable/">jquery-ui</a> and <a href="https://github.com/react-grid-layout/react-draggable#draggable-props">react-draggable</a>, the library gives you a set of features to choose from, such as a drag handle, snap to grid, and constrain position to a rectangle. But what if I want to snap to a hex grid, or constrain to a non-rectangle? I'm out of luck. In my projects, the event handler is <em>decoupled</em> from a state handler, which can implement any of those features, and a lot more. I'm working on <a href="https://www.redblobgames.com/making-of/draggable/examples.html">an examples page</a> to show the kinds of things I do with it. I had hoped it would be ready by now but I keep thinking of more examples to add to it.
</p>
<p>
I think both of these pages may be useful to a wider audience of web developers. For example, the second article was used <a href="https://github.com/ClickHouse/ClickHouse/pull/55649">to improve touch events on ClickHouse</a>. I didn't have that audience in mind when writing these pages but it's something I'll consider for future pages.
</p>
<h3>Updating pages</h3>
<p>
I treat my site like a personal wiki. I publish new pages, but I also go back and improve old pages. I don't consider them "final". I continue to update pages I wrote nearly 30 years ago. So this year is no different — I've made improvements to existing pages.
</p>
<p>
The biggest project was mapgen4. Towards the end of that project, I felt like I never wanted to see the code again. It took me a few years before I wanted to work on it again. Last year I switched the code from JavaScript to TypeScript. This year I changed <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-1.html">changed the internal naming convention</a> at the suggestion of Tom Forsyth. I'm much happier with the new naming.
</p>
<figure>
<img src="https://www.redblobgames.com/x/2314-poisson-with-boundary/blog/double-boundary-points-triangulation.png" alt="Screenshot of Delaunay triangulation with a double border" />
</figure>
<p>
Updating the code reminded me that there was an unsolved problem I had hidden under the edges. The map edges were jagged! They weren't even consistently jagged. They looked awful. My "fix" back in 2018 was to slightly zoom in on the map so that you couldn't see the edges. That fix works. But it's unsatisfying.
</p>
<p>
<a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-2.html">I went back and investigated</a>. I found several bugs and fixed them. <a href="https://www.redblobgames.com/x/2314-poisson-with-boundary/">I wrote up the results</a>. I was very happy to have finally fixed this!
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/7-underground-view.png" alt="Screenshot of mapgen4 showing how the edges are straight and also show the underground areas" />
</figure>
<p>
Working on the edges reminded me of a feature I wanted but couldn't implement because of the jagged edges: showing the sides of the map to make it look 3 dimensional. <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-3.html">I implemented that</a> and loved how it looks! Nobody will see it because the default map view is top-down, but I'm very happy to have it there.
</p>
<p>
On the <a href="https://www.redblobgames.com/maps/terrain-from-noise/">noise-based map generation</a> article, I <a href="https://redblobgames.notion.site/2023-Mar-Terrain-from-noise-demo-section-5702b83b8c4143be88f66f63690f14ec">added sliders to the sample code</a>. Instead of seeing a number you want to change, scrolling down, finding the corresponding slider, moving it, and then scrolling back up to see the map, you can change the number in place and see the map at the same time.
</p>
<p>
At the suggestion of a reader, I updated <a href="https://www.redblobgames.com/x/1902-hexagon-coloring/">my hexagon coloring page</a> with a rock-paper-scissors rule showing which hexes "beat" adjacent hexes. I added <a href="https://www.redblobgames.com/grids/circle-drawing/#sector">a sector restriction option</a> to my circle drawing page. I added an explanation of <a href="https://simblob.blogspot.com/2023/04/explaining-hexagon-layout-class.html">the Layout class for my hexagon library</a>. There were many other changes too small to list here.
</p>
<h3>Experiments</h3>
<p>
A few years ago I had fallen into a perfectionism trap. My hexagon and A* pages were so polished that starting a new page felt daunting. I couldn't live up to my expectations. I had to give myself permission to make incomplete, unpolished pages. That's been a great success. Most of my work now starts out as unpolished pages that are <em>just for myself</em> and not intended for sharing. Some of them I end up sharing.
</p>
<figure>
<img src="https://www.redblobgames.com/x/2344-hex-edge-pathfinding/blog/pathways.png" alt="Screenshot of possible flying paths between hex edges" />
</figure>
<p>
Most of the experiments are incomplete but that's ok. A sample:
</p>
<ul class="org-ul">
<li><a href="https://simblob.blogspot.com/2023/03/tank-control-experiments.html">Tank control with a mouse</a>, ported from Flash</li>
<li><a href="https://www.redblobgames.com/x/2319-mapgen4-scenarios/">Mapgen4 plate tectonics</a>, abandoned</li>
<li><a href="https://www.redblobgames.com/x/2344-hex-edge-pathfinding/">Hexagon edge paths</a>, abandoned</li>
<li><a href="https://www.redblobgames.com/x/2326-hex-3d/">Two ways to make a 3d height map with hexes</a> (hex faces and hex vertices)</li>
<li><a href="https://www.redblobgames.com/pathfinding/a-star/main-loop.html">A* main loop</a>, something I try every year but am still not happy with</li>
<li><a href="https://www.redblobgames.com/x/2346-svg-glitter-line/">A glitter effect in svg</a>, didn't work that well</li>
</ul>
<h3>Learning</h3>
<figure>
<img src="https://www.redblobgames.com/grids/line-drawing/blog/bresenham_abrash-vs-bresenham_wikipedia_1.png" alt="Screenshot showing everywhere two Bresenham implementations differ" />
</figure>
<p>
After many years of encouraging lerp instead of Bresenham's Line Algorithm, I decided I should test the properties of these algorithms. To my great disappointment, lerp wasn't always a winner. To my great surprise, there doesn't seem to be a single canonical "Bresenham's Line Algorithm" implementation, and the many implementations I found <em>differ in their results</em>. I have a <a href="https://www.redblobgames.com/grids/line-drawing/bresenham.html">page where I'm testing the implementations</a> but I haven't written up the results yet.
</p>
<p>
A reader sent in an idea for <a href="https://www.redblobgames.com/x/2317-hexagon-shaped-hex-map-storage/">storing hexagon-shaped maps in rhombuses</a>, and I tried it out. Neat idea, but I haven't had a chance to use it in a real project. A reader sent in an idea for <a href="https://www.redblobgames.com/x/2321-offgrid/">making irregular rectangular room maps</a>, and I tried it out. It's simpler and easier than <em>any</em> other algorithm I've seen for making this style of map, and I ended up using it later in a game project.
</p>
<p>
I continued learning browser technologies, including web components,
<a href="https://www.redblobgames.com/x/2310-drag-drop-api/">the drag-and-drop API</a>, <a href="https://www.redblobgames.com/x/2301-vue-dynamic-template/">Vue dynamic templates</a>, CSS grid, import maps, oklab colors, and <a href="https://www.redblobgames.com/x/2316-learning-petite-vue/">Petite Vue</a>.
</p>
<p>
I've spent time with local large language models (LLMs), especially finding <a href="https://llm.datasette.io/en/stable/">Simon Willison's blog</a> to be helpful, and also following <a href="https://old.reddit.com/r/LocalLLaMA/">reddit <em>r/LocalLLaMA</em></a>. Mostly I'm doing this out of curiosity. I've tried and failed to do some useful things, like <a href="https://amitp.blogspot.com/2023/11/using-llm-to-query-my-notes.html">building an index to ask questions of my notes</a>, calculating which of my pages are most similar to each other, and code completion. Next I want to try <a href="https://hacks.mozilla.org/2023/11/introducing-llamafile/">llamafile</a> with <a href="https://github.com/jart/emacs-copilot">emacs integration</a>.
</p>
<h3>Games</h3>
<figure>
<img src="https://www.redblobgames.com/x/2327-roguelike-dev/build/data-objects.png" alt="Diagram showing how game objects might be related to each other" />
</figure>
<p>
Last year I had attempted to make a Dwarf Fortress clone in a week. I failed. This year I decided I needed a <em>much</em> smaller scope for the summer <code>r/roguelikedev</code> event. I made a colony simulator that had a procedurally generated rooms instead of build-your-own-rooms like other colony simulators. I made a lot of progress and <a href="https://simblob.blogspot.com/2023/10/summer-roguelikedev-event.html">wrote up the results</a>. I was happy with the technical aspects of the project, but didn't get a game design I liked.
</p>
<p>
While working on that project I realized some of the data structures were error prone, and I decided I would draw on relational databases to design something simpler. It's not ECS, but it's related. <a href="https://simblob.blogspot.com/2023/11/jobs-table-implementation-for.html">I wrote a blog post about it</a>.
</p>
<p>
I tried once again to make a <a href="https://www.redblobgames.com/x/2322-spaceship-flyer/">spaceship flyer game</a> but once again I lost motivation. I may have to accept that this is one of those projects where I want to <em>have done it</em> but I don't want <em>actually do it</em>.
</p>
<h3>Site management</h3>
<p>
Many people have noticed that search engines and social media <a href="https://danluu.com/seo-spam/">aren't as useful as they once were</a>. In the old days of the web, everyone shared links to the good stuff, because you couldn't find it on search engines. We don't have enough people linking anymore. My <a href="http://www-cs-students.stanford.edu/~amitp/gameprog.html">game programming site</a> started out as a link sharing site. I linked to the articles I found useful for my projects. I now have over 4000 links from my site.
</p>
<p>
Over time, a lot of these links have broken.
</p>
<ol class="org-ol">
<li>For links <em>from</em> my pages <em>to</em> the outside world: I've integrated gradual link checking into my web site build process. It's no longer something separate that I have to remember to run. The UI is terrible but I'm slowly working on that.</li>
<li>For links <em>to</em> my pages <em>from</em> the outside world: I try to maintain forwarding links forever. <a href="https://simblob.blogspot.com/2023/08/changing-url.html">I wrote up some of what I do</a> to keep links working.</li>
</ol>
<p>
One of my goals for 2024 is to figure out how to make site management easier, both in terms of link checking and also in terms of page organization/searching.
</p>
<p>
For search engines, I've started using <a href="https://help.kagi.com/kagi/why-kagi/kagi-vs-competition.html#kagi-vs-google">Kagi</a>. I love the idea behind <a href="https://teclis.com/">Teclis</a>: crawl a page with and without an ad blocker, and remove pages that are full of ads. I also love the idea behind <a href="https://search.marginalia.nu/">Marginalia</a>: focus on discovering non-commercial pages.
</p>
<p>
For social media, I read Twitter, HN, Reddit, StackOverflow, but I don't post often anymore. I am active on my blog and Discord. I'm not on Mastodon or BlueSky or Threads. I don't want to switch from Twitter to another social network. <a href="https://indieweb.org/">I want to switch to my own site</a>. One of my goals for 2024 is to figure out how to move my Blogspot blog to my own site.
</p>
<p>
For project tracking, I had moved away from Trello when Atlassian bought them. I switched to Notion, but now Notion has become frustrating. I want to share links to my project pages like <a href="https://redblobgames.notion.site/2023-Mar-Terrain-from-noise-demo-section-5702b83b8c4143be88f66f63690f14ec">this</a> but sometimes (inconsistently) Notion will require a login. A lot of sites are doing this now (including Twitter and Reddit), and it discourages me from posting on those sites. One of my goals for 2024 is to move my project tracking to my own site.
</p>
<p>
I am still optimistic about the web. I think the good parts of the web are still there. Instead of using ad blockers to spend time & attention on the parts of the web I don't like, I want to spend my effort to find and share the parts of the web I do like. I visit <a href="https://indieblog.page">random IndieWeb sites</a> to discover new gems. I <a href="https://pinboard.in/u:amitp">link to things I find interesting</a>. I think this is how we take back the web: not by spending our energy hating, but by finding and celebrating things to love.
</p>
<p>
I wish everyone a good 2024!
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-9978770401782697302023-11-02T16:06:00.002-07:002023-11-02T17:03:38.287-07:00Jobs table implementation for r/roguelikedev project<p>
In the <a href="https://simblob.blogspot.com/2023/10/summer-roguelikedev-event.html">last post</a> I mentioned that while working on my colony simulator game for the r/roguelikedev summer event, I ran into a situation where my data structures made me unhappy. I was implementing a job system, where a colonist would keep track of what job they're doing, what items they're holding, and where they are standing. It looked like this before I rewrote it:
</p>
<figure>
<img src="https://www.redblobgames.com/x/2327-roguelike-dev/build/data-objects.png" alt="Object relationships diagram" />
<figcaption>Diagram: objects that point to each other</figcaption>
</figure>
<a name='more'></a>
<p>
Let's start with inventory. Colonists need to know what items they're holding. Items need to know where they are located, either on the ground or in a colonist's inventory. This is a two-way relationship. To simplify, let's assume a colonist can hold only one item, and a ground tile can have only one item. To have a colonist pick up an item from the ground:
</p>
<ol class="org-ol">
<li>Set <code>onGround[item.pos]</code> to <code>null</code>.</li>
<li>Set <code>colonist.inventory</code> to <code>item</code>.</li>
<li>Set <code>item.pos</code> to <code>colonist</code>.</li>
</ol>
<p>
All three of these have to happen to keep the data consistent. And I need to check preconditions too:
</p>
<ul class="org-ul">
<li>Ensure <code>item.pos</code> is the same as <code>colonist.pos</code>.</li>
<li>Ensure <code>colonist.inventory</code> is <code>null</code>.</li>
</ul>
<p>
This is a reasonable implementation. But what happens when the complexity goes up? There are a lot more things to keep in sync when the colonist takes a job, the job points to the colonist, the job points to the item, the colonist points to the item, the item points to the job, and so on.
</p>
<p>
I wanted to <strong>simplify</strong>. I remembered my times working on business apps, where I could store this type of data in <em>relational databases</em>. The main idea is to store data in a way that <a href="https://en.wikipedia.org/wiki/Database_normalization">eliminates redundancy</a>. For inventory, I could store it like this:
</p>
<table class="standard">
<colgroup>
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="text-left">Item</th>
<th scope="col" class="text-left">Location</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-left"><code>apple 3</code></td>
<td class="text-left">x=5, y=8</td>
</tr>
<tr>
<td class="text-left"><code>apple 4</code></td>
<td class="text-left">x=9, y=1</td>
</tr>
<tr>
<td class="text-left"><code>apple 5</code></td>
<td class="text-left"><code>Fred</code></td>
</tr>
</tbody>
</table>
<p>
I can put an <em>index</em> on both the Item and Location columns. That lets me look things up in both directions:
</p>
<ul class="org-ul">
<li>Where is <code>apple 3</code>? Look it up in the table using the Item column, and find the Location is <code>5,8</code>.</li>
<li>What is in location <code>9,1</code>? Look it up in the table using the Location column, and find the Item is <code>apple 4</code>.</li>
<li>What is <code>Fred</code> holding? Look it up in the table using the Location column, and find the Item is <code>apple 5</code>.</li>
</ul>
<p>
If I want <code>Wilma</code> to pick up <code>apple 4</code>, I can look it up in the Item column, and change Location to <code>Wilma</code>. There's only one change, not three like before.
</p>
<p>
But I don't have a relational database in the game. Instead, I can store this data in an array, and implement the indexing as two hash tables, one for Item and one for Location. Then I can encapsulate this in a Table data structure.
</p>
<p>
For the job system, it gets a bit more complicated. <em>Transport jobs</em> are of the form:
</p>
<table class="standard">
<colgroup>
<col class="org-left" />
<col class="org-left" />
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="text-left">Colonist</th>
<th scope="col" class="text-left">Take Item</th>
<th scope="col" class="text-left">From Location</th>
<th scope="col" class="text-left">To Location</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-left"><code>Barney</code></td>
<td class="text-left"><code>apple 3</code></td>
<td class="text-left">x=5, y=8</td>
<td class="text-left">x=3, y=1</td>
</tr>
<tr>
<td class="text-left"><code>Wilma</code></td>
<td class="text-left"><code>iron 14</code></td>
<td class="text-left">x=9, y=1</td>
<td class="text-left">x=5, y=5</td>
</tr>
</tbody>
</table>
<p>
<em>Production</em> jobs are of the form:
</p>
<table class="standard">
<colgroup>
<col class="org-left" />
<col class="org-left" />
<col class="org-left" />
<col class="org-left" />
</colgroup>
<thead>
<tr>
<th scope="col" class="text-left">Colonist</th>
<th scope="col" class="text-left">Work in Location</th>
<th scope="col" class="text-left">And produce item</th>
<th scope="col" class="text-left">duration</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-left"><code>Betty</code></td>
<td class="text-left">x=3, y=1</td>
<td class="text-left"><code>apple pie</code></td>
<td class="text-left">20min</td>
</tr>
<tr>
<td class="text-left"><code>Pebbles</code></td>
<td class="text-left">x=5, y=5</td>
<td class="text-left"><code>pickaxe</code></td>
<td class="text-left">40min</td>
</tr>
</tbody>
</table>
<p>
And that's it. The tables store all the information in one place, instead of it being scattered among many objects. I can ask things like
</p>
<ul class="org-ul">
<li>Is location <code>3,1</code> reserved by a job? Look it up in the To Location column. Answer: yes</li>
<li>Who is going to pick up <code>iron 14</code>? Look it up in the Take Item column. Answer: <code>Wilma</code></li>
<li>Is the crafting table at <code>3,1</code> being used? Look it up in the Work in Location column. Answer: yes</li>
<li>What is <code>Pebbles</code> doing? Look it up in the Colonist column of both tables.</li>
</ul>
<p>
I can also iterate over all jobs to run their simulation code. And tables are easier to serialize than a graph of objects.
</p>
<p>
At this point you might be wondering how this relates to ECS. I think the main difference is that ECS is a <em>subset</em> of the kinds of things relational databases do.
</p>
<ul class="org-ul">
<li><strong>E</strong> = Entity, typically a "game object", represented as unique id in the database</li>
<li><strong>C</strong> = Component, represented as a <em>table</em> in a relational database</li>
<li><strong>S</strong> = Systems, represented as a <em>query</em> involving a <em>join</em> of one or more tables</li>
</ul>
<p>
But neither inventory nor jobs are a "game object". They're separate tables with information <em>about</em> other entities. And most importantly, I want to have <strong>lookup on multiple columns</strong>. Most ECS implementations I've seen assume that lookups should be <em>only</em> on the entity id.
</p>
<p>
I think it's good to look at other things a relational database can do, as I think many of them will turn out to be useful in games. These relationship tables are just one example, and <a href="https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c">some ECS implementations have added them</a>. Range queries, including spatial indices, are another example. Queries like "what objects are within 20 meters of <code>Fred</code>?" are just another type of database query.
</p>
<p>
For the summer r/roguelikedev event, I didn't build a generic reusable Table data structure, but I did implement a non-generic table for the jobs system. I was pretty happy with how it went, but there were some quirks that I glossed over in the description above. In particular, some queries worked better when the transport jobs table and the production jobs table were combined, and some worked better when they were separate. In a relational database, I could build a "view" so that I could have both, but I didn't implement that here. I hope to get some more experience <a href="https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)">by implementing non-generic tables</a> several times. After that, I hope I'll have an idea of what I want in a generic table implementation.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com2tag:blogger.com,1999:blog-5052387.post-34434997719758773492023-10-15T14:41:00.002-07:002023-10-15T14:49:19.328-07:00Summer r/roguelikedev event<p>
Each summer the <a href="https://old.reddit.com/r/roguelikedev/">r/roguelikedev</a> community has a <a href="https://old.reddit.com/r/roguelikedev/wiki/python_tutorial_series">summer event</a> in which we all make a simple roguelike, roughly following the <a href="https://rogueliketutorials.com/">Python libtcod roguelike tutorial</a>. Last year <a href="https://www.redblobgames.com/x/2226-roguelike-dev/">I tried to clone Dwarf Fortress</a> in 40 hours. That was too ambitious. But I did enjoy working on a "fortress mode" project more than an "adventure mode" project, so I wanted to do something like that this year, but with a smaller scope.
</p>
<ul class="org-ul">
<li><a href="https://old.reddit.com/r/roguelikedev/comments/14kz7al/roguelikedev_does_the_complete_roguelike_tutorial/">Event announcement</a></li>
<li><a href="https://old.reddit.com/r/roguelikedev/comments/15xppyg/roguelikedev_does_the_complete_roguelike_tutorial/">Event conclusion</a> from the participants</li>
<li><a href="https://old.reddit.com/r/roguelikedev/comments/166514q/roguelikedev_tutorial_tuesday_2023_a_summary/">Event summary</a> from the organizers</li>
</ul>
<figure>
<img src="https://www.redblobgames.com/x/2327-roguelike-dev/blog/8-final.png" alt="Screenshot" />
<figcaption>Colony simulation game</figcaption>
</figure>
<a name='more'></a>
<p>
Although the event presents itself as a Python tutorial, lots of people pick other languages and libraries. And people deviate from the tutorial topics. For example, this year someone made a Roguelike MMO!
</p>
<p>
Unlike a typical "fortress mode" game, I wanted to have an end to the game. This was inspired by <a href="https://store.steampowered.com/app/1336490/Against_the_Storm/">Against the Storm</a>, which lets you build a town but then you win or lose, and you start another town.
</p>
<p>
In my game the wilderness is all accessible at the beginning. You can use natural resources at small scales. But to advance, you need to set up rooms to process resources. Last year, building rooms and walls tile by tile is what doomed my project. This year, I generated all the rooms procedurally. You'll claim and populate them to progress.
</p>
<p>
How'd it go?
</p>
<p>
I had to take a break in the middle of the project (real life obligations take priority) and continued a few weeks after the event ended. I'm not too worried about following the schedule or topics exactly. For me, the event is a way to inspire me to work on a project.
</p>
<p>
What went badly?
</p>
<ul class="org-ul">
<li>I never did make the game design work well.</li>
<li>I wasted too much time prematurely optimizing the code.</li>
<li>Despite keeping the scope small, <strong>it wasn't small enough</strong>.</li>
<li>I had lots of bugs in the simulation code. I had hoped to find a way to code it that was less error prone.</li>
<li>I lost motivation towards the end, in part because I couldn't figure out a game design I liked.</li>
</ul>
<p>
What went well?
</p>
<ul class="org-ul">
<li>I liked the "pick one of three choices" room unlocking mechanic.</li>
<li><strong>I got the job system working</strong>. This is probably the thing I'm happiest about. It's been a big stumbling block for me in past projects, and I was able to push through and get everything working this year.</li>
<li>I found a <a href="https://www.redblobgames.com/x/2327-roguelike-dev/#job-datastructure-design">nice data structure for the job system</a>. I'll blog about that later.</li>
<li>I tried out a <a href="https://www.redblobgames.com/x/2327-roguelike-dev/#generating-a-map">nice dungeon generation algorithm</a>. It's <em>much simpler</em> than anything else I've seen.</li>
<li>I liked <a href="https://www.redblobgames.com/x/2327-roguelike-dev/#moving-around">the interface I came up with</a>, using modifier keys to change the view and command set.</li>
</ul>
<p>
I ended up dropping the remaining features (game content, game balance) and saying it's Finished. It's not much of a game, but <a href="https://www.redblobgames.com/x/2327-roguelike-dev/">you can play with it here</a> and also read all my development notes later on that page. I learned a lot and I'm glad I participated in this year's event.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-55922394718595524622023-09-19T10:58:00.004-07:002023-09-20T19:28:42.688-07:00Writing HTML by hand<p>
I write some of my pages using a markup language (<a href="https://en.wikipedia.org/wiki/Org-mode">emacs org-mode</a>) and other pages using xhtml, with a few extra <kbd>x:*</kbd> tags that get expanded out into html later. I was curious, when I write html by hand, which tags do I use? I used Python's <a href="https://docs.python.org/3/library/xml.etree.elementtree.html">elementtree</a> to get the answer:
</p>
<pre class="example" id="org3d41f4b">
3085 p
2466 a
2303 li
1042 em
1008 code
876 span
719 x:section
517 br
454 div
446 strong
424 h3
410 figure
359 ul
358 script
331 img
323 pre
262 td
249 x:document
228 x:footer
219 g
</pre>
<p>
A <em>lot</em> of what I write is explanations in <kbd><p></kbd> paragraphs and <kbd><ul></kbd> <kbd><li></kbd> lists. And I try to include lots of <kbd><a></kbd> links to other supporting documents. I do try to use the semantic <kbd><em></kbd> and <kbd><strong></kbd> instead of the visual <kbd><i></kbd> and <kbd><b></kbd>. These results didn't surprise me much.
</p>
<p>
Here's the code (roughly):
</p>
<div class="org-src-container">
<pre class="src src-python"> <span class="variable-name">tag_counts</span> <span class="operator">=</span> collections.Counter()
<span class="keyword">for</span> doc <span class="keyword">in</span> documents:
<span class="variable-name">tree</span> <span class="operator">=</span> etree.fromstring(doc.contents)
<span class="keyword">for</span> el <span class="keyword">in</span> tree.<span class="builtin">iter</span>():
<span class="variable-name">tag_counts</span>[el.tag] <span class="operator">+=</span> 1
<span class="keyword">for</span> (tag, count) <span class="keyword">in</span> tag_counts.most_common(20):
<span class="builtin">print</span>(f<span class="string">"</span>{count:5}<span class="string"> </span>{tag}<span class="string">"</span>)
</pre>
</div>
<p>
Do you write HTML by hand? If so, what tags do you use most?
</p>
<p>
<strong>Update:</strong> [2023-09-20] Some people commented <a href="https://news.ycombinator.com/item?id=37579474">on HackerNews</a> about how they write their HTML, including some debate over closing tags, HTML vs XHTML, and markup languages.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com3tag:blogger.com,1999:blog-5052387.post-14911429357784683802023-08-08T12:23:00.007-07:002023-08-08T12:36:08.343-07:00Changing a URL<p>
One of the things I highly value is stable URLs. When you link to my site, I want that URL to work as long as my site is up. I don't want to lose all those links lightly. But for ease of implementation, the URL structure matches the <em>file</em> structure on my site, and I occasionally want to change the file structure. So what should I do?
</p>
<p>
<strong>I make a 301 redirect from the old URL to the new URL</strong>.
</p>
<p>
There are links on forums, discord, stackoverflow, etc. that I can't expect anyone to change. But I can keep those links working on <em>my</em> end by adding a redirect. I have made 28 of these URL changes in 28 years, an average of once year, and each one has a redirect to keep the old URLs working.
</p>
<a name='more'></a>
<p>
In this case I wanted to move <a href="https://www.redblobgames.com/grids/line-drawing.html">https://www.redblobgames.com/grids/line-drawing.html</a> to <a href="https://www.redblobgames.com/grids/line-drawing/">https://www.redblobgames.com/grids/line-drawing/</a> . The original plan back in 2011 was to have a main page about <em>grids</em> and then a few pages that went along with that. The line drawing page was one one the accompanying pages. But I never did write a main page, so now the line drawing page is its own project. I want to move it into its own folder so that I can add more pages to the project, including a comparison to <a href="https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases">Bresenham's Line Algorithm</a>. Here were the steps:
</p>
<ol class="org-ol">
<li><strong>Move the files</strong></li>
</ol>
<div class="org-src-container">
<pre class="src src-sh"><span class="builtin">cd</span> grids/
mkdir line-drawing
mv line-drawing.bxml line-drawing/index.bxml
mv line-drawing.html line-drawing/index.html
mv line-drawing.js line-drawing/line-drawing.js
mv line-drawing.org line-drawing/index.org
mv line-drawing.png line-drawing/
mv _line-drawing-helper.js line-drawing/
</pre>
</div>
<ol class="org-ol">
<li><strong>Test the new page locally</strong></li>
<p>
I run a staging server locally so that I can test all of my pages before I upload them. I want to make sure I get all the supporting files, and I also want to make sure any relative links to <code>../</code> get changed to <code>../../</code>. I use the Network panel in the browser tools to make sure there are no 404s. It turns out I had missed some files, so I'm glad I tested this before uploading it.
</p>
<li><strong>Create a permanent redirect</strong></li>
<p>
I'm using Nginx for redblobgames.com, so I added this to <code>nginx.conf</code>:
</p>
<div class="org-src-container">
<pre class="src src-nginx"><span class="keyword">location</span> = <span class="function-name">/grids/line-drawing.html</span> {
<span class="keyword">return</span> 301 <span class="variable-name">$scheme</span>://www.redblobgames.com/grids/line-drawing/;
}
</pre>
</div>
<p>
I then have to tell nginx to load the updated configuration file:
</p>
<div class="org-src-container">
<pre class="src src-sh">sudo service nginx reload
</pre>
</div>
<li><strong>Test the redirect</strong></li>
<p>
It turns out I had gotten this wrong, initially putting <code>location ~</code>. I rarely edit my nginx file so I keep forgetting the various rules for matchers.
</p>
<p>
This means the site was broken for a few minutes while I fixed the bug. If I were more disciplined I'd have a staging server for testing nginx configuration, but my staging server runs Caddy so I don't catch this type of bug. I rarely change my <code>nginx.conf</code> though, so it's a low priority to add tests here.
</p>
<li><strong>Update links from the rest of my site</strong></li>
<p>
Since I set up a redirect, I don't <em>have</em> to update the links, but I think it's good to do it anyway.
My upload script checks local links and gave me a list of pages to update:
</p>
<ul class="org-ul">
<li><a href="https://www.redblobgames.com/">https://www.redblobgames.com/</a></li>
<li><a href="https://www.redblobgames.com/grids/hexagons/">https://www.redblobgames.com/grids/hexagons/</a></li>
<li><a href="https://www.redblobgames.com/grids/line-drawing/">https://www.redblobgames.com/grids/line-drawing/</a></li>
<li><a href="https://www.redblobgames.com/grids/parts/">https://www.redblobgames.com/grids/parts/</a></li>
<li><a href="https://www.redblobgames.com/making-of/line-drawing/">https://www.redblobgames.com/making-of/line-drawing/</a></li>
<li><a href="https://www.redblobgames.com/making-of/diagram-structure/">https://www.redblobgames.com/making-of/diagram-structure/</a></li>
<li><a href="https://www.redblobgames.com/making-of/little-things/">https://www.redblobgames.com/making-of/little-things/</a></li>
</ul>
<p>
I went through each one and changed the links manually. I don't have an automated tool for this (other than occasionally using <code>perl -i -pe</code>) because it's not a common thing I do.
</p>
<p>
However I still have links <em>on my blog</em> that aren't included here. Moving my blog from blogspot to my own site is another project. Until then, I have search for links in two places.
</p>
<ul class="org-ul">
<li><a href="https://simblob.blogspot.com/2014/12/line-drawing-on-grid-maps.html">https://simblob.blogspot.com/2014/12/line-drawing-on-grid-maps.html</a></li>
<li><a href="https://simblob.blogspot.com/2016/12/five-year-mission.html">https://simblob.blogspot.com/2016/12/five-year-mission.html</a></li>
<li><a href="https://simblob.blogspot.com/2017/05/making-of-line-drawing-tutorial.html">https://simblob.blogspot.com/2017/05/making-of-line-drawing-tutorial.html</a></li>
<li><a href="https://simblob.blogspot.com/2018/02/how-i-implement-my-interactive-diagrams.html">https://simblob.blogspot.com/2018/02/how-i-implement-my-interactive-diagrams.html</a></li>
<li><a href="https://simblob.blogspot.com/2019/03/brainstorming-with-factoring.html">https://simblob.blogspot.com/2019/03/brainstorming-with-factoring.html</a></li>
<li><a href="https://simblob.blogspot.com/2023/01/making-of-circle-drawing.html">https://simblob.blogspot.com/2023/01/making-of-circle-drawing.html</a></li>
</ul>
<p>
I went through each one and changed the links manually. I can't use <code>perl -i -pe</code> here because blogspot requires me to edit through their editor instead of editing files.
</p>
</ol>
<p>
<strong>I think everything works now</strong>.
</p>
<p>
Also see: <a href="https://www.w3.org/Provider/Style/URI">Cool URIs don't change</a>
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com1tag:blogger.com,1999:blog-5052387.post-1308924944979108982023-07-28T12:07:00.006-07:002023-07-28T12:12:25.432-07:00Experiments and motivation<p>
I was encouraged back in March when I was able to port my <a href="https://simblob.blogspot.com/2023/03/tank-control-experiments.html">tank control experiments from Flash to HTML5</a>. I had previously attempted to port my spaceship control experiments from Flash to HTML5, but failed. Part of the problem was that it wasn't a strict port; I wanted to use a different algorithm. But it didn't work well.
</p>
<a name='more'></a>
<p>
After the tank control experiments I was excited to work on map generation again. I had high hopes and lots of ideas. I fell into a rabbit hole, <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-2.html">fixing many bugs with the map boundary</a>, and then after I got out of the rabbit hole … I had lost the desire to work on map features. Unfortunately this happens to me a lot.
</p>
<p>
So I've kind of been aimless since then. I decided to try the spaceship flyer again, but this time I wanted to try putting it into a game loop, so <a href="https://www.redblobgames.com/x/2322-spaceship-flyer/">I made a little game</a>. This led me to another distraction, <a href="https://javascript.info/keyboard-events">learning about modern keyboard events</a>. The keyboard events used to have some incompatibilities between browsers but they seem to be much better now. That gave me an idea: since browsers now provide both keycode and scancode, can I use that to "discover" the WASD equivalent on different keyboards? <a href="https://www.redblobgames.com/x/2324-keyboard/">I tried that</a>. It works, but I can get that data only <em>after</em> someone types a key. Would it be nice to get the layout without requiring typing? Well, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API">Chrome has it</a> but it turns out <a href="https://news.ycombinator.com/item?id=29793991">it can be used for "fingerprinting"</a>, which is used to track people on the web. So Firefox and Safari both said no, they don't want to add an API that would get used for fingerprinting. Brave blocked it even thought they use Chrome code underneath. But I think it'd be reasonable in the game setup/tutorial to ask someone to move around with <kbd>WASD</kbd> and then see what scancodes come up, and use that as the initial key bindings.
</p>
<p>
But … the keyboard stuff was all a distraction. I learned a bit, but I didn't make a lot of progress on the spaceship flyer. After I got out of the rabbit hole, I had lost the desire to work on that project.
</p>
<p>
Fortunately something came along. Every year, the Reddit <a href="https://old.reddit.com/r/roguelikedev/">r/roguelikedev community</a> has an event to <a href="https://old.reddit.com/r/roguelikedev/wiki/python_tutorial_series">make a roguelike</a>. This year I decided to make <a href="https://www.redblobgames.com/x/2327-roguelike-dev/">a colony simulator</a>. I'm using some of the code from the spaceship & keyboard projects, so those didn't go to waste. I'm hoping the structure of the community project will help me stay motivated for the six weeks. But I don't plan to continue after that. I will look for another project.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com2tag:blogger.com,1999:blog-5052387.post-23242527555852063152023-04-26T09:00:00.002-07:002023-12-27T13:08:39.731-08:00Improving Mapgen4's boundaries, part 3<p>
In the <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-2.html">last post</a> I described how I investigated and fixed several bugs in <code>mapgen4</code>'s boundary points and rendering. I was a bit annoyed at myself because I didn't initially follow great practices while debugging, so it took longer than it should have. But I was also glad I found and fixed the bugs.
</p>
<p>
One reason I wanted to try a <em>double</em> boundary layer was that I thought it might be neat to "fold" the edges downwards a bit, so that when you look at the map from the side, it'd have some depth. So I tried it, and … it worked! And it was so easy (after I fixed the earlier bugs). I then changed the underground color and added a faint line at the fold:
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/7-underground-view.png" alt="" />
<figcaption>Underground view</figcaption>
</figure>
<a name='more'></a>
<p>
<strong>I love the way this looks!</strong> I especially like the faint fold lines (dark for land and light for water). The edges aren't quite smooth though, as some of the landscape rendering interfered with the edges. I had to fix that, as well as river rendering. I made it support blue for ocean and brown for land. The blue/brown are hard-coded though, so that means I needed to re-implement the sepia color option:
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/8-sepia-color-test.png" alt="" />
<figcaption>Supporting the sepia color mode</figcaption>
</figure>
<p>
I also added some horizontal lines:
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/10-horizontal-underground-lines.png" alt="" />
<figcaption>Horizontal lines underground</figcaption>
</figure>
<p>
Unfortunately these are all hacks. I'd love to have a clean way to do this but I don't have that right now. I'll leave the hacks in for now, and revisit later. It's not an important feature of mapgen4, so I'm treating it as an experiment/prototype for now.
</p>
<p>
Other notes:
</p>
<ul class="org-ul">
<li>I'm trying to write things to my blog and not only to Twitter. I started my blog in Feb 2003, so it's over <em>twenty years old</em>. That's older than Twitter. And given how long social networks last, my blog may outlast Twitter. So I'd much rather my content be here than on Twitter.</li>
<li>Writing these three blog posts felt like it took longer than working on the code. Tweeting is quick and easy by comparison. This is frustrating, and is one reason I'm not blogging as much as I'd like to. I don't yet have a solution for this.</li>
</ul>
<p>
Try out <a href="https://www.redblobgames.com/maps/mapgen4/">mapgen4</a>: adjust the Zoom slider slightly out, then adjust the Tilt_deg and Rotate_deg sliders to see the underground rendering.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com1tag:blogger.com,1999:blog-5052387.post-57423198128390438002023-04-25T09:00:00.001-07:002023-04-25T09:00:00.137-07:00Improving Mapgen4's boundaries, part 2<p>
<a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-1.html">Last time</a> I mentioned that I made some changes to my <code>dual-mesh</code> helper library. I use it for my Delaunay/Voronoi map and art projects. Part of the motivation was that I want to work on some new map projects, and wanted to fix some of the issues with the library. I then realized I need to test out the changes in <code>mapgen4</code>.
</p>
<p>
I made a list of <code>mapgen4</code> bugs I wanted to fix. The main one I'm going to talk about here is that the <em>edges of the map are jagged</em>. Why didn't anyone notice? Because I set the default zoom level to be <em>slightly</em> zoomed in, so that you don't see the edges!
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/2-jagged-borders.png" alt="" />
<figcaption>Map edges are quite jagged</figcaption>
</figure>
<a name='more'></a>
<p>
To investigate, I first started a <em>separate</em> project where I could isolate and experiment with the module I was trying to understand. I built <a href="https://www.redblobgames.com/x/2314-poisson-with-boundary/">visualizations to look at the boundary points</a>.
</p>
<p>
After fixing the boundary point function, I imported it back into <code>mapgen4</code> and tried it.
</p>
<p>
Guess what? <em>It didn't help at all!</em> (In fact, it was worse, as I would later discover)
</p>
<p>
I had <em>assumed</em> that the problem was the boundary points, and I spent a lot of time fixing boundary points. But I hadn't <em>verified</em> that the problem was the boundary points first. So I fixed a problem that wasn't there.
</p>
<p>
So I came back to <code>mapgen4</code> and I added a debug visualization to show the boundary points and the Delaunay triangulation. It looked ok! It didn't explain what I'm seeing. And it also tells me that I was looking in the wrong place. <strong>Debug visualizations can be very useful!</strong> After further debugging, I discovered that the jaggedness was a result of the <a href="/x/1725-procedural-elevation/#rendering">quad rendering I'm using</a>. I'm <em>neither</em> drawing the Voronoi regions <em>nor</em> the Delaunay triangles. Instead, I'm drawing quads that combine both, and I'm turning those quads into two triangles.
</p>
<p>
The fix was to exclude <em>boundary triangles</em>, which weren't even something I considered as part of the <code>dual-mesh</code> library. I don't know why I didn't think of this when I was originally writing the code, but I suspect it's because all my debug visualizations were either the Voronoi regions or Delaunay triangles, but never these quads.
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/3-sawtooth-borders-with-gap.png" alt="" />
<figcaption>Fixing quad rendering solves the main jaggedness</figcaption>
</figure>
<p>
But … there's still something weird going on. The bottom and right sides have a black region. The top and left sides do not. That's weird…
</p>
<p>
So I investigated that bug and found that I had an off-by-one error in an unexpected place. Wow. How many more bugs am I going to find?
</p>
<p>
The answer was… several!
</p>
<p>
If I want the map to fill the entire 1000✕1000 square, I need the boundary points to be on the <em>outside</em> of the square. But the Poisson Disc algorithm can only stay away from points inside the square. That made me realize I should have a double boundary, one layer inside to push the Poisson Disc points away, and one layer outside to ensure that I fill the map square.
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/bug-boundary-not-part-of-poisson.png" alt="" />
<figcaption>Debug visualization shows narrow triangles near boundary</figcaption>
</figure>
<p>
But … in changing the way boundary points were generated, I introduced a bug. I need the Poisson Disc points to ignore the interior layer of boundary points. They were often too close to the existing points, creating some narrow triangles. That bug had to be fixed twice, since there are <em>two</em> Poisson Disc stages in <code>mapgen4</code>. One is for the terrain and one is for mountain peaks. Fixing this bug exposed another bug in the new algorithm: a few boundary points were <em>missing</em>, but only on the bottom and right side of the map:
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/bug-boundary-getting-clipped.png" alt="" />
<figcaption>A glitch at the center of the bottom side of the map</figcaption>
</figure>
<p>
This was caused by those points being <em>exactly</em> on the edge of the square, sometimes getting classified as being outside. To fix the bug, I pushed them inside slightly.
</p>
<p>
Once I got the underlying <em>structure</em> correct, I worked on the rendering. I returned to the sawtooth border problem:
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/4-sawtooth-borders.png" alt="" />
<figcaption>Border triangles are sawtooth shaped</figcaption>
</figure>
<p>
I had tried fixing this by moving the points on the CPU, but then I realized I could just as easily do this on the GPU. Either way, I <code>clamp()</code>-ed the points to the map square. That was <em>much</em> easier than I expected, but only worked because I had fixed the other bugs first.
</p>
<figure>
<img src="https://www.redblobgames.com/maps/mapgen4/blog/map-edges/5-smooth-borders.png" alt="" />
<figcaption>Points are now clamped to the map square</figcaption>
</figure>
<p>
I was pretty happy that I fixed all these bugs. I wasn't happy that I had them in the first place, but I'm also not surprised, given how this evolved out of an experimental project.
</p>
<p>
In the <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-3.html">next post</a> I'll talk about a new feature I added, one that was only possible after these bugs were fixed.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-26924108129321916722023-04-24T13:54:00.001-07:002023-04-24T13:57:54.982-07:00Improving Mapgen4's boundaries, part 1<p>
I've been wanting to refamiliarize myself with the mapgen4 code because I'd like to do some new map projects and will want to reuse some of the code from my existing projects.
</p>
<p>
The first thing I decided to work on was my <code>dual-mesh</code> library. I had originally written it to be a generally useful wrapper around Delaunator. Since then, I wrote the Delaunator Guide, which has all the same functions, but in a way that you can adapt to your own needs. I also realized that by making this library public, I was making it harder for <em>my own</em> needs. So version 3 of this library is going to be <em>primarily</em> for my own needs.
</p>
<figure>
<img src="https://www.redblobgames.com/x/2314-poisson-with-boundary/blog/old-boundary-points-triangulation.png" alt="" />
<figcaption>Boundary points (blue) surrounding Poisson Disc points (red)</figcaption>
</figure>
<a name='more'></a>
<p>
The major changes I made so far:
</p>
<ol class="org-ol">
<li>I switched from <code>require()</code> to es6 modules.</li>
<li>I switched from JavaScript to TypeScript. I have mixed feelings about this, as so far it's gotten in the way more often than it has helped. I was already using typescript for <em>type checking</em> with jsdoc types, but typescript for <em>compiling</em> hasn't been a great experience.</li>
<li>I changed the naming convention, from <code>a_to_b</code> meaning <code>a</code> was the <em>input</em> and <code>b</code> was the <em>output</em> to <code>b_from_a</code>. Why? Because when chaining calls, writing <kbd>c = b_to_c(a_to_b(a))</kbd> is much harder to parse and spot errors in than writing <kbd>c = c_from_b(b_from_a(a))</kbd>. A <em>big</em> thanks to <a href="https://tomforsyth1000.github.io/blog.wiki.html">Tom Forsyth</a> for making the argument for this convention. So far, it's been quite a good change for this project.</li>
<li>I made a <a href="https://www.redblobgames.com/x/2312-dual-mesh/">visualization of the boundary points</a>, primarily to figure out the bugs, but also to serve as a reminder to myself of how the ghost and boundary points are arranged.</li>
<li>I found and fixed some bugs in the boundary point function. Look at the diagram above. The spacing of the blue points doesn't match the spacing of the red points, especially at the corners. This leads to some narrow triangles at the corners.</li>
<li>I removed the 1000x1000 limitation and made the boundary point code work for any rectangle.</li>
<li>I removed the depedency on the <code>poisson-disk-sampling</code> library, because I use different different point selection approaches in each project.</li>
</ol>
<p>
I haven't finished the <code>dual-mesh</code> changes, but wanted to put some of them to the test by trying them out in <code>mapgen4</code>. So that's what I did next. Although it went well, I had two thoughts after this:
</p>
<ol class="org-ol">
<li>The <code>dual-mesh</code> library does a bunch of things I don't need in <code>mapgen4</code>.</li>
<li>The <code>dual-mesh</code> library does some things that aren't done the way <code>mapgen4</code> needs it.</li>
</ol>
<p>
This reinforced my belief that a lot of what I need is a <em>starting point</em> or a <em>recipe</em> that I can adapt to each project's needs, instead of a reusable library that tries to do everything. I'm using less than half the code from <code>dual-mesh</code>, and am reimplementing parts of it because it doesn't quite fit my needs.
</p>
<p>
So what works and what doesn't?
</p>
<ol class="org-ol">
<li>The <em>core</em> functions work really well. These are primarily <em>traversal</em> functions, which I wrote about in the <a href="https://mapbox.github.io/delaunator/">Delaunator Guide</a>. The code in that guide was meant to be generic, and use common naming conventions. For some of my projects I use a different naming convention, and for <code>mapgen4</code> I needed to minimize callbacks and allocations for performance reasons. I think this should remain in the library.</li>
<li>The <em>creation</em> functions use Poisson Disc, create boundary points, and create a ghost structure. For <code>mapgen4</code>, I want to use a different boundary point function, I need two different sets of Poisson Disc points, and I want to precalculate all of these and save them to a file. I do use the ghost structure code from the <code>dual-mesh</code> library, but the rest I had to reimplement. These might be best turned into recipes to be adapted to each project's needs.</li>
<li>The <em>test</em> functions are nice but I only used them while developing the <code>dual-mesh</code> code. I don't need them in <code>mapgen4</code>. They should remain in the library, but not the <em>distribution bundle</em> of the library.</li>
</ol>
<p>
I also spent some time just exploring different boundary point algorithms. Along the way I found this one:
</p>
<figure>
<img src="https://www.redblobgames.com/x/2314-poisson-with-boundary/blog/double-boundary-points-triangulation.png" alt="" />
<figcaption>Double layer of boundary points around the Poisson disc points</figcaption>
</figure>
<p>
<em>I didn't plan to use this</em>. I just made it for fun, because it looked like it might be interesting. And it turned to be exactly what I needed for <code>mapgen4</code>. It's a reminder that it's useful to explore possibilities because they add to the set of possible solutions I can pick from.
</p>
<p>
Despite fixing bugs in the boundary point function, <code>mapgen4</code> still had major bugs in the boundary points. That's because when I'm working on <code>dual-mesh</code>, I address <em>imagined</em> problems, whereas when I'm working on <code>mapgen4</code>, I address <em>real</em> problems. This is yet another reminder to myself to <strong>start with the real problems, not the imagined problems</strong>.
</p>
<p>
In the <a href="https://simblob.blogspot.com/2023/04/improving-mapgen4s-boundaries-part-2.html">next post</a> I'll show some of the issues I ran into and how I fixed them.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-32265836044698237922023-04-19T12:59:00.007-07:002023-04-19T13:12:21.176-07:00Explaining the Hexagon Layout class
<p>
On the <a href="https://www.redblobgames.com/grids/hexagons/implementation.html#layout">Hexagon Implementation page</a> I have a <code>Layout</code> class that controls how to convert back and forth between hexagonal coordinates and screen coordinates. It has parameters (<code>size.x</code>, <code>size.y</code>, <code>origin</code>) that aren't explained on the <a href="https://www.redblobgames.com/grids/hexagons/">hexagon concepts page</a>. That's in part because they complicate the explanation and <em>aren't</em> hexagon-specific. The size is a standard <em>scale transform</em> and the origin is a standard <em>translate transform</em>. Although I have some explanation on the implementation page, it's not very good. I made some improvements:
</p>
<a name='more'></a>
<ol class="org-ol">
<li>Added axes and measurements to the visualizations so that you can
<em>see</em> how the <code>size</code> and <code>origin</code> parameter affect the size and
position of the hexagons.</li>
<li>Added <em>common uses</em> of these two parameters, in a more “how to”
style. The <code>size</code> is useful for stretching/shrinking your hexagons to match pixel/sprite art, or for flipping the y-axis. The <code>origin</code> is useful for setting x=0,y=0 to be the origin hexagon's top left instead of its center.</li>
</ol>
<p>
Here's how the <code>size</code> example looked before. I gave two different sizes and showed these diagrams:
</p>
<figure>
<img src="https://www.redblobgames.com/grids/hexagons/blog/hexagon-layout-size-before.png" alt="Old version of the size diagram" />
<figcaption>Old "size" example diagram</figcaption>
</figure>
<p>
Here's how the <code>size</code> example looks after. I give the two sizes, but the reader can <em>read</em> the sizes by looking at the axis labels, and can also <em>visually</em> see the dimensions formed by the measurements lines. I also give the formula instead of making the reader figure it out themselves.
</p>
<figure>
<img src="https://www.redblobgames.com/grids/hexagons/blog/hexagon-layout-size-after.png" alt="New version of the size diagram" />
<figcaption>New "size" example diagram</figcaption>
</figure>
<p>
I'm pretty happy with this change. Fitting hexagons to existing sprite art is something I knew was possible but I hadn't sat down with pen & paper to figure the formulas out until now.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com2tag:blogger.com,1999:blog-5052387.post-33483031840616983522023-03-07T10:02:00.000-08:002023-03-07T10:02:05.763-08:00Tank Control experiments<p>
I'm still a bit sad about Flash going away. Yes, I know lots of people hated it. But it was an amazing tool for quick prototypes. There are some projects that I keep revisiting, and over time I'm converting them to HTML5.
</p>
<p>
Last year I converted my <a href="https://simblob.blogspot.com/2022/06/procedural-tree-generator.html">Flash procedural tree generator</a> (succeeded) and attempted to convert my <a href="https://www.redblobgames.com/x/2216-spaceship-flyer/">Flash spaceship flyer</a> (failed). This time I wanted to run my Tank Control prototype from 2012:
</p>
<figure>
<img src="https://www.redblobgames.com/x/2309-tank-control/blog/flash.png" alt="" style="box-shadow: 0 0 2px 2px rgb(0 0 0 / 0.2)" />
<figcaption>Screenshot of tank control prototype written in Flash</figcaption>
</figure>
<a name='more'></a>
<p>
Tank Control was a series of 8 prototypes in which I tried out various control schemes for driving a tank, both for laptop (mouse/trackpad) and mobile (touch).
</p>
<p>
I can still <em>run</em> Flash code because I have an old copy of Flash Player. But I can't <em>share</em> these prototypes anymore. And it'd be annoying to <em>modify</em> them because I don't have the Flash compiler set up anymore. So if it's not too much work, I want to port them to HTML5.
</p>
<p>
I tried using <a href="https://ruffle.rs/">Ruffle</a>, a Flash emulator that reads <em>existing</em> web pages, converts the SWF files linked there into HTML5, and then inserts the HTML5 version into the page. Magic! <a href="http://www-cs-students.stanford.edu/~amitp/_test/tank-control.html">This worked surprisingly well</a> and was able to generate the <em>output</em> of the tank control experiments. But it didn't support the <em>interactivity</em>. And the interactivity is the main thing I want to play with.
</p>
<p>
So I decided to port it to HTML5.
</p>
<p>
But which one of the eight? Or all of them? I ran all eight and compared them. I also ran <kbd>diff</kbd> on the code to see what was similar and what was different. I decided that Tank Control #3, #6, and #7 were the ones I was most interested in.
</p>
<figure>
<img src="https://www.redblobgames.com/x/2309-tank-control/blog/html5.png" alt="" style="box-shadow: 0 0 2px 2px rgb(0 0 0 / 0.2)" />
<figcaption>Screenshot of tank control prototype written in HTML5</figcaption>
</figure>
<p>
Flash's vector graphics and HTML5's SVG/Canvas vector graphics are not <em>that</em> different, and Flash's scripting language is EcmaScript4 whereas HTML5's scripting language is EcmaScript5, so the scripts were also not <em>that</em> different. Porting wasn't too bad. I wasn't using Flash's bitmap or shader graphics for this project; those are a bit harder to port. And I wasn't using Flash's MXML UI toolkit either.
</p>
<p>
It was fun to play with the controls. I also came up with lots of ideas for variations to try. But I reminded myself that the goal was to <em>port</em> some of the the existing prototypes, <em>not</em> to come up with new control schemes. I can play with new control schemes later, if I have some goal in mind.
</p>
<p>
<a href="https://www.redblobgames.com/x/2309-tank-control/">You can play with the result here</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com2tag:blogger.com,1999:blog-5052387.post-15234961504587642252023-02-28T17:15:00.008-08:002023-04-19T13:01:26.488-07:00Making-of: draggable objects<p>
I noticed last year that some of my projects behave inconsistently on touch devices. I didn't do anything about it. But then I was working on a <a href="https://simblob.blogspot.com/2023/01/making-of-circle-drawing.html">tutorial about how I make my interactive tutorials</a>, I had to show how to handle touch events, and I was <em>embarrassed</em> by my code. I decided to take a week to learn about mouse, touch, and pointer events.
</p>
<p>
That week turned into two. And three. And eight.
</p>
<p>
<strong>I learned a lot</strong>. Not only about the event handling spec, but also differences in browser behavior and operating system behavior.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/draggable/build/mouse-and-touch.svg" alt="" />
<figcaption>State diagram for mouse and touch events</figcaption>
</figure>
<p>
I was able to greatly simplify my code by using newer APIs and by removing unnecessary code.
</p>
<a name='more'></a>
<p>
The trouble with my code was that I was "<a href="https://en.wikipedia.org/wiki/Cargo_cult_programming">cargo culting</a>" my event handling code. I had lines like <kbd>event.stopPropagation()</kbd> without really thinking about or understanding why it was there. I went through and removed each line I wasn't sure about, and tested the behavior.
I tested my code on Gecko/Firefox (Mac, Windows, Linux, Android), Blink/Chrome (Mac, Windows, Linux, Android), and WebKit/Safari (Mac, iPhone, iPad). I did <em>not</em> test on hoverable stylus, hybrid touch+mouse devices, or voice input. I tried dragging different types of objects with different types of drag behavior.
</p>
<p>
I made a <a href="https://www.redblobgames.com/x/2251-draggable/">test page</a> where I printed out all the events, along with some of the additional fields like <code>button</code> and <code>pointerId</code>. I learned a lot this way. This page is also full of unorganized notes.
</p>
<p>
The biggest issues I wanted to solve were:
</p>
<ol class="org-ol">
<li><strong>Capture</strong>: with both mouse and touch, I want to handle drag events that go outside the diagram.</li>
<li><strong>Scrolling</strong>: on touch devices, dragging with the finger scrolls the page, but I also want dragging with the finger to move the object.</li>
</ol>
<p>
But in going through the tests I found lots of other situations I might want to handle: text selection, system drag, right click context menu, middle click autoscroll, holding down multiple buttons, and more. The right click context menu was especially interesting, as Windows, Mac, and Linux work quite differently! And Chrome, Safari, and Firefox also work quite differently.
</p>
<p>
I kept finding more and more weird edge cases! What happens when you start dragging on a phone and then rotate the phone to landscape mode? The behavior is different on iPhone and Android! What happens when you start dragging with a finger on iPad and then use the stylus? The stylus overrides the finger drag. What happens when you resize the window while you're dragging? What happens if you put the computer to sleep while dragging?
</p>
<p>
I just had to stop! I decided I should handle common cases but not worry about all the edge cases.
</p>
<p>
I made a <a href="https://www.redblobgames.com/making-of/draggable/targets.html">test page</a> where I listed some of the edge cases, and also tested different types of dragging. And then I wrote
<a href="https://www.redblobgames.com/making-of/draggable/">my recommended event handling code</a>. And <em>then</em> I implemented this new code on some of my existing pages: <a href="https://www.redblobgames.com/maps/mapgen4/">mapgen4</a>, <a href="https://www.redblobgames.com/grids/hexagons/">hexagons</a>, <a href="https://www.redblobgames.com/grids/edges/">grids/edges</a>, <a href="https://www.redblobgames.com/making-of/responsive-design/">responsive design</a>. It works really well on mapgen4. I had mixed results with hexagons, because that page sometimes uses different interactions for touch and mouse, so the unified event handlers weren't the right approach. The edges page never supported touch events, and now partially supports them. The responsive design page also never supported touch events, and now fully supports them.
</p>
<p>
Despite this project taking <em>much</em> longer than expected, I consider it to be a success. I'll continue updating these pages as I learn more. And I'd love to hear from you about what I got wrong. <strong>Start with the <a href="https://www.redblobgames.com/making-of/draggable/">event handling page</a>.</strong>
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-35653976908485430142023-01-17T20:03:00.009-08:002023-08-08T12:09:10.936-07:00Making-of: circle drawing page<p>
People sometimes ask me how I write my interactive tutorials. I started out using <a href="https://d3js.org/">d3.js</a>, and five years ago I wrote <a href="https://www.redblobgames.com/making-of/line-drawing/">an interactive tutorial about how I made interactive pages with d3.js</a>. I recreated a diagram from my <a href="https://www.redblobgames.com/grids/line-drawing/">line drawing tutorial</a>, which was implemented in d3.js. I now use <a href="https://v3.vuejs.org/">Vue.js v2</a>, so I wrote a new tutorial about how I make interactive pages with Vue. I recreated several diagrams from my <a href="https://www.redblobgames.com/grids/circle-drawing/">circle drawing tutorial</a>.
</p>
<figure>
<a href="https://www.redblobgames.com/making-of/circle-drawing/"><img src="https://www.redblobgames.com/making-of/circle-drawing/blog/layers-diagram.png" alt="Diagram constructed in layers" /></a>
</figure>
<a name='more'></a>
<p>
The page is intended to document <em>how I write my pages</em>. It's not a comprehensive guide to making <a href="https://explorabl.es/">Explorable Explanations</a>. Everyone has their own style and preferred implementation techniques. I approach it as a <em>document</em> (explanations) with added interactivity (explorables), and the implementation reflects that. But I don't use the same approach for every page; I'll switch to Canvas or WebGL for some projects. Most of the ideas on the page will work just as well with other libraries, but I'm showing Vue because that's what I use most often.
</p>
<p>
If you are interested in how I make my pages, or how to get started making your own, <a href="https://www.redblobgames.com/making-of/circle-drawing/">take a look at my new tutorial</a>. Please give me feedback on what was confusing or didn't work well for you, so that I can improve the page.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-10085484836442879322023-01-04T07:27:00.011-08:002023-01-04T07:35:57.619-08:00One letter variable names<p>
I admit I use one letter variable names in my code. But which names? Here's what I use in <code>for</code> loops:
</p>
<pre>
# rg 'for \(let . ?='
# | perl -pe 's/.*let //g' | perl -pe 's/=.*//g'
# | sort | uniq -c | sort -nr | head
509 i
132 x
122 y
118 r
57 t
53 q
47 j
43 s
31 e
14 k
</pre>
<a name='more'></a>
<p>
My naming conventions:
</p>
<ul class="org-ul">
<li><kbd>i</kbd>, <kbd>j</kbd>, <kbd>k</kbd>: generic index</li>
<li><kbd>x</kbd>, <kbd>y</kbd>, <kbd>z</kbd>, <kbd>w</kbd>: coordinates</li>
<li><kbd>q</kbd>, <kbd>r</kbd>: col/row grid coordinates</li>
<li><kbd>r</kbd>: region number</li>
<li><kbd>r</kbd>: range or radius</li>
<li><kbd>t</kbd>: triangle number</li>
<li><kbd>t</kbd>: time index</li>
<li><kbd>s</kbd>, <kbd>e</kbd>: sides/edges of a polygon</li>
<li><kbd>w</kbd>, <kbd>h</kbd>: width/height</li>
</ul>
<p>
Outside of <code>for</code> loops, <kbd>rg 'let . ?= | grep -v for'</kbd> finds that I also use these conventions:
</p>
<ul class="org-ul">
<li><kbd>a</kbd>, <kbd>b</kbd>: endpoints of a range or line segment</li>
<li><kbd>a</kbd>, <kbd>b</kbd>, <kbd>c</kbd>: equation for a line, or quadratic formula</li>
<li><kbd>v</kbd>: generic value</li>
<li><kbd>p</kbd>: point</li>
<li><kbd>t</kbd>: time</li>
<li><kbd>d</kbd>: distance</li>
<li><kbd>d</kbd>: reference to svg <kbd><path></kbd> <kbd>d=</kbd> attribute</li>
<li><kbd>g</kbd>: reference to svg <kbd><g></kbd> element</li>
</ul>
<p>
If I were working in a team I'd follow their naming conventions, but for the most part I am writing the code for myself, so I'm ok with short variable names like these. The common objection is that I won't understand the code six months later, but I haven't found that to be an issue. These are all local variables inside a function, and I tend to use the same conventions across projects.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com3tag:blogger.com,1999:blog-5052387.post-13908305553863943642022-12-30T12:21:00.003-08:002022-12-30T12:21:29.233-08:00What I did in 2022<p>
It's time for my annual self review. It's been another weird year. <a href="https://simblob.blogspot.com/2021/12/what-i-did-in-2021.html">Last year I said</a> I was going to spend more time on experiments, art, and learning, and I would improve my existing guides.
</p>
<figure>
<img src="https://www.redblobgames.com/x/2203-reaction-diffusion/blog/alternating-rotation.jpg" alt="Screenshot from an art project using reaction-diffusion simulations" />
</figure>
<a name='more'></a>
<dl class="org-dl">
<dt>Learning</dt><dd>Related to some of <a href="https://biologicalmodeling.org/prologue/">the biology I've been learning</a>, I implemented an <a href="https://www.redblobgames.com/x/2234-hunar-alife-simulation/">alife simulation</a>, <a href="https://simblob.blogspot.com/2022/06/procedural-tree-generator.html">procedural tree generator</a>, <a href="https://simblob.blogspot.com/2022/02/reaction-diffusion.html">reaction-diffusion simulator</a>, and then made some reaction-diffusion based art: <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/purple-blob.html">purple blob</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/parameter-map.html">parameter map</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/spinning-record.html">spinning record</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/alternating-rotation.html">alternating rotation</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/uniform.html">uniform gaps</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/radial-diffusion.html">radial diffusion</a>, <a href="https://www.redblobgames.com/x/2203-reaction-diffusion/art/vertical-stripe.html">vertical stripe</a>. I also spent time learning SQLite, Node.js/Fastify, Wikipedia data formats, <a href="https://www.redblobgames.com/x/2224-learning-custom-elements/">web components</a>, geology, and physics. My goal was to learn things that are interesting but not necessarily useful for my projects. I'm pretty happy with how it went.</dd>
<dt>Blogging</dt><dd>I had already been wanting to blog more and not put things only on Twitter, but this year's events have reminded me that … I should blog more. No, I haven't joined Mastodon (which doesn't have the features I use often on Twitter) or any similar service yet. I'm trying to put more on my own site, so that it won't go away if the host goes away. Years ago I had put lots of content on FriendFeed, and then FriendFeed went away and I lost it all. I am continuing to blog, but I no longer have a specific goal like "1 post per month". Instead, I'll blog when I have a project to talk about, and I won't blog when I don't. Even though I would've liked to blog more this year, I'm pretty happy with how much I did blog.</dd>
<dt>Site</dt><dd>Every year I have small updates to the site infrastructure. I started this web site 28 years ago. Back then I built parts of it with Python 1. It looks like Python 3 has dropped some of the backwards compatibility with Python 1, so I ported that old code to work in Python 3. I also had part of the site being generated using Chrome (😱) but that was fragile and I removed it this year. I'm trying to choose technologies that are likely to still work ten or twenty years from now. I've made other minor improvements to the site and I'm pretty happy with them.</dd>
</dl>
<figure>
<img src="https://www.redblobgames.com/plotter/2229-svg-logo/blog/photo-of-plotter.jpg" alt="Photo of the Axidraw SE/A3 plotter" />
</figure>
<dl class="org-dl">
<dt>Art</dt><dd><a href="https://twitter.com/redblobgames/status/1583313654472396800">I got a plotter!</a> I adapted some of my existing projects to make plotter versions: <a href="https://www.redblobgames.com/plotter/1723-procedural-river-growing/">river growing</a>, <a href="https://www.redblobgames.com/plotter/1847-mathologer-modulo-circle/">modulo circle</a>, <a href="https://www.redblobgames.com/plotter/1907-logo-generator/">face generator</a>, <a href="https://www.redblobgames.com/plotter/1925-delaunator-animation/">rounded voronoi</a>, <a href="https://www.redblobgames.com/plotter/2229-svg-logo/">another logo</a>, <a href="https://www.redblobgames.com/plotter/2241-rotated-rectangles/">rotated rectangles</a>. I've put it away for the winter (as it uses a lot of desk space) but I plan to resume my plotter projects next summer. I didn't do as much as had hoped to, in part because when I'm faced with something I don't know how to do, it's too easy for me to be distracted doing things I already know how to do.</dd>
</dl>
<figure>
<img src="https://www.redblobgames.com/plotter/1847-mathologer-modulo-circle/blog/screenshot.png" alt="Screenshot of modulo circle line art" />
<figcaption>Modulo circle art</figcaption>
</figure>
<dl class="org-dl">
<dt>Implementation</dt><dd>Although I'm always looking for better ways to implement my interactive diagrams, I realized that Vue 2 has been working really well for me, so I spent a little more time learning how to use it even better: <a href="https://www.redblobgames.com/x/2201-katex-vue3/">KaTeX integration</a> for math formatting, <a href="https://www.redblobgames.com/x/2205-vue-two-way-computed/">computed with setters</a> for two-way transforms, and <a href="https://www.redblobgames.com/x/2217-vue-pointerevents/">pointerevents</a> which are simplifying my code. Unrelated to Vue, I learned <a href="https://twgljs.org/">twgl.js</a>, chose a new <a href="https://simblob.blogspot.com/2022/05/upgrading-prng.html">random number generator</a> for projects going forward, played with text parsers for possible use in a future project, experimented with <a href="https://simblob.blogspot.com/2022/04/offline-access-with-file-save-as.html">File Save/As</a>, played with <a href="https://www.redblobgames.com/making-of/little-things/arrows-across-elements.html">arrows pointing into source code</a>, and attempted to make a Progressive Web App version of my site. I'm glad I learned these things but my gut feeling is that there's something I'm missing, and there should be an even easier way to make the things I want to make.</dd>
</dl>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/page-rearrange.png" alt="Scribbled notes of how I could improve my page" style="border:2px solid #ddd" />
<figcaption>Notes on how I could improve the hexagon guide</figcaption>
</figure>
<dl class="org-dl">
<dt>Existing pages</dt><dd>I added a demo of <a href="https://simblob.blogspot.com/2022/10/connected-components.html">connected components</a> and also improved the rest of that page while I was working on it. I spent quite a bit of time working on the hexagon guide, and blogged about the process: <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-1.html">part 1</a>, <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-2.html">part 2</a>, <a href="https://simblob.blogspot.com/2022/12/introduction-to-hexagons-part-3.html">part 3</a>. I finished my reference page of <a href="https://simblob.blogspot.com/2022/10/little-details.html">little details I add to my pages</a>. I realized I didn't understand the various pixel-to-hexagon formulas as well as I had hoped, so I <a href="https://simblob.blogspot.com/2022/09/hexagon-conversions.html">implemented and tested them all</a>. I also realized I didn't understand <a href="https://simblob.blogspot.com/2022/04/improving-island-shaping-for-map.html">island map generation</a> nearly as well as I pretend to on my page, so I implemented and tested all the formulas I knew of. Reader feedback alerted me to the <a href="https://www.redblobgames.com/maps/terrain-from-noise/#climate">climate section</a> of that same page being a bit confusing, so I improved that too. I'm quite happy with the work I did, but some of it took a lot longer than I expected.</dd>
</dl>
<figure>
<img src="https://www.redblobgames.com/x/2226-roguelike-dev/screenshots/section-6c-path-lines.png" alt="Screenshot of a chicken simulator where the chickens look for food" />
<figcaption>Chicken simulator game</figcaption>
</figure>
<dl class="org-dl">
<dt>Games</dt><dd>For a site called Red Blob <em>Games</em> I don't actually work on games often. This year, I followed the <a href="https://old.reddit.com/r/roguelikedev/wiki/python_tutorial_series/#wiki_roguelikedev_does_the_complete_roguelike_tutorial">/r/roguelikedev summer event</a> in which around forty of us write our own small roguelikes. I decided I'd try to make a "fortress mode" game, more like Dwarf Fortress than Rogue. Even though I was trying to keep the scope small, I seriously underestimated the work, and <a href="https://www.redblobgames.com/x/2226-roguelike-dev/">I didn't get very far</a>. It turns out writing a Dwarf Fortress clone <a href="https://old.reddit.com/r/roguelikedev/comments/wpiske/roguelikedev_does_the_complete_roguelike_tutorial/ikj33oe/">in one week</a> was too ambitious. But I had fun! And it made want to try again at some point. I also started looking at <a href="https://www.redblobgames.com/maps/mapgen5/">map generation</a> again, and have made a list of things I did poorly in the past and want to improve upon. I also attempted to make a <a href="https://www.redblobgames.com/x/2216-spaceship-flyer/">spaceship flying game</a> but didn't get very far. I keep visiting that idea but this year I made progress by realizing that I was <em>stuck</em> on a particular feature that would be "cool" but is relatively unimportant. So I'm glad that I made this realization, and the next time I attempt it I will know to skip that feature. I also have some ideas for a train station simulator game.</dd>
<dt>Future</dt><dd>I looked back at <a href="https://simblob.blogspot.com/2022/03/ten-years-of-red-blob-games.html">the last ten years</a> and realized I don't have a clear goal right now. I'll finish up the two projects I'm working on (a tutorial on <a href="https://www.redblobgames.com/making-of/circle-drawing/">how I write interactive tutorials</a>, and a writeup of which mouse+touch events to capture to handle object dragging), but I have no plans after that. I didn't have clear goals last year either, and I worked on plenty of small projects, learned a lot, and had fun, so I think that will continue, unless I think of something large I want to work on.</dd>
</dl>
<p>
I keep track of project ideas <a href="https://www.notion.so/f8bc2f44fba94607afa9c06711d23245?v=0766432cb1534ce582ce35b33cbbef7e">on Notion</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-2510554452412589162022-12-02T13:05:00.003-08:002022-12-02T13:05:00.185-08:00Introduction to hexagons, part 3<p>
In <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-1.html">part one</a>, I described how I was making a new diagram for my <a href="https://www.redblobgames.com/grids/hexagons/#basics">hexagon grid guide</a>, but that I didn't know how to evaluate the design choices I had to make. In <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-2.html">part two</a>, I described how I looked at the introductory section of the page to decide the purpose of the new diagram and how it fit in with the others.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/radius-diagram-overview-3.png" alt="Even more diagrams" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption><a href="https://www.redblobgames.com/making-of/diagram-design/">Test page with many diagrams</a></figcaption>
</figure>
<a name='more'></a>
<p>
Now that I know the diagram's purpose — to show the <strong>sizes</strong> of a hexagon (<code>size</code>, circumradius, inradius, minimal diameter, maximal diameter, width, height), I made a list of things to look at:
</p>
<ul class="org-ul">
<li><p>
radius label placement:
</p>
<ul class="org-ul">
<li>the extra spokes aren't needed for the width/height</li>
<li>having the radii labels inside the circle feels nicer than having them outside</li>
<li>having them close to each other makes it easier to compare</li>
<li>having them at 90° allows them to align with width/height</li>
<li>having them point up and right feels nicer than down and left</li>
</ul>
<ul class="org-ul">
<li>the triangle shows how the two radii are connected, but this only works in pointy orientation</li>
</ul></li>
<li>diameter label placement:
<ul class="org-ul">
<li>diameters next to radii (outside the circle) makes it easy to see that relation</li>
<li>diameters (outside) far from radii (inside) gives the labels plenty of space</li>
<li>diameters next to each other makes it easy to compare them</li>
<li>angled makes it easy to toggle the hexagon orientation (pointy/flat)</li>
<li>aligned with x and y axes makes it easy to see how they are width/height, but rotation is hard</li>
</ul></li>
<li>circle color:
<ul class="org-ul">
<li>I had started out with the dashed lines because it matched the angle diagram</li>
<li>I liked the solid version much better (thanks twitter!)</li>
</ul></li>
<li>other colors:
<ul class="org-ul">
<li>I started out with red and blue to distinguish the two circles</li>
<li>I switched to red and gray, as it matches the other diagrams on the page better</li>
</ul></li>
</ul>
<p>
<strong>I can't get all the things I want</strong> in the same diagram. What do I prioritize? This diagram is about sizes. I decided <strong>I should prioritize the width and height of the hexagon</strong>. I put the radii inside, at 90°, pointing up and right. I put the diameters outside, at 90°, on left and bottom.
</p>
<p>
The rotation animation between pointy and flat orientations was going to be trickier, and I didn't implement it fully on the test page. I had been using rotation, but to make the diagram I wanted, the minimal diameter rotates <em>left</em>, the maximal diameter rotates <em>right</em>, the inradius rotates <em>right</em>, the circumradius rotates <em>left</em>, the hexagon rotates <em>right</em>. And then after rotation, the radius and diameter labels need to swap between being above to being below the line. Implementing the animation with all the details I want is a lot of work.
</p>
<p>
I ended up building a <em>separate</em> diagram for pointy and flat orientations. It let me hard code a lot of parameters instead of trying to smoothly animate them. It also lets the reader compare the two orientations.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/width-height-diagrams.png" alt="Two diagrams, one for pointy and one for flat orientation" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption>Two diagrams showing width and height</figcaption>
</figure>
<p>
That becomes the new introductory diagram on the page. I'm much happier with the new page:
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/page-3-after.png" alt="New page design (screenshot)" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption>New page design</figcaption>
</figure>
<p>
<a href="https://www.redblobgames.com/grids/hexagons/#basics">Take a look at the new introductory section of the page</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-72209656753658410712022-11-29T13:04:00.004-08:002022-11-29T13:04:00.165-08:00Introduction to hexagons, part 2<p>
In <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-1.html">part one</a>, I described how I created lots of variants of a diagram to try to figure out which one to use on my <a href="https://www.redblobgames.com/grids/hexagons/#basics">hexagon guide</a>.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/radius-diagram-overview-2.png" alt="Lots more diagrams" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption><a href="https://www.redblobgames.com/making-of/diagram-design/">Test page with many diagrams</a></figcaption>
</figure>
<a name='more'></a>
<p>
This diagram fits in between the "math" part of the page and the "size and spacing" part of the page. The link to the math part is that I'm using the math terminology here. But the purpose is to link the math to the practical considerations of a hexagon grid. The reader wants to know the width, height, horizontal-spacing, and vertical-spacing for a hexagonal grid. Those values are derived from the four measurements in the new diagram.
</p>
<p>
Trying to figure out the purpose of this page made me realize I don't like the beginning of the page at all. Let's take a look:
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/page-1-before.png" alt="Screenshot of page before I changed it" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption>The page before I made changes</figcaption>
</figure>
<p>
I added the new diagram, but then I started thinking about <em>why</em> I don't like this section of the page. It starts out showing the reader what a corner and edge of a hexagon is. Do I really need to explain what a corner is?! Why is that even there?
</p>
<p>
The hexagon page grew out of <a href="http://www-cs-students.stanford.edu/~amitp/game-programming/grids/">my 2006 page</a> about centers, corners, and sides of a grid. The introductory diagram was vestigial, reflecting the history but not the current content of the page. It doesn't need to be there anymore.
</p>
<p>
I scribbled some notes on the page:
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/page-rearrange.png" alt="Scribbling notes on the page" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption>Things I might want to change about the page</figcaption>
</figure>
<ol class="org-ol">
<li>Remove the center/corner/side diagram. It belongs on a different page, or in one of the later sections of this page.</li>
<li>Remove the boring angle diagram. Or at least move it down to later. It's hardly the first thing I want to see.</li>
<li>The new diagram is a <em>math definitions</em> diagram. It's not very useful unless you already know the math.</li>
<li>The last diagram is what I want to see first!</li>
</ol>
<p>
The most important diagram here is the size/spacing diagram at the bottom. I want to put it at the top. But that diagram has issues. It serves <strong>two purposes</strong>. One is the width/height of a hexagon. The other is the spacing of a hexagon in a grid. The problem is that the diagram doesn’t show both at once. Instead, you have to hover over <em>text</em> of the page (not something you’d ever discover) to see the diagram switch between modes. Even worse, it shows <em>neither</em> mode when you load the page.
</p>
<p>
This topic is important enough that I shouldn't hide it behind a hover action. I decided to <strong>make it two separate diagrams</strong>.
</p>
<ol class="org-ol">
<li>The first diagram will be about <strong>size.</strong> The <em>new diagram</em> can serve that purpose: size (circumradius), width, height. The math names (minimal diameter, maximal diameter, inradius, circumradius) are less important.</li>
<li>The second diagram will be about <strong>spacing</strong>. It will look very much like the current size/spacing diagram, but won't have hidden modes. It will always show spacing.</li>
</ol>
<p>
I think this section of the page will be much better with these changes! And I now have a <strong>purpose</strong> for the new diagram. It will be about <strong>size</strong>. And now that I know the purpose of the new diagram, I can evaluate all the designs I had come up with. That'll be in <a href="https://simblob.blogspot.com/2022/12/introduction-to-hexagons-part-3.html">part three</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-3984484294477109302022-11-26T13:04:00.001-08:002022-11-26T13:07:56.422-08:00Introduction to hexagons, part 1<p>
When I started making interactive diagrams the hard part was making them <em>interactive</em> part. But over the years I've gotten much better at that. Now the problem is <em>designing</em> the diagrams. A few weeks ago I was looking at the <a href="https://www.redblobgames.com/grids/hexagons/#basics">beginning of my hexagons guide</a> to see what I could improve. I noticed this paragraph had no corresponding diagram:
</p>
<blockquote>
<p>
In math, the "circumradius" is the distance from the center to a corner (I call this size); the "inradius" is the distance from the center to the middle of an edge, sqrt(3/4)*size. The "maximal diameter" is twice the circumradius; the "minimal diameter" is twice the inradius. Wikipedia has more.
</p>
</blockquote>
<p>
My first thought was to add a diagram for this. I already have a diagram about angles, so I could add a diagram showing the inradius and circumradius:
</p>
<figure>
<img width="45%" style="vertical-align:top;box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" src="https://www.redblobgames.com/making-of/diagram-design/blog/angle-diagram.png" alt="Angle diagram" />
<img width="45%" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" src="https://www.redblobgames.com/making-of/diagram-design/blog/radius-diagram-1-spokes.png" alt="Radius diagram" />
<figcaption>Introductory diagrams</figcaption>
</figure>
<a name='more'></a>
<p>
I made it similar to the angle diagram in that I used dashed lines for the circles and also the measurement lines. But I needed <em>two</em> circles. I ended up making them different colors.
</p>
<p>
I didn't like it. But I didn't understand why. So <a href="https://twitter.com/redblobgames/status/1587200972639862784">I asked Twitter</a>, and got lots of feedback.
</p>
<ul class="org-ul">
<li><a href="http://www.flong.com/">Golan Levin</a> suggested <a href="https://twitter.com/golan/status/1587201970875539461">rotating the labels</a></li>
<li>Mega Wolf also <a href="https://twitter.com/WolfEyeRight/status/1587695507740233729">suggested rotating the labels</a></li>
<li>Estevo suggested <a href="https://twitter.com/euccastro/status/1587214973125672966">removing most of the dots</a></li>
<li>curved-ruler suggested <a href="https://twitter.com/curved_ruler/status/1587202290179612672">not showing all the radii</a></li>
<li>Damian Connoly suggested <a href="https://twitter.com/divillysausages/status/1587202865172549635">vertical alignment</a></li>
<li>Stanisław Małolepszy suggested <a href="https://twitter.com/stas/status/1587324429595459585">two separate hexagons</a></li>
<li><a href="https://jenson.org/about-scott/">Scott Jenson</a> suggested <a href="https://twitter.com/scottjenson/status/1587208998863847424">removing detail and moving the labels inside</a> (and made mockups!!)</li>
<li>Zaript suggested <a href="https://twitter.com/zaript/status/1587340569944199168">putting the measurements side by side</a> to make comparison easier</li>
<li>Golan Levin suggested <a href="https://twitter.com/golan/status/1587317009553014784">showing the ratio of the two radii is 2/√3</a></li>
<li>Blank suggested <a href="https://twitter.com/BoredAstronaut/status/1588357033149227009">using one hollow circle instead of two dashed circles</a></li>
<li>Rune Skovbo Johansen suggested <a href="https://twitter.com/runevision/status/1588448669099253760">rotating the radii to show the angle doesn't matter</a></li>
<li>horse paste also <a href="https://twitter.com/teh_handler/status/1588355287081099266">suggested not using dashed lines</a></li>
<li>Scott Jenson suggested <a href="https://twitter.com/BoredAstronaut/status/1588357033149227009">getting rid of blue, and using dash styles</a></li>
<li>Brendan suggested <a href="https://twitter.com/brendanzab/status/1587680840259031040">putting diameter lines on opposite sides</a></li>
</ul>
<p>
So that led me to try out lots of variants of this diagram.
</p>
<figure>
<img src="https://www.redblobgames.com/making-of/diagram-design/blog/radius-diagram-overview-1.png" alt="Lots of diagrams" style="box-shadow:0 0 2px 1px rgb(0 0 0 / 0.5)" />
<figcaption><a href="https://www.redblobgames.com/making-of/diagram-design/">Test page with many diagrams</a></figcaption>
</figure>
<p>
But there are pros and cons to each of these. Which one should I use? How can I evaluate these designs without knowing the <strong>purpose</strong> of this diagram?
</p>
<p>
That's <a href="https://simblob.blogspot.com/2022/11/introduction-to-hexagons-part-2.html">part two</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com2tag:blogger.com,1999:blog-5052387.post-57085607225010511082022-10-08T18:23:00.004-07:002022-10-08T18:23:41.642-07:00Connected Components<p>
Occasionally someone will ask me how to implement Connected Components, which is useful for several things, including
</p>
<ul class="org-ul">
<li>determining whether there's a path between two points; if there isn't, you can skip running pathfinding</li>
<li>finding the islands/continents on a procedurally generated map</li>
</ul>
<p>
It's actually a clever use of Breadth First Search. Every time the queue is empty, you find an unvisited node and put it into the queue, and then run the loop some more. When you have no more unvisited nodes, you're done.
</p>
<figure>
<img src="https://www.redblobgames.com/pathfinding/distance-to-any/blog/island-connected-components.png" alt="">
<figcaption>Connected Components</figcaption>
</figure>
<a name='more'></a>
<p>
I've told people about it but I hadn't implemented it before. So I implemented a demo. I wanted to reuse the generic search diagram I wrote for my pathfinding pages, but that algorithm didn't implement the variant of Breadth First Search I needed. The solution was to use a trick that my Diagram class allows, but that I had forgotten about. The diagrams are implemented in <em>layers</em> (see the diagrams halfway down on <a href="https://www.redblobgames.com/pathfinding/a-star/making-of.html">this page</a>). The trick is that a layer can calculate things too. It's not limited to drawing things. So I implemented layer that calculated the modified Breadth First Search, and then wrote the data back into the diagram so that subsequent layers could visualize it.
</p>
<p>
A reader had asked about connecting these islands together. This can also be done with Breadth First Search. The idea is to put <em>all</em> land locations into the <em>starting points</em>. Then as we use the search algorithm to visit the water tiles, as soon as we find a connection between water tiles emanating from two different islands, that tells us not only the meeting point, but also the path to connect the two islands. I implemented this demo too.
</p>
<figure>
<img src="https://www.redblobgames.com/pathfinding/distance-to-any/blog/island-potential-bridges.png" alt="">
<figcaption>Distance into water and potential bridge points</figcaption>
</figure>
<p>
I added it to my "distance to any" page, which is poorly named. It originally started out as a single demo for a single use of Breadth First Search, but over time I've expanded it to include several demos that share an idea: that you can start with <em>multiple</em> start points.
</p>
<p>
While I was updating that page, I decided to change the sample code to highlight the places where it differs from regular Breadth First Search:
</p>
<figure>
<img src="https://www.redblobgames.com/pathfinding/distance-to-any/blog/highlight-code-that-changed.png" alt="">
<figcaption>Highlighted code that changed from the original algorithm</figcaption>
</figure>
<p>
<a href="https://www.redblobgames.com/pathfinding/distance-to-any/#islands">Take a look at the new section of the page</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com3tag:blogger.com,1999:blog-5052387.post-9146236183865918062022-10-03T10:00:00.001-07:002022-10-03T10:00:00.165-07:00Little details<p>
Back in 2019 I started <a href="https://simblob.blogspot.com/2019/04/little-details.html">a page with a list of all the little things I do on my pages</a>. One of the things I have a problem with is that I don't finish something, and then I don't share it, and then nobody benefits. I'm wanting to share more partial projects. So I shared that page, even though it wasn't finished.
</p>
<p>
Since then I've added sections about
</p>
<ul class="org-ul">
<li>coloring <em>controls</em> to match the diagrams</li>
<li>state machines</li>
<li>linkable sections</li>
<li>arrows in svg, canvas, and outside the container</li>
<li>two column layout</li>
<li>backwards compatibility</li>
<li>removing build steps</li>
<li>adding build steps</li>
<li>markup languages</li>
<li>topic-based vs time-based projects</li>
<li>pre-rendering</li>
<li>meta tags for social media</li>
<li>support for printing</li>
</ul>
<p>
<a href="https://www.redblobgames.com/making-of/little-things/">https://www.redblobgames.com/making-of/little-things/</a>
</p>
<p>
The page is uneven, with some sections being detailed and others being simple links to other pages I've written, but I am hoping it's useful for people writing explanations on the web. More importantly, I'm building it as a reference for myself. I'm adding code snippets there too, so that the next time I need a draggable svg, I can go to that page and grab the code, and also read about the gotchas that I might've forgotten to check.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-54516010289488240072022-09-01T08:31:00.003-07:002022-09-01T08:31:58.846-07:00Hexagon conversions<p>
On my <a href="https://www.redblobgames.com/grids/hexagons/#pixel-to-hex">hexagon grid guide</a> I have a function to convert pixel (cartesian) coordinates to hexagon "cube" coordinates. I reuse a <code>hex_round()</code> function that I needed for line drawing. However, there are many other algorithms out there that work differently. I had made a list on <a href="https://www.redblobgames.com/grids/hexagons/more-pixel-to-hex.html">a separate page</a>. But I hadn't actually implemented them … until now.
</p>
<a name='more'></a>
<p>
I decided to implement five of the algorithms, and ended up implementing ten of them (but publishing only nine, because I couldn't get one of them to work). Several of them didn't quite match up with my original algorithm but I didn't understand why. My first thought was to have an <em>interactive</em> diagram where you can point at any pixel and get back the hex coordinates. I thought it might help me understand what's going on. But the <a href="http://worrydream.com/LadderOfAbstraction/">ladder of abstraction</a> page reminds me that sometimes I can display <em>all</em> the values instead of one at a time. So I made a <em>static</em> diagram using <a href="https://www.redblobgames.com/x/1902-hexagon-coloring/">this coloring</a>:
</p>
<figure>
<img src="https://www.redblobgames.com/grids/hexagons/blog/more-pixel-to-hex-pattern.png" alt="">
<figcaption>Test pattern</figcaption>
</figure>
<p>
This worked pretty well. I could immediately see various errors:
</p>
<ul class="org-ul">
<li>white hexagon not in the center → I have the translation wrong</li>
<li>hexagons are the wrong size → I have the scale wrong</li>
<li>hexagons are stretched or squashed → I have the x/y scale wrong</li>
<li>hexagon coloring wrong → coordinate system didn't match mine</li>
<li>pattern not even hexagons → something went horribly wrong!</li>
</ul>
<p>
It worked <em>much</em> better than interactively testing one pixel at a time.
</p>
<p>
I added interactivity as well, as it was simple to add. The interactivity helped me realize that my visual test was <em>passing</em> even though the coordinates were <em>wrong</em>. <strong>Oops!</strong> It turns out that some of my outputs were flipped left/right or top/bottom and I didn't realize it with a symmetrical test pattern. So I changed the color on two hexagons so that I could more easily spot problems like this (see the red in the lower left and pink in the lower right):
</p>
<figure>
<img src="https://www.redblobgames.com/grids/hexagons/blog/more-pixel-to-hex-orientation.png" alt="">
<figcaption>Orientation test image</figcaption>
</figure>
<p>
With that I was able to spot and fix the remaining errors.
</p>
<p>
Or did I?
</p>
<p>
I did not.
</p>
<p>
Visual tests are nice for trying to figure out what's going on, but it's <strong>easy to miss small differences</strong>. Ideally, I'd test <em>every</em> pixel with <em>every</em> algorithm against the "correct" answer. But I don't actually know which algorithm is most correct. There are boundary cases where it's ambiguous. So the next step was to mark the pixels where <em>any</em> algorithm disagreed:
</p>
<figure>
<img src="https://www.redblobgames.com/grids/hexagons/blog/more-pixel-to-hex-mismatch.png" alt="">
<figcaption>Mismatch test image</figcaption>
</figure>
<p>
<strong>Wow</strong>, this was quite a surprise. There are several patterns here that warrant further investigation. The rectangles at the top are a mystery. The vertical lines at the top suggest that there may be something working differently with negative Y coordinates. The horizontal squiggles suggest there's an inconsistent Y offset in one of the algorithms. But <em>which</em> algorithms are producing these differences?
</p>
<p>
I investigated and found that I had introduced a bug in one of the algorithms. I also seemed to have a bug in my test code. After fixing those, the remaining differences were either at corners, or along edges in the <code>hex_round()</code> algorithm. I'm pretty happy with that test. I also printed out the number of pixels where the output differs so that I could know if there was even 1 pixel off.
</p>
<p>
I'm glad I finally implemented these algorithms. I've found some on the web and some are sent in by readers. <a href="https://justinpombrio.net/programming/2020/04/28/pixel-to-hex.html">Justin Pombrio's page</a> and <a href="http://playtechs.blogspot.com/2007/04/hex-grids.html">James McNeill's page</a> were especially nice. There are still plenty more algorithms that I could implement, but after implementing ten of them I'm ready to do something else for a while.
</p>
<p>
<a href="https://www.redblobgames.com/grids/hexagons/more-pixel-to-hex.html">Take a look at the implementation page</a> to see the algorithms and the code.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-86848288322665132062022-06-09T09:16:00.003-07:002023-03-07T10:02:17.974-08:00Procedural tree generator<p>
I'm still a bit sad about Flash going away. Yes, I know lots of people hated it. But it was an amazing tool for creativity. One of the <em>tiny</em> fun projects I did was procedurally generating trees:
</p>
<figure>
<a href="https://www.redblobgames.com/x/2222-tree-generator/blog/small.png">
<img src="https://www.redblobgames.com/x/2222-tree-generator/blog/small-preview.jpg"
alt="Set of procedurally generated trees" />
</a>
<figcaption>Procedurally generated trees</figcaption>
</figure>
<a name='more'></a>
<p>
The basic idea is to create the tree structure through recursion. Start with a trunk and then spawn several branches. At the end of each branch spawn several more branches. Then draw a leaf cluster at the end of each of these branches, including the intermediate ones. A leaf cluster is just a few green circles.
</p>
<p>
It was a quick fun project, and Flash made things like that easy.
</p>
<p>
I decided to port that code to HTML5. One thing I never liked in Flash was the UI elements (e.g. mxml), but in HTML5 it's much easier to add a slider, so I added a slider. Maybe I should try <a href="https://p5js.org/">p5.js</a> for quick experiments.
</p>
<p>
I also tweaked the leaf drawing. In the original Flash version, I drew the leaves in the recursive pass, but that meant some branches were on top of leaves and and the leaves were drawn left to right. It didn't matter at the original size I drew the trees (<a href="https://www.redblobgames.com/x/2222-tree-generator/blog/flash-version.png">screenshot</a>) but once I had a slider, I could see the artifacts. I moved the leaf drawing to run after all the branches, and then drew them top to bottom, and was happier with the results.
</p>
<figure>
<a href="https://www.redblobgames.com/x/2222-tree-generator/blog/large.jpg">
<img src="https://www.redblobgames.com/x/2222-tree-generator/blog/large.jpg"
alt="Larger drawing of trees" />
</a>
<figcaption>Trees with leaf pattern</figcaption>
</figure>
<p>
I'm pretty happy with it, but I think if I revisit this, I'd like to add even more sliders to replace all the hard-coded parameters.
</p>
<p>
<a href="https://www.redblobgames.com/x/2222-tree-generator/">Try the procedurally tree generator here</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com0tag:blogger.com,1999:blog-5052387.post-16090247418020015892022-05-05T20:49:00.020-07:002022-05-10T09:21:49.445-07:00Upgrading my PRNG for procedural generation<p>
For a long time now I've been using a very simple <a href="https://en.wikipedia.org/wiki/Lehmer_random_number_generator">Park-Miller</a> LCG random number generator for my procedural generation projects. This is in part because back in the Flash days, Michael Baczynski's "polygonal.de" library was popular, and it had <a href="https://github.com/amitp/mapgen2/blob/master/third-party/PM_PRNG/de/polygonal/math/PM_PRNG.as">an implementation of Park-Miller</a>. When I switched to Haxe, I used his <a href="https://github.com/polygonal/ds">Haxe data structures library</a>. And then when I switched to Javascript, I used the <a href="https://github.com/odogono/prng-parkmiller-js">Javascript port of polygonal's library</a>, and then I wrote my own. I love how simple the <a href="https://gist.github.com/blixt/f17b47c62508be59987b">core algorithm</a> is.
</p>
<p>
However I know that it's not a great random number generator.
</p>
<p>
The original Flash version and the Javascript port both point to <a href="http://www.firstpr.com.au/dsp/rand31/">Robin Whittle's page</a> about this algorithm, which explained that 16807 was the original multiplier used in this random number generator, but that "47271 or 69621" would be better. I ended up making my own code that used 47271. But I noticed today that the <a href="https://en.wikipedia.org/wiki/Lehmer_random_number_generator">Wikipedia page</a> says 48271 is better, so I'm wondering if 47271 is a typo. So I looked <a href="https://www.firstpr.com.au/dsp/rand31/p1192-park.pdf">at the paper</a> and indeed, 48271 and 69621 are listed. Not 47271. So for years now I've been using a multiplier based on a typo.
</p>
<p>
Doh!
</p>
<a name='more'></a>
<p>
[update: 2022-05-06 added description of my goals]
</p>
<p>
So I started looking around, partly because of <a href="https://www.reddit.com/r/proceduralgeneration/comments/uifi4u/randu_randomness/?utm_source=share&utm_medium=ios_app&utm_name=iossmf">this gif</a> of RANDU's output. But what are my goals? I want a <em>seedable</em> generator for procedural genreation projects. I want something that I can run from Javascript, C++, and C#. I want something that lets me save the internal state. I want something small and simple. I want something well-tested.
</p>
<p>
<a href="https://github.com/nquinlan/better-random-numbers-for-javascript-mirror">This page from the author of Alea</a> has several Javascript generators, and talks about Javascript numbers and precision. I had planned to switch to one of these. But I also read <a href="https://github.com/bryc/code/blob/master/jshash/PRNGs.md#sfc32">bryc's page</a> and decided that I'll use sfc32. Are there any Javascript implementations out there? Well, bryc's page has one! So I don't need to look any further. But I did.
</p>
<p>
There are libraries like <a href="https://github.com/JoakimCh/pluggable-prng">pluggable-prng</a> (sfc32, alea, mulberry32, pcg32) and <a href="https://github.com/Paul-Browne/npm-deterministic-random-sequence">deterministic-random-sequence</a> (sfc32) and <a href="https://github.com/michaeldzjap/rand-seed">rand-seed</a> (sfc32, mulberry32, xoshiro128ss).
</p>
<p>
Interstingly, <a href="https://github.com/michaeldzjap/rand-seed/blob/develop/src/Algorithms/Sfc32.ts">rand-seed</a> and <a href="https://github.com/Countly/countly-server/blob/master/api/utils/random-sfc32.js">countly-server</a> both have sfc32 code that seems to come from <a href="https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript/47593316">this stackoverflow answer</a> by … bryc. But I tested that code and it doesn't match PractRand's output. It looks like bryc fixed it <a href="https://github.com/bryc/code/commit/dcf6d6c2b669319f5baeef11e41fc4b556517ebc">in this change</a> but didn't update the stackoverflow page. And <a href="https://github.com/Paul-Browne/npm-deterministic-random-sequence/blob/main/index.js">deterministic-random-sequence</a>'s code looks like it's the fixed version of bryc's code. In contrast, <a href="https://github.com/JoakimCh/pluggable-prng/blob/main/source/sfc32.js">pluggable-prng</a> looks like a separate port from PractRand, not using bryc's code. And <a href="https://gist.github.com/imneme/f1f7821f07cf76504a97f6537c818083">M.E. O'Neill's implementation</a> also looks like a separate port.
</p>
<p>
This is my test of the PractRand 0.95 c++ code (this is the original, as Chris Doty-Humphrey wrote PractRand and also sfc; note that earlier versions of PractRand had a different sfc implementation):
</p>
<div class="org-src-container">
<pre class="src src-cpp"><span class="keyword">typedef</span> <span class="type">unsigned</span> <span class="type">int</span> <span class="type">Uint32</span>;
<span class="type">Uint32</span> <span class="variable-name">a</span>, <span class="variable-name">b</span>, <span class="variable-name">c</span>, <span class="variable-name">counter</span>;
<span class="type">Uint32</span> <span class="function-name">raw32</span>() {
<span class="keyword">enum</span> {<span class="variable-name">BARREL_SHIFT</span> = 21, <span class="variable-name">RSHIFT</span> = 9, <span class="variable-name">LSHIFT</span> = 3};
<span class="comment-delimiter">//</span><span class="comment">good sets include {21,9,3},{15,8,3}; older versions used </span>
<span class="comment-delimiter">// </span><span class="comment">{25,8,3} which wasn't as good</span>
<span class="type">Uint32</span> <span class="variable-name">tmp</span> = a + b + counter++;
a = b ^ (b >> RSHIFT);
b = c + (c << LSHIFT);
c = ((c << BARREL_SHIFT) | (c >> (32-BARREL_SHIFT))) + tmp;
<span class="keyword">return</span> tmp;
}
<span class="type">void</span> <span class="function-name">seed</span>(<span class="type">unsigned</span> <span class="type">long</span> <span class="variable-name">s</span>) {
a = 0;<span class="comment-delimiter">//</span><span class="comment">a gets mixed in the slowest</span>
b = Uint32(s >> 0);
c = Uint32(s >> 32);
counter = 1;
<span class="keyword">for</span> (<span class="type">int</span> <span class="variable-name">i</span> = 0; i < 12; i++) raw32();<span class="comment-delimiter">//</span><span class="comment">12</span>
}
<span class="preprocessor">#include</span> <span class="string"><stdio.h></span>
<span class="type">int</span> <span class="function-name">main</span>() {
seed(12345);
<span class="keyword">for</span> (<span class="type">int</span> <span class="variable-name">i</span> = 0; i < 10; i++) {
printf(<span class="string">"%2d: %10u\n"</span>, i, raw32());
}
}
</pre>
</div>
<pre class="example" id="org07dde7a">
235160590
2967261163
116171463
2882324903
362604721
4227106926
1933307004
1608300071
2256615412
2701957640
</pre>
<p>
And here's a test of bryc's stackoverflow code:
</p>
<div class="org-src-container">
<pre class="src src-js"><span class="keyword">function</span> <span class="function-name">sfc32</span>(<span class="variable-name">a</span>, <span class="variable-name">b</span>, <span class="variable-name">c</span>, <span class="variable-name">d</span>) {
<span class="keyword">return</span> <span class="keyword">function</span>() {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
<span class="keyword">var</span> <span class="variable-name">t</span> = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
<span class="keyword">return</span> (t >>> 0) <span class="comment-delimiter">// </span><span class="comment">4294967296;</span>
};
}
<span class="keyword">let</span> <span class="variable-name">R</span> = sfc32(0, 12345, 0, 1);
<span class="keyword">for</span> (<span class="keyword">let</span> <span class="variable-name">i</span> = 0; i < 12; i++) R();
<span class="keyword">for</span> (<span class="keyword">let</span> <span class="variable-name">i</span> = 0; i < 10; i++) {
console.log(R());
}
</pre>
</div>
<pre class="example" id="orgd682dd2">
3650369721
775372277
764175981
2203700667
3130435557
1511882910
2685999717
3426618324
3713301275
1459740074
</pre>
<p>
They don't match. Here's a test of bryc's current version:
</p>
<div class="org-src-container">
<pre class="src src-js"><span class="keyword">function</span> <span class="function-name">sfc32</span>(<span class="variable-name">a</span>, <span class="variable-name">b</span>, <span class="variable-name">c</span>, <span class="variable-name">d</span>) {
<span class="keyword">return</span> <span class="keyword">function</span>() {
a |= 0; b |= 0; c |= 0; d |= 0;
<span class="keyword">var</span> <span class="variable-name">t</span> = (a + b | 0) + d | 0;
d = d + 1 | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = c << 21 | c >>> 11;
c = c + t | 0;
<span class="keyword">return</span> (t >>> 0) <span class="comment-delimiter">// </span><span class="comment">4294967296;</span>
};
}
<span class="keyword">let</span> <span class="variable-name">R</span> = sfc32(0, 12345, 0, 1);
<span class="keyword">for</span> (<span class="keyword">let</span> <span class="variable-name">i</span> = 0; i < 12; i++) R();
<span class="keyword">for</span> (<span class="keyword">let</span> <span class="variable-name">i</span> = 0; i < 10; i++) {
console.log(R());
}
</pre>
</div>
<pre class="example" id="orgcafbbb0">
235160590
2967261163
116171463
2882324903
362604721
4227106926
1933307004
1608300071
2256615412
2701957640
</pre>
<p>
So I'll use bryc's new version instead of bryc's old version. I don't know if the difference actually matters. Also note that PractRand recommends throwing away the first 12 outputs. Not every version of sfc32 does this.
</p>
<p>
I also liked <a href="https://www.pcg-random.org/posts/bob-jenkins-small-prng-passes-practrand.html">JSF</a> and <a href="https://github.com/bryc/code/blob/master/jshash/PRNGs.md#mulberry32">Mulberry32</a>. There are lots of good choices, so how do I choose? Well, I wanted something short and relatively simple, and I decided I was spending too much time on this, so I picked something that <a href="http://pracrand.sourceforge.net/RNG_engines.txt">PractRand rated highly</a>, and also something that bryc rated highly: sfc32. I think jsf32 or mulberry32 would have been just as good though.
</p>
<p>
This was a reminder that I <em>often</em> fall into rabbit holes where I spend way too much time learning about something that doesn't matter that much. But it was an interesting diversion, and I ended up with some <a href="https://gist.github.com/redblobgames/2a48f1b55528a3c23b3824a9a3314fa4">useful code in this gist</a>.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com6tag:blogger.com,1999:blog-5052387.post-69234397509321491742022-04-20T20:25:00.006-07:002022-04-20T20:25:00.173-07:00Improving island shaping for map generation, again<p>
Back in 2019 <a href="https://simblob.blogspot.com/2019/03/improving-island-shaping-for-map.html">I rewrote the island section of my noise-based map generation page</a>. I had high hopes for that rewrite. It was ok, but I was still quite unhappy with it. So I rewrote it again.
</p>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/island-after.jpg" style="margin:1em;max-width:80%">
<figcaption>Island map generation: demo and main ideas</figcaption>
</figure>
<a name='more'></a>
<p>
What did I not like?
</p>
<ol class="org-ol">
<li>The demo isn't interactive. In the text I told the reader to experiment with the parameters, but I didn't actually let them do that.</li>
<li><strong>My recommendations were mediocre</strong>. My thanks to KdotJPG on Reddit for pointing this out.</li>
</ol>
<p>
It turns out <strong>I didn't understand the problem as well as I thought</strong>.
</p>
<p>
So I decided to study it. I had been decomposing the problem into two: a <em>reshaping</em> function that can push the output up into land or down into water, and a <em>distance</em> function that determines how to reshape each part of the map. I made visualizations of various reshaping functions:
</p>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/reshaping-functions-for-islands.png" style="margin:1em;max-width:80%">
<figcaption>Reshaping functions</figcaption>
</figure>
<p>
I learned a lot here. Looking at the outputs, I very much liked the curved variants. But it turns out these charts were misleading! More on that later.
</p>
<p>
I also made visualizations of various distance functions:
</p>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/distance-functions-for-islands.png" style="margin:1em;max-width:80%">
<figcaption>Distance functions</figcaption>
</figure>
<p>
I learned a lot here too. But it turns out these were misleading too! More on that later.
</p>
<p>
Finally, I combined the two and generated lots of islands:
</p>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/island-recommendations.png" style="margin:1em;max-width:80%">
<figcaption>Combined output</figcaption>
</figure>
<p>
I really liked some of the combinations and decided I should recommend them. I <em>love</em> "small multiples" visualizations that let you see all the outputs at once instead of using interactivity to see one at a time. But it turns out these visualizations were misleading too!!
</p>
<p>
What went wrong? How did all these visualizations lead me astray? It turns out that <strong>I still don't understand the island problem.</strong> A lot of my intuitions I had built for my <a href="https://www.redblobgames.com/maps/mapgen2/">polygon map generator</a>. There, I used the island code to generate an island <em>shape</em> and then assigned elevation a different way. In doing that, I had thrown away the elevation data other than the 1-bit water/land sign. So when I recommended that same code on this page, where I <em>don't</em> throw away elevation data, it went badly. The visualizations I made here didn't show the final islands in 3D, and they were all tiny thumbnails, so I didn't see the artifacts.
</p>
<p>
I stopped and stepped back a bit. I needed to evaluate what I had learned and what might actually be useful now that I understand that I was approaching it wrong.
</p>
<p>
I moved all these experiments to <a href="https://www.redblobgames.com/maps/terrain-from-noise/islands.html">their own page</a> and decided to focus on the main page. What were my main complaints about it?
</p>
<ol class="org-ol">
<li>The demo isn't interactive. So I made it interactive. I added a slider to move between the non-island and island versions, and offered a choice of distance functions. I also added a button to generate a new map.</li>
<li>The recommendations are mediocre. I decided to go back to the original <em>reshaping</em> function. It has some known issues but avoids unknown problems from the other reshaping functions I tried. But the original <em>distance</em> functions I had recommended were clearly bad. Are there any that are clearly better? Yes. I changed the recommendation.</li>
</ol>
<p>
Along the way I learned that I needed to see the island in 3D to notice artifacts. So I changed the interactive demo to show the 3D island. And I made the colors prettier, not only for this demo but for the rest of the page. I had previously had a green to white gradient for elevation, and also low elevations became blue (water). But I had started the green underwater instead of at the lowest land elevation. Fixing that made the island map look a little nicer. While I was fixing up diagrams, I made the wraparound maps nicer:
</p>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/render-wraparound-nsew.png" style="margin:1em;max-width:40%">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/render-wraparound-ew.png" style="margin:1em;max-width:40%">
<figcaption>Improved wraparound diagrams</figcaption>
</figure>
<figure style="background:#eee">
<img src="https://www.redblobgames.com/maps/terrain-from-noise/blog/island-after-2022.png" style="margin:1em;max-width:80%">
<figcaption>Improved colors and 3d rendering</figcaption>
</figure>
<p>
I <em>am</em> happier with the island section than I was before, but I'm still not really happy with it. I'll let it be for now and then come back to it later. I'm trying to treat my pages like a wiki: always improving, never finished. <a href="https://www.redblobgames.com/maps/terrain-from-noise/#islands">Take a look!</a>
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com4tag:blogger.com,1999:blog-5052387.post-87074112487903242302022-04-06T18:01:00.005-07:002022-04-06T18:12:26.445-07:00Offline access with File / Save As<p>
One of my low priority goals for my site is to make my pages work offline. I had looked into "web manifests" but they seem more suited for web apps and not for documents like mine. So I looked at what it would take to make File→Save As work, <strong>preserving interactivity</strong>. It turns out it does not work in general on my site. Why?
</p>
<p>
I tried to make the original HTML + JavaScript should work offline in the sense that they only access resources on the current server and not from any CDN, Google Fonts, etc. So if you save the HTML + JavaScript it should work on a local web server. However, File→Save As doesn't work this way!
</p>
<figure>
<img src="https://www.redblobgames.com/x/screenshots/offline-access.svg" alt="Diagram showing what happens when using Save As in the browser on an interactive page" />
<figcaption>Save As for an interactive page (diagram made with <a href="https://excalidraw.com/">excalidraw</a>)</figcaption>
</figure>
<a name='more'></a>
<p>
The browser saves the HTML <em>after</em> it has been modified by running Javascript. But that HTML contains the modified HTML without the event handlers or JavaScript variables. When you load the saved page, it <em>re-runs</em> the JavaScript, and that will modify the HTML again. Unless that code is carefully written, you can end up with two of some things, and some errors when the code can't handle two of something.
</p>
<p>
The property we want is <strong>idempotency</strong>. When the code runs a second time, it should produce the same results as it did the first time it ran.
</p>
<p>
I think it's too much work to expect idempotency on most of my pages. The <a href="https://www.redblobgames.com/grids/hexagons/">Hexagons guide</a> is the main place I thought it'd be useful for someone to have an offline copy, and fortunately most of that page was ready for idempotency, because I used Vue with server-side prerendering. It turns out my <a href="https://www.redblobgames.com/pathfinding/a-star/introduction.html">A* Introduction</a> was mostly idempotent, so I made the rest of it idempotent.
</p>
<p>
Unfortunately it's fragile. Even if they're idempotent now, it would be easy for me to miss something in a future update and lose the idempotency. I could run Save As in a script and do some testing, but it turns out the output HTML is <em>not</em> actually the same the second time. It's <em>functionally</em> the same but I can't test it with a simple string compare. I could either run some kind of in-browser test that simulates clicks and other interactions, or I could run a textural diff on the HTML and make exceptions for all the things the browser changes during Save As.
</p>
<p>
I think the number of people who use Save As is going to be small enough that it's not setting up a lot of infrastructure to test this. :-( It'll be nice when it works but it's just <strong>not worth it</strong>.
</p>
<p>
I also discovered a few other things during this mini project:
</p>
<ol class="org-ol">
<li>Browsers will not load ES6 <strong>modules</strong> from <kbd>file://</kbd> urls. This is unfortunate. To make the A* page work, I had to compile my ES6 module code into non-module code using <a href="https://github.com/evanw/esbuild">esbuild</a> (which I love!). But I really don't like that browser makers are limiting some features to having the correct mime types, or <a href="https://textslashplain.com/2019/10/09/navigating-to-file-urls/">not working on file:// urls</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts/features_restricted_to_secure_contexts">or requiring https</a>. All these limit what locally saved <kbd>file://</kbd> pages can do.</li>
<li>Browsers will only save resources in <strong>subfolders</strong>, not in parent folders. This is reasonable. But since I share code across pages on my site, that code didn't get saved, and that broke the interactivity. For the hex and A* pages I ended up restructuring some files so that they would get saved.</li>
<li><strong>Links</strong> within the page like <kbd><a href="#foo"></kbd> don't get saved property in Chrome or Safari. Those browsers rewrite the links to point back to the original site. Firefox saves them as local links. So I recommend if you want to save an offline copy, use Firefox.</li>
</ol>
<p>
Unfortunately this was all a <strong>distraction</strong>. I sometimes fall into this trap of doing something <em>low value</em> but easier in order to procrastinate doing something high value but <em>harder</em>. I think it was far lower value than <a href="https://simblob.blogspot.com/2018/12/printing-my-pages.html">making my pages printable to PDF</a>, as I think PDF is probably a better solution for offline access than relying on the browser's Save As feature. And even that was pretty low value. Putting the pages on GitHub would be better, but my pages are so tangled together that I will have to solve several issues to make that work.
</p>
<p>
In good news, as part of this project I shrunk the JavaScript down to 15k, and delayed diagram rendering until the page has loaded. This sped up page loading some more. Try going to my <a href="https://www.redblobgames.com/">home page</a> and clicking the A* Introduction link. Does it load fast for you?
</p>
<p>
I want to <strong>shift gears</strong> and work on something unrelated to my tutorials, but I keep falling into these rabbit holes. It's easier to go improve an existing page than to start something unfamiliar and new.
</p>
Amithttp://www.blogger.com/profile/12159325271882018300noreply@blogger.com5