<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <atom:link href="https://www.kleemans.ch/rss.xml" rel="self" type="application/rss+xml"/>
    <title>kleemans.ch</title>
    <link>https://www.kleemans.ch</link>
    <description/>
    <lastBuildDate>Mon, 12 Feb 2024 20:31:36 +0100</lastBuildDate>
    <item>
      <title>Creating a GameBoy TAS</title>
      <link>https://www.kleemans.ch/creating-a-gameboy-tas</link>
      <description>&lt;figure class="gameboy"&gt;&lt;img src="/img/457t.png"&gt;
&lt;figcaption&gt;&lt;p&gt;This post is part of the Game Boy series (#7).&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;In the world of &lt;a href="https://de.wikipedia.org/wiki/Speedrun"&gt;speedrunning&lt;/a&gt;, a &lt;strong&gt;TAS&lt;/strong&gt; is a Tool-assisted speedrun, meaning that it was not (only) done by human hand, but using some sort of tool.&lt;/p&gt;
&lt;p&gt;This leads to a quite different challenge than speedrunning itself: Find the most optimal sequence of inputs for a game and then record a video of how those inputs are being executed. As the sequence of inputs can be written down and saved, they can be then fine-tuned, improved and optimized far beyond what a human player could be capable of.&lt;/p&gt;
&lt;h3&gt;BizHawk&lt;/h3&gt;
&lt;p&gt;Now for the GameBoy, one emulator that has great TAS support is &lt;a href="https://github.com/TASEmulators/BizHawk"&gt;BizHawk&lt;/a&gt;. It is very accurate per-frame (unfortunately not always in real-time, see &lt;a href="https://www.kleemans.ch/static/gb-emulator-timing/"&gt;emulator tests here&lt;/a&gt;) and allows for &amp;quot;programming&amp;quot; a sequence of inputs directly in the UI.&lt;/p&gt;
&lt;p&gt;Let's look at a first example:&lt;/p&gt;
&lt;iframe width="640" height="360" src="https://www.youtube.com/embed/UW43R9r1CtU?si=x6bxoegAcxzWDBz5" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;The game used here is &lt;a href="https://github.com/stacksmashing/gb-wordle"&gt;Wordle for GameBoy&lt;/a&gt;. To the right, a sequence of inputs (Up, Down, Left, Right, A, B) are being scrolled down, and on the left the emulator executes those inputs on the emulator.&lt;/p&gt;
&lt;h3&gt;Starting a new TAS project&lt;/h3&gt;
&lt;p&gt;So let's get started with our TAS! We'll use the newest version of BizHawk for Windows, as it works best there. You can download it from the Github link above or from &lt;a href="https://tasvideos.org/BizHawk/ReleaseHistory#Bizhawk291"&gt;tasvideos.org&lt;/a&gt;, which, by the way, have a lot of TAS resources available.&lt;/p&gt;
&lt;p&gt;After downloading and installing, we are greeted with an empty, black window. Next we need some kind of &lt;strong&gt;GameBoy ROM&lt;/strong&gt;, a game. We'll use the freely available game &amp;quot;Gunman Clive&amp;quot; for GameBoy, you can &lt;a href="http://www.gunmanclive.com/gameboy/"&gt;download the ROM here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After dragging the file into the BizHawk window, the game should load. Now we can open the TAStudio:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/bizhawk-menu.png#500" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;We now have a side-by-side view like in the first video above. We can now let the game start using the &amp;quot;Pause&amp;quot; button, and pause it again when the &amp;quot;Press Start&amp;quot; is visible.&lt;/p&gt;
&lt;p&gt;That's the point we can add our first input: Click in the &amp;quot;S&amp;quot; column to simulate a &amp;quot;Start&amp;quot; press, and the game should start!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/first-input.png#600" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Sometimes you need more than a single-frame input, as manual inputs usually also last for at least 2-3 frames. &lt;/p&gt;
&lt;h3&gt;Learning to walk&lt;/h3&gt;
&lt;p&gt;Now that we're in the actual level, let's try and move forwards. To move on an actual GameBoy you'd press and hold Right, so we're going to add a lot of &amp;quot;R&amp;quot; inputs:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/walk.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: You can just click &amp;amp; hold and then drag down the column to add many inputs at once. The same works for undoing them.&lt;/p&gt;
&lt;p&gt;Next up is an obstacle - let's jump over it! Jump is mapped with an &amp;quot;A&amp;quot; input and we'll have to time it somewhat to actually land on the crate and not before it.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/jump.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If you click and hold in the leftmost, white column and then drag up or down, the emulator will follow and you can watch the game run in slow motion or go back and forth framewise, which is really helpful for &amp;quot;debugging&amp;quot; or optimizing a certain section of the game.&lt;/p&gt;
&lt;p&gt;After putting some effort working on our TAS project, be sure to &lt;strong&gt;save it&lt;/strong&gt;, which you can do via &amp;quot;File &amp;gt; Save As&amp;quot;, and save it as a new .tasproj-File. This file contains all the inputs and other configuration and will let you continue were you left.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I would recommend to backup the .tasproj-File now and then (with creating a simple copy), to make sure you don't lose your progress. It happens now and then that BizHawk crashes or that you mess up with the project (for example with branches, which are a way to explore variants).&lt;/p&gt;
&lt;h3&gt;Advanced: Using RAM watch / RAM search&lt;/h3&gt;
&lt;p&gt;One technique which can be very helpful is digging into the GameBoy's internals, and observe certain values to better understand what is going on.&lt;/p&gt;
&lt;p&gt;For example, in Gunman Clive, you can fire at most 3 bullets at once (using &amp;quot;B&amp;quot;). After that, nothing will happen if you press &amp;quot;B&amp;quot;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/shoot3.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Now let's find out where this limit is stored. Let's close TAStudio for now (don't need it for this step) and open RAM search:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/ram-search-menu.png#300" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Now we see a list of all in-memory values, which the game will update, for example position of te player, movement speed, position of enemies etc.&lt;/p&gt;
&lt;p&gt;Let's try to pin down the exact location of where the limit of 3 bullets is saved. Now this works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You start out with the full memory bank (here 8192 addresses), which you can observe at runtime. One or some of those addresses are related to what you're searching, so you'll have to search for the right ones.&lt;/li&gt;
&lt;li&gt;You can either search for specific values (which is often very tricky, as we don't know how it is stored), or, which is often more successful, filter by &lt;strong&gt;how the value changes&lt;/strong&gt;. For example, if we shoot bullets, we expect the value to decrease. If we do nothing, we expect the value to stay the same. This way, we can filter out all the RAM addresses which are not related to what we're searching.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/ram-search.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;If we have found an address which seems to match, we can &lt;strong&gt;observe&lt;/strong&gt; it by adding it to the RAM Watch. It seems that address &lt;code&gt;1240&lt;/code&gt; is related to what we're searching:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/1240.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;We can then for example set a value ourselves, or &lt;strong&gt;freeze&lt;/strong&gt; it to see what happens:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/0infinite-bullets.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Infinite bullets! It also seems to introduce a bug where bullets will reappear on the left side of the screen if they move out on the right.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Sometimes it's worth switching the word size to 2 or 4 byte, as we don't know how the game stores its values. If some addresses are found it's best play around and see what happens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/0word-size.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;With 2-byte words, we can see what happens now when we freeze the address in RAM:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/6aa742d7f7ea6778b11c8b3f3fa0b484/bullet-stopped.gif" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;We now have the ability to stop time! :-)&lt;/p&gt;
&lt;p&gt;The goal was to show here that it can be handy to know more about the internals of the game. We obviously can't manipulate the game RAM while making our TAS, but it can be really helpful to know exactly how much speed we have, what items give us the best power-up etc.&lt;/p&gt;
&lt;h3&gt;Result&lt;/h3&gt;
&lt;p&gt;You can see the finished (for now - maybe you'll find a more efficient way!) TAS here:&lt;/p&gt;
&lt;iframe width="640" height="360" src="https://www.youtube.com/embed/Zw8B5GZSNr4?si=ihg5siWV3HmnPOBZ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;You can also download the .tasproj-File here: &lt;a href="/static/_files/gunmanclive.zip"&gt;gunmanclive.zip&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Bonus: Discovering glitches&lt;/h3&gt;
&lt;p&gt;Using TASes can also be very helpful in figuring out how a game works internally. For example, in Home Alone 2, there is a glitch which lets you change where the elevator goes, depending on when the Up button is pressed:&lt;/p&gt;
&lt;iframe width="640" height="360" src="https://www.youtube.com/embed/ZHfVXbRw3Iw?si=xLd3QyRDKYtkZzSV" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Thanks for reading and happy TASing!&lt;/p&gt;</description>
      <pubDate>Mon, 05 Feb 2024 12:00:58 +0100</pubDate>
      <guid isPermaLink="false">6aa742d7f7ea6778b11c8b3f3fa0b484</guid>
    </item>
    <item>
      <title>Random pizza chooser</title>
      <link>https://www.kleemans.ch/random-pizza-chooser</link>
      <description>&lt;p&gt;At our team day at work when everybody is around, we often go out for Pizza. Everybody has their few favourites, but recently I got somewhat bored and wanted to try a random pizza - but how to choose?&lt;/p&gt;
&lt;p&gt;I decided to come up with a quick mobile page: Just tap the pizza and it will select a random pizza for you!&lt;/p&gt;
&lt;iframe src="https://akleemans.github.io/random-pizza/" title="Random pizza chooser" style="width: 600px; height: 500px;" scrolling="no"&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href="https://akleemans.github.io/random-pizza/"&gt;Direct link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The pizza menu data is from &lt;a href="https://www.hotel-bahnhof-zollikofen.ch/"&gt;Restaurant Bahnhof&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Source: &lt;a href="https://github.com/akleemans/random-pizza"&gt;https://github.com/akleemans/random-pizza&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading and Bon appétit!&lt;/p&gt;</description>
      <pubDate>Mon, 01 Jan 2024 12:00:22 +0100</pubDate>
      <guid isPermaLink="false">f9efb0705858f8a9ffd7eb59c6fe90fa</guid>
    </item>
    <item>
      <title>Practice Terraform using Christmas trees</title>
      <link>https://www.kleemans.ch/practice-terraform-using-christmas-trees</link>
      <description>&lt;p&gt;&lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; is a great tool to manage your infrastructure via plain text. It helps detecting what changed and how to bring the infrastructure to the state you want it to be in.&lt;/p&gt;
&lt;p&gt;Now Terraform has all the important Cloud services like Azure, AWS etc. as &amp;quot;&lt;a href="https://registry.terraform.io/browse/providers"&gt;providers&lt;/a&gt;&amp;quot; (adapters) available, but there are also some more obscure ones, like &lt;a href="https://registry.terraform.io/providers/conradludgate/spotify/latest/docs"&gt;Spotify&lt;/a&gt; or &lt;a href="https://ndmckinley.github.io/terraform-provider-dominos/"&gt;Dominos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To fiddle around a bit with the concepts of Terraform, we'll use the &lt;a href="https://registry.terraform.io/providers/cappyzawa/christmas-tree/latest/docs/resources/christmas-tree"&gt;Christmas tree provider&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/b3b797313c2520de63059e4d598873af/merry-xmas.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Getting started with Terraform&lt;/h3&gt;
&lt;p&gt;At first we'll need a working Terraform installation. Hashicorp has a good &lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli#install-terraform"&gt;installation guide&lt;/a&gt; for all the major platforms, I'm going to use Ubuntu here.&lt;/p&gt;
&lt;p&gt;After installing the needed dependencies (like &lt;code&gt;gnupg&lt;/code&gt; etc.), adding the HashiCorp GPG key and their Debian package repository, we're able to install terraform using&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;sudo apt install terraform&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this, we'll see that it was installed:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;adrianus@adrianus-ThinkPad:~$ terraform -v
Terraform v1.5.7 on linux_amd64&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Setting up the provider&lt;/h3&gt;
&lt;p&gt;With Terraform installed, we can get going and create a new file &lt;code&gt;main.tf&lt;/code&gt;, where we can add the provider with its version. We could also configure options for our provider, but &lt;code&gt;christmas-tree&lt;/code&gt; doesn't need any (&lt;code&gt;{}&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;terraform {
  required_providers {
    christmas-tree = {
      source = "cappyzawa/christmas-tree"
      version = "0.6.1"
    }
  }
}

provider "christmas-tree" {}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can initialize Terraform:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ terraform init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will setup Terraform for our project, create a &lt;code&gt;.terraform&lt;/code&gt;-folder and download the provider for us.&lt;/p&gt;
&lt;h3&gt;Creating a resource&lt;/h3&gt;
&lt;p&gt;Now let's create our first Christmas Tree. According to the &lt;a href="https://registry.terraform.io/providers/cappyzawa/christmas-tree/latest/docs/resources/christmas-tree"&gt;Documentation&lt;/a&gt;, we can define a new &amp;quot;Christmas tree&amp;quot;-resource as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource "christmas-tree" "my-tree" {
  path        = "/tmp/tree"
  ball_color  = "red"
  star_color  = "yellow"
  light_color = "white"
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now check what Terraform wants to do, based on this resource:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ terraform plan
Terraform will perform the following actions:

  # christmas-tree.my-tree will be created
  + resource "christmas-tree" "my-tree" {
      + ball_color  = "red"
      + id          = (known after apply)
      + light_color = "white"
      + path        = "/tmp/tree"
      + star_color  = "yellow"
    }

Plan: 1 to add, 0 to change, 0 to destroy.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice! It recognizes a new resource and indicates that it wants to create it.&lt;/p&gt;
&lt;h3&gt;How Terraform works&lt;/h3&gt;
&lt;p&gt;When working with Terraform, 3 things are important:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The current reality - your &lt;strong&gt;actual (infrastructure) resources&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Terraform's &lt;strong&gt;internal state&lt;/strong&gt; - this is what Terraform &lt;strong&gt;thinks&lt;/strong&gt; the current status of your resources is. It is maintained in the file &lt;code&gt;terraform.tfstate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The definition of what your resources &lt;strong&gt;should&lt;/strong&gt; be, kept in &lt;code&gt;main.tf&lt;/code&gt;. This is the file we created and which we're going to change.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now when running &lt;code&gt;terraform plan&lt;/code&gt;, Terraform will first check against what is actually there (1.), so it can then update its internal state (2.). After that, it will check what should be the new state (3.) against the current state (2.) and map out the differences (add, update, replace, delete). It will show these differences then as an output of &lt;code&gt;plan&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After making a literal &amp;quot;plan&amp;quot; of what to do, we can accept if with &lt;code&gt;terraform apply&lt;/code&gt;. This will apply all the recognized differences and the planned state (3.) will match both the internal state of Terraform (2.) and your actual resource state (1.)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If we run &lt;code&gt;terraform plan&lt;/code&gt; again, but this time with &lt;code&gt;-out tfplan&lt;/code&gt;, we can run &lt;code&gt;apply&lt;/code&gt; after that to be sure that exactly the changes from before will be applied. This will look like follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ terraform apply "tfplan"
christmas-tree.my-tree: Creating...
christmas-tree.my-tree: Creation complete after 0s [id=/tmp/tree]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Detecting &amp;amp; applying changes&lt;/h3&gt;
&lt;p&gt;What if we change something small, for example the &lt;code&gt;ball_color&lt;/code&gt;? Running a &lt;code&gt;terraform plan&lt;/code&gt; will result in an update:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;christmas-tree.my-tree: Refreshing state... [id=/tmp/tree]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # christmas-tree.my-tree will be updated in-place
  ~ resource "christmas-tree" "my-tree" {
      ~ ball_color  = "red" -&amp;gt; "blue"
        id          = "/tmp/tree"
        # (3 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Breaking changes&lt;/h3&gt;
&lt;p&gt;If we want our Christmas tree at a different location, say &lt;code&gt;/tmp/new-tree&lt;/code&gt; instead of &lt;code&gt;/tmp/tree&lt;/code&gt;, this will be such a big change that Terraform will not be able to update our existing ressource - instead, it will do a &lt;strong&gt;replacement&lt;/strong&gt;. This will be marked with &lt;code&gt;+/-&lt;/code&gt; and will look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ terraform plan -out tfplan
christmas-tree.my-tree: Refreshing state... [id=/tmp/tree]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # christmas-tree.my-tree must be replaced
-/+ resource "christmas-tree" "my-tree" {
      ~ id          = "/tmp/tree" -&amp;gt; (known after apply)
      ~ path        = "/tmp/tree" -&amp;gt; "/tmp/new-tree" # forces replacement
        # (3 unchanged attributes hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks for reading, happy terraforming and merry Christmas!&lt;/p&gt;</description>
      <pubDate>Mon, 18 Dec 2023 12:00:55 +0100</pubDate>
      <guid isPermaLink="false">b3b797313c2520de63059e4d598873af</guid>
    </item>
    <item>
      <title>How to host an Angular App for free on Github</title>
      <link>https://www.kleemans.ch/how-to-host-angular-apps-for-free-on-github</link>
      <description>&lt;p&gt;I work a lot with &lt;a href="https://angular.io/"&gt;Angular&lt;/a&gt; - at work and also at home, for private projects. Often I find myself in the situation where I want to share a simple Angular app, and have it available publicly.&lt;/p&gt;
&lt;p&gt;Github offers an amazing free service, &lt;a href="https://pages.github.com/"&gt;Github pages&lt;/a&gt;, which lets you serve static content. As a compiled Angular App is also just a bunch of static JavaScript and CSS files, here's how to set up a project to
As an example, I'll use the [gb-manual-search](TODO link) which [I've written last time about](TODO blogpost link).&lt;/p&gt;
&lt;h2&gt;Setting up angular.json&lt;/h2&gt;
&lt;p&gt;In the &lt;code&gt;angular.json&lt;/code&gt; build options, we'll have to update two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Github pages has a fixed folder it relies on, the &lt;code&gt;docs&lt;/code&gt; folder, so we can set that here as our build output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The page will be available under a subpath of &lt;code&gt;github.io&lt;/code&gt;, for example &lt;code&gt;https://akleemans.github.io/gb-manual-search/&lt;/code&gt;. To make Angular work with that (and not being in the root path), you'll have to add the &lt;code&gt;/&amp;lt;name-of-your-repository&amp;gt;/&lt;/code&gt; as &lt;code&gt;baseHref&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After changing that, the &lt;code&gt;angular.json&lt;/code&gt; should look like this:&lt;/p&gt;
&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/025570e9849b998528edcc0f3eef88b3/angular-json.png" alt=""&gt;
&lt;h2&gt; Building the App&lt;/h2&gt;
&lt;p&gt;With the &lt;code&gt;angular.json&lt;/code&gt; properly configured, we can trigger &lt;code&gt;npm build&lt;/code&gt; to get our application built. After that, our &lt;code&gt;docs&lt;/code&gt;-folder should look like this:&lt;/p&gt;
&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/025570e9849b998528edcc0f3eef88b3/docs-folder.png" alt="" style="width: 350px;"&gt;
&lt;p&gt;Make sure add it to the tracked git files and commit them to the repository.&lt;/p&gt;
&lt;h2&gt;Activate Github pages&lt;/h2&gt;
&lt;p&gt;Now, for activating Github Pages, go to the Settings of your repository and enable it here:&lt;/p&gt;
&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/025570e9849b998528edcc0f3eef88b3/gh-pages.png" alt=""&gt;
&lt;p&gt;You could also choose another branch besides &lt;code&gt;main&lt;/code&gt;, if you want to keep the published site on a specific branch (so you can push to that branch specifically if you want more control when to &amp;quot;release&amp;quot;). For simplicity's sake I mostly just use &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When activated, it will auto-deploy everything that is in &lt;code&gt;docs&lt;/code&gt; to a web page.&lt;/p&gt;
&lt;h2&gt;Accessing the page&lt;/h2&gt;
&lt;p&gt;After configuring everything, you should see a Github Pages &amp;quot;deployment&amp;quot; (under &amp;quot;Actions&amp;quot;) with a green checkmark:&lt;/p&gt;
&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/025570e9849b998528edcc0f3eef88b3/deployment2.png" alt=""&gt;
&lt;p&gt;You can now access your page, for example &lt;a href="https://akleemans.github.io/gb-manual-search/"&gt;https://akleemans.github.io/gb-manual-search/&lt;/a&gt;! 🎉&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy publishing!&lt;/p&gt;</description>
      <pubDate>Mon, 04 Dec 2023 12:00:09 +0100</pubDate>
      <guid isPermaLink="false">025570e9849b998528edcc0f3eef88b3</guid>
    </item>
    <item>
      <title>Making GameBoy manuals searchable via OCR</title>
      <link>https://www.kleemans.ch/making-gameboy-manuals-searchable-via-ocr</link>
      <description>&lt;figure class="german"&gt;&lt;img src="/img/457t.png"&gt;
&lt;figcaption&gt;&lt;p&gt;This post is part of the Game Boy series (#6).&lt;/p&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;TLDR: I created a searchable index of scanned PDF Game Boy manuals: &lt;a href="https://akleemans.github.io/gb-manual-search/"&gt;akleemans.github.io/gb-manual-search&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;There has been an ongoing effort to preserve and collect GameBoy manuals, and many (although not all) are available today, mostly as PDF.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://sprintinglegs.com/gbdb/manuals/manualdata?id=644"&gt;&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/b1d9de978b71f943ac90e9320fbe41e4/sml2.png#400" alt="" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What I was missing though was to do a &lt;strong&gt;text search&lt;/strong&gt; in them - search for a term across all manuals and then find manual and the passages where they occur.&lt;/p&gt;
&lt;h3&gt;Using OCR on a collection of manual PDFs&lt;/h3&gt;
&lt;p&gt;From multiple source like &lt;a href="http://www.atensionspan.com/KML/GameBoy/"&gt;Kirkland's&lt;/a&gt;, &lt;a href="https://archive.org/details/NintendoGameBoyManuals/"&gt;archive.org dumps&lt;/a&gt; or &lt;a href="https://www.gamesdatabase.org/all_game_system_manuals_for-nintendo_game_boy"&gt;gamesdatabase.org&lt;/a&gt;, I found the most complete to be &lt;a href="https://sprintinglegs.com/gbdb/manuals/"&gt;sprintinglegs' GameBoy manual list&lt;/a&gt;, so that's the one I used.&lt;/p&gt;
&lt;p&gt;Once having all the manuals as PDFs, I decided to use Python to extract the text content. In a first step, the images are extracted page by page using &lt;code&gt;pdf2image&lt;/code&gt;, and then subsequently scanned with OCR using &lt;code&gt;pytesseract&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The content is then saved as JSON (for easy processing in a web app). Here is the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import glob
import json
import pdf2image
import pytesseract

def get_text(pdf_file):
    text = ''
    images = pdf2image.convert_from_path(pdf_file)
    for pg, img in enumerate(images):
        print(f'Working on page {pg}')
        text += pytesseract.image_to_string(img)
    return text

ocr_text = {}
files = glob.glob("manuals/*.pdf")
print(f'Found {len(files)} files.')
for file_name in files:
    print(f'Scanning {file_name}...')
    text = get_text(file_name)
    ocr_text[file_name] = text

with open('text-output.json', 'w') as write_file:
    write_file.write(json.dumps(ocr_text, indent=4))&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Creating a simple Angular App&lt;/h3&gt;
&lt;p&gt;After having all the text content ready, I created a small Angular app to search through the text and show a list of occurences, grouped by the manual. The search term is &amp;quot;highlighted&amp;quot; with brackets (&lt;code&gt;[ ]&lt;/code&gt;) and some context around the occurence (50 characters before and after) is also shown.&lt;/p&gt;
&lt;p&gt;The list is sorted by the amount of occurences, so manuals with more occurences will appear higher:
&lt;img src="https://www.kleemans.ch/bl-content/uploads/pages/b1d9de978b71f943ac90e9320fbe41e4/search-results.png#600" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;You can &lt;a href="https://akleemans.github.io/gb-manual-search/"&gt;try it here yourself&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Source code: &lt;a href="https://github.com/akleemans/gb-manual-search"&gt;https://github.com/akleemans/gb-manual-search&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading and happy searching!&lt;/p&gt;</description>
      <pubDate>Mon, 06 Nov 2023 12:00:06 +0100</pubDate>
      <guid isPermaLink="false">b1d9de978b71f943ac90e9320fbe41e4</guid>
    </item>
  </channel>
</rss>
