<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Felix Terkhorn</title>
    <description>The latest articles on Forem by Felix Terkhorn (@terkwood).</description>
    <link>https://forem.com/terkwood</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F118656%2F5fdcead0-11e6-4550-b395-0eb4bb926102.png</url>
      <title>Forem: Felix Terkhorn</title>
      <link>https://forem.com/terkwood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/terkwood"/>
    <language>en</language>
    <item>
      <title>Redis Streams API Coming to Deno</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Mon, 29 Jun 2020 10:24:30 +0000</pubDate>
      <link>https://forem.com/terkwood/redis-streams-api-coming-to-deno-3f31</link>
      <guid>https://forem.com/terkwood/redis-streams-api-coming-to-deno-3f31</guid>
      <description>&lt;p&gt;🦕 💾 The Redis Streams API will land in deno-redis pretty soon. I'm nearing completion with this pull request and hope for a thorough review.&lt;/p&gt;

&lt;p&gt;🏞 🍕 There's been a lot of activity in this repository, which is encouraging!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/denolib/deno-redis/pull/86"&gt;https://github.com/denolib/deno-redis/pull/86&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>deno</category>
      <category>typescript</category>
      <category>data</category>
    </item>
    <item>
      <title>Redis Streams available in redis-rs</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Tue, 23 Jun 2020 12:44:45 +0000</pubDate>
      <link>https://forem.com/terkwood/redis-streams-available-in-redis-rs-14cd</link>
      <guid>https://forem.com/terkwood/redis-streams-available-in-redis-rs-14cd</guid>
      <description>&lt;p&gt;Woohoo! We now have high-level support for Streams commands in redis-rs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mitsuhiko/redis-rs/issues/162"&gt;https://github.com/mitsuhiko/redis-rs/issues/162&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the conclusion of work described in a previous post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/terkwood/adding-redis-streams-to-rust-464j"&gt;https://dev.to/terkwood/adding-redis-streams-to-rust-464j&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hope someone out there finds it enjoyable! I have a couple of outstanding work items that I'm going to revisit now that there's a pleasant way to interact with this portion of the Redis API. 🦀 💾 &lt;/p&gt;

</description>
      <category>redis</category>
      <category>rust</category>
      <category>news</category>
      <category>data</category>
    </item>
    <item>
      <title>Adding Redis Streams to Rust 💾 🦀</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Wed, 03 Jun 2020 10:28:38 +0000</pubDate>
      <link>https://forem.com/terkwood/adding-redis-streams-to-rust-464j</link>
      <guid>https://forem.com/terkwood/adding-redis-streams-to-rust-464j</guid>
      <description>&lt;p&gt;I recently &lt;a href="https://github.com/mitsuhiko/redis-rs/pull/319"&gt;worked on a pull request&lt;/a&gt; which adds &lt;a href="https://redis.io/topics/streams-intro"&gt;Redis Streams&lt;/a&gt; capabilities to &lt;a href="https://github.com/mitsuhiko/redis-rs"&gt;redis-rs&lt;/a&gt;, the most popular Redis client in Rust's ecosystem.  The overwhelming majority of the effort was &lt;a href="https://github.com/grippy/redis-streams-rs"&gt;contributed by the community&lt;/a&gt;, not by me: I drafted the pull request which combines the two existing works.  In addition to addressing review comments, I added a few examples of how the new API works.&lt;/p&gt;

&lt;p&gt;I'm feeling the hype! 🔥  What is Redis Streams? Why do we need it in Rust?  Does it have anything to do with Kafka Streams?  And can we share any real-life examples?&lt;/p&gt;

&lt;p&gt;Please read on...&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Redis Streams? 🤔
&lt;/h2&gt;

&lt;p&gt;To give a little bit of background, Redis Streams was released as part of Redis 5.0, all the way back &lt;a href="https://redislabs.com/blog/redis-5-0-is-here/"&gt;in Oct 2018&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://antirez.com/news/128"&gt;antirez posts a great explanation of the new features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can think of Redis Streams data as a way to communicate &lt;em&gt;time-indexed&lt;/em&gt; data among processes.  Oversimplifying this, you can imagine that each stream is a CSV with a timestamp.  Digging deeper, Redis Streams exposes functionality that's more advanced than basic pub/sub, as you can have multiple consumers process a single stream:  even if one consumer happens to be faster than the others, Redis will rationally distribute the data.  &lt;/p&gt;

&lt;p&gt;Unlike Redis's pub/sub mechanism, Redis Streams are somewhat durable.  If you miss a message, it's still available in the stream, with limits; a stream will generally have a cap on the amount of messages allowed to accumulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Rust Need Redis Streams? 🔎
&lt;/h2&gt;

&lt;p&gt;The Streams commands are over a year old, so there was &lt;a href="https://github.com/mitsuhiko/redis-rs/issues/162#issuecomment-627459529"&gt;some support expressed&lt;/a&gt; for including them in redis-rs.  &lt;/p&gt;

&lt;p&gt;It's great that the community came together and created a &lt;a href="https://github.com/grippy/redis-streams-rs"&gt;separate lib&lt;/a&gt; exposing the Redis Streams API.  But Redis Streams is a first-class citizen of the larger Redis API, so it makes sense to include it as part of the leading Redis crate.&lt;/p&gt;

&lt;p&gt;We've exposed the new Redis Streams commands as an opt-out feature in redis-rs. If you want to reduce your compile time 🐌 🦀, you can explicitly disable streams support. This is the same as how geospatial operators work in redis-rs, so it should be a familiar concept for developers who have experience with the lib and who want to try out Streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Off-the-Cuff Comparison with Kafka Streams 🌽
&lt;/h2&gt;

&lt;p&gt;After looking into Redis Streams on my own, I did some initial comparisions with Kafka Streams and came to some conclusions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis Streams is a subset of the Redis server API.  The stream commands are exposed by the basic Redis server, not through separate lib.  On the other hand, Kafka Streams is a combination of the server (Kafka) combined with a JVM-based framework/lib (Kafka Streams).&lt;/li&gt;
&lt;li&gt;Kafka Streams apps coordinate data partitioning on the client side, while in Redis Streams, the server decides which consumer group gets which slice of data.  This helps Kafka achieve extraordinary scale: clients can work together to distribute load without the server becoming a bottleneck.&lt;/li&gt;
&lt;li&gt;Naive Kafka Streams apps consume a LOT of RAM, so if you're stingy about that sort of thing (and don't plan on scaling to infinity), Redis Streams can be a great fit.&lt;/li&gt;
&lt;li&gt;The data types exposed by Redis Streams are somewhat deeply nested, and can take a little while to get used to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite the similar naming conventions, the use cases for Kafka Streams and Redis Streams don't overlap as much as you might think.  Redis works great when you have one or two boxes (potentially with an enormous amount of RAM).  Kafka is built for massive, horizontal scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Comparison 🔧
&lt;/h2&gt;

&lt;p&gt;Out of sheer, nerdtastic enthusiasm, I had already written several Kafka Streams applications to process game states and various player-coordination functions for BUGOUT, our implementation of the ancient board game Go (Baduk). I went ahead and rewrote some of this functionality using rust and Redis Streams.  You can see some direct comparisons of how one might structure a Redis Streams app versus a Kafka Streams app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;judging moves in &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/micro-judge"&gt;Rust/Redis Streams&lt;/a&gt; vs &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/judge"&gt;Kafka Streams&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;assigning players to game instances in &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/micro-game-lobby"&gt;Rust/Redis Streams&lt;/a&gt; vs &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/game-lobby"&gt;Kafka Streams&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The declarative style of Kafka Streams apps really stood out as an advantage:  we could just focus on the logic required by our game system, and didn't have to write quite as much boilerplate.&lt;/p&gt;

&lt;p&gt;But our Redis Streams apps written in rust were &lt;em&gt;minuscule&lt;/em&gt; in terms of their memory consumption: in a cold system, just after startup, the micro-judge and micro-game-lobby apps take up about 1MB of RAM, while the Kafka Streams apps ⚠️ usually initialize at 100MB+ ⚠️.  Meanwhile, the Redis server itself continues to cruise along with a similarly small main-memory footprint (~3MB in our low-traffic system).&lt;/p&gt;

&lt;p&gt;Note that these Redis examples don't actually use the nicer API that's discussed as the main focus of this article -- generally we're using the lowest-level interface which specifies Redis commands using strings.  We'll work on upgrading these files once our &lt;a href="https://github.com/mitsuhiko/redis-rs/pull/319"&gt;pull request&lt;/a&gt; is merged! &lt;/p&gt;

&lt;h2&gt;
  
  
  Look Ma, I'm Learning! 🧠
&lt;/h2&gt;

&lt;p&gt;As a result of working the merge, I improved my understanding of the concepts underlying Redis Streams.&lt;/p&gt;

&lt;p&gt;Creating an example of &lt;code&gt;XREADGROUP&lt;/code&gt; command patterns gave me an immediate insight into a &lt;a href="https://github.com/Terkwood/BUGOUT/issues/310"&gt;shortcoming of my board game project&lt;/a&gt;:  I was maintaining boilerplate code, tracking the time IDs processed in a given stream. This code can be destroyed once I switch from naive &lt;code&gt;XREAD&lt;/code&gt; to Redis-controlled &lt;code&gt;XREADGROUP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using the "&amp;gt;" operator in an &lt;code&gt;XREADGROUP&lt;/code&gt; command tells Redis, "hey, give me only the newest records... and YOU keep track of where I am in the stream!"   This functionality, combined with the automatic &lt;code&gt;XACK&lt;/code&gt; provided in in the new additions to redis-rs, makes for a nice combination.&lt;/p&gt;

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

&lt;p&gt;If you're excited about seeing the Redis Streams support finally make it into the Rust ecosystem in a nice way, please &lt;a href="https://github.com/mitsuhiko/redis-rs/pull/319"&gt;dust the PR with emojis&lt;/a&gt;, or better yet, with critical review. 🔬&lt;/p&gt;

&lt;p&gt;If you're using Redis Streams in your own work, we'd love to hear from you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attribution
&lt;/h3&gt;

&lt;p&gt;Thank you to &lt;a href="https://www.flickr.com/photos/audreyjm529/"&gt;Audrey&lt;/a&gt; for the &lt;a href="https://www.flickr.com/photos/98799884@N00/235458062"&gt;image used in the header&lt;/a&gt;.  It is licensed under &lt;a href="https://creativecommons.org/licenses/by/2.0/"&gt;CC by 2.0&lt;/a&gt;. I cropped the image in order to make it fit a bit better as a header.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>rust</category>
      <category>distributedsystems</category>
      <category>data</category>
    </item>
    <item>
      <title>Privileged mode: Running ctop in docker under SELinux</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Wed, 13 May 2020 13:04:52 +0000</pubDate>
      <link>https://forem.com/terkwood/privileged-mode-running-ctop-in-docker-under-selinux-3ldc</link>
      <guid>https://forem.com/terkwood/privileged-mode-running-ctop-in-docker-under-selinux-3ldc</guid>
      <description>&lt;p&gt;Some programs like &lt;code&gt;ctop&lt;/code&gt; are nice to run using docker containers, so that you don't have to manually download a binary and copy it into &lt;code&gt;/usr/local/bin&lt;/code&gt;, where it will sit in a sad little corner, unmanaged by &lt;code&gt;apt&lt;/code&gt; or &lt;code&gt;yum&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/bcicen"&gt;
        bcicen
      &lt;/a&gt; / &lt;a href="https://github.com/bcicen/ctop"&gt;
        ctop
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Top-like interface for container metrics
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/bcicen/ctop/_docs/img/logo.png"&gt;&lt;img width="200px" src="https://res.cloudinary.com/practicaldev/image/fetch/s--rwsLBPiZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/bcicen/ctop/_docs/img/logo.png" alt="ctop"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/86468d6e7e6cceb270d42752c53aabaf07ec6bbbd335bd770e8e17e1f728514a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f62636963656e2f63746f702e737667"&gt;&lt;img src="https://camo.githubusercontent.com/86468d6e7e6cceb270d42752c53aabaf07ec6bbbd335bd770e8e17e1f728514a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f62636963656e2f63746f702e737667" alt="release" title="ctop"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/733376c7232044e8557b326e9f3e57b2b389f4dcd15ad53c706a79b095ba29b4/68747470733a2f2f696d672e736869656c64732e696f2f686f6d65627265772f762f63746f702e737667"&gt;&lt;img src="https://camo.githubusercontent.com/733376c7232044e8557b326e9f3e57b2b389f4dcd15ad53c706a79b095ba29b4/68747470733a2f2f696d672e736869656c64732e696f2f686f6d65627265772f762f63746f702e737667" alt="homebrew" title="ctop"&gt;&lt;/a&gt; &lt;a rel="noopener noreferrer" href="https://camo.githubusercontent.com/7c4e54e2861225b414e9c72c754f5dcabbdaeb8562540602f6ea4dfbd13444cd/68747470733a2f2f7265706f6c6f67792e6f72672f62616467652f76657273696f6e2d666f722d7265706f2f6d6163706f7274732f63746f702e7376673f6865616465723d6d6163706f727473"&gt;&lt;img src="https://camo.githubusercontent.com/7c4e54e2861225b414e9c72c754f5dcabbdaeb8562540602f6ea4dfbd13444cd/68747470733a2f2f7265706f6c6f67792e6f72672f62616467652f76657273696f6e2d666f722d7265706f2f6d6163706f7274732f63746f702e7376673f6865616465723d6d6163706f727473" alt="macports" title="ctop"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Top-like interface for container metrics&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ctop&lt;/code&gt; provides a concise and condensed overview of real-time metrics for multiple containers:&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/bcicen/ctop_docs/img/grid.gif"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vO4OrhGp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/bcicen/ctop_docs/img/grid.gif" alt="ctop"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;as well as a &lt;a href="https://github.com/bcicen/ctop_docs/single.md"&gt;single container view&lt;/a&gt; for inspecting a specific container.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ctop&lt;/code&gt; comes with built-in support for Docker and runC; connectors for other container and cluster systems are planned for future releases.&lt;/p&gt;
&lt;h2&gt;
Install&lt;/h2&gt;
&lt;p&gt;Fetch the &lt;a href="https://github.com/bcicen/ctop/releases"&gt;latest release&lt;/a&gt; for your platform:&lt;/p&gt;
&lt;h4&gt;
Debian/Ubuntu&lt;/h4&gt;
&lt;p&gt;Maintained by a &lt;a href="https://packages.azlux.fr/" rel="nofollow"&gt;third party&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;echo&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;deb http://packages.azlux.fr/debian/ buster main&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;|&lt;/span&gt; sudo tee /etc/apt/sources.list.d/azlux.list
wget -qO - https://azlux.fr/repo.gpg.key &lt;span class="pl-k"&gt;|&lt;/span&gt; sudo apt-key add -
sudo apt update
sudo apt install docker-ctop&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
Arch&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;ctop&lt;/code&gt; is available for Arch in the &lt;a href="https://aur.archlinux.org/packages/ctop-bin/" rel="nofollow"&gt;AUR&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
Linux (Generic)&lt;/h4&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;sudo wget https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-linux-amd64 -O /usr/local/bin/ctop
sudo chmod +x /usr/local/bin/ctop&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
OS X&lt;/h4&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;brew install ctop&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;sudo port install ctop&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;sudo curl -Lo /usr/local/bin/ctop https://github.com/bcicen/ctop/releases/download/v0.7.7/ctop-0.7.7-darwin-amd64
sudo chmod +x /usr/local/bin/ctop&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
Docker&lt;/h4&gt;
&lt;div class="highlight highlight-source-shell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;docker run --rm -ti \
  --name=ctop \
  --volume /var/run/docker.sock:/var/run/docker.sock:ro \
  quay.io/vektorlab/ctop:latest&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
Building&lt;/h2&gt;
&lt;p&gt;Build steps can be found…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/bcicen/ctop"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;But if you run an SELinux-enabled distribution, you'll find that running &lt;code&gt;ctop&lt;/code&gt; &lt;a href="https://github.com/bcicen/ctop#docker"&gt;as the documentation suggests&lt;/a&gt;, fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ctop &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock:ro &lt;span class="se"&gt;\&lt;/span&gt;
  quay.io/vektorlab/ctop:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🐳 🐳 🐳&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ctop - error ───────
  │                                                                                 │
  │  [12:54:15 UTC] attempting to reconnect...                                      │
  │                                                                                 │
  │  [12:54:16 UTC] Get http://unix.sock/info: dial unix /var/run/docker.sock: con  │
  │  nect: permission denied                                               
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's going on here?  Presumably SELinux is blocking the &lt;code&gt;ctop&lt;/code&gt; container's access to information necessary for monitoring the other containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Luckily, there's a very easy fix for this!  You can just run the &lt;code&gt;ctop&lt;/code&gt; container in privileged mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--privileged&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt;  &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ctop   &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume&lt;/span&gt; /var/run/docker.sock:/var/run/docker.sock:ro   quay.io/vektorlab/ctop:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can see all your favorite containers, interact with their log interfaces, dip into their shells, etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ctop - 12:57:42 UTC   8 containers

     NAME        CID         CPU         MEM         NET RX/TX   IO R/W      PIDS

   ◉  bugout_bot… 8f84fe3983…      1%       2M / 944M 19M / 16M   1M / 0B     6
   ◉  bugout_bug… 16f1479be4…      0%       3M / 944M 1M / 1M     6M / 0B     5
   ◉  bugout_gat… fc951914df…      0%       3M / 944M 56M / 35M   256K / 0B   21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>docker</category>
      <category>monitoring</category>
      <category>commandline</category>
    </item>
    <item>
      <title>Easy scrolling with tmux and alacritty</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Wed, 29 Apr 2020 17:05:34 +0000</pubDate>
      <link>https://forem.com/terkwood/fast-scrolling-with-tmux-and-alacritty-3dmn</link>
      <guid>https://forem.com/terkwood/fast-scrolling-with-tmux-and-alacritty-3dmn</guid>
      <description>&lt;p&gt;We want to have a nice experience when scrolling the terminal buffer using both alacritty and tmux on Debian linux.&lt;/p&gt;

&lt;p&gt;When we run alacritty and tmux together and there's a lot of text on the screen (say, by invoking &lt;code&gt;tree&lt;/code&gt;), we need to hit the following tmux key sequence to scroll back through the terminal history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ctrl+B 
[
PageUp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is difficult.  Others have &lt;a href="https://github.com/alacritty/alacritty/issues/1194"&gt;asked about this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We followed the workaround advice given by the author.  We enabled mouse mode, and disabled faux scrolling in &lt;code&gt;.tmux.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set -g mouse on
set -ga terminal-overrides ',*256color*:smcup@:rmcup@'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;.alacritty.conf&lt;/code&gt;, the default scrolling key combination, &lt;code&gt;Shift+PageUp&lt;/code&gt;, isn't too bad.  We can just leave it commented to use it, but if we want to try our own settings, we can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key_bindings:
#
# ...snip...
#
- { key: PageUp,   mods: Shift, action: ScrollPageUp,   mode: ~Alt       }
- { key: PageDown, mods: Shift, action: ScrollPageDown, mode: ~Alt       }
- { key: Home,     mods: Shift, action: ScrollToTop,    mode: ~Alt       }
- { key: End,      mods: Shift, action: ScrollToBottom, mode: ~Alt       }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resolution
&lt;/h2&gt;

&lt;p&gt;With the changes to &lt;code&gt;.tmux.conf&lt;/code&gt;, we can now start an alacritty terminal, enter tmux, and scroll to our hearts' content, without using a difficult key combination.&lt;/p&gt;

&lt;p&gt;🔥 It's FAST! 🔥&lt;/p&gt;

&lt;p&gt;We found that scrolling through history with some basic command line apps wasn't negatively impacted. &lt;/p&gt;

&lt;p&gt;When we entered tmux + alacritty, and then dumped a lot of text into &lt;code&gt;less&lt;/code&gt;, or navigated &lt;code&gt;man bash&lt;/code&gt;, we scrolled happily and without interruption.&lt;/p&gt;

&lt;p&gt;These instructions probably apply to other terminal emulators, as well.&lt;/p&gt;

&lt;p&gt;This is a happy time. 😄&lt;/p&gt;

</description>
      <category>linux</category>
      <category>commandline</category>
      <category>productivity</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Use Keyboard Shortcut to Focus alacritty in Gnome</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Sun, 26 Apr 2020 17:48:24 +0000</pubDate>
      <link>https://forem.com/terkwood/use-keyboard-shortcut-to-focus-alacritty-in-gnome-4cb6</link>
      <guid>https://forem.com/terkwood/use-keyboard-shortcut-to-focus-alacritty-in-gnome-4cb6</guid>
      <description>&lt;h1&gt;
  
  
  Use Keyboard Shortcut to Focus alacritty in Gnome
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/alacritty/alacritty"&gt;alacritty&lt;/a&gt; is a 🔥 hot, fast terminal emulator 🔥 written in rust.  We've been using it for the last 38 microseconds and really enjoy it. It pairs nicely with &lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We want to hit a single key and focus on &lt;code&gt;alacritty&lt;/code&gt; while using GNOME.  If &lt;code&gt;alacritty&lt;/code&gt; isn't already open, it should start up.  This would give us functionality somewhat equivalent to &lt;a href="http://guake-project.org/"&gt;Guake&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a bonus, we'll use &lt;a href="https://deno.land/"&gt;deno&lt;/a&gt; to write a quick helper script and 😇 avoid learning &lt;code&gt;bash&lt;/code&gt;. 😇&lt;/p&gt;

&lt;p&gt;Here's what we did to accomplish this task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install wmctrl
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;wmctrl&lt;/code&gt; so that we can focus on an existing &lt;code&gt;alacritty&lt;/code&gt; window using a program.&lt;/p&gt;

&lt;p&gt;For debian &amp;amp; ubuntu-flavored linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;wmctrl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the redhats:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;wmctrl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start &lt;code&gt;alacritty&lt;/code&gt; up for just a moment and take note of its handle as given by &lt;code&gt;wmctrl -xl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...snip...
0x04800002  1 Alacritty.Alacritty   mybox Alacritty
...snip...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use &lt;code&gt;wmctrl&lt;/code&gt; to focus on a running instance of &lt;code&gt;alacritty&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wmctrl &lt;span class="nt"&gt;-xa&lt;/span&gt; Alacritty.Alacritty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create script to raise or start-up terminal
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;deno&lt;/code&gt; script to either focus on the existing &lt;code&gt;alacritty&lt;/code&gt; window, or start a new one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// saved to /home/nope/bin/raise_alacritty.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/usr/bin/pgrep&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alacritty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wmctrl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-xa&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alacritty.Alacritty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alacritty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bind keyboard shortcut
&lt;/h2&gt;

&lt;p&gt;Finally, in gnome settings,&lt;br&gt;
add a keyboard shortcut which runs our script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FfdCn73C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/81819687-a3904800-94fd-11ea-8f4e-d66c07d600ad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FfdCn73C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/81819687-a3904800-94fd-11ea-8f4e-d66c07d600ad.png" alt="keybinding" width="880" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, we assigned the special MENU button on our keyboard to focus on &lt;code&gt;alacritty&lt;/code&gt;.  We use the default GNOME shortcut (&lt;code&gt;Super H&lt;/code&gt;/&lt;code&gt;WindowsKey H&lt;/code&gt;) to hide the window when we're done with it. &lt;/p&gt;
&lt;h2&gt;
  
  
  Updated for deno 1.0.0-rc3
&lt;/h2&gt;

&lt;p&gt;Please note that as of deno 1.0.0-rc3, the command line invocation needed to make this work has changed. You now need to use the &lt;code&gt;run&lt;/code&gt; subcommand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno run &lt;span class="nt"&gt;--allow-run&lt;/span&gt; /path/to/raise_alacritty.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article was originally published under an older version, which did not require the subcommand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno  &lt;span class="nt"&gt;--allow-run&lt;/span&gt; /path/to/raise_alacritty.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're having trouble getting this to work under the new version of Deno, try adding &lt;code&gt;run&lt;/code&gt;!&lt;/p&gt;

</description>
      <category>commandline</category>
      <category>deno</category>
      <category>linux</category>
      <category>productivity</category>
    </item>
    <item>
      <title>BUGOUT: Play Go against AI 💪 and Human Friends 👪</title>
      <dc:creator>Felix Terkhorn</dc:creator>
      <pubDate>Mon, 30 Mar 2020 23:31:33 +0000</pubDate>
      <link>https://forem.com/terkwood/bugout-play-go-against-ai-and-human-friends-43a1</link>
      <guid>https://forem.com/terkwood/bugout-play-go-against-ai-and-human-friends-43a1</guid>
      <description>&lt;p&gt;BUGOUT provides a web interface to &lt;a href="https://github.com/lightvector/KataGo"&gt;KataGo&lt;/a&gt;, a leading, community-driven AI skilled in the &lt;a href="https://en.wikipedia.org/wiki/Go_(game)"&gt;classic board game, Go&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;BUGOUT also features a multiplayer option for playing against human friends, if you're lucky enough to have them.&lt;/p&gt;

&lt;p&gt;BUGOUT's web interface is a &lt;a href="https://github.com/Terkwood/Sabaki"&gt;derivative&lt;/a&gt; of &lt;a href="https://github.com/SabakiHQ/Sabaki"&gt;Sabaki&lt;/a&gt;, the much-loved desktop app which is already capable of hooking into KataGo, &lt;a href="https://zero.sjeng.org/home"&gt;Leela Zero&lt;/a&gt;, and &lt;a href="https://www.gnu.org/software/gnugo/"&gt;GNU Go&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code for the Project
&lt;/h2&gt;

&lt;p&gt;The project is open-sourced, currently under MIT license. &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Terkwood"&gt;
        Terkwood
      &lt;/a&gt; / &lt;a href="https://github.com/Terkwood/BUGOUT"&gt;
        BUGOUT
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI-driven, Multiplayer Go/Weiqi/Baduk for the web 🐛🤖🦀♟
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This write-up was originally posted at &lt;a href="https://terkwood.farm/tech/BUGOUT/index.html"&gt;terkwood.farm&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Emotional Backdrop
&lt;/h1&gt;

&lt;p&gt;I started playing GO sometime during high school.  Several of my friends also learned to play.  We would often play chess, for which I had a neat little carrying case with a simple game clock, a rollout board, and sturdy pieces.  I came into a possession of a GO board of standard dimensions, and used plastic pieces that were just heavy enough to feel like they mattered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AgI7JAu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77853799-16469d80-71b4-11ea-8f14-8ae52b84b81b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AgI7JAu8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77853799-16469d80-71b4-11ea-8f14-8ae52b84b81b.jpeg" alt="That Good, Old Board" width="800" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We would sometimes meet at a Chinese dive on West 10th Street in Indianapolis, consume delicious, sugary food, and play.&lt;/p&gt;

&lt;p&gt;GO felt different than chess.  There was a sense of immense possibility.  There was time to think about the future.  Winning was hard.  Getting your skill up with a same-stage newbie was exhilarating, because you grew together.&lt;/p&gt;

&lt;p&gt;Eventually I got my board signed by &lt;a href="https://www.victorwooten.com/"&gt;VICTOR WOOTEN&lt;/a&gt; while attending a concert performance given by &lt;a href="https://www.flecktones.com/"&gt;Béla Fleck and the Flecktones&lt;/a&gt; at the &lt;a href="https://www.indianaroof.com/"&gt;Indiana Roof Ballroom&lt;/a&gt;:  we had been playing some small rounds prior to the start of the concert.&lt;/p&gt;

&lt;p&gt;It must have been... 1999?  Victor Wooten was super gracious, and inspired everyone to a wholesome life of technical mastery.  I was young.  I wore an undersized fedora.&lt;/p&gt;

&lt;p&gt;The music was phenomenal, and Wooten's signature fueled my fire.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V5veLJ7h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77853798-15157080-71b4-11ea-9a92-0fba25eb780d.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V5veLJ7h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77853798-15157080-71b4-11ea-9a92-0fba25eb780d.jpeg" alt="That Good Wooten" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In college I dreamt that one day I would wake to the sound of an obstreperous trumpet, having been mired in games and strenuous efforts to build some sort of ultimate strategy.  I dreamt that I would wake to a vision of clear dawn.&lt;/p&gt;

&lt;p&gt;But the actual time I've spent playing GO in my life is tiny.  Life goes on, and its daily demands obscure our roots.&lt;/p&gt;

&lt;p&gt;Still, the aesthetic delight of GO has been enjoyed by millions of individuals over the centuries, and it's this very beauty which has inspired my work over the last nine months.&lt;/p&gt;

&lt;p&gt;GO is a game which helps me combat the lassitude of experience and boredom.&lt;/p&gt;

&lt;p&gt;GO is not a game which must be won.  It's enough to face a player whose skill exceeds yours, and to learn.  When I play 9x9 against KataGo running on a 5W computer, I'm happy if I manage not to lose an embarrassing number of pieces.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GO keeps me sharp&lt;/em&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building the Project
&lt;/h1&gt;

&lt;p&gt;BUGOUT started out as an excuse to implement something (anything) using &lt;a href="https://kafka.apache.org/"&gt;Kafka&lt;/a&gt;.  My previous two professional engagements had barely skirted opportunities to legitimately include a Kafka install:  one was simple enough to rely on HTTP microservices, &lt;a href="https://github.com/WW-Digital/reactive-kinesis"&gt;another had opted for Kinesis&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Fresh into successful semi-retirement, I didn't want to bother with finding a use case for my implementation.  I wanted what I couldn't have reasonably argued for in past, commercial settings.  I wanted to play with &lt;a href="https://kafka.apache.org/documentation/streams/"&gt;Kafka Streams&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;It turns out that mapping the &lt;a href="https://github.com/Terkwood/BUGOUT/blob/unstable/judge/src/main/kotlin/Model.kt"&gt;tiny domain model&lt;/a&gt; of a GO game to kafka streams is... easy.  Initially BUGOUT was envisioned as a multiplayer GO board which could easily run in any modern browser and provide an enjoyable, boutique alternative to more popular servers and venues.  I wanted to play again with my old friends, just like when we were kids.  So the project &lt;a href="https://github.com/Terkwood/BUGOUT/issues/42"&gt;quickly incorporated a game lobby system&lt;/a&gt; which allowed players to choose between joining a quick 19x19 game with the next person to visit the web page, or creating a nicely-formatted URL (&lt;code&gt;https://example.com/?join=nXblGBE7erWyocXYYpRN1YOzdD&lt;/code&gt;) for their private game and sharing it with a friend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uoxfIUS6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851229-e42e3f00-71a5-11ea-8a91-93da91abf87a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uoxfIUS6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851229-e42e3f00-71a5-11ea-8a91-93da91abf87a.png" alt="Choosing a Venue" width="880" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lwo-7j5l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851228-e2647b80-71a5-11ea-9467-cf086d76fb8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lwo-7j5l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851228-e2647b80-71a5-11ea-9467-cf086d76fb8e.png" alt="Board Size Options" width="880" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H2AJpeQ2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851227-e1cbe500-71a5-11ea-8faa-264996a2257a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H2AJpeQ2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851227-e1cbe500-71a5-11ea-8faa-264996a2257a.png" alt="Turn Order Preference" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iRXdCSjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851226-e1334e80-71a5-11ea-834f-0c76a0e35080.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iRXdCSjf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851226-e1334e80-71a5-11ea-834f-0c76a0e35080.png" alt="Sharing a Link to a Game" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Od0jjWdk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851225-ded0f480-71a5-11ea-92a6-bb23755df74c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Od0jjWdk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851225-ded0f480-71a5-11ea-92a6-bb23755df74c.png" alt="Joining a Link to a Game" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, both players can be fairly assigned a side, and the game can begin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cVvPYVYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851236-e7c1c600-71a5-11ea-94a3-750bbeab74df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cVvPYVYL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851236-e7c1c600-71a5-11ea-94a3-750bbeab74df.png" alt="Your Color" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MGbIK6nu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851232-e55f6c00-71a5-11ea-827e-f0201c6d9f51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MGbIK6nu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77851232-e55f6c00-71a5-11ea-827e-f0201c6d9f51.png" alt="Finally, A Game" width="880" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It basically worked.  I spent time &lt;a href="https://github.com/Terkwood/BUGOUT/issues/48"&gt;making sure the websocket connection between the browser and the BUGOUT gateway server was solid&lt;/a&gt;.  I had to &lt;a href="https://github.com/Terkwood/BUGOUT/blob/dfb4a63be1b052bdb8dc448993d2feecfd91ce74/game-lobby/src/main/kotlin/Application.kt#L531"&gt;guarantee that Kafka and all its dependent apps started up in an orderly fashion&lt;/a&gt;.  And I enjoyed writing &lt;a href="https://github.com/Terkwood/BUGOUT/blob/dfb4a63be1b052bdb8dc448993d2feecfd91ce74/game-lobby/src/main/kotlin/Application.kt#L37"&gt;functional-ish Kafka Streams code in Kotlin&lt;/a&gt;:  from a cognitive perspective, it felt clean and tidy, even if the topology graphs quickly got out of hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hMcGcEFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Terkwood/BUGOUT/unstable/game-lobby/topo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hMcGcEFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Terkwood/BUGOUT/unstable/game-lobby/topo.jpg" alt="Game Lobby Message Processing: This is Getting Insane" width="880" height="1679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Too Cheap for Pro Tier
&lt;/h1&gt;

&lt;p&gt;It's pretty obvious to anyone that spends 30 minutes in the Kafka literature, that a production deployment of Kafka involves at least three machines.  I was running a single node, because my average user load was zero.  What's more, I was paying for AWS compute time using a personal credit card, so I've never been exactly fired up about running an individual host that comes anywhere near &lt;a href="https://docs.confluent.io/current/kafka/deployment.html"&gt;the specs recommended by the vendor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some additional reading turned up that &lt;a href="https://www.infoq.com/articles/apache-kafka-best-practices-to-optimize-your-deployment/"&gt;6GB of RAM is a reasonable minimum&lt;/a&gt; for any single host:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In most cases, Kafka can run optimally with 6 GB of RAM for heap space. For especially heavy production loads, use machines with 32 GB or more. Extra RAM will be used to bolster OS page cache and improve client throughput. While Kafka can run with less RAM, its ability to handle load is hampered when less memory is available.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ever the miser, I settled on running a &lt;code&gt;t3.medium&lt;/code&gt; compute host for my degenerate Kafka cluster with its single, lonely node.  4GB of RAM certainly worked well enough for my theoretically scalable backend -- at least as long as it didn't become popular.&lt;/p&gt;

&lt;p&gt;Anyway, who cares about vendor reccomended minima or a realistic-looking deployment?  I was liberated from corporate hierarchy, profit motive, etc, and was ready to experience the raw freedom that I'd always dreamed about as a junior developer.  I was ready to &lt;em&gt;play with toys&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The only problem was that running even a &lt;code&gt;t3.medium&lt;/code&gt; around the clock &lt;a href="https://www.ec2instances.info/?filter=t3&amp;amp;cost_duration=daily"&gt;cost about $1/day&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;🤑 That's expensive! 🤑&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning Kafka On and Off with Rust + AWS
&lt;/h2&gt;

&lt;p&gt;The windmills were ready to tilt.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Terkwood/BUGOUT/issues/75"&gt;I spent a non-trivial amount of effort&lt;/a&gt; using &lt;a href="https://github.com/rusoto/rusoto"&gt;rusoto&lt;/a&gt; to control the startup and shutdown of my "very expensive" &lt;code&gt;t3.medium&lt;/code&gt; instance.  Whenever the system detects that there are no users connected, a timer starts, and after a few minutes, &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/reaper"&gt;it shuts down&lt;/a&gt; the &lt;code&gt;t3.medium&lt;/code&gt; running Kafka, and all of the memory-hogging Kafka Streams apps supporting gameplay.&lt;/p&gt;

&lt;p&gt;Whenever someone comes back to the website and wants to play a game, the system loyally &lt;a href="https://github.com/Terkwood/BUGOUT/tree/unstable/bugle"&gt;boots the Kafka host&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's great.  I can still afford to eat.  Working with rusoto was painless.  But the user experience of waiting for BUGOUT's Kafka backend to initialize is... abysmal.  It takes about 90 seconds for the EC2 instance and its various docker containers to start up.  That's 81 seconds longer than eternity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting on the Micro-Stack
&lt;/h2&gt;

&lt;p&gt;Enough was enough.  I had no boss.  I had no users.  I had no chains.  My self-respect was questionable.&lt;/p&gt;

&lt;p&gt;I would re-implement the backend &lt;a href="https://dev.to/pdambrauskas/event-sourcing-with-redis-45ha"&gt;with redis streams&lt;/a&gt;, so that my game could be online 24/7, using leftover CPU and RAM on the &lt;code&gt;t3.micro&lt;/code&gt; instance that I was already paying for.&lt;/p&gt;

&lt;p&gt;Why not?  Redis is awesome!  And by choosing &lt;code&gt;rust&lt;/code&gt; for this portion of the impl, I knew I could keep my memory and CPU footprint low.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Terkwood/BUGOUT/issues/174"&gt;The effort is ongoing&lt;/a&gt;. It will eventually be completed in the way that one completes a crocheted scarf.&lt;/p&gt;

&lt;h1&gt;
  
  
  Welcome KataGo
&lt;/h1&gt;

&lt;p&gt;After months of relatively steady operation, I came to the sobering conclusion that most of my close friends were busy with their own lives, and playing a synchronous session of GO with them, often across international time zones, was an exercise in scheduling prowess that surpasses even the most skilled project manager.&lt;/p&gt;

&lt;p&gt;So I began to wonder about playing against an &lt;a href="https://en.wikipedia.org/wiki/AlphaZero"&gt;AlphaZero-like&lt;/a&gt; AI through my browser.&lt;/p&gt;

&lt;p&gt;If I can't play against my friends...&lt;/p&gt;

&lt;p&gt;...why not &lt;em&gt;BUILD MY OWN FRIEND?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Kata Meets Nano
&lt;/h2&gt;

&lt;p&gt;Thus was born the effort to integrate &lt;a href="https://github.com/lightvector/KataGo"&gt;KataGo&lt;/a&gt;, a community implementation of cutting edge Go AI that's gotten a nice little boost thanks to &lt;a href="https://blog.janestreet.com/accelerating-self-play-learning-in-go/"&gt;hardware sponsorship from Jane Street&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In fact, &lt;a href="https://online-go.com/player/592684/"&gt;you can already play against KataGo online&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's fine. I like building things on my own -- or in this case, integrating other people's things on my own.  So I purchased an &lt;a href="https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-nano-developer-kit/"&gt;NVIDIA Jetson Nano System-on-a-Chip&lt;/a&gt; and set about linking it to my existing infrastructure.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/Terkwood/BUGOUT/issues/67"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        🤖 Play against KataGo
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#67&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/Terkwood"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--t7HfC7-0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars0.githubusercontent.com/u/38859656%3Fv%3D4" alt="Terkwood avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/Terkwood"&gt;Terkwood&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/Terkwood/BUGOUT/issues/67"&gt;&lt;time&gt;Jul 17, 2019&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h1&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Goal&lt;/h1&gt;
&lt;p&gt;Implement a system which allows playing against a &lt;a href="https://github.com/lightvector/KataGo"&gt;KataGo&lt;/a&gt;  instance running on an NVIDIA Jetson Nano.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Background&lt;/h2&gt;
&lt;p&gt;We picked up an &lt;a href="https://developer.nvidia.com/embedded/jetson-nano-developer-kit" rel="nofollow"&gt;NVIDIA Jetson Nano&lt;/a&gt; board and built KataGo.  &lt;a href="https://github.com/Terkwood/BUGOUT/blob/unstable/tinybrain/README.md"&gt;Documentation is available in the repo now&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Tasks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;[x] build KataGo on the nano&lt;/li&gt;
&lt;li&gt;[x] wrap katago stdin/stdout #190&lt;/li&gt;
&lt;li&gt;[x] make a basic docker image #189&lt;/li&gt;
&lt;li&gt;[x]  gateway: backend routing (#205)&lt;/li&gt;
&lt;li&gt;[x] gateway: send &amp;amp; recv bot attachment and move-making messages&lt;/li&gt;
&lt;li&gt;[x] use websockets correctly #210&lt;/li&gt;
&lt;li&gt;[x] create botlink service and push game states via websocket (#193, #196, #197)&lt;/li&gt;
&lt;li&gt;[x] connect tinybrain to Botlink service via websocket and process game states (#194)&lt;/li&gt;
&lt;li&gt;[x] document reverse-proxy setup&lt;/li&gt;
&lt;li&gt;[x] clean up micro-changelog init (#187)&lt;/li&gt;
&lt;li&gt;[ ] extend Sabaki initial menu, send attach bot command, process makemoves as normal, &lt;code&gt;listenForMove&lt;/code&gt; when human plays white&lt;/li&gt;
&lt;li&gt;[x] fix parse error #220&lt;/li&gt;
&lt;li&gt;[x] fix all coords #223&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Architecture draft&lt;/h1&gt;
&lt;p&gt;Use &lt;a href="https://github.com/lightvector/KataGo/blob/master/docs/Analysis_Engine.md"&gt;KataGo analysis engine&lt;/a&gt; to respond to queries.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/38859656/76307598-83df6800-629f-11ea-9e42-3adc92538a94.jpeg" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UVa2EU81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/76307598-83df6800-629f-11ea-9e42-3adc92538a94.jpeg" alt="76154032-85444100-60a3-11ea-9606-910801c1036d"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Errata&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/lightvector/KataGo"&gt;https://github.com/lightvector/KataGo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/leela-zero/leela-zero"&gt;https://github.com/leela-zero/leela-zero&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.janestreet.com/accelerating-self-play-learning-in-go/" rel="nofollow"&gt;https://blog.janestreet.com/accelerating-self-play-learning-in-go/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lightvector/KataGo/blob/master/README.md#how-to-use"&gt;https://github.com/lightvector/KataGo/blob/master/README.md#how-to-use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/leela-zero/leela-zero/issues/2569#issue-547376644"&gt;https://github.com/leela-zero/leela-zero/issues/2569#issue-547376644&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;An existing service for playing against bots&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.zbaduk.com/tools" rel="nofollow"&gt;https://www.zbaduk.com/tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lightvector/KataGo/issues/120"&gt;and a related KataGo ticket&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Terkwood/BUGOUT/issues/67"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The effort proceeded smoothly.  Soon I was suffering regular blows to my pride, delivered easily by a 128-core GPU that can subside on a mere 5W of power.  NVIDIA's pre-installed CUDA libs made compilation of KataGo relatively easy.  Hooking up the tiny SoC in my office to my dirt-cheap AWS &lt;code&gt;t3.micro&lt;/code&gt; felt pleasantly conservative.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h2jhAMXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859879-e611f580-71d9-11ea-898a-e2f928605cac.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h2jhAMXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859879-e611f580-71d9-11ea-898a-e2f928605cac.jpeg" alt="Nvidia Jetson Nano Unboxed" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  A Happy Ending
&lt;/h1&gt;

&lt;p&gt;There isn't much work left before the AI subsystem is complete.  It's already possible to play against KataGo through the web interface, if you're patient enough to run a read-eval-print-loop and put Kata's pieces on the board manually.&lt;/p&gt;

&lt;p&gt;After just a few more hours of work, I'll be able to head to my website from anywhere in the world, and play a power-efficient little bot that's just right for my skill level.&lt;/p&gt;

&lt;p&gt;And then, free from the lamentable desire for human interaction, I'll play against my own, new type of friend:  a friend born of silicon of and parallel arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zVUo4OMr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859737-1a38e680-71d9-11ea-9a50-3ca25d7b9558.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zVUo4OMr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859737-1a38e680-71d9-11ea-9a50-3ca25d7b9558.png" alt="Love the Log" width="880" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RmcSyhh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859794-61bf7280-71d9-11ea-8f00-d9eef8bc437b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RmcSyhh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/38859656/77859794-61bf7280-71d9-11ea-8f00-d9eef8bc437b.png" alt="Losing peacefully to SkyNet" width="880" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>firstpost</category>
      <category>gamedev</category>
      <category>ai</category>
      <category>kafka</category>
    </item>
  </channel>
</rss>
