{"version":"https://jsonfeed.org/version/1","title":"rubenwardy's blog","description":"Welcome to my blog, where I write about my projects and various tech-related topics.","home_page_url":"https://blog.rubenwardy.com/","feed_url":"https://blog.rubenwardy.com/feed.json","icon":"https://blog.rubenwardy.com/static/rubenwardy.png","favicon":"https://blog.rubenwardy.com/favicon.ico","expired":false,"authors":[{"name":"rubenwardy","url":"https://rubenwardy.com/","avatar":"https://rubenwardy.com/static/ruben-512.png"}],"items":[{"id":"/2026/01/15/cookie-consent/","language":"en","title":"Would you like a cookie?","content_html":"<p>I’d like to talk about how cookie consent laws were damaging to consumer\nunderstanding of privacy. They demonised cookies - a simple way of storing\ninformation - when the real problem was the widespread, unregulated tracking and\nsharing of personal data. It focuses on a technology rather than a behaviour.\nI’d also like to talk about the GDPR and recent efforts to amend it.</p>\n<p>The right to privacy is an area I care about because I think that it is\nimportant for users to be able to understand and control how their data is used.\nI am not an expert in privacy or EU/UK law, so take this article with a grain of\nsalt. I’m also interested to hear from those with more experience.</p>\n<h2 id=\"cookie-consent\" tabindex=\"-1\">Cookie Consent <a class=\"header-anchor\" href=\"#cookie-consent\">#</a></h2>\n<h3 id=\"what-are-cookies-and-cookie-consent\" tabindex=\"-1\">What are cookies and cookie consent? <a class=\"header-anchor\" href=\"#what-are-cookies-and-cookie-consent\">#</a></h3>\n<p>A cookie is something that websites can use to store information to access\nlater. It’s often described as a small text file in your browser. There are many\nunobjectionable uses for cookies, including keeping the user logged in, storing\npreferences, and managing shopping carts. Cookies can also be used to store\ntracking identifiers and other information for targeted advertising and\nanalytics.</p>\n<p>Cookie consent is an EU law that requires website providers to inform and seek\nconsent from users before storing a cookie<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup>. There is an\nexception for “strictly necessary” cookies - websites are allowed to use cookies\nwithout consent for purposes which are strictly necessary to fulfil a user\nrequest. For example, this may include login and shopping cart cookies. Whilst\nit called “cookie” consent, it actually applies to any method of storing data on\nthe client device - including local storage and IndexedDB.</p>\n<h3 id=\"what-is-the-problem-with-cookie-consent\" tabindex=\"-1\">What is the problem with cookie consent? <a class=\"header-anchor\" href=\"#what-is-the-problem-with-cookie-consent\">#</a></h3>\n<p>The problem with cookie consent is that it focuses on a specific technology to\nstore data, rather than a behaviour by the service providers. If cookies were\nthe problem, then browsers could block cookies by default. Browsers could add a\npermission for storing data on the device; there would be no need for all these\nbanners.</p>\n<p>Furthermore, focusing on cookies is confusing for end users. You end up with\nwebsites showing cute messages like “🍪 Hello, would you like a cookie?” rather\nthan “Is it okay if we track everything you do on the Internet across many\ndifferent websites?.” Cookies sound friendly and sweet, after all, who doesn’t\nwant a cookie?</p>\n<p>Another problem is that service providers often err on the side of being very\nstrict on obtaining storage consent. For example, they may require you to opt in\nto cookies and tracking before you can change settings. Some don’t even remember\nyour cookie consent if you opt out, which is explicitly allowed by cookie\nconsent! This results in a detrimental user experience and privacy. In these\ncases, cookies are strictly necessary to fulfil an explicit user request.</p>\n<p>The <a href=\"https://www.politico.eu/article/brussels-knifes-privacy-to-feed-the-ai-boom-gdpr-digital-omnibus/\">EU is currently amending the GDPR through the Digital Omnibus</a>.\nCritics say this will weaken the GDPR after pressure from tech companies to make\nit easier to train Large Language Models (LLMs). However, I was interested to\nsee that this amendment appears to simplify ePrivacy Directive, which introduced\ncookie consent, so I am wondering how much it changes in relation to the\nproblems discussed above.</p>\n<h2 id=\"gdpr\" tabindex=\"-1\">GDPR <a class=\"header-anchor\" href=\"#gdpr\">#</a></h2>\n<h3 id=\"why-is-the-gdpr-better-than-cookie-consent\" tabindex=\"-1\">Why is the GDPR better than cookie consent? <a class=\"header-anchor\" href=\"#why-is-the-gdpr-better-than-cookie-consent\">#</a></h3>\n<p>GDPR was a big improvement by focusing more on the purposes of data usage and\naffirmative consent, rather than the technology being used to store\ndata<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\"><span class=\"sr-only\">footnote </span>2</a></sup>.</p>\n<h3 id=\"consent-or-pay\" tabindex=\"-1\">Consent or pay <a class=\"header-anchor\" href=\"#consent-or-pay\">#</a></h3>\n<p>The UK recently ruled to allow so-called “consent or pay”<sup class=\"footnote-ref\"><a href=\"#fn3\" id=\"fnref3\"><span class=\"sr-only\">footnote </span>3</a></sup>. The\nonly stipulation is the price needs to be sufficiently low - Meta changed from\n~£8 per month to £2.99/m<sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4\"><span class=\"sr-only\">footnote </span>4</a></sup><sup class=\"footnote-ref\"><a href=\"#fn5\" id=\"fnref5\"><span class=\"sr-only\">footnote </span>5</a></sup>. My first thought is that this feels\nabusive. Is it truly consent if the user needs to pay to object?</p>\n<p>Non-personalised advertising is significantly less profitable for the publisher.\nIn the EU, Meta solves this by showing non-skippable adverts for those that opt\nout of personalised advertising<sup class=\"footnote-ref\"><a href=\"#fn6\" id=\"fnref6\"><span class=\"sr-only\">footnote </span>6</a></sup>. I find this to be a much fairer\nsolution. Ultimately, it’s not the advertising that privacy advocates object to.\nIt’s the tracking and personalisation.</p>\n<p>I think that advertising provides perverse incentives for service providers. It\nis often user-hostile, resulting in software that is slower and has lower user\nsatisfaction. Advertising also results in a lot of corporate censorship and\nsanitisation, you end up with speak like “unalive” and “g@y” to get around\nshadow bans. With this in mind, is it consistent for me to be against consent or\npay? Is this the start of a post-advertising online industry?! Unlikely.</p>\n<h3 id=\"consent-banner-fatigue-and-do-not-track\" tabindex=\"-1\">Consent banner fatigue and Do Not Track <a class=\"header-anchor\" href=\"#consent-banner-fatigue-and-do-not-track\">#</a></h3>\n<p>I do think that GDPR banners are unnecessarily confusing at times, with a lot of\ntechnical/legalese-style language behind each option. There is also a certain\namount of fatigue when every website has a privacy banner. I suspect a vast\nmajority of users will just accept all or reject all, and won’t bother\nconfiguring further.</p>\n<p>“Do Not Track” was a browser feature that allowed users to tell websites that\nthey do not wish to be tracked. Due to a lack of regulation, this flag was not\nrespected by most service providers. It has been deprecated by the standards\nbody and removed in Firefox<sup class=\"footnote-ref\"><a href=\"#fn7\" id=\"fnref7\"><span class=\"sr-only\">footnote </span>7</a></sup>. Adding a similar feature for GDPR banners,\nwhich is mandated, might solve the fatigue problem.</p>\n<p><a href=\"https://globalprivacycontrol.org/\">Global Privacy Control</a> is a modern\nsuccessor to Do Not Track<sup class=\"footnote-ref\"><a href=\"#fn8\" id=\"fnref8\"><span class=\"sr-only\">footnote </span>8</a></sup>. It seems to work in much the same way, but has\na legal basis through California’s Consumer Privacy Act. This is supported by\nFirefox but not by Chrome. It’s also unclear the extent to which this is\nrespected by service providers outside of California.</p>\n<p>As part of the <a href=\"https://www.politico.eu/article/brussels-knifes-privacy-to-feed-the-ai-boom-gdpr-digital-omnibus/\">Digital Omnibus</a>,\nthe European Commission aims to solve the “consent fatigue and proliferation of\ncookies banners.”<sup class=\"footnote-ref\"><a href=\"#fn9\" id=\"fnref9\"><span class=\"sr-only\">footnote </span>9</a></sup> The changes will require controllers to ensure\ntheir consent management can be used through automated and machine-readable\nmeans, and for web browsers to provide the technical means for such automated\nindications of data subjects’ choices<sup class=\"footnote-ref\"><a href=\"#fn10\" id=\"fnref10\"><span class=\"sr-only\">footnote </span>10</a></sup>. I’m hoping this will provide\nthe legal basis for Global Privacy Control inside the EU. I’d like to see the\nability to automate consent more granularly. It remains to be seen how these\nmeasures will be implemented, but I am feeling positive about this aspect.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>This is not my area of expertise, but I hope you found this interesting\nnonetheless. I have linked to my sources if you’d like to read more. If I’ve\nmade any mistakes or you have related thoughts on this topic, please comment to\nlet me know.</p>\n<p><em>Cover image: hedgehog biscuits I made during lockdown in 2020.</em></p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><a href=\"https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/guide-to-pecr/cookies-and-similar-technologies/\">Cookies and similar technologies - ICO</a> <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p><a href=\"https://gdpr.eu/cookies/\">Cookies, the GDPR, and the ePrivacy Directive - GDPR.eu</a> <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p><a href=\"https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/online-tracking/consent-or-pay/about-this-guidance/#law\">Consent or pay - ICO</a> <a href=\"#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p><a href=\"https://www.politico.eu/article/meta-brings-optional-ad-free-pay-consent-model-uk-facebook-instagram/\">Meta brings optional ad-free ‘pay or consent’ model to the UK - Politico</a> <a href=\"#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p><a href=\"https://ico.org.uk/about-the-ico/media-centre/news-and-blogs/2025/09/ico-statement-on-changes-to-meta-advertising-model/\">ICO statement on changes to Meta advertising model - ICO</a> <a href=\"#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p><a href=\"https://eutechreg.com/p/why-is-meta-offering-cheaper-and\">Why is Meta offering cheaper and simpler “pay or consent” in the UK? - EUTechReg</a> <a href=\"#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p><a href=\"https://arstechnica.com/gadgets/2024/12/firefox-one-of-the-first-do-not-track-supporters-no-longer-offers-it/\">Back where it started: “Do Not Track” removed from Firefox after 13 years - Arstechnica</a> <a href=\"#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn8\" class=\"footnote-item\"><p><a href=\"https://arstechnica.com/tech-policy/2020/10/coming-to-a-browser-near-you-a-new-way-to-keep-sites-from-selling-your-data/\">Now you can enforce your privacy rights with a single browser tick - Arstechnica</a> <a href=\"#fnref8\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn9\" class=\"footnote-item\"><p><a href=\"https://www.europarl.europa.eu/RegData/docs_autres_institutions/commission_europeenne/com/2025/0837/COM_COM(2025)0837_EN.pdf\">Digital Omnibus - European Commission</a> <a href=\"#fnref9\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn10\" class=\"footnote-item\"><p><a href=\"https://www.osborneclarke.com/insights/digital-omnibus-reshapes-eu-cookie-rules-leaves-banner-fatigue-largely-intact\">Digital Omnibus reshapes EU cookie rules but leaves banner fatigue largely intact - Osbourne Clark</a> <a href=\"#fnref10\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2026/01/15/cookie-consent/","summary":"A discussion about how cookie consent demonises cookies, GDPR, and the digital omnibus.","date_published":"2026-01-15T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/V1aTtJS9tH-540.png","tags":["opinion"]},{"id":"/2026/01/04/interesting-reads-2025/","language":"en","title":"My favourite articles of 2025","content_html":"<p>I read a lot of articles about programming and technology as a way of keeping up\nwith the industry and expanding my knowledge. Here are some articles I read in\n2025 that I recommend reading.</p>\n<h2 id=\"technology\" tabindex=\"-1\">Technology <a class=\"header-anchor\" href=\"#technology\">#</a></h2>\n<p>Here is an interesting article about making databases that synchronise between\nclients (a browser) and a server.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://stopa.io/post/279\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/8yNKO97Qzy-48.webp 48w\" sizes=\"60px\"><img src=\"/static/img/8yNKO97Qzy-48.png\" alt=\"Database in the Browser, a spec\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"48\" height=\"48\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tDatabase in the Browser, a spec\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tStepan Parunashvili</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>An article about burnout in tech.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://medium.com/@silvercross313/the-true-cause-of-tech-burnout-9e49842db613\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/53ztcnsco8-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/53ztcnsco8-375.png\" alt=\"The true cause of tech burnout\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"197\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe true cause of tech burnout\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tKD</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>This article is about what users expect from a search engine. I love the\nintersection where users and technology meet, and so this article was a good\nread.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.hughrundle.net/did-you-mean/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/O2yWpi3IzQ-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/O2yWpi3IzQ-375.png\" alt=\"Did you mean..?\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"250\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tDid you mean..?\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tHugh Rundle</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"online-safety-act\" tabindex=\"-1\">Online Safety Act <a class=\"header-anchor\" href=\"#online-safety-act\">#</a></h2>\n<p>The UK’s Online Safety Act came into effect this year. I went through the\ntedious and time-consuming process of doing the risk assessment for ContentDB,\nand ended up writing <a href=\"/2025/08/19/online-safety-act/\">a letter to my MP</a>.</p>\n<p>Here’s another letter to an MP:</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.bentasker.co.uk/posts/blog/the-internet/feedback-on-the-online-safety-act.html\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/CNz0AvP0ha-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/CNz0AvP0ha-375.png\" alt=\"Feedback on The Online Safety Act (an email to my MP)\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"375\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tFeedback on The Online Safety Act (an email to my MP)\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tBen Tasker</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>And this is an interesting reflection on the OSA, comparing it to food safety:</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://shkspr.mobi/blog/2024/12/food-safety-vs-online-safety/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/r-SLQcODCc-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/r-SLQcODCc-375.png\" alt=\"Food Safety vs Online Safety\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"142\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tFood Safety vs Online Safety\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tTerence Eden</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"side-projects\" tabindex=\"-1\">Side projects <a class=\"header-anchor\" href=\"#side-projects\">#</a></h2>\n<p>Last year, I was looking into\n<a href=\"/2025/05/25/fediverse-own-your-identity/\">how we can own our identities on the fediverse</a>.\nThe Fediverse is a collection of social networks that can communicate with each\nother. This includes Mastodon but many others.</p>\n<p>I discovered an interesting fediverse platform called Takahē. This article is by\nthe author of Takahē discussing why they will no longer be working on it.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://aeracode.org/2023/11/06/life-critical-side-projects/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/GrBjXHFAxl-60.webp 60w\" sizes=\"60px\"><img src=\"/static/img/GrBjXHFAxl-60.svg\" alt=\"Life-Critical Side Projects\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"60\" height=\"60\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tLife-Critical Side Projects\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tAndrew Godwin</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"android-developer-verification\" tabindex=\"-1\">Android developer verification <a class=\"header-anchor\" href=\"#android-developer-verification\">#</a></h2>\n<p>I work as a Android app developer. One of the reasons that I like Android app\ndevelopment is that it’s very open - you can just start making apps for your\nphone, you don’t need to pay for a developer license or a particular laptop.\nAndroid was also friendly to side-loading, the practice of installing apps\noutside of the official channels.</p>\n<p>This changed in 2025 when Google announced that they will be cracking down on\nside-loading, and requiring developer verification for all apps. This is deeply\ntroubling to me. If you own a device, I believe you should have the right to run\nanything you’d like on it.</p>\n<p>Here is an article by F-Droid, the most popular open-source app store for\nAndroid:</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://f-droid.org/2025/09/29/google-developer-registration-decree.html\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/g-ek68BRMG-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/g-ek68BRMG-375.png\" alt=\"F-Droid and Google's Developer Registration Decree\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"375\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tF-Droid and Google's Developer Registration Decree\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tF-Droid - marcprux</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"artificial-intelligence\" tabindex=\"-1\">Artificial Intelligence <a class=\"header-anchor\" href=\"#artificial-intelligence\">#</a></h2>\n<p class=\"alert is-primary\">\n\tWe've seen a huge explosion in the adoption of Large Language Models in the\n\tlast couple of years. This rapid adoption has inevitably resulted in some\n\tgrowing pains and negative impacts. These problems are interesting to\n\tdiscuss and think of solutions for but make\tthis section more negative than\n\tI would like.\n</p>\n<h3 id=\"llms-and-open-source\" tabindex=\"-1\">LLMs and Open Source <a class=\"header-anchor\" href=\"#llms-and-open-source\">#</a></h3>\n<p>Large Language Models are hungry for ever larger datasets. LLM crawlers impose a\nheavy cost on open-source and hobby websites, which are often volunteer run and\nhave small budgets. This article discusses how LLM crawlers perform DDoSes on\nopen-source repositories.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://drewdevault.com/2025/03/17/2025-03-17-Stop-externalizing-your-costs-on-me.html\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/V1_h1-WjTV-60.webp 60w\" sizes=\"60px\"><img src=\"/static/img/V1_h1-WjTV-60.png\" alt=\"Please stop externalizing your costs directly into my face\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"60\" height=\"60\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tPlease stop externalizing your costs directly into my face\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tDrew DeVault</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>This article eloquently explains why Mozilla’s recent focus on LLMs is\nfrustrating and against its mission. I believe that if Mozilla is to participate\nin AI, it should be focused on open-source models and not making an AI-powered\nbrowser.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://gardinerbryant.com/mozilla-ai-in-the-browser-is-disgusting-please-stop/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/hC6FZ4uqhn-60.webp 60w\" sizes=\"60px\"><img src=\"/static/img/hC6FZ4uqhn-60.png\" alt=\"Mozilla: AI in the browser is disgusting. Please stop\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"60\" height=\"60\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tMozilla: AI in the browser is disgusting. Please stop\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tGardiner Bryant</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"mediocracy-machines\" tabindex=\"-1\">Mediocracy machines <a class=\"header-anchor\" href=\"#mediocracy-machines\">#</a></h3>\n<p>The key take away of this article for me is: “In the Who Cares Era, the most\nradical thing you can do is care.” AI is a tool that could lead to higher\nproductivity, but it’s important to resist slop and low effort work.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://dansinker.com/posts/2025-05-23-who-cares/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/5bWNP3I5Hg-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/5bWNP3I5Hg-375.png\" alt=\"The Who Cares Era\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"250\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe Who Cares Era\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tDan Sinker</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"llm-features\" tabindex=\"-1\">LLM features <a class=\"header-anchor\" href=\"#llm-features\">#</a></h3>\n<p>A great analogy for unwanted LLM features appearing in everything. Good\nsoftware starts with user needs.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://blog.foxtrotluna.social/theyre-putting-blue-food-coloring-in-everything/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/dzfwNGnh5d-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/dzfwNGnh5d-375.png\" alt=\"They're putting blue food coloring in everything\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"281\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThey're putting blue food coloring in everything\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tLuna Winters</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>An article about the opportunity cost of LLM features - what if we spend this\neffort making technology better instead?</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://coppolaemilio.com/entries/what-could-have-been/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/eSHYiPXftD-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/eSHYiPXftD-375.png\" alt=\"What could have been\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"211\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tWhat could have been\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tEmilio</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"politics\" tabindex=\"-1\">Politics <a class=\"header-anchor\" href=\"#politics\">#</a></h2>\n<p>Here are two good articles about DHH:</p>\n<div class=\"row gap-3\">\n\t<div class=\"col-md col-6 embed\">\n\t<a href=\"https://blogs.gnome.org/alatiera/2025/11/06/dhh-and-omarchy-midlife-crisis/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/KioNasbfHt-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/KioNasbfHt-375.png\" alt=\"DHH and Omarchy: Midlife crisis\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"211\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tDHH and Omarchy: Midlife crisis\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJordan Petridis</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p><div class=\"col-md col-6 embed\">\n<a href=\"https://christianheilmann.com/2025/09/25/as-i-remember-london/\" class=\"hover-enlarge d-block no-underline\">\n<div class=\"card\">\n<div class=\"row align-items-center\">\n<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/ZNdnyGqpFk-60.webp 60w\" sizes=\"60px\"><img src=\"/static/img/ZNdnyGqpFk-60.png\" alt=\"As I remember London\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"60\" height=\"60\"></picture></div>\n<div class=\"col\">\n<div class=\"card-body px-4 py-2\">\n<h5 class=\"card-title my-0\">\nAs I remember London\n</h5>\n<p class=\"card-text mb-0 mt-2 text-muted\">\nChristian Hellmann</p>\n</div>\n</div>\n</div>\n</div>\n</a></p>\n</div>\n</div>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I hope you found some articles worth reading. What were your favourite reads\nlast year? Feel free to send article suggestions you think I might like.</p>\n<p><a href=\"https://www.pexels.com/photo/clear-glass-teacup-with-coffee-beverage-2299028/\">Cover Image</a>\n© Engin Akyurt</p>\n","url":"https://blog.rubenwardy.com/2026/01/04/interesting-reads-2025/","summary":"Articles are a great way of keeping up with programming and technology. Here are some articles I read in 2025 that I recommend reading.","date_published":"2026-01-04T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/fBiLy502MT-540.png","tags":["blogging","year_in_review"]},{"id":"/2025/11/25/eleventy-translation/","language":"en","title":"Eleventy (11ty) string-based translation with i18next","content_html":"<p>This article shows how you can make an Eleventy (11ty) website translatable\non a string-by-string basis, supporting translation platforms such as Weblate.</p>\n<p>A full working example is available at <a href=\"https://gitlab.com/rubenwardy/eleventy-i18n-example\">https://gitlab.com/rubenwardy/eleventy-i18n-example</a>.</p>\n<p>You can also take a look at the <a href=\"https://www.luanti.org/en/\">Luanti website</a>\nfor an example of this in production.</p>\n<h2 id=\"why-string-based-rather-than-entire-pages\" tabindex=\"-1\">Why string-based rather than entire pages <a class=\"header-anchor\" href=\"#why-string-based-rather-than-entire-pages\">#</a></h2>\n<p>Most Eleventy tutorials describe how you can translate entire pages at a time.\nYou might create a blog post in English in an <code>en</code> folder and then translate\ninto French in a <code>fr</code> folder, for example. This is great if you’re already\nmultilingual or have an in-house translation department, but doesn’t work when\nyou want to work with crowd translation services.</p>\n<p>First of all, a page is way too large and overwelming to translate at once.\nWorking on a string-by-string basis is much more manageable for translators.</p>\n<p>Secondly, if you update the source page, you need to manually go through and\nupdate all the translated pages. By using our source language as the source of\ntruth, we can invalidate and notify translators when the source changes\nautomatically. Translates will only need to update strings that have changed.</p>\n<h2 id=\"why-data-driven\" tabindex=\"-1\">Why data-driven <a class=\"header-anchor\" href=\"#why-data-driven\">#</a></h2>\n<p>Translation platforms like Weblate allow non-technical users to easily\ncontribute translations to a website. It also allows multiple translators to\nwork on the same language at the same time without worrying about Git conflicts.</p>\n<p>By making our translations data-driven rather than requiring editing random files,\nwe allow the use of Weblate. In my solution, translations are stored as i18next\nJSON files.</p>\n<h2 id=\"eleventy-11ty-and-internationalization-i18n-libraries\" tabindex=\"-1\">Eleventy (11ty) and internationalization (i18n) libraries <a class=\"header-anchor\" href=\"#eleventy-11ty-and-internationalization-i18n-libraries\">#</a></h2>\n<p>Eleventy’s built-in i18n plugin allows you to manage translated pages. You\ncan specify the language of a page and use filters to find alternative languages\nfor a page. It does not help you with actually translating the content, that’s\nwhere a third-party library comes in.</p>\n<p>I went with i18next as a third-party internationalization library. By exposing it\nas a filter, we can translate text in templates:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h1</span><span class=\"token punctuation\">></span></span>{{ \"Hello world!\" | i18n }}<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h1</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>When the site renders, the i18n filter can use <code>page.lang</code> and the input text to\nlook up a translation:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h1</span><span class=\"token punctuation\">></span></span>Bonjour le monde!<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h1</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Here’s where the magic happens. If we put all of our page content into a layout,\nwe can translate the page into multiple languages by using it as a layout and\njust changing the <code>lang</code>:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\">---\nlayout: pages/about.html\nlang: fr\n---</code></pre>\n<p>If we move <code>lang</code> to the data-cascade, we can just copy the <code>content/fr</code> folder\nwhen adding support for new languages and only need to edit the cascade file.</p>\n<p>Ideally, we’d be able to generate these pages using pagination by reading a list\nof enabled languages. Unfortunately, this does not work in practice due to how\npagination is implemented.</p>\n<h2 id=\"explaining-the-code\" tabindex=\"-1\">Explaining the code <a class=\"header-anchor\" href=\"#explaining-the-code\">#</a></h2>\n<h3 id=\"i18nplugin\" tabindex=\"-1\">I18nPlugin <a class=\"header-anchor\" href=\"#i18nplugin\">#</a></h3>\n<p>We initialise the Eleventy i18n plugin:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> I18nPlugin <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@11ty/eleventy\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// in the eleventy function</span>\n\neleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addPlugin</span><span class=\"token punctuation\">(</span>I18nPlugin<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token literal-property property\">defaultLanguage</span><span class=\"token operator\">:</span> <span class=\"token string\">\"en\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"i18next\" tabindex=\"-1\">i18next <a class=\"header-anchor\" href=\"#i18next\">#</a></h3>\n<p>First, we import some dependencies and work out the dirname:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> i18next <span class=\"token keyword\">from</span> <span class=\"token string\">'i18next'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Backend <span class=\"token keyword\">from</span> <span class=\"token string\">'i18next-fs-backend'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> join <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'path'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> readdirSync<span class=\"token punctuation\">,</span> lstatSync <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'fs'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> fileURLToPath <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'url'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> dirname <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'path'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> iso639 <span class=\"token keyword\">from</span> <span class=\"token string\">\"iso-639-1\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> __filename <span class=\"token operator\">=</span> <span class=\"token function\">fileURLToPath</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">import</span><span class=\"token punctuation\">.</span>meta<span class=\"token punctuation\">.</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> __dirname <span class=\"token operator\">=</span> <span class=\"token function\">dirname</span><span class=\"token punctuation\">(</span>__filename<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>In your Eleventy config function, we initialise i18next:</p>\n<pre class=\"language-js\"><code class=\"language-js\">i18next<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span>Backend<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">init</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n\t<span class=\"token literal-property property\">initAsync</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token literal-property property\">lng</span><span class=\"token operator\">:</span> <span class=\"token string\">\"en\"</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token literal-property property\">saveMissing</span><span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token literal-property property\">nsSeparator</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token literal-property property\">keySeparator</span><span class=\"token operator\">:</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token literal-property property\">fallbackLng</span><span class=\"token operator\">:</span> <span class=\"token string\">\"en\"</span><span class=\"token punctuation\">,</span>\n\n\t<span class=\"token literal-property property\">backend</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token literal-property property\">loadPath</span><span class=\"token operator\">:</span> <span class=\"token function\">join</span><span class=\"token punctuation\">(</span>__dirname<span class=\"token punctuation\">,</span> <span class=\"token string\">'locales/{{lng}}/{{ns}}.json'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token literal-property property\">addPath</span><span class=\"token operator\">:</span> <span class=\"token function\">join</span><span class=\"token punctuation\">(</span>__dirname<span class=\"token punctuation\">,</span> <span class=\"token string\">'locales/{{lng}}/{{ns}}.json'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n\t<span class=\"token literal-property property\">preload</span><span class=\"token operator\">:</span> <span class=\"token function\">readdirSync</span><span class=\"token punctuation\">(</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span>__dirname<span class=\"token punctuation\">,</span> <span class=\"token string\">'locales'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">fileName</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">const</span> joinedPath <span class=\"token operator\">=</span> <span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span>__dirname<span class=\"token punctuation\">,</span> <span class=\"token string\">'locales'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> fileName<span class=\"token punctuation\">)</span>\n\t\t<span class=\"token keyword\">const</span> isDirectory <span class=\"token operator\">=</span> <span class=\"token function\">lstatSync</span><span class=\"token punctuation\">(</span>joinedPath<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">isDirectory</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t\t<span class=\"token keyword\">return</span> isDirectory<span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>We enable <code>saveMissing</code> so that we can save missing translations into the\nEnglish translation.json when building the site. This allows us to update\nthe template without needing an extraction tool.</p>\n<h3 id=\"filters\" tabindex=\"-1\">Filters <a class=\"header-anchor\" href=\"#filters\">#</a></h3>\n<p>Here’s the i18n filter. I’ll explain how to use it in the next section.</p>\n<pre class=\"language-js\"><code class=\"language-js\">eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"i18n\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">msg<span class=\"token punctuation\">,</span> <span class=\"token operator\">...</span>args</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">const</span> t <span class=\"token operator\">=</span> i18next<span class=\"token punctuation\">.</span><span class=\"token function\">getFixedT</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>page<span class=\"token punctuation\">.</span>lang <span class=\"token operator\">??</span> <span class=\"token string\">\"en\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>args<span class=\"token punctuation\">.</span>length <span class=\"token operator\">%</span> <span class=\"token number\">2</span> <span class=\"token operator\">!==</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"i18n: expected even number of arguments\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token keyword\">const</span> params <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> args<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">const</span> key <span class=\"token operator\">=</span> args<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">const</span> value <span class=\"token operator\">=</span> args<span class=\"token punctuation\">[</span>i <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\t\tparams<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> value<span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">return</span> <span class=\"token function\">t</span><span class=\"token punctuation\">(</span>msg<span class=\"token punctuation\">.</span><span class=\"token function\">replaceAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"[[\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"{{\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">replaceAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"]]\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"}}\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> params<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\neleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"langName\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">langCode</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> iso639<span class=\"token punctuation\">.</span><span class=\"token function\">getNativeName</span><span class=\"token punctuation\">(</span>langCode<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"-\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"making-pages-translatable\" tabindex=\"-1\">Making pages translatable <a class=\"header-anchor\" href=\"#making-pages-translatable\">#</a></h3>\n<p>Start by creating <code>content/en</code> with an <code>content/en/en.json</code> with the following\ncontent:</p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n\t<span class=\"token property\">\"lang\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"en\"</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>You can now add pages, for example, <code>content/en/index.html</code>:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\">---\nlayout: pages/index.html\n---</code></pre>\n<p><code>_includes/pages/index.html</code> should contain the page content:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\">\n---\ntitle: Homepage\nlayout: base\n---\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h1</span><span class=\"token punctuation\">></span></span><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> title <span class=\"token operator\">|</span> <span class=\"token function filter\">i18n</span> <span class=\"token delimiter punctuation\">}}</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h1</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n\t<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> <span class=\"token string\">\"Hello [[name]]\"</span> <span class=\"token operator\">|</span> <span class=\"token function filter\">i18n</span><span class=\"token operator\">:</span> <span class=\"token string\">\"name\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Bob\"</span> <span class=\"token delimiter punctuation\">}}</span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n</code></pre>\n<p>Notice how we use <code>[[name]]</code> rather than <code>{{name}}</code>.\nThis is because curly braces are already used by Eleventy templates. The\ni18n filter will replace double square braces with double curly braces.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Again, a full working example is available at <a href=\"https://gitlab.com/rubenwardy/eleventy-i18n-example\">https://gitlab.com/rubenwardy/eleventy-i18n-example</a>.</p>\n","url":"https://blog.rubenwardy.com/2025/11/25/eleventy-translation/","summary":"This article shows how you can make an Eleventy (11ty) website translatable on a string-by-string basis, supporting translation platforms such as Weblate.","date_published":"2025-11-25T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/vtXd6uYGXP-540.png","tags":["snippet","blogging","tutorials","webdev"]},{"id":"/2025/11/17/plant-watering-first-pcb/","language":"en","title":"Plant watering system: my first PCB","content_html":"<p>Previously, I created a <a href=\"/2022/03/17/plant-monitor/\">plant monitor</a> that tracked\nsoil moisture and sent notifications when the plant needed watering. But\nwouldn’t it be nice if it could water the plant instead of sending a\nnotification? This new project does exactly that.</p>\n<p>My plant watering system supports four plants, with a soil moisture sensor and a\npump for each plant. The pumps draw water from a water tank, which contains a\nwater level sensor. I’m using an ESP32 microcontroller for the brains, as it\ncomes with Internet connectivity and low-power deep sleeping.</p>\n<h2 id=\"why-make-a-pcb\" tabindex=\"-1\">Why make a PCB? <a class=\"header-anchor\" href=\"#why-make-a-pcb\">#</a></h2>\n<p>Why go through the effort of making a PCB when a quick protoboard or stripboard\nwould have been quicker? I did originally make a stripboard but I found that\nthey have terrible grounding properties. I wrote\n<a href=\"/2023/08/29/ghosts-live-in-the-ground/\">a blog post about it</a>, but in\nsummary, noise from the pumps interfered with the microcontroller and caused\nit to crash.</p>\n<figure class=\"undefined\"><a href=\"/static/img/LO71Lq9Spt-540.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/9af6zTL_J7-540.webp 540w\"><img src=\"/static/img/9af6zTL_J7-540.jpeg\" alt=\"The front of the stripboard.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"372\"></picture></a><figcaption>The front of the stripboard.</figcaption></figure>\n<p>A PCB provides much better grounding than a protoboard or stripboard. It allows\nyou to have a layer of the board dedicated to ground, called a ground plane.\nI watched an excellent video on this topic called\n<a href=\"https://www.youtube.com/live/ySuUZEjARPY\">How to Achieve Proper Grounding</a>.\nIt taught me a lot and I applied the lessons to my PCB design.</p>\n<p>I was looking for an excuse to learn how to make PCBs, so I welcomed this\nopportunity.</p>\n<h2 id=\"pcb-design\" tabindex=\"-1\">PCB design <a class=\"header-anchor\" href=\"#pcb-design\">#</a></h2>\n<h3 id=\"schematic-and-pcb\" tabindex=\"-1\">Schematic and PCB <a class=\"header-anchor\" href=\"#schematic-and-pcb\">#</a></h3>\n<p>I designed my PCB using <a href=\"https://www.kicad.org/\">KiCAD</a>, which is free and\nopen-source. I learned how to use KiCAD by watching\n<a href=\"https://www.youtube.com/watch?v=aVUqaB0IMh4\">a tutorial by Phil’s Lab on YouTube</a>.</p>\n<p>There are two steps to designing a PCB. The first is to create a high-level\ndiagram of your circuit called a schematic. The schematic allows you to specify\nan electrical circuit without worrying about how it will be built.</p>\n<figure class=\"undefined\"><a href=\"/static/img/HJ4RX4lkWd-3507.webp\"><img src=\"/static/img/tE2cJ8doUl-3507.webp\" alt=\"The schematic of my circuit, made using KiCAD\" loading=\"lazy\" decoding=\"async\" width=\"3507\" height=\"2480\"></a><figcaption>The schematic of my circuit, made using KiCAD</figcaption></figure>\n<p>The next step is to turn this schematic into a PCB by placing components on a\nboard design and connecting them according to the schematic. KiCAD ensures\nthat the board design matches the schematic. This two-step process reduces\nmistakes.</p>\n<figure class=\"undefined\"><a href=\"/static/img/lEYWsk-zAe-1038.webp\"><img src=\"/static/img/ageXHg8Ype-1038.webp\" alt=\"The PCB's design, made using KiCAD\" loading=\"lazy\" decoding=\"async\" width=\"1038\" height=\"912\"></a><figcaption>The PCB's design, made using KiCAD</figcaption></figure>\n<h3 id=\"component-selection\" tabindex=\"-1\">Component selection <a class=\"header-anchor\" href=\"#component-selection\">#</a></h3>\n<p>My PCB uses two different methods to attach components to the board. The first\nis Through-Hole Technology (THT), where components are attached with wires that\ngo through the board and are soldered to the other side. The second is\nSurface-Mount Device (SMD) or Surface-Mount Technology (SMT), where the\ncomponent sits on top of the board and is soldered to pads on the board.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/Yd-K-Ui_FN-1920.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/ZzYtiEIBRK-540.webp 540w\"><img src=\"/static/img/ZzYtiEIBRK-540.jpeg\" alt=\"Through-hole Technology (THT) (&amp;copy; Christian Taube, <a href=&quot;https://en.wikipedia.org/wiki/Through-hole_technology#/media/File:MOS6581_chtaube061229.jpg&quot;>source</a>)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"388\"></picture></a><figcaption>Through-hole Technology (THT) (&copy; Christian Taube, <a href=\"https://en.wikipedia.org/wiki/Through-hole_technology#/media/File:MOS6581_chtaube061229.jpg\">source</a>)</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/dtEWzVdrmP-1920.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/nkdWy0asLb-540.webp 540w\"><img src=\"/static/img/nkdWy0asLb-540.jpeg\" alt=\"Surface-mount Technology (SMT/SMD) (&amp;copy; Phiarc, <a href=&quot;https://en.wikipedia.org/wiki/Surface-mount_technology#/media/File:Big_SMD_capacitor_2.jpg&quot;>source</a>)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"394\"></picture></a><figcaption>Surface-mount Technology (SMT/SMD) (&copy; Phiarc, <a href=\"https://en.wikipedia.org/wiki/Surface-mount_technology#/media/File:Big_SMD_capacitor_2.jpg\">source</a>)</figcaption></figure></div>\n\t\t\t</div>\n<p>I chose to use a mixture of THT and SMD for different reasons. I used SMD for\nthe resistors, as I wanted to practice soldering SMD components, and passive\ncomponents like resistors are the simplest to work with. I used THT for the\nremaining components because I wanted to use the same components used in the\nbreadboard prototype. Testing the circuit on a breadboard first reduces the risk\nof wasted time and money.</p>\n<p>The components are:</p>\n<ul>\n<li>1x ESP32-WROOM-32 dev board</li>\n<li>4x pumps</li>\n<li>4x capacitive soil moisture sensors</li>\n<li>Silicone tubing</li>\n<li>4x N-Channel MOSFETs (<a href=\"https://www.infineon.com/part/IRLB8721\">IRLB8721</a>, massively overspecced)</li>\n<li>1x P-Channel MOSFET (I need to order one, currently bridging with a wire)</li>\n<li>1x USB-C breakout</li>\n<li>1x 100uF electrolytic capacitor</li>\n<li>4x 100nF ceramic capacitors</li>\n<li>4x diodes (1N4007)</li>\n<li>5x JST 3-pin</li>\n<li>4x JST 2-pin</li>\n<li>SMD resistors: 5x 10K, 5x 1M, 5x 1K, 2x 5.11K</li>\n</ul>\n<h3 id=\"design-lessons\" tabindex=\"-1\">Design lessons <a class=\"header-anchor\" href=\"#design-lessons\">#</a></h3>\n<p>Before I ordered my PCB, I made sure to run the design by some experienced\npeople and ask for feedback.</p>\n<p>One beginner mistake I made was an overuse of right and acute angles when\nrouting traces. Whilst less of a problem in modern fabrication, right and acute\nangles can trap etchant and cause issues with board manufacture.</p>\n<p>I also had some unnecessary bridges. Remember that grounding video? It’s very\nimportant to minimise the number of breaks in the ground plane, especially under\npower and signal traces. I was able to move some components to remove all but a\ncouple of ground breaks and vias.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/cysx9GOpX4-909.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/TeYsvLWb5K-540.webp 540w\"><img src=\"/static/img/TeYsvLWb5K-540.png\" alt=\"The first revision\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"490\"></picture></a><figcaption>The first revision</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/lEYWsk-zAe-1038.webp\"><img src=\"/static/img/ageXHg8Ype-1038.webp\" alt=\"The final PCB design\" loading=\"lazy\" decoding=\"async\" width=\"1038\" height=\"912\"></a><figcaption>The final PCB design</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"usb-c-support\" tabindex=\"-1\">USB-C support <a class=\"header-anchor\" href=\"#usb-c-support\">#</a></h3>\n<p>My PCB supports USB-C for power. I’m a big fan of USB-C, so it was nice to\nhave this instead of Micro-USB like my previous projects. I followed\n<a href=\"https://www.pcbway.com/blog/PCB_Design_Tutorial/How_to_add_USB_C_to_your_projects.html\">a guide by PCBWay</a>.\nIt’s quite easy to implement for 5V power - you just need two CC resistors to\nset the voltage to 5V.</p>\n<p>I used a breakout for the USB-C connector because I didn’t feel confident\nsoldering the USB-C socket onto the board. The breakout doesn’t really add\nanything. On future boards, I’ll just solder a USB-C connector directly.</p>\n<h2 id=\"pcb-assembly\" tabindex=\"-1\">PCB assembly <a class=\"header-anchor\" href=\"#pcb-assembly\">#</a></h2>\n<p>Once I was happy with my PCB’s design, I sent it to be produced by PCBWay.\nNot long after, I had five copies of the PCB on my desk.</p>\n<figure class=\"undefined\"><a href=\"/static/img/85XGI_HYIU-1920.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/Dk2KuzK7uU-540.webp 540w\"><img src=\"/static/img/Dk2KuzK7uU-540.jpeg\" alt=\"The PCBs arrived!\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>The PCBs arrived!</figcaption></figure>\n<p>Before assembling my PCB, I practised soldering SMD components on a practice\nboard I bought from the internet. I also used one of the five copies to practice\nsoldering SMD resistors.</p>\n<p>I started the assembly by soldering SMD resistors, checking with a multimeter\nas I went. I proceeded with the rest of the assembly in this manner - soldering\non components and checking that they were connected correctly. I focused on\ngetting a single pump up and working before I soldered the rest.</p>\n<figure class=\"undefined\"><a href=\"/static/img/kPjcLtzzGh-1920.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/JAUNeVSAR_-540.webp 540w\"><img src=\"/static/img/JAUNeVSAR_-540.jpeg\" alt=\"Assembling the PCB\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"497\"></picture></a><figcaption>Assembling the PCB</figcaption></figure>\n<h3 id=\"jst-xh\" tabindex=\"-1\">JST-XH <a class=\"header-anchor\" href=\"#jst-xh\">#</a></h3>\n<p>The pumps and sensors attach to the board using JST-XH connectors. By far the\nmost challenging part of this project was creating these JST plugs.</p>\n<p>I started by following a guide on YouTube that soldered the plugs. I found that\nthe plugs never fit into the sockets correctly and were very fragile. This was\nespecially a problem for the the thin gauge water pump wires.This one issue was\nvery frustrating and caused me to shelve the project for almost two years.</p>\n<p>In the end, I bought a crimping tool. It turns out that using the proper tool\nfor a job is so much easier! The connectors are meant to be crimped using a\ntool and not soldered.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/cww-e4q_tp-3140.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/hD21hwAN57-540.webp 540w\"><img src=\"/static/img/hD21hwAN57-540.jpeg\" alt=\"A JST connector. The long wing holds the insulator, the short wing holds the conductor.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"502\"></picture></a><figcaption>A JST connector. The long wing holds the insulator, the short wing holds the conductor.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/lZh55D0AfP-455.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/qx5Lc8jjow-455.webp 455w\"><img src=\"/static/img/qx5Lc8jjow-455.png\" alt=\"Crimping tool\" loading=\"lazy\" decoding=\"async\" width=\"455\" height=\"425\"></picture></a><figcaption>Crimping tool</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"final-result\" tabindex=\"-1\">Final result <a class=\"header-anchor\" href=\"#final-result\">#</a></h2>\n<p>I designed a simple enclosure and printed it using my Prusa Mini+. I use a\nplastic food container for the water tank, as 3d prints don’t tend to be\nwatertight. I used an existing design by\n<a href=\"https://www.youtube.com/watch?v=9Fx9zQJe3H4\">Emilostuff</a> to clip the tubes onto\nthe soil sensors.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/xn5W_5R_UX-2827.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/RF2pmKfW6C-540.webp 540w\"><img src=\"/static/img/RF2pmKfW6C-540.jpeg\" alt=\"The 3d printed enclosure.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"406\"></picture></a><figcaption>The 3d printed enclosure.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/txdy_PiDK--2385.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/RciAT4EmwG-540.webp 540w\"><img src=\"/static/img/RciAT4EmwG-540.jpeg\" alt=\"Inside the enclosure.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"406\"></picture></a><figcaption>Inside the enclosure.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/6bl01mDX4B-3075.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/F5YCfoPhWa-540.webp 540w\"><img src=\"/static/img/F5YCfoPhWa-540.jpeg\" alt=\"Clip designed by Emilostuff.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"406\"></picture></a><figcaption>Clip designed by Emilostuff.</figcaption></figure></div>\n\t\t\t</div>\n<p>Here is the system connected to four plants:</p>\n<figure>\n\t<iframe class=\"youtube-video\"\n\t\tsrc=\"https://www.youtube.com/embed/kaLwa5y_CrE\"\n\t\ttitle=\"YouTube video player\"\n\t\tallow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n\t\tallowfullscreen>\n\t</iframe><figcaption>A quick demonstration of the plant watering system.</figcaption></figure>\n<h2 id=\"mistakes-and-future-improvements\" tabindex=\"-1\">Mistakes and future improvements <a class=\"header-anchor\" href=\"#mistakes-and-future-improvements\">#</a></h2>\n<h3 id=\"don-t-use-a-pen-lid-on-a-soldering-iron\" tabindex=\"-1\">Don’t use a pen lid on a soldering iron <a class=\"header-anchor\" href=\"#don-t-use-a-pen-lid-on-a-soldering-iron\">#</a></h3>\n<p>In case anyone is wondering, putting a pen lid on a soldering iron does not end\nwell for the lid or fingers. I thought I was still holding my flux pen. It\ncreated a hole in the lid but luckily my fingers and the iron tip was fine.</p>\n<h3 id=\"reduce-size\" tabindex=\"-1\">Reduce size <a class=\"header-anchor\" href=\"#reduce-size\">#</a></h3>\n<p>I choose to place the MOSFETs in headers to make it easier to replace them. This\nisn’t really necessary, and soldering the MOSFETs directly would make the design\nsmaller.</p>\n<p>A future design could use SMD MOSFETs and capacitors to allow the board to be\neven smaller.</p>\n<h3 id=\"jst-connector-wiring\" tabindex=\"-1\">JST connector wiring <a class=\"header-anchor\" href=\"#jst-connector-wiring\">#</a></h3>\n<p>The order of the connections on the soil moisture sensor JST sockets does not\nmatch the order on the soil moisture sensor. This means that the wires need to\ncross over and also means the cable can only be plugged in one direction.\nPlugging the wrong end of the cable in could lead to damage.</p>\n<p>The plant sensor wires are wrapped in electrical tape to keep the three wires\ntogether. This doesn’t look very professional, it would be nice to do this\nbetter.</p>\n<h3 id=\"screen-and-input\" tabindex=\"-1\">Screen and input <a class=\"header-anchor\" href=\"#screen-and-input\">#</a></h3>\n<p>It would be nice to be able to interact with the system. The PCB has header\npins for an OLED screen and four capacitive touch pads. There are also pins for\nan on-off switch.</p>\n<h3 id=\"internet-of-things-integration\" tabindex=\"-1\">Internet of Things integration <a class=\"header-anchor\" href=\"#internet-of-things-integration\">#</a></h3>\n<p>I chose an ESP32 as it comes with WiFi built in. In the future, I’d like to\nconnect the plant watering system to an open-source IoT dashboard. I like the\nidea of using an open protocol like MQTT for my devices.</p>\n<h3 id=\"create-a-water-tank\" tabindex=\"-1\">Create a water tank <a class=\"header-anchor\" href=\"#create-a-water-tank\">#</a></h3>\n<p>I could attach the plant watering system to the tupperware lid, drilling holes\nfor pipes and the pump wires. This would allow the container to be sealed and\nmake the plant watering system a nice contained unit.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I now know how to make PCBs! This is an exciting new skill to have. I’ve learned\na lot by doing this project and have a lot of ideas for things to do next.</p>\n<p>It’s a shame that the JST issue caused me to shelve this project for so long.\nOnce I got around to using the crimping tool, it wasn’t that hard to finish up\nthe project. I wish I had access to a maker space because I think that would\nhave helped a lot, both with getting help and trying out tools.</p>\n","url":"https://blog.rubenwardy.com/2025/11/17/plant-watering-first-pcb/","summary":"My plant watering system supports four plants, with a soil moisture sensor and a pump for each plant.","date_published":"2025-11-17T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/5LpbJZzi6b-540.png","tags":["projects","electronics","3d_printing"]},{"id":"/2025/10/05/openttd-settings/","language":"en","title":"OpenTTD must-change settings that should be default already","content_html":"<p>OpenTTD is a game where you build a transportation empire with road vehicles,\ntrains, boats, and planes. It’s an open-source remake of a game called\nTransport Tycoon by Chris Sawyer, released in 1994. I have been playing it for\nmany years; I have no idea how many hours I’ve sunk into it.</p>\n<p>There are a few setting changes that I consider essential. It’s bizarre to me\nthat these aren’t the defaults - I guess OpenTTD is trying to keep close to the\noriginal 1994 game. I like to focus on passenger and mail services, and therefore\nconsider these essential.</p>\n<h2 id=\"road-layout\" tabindex=\"-1\">Road layout <a class=\"header-anchor\" href=\"#road-layout\">#</a></h2>\n<p>OpenTTD’s default road layout, “original”, wastes space by building lots of\nunnecessary roads and slows down town growth. You should set “default road\nlayout for new towns” to “Better roads” instead - it has an almost identical\nresult but requires at least two tiles between parallel roads.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/iWabb3iyWg-815.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/pbsI1kEw4--540.webp 540w\"><img src=\"/static/img/pbsI1kEw4--540.png\" alt=\"Original roads\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"422\"></picture></a><figcaption>Original roads</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/jN4x8Mc0WZ-576.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/jbXj2B7yA9-540.webp 540w\"><img src=\"/static/img/jbXj2B7yA9-540.png\" alt=\"Better roads\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"421\"></picture></a><figcaption>Better roads</figcaption></figure></div>\n\t\t\t</div>\n<p>You could also consider using a grid or manual road placement, by turning off\n“towns can build roads”.</p>\n<p>Town growth in OpenTTD sucks. No matter the road layout you choose, the growth\nalgorithm has a flaw that limits the growth of a city. It uses a “random walk”\nthat gives up after a certain distance. Having lots of junctions can cause the\nalgorithm to get lost, which slows down town growth. The original town layout\nhas the most junctions of all town layouts. The solution for town growth is to\nuse tunnels as shortcuts to the outskirts of the city, or to turn off “towns\nbuild roads” and instead build towns in a spiral.</p>\n<figure class=\"undefined\"><a href=\"/static/img/-47ww35tEP-585.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/CllJ5isKf1-540.webp 540w\"><img src=\"/static/img/CllJ5isKf1-540.png\" alt=\"The exit of a growth tunnel. The entrance is right by the city center. This allows the growth algorithm to skip large parts of the city.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"346\"></picture></a><figcaption>The exit of a growth tunnel. The entrance is right by the city center. This allows the growth algorithm to skip large parts of the city.</figcaption></figure>\n<h2 id=\"towns-are-allowed-to-build-level-crossings\" tabindex=\"-1\">Towns are allowed to build level crossings <a class=\"header-anchor\" href=\"#towns-are-allowed-to-build-level-crossings\">#</a></h2>\n<p>There’s nothing more terrifying than hearing the “ding ding ding” of a level\ncrossing in OpenTTD. By default, towns like to build level crossings over\nbusy railways. You can turn this off in the settings.</p>\n<h2 id=\"cargo-distribution\" tabindex=\"-1\">Cargo distribution <a class=\"header-anchor\" href=\"#cargo-distribution\">#</a></h2>\n<p>By default, passengers and mail in OpenTTD are dumb. They get on the train and\nget off at the next stop, unless you manually transfer them.</p>\n<p>Cargo distribution is a feature that simulates destinations for passengers. They\nwill get on the bus to a train station, catch the train to a nearby city, and\nthen get on the bus again to where they want to go. This is all done completely\nautomatically, no need to manually do transfer orders!</p>\n<p>This is such an important feature, I can’t believe they’ve hidden it into\nobscurity by disabling it by default.</p>\n<figure class=\"w-100, m-4\"><a href=\"/static/img/MNIeO8UOpD-1457.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Vua1MrAWpD-900.webp 900w\"><img src=\"/static/img/Vua1MrAWpD-900.png\" alt=\"Map &amp;gt; Cargo flow legend. Shows the routes passengers can take. You can see local bus routes and a town's train station.\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"550\"></picture></a><figcaption>Map &gt; Cargo flow legend. Shows the routes passengers can take. You can see local bus routes and a town's train station.</figcaption></figure>\n<p>Cargo distribution makes passenger and mail focused games much more interesting.\nYou can now use a hierarchy of transport - with local buses/trams, regional\ntrains, high speed trains.</p>\n<figure class=\"w-100, m-4\"><a href=\"/static/img/Kl2Vsc_XJW-1865.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/-OT0TmF9-r-900.webp 900w\"><img src=\"/static/img/-OT0TmF9-r-900.png\" alt=\"A game where I was trying to get 100% coverage of a city and grow it to 1,000,000. Turns out this is a boring way to play, especially with OpenTTD's rubbish way of doing growth.\" loading=\"lazy\" decoding=\"async\" width=\"900\" height=\"471\"></picture></a><figcaption>A game where I was trying to get 100% coverage of a city and grow it to 1,000,000. Turns out this is a boring way to play, especially with OpenTTD's rubbish way of doing growth.</figcaption></figure>\n<p>You should set the “distribution mode for passengers” to “symmetric” and for\nmail to “asymmetric”. This will simulate return journeys for passengers.</p>\n<h2 id=\"number-of-plane-crashes\" tabindex=\"-1\">Number of plane crashes <a class=\"header-anchor\" href=\"#number-of-plane-crashes\">#</a></h2>\n<p>Every plane landing has at least a 0.07% chance to crash and burn in OpenTTD,\nwithout a way to prevent it. This is insane, plane crashes are very rare in real\nlife. You should set “Number of plane crashes” to “none”. Large planes may still\ncrash when landing on a small airport.</p>\n<p>That being said, planes are quite overpowered in OpenTTD so maybe this is by\ndesign.</p>\n<h2 id=\"automatically-pause-when-starting-a-new-game\" tabindex=\"-1\">Automatically pause when starting a new game <a class=\"header-anchor\" href=\"#automatically-pause-when-starting-a-new-game\">#</a></h2>\n<p>Starting a new game paused gives time to assess the map for resources without\nlosing money to loan interests and other costs.</p>\n<h2 id=\"default-rail-type\" tabindex=\"-1\">Default rail type <a class=\"header-anchor\" href=\"#default-rail-type\">#</a></h2>\n<p>Sick of accidentally placing normal rail on an electric AC rail line?\nChange “default rail type” to “most used” so that it defaults to the correct\ntype when loading a game.</p>\n<h2 id=\"show-the-cargoes-the-vehicles-can-carry-in-the-list-windows\" tabindex=\"-1\">Show the cargoes the vehicles can carry in the list windows <a class=\"header-anchor\" href=\"#show-the-cargoes-the-vehicles-can-carry-in-the-list-windows\">#</a></h2>\n<p>Enabling this option shows what types of cargo a vehicle can carry. It makes\nreading the vehicle lists much easier.</p>\n<h2 id=\"multiplayer-settings\" tabindex=\"-1\">Multiplayer settings <a class=\"header-anchor\" href=\"#multiplayer-settings\">#</a></h2>\n<ul>\n<li>Allow level crossings with roads or rails owned by competitors: off</li>\n<li>Allow buying exclusive transport rights: off</li>\n<li>Allow funding local road reconstruction: off</li>\n</ul>\n<h2 id=\"bonus-train-length-and-station-size\" tabindex=\"-1\">Bonus: train length and station size <a class=\"header-anchor\" href=\"#bonus-train-length-and-station-size\">#</a></h2>\n<p>I like to increase the maximum train length and station size to get more realistic\ntrain lengths. I don’t necessarily think these should be changed by default for\nall users.</p>\n<ul>\n<li>Maximum length of trains 10 (Default: 7)</li>\n<li>Maximum station spread 14 (Default: 12)</li>\n</ul>\n<h2 id=\"bonus-trams\" tabindex=\"-1\">Bonus: trams <a class=\"header-anchor\" href=\"#bonus-trams\">#</a></h2>\n<p>Not a setting, but a NewGRF - OpenTTD really should come with\n“Improved Generic Tram Set” by default.</p>\n","url":"https://blog.rubenwardy.com/2025/10/05/openttd-settings/","summary":"Here are some OpenTTD settings that I recommend changing, new and old players alike.","date_published":"2025-10-05T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/eCbTMbJMMN-540.png","tags":["gaming","reviews"]},{"id":"/2025/08/19/online-safety-act/","language":"en","title":"Problems with the Online Safety Act: a letter to my MP","content_html":"<p>I’m writing to express my objections to the Online Safety Act and ask that you\nsupport repealing or amending the Act. The Online Safety Act is highly damaging\nto tech innovation in the UK, is unfair to small and hobby sites, and is a\ncybersecurity and privacy nightmare.</p>\n<h2 id=\"unfair-burden-on-small-and-hobby-websites\" tabindex=\"-1\">Unfair burden on small and hobby websites <a class=\"header-anchor\" href=\"#unfair-burden-on-small-and-hobby-websites\">#</a></h2>\n<p>In my own time, I run a small website with fewer than 1,000 monthly active\nusers. This is a hobby project and does not make a profit. I have had to do two\nrisk assessments — a time-consuming process with thousands of pages of\nOfcom guidance to read. There is no guarantee that I have done these correctly,\nas I am unable to hire an expert to check them. My website is small and actively\nmoderated; it is not a risk to anyone.</p>\n<p>As the Online Safety Act carries a large fine and criminal liabilities, many\ndomestic small and hobby websites are closing down or blocking UK users because\nthey are unwilling to accept the risk or do not have the time/resources to do\nthe compliance.<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup> The majority of websites are not based in the UK and\nit is a lot easier for them to just block UK users rather than comply with the\nAct. This is a huge loss of community and personal freedoms for UK users.</p>\n<p>The biggest online safety risks are big tech services like Instagram, Facebook,\nand X, not hobby forums about hamster care or wood carving.<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\"><span class=\"sr-only\">footnote </span>2</a></sup> The\nhighest risk sites are best placed to handle the additional burden, yet the\nlowest risk sites are most affected by the additional burden. This is what\nmakes it unfair.</p>\n<p>The Online Safety Act’s unfair burden on small and hobby websites is a barrier\nto entry and will result in the UK becoming a less competitive environment for\ntech innovation.</p>\n<p>From the Government’s response to the\n<a href=\"https://petition.parliament.uk/petitions/722903\">Repeal the Online Safety Act</a> petition:</p>\n<blockquote>\n<p>For example, the Government is very concerned about small platforms that host\nharmful content, such as forums dedicated to encouraging suicide or self-harm.\nExempting small services from the Act would mean that services like these\nforums would not be subject to the Act’s enforcement powers.</p>\n</blockquote>\n<p>Safe harbour protections allow the Internet to function. The idea is that a\nservice provider is not liable for the content their users post providing that\nthey swiftly act to remove abusive content when they gain knowledge of it. A\nforum for abusive content like suicide/self-harm would fail the knowledge part\nof this.</p>\n<p>The most small sites should be required to do is have terms of service/rules,\nmoderation, and a reporting system. They should not be subject to costly and\ntime-intensive compliance requiring two risk assessments and reading thousands\nof pages of Ofcom guidance.</p>\n<h2 id=\"age-verification-is-a-cybersecurity-and-privacy-nightmare\" tabindex=\"-1\">Age verification is a cybersecurity and privacy nightmare <a class=\"header-anchor\" href=\"#age-verification-is-a-cybersecurity-and-privacy-nightmare\">#</a></h2>\n<p>Age verification is a new and poorly regulated industry that frequently requires\nUK users to give personal information and ID documents to foreign companies. It\ndesensitises UK users into giving this information to websites online. It is\nonly a matter of time before the identity documents of millions of UK users are\nleaked.</p>\n<p>Additionally, age verification has a negative impact on privacy. It results in\ndisclosing your face and/or full identity to an age verification service. Many\nof these companies are third-party and have dodgy privacy policies — including\nthose chosen by Reddit, Bluesky, and Grindr.<sup class=\"footnote-ref\"><a href=\"#fn3\" id=\"fnref3\"><span class=\"sr-only\">footnote </span>3</a></sup> <sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4\"><span class=\"sr-only\">footnote </span>4</a></sup></p>\n<h2 id=\"age-verification-is-harmful-to-children-and-misses-the-point\" tabindex=\"-1\">Age verification is harmful to children and misses the point <a class=\"header-anchor\" href=\"#age-verification-is-harmful-to-children-and-misses-the-point\">#</a></h2>\n<p>One part of the Online Safety Act is strong age verification to access content\nthat is “harmful to children”. I believe that this misses the point and results\nin more harm to children.<sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4:1\"><span class=\"sr-only\">footnote </span>4:1</a></sup></p>\n<p>Nothing will stop a sufficiently motivated teenager from getting porn. In the\nbest case, they will use a VPN<sup class=\"footnote-ref\"><a href=\"#fn5\" id=\"fnref5\"><span class=\"sr-only\">footnote </span>5</a></sup> or find a loophole. Worst case, they will\ngo to the dark web and poorly moderated websites instead of legitimate\nwebsites.<sup class=\"footnote-ref\"><a href=\"#fn6\" id=\"fnref6\"><span class=\"sr-only\">footnote </span>6</a></sup> These websites are likely to have extreme porn, revenge\nporn, and many other kinds of harm that are much worse than a teenager\n<em>intentionally</em> seeking out porn.</p>\n<p>The focus should be on preventing <em>accidental</em> views of content and parental\ncontrols. It’s quite common to accidentally see adult content on social media;\nadult content is often posted on common safe-for-work hashtags, like cosplays.\nThis is actual harm as the user did not consent to seeing the content. Parental\ncontrols tools already exist and parents could try talking to their children.</p>\n<h2 id=\"censorship-and-government-overreach\" tabindex=\"-1\">Censorship and government overreach <a class=\"header-anchor\" href=\"#censorship-and-government-overreach\">#</a></h2>\n<p>The Online Safety Act has a chilling effect on free speech.<sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4:2\"><span class=\"sr-only\">footnote </span>4:2</a></sup> Posts about\nthe Gaza war, protests, new political parties, and LGBT+ communities have been\nput behind age gates.<sup class=\"footnote-ref\"><a href=\"#fn7\" id=\"fnref7\"><span class=\"sr-only\">footnote </span>7</a></sup> This is deeply concerning at a time when\nLabour is planning to lower the voting age to 16.</p>\n<p>Age verification is for all content that is “harmful to children”, not just\nporn. This results in a chilling effect where sites will ban all such content,\nvery broadly, out of caution.</p>\n<p>The Online Safety Act grants new powers that could be exploited by a less\nfavourable future government.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The Online Safety Act is highly damaging to tech innovation in the UK, is unfair\nto small and hobby sites, and is a cybersecurity and privacy nightmare. It\nshould be repealed or amended to remove age verification requirements and\nfurther exempt small and hobby websites.</p>\n<p>Excluding the strong age verification requirements, I do support more\nobligations being placed on big tech websites to prevent the harms outlined in\nthe act. However, these obligations should only be on large companies with\nmillions of users and not small websites. Too much of the Internet is becoming\ncentralised onto a few large services, and the Online Safety Act only\nexacerbates this problem.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Here is a list of websites that have shut down or have blocked the UK (this list will be very incomplete): <a href=\"https://www.blocked.org.uk/osa-blocks\">https://www.blocked.org.uk/osa-blocks</a>. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p><a href=\"https://www.newstatesman.com/science-tech/big-tech/2025/07/big-tech-is-the-only-winner-of-the-online-safety-act\">Big Tech is the only winner of the Online Safety Act (The New Statesman)</a> <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p><a href=\"https://www.openrightsgroup.org/press-releases/org-calls-for-age-assurance-industry-to-be-regulated/\">Open Rights Group calls for age assurance industry to be regulated</a> <a href=\"#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p><a href=\"https://www.eff.org/deeplinks/2025/08/blocking-access-harmful-content-will-not-protect-children-online-no-matter-how\">Blocking Access to Harmful Content Will Not Protect Children Online - Electronic Frontier Foundation</a> <a href=\"#fnref4\" class=\"footnote-backref\">↩︎</a> <a href=\"#fnref4:1\" class=\"footnote-backref\">↩︎</a> <a href=\"#fnref4:2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p><a href=\"https://www.bbc.co.uk/news/articles/cn72ydj70g5o\">VPNs top download charts as age verification law kicks in - BBC</a> <a href=\"#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p><a href=\"https://www.bbc.co.uk/news/articles/c17n9k54qz2o\">Porn site traffic plummets as UK age verification rules enforced - BBC</a> <a href=\"#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p><a href=\"https://www.theguardian.com/commentisfree/2025/aug/09/uk-online-safety-act-internet-censorship-world-following-suit\">The UK’s Online Safety Act is a licence for censorship – and the rest of the world is following suit - The Guardian</a> <a href=\"#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2025/08/19/online-safety-act/","summary":"A letter to my MP complaining about the Online Safety Act","date_published":"2025-08-19T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/3i3oro2itG-540.png","tags":["opinion"]},{"id":"/2025/06/01/eleventy-stats/","language":"en","title":"Writing statistics for your Eleventy (11ty) blog","content_html":"<p>One of the reasons why I <a href=\"/2023/10/27/switched-to-eleventy/\">switched to Eleventy</a>\nwas to make it easier to do custom stuff, like creating a\n<a href=\"https://blog.rubenwardy.com/stats/\">writing statistics page</a>\nfor my needs. This blog post is to share how I created that page and how you\ncan create one for your blog.</p>\n<h2 id=\"the-problem\" tabindex=\"-1\">The problem <a class=\"header-anchor\" href=\"#the-problem\">#</a></h2>\n<p>My old blog was written in Jekyll. The stats page was incredibly complicated\nwith a lot of template logic. There was also a mess of Python scripts to\ngenerate data.</p>\n<p>Because of this, one of my fundamental design decisions was to do as much as\npossible in JavaScript. The data generated by this JavaScript code should match\nhow it is used on the page. The liquid template should just render the data into\nHTML.</p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n\t<span class=\"token property\">\"spotlight\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Total words written\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"icon\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"pen\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"value\"</span><span class=\"token operator\">:</span> <span class=\"token number\">46891</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Total posts\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"icon\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"newspaper\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"value\"</span><span class=\"token operator\">:</span> <span class=\"token number\">78</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"title\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Total likes received\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"icon\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"heart\"</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"value\"</span><span class=\"token operator\">:</span> <span class=\"token number\">322</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"hint\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\" Experimental, currently only counts Mastodon likes\"</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token property\">\"by_year\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token property\">\"2023\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">14</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">15455</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_avg\"</span><span class=\"token operator\">:</span> <span class=\"token number\">1104</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"2024\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">5</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">3940</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_avg\"</span><span class=\"token operator\">:</span> <span class=\"token number\">788</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token property\">\"2025\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n\t\t\t<span class=\"token property\">\"total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">8</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_total\"</span><span class=\"token operator\">:</span> <span class=\"token number\">5021</span><span class=\"token punctuation\">,</span>\n\t\t\t<span class=\"token property\">\"wc_avg\"</span><span class=\"token operator\">:</span> <span class=\"token number\">628</span>\n\t\t<span class=\"token punctuation\">}</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"how-it-works\" tabindex=\"-1\">How it works <a class=\"header-anchor\" href=\"#how-it-works\">#</a></h2>\n<h3 id=\"register-the-stats-plugin\" tabindex=\"-1\">Register the stats plugin <a class=\"header-anchor\" href=\"#register-the-stats-plugin\">#</a></h3>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> pluginStats <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./plugins/stats.js\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addPlugin</span><span class=\"token punctuation\">(</span>pluginStats<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h3 id=\"stats-js\" tabindex=\"-1\">Stats.js <a class=\"header-anchor\" href=\"#stats-js\">#</a></h3>\n<p>To make it as flexible as possible, we use a filter to pass in the posts to\ngenerate data for.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">buildStats</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">collection</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">/**\n * @param {UserConfig} eleventyConfig\n * @returns {void}\n */</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\televentyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"stats\"</span><span class=\"token punctuation\">,</span> buildStats<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>This <code>buildStats</code> is the meat of the stats page. You can use\n<a href=\"/2023/10/29/eleventy-wordcount/\">my wordcount plugin</a> to generate word counts for each\npost and then aggregate these word points by year, tag, or get a total. For my\nimplementation, see <a href=\"https://gitlab.com/rubenwardy/eleventy-stats-example\">https://gitlab.com/rubenwardy/eleventy-stats-example</a>.</p>\n<h3 id=\"html\" tabindex=\"-1\">HTML <a class=\"header-anchor\" href=\"#html\">#</a></h3>\n<p>The HTML page can pass in the post collection to get statistics:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">assign</span> stats <span class=\"token operator\">=</span> collections<span class=\"token punctuation\">.</span>post <span class=\"token operator\">|</span> <span class=\"token function filter\">stats</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>section</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>by-year<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h2</span><span class=\"token punctuation\">></span></span>By year<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h2</span><span class=\"token punctuation\">></span></span>\n\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>row row-wrap gap-4<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>col-md<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h3</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>mt-0 mb-3<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Number of posts<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h3</span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">include</span> <span class=\"token string\">\"bar_chart.html\"</span> data<span class=\"token operator\">:</span> stats<span class=\"token punctuation\">.</span>by_year<span class=\"token punctuation\">,</span> value_key<span class=\"token operator\">:</span> <span class=\"token string\">\"total\"</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>col-md<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h3</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>mt-0 mb-3<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Total words<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h3</span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">include</span> <span class=\"token string\">\"bar_chart.html\"</span> data<span class=\"token operator\">:</span> stats<span class=\"token punctuation\">.</span>by_year<span class=\"token punctuation\">,</span> value_key<span class=\"token operator\">:</span> <span class=\"token string\">\"wc_total\"</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>col-md<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h3</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>mt-0 mb-3<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Average post word count<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h3</span><span class=\"token punctuation\">></span></span>\n\t\t\t<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">include</span> <span class=\"token string\">\"bar_chart.html\"</span> data<span class=\"token operator\">:</span> stats<span class=\"token punctuation\">.</span>by_year<span class=\"token punctuation\">,</span> value_key<span class=\"token operator\">:</span> <span class=\"token string\">\"wc_avg\"</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\t\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n\t<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>section</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Hopefully, this was useful as an example on how to get started with creating\na writing stats page for your blog. The power of Eleventy is how easy it is\nto create custom logic and behaviour for your websites, so you should definitely\nmodify this for your needs.</p>\n<p>Again, a full working example is available at <a href=\"https://gitlab.com/rubenwardy/eleventy-stats-example\">https://gitlab.com/rubenwardy/eleventy-stats-example</a>.</p>\n","url":"https://blog.rubenwardy.com/2025/06/01/eleventy-stats/","summary":"A short tutorial on how to create a writing stats page for Eleventy.","date_published":"2025-06-01T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/FQ8hKd5l-9-540.png","tags":["snippet","blogging","tutorials","webdev"]},{"id":"/2025/05/25/fediverse-own-your-identity/","language":"en","title":"How can we own our identity on the Fediverse?","content_html":"<p>The Fediverse is a decentralised social media network. This includes Mastodon,\nwhich is where a majority of the users are, but it is not the only option. I\nhave been using Mastodon for seven years. I made my home on Fosstodon, an\ninstance focused on free and open source software.</p>\n<p>Last month, it came to light that Fosstodon had a far-right moderator and some\nother instances defederated with Fosstodon over this. I don’t want to get into\nthe specifics of this situation; whilst it is an important topic, it isn’t\nsomething I have any desire or qualification to discuss. I’ll leave this to\nother people. However, having to move instances did raise some thoughts about\nthe federation model and owning your identity and data.</p>\n<h2 id=\"owning-your-identity-and-data\" tabindex=\"-1\">Owning your identity and data <a class=\"header-anchor\" href=\"#owning-your-identity-and-data\">#</a></h2>\n<h3 id=\"the-problem\" tabindex=\"-1\">The problem <a class=\"header-anchor\" href=\"#the-problem\">#</a></h3>\n<p>When you join the fediverse, you need to find a home instance to sign up to.\nIt’s quite like email - you sign up to a domain, get a handle like\n<code>@rubenwardy@fosstodon.org</code>, and can then communicate with people on other\ndomains.</p>\n<p>This handle is your identity on the fediverse. If the instance you are on dies\nwithout warning or bans you<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup>, you have no recourse. There’s no way to\nmigrate your followers to another domain if you no longer have access to that\ndomain.</p>\n<p>Another issue is that you cannot bring your posts with you when moving\ninstances. Mastodon can “migrate” followers - you sign up for a new account,\nimport your following lists, and set up your old account to move followers\nacross to your new account. You lose all your posts as they are bound to the old\ninstance and old handle.</p>\n<h3 id=\"single-user-instances\" tabindex=\"-1\">Single-user instances <a class=\"header-anchor\" href=\"#single-user-instances\">#</a></h3>\n<p>Ideally, it would be possible to use your own domain as a fediverse identifier.</p>\n<p>You can do this already with a single-user instance. This is your own Mastodon\n(or alternative) instance, which you either self-host or pay a service to host\nfor you. This has a maintenance cost and means you don’t benefit from a local\ncommunity and moderation.</p>\n<p>I’m interested in finding out how practical single-user instances are for\ndiscovering content; they are less connected to other instances. I use hashtags\nquite extensively to find good content on Mastodon. A post will only appear in a\nhashtag if your local instance has been sent that post. As a result, single-user\ninstances will only see posts in hashtags by those they follow, or posts boosted\nby those they follow. It looks like you can solve this issue by connecting the\ninstance to an ActivityPub relay<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\"><span class=\"sr-only\">footnote </span>2</a></sup>, which will deliver posts from across\nthe Fediverse to you.</p>\n<h3 id=\"multi-tenancy\" tabindex=\"-1\">Multi-tenancy <a class=\"header-anchor\" href=\"#multi-tenancy\">#</a></h3>\n<p>One way that Mastodon could resolve this is by allowing custom domains on a\nsingle server. This is known as multi-tenancy. This would allow you to\nparticipate in a community server using your own domain as an identifier,\nretaining ownership of your identity and making migration seamless. There’s\n<a href=\"https://github.com/mastodon/mastodon/issues/2668\">a Mastodon issue</a> requesting\nsupport for this, but Mastodon developers aren’t currently planning to add such\na feature. This solution means you don’t fully own your data, however, but with\nfrequent backups this may not be a problem.</p>\n<p>I found a new fediverse server called <a href=\"https://jointakahe.org/\">Takahe</a> that\nis based around this premise of multi-tenancy, although it is unfortunately\n<a href=\"https://aeracode.org/2023/11/06/life-critical-side-projects/\">unmaintained</a>.</p>\n<h3 id=\"the-indie-web\" tabindex=\"-1\">The Indie Web <a class=\"header-anchor\" href=\"#the-indie-web\">#</a></h3>\n<p>The <a href=\"/2023/10/10/hello-indieweb/\">Indie Web</a> is a movement towards owning your\nown domain, using it as your identity, and owning your data. The Indie Web\napproach to this problem would be to post to your website first - as\n<a href=\"https://indieweb.org/note\">notes</a> - and have the posts automatically syndicated\nto Mastodon. I think this is a good idea for some of the more serious posts,\nlike project teasers, but I wouldn’t want all of my posts to be on my main\nwebsite or blog.</p>\n<h2 id=\"federation\" tabindex=\"-1\">Federation <a class=\"header-anchor\" href=\"#federation\">#</a></h2>\n<p>The Fosstodon situation also highlighted another issue - the brittle nature of\nthe fediverse. If the connections of 20,000 users can be broken because of the\nactions of a few people, then it doesn’t seem very sustainable. Perhaps the\nfuture is with smaller instances. Or perhaps it’s with “too big to block”\ninstances like <code>mastodon.social</code>. Who knows. I like smaller instances. If I\ncould own my identity and migrate my posts, then moving in the future wouldn’t\nbe as big of a problem.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I moved from Fosstodon to <a href=\"https://hachyderm.io/@rubenwardy\">Hachyderm</a>, but I’m\nthinking I might look into hosting a single-user instance using GotoSocial and\nimporting all my posts from Fosstodon into there.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>There’s two types of ban on Mastodon.\nFrozen accounts can set up account migrations. Suspended accounts cannot.\n<a href=\"https://github.com/mastodon/mastodon/issues/30811\">Issue</a>. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>See <a href=\"https://relay.fedi.buzz/\">https://relay.fedi.buzz/</a> and <a href=\"https://relaylist.com/\">https://relaylist.com/</a> <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2025/05/25/fediverse-own-your-identity/","summary":"Having to move instances from Fosstodon raises some thoughts about the federation model and owning your identity and data.","date_published":"2025-05-25T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/MnwaUVSnBe-540.png","tags":["blogging","reviews","opinion"]},{"id":"/2025/03/10/interesting-reads-2024/","language":"en","title":"My favourite articles of 2024","content_html":"<p>I read a lot of articles about programming and technology as a way of keeping up\nwith the industry and expanding my knowledge. Here are some articles I read in\n2024 that I recommend reading.</p>\n<p>2024 was a busy year for me so I didn’t read as much as in previous years.\nBut still, here’s my top articles.</p>\n<h2 id=\"artificial-intelligence\" tabindex=\"-1\">Artificial Intelligence <a class=\"header-anchor\" href=\"#artificial-intelligence\">#</a></h2>\n<p>The AI craze continues for another year. This first article is incredibly\nwell-written, it’s written by a data scientist with a lot of experience with\nmachine learning.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://ludic.mataroa.blog/blog/i-will-fucking-piledrive-you-if-you-mention-ai-again/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/e71QIyY94Y-60.webp 60w\" sizes=\"60px\"><img src=\"/static/img/e71QIyY94Y-60.svg\" alt=\"I Will Fucking Piledrive You If You Mention AI Again\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"60\" height=\"60\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tI Will Fucking Piledrive You If You Mention AI Again\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tLudicity</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>This second article talks about a more specific issue with AI: the quality\nof code it produces. AI regularly produces inaccessible and obscure code that\nlooks correct.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://joshcollinsworth.com/blog/copilot\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/LfRo0gdXHD-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/LfRo0gdXHD-375.png\" alt=\"I worry our Copilot is leaving some passengers behind\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"211\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tI worry our Copilot is leaving some passengers behind\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJosh Collingsworth</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"tech\" tabindex=\"-1\">Tech <a class=\"header-anchor\" href=\"#tech\">#</a></h2>\n<p>I found that this article resonated with me and made for a good read.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://mccue.dev/pages/8-16-24-just-use-postgres\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/AaFvDuObsK-48.webp 48w\" sizes=\"60px\"><img src=\"/static/img/AaFvDuObsK-48.png\" alt=\"Just use Postgres\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"48\" height=\"48\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tJust use Postgres\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tEthan McCue</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p>An interesting look into Google search.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://blog.zarfhome.com/2023/08/the-myth-of-google-plus\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/VHgRA21mx9-32.webp 32w\" sizes=\"60px\"><img src=\"/static/img/VHgRA21mx9-32.png\" alt=\"The myth of Google's plus\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"32\" height=\"32\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe myth of Google's plus\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tZarf Updates</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"blogging\" tabindex=\"-1\">Blogging <a class=\"header-anchor\" href=\"#blogging\">#</a></h2>\n<p>This article has a lot of ideas for things to do with your personal website.\nI found it interesting and even did a few of them!</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://jamesg.blog/2024/02/19/personal-website-ideas\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/ahBVRWT_RU-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/ahBVRWT_RU-1200.svg\" alt=\"100 things you can do on your personal website\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"1200\" height=\"630\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\t100 things you can do on your personal website\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJame's Coffee Blog</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I hope you found some articles worth reading. What were your favourite reads\nlast year? Feel free to send article suggestions you think I might like.</p>\n<p><a href=\"https://www.pexels.com/photo/clear-glass-teacup-with-coffee-beverage-2299028/\">Cover Image</a>\n© Engin Akyurt</p>\n","url":"https://blog.rubenwardy.com/2025/03/10/interesting-reads-2024/","summary":"Articles are a great way of keeping up with programming and technology. Here are some articles I read in 2024 that I recommend reading.","date_published":"2025-03-10T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/fBiLy502MT-540.png","tags":["blogging","year_in_review"]},{"id":"/2025/02/12/rvwp/","language":"en","title":"Ruben's Virtual World Project","content_html":"<p>Ruben’s Virtual World Project was a multiplayer top-down shooter with\nbase-building elements that was in development for five years, from 2015\nto 2020. It was a hybrid between a top-down shooter and a base-builder, allowing\nthe player to switch between Hero and Architect modes at any time.</p>\n<p>Whilst I started the project to learn about multiplayer networking at a low\nlevel - including latency mitigation, reliability, and prevention of cheating -\nit quickly turned into a project to make a game and game engine.</p>\n<h2 id=\"gameplay\" tabindex=\"-1\">Gameplay <a class=\"header-anchor\" href=\"#gameplay\">#</a></h2>\n<p>In Hero mode, the player directly controls the actions of their character. They\ncan explore freely, recruit, and fight. If the player’s character dies, then\nthey would need to make a new character and join the same world again.\nEventually, I planned on having a mission system requiring players to leave the\nbase to achieve certain narrative goals.</p>\n<figure class=\"undefined\"><a href=\"/static/img/hcUMSwwyEp-686.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/QJdfVVmOCk-540.webp 540w\"><img src=\"/static/img/QJdfVVmOCk-540.png\" alt=\"Hero mode, showing multiplayer\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"404\"></picture></a><figcaption>Hero mode, showing multiplayer</figcaption></figure>\n<p>In Architect mode, the player isn’t tied to any particular character and is able\nto issue orders. RVWP was heavily inspired by the colony simulation elements of\nRimWorld; I had been playing it quite a lot in 2015 and it remains my favourite\ngame to this day. In RVWP’s Architect mode, you can place blueprints for walls\nand queue jobs like cooking food. NPCs will build things and do jobs for you.</p>\n<figure class=\"undefined\"><a href=\"/static/img/z5GG_qT3fM-814.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/24zabsrqPU-540.webp 540w\"><img src=\"/static/img/24zabsrqPU-540.png\" alt=\"Architect mode\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"425\"></picture></a><figcaption>Architect mode</figcaption></figure>\n<p>While I never got around to developing the narrative or world of the game, the\nidea was for the game to be about managing a base of rebels hiding from the\nauthorities, and doing missions to further their cause. I thought that a group\nof magic users seeking freedom from prosecution might be a good setting for\nthis.</p>\n<h2 id=\"early-development\" tabindex=\"-1\">Early development <a class=\"header-anchor\" href=\"#early-development\">#</a></h2>\n<p>I wrote an article about the first two years of development here:\n<a href=\"/2017/08/13/multiplayer-topdown-sandbox-game/\">RVWP: Multiplayer Topdown Sandbox Game in C++</a>.</p>\n<p>By the mid-2017, I had a game with the following features:</p>\n<ul>\n<li>Infinite map</li>\n<li>Multiplayer</li>\n<li>Tile placement</li>\n<li>Multiple map z-levels (can go up and down stairs)</li>\n<li>Doors and stairs</li>\n<li>Player chat</li>\n<li>Game controller support</li>\n<li>Inventory and dual-wielding</li>\n<li>Weapons (guns and melee)</li>\n<li>Main menu</li>\n</ul>\n<h2 id=\"tech-dive\" tabindex=\"-1\">Tech dive <a class=\"header-anchor\" href=\"#tech-dive\">#</a></h2>\n<h3 id=\"world-lighting-and-rendering\" tabindex=\"-1\">World, lighting, and rendering <a class=\"header-anchor\" href=\"#world-lighting-and-rendering\">#</a></h3>\n<p>RVWP’s world was a 3D near-infinite voxel world rendered in 2D. It had voxel\nlighting based on a 3D flood fill algorithm outlined in a Seed of Andromeda blog\npost (no longer available).</p>\n<figure class=\"undefined\"><a href=\"/static/img/OTyw-uIQ7E-400.gif\"><img src=\"/static/img/ogXwDoCf6g-400.gif\" alt=\"The world is 3D drawn in 2D\" loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"300\"></a><figcaption>The world is 3D drawn in 2D</figcaption></figure>\n<p>Each tile was lit based on the direction of the light source. This was done\nusing a normal map for each tile. I wrote an\n<a href=\"/2019/04/24/rendering-multi-z-layer-topdown-map-using-SFML/\">article about how RVWP renders the world</a>.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md col-6\"><figure class=\"undefined\"><a href=\"/static/img/cEy_tM0g9u-553.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/GmDu3lChjC-540.webp 540w\"><img src=\"/static/img/GmDu3lChjC-540.png\" alt=\"Diffuse\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"542\"></picture></a><figcaption>Diffuse</figcaption></figure></div><div class=\"col-md col-6\"><figure class=\"undefined\"><a href=\"/static/img/5FEfxPe4Hh-553.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/L39CW2vBkE-540.webp 540w\"><img src=\"/static/img/L39CW2vBkE-540.png\" alt=\"Normals\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"542\"></picture></a><figcaption>Normals</figcaption></figure></div><div class=\"col-md col-6\"><figure class=\"undefined\"><a href=\"/static/img/HlxhS3It-l-553.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/EDJfhRK_PO-540.webp 540w\"><img src=\"/static/img/EDJfhRK_PO-540.png\" alt=\"Voxel lighting\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"542\"></picture></a><figcaption>Voxel lighting</figcaption></figure></div><div class=\"col-md col-6\"><figure class=\"undefined\"><a href=\"/static/img/Uze5aVL_4R-553.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/TDGGROsjZa-540.webp 540w\"><img src=\"/static/img/TDGGROsjZa-540.png\" alt=\"Result\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"542\"></picture></a><figcaption>Result</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"lua-scripting\" tabindex=\"-1\">Lua scripting <a class=\"header-anchor\" href=\"#lua-scripting\">#</a></h3>\n<p>RVWP had support for Lua scripting. All the content in the world, including\nfurniture, walls, and moving things, was defined in Lua scripts. Whilst I wasn’t\naiming to make a fully customisable engine, I wanted to keep as much gameplay in\nLua to make content creation and modding super easy.</p>\n<p>The Lua scripting API supported hot reloading. This means that it is possible to\nreload all mods at runtime, without restarting the game. This is incredibly\nconvenient for development but does come with some drawbacks - you need to be\nmuch more careful about how you store state. RVWP mods need to be written in a\nway that they do not store any important data themselves, they instead use RVWP\nAPIs to store and persist data in the engine.</p>\n<p>I learned a lot from the mistakes of <a href=\"https://www.luanti.org\">Luanti’s</a> API\nhere. One example is that the entity API is much cleaner, with a clear\nseparation between players and objects.</p>\n<figure class=\"mx-0\">\n\t<div class=\"row row-wrap gap-6\">\n\t\t<div class=\"col-md\"><pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token function\">print</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">print</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_pos</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></code></pre>\n</div>\n\t\t<div class=\"col-md\"><pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token function\">print</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">local</span> entity <span class=\"token operator\">=</span> player<span class=\"token punctuation\">:</span><span class=\"token function\">get_entity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token function\">print</span><span class=\"token punctuation\">(</span>entity<span class=\"token punctuation\">:</span><span class=\"token function\">get_pos</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span></code></pre>\n</div>\n\t</div>\n\t<figcaption class=\"text-center mt-4\">\n\t\tWhilst longer, separating players and entities makes the API much cleaner.\n\t</figcaption>\n</figure>\n<p>Another example is a much better tile timer API:</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">rvwp<span class=\"token punctuation\">.</span><span class=\"token function\">register_tile</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"stove\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    on_cook <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> layer<span class=\"token punctuation\">,</span> tile<span class=\"token punctuation\">)</span>\n        rvwp<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_all</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Stove cooked something!\"</span><span class=\"token punctuation\">)</span>\n\n        <span class=\"token keyword\">local</span> timer <span class=\"token operator\">=</span> rvwp<span class=\"token punctuation\">.</span><span class=\"token function\">get_timer</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> layer<span class=\"token punctuation\">,</span> <span class=\"token string\">\"cook\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>timer<span class=\"token punctuation\">:</span><span class=\"token function\">start</span><span class=\"token punctuation\">(</span><span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">--- somewhere else</span>\n<span class=\"token keyword\">local</span> <span class=\"token keyword\">function</span> <span class=\"token function\">start</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">local</span> timer <span class=\"token operator\">=</span> rvwp<span class=\"token punctuation\">.</span><span class=\"token function\">get_timer</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> rvwp<span class=\"token punctuation\">.</span>layers<span class=\"token punctuation\">.</span>tile<span class=\"token punctuation\">,</span> <span class=\"token string\">\"cook\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>timer<span class=\"token punctuation\">:</span><span class=\"token function\">start</span><span class=\"token punctuation\">(</span><span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span></code></pre>\n<p>You can read <a href=\"https://rubenwardy.com/rvwp/api/\">RVWP’s Lua API documentation</a>\nonline.</p>\n<h3 id=\"entities-and-npcs\" tabindex=\"-1\">Entities and NPCs <a class=\"header-anchor\" href=\"#entities-and-npcs\">#</a></h3>\n<p>RVWP allowed Lua scripts to define custom entities - moving things in the world.\nEntities are fully dynamic and can be changed at runtime. Non-Player Characters\nwere implemented in Lua, using engine APIs like pathfinding and tile searching.</p>\n<p>The only difference between a player entity and a Lua-created entity is which\ncode controls the entity. Both are instances of the <code>Entity</code> class; the former\nis controlled by <code>PlayerEntityController</code> and the latter by Lua code. Using a\nsingle class like this rather than inheritance made networking much simpler.\nOther clients don’t even know whether an entity is a player or Lua-controlled!</p>\n<p>NPC AI was implemented using Behavior Trees. They have the benefit of avoiding\ncomplicated state transition logic, allowing any state to respond correctly to\nthreats or other interruptions. The code for this in RVWP is custom, but the\ntrees themselves are edited using a GUI tool called\n<a href=\"https://github.com/pruttned/owl-bt\">owl-bt</a>. I wrote an entire\n<a href=\"/2022/07/17/game-ai-for-colonists/\">article about how the NPCs follow orders</a>.</p>\n<figure class=\"undefined\"><a href=\"/static/img/GLThTPtkRV-829.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/so6LLFs6Ip-540.webp 540w\"><img src=\"/static/img/so6LLFs6Ip-540.png\" alt=\"A behaviour tree\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"504\"></picture></a><figcaption>A behaviour tree</figcaption></figure>\n<h3 id=\"immediate-mode-debug-annotations\" tabindex=\"-1\">Immediate-mode debug annotations <a class=\"header-anchor\" href=\"#immediate-mode-debug-annotations\">#</a></h3>\n<p>When working on implementing Non-Player Characters, I wanted a tool to make it\neasier to see what is going on. I introduced an immediate-mode API to add debug\nannotations to the API.</p>\n<p>Immediate mode means that the annotations are added by\ncalling a function every tick rather than needing to make and persist objects\nfor each line and label.</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">if</span> rvwp<span class=\"token punctuation\">.</span>debug <span class=\"token keyword\">then</span>\n\t<span class=\"token keyword\">for</span> i <span class=\"token operator\">=</span> self<span class=\"token punctuation\">.</span>path_i <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">#</span>self<span class=\"token punctuation\">.</span>path <span class=\"token keyword\">do</span>\n\t\trvwp<span class=\"token punctuation\">.</span>debug<span class=\"token punctuation\">:</span><span class=\"token function\">draw_line</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">[</span>i <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> self<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"#999\"</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token keyword\">end</span>\n<span class=\"token keyword\">end</span></code></pre>\n<p>I used this tool to show planned paths and the internal AI state.</p>\n<figure class=\"undefined\"><a href=\"/static/img/9iZpPBVStF-700.webp\"><img src=\"/static/img/gW7bOdHkbC-700.webp\" alt=\"Immediate mode debug UI\" loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"700\"></a><figcaption>Immediate mode debug UI</figcaption></figure>\n<h2 id=\"cancellation\" tabindex=\"-1\">Cancellation <a class=\"header-anchor\" href=\"#cancellation\">#</a></h2>\n<p>In 2021, I became disillusioned with using C++ and SFML/SDL/OpenGL to make games.\nWorking at such a low level requires you to take on the task of implementing a\ngame engine as well, which is a huge disadvantage when you want to make a game.\nI decided to cancel RVWP to focus on making games faster by using engines such\nas Godot. I wrote an article about this decision titled\n<a href=\"/2023/07/14/make-games-not-game-engines/\">Make games not engines: why I’ll be using Godot engine</a></p>\n<p>The following ideas were never implemented:</p>\n<ul>\n<li>Semi-realistic combat, with knowledge of cover and hit stats.</li>\n<li>Procedurally generated missions to further your cause.</li>\n<li>Recruitment and loyalty.</li>\n<li>Traffic, pedestrians, and vehicles.</li>\n<li>Surveillance, AI police investigations, law and order.</li>\n</ul>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Whilst I am disappointed that RVWP never made it to be a game, it was an\nexcellent project for learning technology. It improved my knowledge of C++,\ngraphics, networking, and game engine development in general.</p>\n<p>No builds are available, but you can compile yourself from\n<a href=\"https://gitlab.com/rubenwardy/rvwp\">RVWP’s source code</a>.</p>\n","url":"https://blog.rubenwardy.com/2025/02/12/rvwp/","summary":"A multiplayer top-down shooter with base-building elements that was in development for five years","date_published":"2025-02-12T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/edf1JVK-k_-540.png","tags":["projects","gamedev","rvwp"]},{"id":"/2024/12/10/bean-there/","language":"en","title":"Bean There: An app to rate coffee shops","content_html":"<p>I made an app to rate coffee shops as an exercise to learn React Native. The app\nallows users to rate locations based on customisable categories. Whilst it was\ndesigned for coffee and coffee shops, it could be used to rate any kind of\nplace. The ratings are stored locally on the device and not uploaded anywhere.</p>\n<p>The original idea was to implement the same app in React Native, Jetpack\nCompose, and Flutter, but I’ve only made the React Native version so far. I’ve\nstarted the Jetpack Compose version and will hopefully have time to finish it at\nsome point.</p>\n<h2 id=\"but-why\" tabindex=\"-1\">But why? <a class=\"header-anchor\" href=\"#but-why\">#</a></h2>\n<p>For the last few years, a friend and I have been on a mission to find the best\ncafe in our city. I’m a big fan of coffee; she likes hot chocolate. Whenever we\nmeet up, we’ve been trying somewhere new and keeping a spreadsheet of scores for\neach place. You see, you can’t just have an opinion. You gotta back it up with\nhard subjective evidence. I rate based on the coffee’s appearance, bitterness,\ntaste and the shop’s location and comfort. I like to work in coffee shops so\nthat last part is important to me.</p>\n<p>Last year, I wrote about\n<a href=\"/2023/05/04/android/\">native Android development and why I want to learn cross-platform</a>.\nI’m quite a project-motivated person, I find it hard to learn something when I\ndon’t have a project in mind. I’ve been on the lookout for app ideas since\nwriting that article.</p>\n<p>Whilst perhaps of limited appeal, I realised that an app to replace my coffee\nrating spreadsheet is something that I might find useful. It’s also a large\nenough app to be useful as a project to learn and compare React Native and\nFlutter.</p>\n<h2 id=\"using-the-app\" tabindex=\"-1\">Using the app <a class=\"header-anchor\" href=\"#using-the-app\">#</a></h2>\n<p>When you open the app, you see a dashboard with an overview of recent ratings.\nYou can also search for a location anywhere in the world from this page. For\nease of use, results are biased by distance to your current location.</p>\n<figure class=\"undefined\"><a href=\"/static/img/N-E4LjeHsV-1440.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/5uREZySevE-540.webp 540w\"><img src=\"/static/img/5uREZySevE-540.png\" alt=\"Bean There dashboard, dark mode\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"1170\"></picture></a><figcaption>Bean There dashboard, dark mode</figcaption></figure>\n<p>The Chains tab allows you to see a list of coffee shops grouped by chain. You\ncan sort the list by A-Z or rating. Tapping a chain reveals a list of locations\nfor that chain.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/JEje-3h35N-1440.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/F2w7gvTBpf-540.webp 540w\"><img src=\"/static/img/F2w7gvTBpf-540.png\" alt=\"Bean There chains tab\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"510\"></picture></a><figcaption>Bean There chains tab</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/1GExEXjBre-1440.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/xIZQqOk5NJ-540.webp 540w\"><img src=\"/static/img/xIZQqOk5NJ-540.png\" alt=\"Bean There chains tab sorting\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"510\"></picture></a><figcaption>Bean There chains tab sorting</figcaption></figure></div>\n\t\t\t</div>\n<p>The location screen shows a gallery of photos and ratings. You can rate a\nlocation based on customisable categories.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/cpAjjqtsVE-1440.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/qsU5R9UzCT-540.webp 540w\"><img src=\"/static/img/qsU5R9UzCT-540.png\" alt=\"Bean There location\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"1170\"></picture></a><figcaption>Bean There location</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/lQEhsQou4n-1440.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/muVRBqYQZT-540.webp 540w\"><img src=\"/static/img/muVRBqYQZT-540.png\" alt=\"Bean There rating\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"1170\"></picture></a><figcaption>Bean There rating</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"react-native-and-expo\" tabindex=\"-1\">React-Native and Expo <a class=\"header-anchor\" href=\"#react-native-and-expo\">#</a></h2>\n<p>React-Native itself is quite a bare framework. Expo is a platform built on\nReact-Native that makes it much easier to develop apps, including support for\ngenerating the native code, live reloading of the app, and many helpful\nutilities. I chose to use Expo for these benefits.</p>\n<p>Expo heavily promotes Expo Application Services (EAS) - a commercial cloud\nservice for building and deploying Expo-based apps. I chose not to use this as I\ndon’t like having hard dependencies on proprietary services. I don’t like how\nheavily Expo’s documentation promotes EAS and how little they document building\nand publishing apps yourself.</p>\n<p>I use Redux and AsyncStorage to persist data. This stores select Redux state in\na JSON-like NoSQL way. I am a big fan of relational databases; I like\ndatabase-level constraints and easy relational queries. Using something so\nbarebones was a little disappointing but it is simple and just works. This\nmethod does load the entire data into memory at start up, which becomes more of\na problem the more I use the app. I also discovered that it has a timeout when\nloading the data, causing data loss when the saved data reached a certain size -\nluckily I had backups.</p>\n<h2 id=\"achieving-a-native-look-and-feel\" tabindex=\"-1\">Achieving a native look and feel <a class=\"header-anchor\" href=\"#achieving-a-native-look-and-feel\">#</a></h2>\n<p>I want to be able to make apps for Android and iOS that look and feel native but\nactually use a single codebase. Matching Android’s Material Design and iOS’s\nHuman Interface Guidelines is therefore quite important to me.</p>\n<p>Each platform has different conventions for navigation and components. Whilst\nmatching the look and feel of native components is nice, the most important\nthing is to match the feel and behaviour of native navigation conventions:</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/FSMWilcgmO-892.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/CZs_RCIVqy-540.webp 540w\"><img src=\"/static/img/CZs_RCIVqy-540.png\" alt=\"Android navigation\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"555\"></picture></a><figcaption>Android navigation</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/I0br7M05Ez-536.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/r0lcva8xAe-536.webp 536w\"><img src=\"/static/img/r0lcva8xAe-536.png\" alt=\"iOS navigation\" loading=\"lazy\" decoding=\"async\" width=\"536\" height=\"580\"></picture></a><figcaption>iOS navigation</figcaption></figure></div>\n\t\t\t</div>\n<p>Whilst React-Native claims to use native UI elements, I found that they do not\nlook or behave like native components by default. I ended up using a library\ncalled React-Native-Paper to do Material Design in the app, but this failed my\ngoal as the app looks like Material Design on iOS. I believe the better approach\nis to develop your own library of reusable components that change based on the\ntarget platform. I am now leading a React Native project at work and I’m finding\nthis to be much more effective.</p>\n<h2 id=\"testing\" tabindex=\"-1\">Testing <a class=\"header-anchor\" href=\"#testing\">#</a></h2>\n<p>I used Detox for end-to-end UI testing. It was quite a pain to use. This is a\ncommon problem with e2e UI testing and it limits the number and quality of\ntests. Testing needs to be as easy as possible to be most effective.</p>\n<p>I also used React Native component tests - these are unit tests for React\ncomponents, allowing you to simulate user actions on a component and mock\ndependencies. I found this to be much more effective at exhaustively testing a\nlot of components, although it was a pain for anything that used async\nbehaviour.</p>\n<p>In the future, I’d like to look into using\n<a href=\"https://storybook.js.org/\">Storybook</a> as an alternative to component tests.\nI’ve been recommended it a few times by people with more experience in React\nNative than me.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>This was a nice project to learn React Native and put me in a good position to\nstart a React Native project at work. The speed at which you can introduce new\nuser interfaces is quite addicting.</p>\n<p>I could do without the pain of JavaScript land, the number of strange errors you\nget is just quite tiring. I also wish that React Native made it easier to make\nnative-themed apps by default. I’d love to see a UI library for React Native\nwith this as a goal.</p>\n","url":"https://blog.rubenwardy.com/2024/12/10/bean-there/","summary":"I made an app to rate coffee shops as an exercise to learn React Native.","date_published":"2024-12-10T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/atNjQi9mq8-540.png","tags":["projects","android"]},{"id":"/2024/06/07/firefox-is-awesome/","language":"en","title":"Why I use Firefox","content_html":"<p>Firefox has been my browser of choice ever since I was first recommended it\ntwelve years ago. It has the extensions I like and it supports the open web.</p>\n<h2 id=\"my-favourite-extensions\" tabindex=\"-1\">My favourite extensions <a class=\"header-anchor\" href=\"#my-favourite-extensions\">#</a></h2>\n<h3 id=\"tree-style-tab\" tabindex=\"-1\">Tree Style Tab <a class=\"header-anchor\" href=\"#tree-style-tab\">#</a></h3>\n<p>The <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/tree-style-tab/\">Tree Style Tab</a>\nextension shows tabs on the left as a hierarchical list. This is great for\nresearch; you can still read all the titles when there are a lot of tabs open.\nYou also get a scrollable tree structure for your deep dives, which you can use\nto capture and group similar tabs during research. It’s possible to collapse\nnodes and bookmark entire trees.</p>\n<figure class=\"undefined\"><a href=\"/static/img/BD1XI8H1q1-489.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/qDqj7VqPRq-489.webp 489w\"><img src=\"/static/img/qDqj7VqPRq-489.png\" alt=\"Tree Style Tab makes learning and researching so much easier.\" loading=\"lazy\" decoding=\"async\" width=\"489\" height=\"422\"></picture></a><figcaption>Tree Style Tab makes learning and researching so much easier.</figcaption></figure>\n<p>My monitors are horizontal but most web pages are vertical. I like having the\ntabs to the left as it frees up vertical space. To remove the default tabs at\nthe top, I needed to modify Firefox’s stylesheet (userChrome.css) as extensions\nare not allowed to do that. This was quite straightforward, however.</p>\n<p>Tree Style Tab does not exist for Chrome as it’s not possible to make using the\nChrome API. The closest you can get is a pop-out menu for the list. Edge does\nhave built-in support for a similar feature called\n<a href=\"https://www.microsoft.com/en-us/edge/features/vertical-tabs?form=MA13FJ\" rel=\"nofollow\">vertical tabs</a>,\nbut this just shows tabs on the left and doesn’t have a tree structure.</p>\n<h3 id=\"containers\" tabindex=\"-1\">Containers <a class=\"header-anchor\" href=\"#containers\">#</a></h3>\n<p>The <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/multi-account-containers/\">Containers</a>\nextension allows you to open tabs in different containers, each with its own\ncookies and sessions. This allows you to log into a website multiple times, with\ndifferent accounts. It’s useful for keeping something separate from the rest of\nyour browsing data, like work. This is similar to using multiple profiles but\nis per tab rather than requiring a new window.</p>\n<figure class=\"undefined\"><a href=\"/static/img/j0pQtjGfgU-402.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/sIq_ZEdv4a-402.webp 402w\"><img src=\"/static/img/sIq_ZEdv4a-402.png\" alt=\"Tabs opened in different containers.\" loading=\"lazy\" decoding=\"async\" width=\"402\" height=\"110\"></picture></a><figcaption>Tabs opened in different containers.</figcaption></figure>\n<p>You can tell the Containers extension to always open a certain website in a\ncontainer. The <a href=\"https://addons.mozilla.org/en-GB/firefox/addon/facebook-container/\">Facebook Container</a>\nextension builds on this by always opening Meta-owned domains in a Facebook\ncontainer and blocking tracking.</p>\n<h2 id=\"supporting-the-open-web-and-standards\" tabindex=\"-1\">Supporting the open web and standards <a class=\"header-anchor\" href=\"#supporting-the-open-web-and-standards\">#</a></h2>\n<p>The web has been successful because it is an open platform; it’s not owned and\nlocked down by a single company. This makes it super accessible, opening it up\nto a large range of users, devices, and websites.</p>\n<p>The real reason why Internet Explorer was bad wasn’t because it lacked features.\nFor a time, Internet Explorer was actually ahead of the curve. Internet Explorer\nwas bad because it had a near-monopoly on web browsers, holding back web\ndevelopment for years. Microsoft abused their position to push Internet Explorer\nto users and introduced proprietary extensions to the web whilst flaunting\nactual standards. This meant that a lot of web development wasn’t <em>web</em>\ndevelopment - but developing for Internet Explorer. It prevented developers from\nusing newer web features and it prevented users from switching to other browsers\nas some websites relied on Internet Explorer quirks<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup>.</p>\n<p>Today, Google Chrome dominates web browser market share. A lot of so-called\ncompetitors, like Edge and Brave, are just Chromium reskins. Over 70% of web\nusers use a browser based on Chromium and 88% of web users use a browser derived\nfrom WebKit<sup class=\"footnote-ref\"><a href=\"#fn2\" id=\"fnref2\"><span class=\"sr-only\">footnote </span>2</a></sup>, the web engine used by Chrome and Safari<sup class=\"footnote-ref\"><a href=\"#fn3\" id=\"fnref3\"><span class=\"sr-only\">footnote </span>3</a></sup>. Many\ndevelopers just test on Chrome and Chrome-based browsers. This gives Google a\nlot of power over the web.</p>\n<p>Firefox maintains the second most popular browser engine outside of\nChromium/Safari’s Blink/Webkit. Using Firefox contributes to its user base and\nkeeps browser engine diversity alive. Mozilla (despite their missteps)\nconstantly champion the open web. One example I have benefited from is\n<a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions\">WebExtensions</a>, an open cross-platform browser extension API compatible with Chrome\nExtensions.</p>\n<p>I have contributed to Firefox development by\n<a href=\"https://bugzilla.mozilla.org/buglist.cgi?query_format=advanced&amp;emailtype1=exact&amp;emailreporter1=1&amp;email1=rw%40rubenwardy.com&amp;list_id=17019898\">reporting bugs</a>.\nIt would be pretty cool to contribute to Firefox’s code one day.</p>\n<h2 id=\"adware-companies\" tabindex=\"-1\">Adware companies <a class=\"header-anchor\" href=\"#adware-companies\">#</a></h2>\n<p>Google is a company that makes its money from ads. This introduces a conflict of\ninterest with Chrome’s users. Google is in the process of moving to a new\nextensions API called Manifest V3, which will limit the capability of\nadblockers<sup class=\"footnote-ref\"><a href=\"#fn4\" id=\"fnref4\"><span class=\"sr-only\">footnote </span>4</a></sup><sup class=\"footnote-ref\"><a href=\"#fn5\" id=\"fnref5\"><span class=\"sr-only\">footnote </span>5</a></sup><sup class=\"footnote-ref\"><a href=\"#fn6\" id=\"fnref6\"><span class=\"sr-only\">footnote </span>6</a></sup>. Mozilla is a non-profit that does not make\nmoney from ads; Firefox is implementing support for Manifest V3 without limiting\nadblockers.</p>\n<p>Cross-site cookies negatively impact privacy on the web<sup class=\"footnote-ref\"><a href=\"#fn7\" id=\"fnref7\"><span class=\"sr-only\">footnote </span>7</a></sup>. It is one method\ncompanies use to track you across many websites. Many browsers, including\nFirefox and Safari, have already blocked cross-site cookies by\ndefault<sup class=\"footnote-ref\"><a href=\"#fn8\" id=\"fnref8\"><span class=\"sr-only\">footnote </span>8</a></sup>. Because of Google’s conflict of interest, Chrome has not\nblocked third-party cookies yet. Instead, Google is working on new browser APIs\nthat track you, such as Federated Learning of Cohorts (FLoC) and\nTopics<sup class=\"footnote-ref\"><a href=\"#fn9\" id=\"fnref9\"><span class=\"sr-only\">footnote </span>9</a></sup>.</p>\n<p>I commonly see people recommend Brave in response to this, Brave is a\nChromium-based browser with a built-in adblocker. I absolutely do not recommend\nusing Brave as they have shown themselves to be untrustworthy in\n<a href=\"https://www.spacebar.news/p/stop-using-brave-browser\">multiple ways</a>.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I like using Firefox and will continue doing so for the foreseeable future.\nHopefully, this article will persuade some to give it a chance too.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><a href=\"https://www.wired.com/2016/01/the-sorry-legacy-of-microsoft-internet-explorer/\">The Sorry Legacy of Internet Explorer - Wired</a> <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p><a href=\"https://gs.statcounter.com/browser-market-share\">Browser market share - Statcounter</a> <a href=\"#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>Chrome’s <a href=\"https://en.wikipedia.org/wiki/Blink_(browser_engine)\">Blink</a> browser engine is based on Safari’s WebKit, but has received a lot of changes. <a href=\"#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p><a href=\"https://arstechnica.com/gadgets/2023/11/google-chrome-will-limit-ad-blockers-starting-june-2024/\">Google Chrome will limit ad blockers starting June 2024 - Ars Technica</a> <a href=\"#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p><a href=\"https://github.com/uBlockOrigin/uBOL-home/wiki/Frequently-asked-questions-(FAQ)\">FAQ - uBlock Origin Lite</a> <a href=\"#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p><a href=\"https://www.theregister.com/2024/08/06/chrome_web_store_warns_end/\">Chrome Web Store warns end is nigh for uBlock Origin - The Register</a> <a href=\"#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p><a href=\"https://www.w3.org/blog/2024/third-party-cookies-have-got-to-go/\">Third-party cookies have got to go - W3C</a> <a href=\"#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn8\" class=\"footnote-item\"><p><a href=\"https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/\">Saying goodbye to third-party cookies in 2024 - MDN</a> <a href=\"#fnref8\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn9\" class=\"footnote-item\"><p><a href=\"https://www.eff.org/deeplinks/2023/09/how-turn-googles-privacy-sandbox-ad-tracking-and-why-you-should\">How To Turn Off Google’s “Privacy Sandbox” Ad Tracking—and Why You Should - EFF</a> <a href=\"#fnref9\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2024/06/07/firefox-is-awesome/","summary":"Firefox has been my browser of choice ever since I was first recommended it twelve years ago. It has the extensions I like and it supports the open web.","date_published":"2024-06-07T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/CQfQ7HKJs--540.png","tags":["reviews","opinion"]},{"id":"/2024/05/24/detox-permissions/","language":"en","title":"Detox and React Native: UI testing Android permission flows","content_html":"<p>I’m currently learning React Native and Expo as an alternative to\n<a href=\"/2023/05/04/android/\">native app development</a>. To do this, I’ve been\nworking on an app so that I can get hands-on experience with React Native app\ndevelopment. This includes end-to-end UI testing, using Detox.</p>\n<p>I was surprised that Detox doesn’t have an API to interact with permissions. The\n<code>device.launchApp</code> function does have a <code>permissions</code> field, but this only works\non iOS and runs on app start-up - it doesn’t allow you to test the actual user\nflow.</p>\n<p>In this article, I will explain how you can do end-to-end testing with Android\npermissions, including simulating user interaction with the Android permission\nrequest modal.</p>\n<h2 id=\"starting-the-app-without-permissions\" tabindex=\"-1\">Starting the app without permissions <a class=\"header-anchor\" href=\"#starting-the-app-without-permissions\">#</a></h2>\n<p>By default, Detox will start the app with all permissions granted. This will not\nbe the case when an actual user runs the app. To work around this, we’ll need to\nreset all permissions using adb.</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> expect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"detox\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> execSync <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"child_process\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> packageName <span class=\"token operator\">=</span> <span class=\"token string\">\"com.example.app_package_name\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">resetPermissions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell pm reset-permissions -p </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>packageName<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Permission request card\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">beforeAll</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token function\">resetPermissions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token keyword\">await</span> device<span class=\"token punctuation\">.</span><span class=\"token function\">launchApp</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n\t\t\tnewInstance<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n\t\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"programmatically-granting-and-revoking-permissions\" tabindex=\"-1\">Programmatically granting and revoking permissions <a class=\"header-anchor\" href=\"#programmatically-granting-and-revoking-permissions\">#</a></h2>\n<p>You may want to programmatically grant or revoke a permission without having to\ngo through the system UI. You can do that using the following adb commands:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">function</span> <span class=\"token function\">grantPermission</span><span class=\"token punctuation\">(</span>permission<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell pm grant </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>packageName<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>permission<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">revokePermission</span><span class=\"token punctuation\">(</span>permission<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell pm revoke </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>packageName<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>permission<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// Inside a test:</span>\n<span class=\"token function\">grantPermission</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"android.permission.ACCESS_FINE_LOCATION\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">revokePermission</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"android.permission.ACCESS_FINE_LOCATION\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"interacting-with-the-android-permission-request-modal\" tabindex=\"-1\">Interacting with the Android permission request modal <a class=\"header-anchor\" href=\"#interacting-with-the-android-permission-request-modal\">#</a></h2>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/LvKBQzJi8u-1080.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/oDN81WT0Ch-540.webp 540w\"><img src=\"/static/img/oDN81WT0Ch-540.png\" alt=\"A card with a permission justification.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"894\"></picture></a><figcaption>A card with a permission justification.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/ky-Y0jvgQa-1080.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/MSIXiSmk4r-540.webp 540w\"><img src=\"/static/img/MSIXiSmk4r-540.png\" alt=\"The Android permission request modal.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"894\"></picture></a><figcaption>The Android permission request modal.</figcaption></figure></div>\n\t\t\t</div>\n<p>Let’s say you have a UI where the user is shown a permission justification. They\ncan then click a button to open up the permission request modal and grant\npermission.</p>\n<h3 id=\"using-key-events-to-control-system-uis\" tabindex=\"-1\">Using key events to control system UIs <a class=\"header-anchor\" href=\"#using-key-events-to-control-system-uis\">#</a></h3>\n<p>Unfortunately, Detox does not provide an API for interacting with System UIs\nlike the permission request modal.<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup> Instead, we can use adb to interact with\nthe modal using the keyboard.</p>\n<p>Detox has a feature called synchronization, where it waits for the app to become\nidle before proceeding with the test. This means that <code>tap()</code> will freeze when a\nsystem UI modal is open. We’ll need to disable this before tapping.</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> expect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"detox\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> execSync <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"child_process\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> Key_Tab <span class=\"token operator\">=</span> <span class=\"token string\">\"KEYCODE_TAB\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> Key_Enter <span class=\"token operator\">=</span> <span class=\"token string\">\"KEYCODE_ENTER\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">sleep</span><span class=\"token punctuation\">(</span>ms<span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token builtin\">Promise</span><span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">></span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\"><span class=\"token builtin\">Promise</span></span><span class=\"token punctuation\">(</span>resolve <span class=\"token operator\">=></span> <span class=\"token function\">setTimeout</span><span class=\"token punctuation\">(</span>resolve<span class=\"token punctuation\">,</span> ms<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">pressKeys</span><span class=\"token punctuation\">(</span>keys<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> key <span class=\"token keyword\">of</span> keys<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell input keyevent </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>key<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">sleep</span><span class=\"token punctuation\">(</span><span class=\"token number\">100</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Permission request card\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should ask for permission\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">element</span><span class=\"token punctuation\">(</span>by<span class=\"token punctuation\">.</span><span class=\"token function\">id</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"current-location-msg\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toHaveText</span><span class=\"token punctuation\">(</span>\n\t\t\t<span class=\"token string\">\"Grant location permission to see nearby locations here.\"</span>\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// Disable synchronization, otherwise \"tap\" will never return due</span>\n\t\t<span class=\"token comment\">// to the appearance of System UI</span>\n\t\t<span class=\"token keyword\">await</span> device<span class=\"token punctuation\">.</span><span class=\"token function\">disableSynchronization</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">element</span><span class=\"token punctuation\">(</span>by<span class=\"token punctuation\">.</span><span class=\"token function\">id</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"btn-grant\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">tap</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">sleep</span><span class=\"token punctuation\">(</span><span class=\"token number\">1000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">await</span> device<span class=\"token punctuation\">.</span><span class=\"token function\">enableSynchronization</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// Use the keyboard to select the button</span>\n\t\t<span class=\"token comment\">//</span>\n\t\t<span class=\"token comment\">// For location permissions, there are 4 tabs as there is a selector</span>\n\t\t<span class=\"token comment\">// between precise and coarse locations.</span>\n\t\t<span class=\"token comment\">//</span>\n\t\t<span class=\"token comment\">// For other permissions, you may only need 2 tabs.</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">pressKeys</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span> Key_Tab<span class=\"token punctuation\">,</span> Key_Tab<span class=\"token punctuation\">,</span> Key_Tab<span class=\"token punctuation\">,</span> Key_Tab<span class=\"token punctuation\">,</span> Key_Enter <span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t\t<span class=\"token comment\">// Check UI updates</span>\n\t\t<span class=\"token keyword\">await</span> <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">element</span><span class=\"token punctuation\">(</span>by<span class=\"token punctuation\">.</span><span class=\"token function\">id</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"current-location-msg\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>not<span class=\"token punctuation\">.</span><span class=\"token function\">toHaveText</span><span class=\"token punctuation\">(</span>\n\t\t\t<span class=\"token string\">\"Grant location permission to see nearby locations here.\"</span>\n\t\t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"using-uiautomator-and-touch-events-to-control-system-uis\" tabindex=\"-1\">Using uiautomator and touch events to control system UIs <a class=\"header-anchor\" href=\"#using-uiautomator-and-touch-events-to-control-system-uis\">#</a></h3>\n<p>In the above solution, we hardcode the number of “tab” presses to select the\ncorrect button in the system modal. This is a fairly hacky solution that is\nlikely to break on Android OS updates.</p>\n<p>A much better solution would be using <code>uiautomator</code> to find the on-screen\nposition of the button we’re looking for. This is more complicated but should be\nmore reliable. To do this, we can use the dump command to export an xml file\nrepresenting the GUI hierarchy:</p>\n<pre class=\"language-sh\"><code class=\"language-sh\">adb shell uiautomator dump\nadb shell <span class=\"token function\">cat</span> /sdcard/window_dump.xml</code></pre>\n<p>The button looks like this in the XML:</p>\n<pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>node</span> <span class=\"token attr-name\">index</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>0<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>While using the app<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">resource-id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.android.permissioncontroller:id/permission_allow_foreground_only_button<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>android.widget.Button<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">package</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.google.android.permissioncontroller<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">content-desc</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span><span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">checkable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">checked</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">clickable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">enabled</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">focusable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>true<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">focused</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">scrollable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">long-clickable</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">password</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span>\n\t<span class=\"token attr-name\">selected</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>false<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">bounds</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>[137,1273][943,1427]<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span></code></pre>\n<p>With an XML parser, we can find the correct node and read the “bounds”\nattribute:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> <span class=\"token constant\">JSDOM</span> <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"jsdom\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">findSystemUINodePosition</span><span class=\"token punctuation\">(</span>resourceId<span class=\"token operator\">:</span> <span class=\"token builtin\">string</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token builtin\">number</span><span class=\"token punctuation\">,</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">|</span> <span class=\"token keyword\">undefined</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token comment\">// Dump window hierarchy</span>\n\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell uiautomator dump</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">const</span> windowDump <span class=\"token operator\">=</span> <span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell cat /sdcard/window_dump.xml</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\t<span class=\"token comment\">// Parse XML</span>\n\t<span class=\"token keyword\">const</span> jsdom <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\"><span class=\"token constant\">JSDOM</span></span><span class=\"token punctuation\">(</span>windowDump<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> contentType<span class=\"token operator\">:</span> <span class=\"token string\">\"text/xml\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">const</span> document <span class=\"token operator\">=</span> jsdom<span class=\"token punctuation\">.</span>window<span class=\"token punctuation\">.</span>document<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token comment\">// Find the node</span>\n\t<span class=\"token keyword\">const</span> buttonNode <span class=\"token operator\">=</span> document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">node[resource-id='</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>resourceId<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">']</span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>buttonNode<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Unable to find button\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">undefined</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// Get bounds</span>\n\t<span class=\"token keyword\">const</span> bounds <span class=\"token operator\">=</span> buttonNode<span class=\"token punctuation\">.</span><span class=\"token function\">getAttribute</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"bounds\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">const</span> parts <span class=\"token operator\">=</span> bounds<span class=\"token operator\">?.</span><span class=\"token function\">match</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]</span><span class=\"token regex-delimiter\">/</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>parts<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token builtin\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Unable to match bounds\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\t\t<span class=\"token keyword\">return</span> <span class=\"token keyword\">undefined</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token comment\">// Return center point</span>\n\t<span class=\"token keyword\">const</span> x <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>parts<span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>parts<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">2</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">const</span> y <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>parts<span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>parts<span class=\"token punctuation\">[</span><span class=\"token number\">4</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">2</span><span class=\"token punctuation\">;</span>\n\t<span class=\"token keyword\">return</span> <span class=\"token punctuation\">[</span>x<span class=\"token punctuation\">,</span> y<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>We can then tap on the button:</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">import</span> jestExpect <span class=\"token keyword\">from</span> <span class=\"token string\">\"expect\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">tapAt</span><span class=\"token punctuation\">(</span>x<span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">,</span> y<span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token function\">execSync</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">adb -e -s </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>device<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> shell input tap </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>x<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>y<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// Instead of \"pressKeys\" above</span>\n<span class=\"token keyword\">const</span> resourceId <span class=\"token operator\">=</span> <span class=\"token string\">\"com.android.permissioncontroller:id/permission_allow_foreground_only_button\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> point <span class=\"token operator\">=</span> <span class=\"token function\">findSystemUINodePosition</span><span class=\"token punctuation\">(</span>resourceId<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">jestExpect</span><span class=\"token punctuation\">(</span>point<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeTruthy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">tapAt</span><span class=\"token punctuation\">(</span>point<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> point<span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Hopefully, Detox will have built-in support for permissions in the future.\nUntil then, I hope this article is helpful to others.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Detox feature request for a permissions API: <a href=\"https://github.com/wix/Detox/issues/2184\">https://github.com/wix/Detox/issues/2184</a> <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2024/05/24/detox-permissions/","summary":"In this article, I will explain how you can do end-to-end testing with Android permissions, including interaction with the Android permission request modal.","date_published":"2024-05-24T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/2Hpsln9V1M-540.png","tags":["snippet","tutorials","android"]},{"id":"/2024/03/08/typescript-is-awesome/","language":"en","title":"Why I like and recommend using TypeScript","content_html":"<p>I’ve recently gotten into using TypeScript when working with web technology, for\ncreating web apps and servers. I find that TypeScript makes JavaScript more\nenjoyable to use. In this article, I’ll explain my reasons why.</p>\n<h2 id=\"improves-the-developer-experience\" tabindex=\"-1\">Improves the developer experience <a class=\"header-anchor\" href=\"#improves-the-developer-experience\">#</a></h2>\n<p>The number one benefit for me is that it massively improves the developer\nexperience without impacting the agility of an interpreted language. TypeScript\ncombined with a supported IDE like VSCode(ium) delivers a great experience.</p>\n<h3 id=\"makes-the-ide-more-helpful\" tabindex=\"-1\">Makes the IDE more helpful <a class=\"header-anchor\" href=\"#makes-the-ide-more-helpful\">#</a></h3>\n<p>TypeScript provides type information that helps the IDE help you. Whilst type\ninformation is supported for JavaScript, it’s not as reliable or fully featured.</p>\n<p>With type information, the IDE can show relevant suggestions for methods and\nmembers (ie: IntelliSense), allowing you to see what is inside an object. This\nincreases productivity. TypeScript makes refactoring operations like renaming\nconsiderably more reliable as it can accurately find all the uses of a symbol.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Dmj8IEK8QO-1057.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/vHZnJzxY5P-1057.webp 1057w\"><img src=\"/static/img/vHZnJzxY5P-1057.png\" alt=\"VSCode listing the members of an interface\" loading=\"lazy\" decoding=\"async\" width=\"1057\" height=\"169\"></picture></a><figcaption>VSCode listing the members of an interface</figcaption></figure>\n<h3 id=\"types-as-documentation\" tabindex=\"-1\">Types as documentation <a class=\"header-anchor\" href=\"#types-as-documentation\">#</a></h3>\n<p>Defining types acts as a form of documentation. This doesn’t replace actual\ndocumentation but instead supplements it - it tells you the types that an API\nexpects.</p>\n<p>Here’s the signature of a function without any comments. Just the argument names\nand the types are enough to get an idea of how to use it.</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token function\">findPath</span><span class=\"token punctuation\">(</span>from<span class=\"token operator\">:</span> Vector2<span class=\"token punctuation\">,</span> to<span class=\"token operator\">:</span> Vector2<span class=\"token punctuation\">,</span> maxDistance<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token builtin\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Vector2<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span></code></pre>\n<p>An alternative to TypeScript is JSDoc. This allows you to document the types in\nJavaScript using comments instead of type annotations. This is a good stopgap if\nyou have an existing code base in JavaScript, but is quite verbose and not as\neffective.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">/**\n * @param {Vector2} from\n * @param {Vector2} to\n * @param {Vector2} [maxDistance]\n * @returns {Vector2[]} the path\n */</span>\n<span class=\"token function\">findPath</span><span class=\"token punctuation\">(</span>from<span class=\"token punctuation\">,</span> to<span class=\"token punctuation\">,</span> maxDistance<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"static-linting-errors-for-types-and-mistakes\" tabindex=\"-1\">Static linting: errors for types and mistakes <a class=\"header-anchor\" href=\"#static-linting-errors-for-types-and-mistakes\">#</a></h2>\n<p>A secondary benefit for me is that TypeScript will emit compile errors when\ntypes do not match. This is very useful for discovering a lot of different\nproblems.</p>\n<p>One example of a situation where this helps is when dealing with optional types.\nTypeScript will tell you if you try to access an optional type without first\nchecking for null/undefined. I find that in a lot of languages, nullability\ncauses a lot of issues. It’s a big issue in Java, for example, and one of the\nreasons why I prefer Kotlin. Remember the types-as-documentation argument as\nwell: having nullability in the types tells you, as the developer, whether you\nneed to handle the null case.</p>\n<p>Type checking is also very useful when refactoring code and updating\ndependencies, as it gives you compile errors for invocations that don’t match\nchanges made.</p>\n<p>If you’re doing development properly, you should have unit tests and code\nreviews. These have a greater impact on code quality and correctness than\nTypeScript does, but it doesn’t negate the benefit completely.</p>\n<h3 id=\"compile-time-is-better-than-run-time\" tabindex=\"-1\">Compile-time is better than run-time <a class=\"header-anchor\" href=\"#compile-time-is-better-than-run-time\">#</a></h3>\n<p>A common complaint II’ve read about TypeScript is that it only does compile-time\ntype checking and not runtime. It’s important to note that compile-time checks\nare better than run-time checks. It’s good to catch as many errors as possible\nat compile-time, as run-time is slower and less reliable. Compile-time errors\nare fast enough to be shown in your IDE as you edit the code.</p>\n<p>C and C++ are considered to be strongly typed languages and rely almost entirely\non compile-time type checking. The “static” in <em>static</em> type checking means that\nthe types are verified at compile-time. In C, the concept of types doesn’t even\nexist at runtime. C++ has\n<a href=\"https://en.wikipedia.org/wiki/Run-time_type_information\">Run-Time Type Information (RTTI)</a>,\nwhich allows you to do safe casts at run-time (<code>std::dynamic_cast</code>) and also get\ntype information (<code>typeid</code> and <code>std::type_info</code>). These features are useful but\nnot essential - it’s common for developers to disable RTTI to reduce program\nsize in embedded applications. Now, perhaps C/C++ isn’t the best role model when\nit comes to type safety. Rust would be a better example, as it has a\nwell-written compiler and is memory-safe.</p>\n<p>So, what’s the problem here with TypeScript? The problem isn’t that it only does\ncompile-time checks, the problem is that incorrect types can enter the program.\nFirst, incorrect types can enter the program through third-party dependencies.\nThis is especially common when the dependencies are written in JavaScript.\nSecond, TypeScript has dangerous operations that can result in incorrect types.\nFor example, type assertions (<code>as T</code>) allow you to tell the compiler that a\nsymbol is a certain type even if this isn’t true. The use of <code>any</code> is quite\ndangerous as well.</p>\n<p>It’s possible for compile-time checking to be fully capable if you limit the use\nof dangerous operations. You can use <code>eslint</code> to enforce this in a project.</p>\n<h3 id=\"dealing-with-untyped-data\" tabindex=\"-1\">Dealing with untyped data <a class=\"header-anchor\" href=\"#dealing-with-untyped-data\">#</a></h3>\n<p>Incorrect types can enter a TypeScript program when interacting with untyped\ndependencies and data. One way to ensure this doesn’t happen is by using a\nlibrary like Zod. <a href=\"https://github.com/colinhacks/zod\">Zod</a> is a TypeScript\nvalidation library that allows you to check unknown data using a schema. It also\ngenerates TypeScript types, allowing schemas to work seamlessly with TypeScript.</p>\n<pre class=\"language-ts\"><code class=\"language-ts\"><span class=\"token keyword\">const</span> User <span class=\"token operator\">=</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">object</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n\tusername<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\tfull_name<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">string</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\tage<span class=\"token operator\">:</span> z<span class=\"token punctuation\">.</span><span class=\"token function\">number</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> User<span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span><span class=\"token constant\">JSON</span><span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>json<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// `user` will have the correct TypeScript type.</span></code></pre>\n<p>Another cool thing is that Zod can transform data for you whilst validating. If\nan API gives you a date timestamp in ISO format, you can use <code>z.date()</code> to\nconvert that string to a JS <code>Date</code> object. You can also provide custom transform\nfunctions to validate and manipulate the data. This makes it a very powerful\ntool for dealing with untyped data.</p>\n<h2 id=\"drawbacks\" tabindex=\"-1\">Drawbacks <a class=\"header-anchor\" href=\"#drawbacks\">#</a></h2>\n<p>Like any technology, TypeScript does have some drawbacks. The main one for me is\nthat it adds complexity to a project. You need to set up a build system to\nprocess TypeScript into JavaScript. I don’t think this is a problem once a\nproject reaches a certain size, but if I’m just writing a few small scripts for\na website I’ll use plain JavaScript.</p>\n<p>A lot of people say they have issues with TypeScript types, but I’m not too\nworried about this. The majority of my code is fairly straightforward type wise.\nI have had more issues with Webpack and NPM modules than TypeScript itself.</p>\n<p>For another opinion, you might be interested in reading\n<a href=\"https://medium.com/javascript-scene/the-typescript-tax-132ff4cb175b\"\n\t\trel=\"nofollow noreferrer noopener\">\nThe TypeScript Tax</a>.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I’m not saying that TypeScript is a perfect language - there’s no such thing,\nonly different tradeoffs. Whether or not TypeScript is worth it depends on your\nproject and priorities.</p>\n<p>In my opinion, TypeScript is great when you’re working with web technology and\nwould otherwise be using JavaScript. It improves the developer experience and\nincreases resilience without a huge amount of cost. I find it enjoyable to use\nand will continue using it for the foreseeable future.</p>\n","url":"https://blog.rubenwardy.com/2024/03/08/typescript-is-awesome/","summary":"I find that TypeScript makes JavaScript more enjoyable to use. In this article, I'll explain why.","date_published":"2024-03-08T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/YeAZ5KZCjT-540.png","tags":["reviews","webdev","opinion"]},{"id":"/2024/01/28/interesting-reads-2023/","language":"en","title":"My favourite articles of 2023","content_html":"<p>I read a lot of articles about programming and technology as a way of keeping up\nwith the industry and expanding my knowledge. Here are some articles I read in\n2023 that I recommend reading.</p>\n<h2 id=\"tech-industry\" tabindex=\"-1\">Tech industry <a class=\"header-anchor\" href=\"#tech-industry\">#</a></h2>\n<h3 id=\"tweeting-for-10-000-years-ended-after-4-years-8-months\" tabindex=\"-1\">Tweeting for 10,000 years ended after 4 years, 8 months <a class=\"header-anchor\" href=\"#tweeting-for-10-000-years-ended-after-4-years-8-months\">#</a></h3>\n<p>This article is an interesting read on software longevity, with a humorously\nshort outcome. It shows the risks of\n<a href=\"/2023/10/10/hello-indieweb/\">relying on private platforms</a> and shows how\nfragile software is compared to other forms of engineering.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://brandur.org/10000-years\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/isbaJpOY7r-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/isbaJpOY7r-375.png\" alt=\"Tweeting for 10,000 Years: An Experiment in Autonomous Software\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"188\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tTweeting for 10,000 Years: An Experiment in Autonomous Software\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tBrandur</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"stop-using-brave-browser\" tabindex=\"-1\">Stop using Brave browser <a class=\"header-anchor\" href=\"#stop-using-brave-browser\">#</a></h3>\n<p>Here is a must-read for those considering using Brave. I support using whatever\nsoftware works best for you, but I consider Brave to be untrustworthy due to\nthe issues highlighted in this article.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.spacebar.news/p/stop-using-brave-browser\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/kiOlIfWAvs-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/kiOlIfWAvs-375.png\" alt=\"Stop using Brave Browser\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"188\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tStop using Brave Browser\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tCorbin Davenport</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"programming\" tabindex=\"-1\">Programming <a class=\"header-anchor\" href=\"#programming\">#</a></h2>\n<h3 id=\"selling-your-extension\" tabindex=\"-1\">Selling your extension <a class=\"header-anchor\" href=\"#selling-your-extension\">#</a></h3>\n<p>A couple of years ago, I released a browser extension called\n<a href=\"https://renewedtab.com\">Renewed Tab</a> to the Firefox and Chrome stores. Since\nthen, I’ve been getting a lot of emails about selling my extension or adding\nspyware to it. These spammers all have the same goal - to steal the search and\nbrowsing data of my users, and sell it on to data brokers.</p>\n<p>I would never sell out my users, but not every browser extension developer is\nthe same. My extension only had a few hundred installs when I first started\nreceiving emails, so I’m sure the vast majority of extension developers will be\nreceiving these “offers”.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://ericwbailey.website/published/re-selling-your-extension/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/NPgPbO0vLl-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/NPgPbO0vLl-375.png\" alt=\"RE: Selling your extension\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"197\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tRE: Selling your extension\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tEric Bailey</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"self-closing-tags-img\" tabindex=\"-1\">Self-closing tags (&lt;img /&gt;) <a class=\"header-anchor\" href=\"#self-closing-tags-img\">#</a></h3>\n<p>I found this article interesting as it taught me something new about HTML.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://jakearchibald.com/2023/against-self-closing-tags-in-html/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/hBW5rBCaqk-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/hBW5rBCaqk-375.png\" alt=\"The case against self-closing tags in HTML\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"195\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe case against self-closing tags in HTML\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJake Archibald</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"what-they-don-t-tell-you-when-you-translate-your-app\" tabindex=\"-1\">What they don’t tell you when you translate your app <a class=\"header-anchor\" href=\"#what-they-don-t-tell-you-when-you-translate-your-app\">#</a></h3>\n<p>Software internationalisation is super important for the accessibility and reach\nof your software. Even though I do not speak multiple languages, I try to\nsupport translation in my applications.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://ericwbailey.website/published/what-they-dont-tell-you-when-you-translate-your-app/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/yS75Z6WBob-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/yS75Z6WBob-375.png\" alt=\"What they don't tell you when you translate your app\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"197\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tWhat they don't tell you when you translate your app\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tEric Bailey</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"work\" tabindex=\"-1\">Work <a class=\"header-anchor\" href=\"#work\">#</a></h2>\n<h3 id=\"on-consultancies\" tabindex=\"-1\">On consultancies <a class=\"header-anchor\" href=\"#on-consultancies\">#</a></h3>\n<p>I’ve been working at consultancies and software houses since I graduated.\nWhilst I haven’t necessarily experienced all these things, I found this article\nto be an interesting read.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://underscorehao.net/programmers-limbo/on-consultancies/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tOn Consultancies\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tPavel Danov</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"blogging\" tabindex=\"-1\">Blogging <a class=\"header-anchor\" href=\"#blogging\">#</a></h2>\n<h3 id=\"seven-reasons-why-i-don-t-write\" tabindex=\"-1\">Seven reasons why I don’t write <a class=\"header-anchor\" href=\"#seven-reasons-why-i-don-t-write\">#</a></h3>\n<p>I found this article quite influential in changing how I think about blogging.\nI try to write from my perspective and experiences, without worrying if it’s\nbeen said before. I also try to write what I want to write, rather than trying\nto go viral.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://mxb.dev/blog/seven-reasons-why-i-dont-write/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/1o-2IGM5Y3-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/1o-2IGM5Y3-375.png\" alt=\"7 reasons why I don't write\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"238\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\t7 reasons why I don't write\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tMax Böck</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"what-happened-to-blogging-for-the-hell-of-it\" tabindex=\"-1\">What happened to blogging for the hell of it <a class=\"header-anchor\" href=\"#what-happened-to-blogging-for-the-hell-of-it\">#</a></h3>\n<p>The Internet sometimes feels like one grift after another. This article presents\nan argument against that. You don’t need to blog to boost vanity metrics\nor to increase your value to employers. Just blog for the hell of it.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://whiona.weblog.lol/2023/10/what-happened-to-blogging-for-the-hell-of-it\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tWhat happened to blogging for the hell of it?\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tWhiona</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"misc\" tabindex=\"-1\">Misc <a class=\"header-anchor\" href=\"#misc\">#</a></h2>\n<h3 id=\"mechanical-watch\" tabindex=\"-1\">Mechanical Watch <a class=\"header-anchor\" href=\"#mechanical-watch\">#</a></h3>\n<p>I love explorables - interactive articles that explain how something works.\nHere is a great one about how mechanical watches work.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://ciechanow.ski/mechanical-watch/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><picture><source type=\"image/webp\" srcset=\"/static/img/CSv38_jp_I-375.webp 375w\" sizes=\"375px\"><img src=\"/static/img/CSv38_jp_I-375.png\" alt=\"Mechanical Watch\" loading=\"lazy\" decoding=\"async\" style=\"background-color: rgba(0, 0, 0, 1.0);\" width=\"375\" height=\"197\"></picture></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tMechanical Watch\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tBartosz Ciechanowski</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I hope you found some articles worth reading. What were your favourite reads\nlast year? Feel free to send article suggestions you think I might like.</p>\n<p><a href=\"https://www.pexels.com/photo/clear-glass-teacup-with-coffee-beverage-2299028/\">Cover Image</a>\n© Engin Akyurt</p>\n","url":"https://blog.rubenwardy.com/2024/01/28/interesting-reads-2023/","summary":"Articles are a great way of keeping up with programming and technology. Here are some articles I read in 2023 that I recommend reading.","date_published":"2024-01-28T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/fBiLy502MT-540.png","tags":["blogging","year_in_review"]},{"id":"/2023/11/18/why-i-write/","language":"en","title":"Five reasons why I write","content_html":"<p>In 2022, I made a <a href=\"/stats/#goal\">goal</a> of writing at least twelve long-form\narticles on my blog each year. But why bother? In this article, I will explain\nthe reasons why I write.</p>\n<h2 id=\"to-get-better-at-writing\" tabindex=\"-1\">To get better at writing <a class=\"header-anchor\" href=\"#to-get-better-at-writing\">#</a></h2>\n<p>I write to improve my technical writing. Technical writing is a very useful\nskill as a software engineer; communicating complex concepts is an essential\npart of my day job. Writing articles regularly is a great way to practice my\nplanning and writing skills.</p>\n<p>I’ve gotten pretty good at the <em>process</em> of writing articles, but I’m not sure\nwhether I’m actually getting better at writing. To get better at something, you\nneed timely and relevant feedback<sup class=\"footnote-ref\"><a href=\"#fn1\" id=\"fnref1\"><span class=\"sr-only\">footnote </span>1</a></sup>. I don’t have an editor to point\nout issues and readers don’t tend to comment on writing quality unless it is\nawful. It’s hard to improve at writing without feedback. The type of writing I\ndo also isn’t especially ambitious; perhaps creative writing or poetry would\nprovide better practice, although it’s not really what I want to be doing.</p>\n<p>For the last ten years, I’ve been working on an online book to teach Minetest\nmodding and game creation called the\n<a href=\"https://rubenwardy.com/minetest_modding_book/\">Minetest Modding Book</a>. One day,\nI’d like to self-publish a physical book on this subject. This will be a huge\nundertaking - and you can’t remotely patch books once they are printed. Getting\nbetter at writing helps towards this goal and will make the end result better.</p>\n<h2 id=\"to-document-my-projects\" tabindex=\"-1\">To document my projects <a class=\"header-anchor\" href=\"#to-document-my-projects\">#</a></h2>\n<p>I learnt to program because I wanted to create things. Websites, games,\nworld-ending artificial intelligences, and apps. I’m the sort of person who has\na lot of projects on the go at any one time.</p>\n<p>Writing about my <a href=\"/tags/projects/\">projects</a> is a way of sharing them with the\nworld. Whilst I do publish many of my projects as open source, some are less\naccessible. For example, you can’t use my <a href=\"/tags/electronics/\">electronics</a> or\n<a href=\"/tags/3d_printing/\">3D printing</a> projects unless you have electronics skills\nand a 3D printer. It would also be quite time-consuming to replicate these\nprojects.</p>\n<p>Writing whilst my memory is fresh allows me to archive my thoughts about a\nproject for the future, and share the lessons I learned. This is quite useful as\nsomething to refer back to.</p>\n<h2 id=\"to-get-feedback-and-learn\" tabindex=\"-1\">To get feedback and learn <a class=\"header-anchor\" href=\"#to-get-feedback-and-learn\">#</a></h2>\n<p>Recently, I’ve been learning electronics. My eventual goal with electronics is\nto create autonomous robots and drones; I’d like to make a quadcopter with my\nown flight controller that can take off, land, and follow a target. This will be\nquite an ambitious project, and I’m nowhere near capable enough for that yet.</p>\n<p>To get better at electronics, I’ve been working on\n<a href=\"/tags/electronics/\">easier projects</a> to develop the skills I will need. Writing\nabout these projects and the problems I’ve faced has allowed me to get\nsuggestions from those experienced with electronics. This has been invaluable\nfor my self-development.</p>\n<p>You don’t know what you don’t know. Even if I have solved all the problems and\nfinished an electronics project, I still benefit from suggestions for\nimprovements and better ways of doing things.</p>\n<h2 id=\"to-share-knowledge-and-solutions\" tabindex=\"-1\">To share knowledge and solutions <a class=\"header-anchor\" href=\"#to-share-knowledge-and-solutions\">#</a></h2>\n<p>There’s been a lot of cases where I’ve had to work out how to do something\nmyself. By publishing <a href=\"/tags/tutorials/\">tutorials</a>, I can hopefully save the\nnext searcher from struggling with the same problem.</p>\n<figure class=\"undefined\"><a href=\"/static/img/y0GvM1uTpI-485.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/-CauQE5j61-485.webp 485w\"><img src=\"/static/img/-CauQE5j61-485.png\" alt=\"Web comic.\nThe left is text that says:\nNever have I felt so close to another soul, and yet so helplessly alone, as when I google an error, and there's one result. A thread by someone with the same problem and no answer. Last posted to in 2003.\nThe right half shows a stick figure shaking a monitor saying:\n&quot;Who were you DenverCoder9? WHAT DID YOU SEE?&quot;\" loading=\"lazy\" decoding=\"async\" title=\"All long help threads should have a sticky globally-editable post at the top saying 'DEAR PEOPLE FROM THE FUTURE: Here's what we've figured out so far ...'\" width=\"485\" height=\"270\"></picture></a><figcaption>A relevant <a href=\"https://xkcd.com/979/\">XKCD</a>: \"Wisdom of the Ancients\"</figcaption></figure>\n<h2 id=\"to-think-and-explore-opinions\" tabindex=\"-1\">To think and explore opinions <a class=\"header-anchor\" href=\"#to-think-and-explore-opinions\">#</a></h2>\n<p>Writing blog posts is a great way to develop opinions about subjects. As part of\nmy writing process, I break down concepts, make notes, and figure out which\npoints are the most important. By attempting to explain something clearly, you\ncan often identify weaknesses in your arguments or knowledge. Writing articles\nallows me to brainstorm, weigh up my thoughts, and build/challenge opinions.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I write to get better at writing, to document my projects, to get feedback, to\nshare knowledge, and to develop opinions.</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p><a href=\"https://www.youtube.com/watch?v=5eW6Eagr9XA\">The 4 things it takes to be an expert</a> by\nVeritasium is a good explainer on how to get better at something. <a href=\"#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n","url":"https://blog.rubenwardy.com/2023/11/18/why-i-write/","summary":"In 2022, I made a goal of writing at least twelve long-form articles on my blog each year. But why bother? In this article, I will explain why I write.","date_published":"2023-11-18T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/cpvnw5nDhY-540.png","tags":["blogging","opinion"]},{"id":"/2023/10/29/eleventy-wordcount/","language":"en","title":"Better word counts and reading time in Eleventy (11ty)","content_html":"<p>I recently <a href=\"/2023/10/27/switched-to-eleventy/\">switched to using Eleventy</a> to\ngenerate my blog. As part of this process, I needed to implement word counts for\neach of my posts. I made my own plugin for this as I was unable to find an\nexisting one that met my requirements.</p>\n<p>The below code should work with any template engine, including Liquid and\nNunjucks.</p>\n<h2 id=\"what-s-wrong-with-other-implementations\" tabindex=\"-1\">What’s wrong with other implementations? <a class=\"header-anchor\" href=\"#what-s-wrong-with-other-implementations\">#</a></h2>\n<p>Other implementations either used regex to parse HTML or included non-text\nlike scripts and code snippets in the count. Both of these result in\ninaccurate word counts.</p>\n<h2 id=\"what-exactly-is-a-word\" tabindex=\"-1\">What exactly is a word? <a class=\"header-anchor\" href=\"#what-exactly-is-a-word\">#</a></h2>\n<p>First of all, we need to work out exactly how we are going to count words.</p>\n<p>A word is a unit of prose or writing. We need to make sure we exclude code\nsnippets and scripts from the word count.</p>\n<p>A naive approach would be to split text by spaces and count all the parts.\nHowever, this misses words that are joined together like “Dog/Cat”. Another\napproch would be to split text by all punctuation, but this would count words\nlike “self-hosting” as two words.</p>\n<p>To validate my word counter, I made a page where I dumped all the detected words\nfrom a post. I used this to refine the list of delimators.</p>\n<h2 id=\"counting-words\" tabindex=\"-1\">Counting words <a class=\"header-anchor\" href=\"#counting-words\">#</a></h2>\n<h3 id=\"dependencies\" tabindex=\"-1\">Dependencies <a class=\"header-anchor\" href=\"#dependencies\">#</a></h3>\n<p>You need to install JSDom:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">--save</span> jsdom</code></pre>\n<h3 id=\"eleventy-js\" tabindex=\"-1\">.eleventy.js <a class=\"header-anchor\" href=\"#eleventy-js\">#</a></h3>\n<p>In the eleventy config, you need to add our new plugin:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> pluginWordcount <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./plugins/wordcount.js\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addPlugin</span><span class=\"token punctuation\">(</span>pluginWordcount<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// You can only have one module.exports in a configuration file,</span>\n    <span class=\"token comment\">// so make sure you add the above line to your existing one.</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h3 id=\"wordcount-js-plugin\" tabindex=\"-1\">wordcount.js plugin <a class=\"header-anchor\" href=\"#wordcount-js-plugin\">#</a></h3>\n<p>This is the file for the plugin. It contains <code>extractText</code> to get all the text\nfrom HTML, and countWords to count the words in a piece of text.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> UserConfig <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"@11ty/eleventy\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> <span class=\"token constant\">JSDOM</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"jsdom\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> <span class=\"token constant\">TO_STRIP</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token string\">\"code\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"pre code\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"script\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\".header-anchor\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">extractText</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">html</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> dom <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">JSDOM</span><span class=\"token punctuation\">(</span>html<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> document <span class=\"token operator\">=</span> dom<span class=\"token punctuation\">.</span>window<span class=\"token punctuation\">.</span>document<span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Remove non-text elements</span>\n    document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelectorAll</span><span class=\"token punctuation\">(</span><span class=\"token constant\">TO_STRIP</span><span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">\", \"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">child</span> <span class=\"token operator\">=></span> child<span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> document<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">const</span> cache <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">countWords</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">value</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>cache<span class=\"token punctuation\">[</span>value<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> cache<span class=\"token punctuation\">[</span>value<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token function\">extractText</span><span class=\"token punctuation\">(</span>value<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">[\\s;/\\\\]</span><span class=\"token regex-delimiter\">/</span></span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">x</span> <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span><span class=\"token function\">trim</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token comment\">// Word is non-empty with at least one letter or number</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">x</span> <span class=\"token operator\">=></span> x<span class=\"token punctuation\">.</span><span class=\"token function\">match</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">.*[a-z0-9].*</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">i</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span>\n\n    cache<span class=\"token punctuation\">[</span>value<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> result<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token parameter\">eleventyConfig</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"wordcount\"</span><span class=\"token punctuation\">,</span> countWords<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"inside-post-layout\" tabindex=\"-1\">Inside post layout <a class=\"header-anchor\" href=\"#inside-post-layout\">#</a></h3>\n<p>Here’s how you might use the wordcount filter inside a post layout that uses\nliquid:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">assign</span> wordcount <span class=\"token operator\">=</span> content <span class=\"token operator\">|</span> <span class=\"token function filter\">wordcount</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> wordcount <span class=\"token operator\">|</span> <span class=\"token function filter\">divided_by</span><span class=\"token operator\">:</span> <span class=\"token number\">238</span> <span class=\"token operator\">|</span> <span class=\"token function filter\">round</span> <span class=\"token delimiter punctuation\">}}</span></span> min read\n(<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> wordcount <span class=\"token delimiter punctuation\">}}</span></span> words)</code></pre>\n<h3 id=\"unit-tests\" tabindex=\"-1\">Unit tests <a class=\"header-anchor\" href=\"#unit-tests\">#</a></h3>\n<p>As a bonus, here’s the unit tests I used when writing the word counter:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> describe <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"mocha\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> expect <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"chai\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> count <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./wordcount\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> parameterisedTests <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token string-property property\">\"empty\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"just symbols\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token string\">\". . -//!\\\"$%^&amp;*()\\\\`\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"single word paragraph\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;p> Hey! &lt;/p>\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"punctuation\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token string\">\"&lt;p>Hello world! This is a test, of the word/counter&lt;/p>\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"strips scripts\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;p>Hello world! This is a test, of the word/counter&lt;/p>\n            &lt;script>\n                alert(\"Hello world!\")\n            &lt;/script>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"strips code blocks\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;p>Hello world! This is a test, of the word/counter&lt;/p>\n            &lt;pre>\n                &lt;code>\n                    alert(\"Hello world!\")\n                &lt;/code>\n            &lt;/pre>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"strips inline code\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;p>Hello world! This is a &lt;code>test&lt;/code>, of the word/counter&lt;/p>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">9</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"strips heading anchors\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;h2>A heading&lt;/h2>\n            &lt;a class=\"header-anchor\">1&lt;/a>\n            &lt;p>Hello world! One two&lt;/p>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">6</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"counts numbers but not symbols\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;p>Hello world! You are 26.0 today - or so&lt;/p>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">8</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token string-property property\">\"words can contain hyphens\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token literal-property property\">html</span><span class=\"token operator\">:</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">\n            &lt;p>Hello world! One-two three&lt;/p>\n        </span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">,</span>\n        <span class=\"token literal-property property\">expected</span><span class=\"token operator\">:</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"countWords\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    Object<span class=\"token punctuation\">.</span><span class=\"token function\">entries</span><span class=\"token punctuation\">(</span>parameterisedTests<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\"><span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">,</span> data<span class=\"token punctuation\">]</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">it</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">count</span><span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">.</span>html<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>to<span class=\"token punctuation\">.</span><span class=\"token function\">equal</span><span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">.</span>expected<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"full-example\" tabindex=\"-1\">Full example <a class=\"header-anchor\" href=\"#full-example\">#</a></h2>\n<p>You can see a full working example in my eleventy writing stats example\nrepo: <a href=\"https://gitlab.com/rubenwardy/eleventy-stats-example\">https://gitlab.com/rubenwardy/eleventy-stats-example</a>.</p>\n","url":"https://blog.rubenwardy.com/2023/10/29/eleventy-wordcount/","summary":"A short tutorial on how to count words and determine the reading time using Eleventy.","date_published":"2023-10-29T00:00:00.000Z","tags":["snippet","blogging","tutorials","webdev"]},{"id":"/2023/10/29/eleventy-backlinks/","language":"en","title":"How to find backlinks using Eleventy (11ty)","content_html":"<p>I recently <a href=\"/2023/10/27/switched-to-eleventy/\">switched to using Eleventy</a> to\ngenerate my blog. Because Eleventy is very easy to extend, I was able to add\nmore features to my blog - including backlinks.</p>\n<p>In a post’s sidebar, there is a list of other posts that link to the current\npost. This is an effective way to allow readers to find related content.</p>\n<p>To implement this, I created a small plugin that adds a filter to find which\nitems in a collection link to the given URL. To find links in HTML, I used the\nJSDom library.</p>\n<p>Whilst there are existing backlink plugins, they didn’t meet my purposes.\n<code>eleventy-plugin-backlinks</code>, for example, only finds links made using\nwikilinks-style markup (ie: <code>[[Other Post Name]])</code>). I wanted backlinks to\nwork with any link in a post - whether the post is markdown, HTML, or something\nelse.</p>\n<p>The below code should work with any template engine, including Liquid and\nNunjucks.</p>\n<h2 id=\"finding-backlinks\" tabindex=\"-1\">Finding Backlinks <a class=\"header-anchor\" href=\"#finding-backlinks\">#</a></h2>\n<h3 id=\"dependencies\" tabindex=\"-1\">Dependencies <a class=\"header-anchor\" href=\"#dependencies\">#</a></h3>\n<p>You need to install JSDom:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">npm</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">--save</span> jsdom</code></pre>\n<h3 id=\"eleventy-js\" tabindex=\"-1\">.eleventy.js <a class=\"header-anchor\" href=\"#eleventy-js\">#</a></h3>\n<p>In the eleventy config, you need to add our new plugin:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> pluginBacklinks <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./plugins/backlinks.js\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addPlugin</span><span class=\"token punctuation\">(</span>pluginBacklinks<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// You can only have one module.exports in a configuration file,</span>\n    <span class=\"token comment\">// so make sure you add the above line to your existing one.</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h3 id=\"backlinks-js-plugin\" tabindex=\"-1\">backlinks.js plugin <a class=\"header-anchor\" href=\"#backlinks-js-plugin\">#</a></h3>\n<p>This is the file for the plugin. It contains <code>getLinks</code> to extract links from\nHTML and a plugin function to register a filter.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> UserConfig <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"@11ty/eleventy\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> <span class=\"token constant\">JSDOM</span> <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"jsdom\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> hostname <span class=\"token operator\">=</span> <span class=\"token string\">\"blog.rubenwardy.com\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> cache <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">/**\n * Extract links from html, not including hash parts\n */</span>\n<span class=\"token keyword\">function</span> <span class=\"token function\">getLinks</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">html</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>cache<span class=\"token punctuation\">[</span>html<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> cache<span class=\"token punctuation\">[</span>html<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">const</span> dom <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">JSDOM</span><span class=\"token punctuation\">(</span>html<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> document <span class=\"token operator\">=</span> dom<span class=\"token punctuation\">.</span>window<span class=\"token punctuation\">.</span>document<span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token operator\">...</span>document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelectorAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"a[href]\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">x</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">let</span> href <span class=\"token operator\">=</span> x<span class=\"token punctuation\">.</span><span class=\"token function\">getAttribute</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"href\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n            <span class=\"token comment\">// Normalise internal links</span>\n            <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">URL</span><span class=\"token punctuation\">(</span>href<span class=\"token punctuation\">,</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">https://</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>hostname<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">.</span>hostname <span class=\"token operator\">==</span> hostname<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> url<span class=\"token punctuation\">.</span>pathname<span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n\n            url<span class=\"token punctuation\">.</span>hash <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">return</span> url<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    cache<span class=\"token punctuation\">[</span>html<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> result<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> result<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    eleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"links_to\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">collection<span class=\"token punctuation\">,</span> target</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> collection<span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">item</span> <span class=\"token operator\">=></span> <span class=\"token function\">getLinks</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">.</span>content<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">has</span><span class=\"token punctuation\">(</span>target<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"inside-post-layout\" tabindex=\"-1\">Inside post layout <a class=\"header-anchor\" href=\"#inside-post-layout\">#</a></h3>\n<p>This is how you use the <code>links_to</code> filter to get backlinks inside a post layout\nthat uses liquid:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">assign</span> backlinks <span class=\"token operator\">=</span> collections<span class=\"token punctuation\">.</span>post <span class=\"token operator\">|</span> <span class=\"token function filter\">links_to</span><span class=\"token operator\">:</span> <span class=\"token object\">page</span><span class=\"token punctuation\">.</span>url <span class=\"token delimiter punctuation\">%}</span></span>\n<span class=\"token comment\">&lt;!-- An empty list isn't false-y in Eleventy liquid --></span>\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">assign</span> backlinks_count <span class=\"token operator\">=</span> backlinks <span class=\"token operator\">|</span> <span class=\"token function filter\">size</span> <span class=\"token delimiter punctuation\">%}</span></span>\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">if</span> backlinks_count <span class=\"token operator\">></span> <span class=\"token number\">0</span> <span class=\"token delimiter punctuation\">%}</span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>aside</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>backlinks<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h3</span><span class=\"token punctuation\">></span></span>Links here<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h3</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>ul</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%-</span> <span class=\"token keyword\">for</span> post <span class=\"token keyword\">in</span> backlinks <span class=\"token delimiter punctuation\">-%}</span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>li</span><span class=\"token punctuation\">></span></span>\n                    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> post<span class=\"token punctuation\">.</span>url <span class=\"token delimiter punctuation\">}}</span></span><span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{{</span> post<span class=\"token punctuation\">.</span>data<span class=\"token punctuation\">.</span>title <span class=\"token delimiter punctuation\">}}</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>li</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%-</span> <span class=\"token keyword\">endfor</span> <span class=\"token delimiter punctuation\">-%}</span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>aside</span><span class=\"token punctuation\">></span></span>\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">endif</span> <span class=\"token delimiter punctuation\">%}</span></span></code></pre>\n","url":"https://blog.rubenwardy.com/2023/10/29/eleventy-backlinks/","summary":"A short tutorial on how to find backlinks (pages that link to the current page) using Eleventy","date_published":"2023-10-29T00:00:00.000Z","tags":["snippet","blogging","tutorials","webdev"]},{"id":"/2023/10/27/switched-to-eleventy/","language":"en","title":"I've switched to Eleventy (11ty): my thoughts","content_html":"<p>After nine years of using Jekyll, I’ve switched to using\n<a href=\"https://www.11ty.dev/\">Eleventy</a> (11ty) and redesigned my blog whilst I was at\nit. I was finding that Jekyll was too limiting and wanted something more\ncapable. In this article, I will talk about why I switched, how I did it, and my\nthoughts about Eleventy as a new user.</p>\n<h2 id=\"jekyll-and-why-i-decided-to-switch\" tabindex=\"-1\">Jekyll, and why I decided to switch <a class=\"header-anchor\" href=\"#jekyll-and-why-i-decided-to-switch\">#</a></h2>\n<p>I chose to use Jekyll in 2014 because it was the only static site generator that\nGitHub Pages supported. It allowed me to use multiple pages without having\nto duplicate all the layout markup, making it easier to grow the size of my\nwebsite.</p>\n<p>Recently, I’ve been finding Jekyll increasingly painful to use and too limited\nfor my purposes. Since 2022, my goal has been to write at least twelve posts a\nyear. This means that I’ve been working on posts much more frequently than I\nhave done in the past. Ease of writing posts is much more important now.</p>\n<p>One of my biggest annoyances with writing a post was creating thumbnails for my\nimages - this was quite time-consuming; I wanted to fully automate it in a\nmanner that I don’t even need to think about when writing a post.</p>\n<p>Another thing was that the files were split into different places - the text was\nplaced in <code>_posts</code> and the images were in <code>static</code>. I wanted the ability to\nmake a single folder for an article and get the generator to automatically move\nthe assets to where they are needed.</p>\n<figure class=\"mx-0\">\n  <div class=\"row row-wrap gap-6\">\n    <div class=\"col-md\">\n      <pre><code>📁 static\n├─ 📁 hello-indieweb\n|  └─ 🖼️ cover.png\n└─ 📁 switched-to-eleventy\n   ├─ 🖼️ cover.png\n   └─ 🖼️ schematic.png\n📁 _posts\n├─ 📄 2023-10-10-hello-indieweb.md\n└─ 📄 2023-10-27-switched-to-eleventy.md</code></pre>\n    </div>\n    <div class=\"col-md\">\n      <pre><code>📁 posts\n├─ 📁 2023-10-10-hello-indieweb\n|  ├─ 📄 index.md\n|  └─ 🖼️ cover.png\n└─ 📁 2023-10-27-switched-to-eleventy\n   ├─ 📄 index.md\n   ├─ 🖼️ cover.png\n   └─ 🖼️ schematic.png\n  </code></pre>\n    </div>\n  </div>\n  <figcaption class=\"text-center mt-4\">Which would you prefer?</figcaption>\n</figure>\n<p>Whilst I could have hacked together more scripts to fix these issues, my setup\nwas already quite convoluted. To generate my <a href=\"/stats/\">writing statistics page</a>,\nI had a mess of Python scripts and complicated layout code. Liquid isn’t the\nmost capable language to use by itself. So, I decided to look for other static\nsite generators.</p>\n<h2 id=\"my-requirements\" tabindex=\"-1\">My requirements <a class=\"header-anchor\" href=\"#my-requirements\">#</a></h2>\n<p>My first requirement was that it must be a static site generator. A static site\nis the easiest type of website to host - it can be hosted on any web host, even\na Content Delivery Network (CDN). This gives a lot of flexibility. It also\nincreases longevity - if, for whatever reason, I cease maintaining my website,\nit can continue to be hosted as a low-tech static website for a long time. To\nget dynamic features like commenting, I have\n<a href=\"/2023/10/10/hello-indieweb/#comment-form\">a NodeJS server</a> hosted on a separate\nsubdomain.</p>\n<p>The second requirement was that it should not <em>require</em> any client-side\nJavaScript. Requiring JavaScript is bad for SEO and accessibility and bloats the\nwebpage. Client-side JavaScript should only be used to\n<a href=\"https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement\">enhance</a>\nthe behaviour.</p>\n<p>The third requirement was that the generator must be easy to extend and\ncustomise to match my workflow. I’m not fond of Ruby or Bundler; I found\nBundler quite painful to use. I felt like JavaScript/TypeScript would be the\nbetter language to use as it is a web technology.</p>\n<p>The final requirement is the generator must be fully open source. Using\nopen-source tools is vital to achieve flexibility and longevity.</p>\n<h2 id=\"switching-to-eleventy\" tabindex=\"-1\">Switching to Eleventy <a class=\"header-anchor\" href=\"#switching-to-eleventy\">#</a></h2>\n<p>After looking into the options, I decided to give Eleventy a go. I started by\nsetting up an Eleventy project and porting a single blog post. To derisk the\nprocess, I focused on implementing and prototyping the unknowns first, such as\nthumbnails, SCSS, post/tags, and writing statistics. This allowed me to quickly\nverify whether Eleventy was a good fit before spending a lot of effort porting\nall my content across.</p>\n<p>I also decided to use this opportunity to redesign my blog as well. I used to\nuse Bootstrap; I wanted to go with something a bit more custom and unique this\ntime. I also wanted a light/dark mode switcher.</p>\n<p>Before I published the new Eleventy version to production, I wrote a simple\nscript to make sure that no URLs had changed or were missing.\n<a href=\"https://www.w3.org/Provider/Style/URI\">Cool URLs never change</a>, so I considered\nverifying this important.</p>\n<h2 id=\"the-good\" tabindex=\"-1\">The good <a class=\"header-anchor\" href=\"#the-good\">#</a></h2>\n<h3 id=\"eleventy-image\" tabindex=\"-1\">Eleventy-Image <a class=\"header-anchor\" href=\"#eleventy-image\">#</a></h3>\n<p><a href=\"https://www.11ty.dev/docs/plugins/image/\">Eleventy-Image</a> is an official plugin\nfor Eleventy that allows you to resize images and create thumbnails.</p>\n<p>Using Eleventy-Image, I defined custom template functions to create image\nthumbnails based on how they are used. For example, the <code>figure</code> function\ncreates a 540px width thumbnail and then renders a <code>&lt;figure&gt;</code> element to the\nHTML.</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> figure <span class=\"token string\">\"./schematic.png\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"The schematic for my plant watering system's circuit. Created using KiCAD.\"</span> <span class=\"token delimiter punctuation\">%}</span></span></code></pre>\n<p>The source image is stored in the post’s directory. The <code>figure</code> function saves\nresized versions to the <code>static</code> directory. This allows me to keep the text and\nimages of a post together, and only create specific image sizes when needed.</p>\n<h3 id=\"flexibility\" tabindex=\"-1\">Flexibility <a class=\"header-anchor\" href=\"#flexibility\">#</a></h3>\n<p>Eleventy is very flexible, powerful, and fairly easy to customise. Using\nJavaScript, you can define custom template filters and functions. You can even\nbuild entire pages using JS - or JSX with an additional library.</p>\n<p>It was incredibly easy to add features such as a table of contents, writing\nstatistics, and backlinks. For a table of contents, I used an existing plugin\nand only needed to add <code>{{ toc }}</code> to place it. I\nimplemented <a href=\"/2025/06/01/eleventy-stats/\">writing statistics</a>,\n<a href=\"/2023/10/29/eleventy-wordcount/\">word counts</a>,\nand <a href=\"/2023/10/29/eleventy-backlinks/\">backlinks</a> by creating plugins to add\ntemplate functions using JavaScript.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// Usage:</span>\n<span class=\"token comment\">// {% assign backlinks = collections.post | links_to: page.url %}</span>\neleventyConfig<span class=\"token punctuation\">.</span><span class=\"token function\">addFilter</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"links_to\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">posts<span class=\"token punctuation\">,</span> targetURL</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> posts<span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">post</span> <span class=\"token operator\">=></span> <span class=\"token function\">getLinks</span><span class=\"token punctuation\">(</span>post<span class=\"token punctuation\">.</span>content<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">has</span><span class=\"token punctuation\">(</span>targetURL<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Another thing is that Eleventy allows you to customise or provide your own\nMarkdown parser, or use different formats entirely. To get heading anchors\n(<code>#</code>), I just needed to enable that option.</p>\n<p>Finally, Eleventy has support for JavaScript data files, which is great for\nfetching data from the network before building the site. I used this feature to\nload <a href=\"/2023/10/10/hello-indieweb/#webmentions\">Webmentions</a> from the\nWebmention.io REST API.</p>\n<h2 id=\"the-bad\" tabindex=\"-1\">The bad <a class=\"header-anchor\" href=\"#the-bad\">#</a></h2>\n<h3 id=\"learning-curve-and-inconsistencies\" tabindex=\"-1\">Learning curve and inconsistencies <a class=\"header-anchor\" href=\"#learning-curve-and-inconsistencies\">#</a></h3>\n<p>Eleventy has quite a lot of quirks that make learning it quite difficult. It\ntook much longer than I expected to switch to Eleventy.</p>\n<p>First, Eleventy uses variables inconsistently. At the top of each page, there’s\nmetadata known as front matter:</p>\n<pre class=\"language-yml\"><code class=\"language-yml\"><span class=\"token punctuation\">---</span>\n<span class=\"token key atrule\">title</span><span class=\"token punctuation\">:</span> I've redesigned my blog and switched to Eleventy\n<span class=\"token key atrule\">description</span><span class=\"token punctuation\">:</span> <span class=\"token string\">\"\"</span>\n<span class=\"token key atrule\">tags</span><span class=\"token punctuation\">:</span>\n  <span class=\"token punctuation\">-</span> webdev\n  <span class=\"token punctuation\">-</span> reviews\n<span class=\"token punctuation\">---</span></code></pre>\n<p>To access the title and URL inside a template, you do <code>title</code> and <code>page.url</code>.\nInside JavaScript or when reading a collection, it’s <code>item.data.title</code>,\n<code>item.url</code>, or <code>item.page.url</code>.</p>\n<p>Having a lot of global variables inside the template is quite confusing. It’s\nnot just pages - Liquid allows you to include other liquid files. In Jekyll, you\naccess parameters by writing <code>include.param1</code>. In Eleventy, the parameter is\njust <code>param1</code>.</p>\n<p>The Eleventy liquid syntax is also slightly different from Jekyll liquid, for some\nreason. To include a directory, you need to quote the file name and use <code>:</code>\ninstead of <code>=</code>:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token comment\">&lt;!-- Eleventy --></span>\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">include</span> <span class=\"token string\">\"youtube.html\"</span><span class=\"token punctuation\">,</span> id<span class=\"token operator\">:</span> <span class=\"token string\">\"dQw4w9WgXcQ\"</span><span class=\"token punctuation\">,</span> caption<span class=\"token operator\">:</span> <span class=\"token string\">\"Funny cat videos\"</span> <span class=\"token delimiter punctuation\">%}</span></span>\n\n<span class=\"token comment\">&lt;!-- Jekyll --></span>\n<span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> <span class=\"token keyword\">include</span> youtube<span class=\"token punctuation\">.</span>html id<span class=\"token operator\">=</span><span class=\"token string\">\"dQw4w9WgXcQ\"</span> caption<span class=\"token operator\">=</span><span class=\"token string\">\"Funny cat videos\"</span> <span class=\"token delimiter punctuation\">%}</span></span></code></pre>\n<p>But custom shortcodes do not support named parameters, you can only pass them in\nby order:</p>\n<pre class=\"language-liquid\"><code class=\"language-liquid\"><span class=\"token liquid language-liquid\"><span class=\"token delimiter punctuation\">{%</span> youtube <span class=\"token string\">\"dQw4w9WgXcQ\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Funny cat videos\"</span> <span class=\"token delimiter punctuation\">%}</span></span></code></pre>\n<h3 id=\"painful-to-read-error-messages\" tabindex=\"-1\">Painful to read error messages <a class=\"header-anchor\" href=\"#painful-to-read-error-messages\">#</a></h3>\n<p>The error messages produced by Eleventy are very long and <a href=\"https://github.com/11ty/eleventy/issues/3076\">point to the wrong\nlines</a> in templates. It looks like\nthe line numbers don’t include the height of the front matter.</p>\n<pre><code>[11ty] Problem writing Eleventy templates: (more in DEBUG output)\n[11ty] 1. Having trouble rendering liquid template ./file.md (via TemplateContentRenderError)\n[11ty] 2. tag &quot;doesnotexist&quot; not found, file:./file.md, line:2, col:1 (via ParseError)\n[11ty] 3. tag &quot;doesnotexist&quot; not found (via AssertionError)\n[11ty]\n[11ty] Original error stack trace: AssertionError: tag &quot;doesnotexist&quot; not found\n[11ty]     at assert (/home/ruben/dev/tmp/eleventy_repro/node_modules/liquidjs/dist/liquid.node.cjs.js:319:15)\n[11ty]     at Parser.parseToken (/home/ruben/dev/tmp/eleventy_repro/node_modules/liquidjs/dist/liquid.node.cjs.js:2284:17)\n[11ty]     at Parser.parseTokens (/home/ruben/dev/tmp/eleventy_repro/node_modules/liquidjs/dist/liquid.node.cjs.js:2276:33)\n[11ty]     at Parser.parse (/home/ruben/dev/tmp/eleventy_repro/node_modules/liquidjs/dist/liquid.node.cjs.js:2270:21)\n[11ty]     at Liquid.parse (/home/ruben/dev/tmp/eleventy_repro/node_modules/liquidjs/dist/liquid.node.cjs.js:3618:28)\n[11ty]     at Liquid.compile (/home/ruben/dev/tmp/eleventy_repro/node_modules/@11ty/eleventy/src/Engines/Liquid.js:255:28)\n[11ty]     at Markdown.compile (/home/ruben/dev/tmp/eleventy_repro/node_modules/@11ty/eleventy/src/Engines/Markdown.js:68:28)\n[11ty]     at TemplateRender.getCompiledTemplate (/home/ruben/dev/tmp/eleventy_repro/node_modules/@11ty/eleventy/src/TemplateRender.js:269:26)\n[11ty]     at Template.compile (/home/ruben/dev/tmp/eleventy_repro/node_modules/@11ty/eleventy/src/TemplateContent.js:362:42)\n[11ty]     at async Template._render (/home/ruben/dev/tmp/eleventy_repro/node_modules/@11ty/eleventy/src/TemplateContent.js:486:16)\n[11ty] Wrote 0 files in 0.06 seconds (v2.0.1)\n</code></pre>\n<p>Eleventy would have been much easier to use if the error message was at the\nbottom and it gave a link to the exact line in the template. Providing the\ntemplate path as <code>path/to/file.h:123</code> like the rest of the paths would also make\nit clickable in IDEs.</p>\n<h3 id=\"typing-and-ide-support\" tabindex=\"-1\">Typing and IDE support <a class=\"header-anchor\" href=\"#typing-and-ide-support\">#</a></h3>\n<p>Another thing I didn’t like is that Eleventy does not support TypeScript. I like\nTypeScript because it makes my IDE (VSCodium) more helpful. IntelliSense allows\nme to inspect what methods are available on an object, and linting shows errors\nwhen I use something incorrectly.</p>\n<p>I tried setting up TypeScript with Eleventy, but there were no types available.\nEleventy does come with <code>index.d.ts</code>, but it only documents a single thing and\nnot the entire API.</p>\n<p>One thing I did discover is that you can use JSDoc to get IntelliSense to work a\nlittle bit. JSDoc does feel a bit like “TypeScript at home” though. It’s very\nverbose and doesn’t work as well.</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> UserConfig <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">require</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"@11ty/eleventy/src/UserConfig\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">/**\n * @param {UserConfig} eleventyConfig\n * @returns {void}\n */</span>\nmodule<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">exports</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">eleventyConfig</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// configure here</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h3 id=\"old-style-imports-commonjs\" tabindex=\"-1\">Old-style imports: CommonJS <a class=\"header-anchor\" href=\"#old-style-imports-commonjs\">#</a></h3>\n<p>As you may have seen in the above example, Eleventy uses CommonJS <code>require</code>\nrather than the more modern ES6 <code>import</code>. This is not great, but will\n<a href=\"https://github.com/11ty/eleventy/issues/836\">be fixed</a> in Eleventy 3.0.0.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Whilst I’m not 100% happy with Eleventy, it is more capable than Jekyll and\nbetter for my purposes. Many of the issues I have are fixable, and now that I\nknow how to use Eleventy, it should be easier to do things in the future.</p>\n","url":"https://blog.rubenwardy.com/2023/10/27/switched-to-eleventy/","summary":"In this article, I will talk about why I switched, how I did it, and my thoughts about Eleventy as a new user.","date_published":"2023-10-27T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/bn8U-9HAwK-540.png","tags":["webdev","reviews","blogging"]},{"id":"/2023/10/10/hello-indieweb/","language":"en","title":"I have joined the IndieWeb","content_html":"<p>The collapse of Twitter last year got me thinking about closed platforms and\nreducing the hold that privately owned platforms have over the Internet.</p>\n<p>I’ve been blogging for nine years now on my personal website. I like owning my\nown domain as it allows me to retain control and stay independent of particular\nservices. Private platforms have a tendency to be bought out and/or ruined by\ncommercial interests, especially now with tech growth slowing down and investors\ngetting uneasy.</p>\n<p>However, there are some benefits to closed blogging platforms. Medium provides a\nnetwork effect that small blogs don’t have. It has an algorithm that promotes\nposts that users may find interesting. This allows the blogs to organically gain\nnew readers. Additionally, Medium makes it super easy to like, comment, and\nreply to a post, resulting in a platform that feels a lot more like a social\nnetwork than your standard cloud blogging service.</p>\n<p>Last year, I started looking into ways independent blogs could communicate, just\nlike on Medium. I considered making my blog ActivityPub-compatible, as that\nwould allow users on Mastodon and the Fediverse to like, share, and comment on\narticles. And then, I stumbled upon the IndieWeb.</p>\n<h2 id=\"what-is-indieweb\" tabindex=\"-1\">What is IndieWeb? <a class=\"header-anchor\" href=\"#what-is-indieweb\">#</a></h2>\n<p>IndieWeb.org describes the IndieWeb as:</p>\n<figure class=\"figure \">\n\t<blockquote class=\"mb-1\" cite=\"https://indieweb.org/\"><p><strong>The IndieWeb is a community of independent &amp; personal websites</strong> connected by simple standards, based on the principles of: owning your domain &amp; using it as your primary identity, publishing on your own site (optionally syndicating elsewhere), and owning your data.</p>\n</blockquote>\n\t<figcaption class=\"pl-4 mt-3\">\n\t\t—<a href=\"https://indieweb.org/\">\n\t\t\t\tIndieWeb.org\n\t\t\t</a></figcaption>\n</figure>\n<p>To phrase it another way, IndieWeb is about posting the things you make on your\npersonal website and domain, to keep control of your data and stay independent\nfrom private platforms (aka silos). You may still post to silos but you should\npost to your personal website first.</p>\n<p>IndieWeb isn’t just about blogging. You might post Twitter-like microposts,\nphotos, location check-ins, reviews, replies to other sites, and more.</p>\n<h3 id=\"posse-and-backfeeding\" tabindex=\"-1\">POSSE and Backfeeding <a class=\"header-anchor\" href=\"#posse-and-backfeeding\">#</a></h3>\n<p>An important concept is “<strong>P</strong>ublish on your <strong>O</strong>wn <strong>S</strong>ite; <strong>S</strong>yndicate\n<strong>E</strong>lsewhere” (<a href=\"https://indieweb.org/POSSE\">POSSE</a>). This means that you should\npost the original version on your own website and then share links or copies of\nyour content with relevant social media communities. This is simpler and more\nflexible than adding ActivityPub support to my blog, and is so obvious that I’ve\nalready been doing it without realising it.</p>\n<p>You may be thinking that POSSE is pretty obvious and a bit of a cop-out. But\nwhere POSSE truly shines is when combined with <em>backfeeding</em>. A <em>Backfeed</em> is a\nlist of replies, likes, and mentions for the current page. Combined with POSSE,\nthis allows you to see replies to the current page across all different private\nsilos. For example, you might see comments from Mastodon and Reddit at the\nbottom of a blog post, as well as replies from other IndieWeb websites.</p>\n<p>Together, POSSE and backfeeding strike a good compromise between owning your\nown presence and participating in silos. They improve discoverability and allow\nfor reader interaction.</p>\n<h2 id=\"webmentions\" tabindex=\"-1\">Webmentions <a class=\"header-anchor\" href=\"#webmentions\">#</a></h2>\n<p>The IndieWeb community has authored several standards that allow IndieWeb\nwebsites to communicate.</p>\n<p><a href=\"https://indieweb.org/Webmention\">Webmentions</a> allow websites to be notified\nwhen another site links to them. By receiving a notification, a site can know\nabout replies and mentions without having to maintain impractical web crawlers\nor subscribe to a backlinking service.</p>\n<h3 id=\"receiving-webmentions\" tabindex=\"-1\">Receiving Webmentions <a class=\"header-anchor\" href=\"#receiving-webmentions\">#</a></h3>\n<p>I started by\n<a href=\"https://indieweb.org/webmention-implementation-guide\">implementing support</a> for\nreceiving Webmentions. This was super easy, I just needed to add a couple of\n<code>link</code> tags to the top of all pages:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>webmention<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://webmention.io/example.com/webmention<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>pingback<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://webmention.io/example.com/xmlrpc<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span></code></pre>\n<p><a href=\"https://webmention.io/\">WebMention.io</a> is a cloud service for receiving\nWebmentions. You might think it’s odd to use a cloud service for this, but it’s\nnot a problem as I’m still using my own domain for the pages and could switch\nthe Webmentions service at any time. IndieWeb isn’t about self-hosting, it’s\nabout owning your identity and data.</p>\n<h3 id=\"sending-webmentions\" tabindex=\"-1\">Sending Webmentions <a class=\"header-anchor\" href=\"#sending-webmentions\">#</a></h3>\n<p>I currently send Webmentions manually using\n<a href=\"https://telegraph.p3k.io/\">Telegraph</a> or <a href=\"https://indiewebify.me/\">IndieWebify</a>.</p>\n<p>My blog is statically hosted and is built using GitLab CI. As the site is only\npublished when CI finishes, it would be impossible to include sending web\nmentions as part of the same CI pipeline. In the future, I’ll probably look into\nusing Brid.gy or some other tool to send Webmentions by monitoring my web feeds.</p>\n<h2 id=\"microformats2\" tabindex=\"-1\">Microformats2 <a class=\"header-anchor\" href=\"#microformats2\">#</a></h2>\n<p>Personal websites can contain a variety of content. Long-form articles,\nTwitter-like notes, location check-ins, reviews, and replies.\n<a href=\"https://microformats.org/\">Microformats2</a> is a way of marking up the content of\nweb pages so that machines can understand it better. This is a powerful thing\nwhen combined with Webmentions as it allows the receiving website to understand\nwhat is linking to it and why.</p>\n<p>Microformats2 works by adding classes to elements representing content:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>article</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>h-entry<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h2</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>p-name<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Hello world!<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h2</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/tags/a-tag/<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>p-category<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        A tag\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>e-content<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        This is the article's content.\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>article</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Implementing support for <a href=\"https://microformats.org\">Microformats2</a> (mf2) was a\nhuge pain, I cannot overstate how much so. The documentation was very fragmented\nand inconsistent, and the tools I found to test mf2 didn’t match the\ndocumentation.</p>\n<p>The most extreme problem I had was with\n<a href=\"https://indieweb.org/authorship\">authorship</a> - authorship is how you find out\nwho is the author of a piece of content. The documentation says that you should\nbe able to just include a link to the homepage in each piece of content, and\ntools should fetch the author info:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>article</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>h-entry<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>e-content<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>This is an example note<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://rubenwardy.com<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>u-author<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>article</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>However, this did not work at all. Most of the tools I found didn’t make further\nrequests and only looked at the current page. This makes sense I guess, but it’s\nannoying that the documentation said it was possible.</p>\n<p>The next thing I looked at was including the authorship information in the\nfooter of each page, and then referencing it from each piece of content like so:</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>article</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>h-entry<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>e-content<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>This is an example note<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>u-author<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>article</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>footer</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>h-card<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>img</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>u-photo<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/me.jpg<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>span</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>p-name<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Author Name<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>span</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>footer</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Unfortunately, this didn’t work with any of the tools either. The only thing I\nfound that worked was to include the authorship information in full in every\nsingle piece of content.</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>article</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>h-entry<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>e-content<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>This is an example note<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>p-author h-card d-none<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>u-url p-name<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://rubenwardy.com/<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>rubenwardy<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>img</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>u-photo<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>/me.jpg<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>article</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Thank you to users on the IndieWeb IRC channels for pointing towards useful\ntools and documentation, and asking my newbie questions. Without them, I\nwouldn’t have been able to implement support at all. I believe that they have\nimproved the documentation a bit based on my feedback, although the authorship\npage still mentions the methods I tried that didn’t work.</p>\n<p>The three main tools I used for testing Microformats2 were\n<a href=\"https://indiewebify.me/\">IndieWebify</a>, <a href=\"https://pin13.net/mf2/\">pin13.net mf2</a>,\n<a href=\"https://waterpigs.co.uk/php-mf2/\">Waterpigs mf2</a>.</p>\n<h2 id=\"backfeeding\" tabindex=\"-1\">Backfeeding <a class=\"header-anchor\" href=\"#backfeeding\">#</a></h2>\n<p>A <em>Backfeed</em> is a list of replies, likes, and mentions for the current page. For\nexample, you might see comments from Mastodon and Reddit at the bottom of a blog\npost, as well as replies from other IndieWeb websites.</p>\n<h3 id=\"backfeeding-likes\" tabindex=\"-1\">Backfeeding likes <a class=\"header-anchor\" href=\"#backfeeding-likes\">#</a></h3>\n<p>My blog shows likes from Mastodon and other social platforms using\n<a href=\"https://brid.gy/\">Brid.gy</a> and Webmentions. When I post a link to my blog on\nsocial media, Brid.gy monitors activity and sends Webmentions. JavaScript on my\nblog fetches Webmentions and updates the counter. My blog also caches like\ncounts at build time. In the future, I’ll make it so that the JS only fetches\nactivity since the blog was last built, reducing the amount of work the Web\nMentions API needs to do.</p>\n<h3 id=\"backfeeding-replies\" tabindex=\"-1\">Backfeeding replies <a class=\"header-anchor\" href=\"#backfeeding-replies\">#</a></h3>\n<p>I decided not to implement the backfeeding of comments from social media as I’m\nconcerned about the privacy implications. Just because someone decides to reply\npublicly on social media silos doesn’t mean that they want their post and their\nprofile picture to appear on my website. Additionally, Webmentions can’t be\ndeleted meaning that the comment may continue to appear on my website even after\nthe author deletes it on the silo.</p>\n<p>Two good articles discussing the ethics and privacy challenges of backfeeding\ninclude\n<a href=\"https://shkspr.mobi/blog/2022/12/the-ethics-of-syndicating-comments-using-webmentions/\">“The ethics of syndicating comments using WebMentions”</a>\nand\n<a href=\"https://sebastiangreger.net/2018/05/indieweb-privacy-challenge-webmentions-backfeeds-gdpr/\">“The IndieWeb privacy challenge”</a>.</p>\n<p>I may reconsider this in the future. I’d need to make it sufficiently clear to\ncommenters and allow them to opt-out. I’d also need to make sure that deleting\nthe comment on the silo also deletes it from my website.</p>\n<h3 id=\"comment-form\" tabindex=\"-1\">Comment form <a class=\"header-anchor\" href=\"#comment-form\">#</a></h3>\n<p>I added a comment form to the bottom of posts on my blog. My blog is statically\nhosted. To collect comments, I have a service running on another subdomain that\ncollects any comments and sends them to me. Users can also choose to send\ncomments by email or another method. All comments are moderated before showing\non my blog.</p>\n<p>To avoid spam, the comment form has a “username” form hidden using CSS. Most\nspam bots don’t bother applying the CSS so will see the field and fill it in.\nThis is called a honeypot field and is surprisingly effective - I was receiving\nmultiple spam comments a day, but since adding the field I’ve only received a\nsingle spam comment.</p>\n<pre class=\"language-html\"><code class=\"language-html\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>style</span><span class=\"token punctuation\">></span></span><span class=\"token style\"><span class=\"token language-css\">\n    <span class=\"token selector\">input[name=\"username\"]</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">display</span><span class=\"token punctuation\">:</span> none<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n</span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>style</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>username<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span></code></pre>\n<p>You can find\n<a href=\"https://gitlab.com/rubenwardy/blog_api\">the source code behind commenting on GitLab</a>.</p>\n<h2 id=\"thoughts-on-the-indieweb\" tabindex=\"-1\">Thoughts on the IndieWeb <a class=\"header-anchor\" href=\"#thoughts-on-the-indieweb\">#</a></h2>\n<p>IndieWeb standards are fairly obscure and don’t seem to have been adopted much\nyet. Of all the posts I’ve made since adding IndieWeb support, this is probably\nthe only one that will actually find websites linked to that can receive\nWebmentions. If a popular Content Management System, like WordPress, added\nbuilt-in support for Webmentions and mf2, I could see it suddenly becoming a lot\nmore popular.</p>\n<p>Whilst Webmentions are pretty cool, Microformats2 is pretty complicated and was\npretty annoying to implement. I know that the IndieWeb crowd will have strong\nopinions on this, but I quite like how simple <a href=\"https://json-ld.org/\">JSON-LD</a>\nwas to add support for and that it’s JSON.</p>\n<p>As for the community side of IndieWeb, personal websites have seen a big\nresurgence since the fall of Twitter. I think we’re in a new golden age for RSS\nand personal websites. I’ve been encouraging a lot of my friends to take up\nblogging.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I’m certainly a more technical user than the average blogger. I don’t mind being\nan early adopter of technology and appreciate the goals of IndieWeb. I like how\nthey try to focus on the people before the technology, even though Microformats2\nleaves much to be desired.</p>\n<p>I currently only post blog posts on my website. I don’t plan to post notes on my\nwebsite as I prefer to use Mastodon directly. But I might start posting my\nphotography here.</p>\n<p>I’m undecided as to whether I’ll stick with IndieWeb technology in the long\nterm, but I’ll certainly continue to own and publish on my own domain.</p>\n","url":"https://blog.rubenwardy.com/2023/10/10/hello-indieweb/","summary":"The collapse of Twitter last year got me thinking about closed platforms and reducing the hold that privately-owned silos have over the Internet.","date_published":"2023-10-10T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/V34DGI5mGz-540.png","tags":["reviews","webdev","blogging"]},{"id":"/2023/08/29/ghosts-live-in-the-ground/","language":"en","title":"Ghosts live in the ground: debugging an electronic circuit","content_html":"<p>I’m making a device to water my basil plants. Whilst working on this, I\nencountered an issue where running the pumps whilst connected to WiFi would\ncrash the microcontroller. After investigation, I found the cause was poor\ngrounding and Electromagnetic Interference (EMI).</p>\n<h2 id=\"plant-watering-system\" tabindex=\"-1\">Plant watering system <a class=\"header-anchor\" href=\"#plant-watering-system\">#</a></h2>\n<p>Previously, I created a <a href=\"/2022/03/17/plant-monitor/\">plant monitor</a> that tracked\nsoil moisture and sent notifications when the plant needed watering. But wouldn’t\nit be nice if it could water the plant instead of sending a notification? This\nnew project will do exactly that.</p>\n<p>I’m designing the plant watering system to have support for four plants, with a\nsoil moisture sensor and pump for each plant. The pumps will draw water from a\nwater tank, which contains a water level sensor. I’m using an ESP32\nmicrocontroller as the brains, as it comes with Internet connectivity and\nlow-power deep sleeping.</p>\n<p>I designed a circuit and then made a prototype on a breadboard, which worked as\nexpected. Making a prototype allows me to find any issues with the circuit\ndesign before I bring out the soldering iron. Or, at least, it’s supposed to.</p>\n<figure class=\"undefined\"><a href=\"/static/img/9sXPrJA3X0-3507.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/LiESHuP_Q0-540.webp 540w\"><img src=\"/static/img/LiESHuP_Q0-540.png\" alt=\"The schematic for my plant watering system's circuit. Created using KiCAD.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"381\"></picture></a><figcaption>The schematic for my plant watering system's circuit. Created using KiCAD.</figcaption></figure>\n<h2 id=\"the-problem\" tabindex=\"-1\">The problem <a class=\"header-anchor\" href=\"#the-problem\">#</a></h2>\n<p>I started working on the production version, soldered on to stripboard.\nStripboard is a material for making circuits; it comes pre-formed with a grid of\nholes and parallel strips of copper traces. I use it because it’s an easy way to\nmake semi-permanent circuits for my projects.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/LO71Lq9Spt-540.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/9af6zTL_J7-540.webp 540w\"><img src=\"/static/img/9af6zTL_J7-540.jpeg\" alt=\"The front of the stripboard.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"372\"></picture></a><figcaption>The front of the stripboard.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/N_rVBVkMyh-540.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/eYXZMPQpuo-540.webp 540w\"><img src=\"/static/img/eYXZMPQpuo-540.jpeg\" alt=\"The back of the stripboard, showing copper traces.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"372\"></picture></a><figcaption>The back of the stripboard, showing copper traces.</figcaption></figure></div>\n\t\t\t</div>\n<p>I decided to work on the circuit in parts, so I could test the circuit as I\nwent. I started by soldering on the pump circuits and connecting them to the\nmicrocontroller. It is at this point that I discovered a big issue - when I ran\na pump, the microcontroller would crash. This would result in the pump staying\non until power was cut.</p>\n<blockquote>\n<p>Guru Meditation Error: Core  0 panic’ed (IllegalInstruction). Exception was\nunhandled.</p>\n</blockquote>\n<p>I noticed that if I disconnected the microcontroller from WiFi, the crash would\nno longer happen.</p>\n<h2 id=\"investigating-the-issue\" tabindex=\"-1\">Investigating the issue <a class=\"header-anchor\" href=\"#investigating-the-issue\">#</a></h2>\n<h3 id=\"power-transistor-drawing-too-much-current\" tabindex=\"-1\">Power transistor drawing too much current? <a class=\"header-anchor\" href=\"#power-transistor-drawing-too-much-current\">#</a></h3>\n<p>I’m using power transistors to allow the microcontroller to turn the pumps on\nand off. This is required because the pumps would draw way too much current if\npowered directly by the microcontroller. I’m using two power supplies. The pumps\nare powered by a 5V USB plug, the kind commonly used to charge phones. The\nmicrocontroller is powered by a micro USB cable to my computer so that I can\nprogram and debug it.</p>\n<p>At first, I thought that the power transistors were still drawing too much\ncurrent from the microcontroller and causing the crash. The fact that the crash\nonly happened when the WiFi was running strongly hinted at this, as WiFi\nrequires a lot of current itself. To test this theory, I replaced the\ntransistors with a push button. The microcontroller still crashed, showing this\nwasn’t the cause.</p>\n<h3 id=\"adjusted-capacitors\" tabindex=\"-1\">Adjusted capacitors <a class=\"header-anchor\" href=\"#adjusted-capacitors\">#</a></h3>\n<p>After discussing the issue with a friend, I moved the capacitor closer to the\npump and added another, smaller, capacitor. This didn’t fix the issue.</p>\n<h3 id=\"severed-common-ground\" tabindex=\"-1\">Severed common ground <a class=\"header-anchor\" href=\"#severed-common-ground\">#</a></h3>\n<p>At this point, the only connection to the microcontroller was a ground rail. My\nfriend now suspected that the issue might be due to a difference in voltage\nbetween the grounds of the two power supplies causing current to flow along the\ncommon ground. I severed the ground rail but the issue still happened. This was\nvery strange as there was now no connection at all between the pump and the\nmicrocontroller?!</p>\n<h3 id=\"last-hopes\" tabindex=\"-1\">Last hopes <a class=\"header-anchor\" href=\"#last-hopes\">#</a></h3>\n<p>I decided to create the stripboard again from scratch to make sure it wasn’t a\nsoldering issue. I did this, and the problem still happened.</p>\n<p>At this point, I suspected the circuit was either haunted or there was some kind\nof EM interference. I broke the ground rail closer to the ESP32 and it started\nworking again. It turns out that the ground rail was acting like an antenna. A\ncurrent was being induced by the noisy pump and caused the ESP32 to crash\ndespite not being connected.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Dn4DU3Jd_c-669.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/yZPdTBLGGn-540.webp 540w\"><img src=\"/static/img/yZPdTBLGGn-540.jpeg\" alt=\"Photo showing where I broke the ground rail closer to the ESP32.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"817\"></picture></a><figcaption>Photo showing where I broke the ground rail closer to the ESP32.</figcaption></figure>\n<h2 id=\"achieving-good-grounding\" tabindex=\"-1\">Achieving good grounding <a class=\"header-anchor\" href=\"#achieving-good-grounding\">#</a></h2>\n<p>The lesson here is that stripboards have pretty terrible grounding and\nElectromagnetic Compatibility (EMC) properties.</p>\n<p>Someone on the BPS Space Discord recommended\n<a href=\"https://www.youtube.com/live/ySuUZEjARPY\">a video</a> on proper grounding in\ncircuits. This was very interesting, I learned a lot from it and it will improve\nmy circuit designs in the future.</p>\n<p>While watching that video in a coffee shop, a plant fell on me from a shelf\nabove, covering me in soil. I guess it wanted to teach me the difference between\ngrounding and earthing.</p>\n<p>To continue with this project, I have decided to bite the bullet and make a\nPrinted Circuit Board (PCB) for it. I have been looking for an excuse to make a\nPCB for a while. PCBs allow you to get vastly better grounding, which will avoid\nEMI issues like this in the future.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/7tMKsLMwHL-1125.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/wjIAEcZCjT-540.webp 540w\"><img src=\"/static/img/wjIAEcZCjT-540.png\" alt=\"My Work In Progress Printed Circuit Board (PCB) design. Created using KiCAD.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"458\"></picture></a><figcaption>My Work In Progress Printed Circuit Board (PCB) design. Created using KiCAD.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/5zdnRETV9d-908.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/VRyH7zT4yP-540.webp 540w\"><img src=\"/static/img/VRyH7zT4yP-540.png\" alt=\"The source of my PCB, showing footprints and traces. Created using KiCAD.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"459\"></picture></a><figcaption>The source of my PCB, showing footprints and traces. Created using KiCAD.</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Thanks to my friend and people on the BPS Discord for helping me with this\nissue.</p>\n<p>Well, this was certainly an… educational problem to have. This problem, and\nthe switch to making a PCB, has delayed this project by a month at least. But I\nwill learn a lot more and gain more value from this project. I just hope my\nbasil can survive in the meantime.</p>\n","url":"https://blog.rubenwardy.com/2023/08/29/ghosts-live-in-the-ground/","summary":"Whilst working on a plant watering system, I spent hours debugging an issue that turned out to be Electromagnetic Interference (EMI)","date_published":"2023-08-29T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/hxAMPXvDq8-540.png","tags":["electronics","projects"]},{"id":"/2023/08/16/minetest-ctf-is-10/","language":"en","title":"Ten years of Capture The Flag","content_html":"<p>Ten years ago today, I uploaded the first version of Capture The Flag. Capture\nThe Flag is a multiplayer game where two teams of players battle to claim the\nother team’s flag whilst defending their own. Capture The Flag is played in a\ndestructible voxel environment, allowing players to build defences and place\ntraps.</p>\n<p>Capture The Flag started life as a persistent kingdoms game but quickly pivoted\nto a match-based team game. It was developed iteratively, taking into account\nplayer feedback. I hosted a server for the game for many years and a community\nformed around it. In 2021, I handed over the reins to CTF to very capable hands;\nit remains <a href=\"https://www.minetest.net/\">Minetest</a>’s most popular server to this\nday.</p>\n<p>This article covers the history of CTF, the lessons I learned, and the changes I\nmade along the way.</p>\n<h2 id=\"2013-city-vs-city-game\" tabindex=\"-1\">2013: City vs City game <a class=\"header-anchor\" href=\"#2013-city-vs-city-game\">#</a></h2>\n<p>I originally created Capture The Flag to be used on a persistent map, where\nteams were countries and players worked together to defend castles. Any player\ncould create a new country and place flags. Flags were placed at the top of\ncastles and allowed a country to claim ownership of the surrounding land.</p>\n<p>There was a diplomacy system that allowed countries to declare war and peace,\nand form alliances. Players could place turrets that automatically attacked\nenemy players. If an enemy country claimed a flag, it would gain ownership of\nthe surrounding land - including any turrets.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Md1sPSK36P-936.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/EZDm1V5gUS-540.webp 540w\"><img src=\"/static/img/EZDm1V5gUS-540.png\" alt=\"A Minetest GUI. There's two rows. The first says &quot;red, peace&quot;, the second says &quot;blue, war&quot;.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"373\"></picture></a><figcaption>The diplomacy tab in the team GUI.</figcaption></figure>\n<p>I created a private server to test the game with friends. I hosted this at home,\nso it wasn’t online that much. I implemented the basics - countries, flags, land\nownership, and turrets - but never really got to the point where I felt like it\nwas ready to host publicly.</p>\n<p>When I first started working on the game, I knew it would be running on a server\nand didn’t want it to crash. I added a lot of if-statements for things that\nshould never happen, and in the process caused a lot of bugs to become subtle\nbehaviour issues rather than crashes. The lesson I learned here is that it’s\nbetter to crash than to obscure a bug or end up in an unknown state.</p>\n<p>From the start, I wanted the core mods to support being used in any team-based\ngames, even without flags. A lot of features were added in additional mods, for\nexample, flags were added by <code>ctf_flags</code>. There were also a lot of configuration\noptions that were never really needed. I learned the principle of\n<a href=\"https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it\">You Aren’t Gonna Need It (YAGNI)</a>\nthe hard way.</p>\n<h2 id=\"2015-switch-to-match-based-capture-the-flag\" tabindex=\"-1\">2015: Switch to match-based Capture The Flag <a class=\"header-anchor\" href=\"#2015-switch-to-match-based-capture-the-flag\">#</a></h2>\n<p>In July 2015, after a while of not working on the game, I decided to pivot to\nmaking a match-based game instead. I still planned to work on a persistent\nkingdoms game eventually, but doing a match-based game first would allow me to\ndevelop the core code and test it. I split the game into a reusable mod pack\ncalled CTF PvP Engine and a game called Capture The Flag.</p>\n<p>Around this time, I played\n<a href=\"https://store.steampowered.com/app/219830/King_Arthurs_Gold/\">King Arthur’s Gold</a> -\na 2d sidescrolling capture-the-flag game where players build castles and then\nattack each other. The game’s structural integrity mechanic made sieges pretty\nawesome. I quite liked the defensive building part of the game and felt that a\nvoxel world was especially well-suited for this kind of gameplay. King Arthur’s\nGold was Capture The Flag’s main inspiration.</p>\n<figure class=\"undefined\"><a href=\"/static/img/npEmqtDlgO-1920.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/a2QG9JSTTH-540.webp 540w\"><img src=\"/static/img/a2QG9JSTTH-540.jpeg\" alt=\"Screenshot of a 2d sidescrolling game. On each side of the image, there's castles built out of stone. Red and blue players are approach each other from opposite sides, firing arrows and raising shields.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>King Arthur's Gold (source: <a href=\"https://store.steampowered.com/app/219830/King_Arthurs_Gold/\">Steam page</a>)</figcaption></figure>\n<h2 id=\"2015-public-server\" tabindex=\"-1\">2015: Public server <a class=\"header-anchor\" href=\"#2015-public-server\">#</a></h2>\n<p>In September 2015, I started the official Capture The Flag server, hosted on\nDigitalOcean. The server was mostly dead for a few months but then suddenly\nbecome popular - I guess it had reached the critical number of players that\nallowed the game to become fun.</p>\n<p>By this point, I had been making mods for Minetest for a couple of years but\nhadn’t created a public server before. One thing I learned was just how good\nrunning a server is at finding bugs - players do a lot of things that you don’t\nexpect. One example is when players discovered that dropping sand on flags\ncaused them to be dropped as items, allowing players to place multiple flags for\ntheir team. This made it much harder for the other team to win as they would\nhave needed to capture all the flags.</p>\n<p>I found that another benefit of running a public server is getting access to a\ncommunity of players and feedback. From this point onwards, I was able to\niteratively develop the game based on how the players played it and what\nfeedback they gave. For example, the game contained team chests that allowed\nplayers to share weapons and items. I discovered that a lot of team members were\nintentionally destroying unused weapons rather than placing them in the team\nchest, to prevent newbie players from picking them up, dying, and delivering the\nweapons to the enemy team. To fix this issue, I made it so part of the chest was\nrestricted to players who had at least a certain amount of in-game experience.</p>\n<figure class=\"undefined\"><a href=\"/static/img/d9hrCqOul9-762.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/FNlkIyfTCH-540.webp 540w\"><img src=\"/static/img/FNlkIyfTCH-540.png\" alt=\"Screenshot of a chest inventory GUI. The inventory is split in half. On the left, there's &quot;Any player can take from here, including enemies.&quot; On the right, it says &quot;You need more score (200+)&quot;\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"574\"></picture></a><figcaption>The pro section in the team chest. An early version allowed enemies to take from the chest too, although this was changed later on.</figcaption></figure>\n<p>I delegated the day-to-day running of the server to moderators. I wasn’t\ninterested in being a moderator or server owner, for me the fun was in creating\nan experience and seeing how the players react, rather than enforcing rules. I\nchose new moderators based on the recommendation of existing moderators - I felt\nthat the existing moderators were better placed to judge character. A lot of\npeople asked to become a moderator, but I rarely granted this as asking to\nbecome a moderator is a red flag.</p>\n<p>The DigitalOcean server I was using was quite underpowered. In April 2016, a\ncommunity member called Calinou offered to host the server and did so until they\nstopped it in November 2016.</p>\n<h2 id=\"2017-restarted-the-server-and-development\" tabindex=\"-1\">2017: Restarted the server and development <a class=\"header-anchor\" href=\"#2017-restarted-the-server-and-development\">#</a></h2>\n<p>In October 2017, I decided to restart the server and development on Capture the\nFlag due to popular demand. This time, I hosted the server using a dedicated\nmachine on Kimsufi - a brand owned by OVH. With hindsight, hosting on a cheap\ndedicated server wasn’t good value for money, but it was faster than the\nDigitalOcean VPS.</p>\n<p>My aim for development at this point was to improve the variety of matches,\nincrease strategy, and incentivise co-op. It was too easy for a match to be won\nby a lone hero.</p>\n<h2 id=\"2018-multiple-maps\" tabindex=\"-1\">2018: Multiple maps <a class=\"header-anchor\" href=\"#2018-multiple-maps\">#</a></h2>\n<p>After months of work in secret, I released support for multiple maps. Instead of\nalways playing on the same map, the map was now different for each match.\nPlayers could use a dedicated tool to create new maps and then submit them for\ninclusion.</p>\n<p>The feature shipped with three maps - Two Hills and Two Lakes (the previous map\nused), The Bridge, and Caverns. Caverns was my favourite map. It’s set\nunderground, with three caverns and a small dungeon to explore. All of these\nwere disconnected; players needed to tunnel to reach the enemy team. I liked how\nthis added a stealth aspect to the game.</p>\n<p>Notice how none of these maps came with prebuilt structures. The aim was still\nto encourage players to build their own defences and use the destructible\nenvironment to their advantage. But that doesn’t mean I was against it\ncompletely - some of the new maps contributed after the feature was released\nincluded entire towns.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/K30MIKRBHv-987.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/zKpj7-UZxx-540.webp 540w\"><img src=\"/static/img/zKpj7-UZxx-540.png\" alt=\"Screenshot of a voxel world. To the top and the bottom of the image are grassy hills. Between them are two lakes and sand.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"378\"></picture></a><figcaption><b>Two Hills, Two Lakes:</b> The classic CTF map. Photo by Shara.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/S0GvN3Ty9R-1920.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/dYcBxWIoBL-540.webp 540w\"><img src=\"/static/img/dYcBxWIoBL-540.png\" alt=\"Screenshot of a voxel world. A canyon with a river splits the terrain. There's a one meter wide bridge from one side to the other.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption><b>The Bridge:</b> Very little cover and no apple trees. A river separates the two teams.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/DHG8561lPB-1920.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/4DCq8EFlrh-540.webp 540w\"><img src=\"/static/img/4DCq8EFlrh-540.png\" alt=\"Screenshot of a voxel cavern light by torches. In the center, there's a blue flag.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption><b>Caverns:</b> Each team has a cavern. Lots of ores and tunnelling.</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"2019-apple-cheating-and-medkits\" tabindex=\"-1\">2019: Apple cheating and medkits <a class=\"header-anchor\" href=\"#2019-apple-cheating-and-medkits\">#</a></h2>\n<p>Apples were used to heal the player in CTF. Someone made a cheat that would eat\napples automatically when they took damage, by emulating player input.</p>\n<p>The cheat was programmed quite poorly and was vulnerable to a\n<a href=\"https://en.wikipedia.org/wiki/Arbitrary_code_execution\">Remote Code Execution (RCE)</a>\nbug. This could have allowed the server and other players to run non-sandboxed\nshell commands, taking over the cheater’s computer. I didn’t exploit this for\nobvious reasons.</p>\n<p>I wrote a mod to detect the use of this cheat. At the end of each match, a GUI\nwindow is shown with the match results. I dealt damage to all players when this\nwindow was shown and then listened to key presses using a hidden text field. If\na bunch of numbers were pressed (ie: 3333331), then I’d know that they were\nusing the apple cheat. The anti-cheat mod would wait a random period, 5-10\nminutes, and then ban them with an anti-cheat message.</p>\n<p>This anti-cheat wasn’t a permanent solution but was enough to catch and\nfrustrate the cheat writer, which was the main goal. We eventually replaced\napples with medkits and bandages. Players can use medkits to heal themselves but\nhave to remain still whilst it acts, leaving them vulnerable. Players can use\nbandages to instantly heal their teammates but not themselves.</p>\n<h2 id=\"2020-classes-and-ctf-2-0\" tabindex=\"-1\">2020: Classes and CTF 2.0 <a class=\"header-anchor\" href=\"#2020-classes-and-ctf-2-0\">#</a></h2>\n<p>In 2020, the COVID pandemic meant that I was able to find time again for the\ngame. I worked on a classes feature inspired by King Arthur’s Gold. It allowed\nplayers to select a class at their team’s flag. Classes come with different\nabilities and initial items. The aim was to increase variety and allow other\nstyles of play to become more viable, as to this point the most successful\ntactic was to spam with a steel sword.</p>\n<p>I also refactored a lot of the code to remove unused features left over from the\nCity vs City days. I released this and classes as Capture The Flag 2.0.</p>\n<figure class=\"undefined\"><a href=\"/static/img/xu6unFc-aZ-1122.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Iv_HbeSSAR-540.webp 540w\"><img src=\"/static/img/Iv_HbeSSAR-540.png\" alt=\"A Minetest GUI. There's four columns, one for each class. Knight, Sharp Shooter, Medic, and Rocketeer.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"178\"></picture></a><figcaption>Players could change classes by right-clicking their team's flag. RIP Rocketeer.</figcaption></figure>\n<h2 id=\"2021-handover-to-landarvargan\" tabindex=\"-1\">2021+: Handover to LandarVargan <a class=\"header-anchor\" href=\"#2021-handover-to-landarvargan\">#</a></h2>\n<p>By 2021, I had fully lost interest in working on Capture The Flag. I had limited\ntime and there were many other projects I wanted to be working on. I chose\n<a href=\"https://fosstodon.org/@Lone_Wolf\">LandarVargan</a> to become the new project lead\nas he had been significantly contributing to CTF for a while.</p>\n<p>After taking over, LandarVargan added savilli to the development team. They\nworked together on Capture the Flag 3.0, a rewrite to get rid of eight years of\ntechnical debt. Many new gameplay features have been added to 3.0 including\nsupport for multiple game modes. These game modes are another way of increasing\nvariety.</p>\n<p>During a testing session for the 3.0 rewrite, some players got ahold of\nnear-infinite stacks of grenades. This formed the inspiration for the Nade Fight\ngame mode added by savilli. In this mode, players fight with an unlimited\nsupply of 3 different grenades. Each one has a unique use and a cooldown after\nthrowing. Void grenades trap players, firecrackers damage them, and yellow\ngrenades allow the player to move quickly through the air.</p>\n<p>Unlike me, LandarVargan plays existing competitive PvP games in his own time,\nincluding Albion Online and Brawlhalla. He was interested in what about those\ngames and CTF was fun and attracted their player base. In studying them, he got\na lot of ideas for how he could improve CTF.</p>\n<figure class=\"figure \">\n\t<blockquote class=\"mb-1\" ><p>CTF’s player base is small if you compare it to other games, but that also\nmeans it’s much easier for devs to stay in touch with the community and\nlisten to their feedback. Most people who play Minetest also have some\nexperience programming too, and have access to CTF’s open-source code, so\nfeedback is much higher quality than it is for most games.</p>\n<p>It’s extremely helpful to have staff that can handle the day-to-day\nrunning of the server and social media platforms (ie: Discord) for you.\nI was semi-aware of the difficulties of running a server and community\nbefore taking over CTF but greatly underestimated it, and the ever-ongoing\nprocess of learning how to do/delegate that has probably been the most\nvaluable experience I’ve gotten out of working on CTF.</p>\n<p>In the future, I don’t plan to make many more big additions - like I\nalready have with the cosmetics system, multiple modes system, or Elysium\n(A hub area outside of the main gameplay loop where players can hang out/\npractice PvP after capturing the flag) and hope to get to a point where\nI’m just playing the game, polishing/improving existing things, and adding\nnew modes, which most of my remaining ideas revolve around.</p>\n</blockquote>\n\t<figcaption class=\"pl-4 mt-3\">\n\t\t—LandarVargan</figcaption>\n</figure>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I never ended up continuing my City vs City game, but someone else used the\nCTF PvP Engine to make a similar game called\n<a href=\"https://forum.minetest.net/viewtopic.php?t=14587\">Persistent Kingdoms</a>.</p>\n<p>Capture The Flag feels slightly bittersweet to me. Whilst it has been popular\nand liked by a lot of players, I feel like it never became the game I wanted it\nto be. I was never that interested in first-person shooters; I wanted it to be a\ngame of strategy and castle building and sieging. To get it to that I probably\nwould have had to implement some difficult features like structural integrity\nand pivot the game’s features more to that goal. Not sure whether the player\nbase would have wanted this. Capture The Flag was ultimately developed by\nwatching how it was played.</p>\n<p>Whilst I am no longer interested in continuing CTF myself, I am glad that the\ncommunity grew to a point where it was able to attract contributors and survive\nwithout me. I think this is the best indicator of the project’s success.</p>\n<h3 id=\"join-the-celebrations\" tabindex=\"-1\">Join the celebrations <a class=\"header-anchor\" href=\"#join-the-celebrations\">#</a></h3>\n<p>The 8th anniversary of the server will be the 1st of September. Fancy\ncelebrating? From today until the 1st of September, there will be fireworks and\nparty hats on the server. You can play Capture The Flag by joining\n<a href=\"https://ctf.rubenwardy.com/\">ctf.rubenwardy.com</a>:30001 using a Minetest client.</p>\n<figure class=\"undefined\"><a href=\"/static/img/AWPOfFx3qU-1115.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/o4wv1rDVVY-540.webp 540w\"><img src=\"/static/img/o4wv1rDVVY-540.png\" alt=\"A Minetest GUI. There's four columns, one for each class. Knight, Sharp Shooter, Medic, and Rocketeer.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"155\"></picture></a><figcaption>Fireworks will spawn in treasure chests. You can get an Anniversary Hat by launching a rocket into the sky. Screenshot by LandarVargan.</figcaption></figure>\n<!--\n## Timeline\n\n* 2013, Aug: start of code\n  * Was originally titled \"Capture The Flag / City vs City combat\" in a repo called capture_the_flag\n  * Persistent kingdoms-style gameplay\n  * Turrets, diplomacy (war, peace, alliance), ...\n  * Had private (invite-only) server for testing\n* ?: Someone else hosted a public City vs City server, forget who\n* 2015, Jul: Split into mod pack (ctf_pvp_engine) and game (capturetheflag)\n* 2015, Jul: Switched to match-based CTF\n* 2015, Sep: Started hosting my own server\n* 2015, Nov: Guns! Started with kaeza's firearms mod, was buggy, switched to shooter\n* 2015, Dec: Team chests\n* 2015, Dec: Stats / league table\n* 2016, Feb: Two hills and two lakes map\n* 2016, Apr: Calinou takes over hosting CTF\n* 2016, Apr: kill history\n* 2016, between Nov-Jan: Server stopped\n* 2017, Oct: Server restarted after someone asked\n* 2018, Jan: sprinting, team doors\n* 2018, Jan: [Multiple maps](https://forum.minetest.net/viewtopic.php?p=309834#p309834)\n* 2018/19?: ANAND as a developer\n* 2018, May: Discord\n* 2019, Mar: Apples replaced with medkits and bandages\n* ANAND leaves\n* 2020, Mar: Classes\n* LW as a developer\n* 2021, Nov: hand over to LW\n* 2021, Nov: v3\n-->\n","url":"https://blog.rubenwardy.com/2023/08/16/minetest-ctf-is-10/","summary":"CTF is a PvP game made using Minetest. This article covers the history of CTF, the lessons I learned, and the changes I made along the way.","date_published":"2023-08-16T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/Rhe2YDjcPg-540.png","tags":["luanti","projects","gamedev"]},{"id":"/2023/07/14/make-games-not-game-engines/","language":"en","title":"Make games not engines: why I'll be using Godot engine","content_html":"<p>One of the reasons I learned how to program was to make games. Games are a\nunique form of creative medium, combining art, interactive storytelling, and\nvibrant worlds. But as a software engineer, it’s easy to lose sight of my goals\nand get trapped by the technical details. It’s common for software engineers in\ngame dev to roll their own engine, which I believe reduces productivity and is\nultimately a distraction to making a game.</p>\n<p>Note that I’m not just referring to making reusable or generic game engines; for this\narticle, I consider using low-level technology like OpenGL, SFML, or SDL to make\ngames to include the act of rolling your own game engine, even if the focus is\nspecific. It’s more manageable, but you still end up reinventing the wheel and\nhaving to solve many of the same problems.</p>\n<p>There are plenty of other articles about whether or not to make your own game\nengine. This article is personal to me; it’s an exploration of my journey in\ngame dev, a discussion of what motivates me, and a promise for the future.</p>\n<h2 id=\"my-journey\" tabindex=\"-1\">My Journey <a class=\"header-anchor\" href=\"#my-journey\">#</a></h2>\n<h3 id=\"learning-to-program\" tabindex=\"-1\">Learning to Program <a class=\"header-anchor\" href=\"#learning-to-program\">#</a></h3>\n<p>One of the first programming languages I learned was Scratch. I created a lot of\nsmall games, the most impressive of which was\n<a href=\"https://scratch.mit.edu/projects/1162598/\">Spirit Walker</a>, an adaption of my\nfavourite book series at the time.</p>\n<p>Not long afterwards, I was given a book called “Game Programming for Teens”,\nwhich taught me how to make games using the Blitz3D game engine. I created a series\nof competitive games, mostly with split-screen multiplayer, called\n<a href=\"https://github.com/rubenwardy/ProSeriesMiniGames\">Pro Series</a>.</p>\n<p>Neither of these tools were perfect: they were pretty limited, and the code I\nwrote sucked almost as much as the art I made. But those tools allowed seeing\nresults immediately and that is very important for motivation. I was able to\ncreate a lot of impressive things (for my age) in a fairly short time.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/wETt-G5wBT-480.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/T6FP5e00J8-480.webp 480w\"><img src=\"/static/img/T6FP5e00J8-480.png\" alt=\"Spirit Walker (2010)\" loading=\"lazy\" decoding=\"async\" width=\"480\" height=\"360\"></picture></a><figcaption>Spirit Walker (2010)</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/zccLmJBG0q-1326.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/af4ybxP4w2-540.webp 540w\"><img src=\"/static/img/af4ybxP4w2-540.png\" alt=\"Pro Series: Dogfight - two-player plane battle (2011)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>Pro Series: Dogfight - two-player plane battle (2011)</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/vyuTMmUdhv-1024.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/1-aOmjY9YJ-540.webp 540w\"><img src=\"/static/img/1-aOmjY9YJ-540.jpeg\" alt=\"Pro Series: Obstacle - adventure obstacle course (2011)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Pro Series: Obstacle - adventure obstacle course (2011)</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"rolling-my-own-engine\" tabindex=\"-1\">Rolling My Own Engine <a class=\"header-anchor\" href=\"#rolling-my-own-engine\">#</a></h3>\n<p>It wasn’t long before I was running up against the limitations of Scratch and\nBlitz3D. I briefly tried Unity, but I wasn’t a fan of how it worked.</p>\n<p>I googled “programming languages for game dev” and found C++. Perhaps not the\nbest advice for a beginner. From there, I found\n<a href=\"https://irrlicht.sourceforge.io/\">Irrlicht</a>, a 3d rendering library on top of\nwhich you can make your own engines. Whilst Irrlicht is largely obsolete and\ndead these days, at the time it had a thriving community and wasn’t too\noutdated. I discovered <a href=\"https://www.minetest.net\">Minetest</a> on the Irrlicht\nforums; I’ve been contributing to Minetest since this time, eventually making\ncore developer in 2015.</p>\n<p>I made a few simple game prototypes with Irrlicht, including a zombie survival\ngame. I also made <a href=\"https://rubenwardy.com/NodeBoxEditor/\">NodeBoxEditor</a>, a\nvoxel 3d modelling program for Minetest.</p>\n<p>Whilst these projects served as great learning exercises, I wasn’t able to\nfinish many projects as much of the focus was on the <em>tech</em> rather than on the\n<em>games</em>. One exception during this time was my HTML5 projects, I was able to\ncomplete quite a few small projects including\n<a href=\"https://physics.rubenwardy.com\">Physics Simulation Kit</a>.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/8jo9Zp_sV1-1024.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/P7Pmoo8-NB-540.webp 540w\"><img src=\"/static/img/P7Pmoo8-NB-540.png\" alt=\"A zombie survival game that was never finished, C++ Irrlicht (2012)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>A zombie survival game that was never finished, C++ Irrlicht (2012)</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/APK9Mum01X-756.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/j42mWXwWSD-540.webp 540w\"><img src=\"/static/img/j42mWXwWSD-540.png\" alt=\"A government politics sim prototype, HTML5 (2014)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"389\"></picture></a><figcaption>A government politics sim prototype, HTML5 (2014)</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"the-final-straw-rvwp\" tabindex=\"-1\">The Final Straw: RVWP <a class=\"header-anchor\" href=\"#the-final-straw-rvwp\">#</a></h3>\n<p><a href=\"/2025/02/12/rvwp/\">Ruben’s Virtual World Project (RVWP)</a> was a\nhybrid top-down shooter and base builder, where you manage a group of rebels\nhiding from a dystopian government. In “base builder” mode, the player would\nbuild a base and manage a group of characters similar to RimWorld. Then they\nwould switch to “Hero” mode, and go on missions in a top-down shooter.</p>\n<p>RVWP was written using C++ and SFML; I started the project to learn low-level\nmultiplayer networking but transitioned to making a game after a while. I spent\nfive years (on and off) working on it. In the end, I implemented very basic\ntop-down gameplay, a build mode, and some\n<a href=\"/2022/07/17/game-ai-for-colonists/\">NPC AI</a>. The amount of time I spent on\ngameplay was massively dwarfed by the amount of time working on the engine; I\nimplemented multiplayer networking, voxel lighting, entities, behaviour trees,\nand Lua scripting. By the time I got to this point, I had mostly lost interest\nin the game idea.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/OTyw-uIQ7E-400.gif\"><img src=\"/static/img/ogXwDoCf6g-400.gif\" alt=\"Z-Layer system in RVWP, C++ SFML (2015-2021)\" loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"300\"></a><figcaption>Z-Layer system in RVWP, C++ SFML (2015-2021)</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/z5GG_qT3fM-814.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/24zabsrqPU-540.webp 540w\"><img src=\"/static/img/24zabsrqPU-540.png\" alt=\"Base building in RVWP, C++ SFML (2015-2021)\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"425\"></picture></a><figcaption>Base building in RVWP, C++ SFML (2015-2021)</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"thoughts\" tabindex=\"-1\">Thoughts <a class=\"header-anchor\" href=\"#thoughts\">#</a></h2>\n<h3 id=\"what-did-i-learn\" tabindex=\"-1\">What did I learn? <a class=\"header-anchor\" href=\"#what-did-i-learn\">#</a></h3>\n<p>When I first started making games, most of my time was spent implementing\ngameplay. As I became a better programmer, solutions became increasingly more\nprogramming-focused, with less time being spent on gameplay.</p>\n<p>It all comes down to priorities. Creating my own game engines taught me a lot\nabout programming and game development. I implemented scene graphs, entity\nsystems, sounds, multiplayer networking, physics, tools, and more. I became\nbetter at C++ and programming overall. But I’m no longer interested in\nprogramming as a learning exercise, I want to actually make games.</p>\n<p>Time spent rolling my own tech is time I could be working on gameplay; how good\na game is depends a lot on the number of iterations and feedback cycles it goes\nthrough. Modern game engines make it much easier to prototype and get a game to\na playable state quickly. I’m not planning on starting an indie dev company, but\nif I were - time is money and using a game engine is a competitive advantage.</p>\n<p>I’m not saying that no one should write game engines. Someone needs to, and they\nare amazing learning experiences. But if you want to make a game, make a game,\ndon’t make an engine as well. And if you seriously want to make a game engine,\nmake sure you have a team and a limited scope (ideally a game in mind).</p>\n<figure class=\"figure \">\n\t<blockquote class=\"mb-1\" cite=\"https://web.archive.org/web/20201215065909/https://twitter.com/epyoncf/status/1123155254760038400\"><p>Give a man a game engine, and he’ll have fun making a game. Teach a man to\ncreate an engine and… I dunno, he’s still creating the, what is it, fifth\niteration of his engine?</p>\n</blockquote>\n\t<figcaption class=\"pl-4 mt-3\">\n\t\t—<a href=\"https://web.archive.org/web/20201215065909/https://twitter.com/epyoncf/status/1123155254760038400\">\n\t\t\t\tKornel Kisielewicz (@epyoncf)\n\t\t\t</a></figcaption>\n</figure>\n<h3 id=\"why-did-i-write-my-own-engine\" tabindex=\"-1\">Why did I write my own engine? <a class=\"header-anchor\" href=\"#why-did-i-write-my-own-engine\">#</a></h3>\n<p>So why did I ever decide to write my own engines? Part of it is the learning\nexercise excuse, RVWP was started so I could learn about multiplayer networking\nat a low level.</p>\n<p>Another part is being able to know and own all my code. Before I started using\nC++ and Irrlicht, I tried making games with Unity. I ran into several bugs in\nthe engine - given that Unity is closed-source, I was frustrated at not being\nable to fix the bugs or have any visibility into why they were happening. This\nis not as much of a problem with Godot, as it <s>has zero bugs</s> is open-source.</p>\n<p>Another part of it is dogma - I felt like existing engines were impure, buggy,\nand bloated. As a software engineer, it was much more fun to write my own tech,\nespecially to begin with when the project is new. It was also easy to justify -\nthis is just a learning exercise! The most important thing when making a game is\nto make the game. Players don’t care if the game is a few extra megabytes -\ngames are regularly in the gigabytes these days! Nowadays, I care more about the\noutcome than always aiming for ‘perfect’ tech. This quote comes to mind:</p>\n<figure class=\"figure \">\n\t<blockquote class=\"mb-1\" cite=\"https://www.polygon.com/2020/1/13/21064100/vvvvvv-source-code-game-development-terry-cavanagh-release\"><p>There’s a word for games where the code is barely hanging together, with\nstupid layout, utterly unscaleable fixes and workarounds on top of\nworkarounds.</p>\n<p>“Shipped”</p>\n</blockquote>\n\t<figcaption class=\"pl-4 mt-3\">\n\t\t—<a href=\"https://www.polygon.com/2020/1/13/21064100/vvvvvv-source-code-game-development-terry-cavanagh-release\">\n\t\t\t\tMike Bithell\n\t\t\t</a></figcaption>\n</figure>\n<p>Obviously, the above is taken to the extreme. I’m not planning to become a\n<a href=\"https://en.wikipedia.org/wiki/Flying_Spaghetti_Monster\">Pastafarian</a>, but I am\nnow willing to take a more pragmatic approach to creating technology. My goal\nfor the future is to focus less on the perfect tech and more on the thing I’m\ncreating. I will use established game engines and not be afraid to write\nimperfect code.</p>\n<h2 id=\"a-new-hope-godot\" tabindex=\"-1\">A New Hope: Godot <a class=\"header-anchor\" href=\"#a-new-hope-godot\">#</a></h2>\n<p>I tried out Godot quite a while ago, but it was still quite immature. Godot is a\nvery capable engine now, especially Godot 4. I’ve started using it for new game\nprojects. I really didn’t like Phaser.JS, so ended up converting my\n<a href=\"/2022/03/19/ld48-tin-mining/\">Tin Mining</a> game jam entry from that to Godot\nas well.</p>\n<p>Godot isn’t perfect. When I first used it, I wasn’t a fan of how it used\ninheritance rather than something more component-based. But this is an example\nof ‘purity’ again, there are plenty of ways to do composition in Godot and avoid\nall the issues with OOP-based designs.</p>\n<p>Premade game engines can have a bit of a learning curve, but it’s worth it in\nthe long run. Godot’s\n<a href=\"https://docs.godotengine.org/en/stable/index.html\">documentation</a> is very good\nand there are a lot of third-party tutorials and support available.</p>\n<figure class=\"undefined\"><a href=\"/static/img/JPiMw1C2RY-900.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/VGFWe5QjaR-540.webp 540w\"><img src=\"/static/img/VGFWe5QjaR-540.jpeg\" alt=\"My <a href=&quot;/2022/03/19/ld48-tin-mining/&quot;>Tin Mining</a> game running on Steam Deck. This took almost no time to set up, thanks to Godot.\n\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"211\"></picture></a><figcaption>My <a href=\"/2022/03/19/ld48-tin-mining/\">Tin Mining</a> game running on Steam Deck. This took almost no time to set up, thanks to Godot.\n</figcaption></figure>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I’m a software engineer. It’s easy to fall into the trap of writing code for the\nfun of it, rather than to actually make something. But this is a distraction, I\nlike making things and want to actually finish a game.</p>\n<p>I’ve been learning Godot and enjoy using it. One day, hopefully soon, I’d like\nto finish a game and publish it to Steam. I’m not expecting to make the next\nbestseller, just something that I’m proud of enough to publish even if niche.</p>\n<h3 id=\"further-reading\" tabindex=\"-1\">Further reading <a class=\"header-anchor\" href=\"#further-reading\">#</a></h3>\n<ul>\n<li><a href=\"https://pikuma.com/blog/why-make-a-game-engine\">Should I Write a Game Engine or use an Existing One?</a> -\nthis article contains a lot of pros and cons of making your own engine.</li>\n<li><a href=\"https://geometrian.com/programming/tutorials/write-games-not-engines/\">Write Games, Not Engines</a> -\nthe author defines ‘engine’ as strictly reusable code, which is a more narrow\ndefinition (but potentially more accurate) than what I use. This is a good\narticle to read if you do want to make engines, though, as it describes the\nbest approach - make your engine with a game in mind.</li>\n<li><a href=\"https://duerrenberger.dev/blog/2023/04/11/should-i-develop-a-game-or-an-engine/\">Should I Develop a Game or an Engine?</a></li>\n<li><a href=\"https://www.polygon.com/2020/1/13/21064100/vvvvvv-source-code-game-development-terry-cavanagh-release\">The truth is that many games are held together by duct tape - Polygon</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2023/07/14/make-games-not-game-engines/","summary":"This article is an exploration of my journey in game dev, a discussion of what motivates me, and a promise for the future.","date_published":"2023-07-14T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/O3rYFRbz-F-540.png","tags":["gamedev","projects","rvwp","cpp","sfml","opinion"]},{"id":"/2023/05/31/conquer/","language":"en","title":"Conquer: a deep-dive into my Luanti mod jam entry","content_html":"<p>In 2020, the Minetest Discord community ran a mod-making competition with\n“combat” as the theme. Participants had one week to create a mod with all the\ncode written within the time, but pre-existing art was allowed. I made a\nReal-Time Strategy (RTS) minigame called\n<a href=\"https://content.minetest.net/packages/rubenwardy/conquer/\">Conquer</a>;\nit received first place.</p>\n<p>In this article, I will discuss some of the interesting challenges that Conquer\nneeded to solve. I believe that Conquer is a great example to learn from as it\ndemonstrates best practices for Minetest modding; it is well-structured and\nunit-tested.</p>\n<h2 id=\"what-is-conquer\" tabindex=\"-1\">What is Conquer? <a class=\"header-anchor\" href=\"#what-is-conquer\">#</a></h2>\n<p><a href=\"https://content.minetest.net/packages/rubenwardy/conquer/\">Conquer</a> is a mod\nthat adds RTS gameplay. It allows players to start Conquer mini-games, where\nthey can place buildings, train units, and fight other players.</p>\n<p>Whilst I created Conquer as a mod, it was designed with plans to eventually\nconvert it into a game, with custom map generation and more in-depth gameplay.</p>\n<p>Players select units by left-clicking them. To issue commands, the player uses\nthe scroll wheel to select a command and then right-click to perform it.\nCommands include move, melee attack, and ranged attack. Archers automatically\naim at nearby enemies, and all units will defend themselves from melee attacks.</p>\n<p>Units can damage buildings using melee or ranged attacks. When a player’s keep\nis destroyed, that player is out of the game. The appearance of a building\nreflects how much damage it has taken.</p>\n<figure>\n\t<iframe class=\"youtube-video\"\n\t\tsrc=\"https://www.youtube.com/embed/jB_sFZGRUgw\"\n\t\ttitle=\"YouTube video player\"\n\t\tallow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n\t\tallowfullscreen>\n\t</iframe><figcaption>Conquer gameplay trailer</figcaption></figure>\n<h2 id=\"my-process\" tabindex=\"-1\">My Process <a class=\"header-anchor\" href=\"#my-process\">#</a></h2>\n<p>I used GitLab projects to organise my tasks. I decided what the Minimal Viable\nProduct (MVP) was and created issues for the core features. During development,\nI created new issues to break down high-level goals into smaller steps.</p>\n<p>I only had seven days to make quite a complicated mod. I needed to cut a lot of\nfeatures that would have been desirable, such as squad movement, walls, and\nsiege engines. I pushed features that weren’t needed in the MVP to the bottom of\nthe backlog and resisted implementing them.</p>\n<figure class=\"undefined\"><a href=\"/static/img/yFodKQw_Gw-1191.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/n_9UJDJMpZ-540.webp 540w\"><img src=\"/static/img/n_9UJDJMpZ-540.png\" alt=\"Three columns with 4 issues in each column\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"235\"></picture></a><figcaption>Current GitLab project board for conquer. Tasks to work on next are on the right</figcaption></figure>\n<h2 id=\"unit-ai-behaviour\" tabindex=\"-1\">Unit AI Behaviour <a class=\"header-anchor\" href=\"#unit-ai-behaviour\">#</a></h2>\n<p>Units need to know how to do some tasks without being micromanaged by the\nplayer. They need to understand how to navigate the world, melee attack other\nunits, and arrows at moving targets. To tell the units how to do these things, I\nchose to use Behaviour Trees.</p>\n<h3 id=\"behaviour-trees\" tabindex=\"-1\">Behaviour Trees <a class=\"header-anchor\" href=\"#behaviour-trees\">#</a></h3>\n<p>Explaining Behavior Trees in full is a bit out of scope for this article; I\nhighly recommend reading\n<a href=\"https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work\">“Behavior trees for AI: How they work”</a>\nby Chris Simpson. However, I will try to explain the basics.</p>\n<p>Behaviour trees allow you to control an NPC’s decision-making by combining\nreusable nodes rather than writing code for specific states. They work best when\nthe nodes are small and specific, for example, you might have nodes to check\nconditions or walk to a position.</p>\n<p>Behaviour trees are basically a programming language in themselves, but for game\nAI. Execution starts at the top of the tree, and then works downwards based on\nthe rules of different nodes. A node is either running, succeeded, or failed.</p>\n<figure class=\"undefined\"><a href=\"/static/img/h2NpJ4FboX-450.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/_POkdAl5wF-450.webp 450w\"><img src=\"/static/img/_POkdAl5wF-450.png\" alt=\"A behaviour tree with 3 nodes. The root is a sequence node, and the two children are &quot;Go to target&quot; and &quot;Attack target&quot;\" loading=\"lazy\" decoding=\"async\" width=\"450\" height=\"237\"></picture></a><figcaption>The behaviour tree for melee attacks. The unit first moves to be next to the target, and then performs the attack</figcaption></figure>\n<p>Using behaviour trees for Conquer unit AI was probably overkill. Behaviour trees\nare great for structuring complex behaviour, but Conquer’s unit AI is fairly\nsimple. I decided to use behaviour trees as I wanted to make a Lua\nimplementation to use in other projects anyway - I ended up using it\n<a href=\"/2022/07/17/game-ai-for-colonists/\">in another project to create worker NPCs that can build</a>.</p>\n<p>The nodes implemented in Conquer include:</p>\n<ul>\n<li>Selector: run one child after another until one succeeds</li>\n<li>Sequence: run all children in order, stop if one fails</li>\n<li>FollowPath: cause the unit to follow a path</li>\n<li>MoveToNearTarget: cause the unit to move to be near a target</li>\n<li>MeleeAttack and RangedAttack.</li>\n</ul>\n<h3 id=\"aiming-arrows\" tabindex=\"-1\">Aiming arrows <a class=\"header-anchor\" href=\"#aiming-arrows\">#</a></h3>\n<figure class=\"undefined\"><a href=\"/static/img/pyZl7dtMq3-960.webp\"><img src=\"/static/img/LJabCsl4rV-960.webp\" alt=\"Archers shooting a volley of arrows\" loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"540\"></a><figcaption>Archers shooting a volley of arrows</figcaption></figure>\n<p>Archers need to know what direction to fire an arrow to hit a moving target. I\noriginally tried to derive the maths for this myself, but ultimately found\n<a href=\"https://en.wikipedia.org/wiki/Projectile_motion#Angle_%CE%B8_required_to_hit_coordinate_(x,_y)\">an equation on Wikipedia</a>\nthat worked. This was by far the thing I found most challenging whilst creating\nConquer, it took a while to get it to work as expected.</p>\n<p>Here’s the final code. It’s just the equation converted to Lua.</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">local</span> <span class=\"token keyword\">function</span> <span class=\"token function\">calculate_projectile_direction</span><span class=\"token punctuation\">(</span>from<span class=\"token punctuation\">,</span> to<span class=\"token punctuation\">,</span> initial_speed<span class=\"token punctuation\">,</span> gravity<span class=\"token punctuation\">)</span>\n    <span class=\"token comment\">-- Turn this into a 2D problem by considering just the plane</span>\n    <span class=\"token keyword\">local</span> delta <span class=\"token operator\">=</span> to <span class=\"token operator\">-</span> from\n    <span class=\"token keyword\">local</span> x <span class=\"token operator\">=</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">sqrt</span><span class=\"token punctuation\">(</span>delta<span class=\"token punctuation\">.</span>x<span class=\"token operator\">*</span>delta<span class=\"token punctuation\">.</span>x <span class=\"token operator\">+</span> delta<span class=\"token punctuation\">.</span>z<span class=\"token operator\">*</span>delta<span class=\"token punctuation\">.</span>z<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">local</span> y <span class=\"token operator\">=</span> delta<span class=\"token punctuation\">.</span>y\n\n    <span class=\"token keyword\">local</span> x2 <span class=\"token operator\">=</span> x<span class=\"token operator\">*</span>x\n    <span class=\"token keyword\">local</span> v2 <span class=\"token operator\">=</span> initial_speed<span class=\"token operator\">*</span>initial_speed\n\n    <span class=\"token comment\">-- If there's an imaginary number, no solution is possible</span>\n    <span class=\"token keyword\">local</span> square <span class=\"token operator\">=</span> v2<span class=\"token operator\">*</span>v2 <span class=\"token operator\">-</span> gravity<span class=\"token operator\">*</span><span class=\"token punctuation\">(</span>gravity<span class=\"token operator\">*</span>x2 <span class=\"token operator\">+</span> <span class=\"token number\">2</span><span class=\"token operator\">*</span>y<span class=\"token operator\">*</span>v2<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> square <span class=\"token operator\">&lt;=</span> <span class=\"token number\">0</span> <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">nil</span>\n    <span class=\"token keyword\">end</span>\n\n    <span class=\"token keyword\">local</span> t_x <span class=\"token operator\">=</span> gravity <span class=\"token operator\">*</span> x\n    <span class=\"token keyword\">local</span> t_y <span class=\"token operator\">=</span> v2 <span class=\"token operator\">-</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">sqrt</span><span class=\"token punctuation\">(</span>square<span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">local</span> factor <span class=\"token operator\">=</span> t_x <span class=\"token operator\">/</span> x\n    <span class=\"token keyword\">local</span> u_y <span class=\"token operator\">=</span> t_y\n    <span class=\"token keyword\">return</span> vector<span class=\"token punctuation\">.</span><span class=\"token function\">normalize</span><span class=\"token punctuation\">(</span>vector<span class=\"token punctuation\">.</span><span class=\"token function\">new</span><span class=\"token punctuation\">(</span>delta<span class=\"token punctuation\">.</span>x <span class=\"token operator\">*</span> factor<span class=\"token punctuation\">,</span> u_y<span class=\"token punctuation\">,</span> delta<span class=\"token punctuation\">.</span>z <span class=\"token operator\">*</span> factor<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span></code></pre>\n<p>The above function returns the direction to fire an arrow to hit a stationary\ntarget. To hit a moving target, I predict the target’s position at a set time\nin the future:</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">local</span> guessed_time <span class=\"token operator\">=</span> vector<span class=\"token punctuation\">.</span><span class=\"token function\">distance</span><span class=\"token punctuation\">(</span>from<span class=\"token punctuation\">,</span> target<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.11</span>\n<span class=\"token keyword\">local</span> offset_target <span class=\"token operator\">=</span> target <span class=\"token operator\">+</span> target_velocity <span class=\"token operator\">*</span> guessed_time</code></pre>\n<p>This is a simple approach that works a lot of the time but is flawed as the way\nit estimates the flight time using distance isn’t accurate. It’s good enough for\nmy purposes, however; archers occasionally missing could be considered a feature!</p>\n<h2 id=\"buildings-and-nodeobjectref\" tabindex=\"-1\">Buildings and NodeObjectRef <a class=\"header-anchor\" href=\"#buildings-and-nodeobjectref\">#</a></h2>\n<p>In Conquer, barracks produce new units, farms produce food, and the keep must be\ndefended at all costs. The appearance of each building changes based on its\nhealth. Buildings regenerate health when no enemy units are nearby.</p>\n<figure class=\"undefined\"><a href=\"/static/img/hJrVSykgiK-1218.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/C9s_OFtc2K-540.webp 540w\"><img src=\"/static/img/C9s_OFtc2K-540.png\" alt=\"Screenshot. A damaged keep with a tower missing, and a damage barracks.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"359\"></picture></a><figcaption>A damaged keep and barracks</figcaption></figure>\n<h3 id=\"node-objectref\" tabindex=\"-1\">Node ObjectRef <a class=\"header-anchor\" href=\"#node-objectref\">#</a></h3>\n<p>An object in Minetest is a moving thing with a position and velocity. For\nexample, both players and Conquer units are objects in the world. In contrast, a\nnode is a static thing in the voxel grid. Grass, keeps, and barracks are\nexamples of nodes.</p>\n<p>To interact with an object using the Lua API, you use an ObjectRef. ObjectRefs\nhave methods giving you access to the position, velocity, and hp. Nodes do not\nhave ObjectRefs as they are not objects.</p>\n<p>Conquer units need to be able to attack enemy units and nodes. Whilst I could\nhave implemented these abilities separately, I decided instead to create a Node\nObjectRef, a bit of Lua code that allows interacting with a building node\nas if it were an object.</p>\n<p>This is a nice abstraction that vastly simplifies the code. I intentionally only\nimplemented a subset of the ObjectRef interface: <code>get_pos</code>, <code>get_velocity</code>,\n<code>get_hp</code>, and <code>punch</code>.</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">local</span> node_object <span class=\"token operator\">=</span> conquer<span class=\"token punctuation\">.</span><span class=\"token function\">create_target</span><span class=\"token punctuation\">(</span>vector<span class=\"token punctuation\">.</span><span class=\"token function\">new</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">4</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\">-- Can now interact with the node like an object</span>\nnode_object<span class=\"token punctuation\">:</span><span class=\"token function\">get_pos</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\nnode_object<span class=\"token punctuation\">:</span><span class=\"token function\">get_velocity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\nnode_object<span class=\"token punctuation\">:</span><span class=\"token function\">punch</span><span class=\"token punctuation\">(</span>unit_entity<span class=\"token punctuation\">,</span> <span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span> caps<span class=\"token punctuation\">)</span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The mod jam received 16 entries. After the deadline, the organisers set up a\nserver to play all the mods - it was chaos and a lot of fun. I ran a server just\nfor Conquer as a flat area was needed to play on. I also didn’t get around to\nimplementing persistence before the jam deadline, so other mods crashing the\nshared server would have caused ongoing sessions to be lost.</p>\n<!-- Conquer was 4,200 lines of code written in a week. -->\n<p>I continued to develop Conquer after the jam, adding multi-select and other\nquality-of-life features. The next big thing to implement is squad movement,\nallowing a selection of units to move as one and avoid occupying the same\nposition.</p>\n<p>Thanks to <a href=\"https://www.greenxenith.com/\">GreenXenith</a> and others for organising\nthe mod jam. It was a pretty cool experience and had a good atmosphere to it. It\nwas a lot more successful than I expected.\nYou can <a href=\"https://content.minetest.net/packages/?tag=jam_combat_mod\">find other jam entries on ContentDB</a>.</p>\n<p>After the success of the mod jam, GreenXenith and others arranged game jams in\n2021 and 2022. These were open to the wider Minetest community and proved even\nmore successful. I was a judge on the game jams and didn’t enter.</p>\n<!--\n\nOrder of work:\n\n* Sessions\n* Keep and UnitEntity\n* Unit selection and movement\n* Pathfinding\n* Unit types: knight vs archer\n* HUD and actions\n* Behaviour trees\n* Melee\n* Keep GUI\n* Training units\n* Build mode HUD\n* Arrows and ranged attack\n* Units defend themselves\n* Archer auto attack\n* Damaging buildings\n* Defeating players\n\nPost jam\n\n* Multiselection\n* Farm building\n* Help GUI\n* Buildings cost resources\n* Persistance\n* Healthbars\n\n\n-->\n","url":"https://blog.rubenwardy.com/2023/05/31/conquer/","summary":"Conquer is an RTS minigame created for the 2020 mod jam. It received first place. This article will discuss some interesting challenges that it needed to solve.","date_published":"2023-05-31T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/iYbsZN4AqX-540.png","tags":["projects","luanti","jams"]},{"id":"/2023/05/04/android/","language":"en","title":"Thoughts on native Android development","content_html":"<p>I love making apps. I like making stuff and it’s even better when I can interact\nwith the stuff I make. App development feels especially real as I can hold and\ntouch what I made. So why don’t I have many Android hobby projects? This article\nis a reflection on the projects I’ve worked on, the problems I have, and what I\nwant to try in the future.</p>\n<h2 id=\"my-background-in-android-dev\" tabindex=\"-1\">My background in Android dev <a class=\"header-anchor\" href=\"#my-background-in-android-dev\">#</a></h2>\n<h3 id=\"minetest-mods\" tabindex=\"-1\">Minetest Mods <a class=\"header-anchor\" href=\"#minetest-mods\">#</a></h3>\n<figure class=\"float-right mt-0 mr-0 ml-6 mb-2\"><a href=\"/static/img/vM0G7svjPn-164.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/PiOqU28Q7s-164.webp 164w\"><img src=\"/static/img/PiOqU28Q7s-164.png\" alt=\"App screenshot, a list of mod titles and descriptions\" loading=\"lazy\" decoding=\"async\" width=\"164\" height=\"164\"></picture></a><figcaption>Minetest Mods App</figcaption></figure>\n<p>I learned native Android development in 2016 to create an\n<a href=\"/2022/10/22/Minetest-mods-app/\">app to install mods for Minetest</a>. I published\nMinetest Mods to the Play Store and maintained it there for several years,\nunpublishing it only after I added built-in support for downloading mods inside\n<a href=\"https://www.minetest.net\">Minetest</a>.</p>\n<p>I took great pride in ensuring the app followed\n<a href=\"https://m2.material.io/design/introduction\">Material Design</a> and had a good\nuser experience, and was rewarded with an average rating of 4.3 stars. The\nrating was reduced by the fact that it only supported mods, and not games or\ntexture packs. There were also a lot of issues with flawed data,\n<a href=\"/2022/03/24/contentdb/\">I created a new backend called ContentDB</a> to solve this\nissue, ultimately leading to the obsolescence of the app.</p>\n<div class=\"clearfix\"></div>\n<h3 id=\"first-job\" tabindex=\"-1\">First job <a class=\"header-anchor\" href=\"#first-job\">#</a></h3>\n<figure class=\"float-right mt-0 mr-0 ml-6 mb-2\"><a href=\"/static/img/8ZsWHt9vGa-164.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/xxQW6bbZo6-164.webp 164w\"><img src=\"/static/img/xxQW6bbZo6-164.png\" alt=\"App screenshot, a cow banner image and a list of news articles\" loading=\"lazy\" decoding=\"async\" width=\"164\" height=\"164\"></picture></a><figcaption>BCVA App</figcaption></figure>\n<p>My first job was doing Android, iOS, and backend development at a small software\nhouse alongside my studies. It was awesome to get paid for doing the thing I\nloved! Compare that to my job before, where I worked in hospitality for not much\nmoney.</p>\n<p>I worked on six different client projects, including two iOS apps and five\nAndroid apps. Most of the team had a background in web design, so I was able to\nplay a leading role in the technical direction of the projects including pushing\nfor the adoption of best practices like continuous integration and improved\nauthentication (OAuth2).</p>\n<div class=\"clearfix\"></div>\n<h3 id=\"hobby-apps\" tabindex=\"-1\">Hobby apps <a class=\"header-anchor\" href=\"#hobby-apps\">#</a></h3>\n<figure class=\"float-right mt-0 mr-0 ml-6 mb-2\"><a href=\"/static/img/8Ad-811h2d-164.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Q1qxcyKhPv-164.webp 164w\"><img src=\"/static/img/Q1qxcyKhPv-164.png\" alt=\"App screenshot, a pie chart and a graph showing spending\" loading=\"lazy\" decoding=\"async\" width=\"164\" height=\"164\"></picture></a><figcaption>Analytics for Monzo</figcaption></figure>\n<p>I did other hobby Android apps but ultimately didn’t complete them due to\nissues.</p>\n<p>Analytics for Monzo was an app to show pretty charts for Monzo bank users, but I\nwas unable to finish it due to a restricted API. With a banking API? Who would\nhave thought?!</p>\n<p><a href=\"/2020/09/13/return-to-android-dev/\">ForumMate</a> was a forum aggregator for\nold-style bulletin board forums, but problems with getting it to work with all\nforums and concerns about legality led me to abandon the project. I started the\nproject as an excuse to get back up to speed with the latest Android SDK\nconstructs, so it did serve its purpose.</p>\n<div class=\"clearfix\"></div>\n<h3 id=\"current-job\" tabindex=\"-1\">Current job <a class=\"header-anchor\" href=\"#current-job\">#</a></h3>\n<p>My current project at work is an Android app used by millions of people. It’s\nwritten mostly in Java with a mixture of modern and legacy APIs. We are slowly\nrewriting the app into Kotlin.</p>\n<p>One of the things that stands out about this project is that it’s the first time\nI’ve worked on something where I know people that use it. At previous jobs, I\nmostly worked on niche apps and internal tools for aerospace/transport.</p>\n<p>The large user base also allows small changes to make a huge difference to\nusers. I noticed a UX pain point in the app - an icon being used incorrectly. I\nproposed and implemented the fix for this, which increased the discoverability\nof an essential feature from a success rate of 26% to 92%. That’s a huge\ndifference from such a small change.</p>\n<h2 id=\"so-what-s-the-problem-then\" tabindex=\"-1\">So, what’s the problem then? <a class=\"header-anchor\" href=\"#so-what-s-the-problem-then\">#</a></h2>\n<p>I’m very project-driven; I’ve rarely learned technology just for the sake of it,\nit’s always because I have an idea I want to make. I learned React to create\n<a href=\"https://renewedtab.com\">Renewed Tab</a> because I couldn’t find a New Tab\nextension that worked how I wanted. The fact is that there are not many things\nthat need an Android app rather than a web app. Web apps can be used on a much\nwider range of platforms, including phones. Web apps are also much easier to\nmake.</p>\n<p>This cross-platform issue is a big one - I rarely write applications for just\nLinux or Windows, I always prefer to use cross-platform toolkits. Even if I only\npublish to Linux, the platform I use, I still choose toolkits that are\ncross-platform. So why do I treat mobile app development differently?</p>\n<p>Another issue is that the Android SDK is especially painful to deal with. It’s\ncomplicated and there are so many ways to do things; it’s not always clear which\nshould be used. Java as a language leads to boilerplate code, the introduction\nof Kotlin made a big difference in cutting this down. I do enjoy native Android\ndevelopment, the costs are just high compared to other options.</p>\n<h2 id=\"to-the-future\" tabindex=\"-1\">To the future <a class=\"header-anchor\" href=\"#to-the-future\">#</a></h2>\n<h3 id=\"cross-platform-toolkits\" tabindex=\"-1\">Cross-platform toolkits <a class=\"header-anchor\" href=\"#cross-platform-toolkits\">#</a></h3>\n<p>Cross-platform toolkits allow you to develop for Android and iOS from the same\ncodebase. Some even allow targetting desktop as well. This is appealing as it\nlowers the development effort when creating apps and reduces the time to market.</p>\n<p>I have historically been quite sceptical of cross-platform app development\ntoolkits as I got the impression that they gave subpar results.</p>\n<p>First, I saw these toolkits as being quite janky and hard to make a good user\nexperience in. This may just be because my exposure has been with thin wrappers\nover webviews, on badly coded apps. Modern toolkits like React Native and\nFlutter make it much easier to create good user experiences.</p>\n<p>Second, I didn’t think these toolkits would be able to match the appearance and\nfunctionality of native apps. This is something I’d like to look into, but I\nwill note that it is very common for Android apps to break material guidelines\nand instead look like iOS apps. Companies like to avoid doing the same work\nmultiple times. Just because an app is native doesn’t mean that it provides a\nperfect native experience, it may be easier to provide a good experience using\ncross-platform toolkits.</p>\n<figure class=\"figure \">\n\t<blockquote class=\"mb-1\" cite=\"https://flutter.dev/multi-platform/mobile\"><p>Flutter empowers you to create tailored experiences for iOS and Android users\nwithout compromise and with fewer resources.</p>\n</blockquote>\n\t<figcaption class=\"pl-4 mt-3\">\n\t\t—<a href=\"https://flutter.dev/multi-platform/mobile\">\n\t\t\t\tflutter.dev\n\t\t\t</a></figcaption>\n</figure>\n<h3 id=\"ideas-that-work-well\" tabindex=\"-1\">Ideas that work well <a class=\"header-anchor\" href=\"#ideas-that-work-well\">#</a></h3>\n<p>I need to look out for use cases and ideas I have that would suit an app. I\ndon’t want to start making things just for the sake of it. Apps are best suited\nfor ideas that require deeper integration with the device or a user experience\nthat web apps are unable to provide.</p>\n<p>In 2019, I started the <a href=\"/2020/09/13/return-to-android-dev/\">ForumMate</a> project\nas an excuse to get back up to speed with the latest Android SDK constructs -\nincluding MVVM, Room, repositories, Dagger, data binding, and Jetpack\nnavigation. I choose this idea as it had enough complexity to be an interesting\nproject.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Hopefully this article wasn’t too negative, it’s not meant to be. I do enjoy\nnative Android dev, but I am wary of the costs especially when working on\nprojects by myself. Native development may still have its place on bigger\nprojects.</p>\n<p>The next time I have an idea for an app, I’ll use it as an opportunity to learn\na cross-platform toolkit like React Native or Flutter. If you have any thoughts\non React Native, Flutter, or other cross-platform toolkits, it would be great to\nsee replies.</p>\n","url":"https://blog.rubenwardy.com/2023/05/04/android/","summary":"This article is a reflection on the projects I've worked on, the problems I have, and what I want to try in the future.","date_published":"2023-05-04T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/KyTAkYvG2k-540.png","tags":["android","opinion"]},{"id":"/2023/04/26/bing-ban/","language":"en","title":"Bing (and DuckDuckGo) shadow banned my website","content_html":"<p class=\"alert is-secondary\">\n    Bing has now unbanned my blog. My site is still penalised/low in the\n    results, but at least it's appearing now. See the bottom of this article\n    for their response.\n</p>\n<p>My domain was shadow banned by Bing and DuckDuckGo last year, and I don’t know\nwhy. Since then, my root domain (rubenwardy.com) has been unbanned but my blog\nremains banned. The event also negatively impacted the search placement of my\nroot domain; another site that reuploaded some of my content is appearing as the\nfirst result when searching for it.</p>\n<h2 id=\"events\" tabindex=\"-1\">Events <a class=\"header-anchor\" href=\"#events\">#</a></h2>\n<h3 id=\"june-2022-discovered-my-domain-was-banned\" tabindex=\"-1\">June 2022: Discovered my domain was banned <a class=\"header-anchor\" href=\"#june-2022-discovered-my-domain-was-banned\">#</a></h3>\n<p>Last year, I received a message from a user saying that my website wasn’t\nappearing on DuckDuckGo. Looking into it, I discovered that DuckDuckGo uses Bing\nand my website wasn’t appearing there either.</p>\n<figure class=\"undefined\"><a href=\"/static/img/8XQapg-2g2-821.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/OtTYdHO5ff-540.webp 540w\"><img src=\"/static/img/OtTYdHO5ff-540.png\" alt=\"There are no results for site:blog.rubenwardy.com\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"147\"></picture></a><figcaption>There are no results for site:blog.rubenwardy.com</figcaption></figure>\n<p>The first thing I did was sign all my domains up to Bing Webmaster. The\ndashboards were empty, and using the URL inspection tool resulted in the\nfollowing generic error:</p>\n<figure class=\"undefined\"><a href=\"/static/img/77Nib4ZmP7-997.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Xvl4hknYGk-540.webp 540w\"><img src=\"/static/img/Xvl4hknYGk-540.png\" alt=\"Screenshot of Bing URL inspection for my blog. The page says: &quot;Discovered but not crawled. URL cannot appear on Bing. The inspected URL is known to Bing but has some issues which are preventing indexation. We recommend you to follow Bing Webmaster Guidelines to increase your chances of indexation.&quot;\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"371\"></picture></a><figcaption>Unhelpful webmaster URL inspection</figcaption></figure>\n<p>I’ve read through the Bing Webmaster Guidelines and I’m certain that my website\nfollows them.</p>\n<p>rubenwardy.com hosts <a href=\"https://rubenwardy.com/\">my portfolio</a>, blog, and several\nopen-source projects - including\n<a href=\"https://rubenwardy.com/minetest_modding_book/en/index.html\">the book</a> I wrote\nabout creating mods for Minetest. At this point last year, my best guess for the\ncause of the ban was that Bing incorrectly thought that I was hosting stolen\ncontent, as another domain had reuploaded the book. Unfortunately, the book is\nlicensed under CC BY-SA 4.0, which means I am unable to use copyright to take\nit down.</p>\n<h3 id=\"june-july-contacted-bing-support\" tabindex=\"-1\">June-July: Contacted Bing support <a class=\"header-anchor\" href=\"#june-july-contacted-bing-support\">#</a></h3>\n<p>At this point, I sent an email to Bing support explaining the situation\n(UCM000004402991). After a week, I got the following response:</p>\n<blockquote>\n<p>Thank you for writing to Bing Webmaster Support. I have reviewed your site,\nand I have now reached out to our Product Review Group for further analysis.</p>\n<p>In the meantime, you can go through our Bing Webmaster guidelines to check if\nyour site is violating any of the rules cited in Bing Webmaster Guidelines,\nespecially the section Things to Avoid, and thoroughly check your site for any\ndeliberately or accidentally employed SEO techniques that may have adversely\naffected your standing in Bing and Bing-powered search results.</p>\n</blockquote>\n<p>Googling this, I’ve found the exact same text sent to someone else, so it’s a\ncopy-paste response.</p>\n<p>I once again went through the Bing Webmaster Guidelines, but could not find\nanything that I was doing wrong. After a month, I chased up the email and got\nthe following:</p>\n<blockquote>\n<p>Our engineering team is still looking into this issue. I will keep you posted\non this case. Please allow some time on this request</p>\n</blockquote>\n<h3 id=\"october-rubenwardy-com-unbanned-blog-rubenwardy-com-remains-banned\" tabindex=\"-1\">October: rubenwardy.com unbanned, blog.rubenwardy.com remains banned <a class=\"header-anchor\" href=\"#october-rubenwardy-com-unbanned-blog-rubenwardy-com-remains-banned\">#</a></h3>\n<p>Without any update from Bing support, rubenwardy.com started appearing in the\nresults albeit heavily penalised. My blog has since remained banned, with the\nsame error message.</p>\n<p>Given this, it seems like the problem may be with the blog rather than my root\ndomain and the reuploaded content issue. I’m not sure what my blog could be\ndoing wrong.</p>\n<p>I followed up with Bing support, and they replied saying they have been\nfollowing up with the engineering team and will keep me posted, but I never\nreceived an update.</p>\n<h2 id=\"ideas-for-why\" tabindex=\"-1\">Ideas for why <a class=\"header-anchor\" href=\"#ideas-for-why\">#</a></h2>\n<h3 id=\"reuploaded-content\" tabindex=\"-1\">Reuploaded content <a class=\"header-anchor\" href=\"#reuploaded-content\">#</a></h3>\n<p>My original idea was that it was due to another site reuploading my modding book\nand being considered the original version. But given that the root domain was\nunbanned and the blog domain wasn’t, this looks less likely.</p>\n<p>I have now added canonical tags to most of my pages. Unfortunately, the owner of\nthe other site is malicious so I cannot ask them to fix the problem. The book is\nlicensed under CC BY-SA 4.0, which means I am unable to use copyright to take it\ndown.</p>\n<h3 id=\"lots-of-inbound-links\" tabindex=\"-1\">Lots of inbound links <a class=\"header-anchor\" href=\"#lots-of-inbound-links\">#</a></h3>\n<p>I’m quite active online and tend to put my website in my profiles and forum\nsignatures. This has resulted in a lot of inbound links, especially from\nminetest.net, which may have resulted in Bing considering it link spam.</p>\n<p>I <a href=\"/2022/03/24/contentdb/\">developed and host ContentDB</a>, Minetest’s website to\nfind games, mods, and texture packs. I placed a link to rubenwardy.com in\nContentDB’s footer as a copyright attribution without realising the SEO\nimplications. As ContentDB has over 14,000 pages, this has resulted in a huge\nnumber of inbound links. I have now replaced that footer link with one to an\n<a href=\"https://content.minetest.net/about/\">about page</a> that links to my site,\nthis will result in a single quality link rather than thousands of poor-quality\nlinks. But these links were to rubenwardy.com, not blog.rubenwardy.com - this\ndoesn’t explain why my blog is still banned.</p>\n<figure class=\"undefined\"><a href=\"/static/img/-FD924P4v0-442.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/FYNyPQH8Wt-442.webp 442w\"><img src=\"/static/img/FYNyPQH8Wt-442.png\" alt=\"Google Search Console showing a large number of inbound links from minetest.net.\" loading=\"lazy\" decoding=\"async\" width=\"442\" height=\"159\"></picture></a><figcaption>Google Search Console showing a large number of inbound links from minetest.net.</figcaption></figure>\n<h3 id=\"html5-validation-issues\" tabindex=\"-1\">HTML5 Validation issues <a class=\"header-anchor\" href=\"#html5-validation-issues\">#</a></h3>\n<p>I ran an HTML5 validator on my blog and main website, and fixed a couple of\nsmall mistakes.</p>\n<h3 id=\"weird-urls\" tabindex=\"-1\">Weird URLs <a class=\"header-anchor\" href=\"#weird-urls\">#</a></h3>\n<p>Google Search Console alerted me about a large number of Not Found (404) URLs.\nThese URLs are bizarre and look like spam. Another site must be creating these\nlinks to me, perhaps they’re hoping my site is badly programmed and will return\na soft not found with the links present. After searching, I found some\n<a href=\"https://support.google.com/webmasters/thread/205198696/email-from-google-page-indexing-issue?hl=en\">threads which confirmed this</a>.\nAs I correctly return a 404, there’s nothing for me to do here.</p>\n<figure class=\"undefined\"><a href=\"/static/img/sGXEdnOYGk-933.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/T6yQsH2qe8-540.webp 540w\"><img src=\"/static/img/T6yQsH2qe8-540.png\" alt=\"Screenshot of Google Search Console showing a large number of links with Chinese text and a URL\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"272\"></picture></a><figcaption>Chinese link spam in Google Search Console</figcaption></figure>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>My blog (blog.rubenwardy.com) remains banned, and the event has negatively\nimpacted the search placement of my root domain; another site that reuploaded\nsome of my content is appearing as the first result when searching for it. This\nis especially annoying as it’s the most popular thing on my website, many users\ngo looking for it and instead find an outdated version on a dodgy domain.</p>\n<p>It appears that Bing banning small sites may be a common issue, I’ve found other\npeople reporting that their site was similarly effected:</p>\n<ul>\n<li><a href=\"https://daverupert.com/2023/01/shadow-banned-by-duckduckgo-and-bing/\">I’m Shadow Banned by DuckDuckGo (and Bing) - Dave Rupert</a></li>\n<li><a href=\"https://dariusz.wieckiewicz.org/en/bing-jail/\">Bing Jail - Dariusz Więckiewicz</a></li>\n</ul>\n<p>There are potentially over a billion websites on the Internet, Bing needs to\nindex and rank them whilst avoiding abuse. Some websites contain illegal or\nharmful content, others attempt to manipulate the search results. Given the\nscale of the problem, it is understandable for Bing to sometimes make mistakes.</p>\n<p>In the end, Dave Rupert discovered that Bing was\n<a href=\"https://daverupert.com/2023/02/solved-the-case-of-the-bing-ban-theory/\">incorrectly classifying his site as a spam blog</a>.\nA spam blog is a website created purely to link to and promote other websites,\noften with useless information. Dave was able to fix his problem are blogging\nabout it and raising awareness, so hopefully this post will be able to resolve\nmy problems. I’d appreciate suggestions and help resolving this issue.</p>\n<h2 id=\"update-my-blog-has-been-unbanned\" tabindex=\"-1\">Update: My blog has been unbanned <a class=\"header-anchor\" href=\"#update-my-blog-has-been-unbanned\">#</a></h2>\n<p>Bing has now unbanned my blog. My site is still penalised/low in the results,\nbut at least it’s appearing now</p>\n<blockquote>\n<p>Good day and thank you for your patience while we investigated the issue.</p>\n<p>Firstly, I am happy to provide you information that our Product Review Group\nsucceeded in resolving the issue which prevented your site from showing in our\nindex. After submitting your site to be reviewed, the team has decided to lift\nthe block. Allow up to two to three weeks for your site to be crawled,\nindexed, and serving again.</p>\n<p>I am unable to provide you the specifics of the block, as our Product Review\nteam does not share the details of the block. However, I wish to help you with\na few &gt; links and help docs, which will not only help in avoiding blocks, but\nalso help you to rank better in the Bing index.</p>\n<ul>\n<li><a href=\"https://www.bing.com/webmasters/help/why-is-my-site-not-in-the-index-2141dfab\">Why is My Site Not in the Index?</a></li>\n<li><a href=\"https://blogs.bing.com/webmaster/2014/05/09/10-seo-myths-reviewed\">10 SEO Myths Reviewed</a></li>\n<li><a href=\"https://blogs.bing.com/webmaster/2014/04/24/is-seo-the-future-no-and-heres-why\">Is SEO The Future? No, And Here’s Why</a></li>\n<li><a href=\"https://blogs.bing.com/webmaster/2014/10/17/building-authority-setting-expectations\">Building Authority &amp; Setting Expectations</a></li>\n<li><a href=\"https://blogs.bing.com/search-quality-insights/2014/12/08/the-role-of-content-quality-in-bing-ranking\">The Role of Content Quality in Bing Ranking</a></li>\n</ul>\n<p>When Bing crawls and indexes pages on the Internet, it makes decisions about\nwhich pages will most likely resonate with searchers, so we look at these\nsignals relative to all other sites on the web as part of our selection\ncriteria. Always ensure you follow webmaster guidelines, especially the\nsection <a href=\"https://www.bing.com/webmasters/help/webmasters-guidelines-30fba23a\">Things to Avoid</a>.</p>\n<p>Let me know if you have any follow-up questions or concerns regarding this\nissue and I would be more than happy to assist.</p>\n<p>Have a wonderful day ahead!</p>\n</blockquote>\n","url":"https://blog.rubenwardy.com/2023/04/26/bing-ban/","summary":"My domain was shadow banned by Bing and DuckDuckGo last year, and I don't know why. My root domain has since been unbanned, but my blog remains banned.","date_published":"2023-04-26T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/--jfaG77ez-540.png","tags":["webdev","luanti","opinion"]},{"id":"/2023/03/16/lockbox/","language":"en","title":"3D-printed IoT lock box using ESP32","content_html":"<p>My eventual goal with electronics is to create autonomous robots and drones; I’d\nlike to make a quadcopter with my own flight controller that can take off, land,\nand follow a target. This will be quite an ambitious project, and I’m nowhere\nnear capable enough for that yet.</p>\n<p>Previously, I created a simple <a href=\"/2022/03/17/plant-monitor/\">plant monitor</a> that\nreported stats to an online dashboard. This allowed me to learn soldering,\nstripboards, and 3d printing. To work on future projects, I need to\nbe able to produce ever more complicated circuits and mechanical designs.</p>\n<p>After watching some Lock Picking Lawyer on YouTube, I was inspired to think\nabout different locking mechanisms. A locking box would be a good experiment\nwith mechanical design, and if combined with IoT, would be good for electronics\ntoo.</p>\n<p>In this article, I will cover how I created my lock box - from the problems I\nhad, the iterations I made, and the final design I settled on. It’s a bit of a\nrandom project, I didn’t have set goals in mind. I started by exploring\ndifferent locking mechanisms, and then moved to focusing on the electronics and\ngetting it working.</p>\n<h2 id=\"initial-servo-designs\" tabindex=\"-1\">Initial servo designs <a class=\"header-anchor\" href=\"#initial-servo-designs\">#</a></h2>\n<h3 id=\"pin\" tabindex=\"-1\">Pin <a class=\"header-anchor\" href=\"#pin\">#</a></h3>\n<p>My initial idea was to use a servo to move a pin to unlock the box. Whilst I\ncould have used the servo head as part of the lock mechanism directly, I didn’t\nwant the servo to be a load-bearing part of the lock.</p>\n<p>My first design was to use a pin, a paperclip pushrod, and a two-part slot. I\nfound it quite hard to get the pin to move straight without falling out at the\nend. The slightest bit of misalignment in the two latches also caused a lot of\nfriction on the pin. Due to these problems, I decided to change to a design that\ndidn’t need as much horizontal movement.</p>\n<figure class=\"undefined\"><a href=\"/static/img/yaE0GVZnFj-640.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/jwcbPAuq_1-540.webp 540w\"><img src=\"/static/img/jwcbPAuq_1-540.jpeg\" alt=\"Servo-based lock design using a sliding pin.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Servo-based lock design using a sliding pin.</figcaption></figure>\n<h3 id=\"e-shaped-gripper\" tabindex=\"-1\">E-shaped gripper <a class=\"header-anchor\" href=\"#e-shaped-gripper\">#</a></h3>\n<p>My second design used a servo to move an E-shaped gripper. When in the locked\nposition, the gripper blocks the lid from being removed by colliding with tabs\non the side of the lid.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/npmYo2I-wK-640.webp\"><img src=\"/static/img/ywwmIAcSO1-640.webp\" alt=\"Servo-based lock design using E-shaped gripper.\" loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"360\"></a><figcaption>Servo-based lock design using E-shaped gripper.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/VlxKWXmdbw-224.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/F61v5Olkba-224.webp 224w\"><img src=\"/static/img/F61v5Olkba-224.jpeg\" alt=\"Tabs on the lid.\" loading=\"lazy\" decoding=\"async\" width=\"224\" height=\"360\"></picture></a><figcaption>Tabs on the lid.</figcaption></figure></div>\n\t\t\t</div>\n<p>The nice thing about this design is that it didn’t need much calibration, the\nservo just needed to be in the right place to move the gripper.</p>\n<p>However, the servo I used was pretty unreliable - it would frequently do\n360° rotations and break the lock. I also had problems with removing the\nlid, there was way too much flex in the lid and base, causing it to get stuck at\nweird angles.</p>\n<p>Another problem is that I designed the case before the electronics, and\nunderestimated the amount of space I’d need for them. Increasing the size of the\ncase to fix the electronics would exacerbate the problems further, I need to\ncome up with a design with fewer moving parts.</p>\n<h2 id=\"solenoid-design\" tabindex=\"-1\">Solenoid design <a class=\"header-anchor\" href=\"#solenoid-design\">#</a></h2>\n<p>I had bought a\n<a href=\"https://thepihut.com/products/small-lock-style-solenoid-6vdc-600mah-with-2-pin-jst\">cheap solenoid lock from PiHut</a>\na few months ago, so I decided to redesign the box using it. I also decided to\nget the full electronic design done first and then design around that, allowing\nthe case to correctly fit the electronics.</p>\n<h3 id=\"electronics\" tabindex=\"-1\">Electronics <a class=\"header-anchor\" href=\"#electronics\">#</a></h3>\n<p>The electronics were soldered onto two stripboards, with 22-gauge wire\nconnecting them.</p>\n<p>The main board has the ESP32 microcontroller, Real-Time Clock, MicroSD card\nreader, and solenoid driver. I included an SDCard reader to store configuration\ndata and for logging; this is something I wanted to learn how to do as it will\nbe much more useful in future projects. The solenoid driver consists of a power\nMOSFET and a flyback diode.</p>\n<p>I included a separate MicroUSB port to act as the power supply. Using the\nmicrocontroller’s MicroUSB port would limit the amount of current available to\nthe solenoid and would also allow the lock to be bypassed by reprogramming the\nmicrocontroller. The MicroUSB module is connected to the main board using\n22-gauge wire.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/wE64tFXAym-1650.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/M5gB6FdZWU-540.webp 540w\"><img src=\"/static/img/M5gB6FdZWU-540.png\" alt=\"Electronics schematic, created using KiCAD. The ESP32 has builtin pull-up resistors.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"443\"></picture></a><figcaption>Electronics schematic, created using KiCAD. The ESP32 has builtin pull-up resistors.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/b-H81bRaN8-360.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/WatW1D0CcQ-360.webp 360w\"><img src=\"/static/img/WatW1D0CcQ-360.jpeg\" alt=\"Inside the lockbox.\" loading=\"lazy\" decoding=\"async\" width=\"360\" height=\"480\"></picture></a><figcaption>Inside the lockbox.</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"screen\" tabindex=\"-1\">Screen <a class=\"header-anchor\" href=\"#screen\">#</a></h3>\n<p>The screen board contains an SSD1306 OLED screen and four push buttons.\n3D-printed button caps rest on the buttons and allow them to be pressed through\nthe case. The lid has holes for the screens and the four button caps.</p>\n<p>Creating the screen board was by far the hardest part of the project - space was\ntight and once the screen was on it blocked access to the rest of the screen\nboard. At one point, the cheap soldering iron I was using overheated - it\nstarted glowing red hot, indicating it was over 200°C hotter than it should be!\nI bought a new soldering station, a Hakko FX-888D, which made soldering so much\neasier.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/_Bdx-u19IT-640.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/w6S0xe4a4P-540.webp 540w\"><img src=\"/static/img/w6S0xe4a4P-540.jpeg\" alt=\"The screen board.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>The screen board.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/8B5Gq29YQu-640.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/U3BYXeJcWM-540.webp 540w\"><img src=\"/static/img/U3BYXeJcWM-540.jpeg\" alt=\"Underside of the lid with 4 button caps and the screen hole.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Underside of the lid with 4 button caps and the screen hole.</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"case-and-lock\" tabindex=\"-1\">Case and lock <a class=\"header-anchor\" href=\"#case-and-lock\">#</a></h3>\n<p>Rather than making the lid part of the lock mechanism, I added a sliding drawer\nto hold the contents. Using a smaller moving part prevents the issues with the\nplastic flexing. The drawer has a slot on the bottom for the solenoid lock, and\na lip to prevent picking.</p>\n<p>The lid slides onto the base using tabs, locking it into place vertically. The\ndrawer slides into rails on the lid and base, locking the whole thing together.\nThe drawer prevents the lid from sliding horizontally and provides extra\nstrength vertically. Using the drawer to lock the lid in place is a simple and\nelegant way to prevent access to the electronics whilst locked - it avoids\nneeding to use screws.</p>\n<p>The case has a hole for the power cable and a small platform on the inside to\nmount the MicroUSB module using screws.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/FNdQnmWSV_-860.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/N2DkdlDZCq-540.webp 540w\"><img src=\"/static/img/N2DkdlDZCq-540.png\" alt=\"After maintenance, the lid is lowered and pushed horizontally to lock into place.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>After maintenance, the lid is lowered and pushed horizontally to lock into place.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/qaHmR2PHcD-860.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/0KYps6H3_s-540.webp 540w\"><img src=\"/static/img/0KYps6H3_s-540.png\" alt=\"The drawer slides into the slot, preventing the lid from being removed horizontally.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>The drawer slides into the slot, preventing the lid from being removed horizontally.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/5qYAq21RiH-860.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/gs7SmzhDqz-540.webp 540w\"><img src=\"/static/img/gs7SmzhDqz-540.png\" alt=\"The drawer also has tabs that prevent the lid from being removed vertically.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>The drawer also has tabs that prevent the lid from being removed vertically.</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"software\" tabindex=\"-1\">Software <a class=\"header-anchor\" href=\"#software\">#</a></h2>\n<p>The software is pretty minimal. ESP32 uses the Arduino C++ SDK so I was able to\nbenefit from existing libraries for components such as the display (SSD1306),\nReal-Time Clock, and MicroSD card reader. One of my other projects is built\nusing the Pico C++ SDK and required me to write my own firmware for this\nhardware.</p>\n<p>When connected to a WiFi network, the microcontroller will synchronise the\ncurrent time using the Network Time Protocol. The time is stored in the\nReal-Time Clock module, allowing the time to be known whilst offline.</p>\n<p>The user can lock the device using a pin or set up a time-based lock. The device\ncould also use a REST API, but I didn’t get around to exploring this.</p>\n<p>One thing that surprised me about working with the MicroSD card reader is just\nhow slow it was. It took 0.65 seconds to open a file, write a few bytes, and\nthen close it. Appending to a file was super fast, however, likely because the\ncards are optimised for sequential access. This is probably just a software\nproblem. I used SD.h from the Arduino SDK, which uses sdfatlib, with a 32GB\nMicroSD card formatted to FAT32.</p>\n<h2 id=\"future-improvements\" tabindex=\"-1\">Future Improvements <a class=\"header-anchor\" href=\"#future-improvements\">#</a></h2>\n<h3 id=\"increase-storage-space\" tabindex=\"-1\">Increase storage space <a class=\"header-anchor\" href=\"#increase-storage-space\">#</a></h3>\n<p>The drawer is too small to be useful for much - it only really fits a key or\nnote, and not much else. This is just a prototype and a learning exercise, so it\ndoesn’t matter that much. A larger version could have quite a few use cases,\nsuch as rewarding good habits.</p>\n<p>The solenoid design is 15% larger than the servo design but has 70% less storage\nspace. Now, the old design didn’t really fit the electronics so it’s not a fair\ncomparison, but it highlights a problem with the size of the box vs the size of\nthe lockable storage space.</p>\n<figure class=\"undefined\"><a href=\"/static/img/I5jJo5PlPI-640.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/6yTrQnA7cX-540.webp 540w\"><img src=\"/static/img/6yTrQnA7cX-540.jpeg\" alt=\"The two designs side-by-side.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>The two designs side-by-side.</figcaption></figure>\n<p>One way to solve this would be to miniaturize the electronics. A Pi Pico W would\nhave a smaller footprint and also has a built-in RTC, so the external RTC could\nbe removed. Making a PCB could make the electronics smaller, especially if you\nembed the various breakouts onto it. Glueing the screen rather than using a\nholder plate to screw it in would also save space.</p>\n<p>Another option would be to make the whole thing bigger. The same amount of\nelectronics is required, no matter the size. So making it bigger will allow the\nstorage efficiency to be higher.</p>\n<!--\nold vol: 65533.46 mm³\nnew vol: 75292.361 mm³\n\nold loc: 86904    71*68*(25-(13.2-6.2))\nnew loc: 24064    38.9*38.9*16.2-10*10*4.5\n-->\n<h3 id=\"battery\" tabindex=\"-1\">Battery <a class=\"header-anchor\" href=\"#battery\">#</a></h3>\n<p>Another possible future improvement would be to add a small li-ion battery,\nallowing the box to become more portable.</p>\n<h3 id=\"limit-switch\" tabindex=\"-1\">Limit switch <a class=\"header-anchor\" href=\"#limit-switch\">#</a></h3>\n<p>A limit switch could be used to automatically detect when the drawer is in the\nbox. This would be a small user-experience improvement.</p>\n<h3 id=\"custom-locks\" tabindex=\"-1\">Custom locks <a class=\"header-anchor\" href=\"#custom-locks\">#</a></h3>\n<p>It would be good to get my custom mechanical designs based on servos working.\nWhilst there isn’t a huge amount of benefit to them, I am disappointed that I\nwasn’t able to get them working correctly.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Well, I definitely learned a lot in this project. I probably won’t continue to\nwork on it as I’ve already spent more time on it than I was planning and I don’t\nhave much use for it.</p>\n<p>I’ve been working on a new robotic car platform using a Pi Pico. The aim is for\nit to be a platform for me to experiment with various robotic techniques,\nincluding dead reckoning and\n<a href=\"https://en.wikipedia.org/wiki/Simultaneous_localization_and_mapping\">Simultaneous Localization and Mapping (SLAM)</a>.\nI’d also like to learn how to use lipo batteries and RF transceivers.</p>\n<p>Another project is likely to be an automatic watering station for my basil,\nwhich needs to be watered daily. This would be a nice continuation of my\n<a href=\"/2022/03/17/plant-monitor/\">plant monitor</a> project.</p>\n","url":"https://blog.rubenwardy.com/2023/03/16/lockbox/","summary":"In this article, I will cover how I created my lock box - from the problems I had, the iterations I made, and the final design I settled on.","date_published":"2023-03-16T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/1XWIOl7tCu-540.png","tags":["projects","electronics","3d_printing"]},{"id":"/2023/02/17/interesting-reads/","language":"en","title":"My favourite programming and game dev articles","content_html":"<p>Articles are a great way to expand your knowledge as a programmer, by learning\nfrom the experiences of others. A good article is also interesting to read.\nHere is a collection of articles I recommend reading.</p>\n<h2 id=\"game-dev\" tabindex=\"-1\">Game dev <a class=\"header-anchor\" href=\"#game-dev\">#</a></h2>\n<h3 id=\"the-simulation-dream\" tabindex=\"-1\">The Simulation Dream <a class=\"header-anchor\" href=\"#the-simulation-dream\">#</a></h3>\n<p>Tynan Sylvester’s\n<a href=\"https://tynansylvester.com/2013/06/the-simulation-dream/\">The Simulation Dream</a>\nexplains how to create a deep simulated world without super complex systems that\nwould confuse players. It makes the point that the player’s perception of the\ngame is more important than what the game is actually doing.</p>\n<p>Tynan is the creator of <a href=\"https://rimworldgame.com/\">RimWorld</a>, my favourite game\nof all time.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://tynansylvester.com/2013/06/the-simulation-dream/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/qvPDXNADPu-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"The Simulation Dream\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe Simulation Dream\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tTynan Sylvester</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"behaviour-trees\" tabindex=\"-1\">Behaviour Trees <a class=\"header-anchor\" href=\"#behaviour-trees\">#</a></h3>\n<p>Behaviour Trees are a nice way to construct game AIs. They allow you to control\nan NPC’s decision-making by combining reusable nodes rather than creating new\nstates in a state machine. They’re great for making extensible\n<a href=\"/2022/07/17/game-ai-for-colonists/\">worker NPCs</a>. Below are the two articles\nI used to learn how behaviour trees work.</p>\n<div class=\"row gap-3\">\n    <div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/FzwH3ULtVE-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"Behavior trees for AI: How they work\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tBehavior trees for AI: How they work\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tChris Simpson- GameDeveloper.com</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<p><div class=\"col-md col-6 embed\">\n<a href=\"https://takinginitiative.wordpress.com/2014/02/17/synchronized-behavior-trees/\" class=\"hover-enlarge d-block no-underline\">\n<div class=\"card\">\n<div class=\"row align-items-center\">\n<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/4rQnFoVDDw-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"Synchronized Behavior Trees\"></div>\n<div class=\"col\">\n<div class=\"card-body px-4 py-2\">\n<h5 class=\"card-title my-0\">\nSynchronized Behavior Trees\n</h5>\n<p class=\"card-text mb-0 mt-2 text-muted\">\nBobby Anguelov- TakingInitiative</p>\n</div>\n</div>\n</div>\n</div>\n</a></p>\n</div>\n</div>\n<h3 id=\"enemy-ai-in-mafia-iii\" tabindex=\"-1\">Enemy AI in Mafia III <a class=\"header-anchor\" href=\"#enemy-ai-in-mafia-iii\">#</a></h3>\n<p>Here is an interesting article about how enemy NPCs work in Mafia III, with\nstealth, cover, perception, tactics, and combat.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.gamedeveloper.com/design/open-world-enemy-ai-in-mafia-iii\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/WhOlEhUJpx-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"Open-world enemy AI in Mafia III\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tOpen-world enemy AI in Mafia III\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJiri Holba- GameDeveloper.com</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"physics-for-game-programmers\" tabindex=\"-1\">Physics for Game Programmers <a class=\"header-anchor\" href=\"#physics-for-game-programmers\">#</a></h3>\n<p>This is actually a talk rather than an article, but it was too good to leave out.\nThe creator of Box2D talks about how to implement a character controller with\nswept collision and an iterative solver. It’s dense but super interesting and\nthe best resource I’ve found about building your own physics engine.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://youtu.be/SHinxAhv1ZE\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/oJK0DQgegJ-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"Physics for Game Programmers: Understanding Constraints\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tPhysics for Game Programmers: Understanding Constraints\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tErin Catto- YouTube</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"minetest-miniforts\" tabindex=\"-1\">Minetest MiniForts <a class=\"header-anchor\" href=\"#minetest-miniforts\">#</a></h3>\n<p>This is one of the most technically impressive things I’ve seen made in Minetest.\nIt’s a base-building game where you command minions to build up the base and\ndefend it from attackers. Without spoiling too much, they use TypeScript,\ntheir own JSX GUI API, data binding, and a hierarchical pathfinder.</p>\n<p>I’m a big fan of RimWorld and have\n<a href=\"/2022/07/17/game-ai-for-colonists/\">incorporated mechanics from it in my own games</a>\nbefore, so this is pretty cool to see.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://leanrada.com/wares/miniforts/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/lxxJpd6pNl-361.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"MiniForts: A game about building and defending a fort\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tMiniForts: A game about building and defending a fort\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tLean Rada</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h2 id=\"programming\" tabindex=\"-1\">Programming <a class=\"header-anchor\" href=\"#programming\">#</a></h2>\n<h3 id=\"stack-overflow\" tabindex=\"-1\">Stack Overflow <a class=\"header-anchor\" href=\"#stack-overflow\">#</a></h3>\n<p>Stack Overflow has a reputation for being unfriendly to those asking questions,\nbut it turns out that this is by design - SO’s primary purpose is to be a wiki\nfor programming answers, rather than a place to ask questions. This interesting\narticle by one of Stack Overflow’s founders explains the reasons behind the\nsite’s design.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://blog.codinghorror.com/what-does-stack-overflow-want-to-be-when-it-grows-up/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/qpnua-gFnT-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"What does Stack Overflow want to be when it grows up?\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tWhat does Stack Overflow want to be when it grows up?\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJeff Atwood- Coding Horror</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"the-rewriting-curse\" tabindex=\"-1\">The Rewriting Curse <a class=\"header-anchor\" href=\"#the-rewriting-curse\">#</a></h3>\n<p>Written by the other founder of Stack Overflow, this article discusses the\ndangers of rewriting software from scratch.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/b3XBXAp83j-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"Things You Should Never Do, Part I\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThings You Should Never Do, Part I\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tJoel Spolsky- Joel on Software</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"yaml\" tabindex=\"-1\">YAML <a class=\"header-anchor\" href=\"#yaml\">#</a></h3>\n<p>YAML is a nice, simple format for configuration, right? Wrong. YAML has a\nshocking number of edge cases and foot guns.</p>\n<div class=\"col-md col-6 embed\">\n\t<a href=\"https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell\" class=\"hover-enlarge d-block no-underline\">\n\t\t<div class=\"card\">\n\t\t\t<div class=\"row align-items-center\">\n\t\t\t\t<div class=\"col col-basis-66 ratio-4x3 h-100\"><img class=\"my-0\" src=\"/static/img/tuGfWvbH_D-270.png\" loading=\"lazy\"\n\t\t\t\t\t\t\talt=\"The yaml document from hell\"></div>\n\t\t\t\t<div class=\"col\">\n\t\t\t\t\t<div class=\"card-body px-4 py-2\">\n\t\t\t\t\t\t<h5 class=\"card-title my-0\">\n\t\t\t\t\t\t\tThe yaml document from hell\n\t\t\t\t\t\t</h5>\n\t\t\t\t\t\t<p class=\"card-text mb-0 mt-2 text-muted\">\n\t\t\t\t\t\t\tRuud van Asseldonk</p>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</a>\n</div>\n<h3 id=\"honourable-mentions\" tabindex=\"-1\">Honourable Mentions <a class=\"header-anchor\" href=\"#honourable-mentions\">#</a></h3>\n<ul>\n<li><a href=\"https://findthatmeme.com/blog/2023/01/08/image-stacks-and-iphone-racks-building-an-internet-scale-meme-search-engine-Qzrz7V6T.html\">Image Stacks and iPhone Racks - Building an Internet Scale Meme Search Engine</a></li>\n<li><a href=\"https://nolanlawson.com/2019/11/05/what-ive-learned-about-accessibility-in-spas/\">What I’ve learned about accessibility in SPAs</a></li>\n<li><a href=\"https://ericwbailey.website/published/aria-label-is-a-code-smell/\">aria-label is a code smell</a></li>\n</ul>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>I hope you found these interesting. Feel free to reply with any articles or\ntalks you liked or think I might like.</p>\n<p><a href=\"https://www.pexels.com/photo/clear-glass-teacup-with-coffee-beverage-2299028/\">Cover Image</a>\n© Engin Akyurt</p>\n","url":"https://blog.rubenwardy.com/2023/02/17/interesting-reads/","summary":"Articles are a great way to expand your knowledge by learning from the experiences of others. Here is a collection of articles I recommend reading.","date_published":"2023-02-17T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/fBiLy502MT-540.png","tags":["gamedev","blogging"]},{"id":"/2023/01/24/using_sdl_gamecontroller/","language":"en","title":"SDL_GameController: Making gamepads just work","content_html":"<p>When implementing controller support in a game, it’s desirable for gamepads to\njust work without a lot of user configuration. Platform APIs are pretty useless\nfor this, the solution is an API like SDL_GameController that allows you to\ntarget a large number of gamepads without much effort.</p>\n<p>Each operating system has its own API for gamepad input. Windows has XInput, and\nLinux has the joystick and evdev APIs. When a gamepad button is pressed,\napplications will receive a button id. This is a number, there’s no OS way to\nknow which button id corresponds with which button. The ids for a button are not\nthe same on different gamepads and platforms, making it super hard to support\nmore than a couple of devices.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">SDL_JoystickGetButton</span><span class=\"token punctuation\">(</span>joystick<span class=\"token punctuation\">,</span> <span class=\"token number\">8</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    std<span class=\"token double-colon punctuation\">::</span>cerr <span class=\"token operator\">&lt;&lt;</span> <span class=\"token string\">\"no idea what button 8 is\"</span> <span class=\"token operator\">&lt;&lt;</span> std<span class=\"token double-colon punctuation\">::</span>endl<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>One thing platforms do give you is the name, model, and manufacturer\nof the game controller. If you test with a large number of gamepads, you can\ncreate a database from gamepad name to layout. Luckily, SDL_GameController\nhas already done this for you. Instead of a random number, you can use a named\nbutton that will work no matter the gamepad and platform:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">SDL_GameControllerGetButton</span><span class=\"token punctuation\">(</span>controller<span class=\"token punctuation\">,</span> SDL_GameControllerButton<span class=\"token double-colon punctuation\">::</span>SDL_CONTROLLER_BUTTON_X<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    std<span class=\"token double-colon punctuation\">::</span>cerr <span class=\"token operator\">&lt;&lt;</span> <span class=\"token string\">\"X was pressed!\"</span> <span class=\"token operator\">&lt;&lt;</span> std<span class=\"token double-colon punctuation\">::</span>endl<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"what-is-sdl-gamecontroller\" tabindex=\"-1\">What is SDL_GameController? <a class=\"header-anchor\" href=\"#what-is-sdl-gamecontroller\">#</a></h2>\n<p>SDL_GameController is an abstraction that allows you to program input based on\nan Xbox-like controller layout, and have it work with a huge variety of devices.\nIt’s a layer built on top of the raw SDL_Joystick API.</p>\n<p>Xbox-like controllers have a DPAD, two analog sticks, 4 buttons on the right\n(often called A/B/X/Y), shoulder buttons, and 3 buttons in the middle\n(start/back/logo). Examples include PlayStation DualShock, Nintendo Switch, and\nSteam Deck (Neptune).</p>\n<h3 id=\"mapping\" tabindex=\"-1\">Mapping <a class=\"header-anchor\" href=\"#mapping\">#</a></h3>\n<p>SDL2 comes with a database of game controllers, mapping from controller id to\nlayout information. Users can also provide custom mappings, which is supported\nwithout you needing to do anything.</p>\n<p>Steam also comes with built-in support for SDL_GameController, allowing users to\nremap their controllers for your game in Steam. If Steam supports the\ncontroller, your game will too.</p>\n<h2 id=\"using-sdl-gamecontroller\" tabindex=\"-1\">Using SDL_GameController <a class=\"header-anchor\" href=\"#using-sdl-gamecontroller\">#</a></h2>\n<h3 id=\"setup\" tabindex=\"-1\">Setup <a class=\"header-anchor\" href=\"#setup\">#</a></h3>\n<p>When initializing your SDL2 device, add the <code>SDL_INIT_GAMECONTROLLER</code> flag:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">SDL_Init</span><span class=\"token punctuation\">(</span>SDL_INIT_VIDEO <span class=\"token operator\">|</span> SDL_INIT_GAMECONTROLLER<span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    std<span class=\"token double-colon punctuation\">::</span>cerr <span class=\"token operator\">&lt;&lt;</span> <span class=\"token string\">\"SDL could not initialize! SDL Error: \"</span> <span class=\"token operator\">&lt;&lt;</span> <span class=\"token function\">SDL_GetError</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&lt;&lt;</span> std<span class=\"token double-colon punctuation\">::</span>endl<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>There are events for controllers connecting and disconnecting, but they’re\nnot fired for controllers that are already connected. So, before starting\nthe game loop, you’ll need to check for existing controllers:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">SDL_GameController <span class=\"token operator\">*</span><span class=\"token function\">findController</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">int</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> <span class=\"token function\">SDL_NumJoysticks</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">SDL_IsGameController</span><span class=\"token punctuation\">(</span>i<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> <span class=\"token function\">SDL_GameControllerOpen</span><span class=\"token punctuation\">(</span>i<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">nullptr</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>For simplicity, we’ll only be setting up and tracking a single controller at a\ntime. But it’s a similar process to handle multiple controllers.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">SDL_GameController <span class=\"token operator\">*</span>controller <span class=\"token operator\">=</span> <span class=\"token function\">findController</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h3 id=\"controller-connection-and-removal\" tabindex=\"-1\">Controller connection and removal <a class=\"header-anchor\" href=\"#controller-connection-and-removal\">#</a></h3>\n<p>Next, you’ll need to listen for the SDL_CONTROLLERDEVICEADDED and\nSDL_CONTROLLERDEVICEREMOVED events in the SDL2 event handler:</p>\n<pre class=\"language-c\"><code class=\"language-c\"><span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"token keyword\">case</span> SDL_CONTROLLERDEVICEADDED<span class=\"token operator\">:</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>controller<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        controller <span class=\"token operator\">=</span> <span class=\"token function\">SDL_GameControllerOpen</span><span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>cdevice<span class=\"token punctuation\">.</span>which<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">case</span> SDL_CONTROLLERDEVICEREMOVED<span class=\"token operator\">:</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>controller <span class=\"token operator\">&amp;&amp;</span> event<span class=\"token punctuation\">.</span>cdevice<span class=\"token punctuation\">.</span>which <span class=\"token operator\">==</span> <span class=\"token function\">SDL_JoystickInstanceID</span><span class=\"token punctuation\">(</span>\n            <span class=\"token function\">SDL_GameControllerGetJoystick</span><span class=\"token punctuation\">(</span>controller<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">SDL_GameControllerClose</span><span class=\"token punctuation\">(</span>controller<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        controller <span class=\"token operator\">=</span> <span class=\"token function\">findController</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>The controller removed event gives us the Joystick Instance ID as which. To\ncheck whether this is the same controller, we first need to get the SDL_Joystick\nand then the ID for the controller.</p>\n<h3 id=\"handling-input\" tabindex=\"-1\">Handling Input <a class=\"header-anchor\" href=\"#handling-input\">#</a></h3>\n<p>There are two ways of receiving input; you can either use events or you can\npoll for input state. The following events are available:</p>\n<ul>\n<li><a href=\"https://wiki.libsdl.org/SDL2/SDL_ControllerDeviceEvent\">Device Events</a>:\n<ul>\n<li><code>SDL_CONTROLLERDEVICEADDED</code>: A controller was added.</li>\n<li><code>SDL_CONTROLLERDEVICEREMOVED</code>: A controller was removed.</li>\n<li><code>SDL_CONTROLLERDEVICEREMAPPED</code>: A controller was remapped, you can mostly\nignore this event unless you use raw joysticks.</li>\n</ul>\n</li>\n<li><a href=\"https://wiki.libsdl.org/SDL2/SDL_ControllerButtonEvent\">Button Events</a>:\n<ul>\n<li><code>SDL_CONTROLLERBUTTONDOWN</code>: A button was pressed on a controller.</li>\n<li><code>SDL_CONTROLLERBUTTONUP</code>: A button was released on a controller.</li>\n</ul>\n</li>\n<li><a href=\"https://wiki.libsdl.org/SDL2/SDL_ControllerAxisEvent\">Axis Events</a>:\n<ul>\n<li><code>SDL_CONTROLLERAXISMOTION</code>: An axis was moved, such as a thumbstick or analog trigger.</li>\n</ul>\n</li>\n</ul>\n<p>Here’s an example of handling the X button using the event:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">case</span> SDL_CONTROLLERBUTTONDOWN<span class=\"token operator\">:</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>controller <span class=\"token operator\">&amp;&amp;</span> event<span class=\"token punctuation\">.</span>cdevice<span class=\"token punctuation\">.</span>which <span class=\"token operator\">==</span> <span class=\"token function\">SDL_JoystickInstanceID</span><span class=\"token punctuation\">(</span>\n            <span class=\"token function\">SDL_GameControllerGetJoystick</span><span class=\"token punctuation\">(</span>controller<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">switch</span> <span class=\"token punctuation\">(</span>event<span class=\"token punctuation\">.</span>cbutton<span class=\"token punctuation\">.</span>button<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">case</span> SDL_GameControllerButton<span class=\"token double-colon punctuation\">::</span>SDL_CONTROLLER_BUTTON_X<span class=\"token operator\">:</span>\n            std<span class=\"token double-colon punctuation\">::</span>cerr <span class=\"token operator\">&lt;&lt;</span> <span class=\"token string\">\"X pressed!\"</span> <span class=\"token operator\">&lt;&lt;</span> std<span class=\"token double-colon punctuation\">::</span>endl<span class=\"token punctuation\">;</span>\n            <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span></code></pre>\n<p>and by polling for input state:</p>\n<pre class=\"language-c\"><code class=\"language-c\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">SDL_GameControllerGetButton</span><span class=\"token punctuation\">(</span>controller<span class=\"token punctuation\">,</span> SDL_GameControllerButton<span class=\"token operator\">::</span>SDL_CONTROLLER_BUTTON_X<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"X was pressed!\\n\"</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<!-- todo: axes and thresholds -->\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Using SDL_GameController, it’s possible to target a large number of gamepads on\ndifferent platforms without much effort. How to manage multiple controllers,\nkeyboard and mouse, and input binding is definitely a story for another time.</p>\n<p>The\n<a href=\"https://gitlab.com/rubenwardy/sdl_gamecontroller_example\">complete example code for SDL_GameController</a>\nis available for\n<a href=\"https://gitlab.com/rubenwardy/sdl_gamecontroller_example/-/archive/main/sdl_gamecontroller_example-main.zip\">download</a>\nfrom GitLab.</p>\n<h3 id=\"sources\" tabindex=\"-1\">Sources <a class=\"header-anchor\" href=\"#sources\">#</a></h3>\n<p><a href=\"https://www.pexels.com/photo/flat-lay-photo-of-black-sony-ps4-game-controller-on-blue-background-2520829/\">Image</a>\n© 2019 Stas Knop</p>\n<ul>\n<li><a href=\"https://wiki.libsdl.org/SDL2/CategoryGameController\">https://wiki.libsdl.org/SDL2/CategoryGameController</a></li>\n<li><a href=\"https://stackoverflow.com/questions/50022316/what-is-sdl-joystick-and-what-is-sdl-gamecontroller-what-are-the-relationships\">https://stackoverflow.com/questions/50022316/what-is-sdl-joystick-and-what-is-sdl-gamecontroller-what-are-the-relationships</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2023/01/24/using_sdl_gamecontroller/","summary":"When implementing controller support in a game, you want gamepads to just work. The solution is SDL_GameController, which allows you to target a large number of gamepads.","date_published":"2023-01-24T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/4pMLaEHt-3-540.png","tags":["tutorials","gamedev","cpp"]},{"id":"/2022/12/08/contentdb-youtuber-finds-minetest/","language":"en","title":"Large spike in Minetest traffic caused by YouTube video","content_html":"<p>Last month, ContentDB received a large spike in traffic.\n<a href=\"/2022/03/24/contentdb/\">ContentDB</a> is the website\nI created that allows you to install mods/games/texture packs from within\n<a href=\"https://www.minetest.net\">Minetest’s</a> main menu. On Saturday 5th, Bobicraft,\na Spanish-language Youtube channel with 5M subscribers, posted\n<a href=\"https://www.youtube.com/watch?v=g74i2CwuUp4\">a video</a> about Minetest.\nAt the time of writing, it has reached 1.3 million views and is now the\nmost-watched Minetest video.</p>\n<p>In this article, I will show the data about this increase in traffic. As the\ntools that I use to monitor ContentDB are ephemeral, this will keep the data\naround for future reference.</p>\n<h2 id=\"the-data\" tabindex=\"-1\">The data <a class=\"header-anchor\" href=\"#the-data\">#</a></h2>\n<p>Saturday 5th saw an overall 3.4x increase in unique visitors and a 10x increase in\ntotal bandwidth used. At its peak at 15:18 UTC, ContentDB saw a 46x spike in\ndownloads.</p>\n<figure class=\"undefined\"><a href=\"/static/img/3tlJiwKHIl-733.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/c8PielAq9X-540.webp 540w\"><img src=\"/static/img/c8PielAq9X-540.png\" alt=\"Screenshot of ContentDB's <a href=&quot;https://monitor.rubenwardy.com/d/3ELzFy3Wz/contentdb&quot;>monitoring dashboard</a>, based on Grafana.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"618\"></picture></a><figcaption>Screenshot of ContentDB's <a href=\"https://monitor.rubenwardy.com/d/3ELzFy3Wz/contentdb\">monitoring dashboard</a>, based on Grafana.</figcaption></figure>\n<p>It usually has 2TB downloads in a month, but that weekend alone used 0.86TB. If\nthis level of activity was experienced for the whole month, it would use 16TB.\nMy hosting quota is around 100TB a month so I still have a while to go.</p>\n<figure class=\"undefined\"><a href=\"/static/img/0e6vmitoKq-757.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Ps9mTYwwmC-540.webp 540w\"><img src=\"/static/img/Ps9mTYwwmC-540.png\" alt=\"Bandwidth usage from Nginx static files.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"378\"></picture></a><figcaption>Bandwidth usage from Nginx static files.</figcaption></figure>\n<h3 id=\"per-package-statistics\" tabindex=\"-1\">Per-package Statistics <a class=\"header-anchor\" href=\"#per-package-statistics\">#</a></h3>\n<p>A few days after this event, I implemented per-package statistics on ContentDB,\nshowing daily downloads. I ingested Nginx logs to get data covering the\nevent and several weeks prior. Package statistics are public, you can go to\nany package page and click “Statistics” to see them.</p>\n<figure class=\"undefined\"><a href=\"/static/img/pZf25nek6i-845.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/wt0jaYJ0PP-540.webp 540w\"><img src=\"/static/img/wt0jaYJ0PP-540.png\" alt=\"Downloads per day of i3, ContentDB's most downloaded mod, from the <a href=&quot;https://content.minetest.net/packages/jp/i3/stats/&quot;>new statistics page</a>.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"331\"></picture></a><figcaption>Downloads per day of i3, ContentDB's most downloaded mod, from the <a href=\"https://content.minetest.net/packages/jp/i3/stats/\">new statistics page</a>.</figcaption></figure>\n<h2 id=\"the-impact\" tabindex=\"-1\">The Impact <a class=\"header-anchor\" href=\"#the-impact\">#</a></h2>\n<p>ContentDB was easily able to handle the load, I didn’t notice any performance\ndegradation. ContentDB is hosted on a Virtual Dedicated Server (VDS) from\nnetCup (RS 2000 G9.5, 6-core 3.35GHz, 16GB RAM), and so has dedicated compute\nresources.</p>\n<p>Earlier this year, we saw a huge spike after AntVenom released a video on Minetest.\nThis resulted in quite a big growth in our community, with the Discord server\ngaining a few hundred new users.</p>\n<figure class=\"undefined\"><a href=\"/static/img/AvXlvpOOG1-640.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/N0t0D11dPP-540.webp 540w\"><img src=\"/static/img/N0t0D11dPP-540.png\" alt=\"Users joining the Discord server after the AntVenom video (By GreenXenith).\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Users joining the Discord server after the AntVenom video (By GreenXenith).</figcaption></figure>\n<p>Despite this new video exceeding the number of views, we haven’t seen anything\nlike the level of growth seen after the AntVenom video. Not many new users\njoined the Discord server, and there was no noticeable uptick in online players.\nThat being said, there wasn’t an increase in online players after AntVenom either.</p>\n<figure class=\"undefined\"><a href=\"/static/img/IiUFUID0vU-913.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/940rnwLrr0-540.webp 540w\"><img src=\"/static/img/940rnwLrr0-540.png\" alt=\"No increase in players playng online.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"237\"></picture></a><figcaption>No increase in players playng online.</figcaption></figure>\n<p>The latter half of the video focused on ContentDB and the packages available\nfrom it, so perhaps a lot of the viewers decided to download and try them out in\nsingleplayer.</p>\n<p>Another reason may be that the video is Spanish language and our communities are\nmostly English language.</p>\n<p>The AntVenom video accurately portrayed Minetest and our values in a\nmore serious manner, which may have led to the correct users finding Minetest\nand joining our communities.</p>\n<h2 id=\"the-future\" tabindex=\"-1\">The Future <a class=\"header-anchor\" href=\"#the-future\">#</a></h2>\n<p>Now that Minetest has been noticed by a couple of big YouTubers, we might start\nseeing more YouTubers and media sources checking out Minetest.</p>\n<p>One of the things we’ve been working towards is an improved main menu redesign.\nThis is vital to attract and retain users, as the current main menu doesn’t do a\nvery good job of promoting Minetest or its values as an open game creation\nplatform.</p>\n<p>Another thing we’ll be doing soon is listing Minetest on Steam. It’ll start in a\n“coming soon” state whilst we improve the main menu and other things, allowing\nplayers to wishlist it.</p>\n","url":"https://blog.rubenwardy.com/2022/12/08/contentdb-youtuber-finds-minetest/","summary":"A month ago, ContentDB received a large spike in traffic. Bobicraft, a Spanish-language Youtube channel with 5M subscribers, posted a video about Minetest.","date_published":"2022-12-08T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/po1vDlLBwW-540.png","tags":["luanti"]},{"id":"/2022/12/02/minetest-steam-deck/","language":"en","title":"Luanti (Minetest) on Steam Deck: usage and review","content_html":"<p>A few months ago, my Steam Deck arrived. So, obviously, one of the first things\nI did was play <a href=\"https://www.minetest.net/\">Luanti</a>. Whilst Luanti isn’t\navailable on Steam yet, it is available on Flatpak and so can be installed from\ndesktop mode.</p>\n<p>Luanti plays very well on the Steam Deck, it performs great and Steam Input\nmakes setting up controls easy. The Deck is an open console, so there’s full\nsupport for modding and you can cross-play with other platforms.</p>\n<p>In this article, I will explain how to set up Luanti on the deck, and review\nthe controls, performance, and experience. I’ve already written\n<a href=\"/2022/10/14/steam-deck-is-awesome/\">my thoughts about the Deck</a>\nin another article, and recommend checking that out.</p>\n<h2 id=\"setting-up-luanti-on-steam-deck\" tabindex=\"-1\">Setting Up Luanti on Steam Deck <a class=\"header-anchor\" href=\"#setting-up-luanti-on-steam-deck\">#</a></h2>\n<h3 id=\"install-luanti\" tabindex=\"-1\">Install Luanti <a class=\"header-anchor\" href=\"#install-luanti\">#</a></h3>\n<p>As Luanti isn’t available on Steam yet, you’ll need to boot into the Deck’s\nDesktop Mode and install Luanti from Flatpak. Press and hold the power button\nand then select Switch to Desktop Mode.</p>\n<p>Next, open up the Discover app. To show the onscreen keyboard, you’ll need to\npress Steam and X at the same time. Search for Luanti and install it.</p>\n<p>Once installed, open up the start menu, find Luanti, and right-click on it by\npressing the left trigger (L2). Select “add to Steam” and wait for it to finish.</p>\n<p>You can now boot back into game mode. You’ll find Luanti in Library &gt;\nNon-Steam Games.</p>\n<figure class=\"undefined\"><a href=\"/static/img/t3wD9g42hO-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/9EO2qe_WA2-540.webp 540w\"><img src=\"/static/img/9EO2qe_WA2-540.png\" alt=\"After installing using Discover, open up the start menu and add to Steam.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>After installing using Discover, open up the start menu and add to Steam.</figcaption></figure>\n<h3 id=\"add-cover-art-optional\" tabindex=\"-1\">Add Cover Art (optional) <a class=\"header-anchor\" href=\"#add-cover-art-optional\">#</a></h3>\n<p>The Steam Deck’s UI shows art for each game. It’s likely that you’ll want to add\nart to Luanti so that it’s not just a grey square.</p>\n<p>Run Luanti at least once in Game Mode, stop it, and return to Desktop Mode.</p>\n<p>Now you need to download the cover art you’d like to use. I’ll be posting some\nexample images at some point. In the meantime, you’ll need to provide horizontal\n(460x215px) and vertical (600x300px) images in the correct resolutions.</p>\n<p>Launch Steam and go to the Library. Find Luanti’s tile on the right side of\nthe window, then right-click &gt; Manage &gt; Set Custom Art &gt; Select the vertical\nimage.</p>\n<p>Scroll down until you see the blank landscape image of the app.\nRight-click &gt; Manage &gt; Select artwork &gt; Choose the horizontal image.</p>\n<p>If you’re stuck, <a href=\"https://www.youtube.com/watch?v=kI8kSws7aS0\">here’s the tutorial I followed</a>\non YouTube.</p>\n<h2 id=\"controls\" tabindex=\"-1\">Controls <a class=\"header-anchor\" href=\"#controls\">#</a></h2>\n<h3 id=\"setting-up-a-control-layout\" tabindex=\"-1\">Setting up a Control Layout <a class=\"header-anchor\" href=\"#setting-up-a-control-layout\">#</a></h3>\n<p>Unfortunately, Luanti doesn’t have built-in support for\n<a href=\"https://wiki.minetest.net/Gamepads\">gamepads</a>.\nThere is some basic code for it, but it’s very broken and doesn’t support\nuser interfaces at all. I’m planning to fix this at some point, but in the\nmeantime, we’ll need to make do without it.</p>\n<p>When a game doesn’t have built-in controller support, you can use Steam Input to\nbind the controls to fake mouse movement and key presses.</p>\n<p>I’ve already made a config for Luanti. Select the gamepad icon, and then click\nthe layout button. Go to Community and select “Luanti Semi-official Steam\nDeck” by rubenwardy. You can now customise my layout as desired, or\nalternatively make your own layout based on Templates &gt; Keyboard (WASD) and\nMouse.</p>\n<figure class=\"undefined\"><a href=\"/static/img/UV6gQHq_jN-881.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/jGLwV8iVkN-540.webp 540w\"><img src=\"/static/img/jGLwV8iVkN-540.png\" alt=\"You can find my Steam Deck layout in the Community Layouts tab.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"172\"></picture></a><figcaption>You can find my Steam Deck layout in the Community Layouts tab.</figcaption></figure>\n<p>If, at any time, you’ve forgotten the controls or want to change them, you can\npress the Steam button and go to controller options. It shows you the controls\nand allows you to edit them whilst the game is running.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/Hspt2iQZwX-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/HI3IUjW4EJ-540.webp 540w\"><img src=\"/static/img/HI3IUjW4EJ-540.png\" alt=\"Check or edit your layout whilst playing using the Steam menu.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"285\"></picture></a><figcaption>Check or edit your layout whilst playing using the Steam menu.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/p3rd6uQgvC-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/d2zVloIe_H-540.webp 540w\"><img src=\"/static/img/d2zVloIe_H-540.png\" alt=\"My controller layout.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"285\"></picture></a><figcaption>My controller layout.</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"playing-the-game\" tabindex=\"-1\">Playing the Game <a class=\"header-anchor\" href=\"#playing-the-game\">#</a></h3>\n<p>The game plays well in classic creative/survival gameplay, it’s easy to place\nblocks and move around. Playing PvP or games that require parkour is harder, but\nthere are tricks to making it playable - you can use the touchpad for finer\nmovement or the gyro to aim.</p>\n<p>One problem with going from a keyboard+mouse game to a gamepad is that a gamepad\nhas way fewer buttons. Steam Deck comes with extra buttons on the back, which\nare great for extra actions. You can also use the left touchpad as a radial\nmenu, selecting options by dragging your finger to an angle and pressing to\nselect.</p>\n<figure class=\"undefined\"><a href=\"/static/img/8QXYz6VTdF-843.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/xP4MUQbPcZ-540.webp 540w\"><img src=\"/static/img/xP4MUQbPcZ-540.png\" alt=\"A radial menu can be accessed using the left trackpad.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>A radial menu can be accessed using the left trackpad.</figcaption></figure>\n<h3 id=\"user-interfaces\" tabindex=\"-1\">User Interfaces <a class=\"header-anchor\" href=\"#user-interfaces\">#</a></h3>\n<p>You can use the right touchpad or thumbstick to move and click the mouse.\nBecause “place” is right-click, you can use the left trigger to right-click in\nthe inventory, allowing you to place a single item in a slot. You can also use\nsneak to shift+click.</p>\n<p>Steam Deck does come with a touchscreen, but because Luanti was built in\ndesktop mode it just emulates a mouse. You can’t use the touch gestures\navailable on the Android port.</p>\n<p>To type, you’ll need to manually open the keyboard by pressing Steam+X.</p>\n<p>Interacting with Luanti’s User Interfaces is perfectly usable but is a bit\nclunky. It would be nice to have intelligent focus using the left thumbstick,\nbut this will require engine changes to fully support gamepads.</p>\n<h2 id=\"performance\" tabindex=\"-1\">Performance <a class=\"header-anchor\" href=\"#performance\">#</a></h2>\n<p>It’s hard to get an objective measure of the performance whilst playing Luanti\nas it depends a lot on what you’re doing. How much of the world and what you can\nsee makes a big difference - viewing a large dense area of the map with lots of\ntrees, walls, and entities will drop the FPS lower than viewing a similar area\nof grassy plains.</p>\n<p>Starting a brand new world, I got a stable 60FPS walking around a dense forest\nwith default settings (200 view range). Flying above the forest, the FPS drops\ndown to 25FPS, which is still perfectly playable.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/ir6WESSADS-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/mGJc8pBqgy-540.webp 540w\"><img src=\"/static/img/mGJc8pBqgy-540.png\" alt=\"Chilling on a tree. 200 view range, 60FPS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>Chilling on a tree. 200 view range, 60FPS</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/xu5X6j8DfM-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/fi8OJ9KgfT-540.webp 540w\"><img src=\"/static/img/fi8OJ9KgfT-540.png\" alt=\"Flying high. 500 view range, 25FPS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>Flying high. 500 view range, 25FPS</figcaption></figure></div>\n\t\t\t</div>\n<p>Walking around my farm with default settings, I get 56-60FPS with occasional\ndrops down to 50FPS. The town has a lot of underground tunnels with mesecons and\npipeworks networks. There are also a lot of mobs roaming around, it is a farm\nafter all. Turning on medium shadows drops the FPS to 37-45.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/X0h4ZAPY3v-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/y4Zg0CIkkL-540.webp 540w\"><img src=\"/static/img/y4Zg0CIkkL-540.png\" alt=\"200 view range, default settings, 56-60FPS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>200 view range, default settings, 56-60FPS</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/R-MHITfDBj-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/DE2NI1ZAAu-540.webp 540w\"><img src=\"/static/img/DE2NI1ZAAu-540.png\" alt=\"200 view range with medium shadows, 37-45FPS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>200 view range with medium shadows, 37-45FPS</figcaption></figure></div>\n\t\t\t</div>\n<p>Luanti is largely CPU bound. The Steam Deck has an AMD RDNA 2, which is quite\na good GPU in terms of power to performance; it certainly outperforms the iGPU\non <a href=\"https://rubenwardy.com/uses/#laptop\">my laptop</a>. If Luanti were to receive\nthe attention needed to upgrade to make use of modern pipelines and the GPU, I’m\nsure the Deck would benefit and perform better.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Steam Deck is an awesome new way to play Luanti. Its openness makes it the\nbest console to play Luanti on.</p>\n<p>In the future, I plan on adding full gamepad support to Luanti, which will\nmake using GUIs much easier. I also plan on releasing Luanti to Steam once\nwe have a new main menu; this will make it easier to install Luanti on the\nDeck.</p>\n<p>This article was originally going to be a YouTube video before I discovered that\nrecording the screen was painful, almost as painful as recording the voice-over.</p>\n","url":"https://blog.rubenwardy.com/2022/12/02/minetest-steam-deck/","summary":"In this article, I will explain how to set up Luanti on the deck, and review the controls, performance, and experience.","date_published":"2022-12-02T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/PCDEf-Th5O-540.png","tags":["luanti","steamdeck","linux","reviews","tutorials"]},{"id":"/2022/11/16/thinkpad-x1-fingerprint-auth/","language":"en","title":"Setting up fingerprint auth on Kubuntu (Thinkpad X1)","content_html":"<p>Using a fingerprint to unlock your computer or elevate privileges (sudo) is a\nnice convenience, saving time during daily use of a computer. This article will\nshow you how to set up fingerprint authentication on Ubuntu KDE. You’ll still\nneed to enter your password to login, but once logged in you will be able to use\na fingerprint to unlock or use sudo.</p>\n<p>This guide is based on a Thinkpad X1 running Kubuntu 22.04, but it should work\nfor most laptops with supported fingerprint sensors. You can check whether your\nfingerprint sensor is supported by searching for “Linux fingerprint” and your\ncomputer’s make/model. The Arch wiki is\n<a href=\"https://wiki.archlinux.org/title/Lenovo_ThinkPad_X1_Carbon_(Gen_7)#Fingerprint_sensor\">especially useful</a>\nfor this sort of thing, even if you don’t use Arch.</p>\n<p>It goes without saying that this comes without warranty; backup your data, and\nhave a LiveUSB ready. There are some pointers on recovering from mistakes at the\nbottom of this page. Luckily, I never needed to use a LiveUSB, I could easily\nrecover from a virtual console.</p>\n<h2 id=\"steps\" tabindex=\"-1\">Steps <a class=\"header-anchor\" href=\"#steps\">#</a></h2>\n<h3 id=\"install-fprint\" tabindex=\"-1\">Install fprint <a class=\"header-anchor\" href=\"#install-fprint\">#</a></h3>\n<p>Run <code>lsusb</code> and check that you can see your fingerprint sensor.\nFor a Thinkpad X1, this will be <code>06cb:00bd Synaptics, Inc. Prometheus MIS Touch Fingerprint Reader</code>.</p>\n<p>If you don’t, then your firmware may be outdated or the sensor may not be supported.\nYou can use <code>fwupdmgr</code> to update your firmware.</p>\n<p>Next install fprint: <code>sudo apt install -y fprintd libpam-fprintd</code></p>\n<h3 id=\"enroll-fingerprint\" tabindex=\"-1\">Enroll Fingerprint <a class=\"header-anchor\" href=\"#enroll-fingerprint\">#</a></h3>\n<p>You now need to enroll at least one finger so that fprint can recognise you.</p>\n<p>You can do that using the GUI, by going to Settings &gt; Users &gt;\nConfigure Fingerprint Authentication.</p>\n<p>Alternatively, you can use the CLI: Run <code>fprintd-enroll $USER</code> and swipe your\nfinger over the sensor 3 times.</p>\n<h3 id=\"set-up-the-authentication-module-pam\" tabindex=\"-1\">Set up the Authentication Module (PAM) <a class=\"header-anchor\" href=\"#set-up-the-authentication-module-pam\">#</a></h3>\n<p>Enable the fprint pam module by running <code>sudo pam-auth-update</code>, checking Fingerprint,\nand selecting OK.</p>\n<h3 id=\"manual-changes\" tabindex=\"-1\">Manual Changes <a class=\"header-anchor\" href=\"#manual-changes\">#</a></h3>\n<p>In theory, the above should be all that you need to do to use fingerprints for\nlogin and sudo auth, but I found that logins were broken. So we’ll need to do a\nfew more steps to fix that.</p>\n<p>The Pluggable Authentication Module (PAM) configuration files can be found at\n<code>/etc/pam.d/</code>, and are used to determine how to authenticate the user. I needed\nto change the order of the authentication methods to get it to work.</p>\n<p>First, comment out the <code>fprint</code> line in <code>/etc/pam.d/common-auth</code> if present, to\nprevent fprint from always being used.</p>\n<p>To use fingerprints for sudo, add the following lines to <code>/etc/pam.d/sudo</code> above\n<code>@include common-auth</code>:</p>\n<pre><code>auth [success=2 default=ignore] pam_fprintd.so max_tries=2 timeout=10\n</code></pre>\n<p>To use fingerprints to unlock, create a new file <code>/etc/pam.d/kde</code>:</p>\n<pre><code>auth sufficient pam_unix.so try_first_pass likeauth nullok\nauth sufficient pam_fprintd.so max_tries=2 timeout=10\n</code></pre>\n<h3 id=\"test-it\" tabindex=\"-1\">Test It <a class=\"header-anchor\" href=\"#test-it\">#</a></h3>\n<p>Lock your screen, and check you can still use your password to unlock - this is\nuseful when the fingerprint sensor fails. Now check that the fingerprint works;\npress Enter on the password field and then touch the sensor.</p>\n<h2 id=\"recovering-from-mistakes\" tabindex=\"-1\">Recovering from Mistakes <a class=\"header-anchor\" href=\"#recovering-from-mistakes\">#</a></h2>\n<p>When changing authentication settings, there’s always a possibility of being\nlocked out; this happened a few times before I figured out how to make this\nwork.</p>\n<h3 id=\"using-virtual-consoles\" tabindex=\"-1\">Using Virtual Consoles <a class=\"header-anchor\" href=\"#using-virtual-consoles\">#</a></h3>\n<p>You can switch to a Virtual Console by pressing Ctrl + Alt + F2. This is a\nfullscreen console that allows you to log into the system without going through\nthe lock screen. From here, you can edit the configuration files using nano or\nyour CLI editor of choice.</p>\n<p>See <a href=\"https://www.makeuseof.com/what-are-linux-virtual-consoles/\">Linux Virtual Consoles Explained</a> for more info.</p>\n<h3 id=\"using-a-liveusb\" tabindex=\"-1\">Using a LiveUSB <a class=\"header-anchor\" href=\"#using-a-liveusb\">#</a></h3>\n<p>As a last resort, it’s possible to use a LiveUSB/LiveCD to edit the\nauthentication configuration files. This is possible for both unencrypted and\nencrypted drives, as you can\n<a href=\"https://askubuntu.com/questions/653408/mounting-encrypted-luks-partition-from-live-cd\">mount encrypted partitions using cryptsetup</a>.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<h3 id=\"sources\" tabindex=\"-1\">Sources <a class=\"header-anchor\" href=\"#sources\">#</a></h3>\n<p><a href=\"https://www.pexels.com/photo/black-and-silver-laptop-computer-3550482/\">Image</a>\n© 2019 Olena Bohovyk</p>\n<ul>\n<li><a href=\"https://unix.stackexchange.com/questions/655203/how-do-i-implement-fprintd-into-login-in-kubuntu\">https://unix.stackexchange.com/questions/655203/how-do-i-implement-fprintd-into-login-in-kubuntu</a></li>\n<li><a href=\"https://wiki.archlinux.org/title/fprint\">https://wiki.archlinux.org/title/fprint</a></li>\n<li><a href=\"https://www.reddit.com/r/kde/comments/sohmso/the_new_fingerprint_feature_is_not_working/\">https://www.reddit.com/r/kde/comments/sohmso/the_new_fingerprint_feature_is_not_working/</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2022/11/16/thinkpad-x1-fingerprint-auth/","summary":"Unlock your computer or use sudo with your fingerprint on Ubuntu KDE.","date_published":"2022-11-16T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/nyK_t8P-QU-540.png","tags":["linux","tutorials"]},{"id":"/2022/10/22/Minetest-mods-app/","language":"en","title":"Minetest Mods: my first Android app","content_html":"<p>In 2016, I created an app to install mods for Minetest’s Android port. It\nwas my first Android app; it taught me a great deal about Android development\nand also helped me get my first programming job.</p>\n<p><a href=\"https://www.minetest.net\">Minetest</a> is an open-source game engine with millions\nof downloads and thousands of weekly players. The project has a very active\nmodding community, with many games available to play. Before I created the app,\nusers had to manually install content by unzipping their files into a directory;\nthis was a poor user experience, especially on Android, so I created the app\nto make this easier.</p>\n<p>Minetest now has <a href=\"/2022/03/24/contentdb/\">ContentDB</a>, a platform I created to\nbrowse and install mods and games within Minetest. Because of this, the app is\nnow obsolete and is no longer available for download. That doesn’t mean this app\nis fully gone - the lessons I learned live on in ContentDB.</p>\n<h2 id=\"getting-data\" tabindex=\"-1\">Getting data <a class=\"header-anchor\" href=\"#getting-data\">#</a></h2>\n<p>For the app to function, it needs a list of mods that can be installed.\nIn 2017, the closest thing to this list was\n<a href=\"https://krock-works.uk.to/minetest/modSearch.php\">Krock’s Mod Search</a>, an index\nof mod topics on the forum, but this was super unreliable. It was common for\nthere to be no download links or git repo, and a lot of the metadata was plain\nwrong.</p>\n<p>My first challenge before I could even start on the app was to create a backend\nAPI with validated data. I did this by using Krock’s Mod Search as a base and\nthen performing extra checks. I fetched forum topics to extract more data and\nlinks. I fetched downloads and checked their contents, making sure that they\ncontained a valid mod with the correct mod name. I was able to manually override\nthe data where the automatic crawler failed.</p>\n<p>One of the most important parts of this validation system was good tooling. I\ncreated a simple web front-end that allowed you to find mods and see any problems\nthat were preventing them from appearing in the app.</p>\n<h2 id=\"the-app\" tabindex=\"-1\">The app <a class=\"header-anchor\" href=\"#the-app\">#</a></h2>\n<p>The app’s homepage had a list of mods, with installed mods appearing pinned at\nthe top. By tapping a mod you go to the mod’s detail screen, where you can see\nmore information about the mod and install it.</p>\n<p>Mod installation was done in a background service, allowing the user to navigate\naway from the mod page whilst it was being installed. A notification showed the\ncurrent download progress. Once a mod was installed, the app could then prompt\nthe user to install missing dependencies.</p>\n<figure class=\"undefined\"><a href=\"/static/img/WH7sjxzqGx-2304.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/J8RTMnkJNT-540.webp 540w\"><img src=\"/static/img/J8RTMnkJNT-540.png\" alt=\"The app running on a tablet\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>The app running on a tablet</figcaption></figure>\n<h2 id=\"google-play-and-post-release\" tabindex=\"-1\">Google Play and post-release <a class=\"header-anchor\" href=\"#google-play-and-post-release\">#</a></h2>\n<p>I published the app on Google Play and F-Droid on the 24th of March 2016. It was\nreceived with favourable reviews, averaging 4.3 stars.</p>\n<p>I maintained it for 3 years, adding features to improve user experience such as\ndependency installation and world configuration, and rewriting parts as my\nknowledge of Android dev increased.</p>\n<p>As the app predated Android Kotlin and Jetpack by over a year, it was originally\nwritten in Java. Towards the end I rewrote parts of the app in Kotlin using\nmore modern techniques. With the introduction of ContentDB, I ended up\nsunsetting the app and removing it from the Play Store.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The Minetest Mods app was my first Android app; it taught me a great deal about\nAndroid development. I’m happy with the result; I think it looked good and had a\ngood user experience. One of the reasons I like making user interfaces is being\nable to create quality user experiences that I can be proud of.</p>\n<p>One of the primary flaws in the app was the data. It was quite unreliable; a lot\nof the mods failed to installed due to changes that the crawler didn’t pick up.\nI made a lot of changes to the backend to improve this situation, and they did\nhelp. But the problem was ultimately solved by ditching the crawler and using\nmods from ContentDB instead.</p>\n<p>Another flaw is that the app was designed just for mods, and not for other\ncontent like games and texture packs. Towards the end, I started to work on\nextending the app to support games and texture packs - but I was never happy\nwith the result, and so it never made it into a release.</p>\n<p>The app also got me my first programming job at a software house, creating\nAndroid and iOS apps for clients. Being able to show my app on Google Play in\nthe interview was a big selling point.</p>\n","url":"https://blog.rubenwardy.com/2022/10/22/Minetest-mods-app/","summary":"In 2016, I created an app to install mods for Minetest's Android port. It taught me a great deal about Android dev and helped me get my first programming job.","date_published":"2022-10-22T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/KC4e_DF3Vc-540.png","tags":["projects","android","luanti"]},{"id":"/2022/10/16/steam-deck-upload-game/","language":"en","title":"Devkit CLI: Upload your game to Steam Deck from your dev computer","content_html":"<p><a href=\"/2022/10/14/steam-deck-is-awesome/\">Steam Deck</a> is a new portable gaming\nconsole from Valve. The Deck is a very open platform as it’s a full Linux PC,\nmaking it very easy to use third-party games and stores. All Decks can act as a\ndevkit machine, and come with devkit tools preinstalled. There was a Steam Deck\nDevkit edition, but that was a preproduction version of the hardware to allow\ngame developers to test their games.</p>\n<p>This article will show you how to upload and run your games on the Steam Deck\nfrom your development computer, using a VSCode task or a script you can use in\nany editor. Whilst you could develop on the Deck directly, it would require\nsetting up a new dev environment. Being able to develop on your computer and\ntest on the Deck allows you to reuse your current dev environment.</p>\n<h2 id=\"set-up-steamos-devkit\" tabindex=\"-1\">Set up SteamOS Devkit <a class=\"header-anchor\" href=\"#set-up-steamos-devkit\">#</a></h2>\n<p>You’ll need to follow the “Install tools” and “Connecting to Deck” instructions\nfound in the\n<a href=\"https://partner.steamgames.com/doc/steamdeck/loadgames\">Steamworks documentation</a>.</p>\n<h2 id=\"how-does-devkit-upload-to-the-deck\" tabindex=\"-1\">How does DevKit upload to the Deck? <a class=\"header-anchor\" href=\"#how-does-devkit-upload-to-the-deck\">#</a></h2>\n<p>Before we continue with the process, it’s important to explain what the DevKit\nGUI does when you upload a game. The GUI is quite complicated and there’s no\nofficial CLI or library, so it would be nice if we could simplify the process.</p>\n<p>It performs the following steps:</p>\n<ol>\n<li>Uploads some utility scripts</li>\n<li>Creates a game on the Deck</li>\n<li>Uploads the game files</li>\n<li>Creates a shortcut for the game on the Deck</li>\n</ol>\n<p>Steps 1, 2, and 4 don’t need to be done every time. Once the game is registered\nusing the GUI, you only need to upload the files to upload a new version.</p>\n<h2 id=\"create-your-game-using-the-devkit-client-gui\" tabindex=\"-1\">Create your game using the Devkit Client GUI <a class=\"header-anchor\" href=\"#create-your-game-using-the-devkit-client-gui\">#</a></h2>\n<p>With your Deck connected, go to Title Upload and fill in the fields:</p>\n<ul>\n<li><strong>Name:</strong> Programmatic name, no spaces are allowed. Ex: <code>MyGame</code></li>\n<li><strong>Local folder:</strong> Path to the build folder. Ex: <code>/home/user/dev/games/mygame/build/</code>.</li>\n<li><strong>Upload filtering:</strong> You can filter what files are uploaded to the deck here.</li>\n<li>Check “Delete remote files not present in local folder”</li>\n<li><strong>Start command:</strong> Executable name, relative to local folder. Ex: <code>mygame.x84_64</code></li>\n<li>If your game is a Windows executable, you’ll need to enable Steam Play.</li>\n<li>Save config</li>\n<li>Click <strong>Upload</strong></li>\n</ul>\n<p>You should now go to Library &gt; Non-Steam Games and run your game. If it fails\nto open, you should check the logs - you can do that using the “Device Logs” tab\nof the Devkit Client GUI.</p>\n<h2 id=\"the-script\" tabindex=\"-1\">The Script <a class=\"header-anchor\" href=\"#the-script\">#</a></h2>\n<p>The script uses\n<a href=\"https://www.digitalocean.com/community/tutorials/how-to-use-rsync-to-sync-local-and-remote-directories\">rsync</a>\nto upload the files to the deck. rsync is a common program used to copy entire\ndirectories; in this case, we’re using it to upload over SSH. If you’re using\nWindows, you may need to use WSL.</p>\n<p>Save the following as <code>./utils/deck.sh</code> in your project and customise the variables\nat the top.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token builtin class-name\">set</span> <span class=\"token parameter variable\">-e</span>\n\n<span class=\"token comment\"># Details as provided to the DevKit client</span>\n<span class=\"token assign-left variable\">GAME_NAME</span><span class=\"token operator\">=</span><span class=\"token string\">\"MyGame\"</span>\n<span class=\"token assign-left variable\">BUILD_DIR</span><span class=\"token operator\">=~</span>/dev/games/mygame/build/\n<span class=\"token assign-left variable\">EXE</span><span class=\"token operator\">=</span><span class=\"token string\">\"mygame.x86_64\"</span>\n\n<span class=\"token comment\"># Deck IP address</span>\n<span class=\"token assign-left variable\">IP_ADDRESS</span><span class=\"token operator\">=</span><span class=\"token string\">\"192.168.0.110\"</span>\n\n<span class=\"token comment\"># No need to edit these</span>\n<span class=\"token assign-left variable\">TARGET_PATH</span><span class=\"token operator\">=</span><span class=\"token string\">\"/home/deck/devkit-game/<span class=\"token variable\">$GAME_NAME</span>/\"</span>\n<span class=\"token assign-left variable\">RSA_KEY_PATH</span><span class=\"token operator\">=~</span>/.config/steamos-devkit/devkit_rsa\n\n<span class=\"token comment\"># Upload game files</span>\n<span class=\"token function\">rsync</span> <span class=\"token parameter variable\">-av</span> <span class=\"token parameter variable\">--chmod</span><span class=\"token operator\">=</span>Du<span class=\"token operator\">=</span>rwx,Dgo<span class=\"token operator\">=</span>rx,Fu<span class=\"token operator\">=</span>rwx,Fog<span class=\"token operator\">=</span>rx <span class=\"token punctuation\">\\</span>\n  <span class=\"token parameter variable\">-e</span> <span class=\"token string\">'ssh  -o StrictHostKeyChecking=no -i $RSA_KEY'</span> <span class=\"token punctuation\">\\</span>\n  <span class=\"token parameter variable\">--update</span> <span class=\"token parameter variable\">--delete</span> --delete-excluded --delete-delay <span class=\"token punctuation\">\\</span>\n  <span class=\"token parameter variable\">--checksum</span> <span class=\"token string\">\"<span class=\"token variable\">$BUILD_DIR</span>\"</span> <span class=\"token string\">\"deck@<span class=\"token variable\">$IP_ADDRESS</span>:<span class=\"token variable\">$TARGET_PATH</span>\"</span></code></pre>\n<p>You can add this as a VSCode task by adding the following to\n<code>.vscode/tasks.json</code>:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// See https://go.microsoft.com/fwlink/?LinkId=733558</span>\n    <span class=\"token comment\">// for the documentation about the tasks.json format</span>\n    <span class=\"token string-property property\">\"version\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"2.0.0\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string-property property\">\"tasks\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token punctuation\">{</span>\n            <span class=\"token string-property property\">\"label\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Run on Steam Deck\"</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string-property property\">\"type\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"shell\"</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string-property property\">\"command\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"./utils/deck.sh\"</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string-property property\">\"presentation\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token string-property property\">\"reveal\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"always\"</span><span class=\"token punctuation\">,</span>\n                <span class=\"token string-property property\">\"panel\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"shared\"</span><span class=\"token punctuation\">,</span>\n                <span class=\"token string-property property\">\"showReuseMessage\"</span><span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n                <span class=\"token string-property property\">\"clear\"</span><span class=\"token operator\">:</span> <span class=\"token boolean\">true</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The fact that the Deck is a Linux PC made this super easy to do. In the future,\nI’d like to make a devkit CLI that allows you to create games as well as update.\nI’d also like to be able to start and stop the game, this would be super useful\nduring testing.</p>\n","url":"https://blog.rubenwardy.com/2022/10/16/steam-deck-upload-game/","summary":"How to upload and run your games on the Steam Deck from your development computer, using a VSCode task or a script you can use in any editor.","date_published":"2022-10-16T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/MZtU3zaEn7-540.png","tags":["steamdeck","linux","tutorials"]},{"id":"/2022/10/14/steam-deck-is-awesome/","language":"en","title":"Steam Deck is Awesome","content_html":"<p>My Steam Deck arrived two months ago, and I’ve been having a blast ever since.\nI’m not much of a gamer; RimWorld and Skyrim are the only two games I’ve spent\nmore than 100 hours in. I find it hard to play a game without wanting to do game\ndev instead. But with the Deck, I’ve been playing the games that have been on my\nplay list for a while.</p>\n<p>This article contains my thoughts about the Steam Deck. It’s not meant to be\na review applicable to everyone; I’m not your average user nor do I claim to\nbe. I recommend reading a wide range of reviews before buying something\nexpensive.</p>\n<h2 id=\"why-i-like-the-steam-deck\" tabindex=\"-1\">Why I Like the Steam Deck <a class=\"header-anchor\" href=\"#why-i-like-the-steam-deck\">#</a></h2>\n<h3 id=\"the-steam-store-and-my-steam-library\" tabindex=\"-1\">The Steam Store and My Steam library <a class=\"header-anchor\" href=\"#the-steam-store-and-my-steam-library\">#</a></h3>\n<p>I was given an Xbox 360 when it first came out and I sold my original Xbox.\nWhile I had over 30 games for the original Xbox, I only had three games for the\nXbox 360 and didn’t have money to buy more. Because of this, I didn’t play the\nXbox 360 much.</p>\n<p>The variety and cost of games are very important for a console. The Deck comes\nwith Steam by default, giving access to a huge number of varied and inexpensive\ngames. Steam regularly runs sales, making it even less expensive to get good\ngames. I recently bought The Witcher 3, with all DLCs, for £6; that’s a\nbrilliant game with easily more than 100 hours of content.</p>\n<p>The Steam store is the leading store for PC gamers. Over the years, I’ve bought\na lot of games for cheap on Steam sales but haven’t had time to play them. With\nthe Steam Deck, I am finally playing these games. Recent examples include Portal\n1 and The Stanley Parable.</p>\n<h3 id=\"super-open-device\" tabindex=\"-1\">Super Open Device <a class=\"header-anchor\" href=\"#super-open-device\">#</a></h3>\n<p>Steam Deck is a full Linux PC. You can enter desktop mode and install apps using\nFlatpak. Don’t want to install from Steam? You can install other game stores,\nsuch as Heroic Games (for Epic Game Store), Itch.io, and GOG. You can even use\nXbox Game Pass.</p>\n<p>I was able to get one of my games working in under 30 minutes. I exported my\ngame from Godot and used the SteamOS DevKit to upload it to the Deck. The DevKit\njust uses rsync, a standard Linux file transfer utility, so it’s perfectly\npossible to upload in the same way using a custom script. The trickier bit is\nadding the game to Steam and creating the shortcuts, the devkit has a few long\nscripts to do this.</p>\n<figure class=\"undefined\"><a href=\"/static/img/JPiMw1C2RY-900.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/VGFWe5QjaR-540.webp 540w\"><img src=\"/static/img/VGFWe5QjaR-540.jpeg\" alt=\"My <a href='/2022/03/19/ld48-tin-mining/'>Tin Mining</a> game running on Steam Deck.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"211\"></picture></a><figcaption>My <a href='/2022/03/19/ld48-tin-mining/'>Tin Mining</a> game running on Steam Deck.</figcaption></figure>\n<h3 id=\"controls-and-steam-input\" tabindex=\"-1\">Controls and Steam Input <a class=\"header-anchor\" href=\"#controls-and-steam-input\">#</a></h3>\n<p>One of the main benefits of the Steam Deck is the amazing controls. You get\nfull-size thumbsticks with ergonomic triggers and grips.</p>\n<p>Steam Input allows you to easily rebind controls and add gamepad support to\ngames that don’t otherwise support them.\n<a href=\"/2022/12/02/minetest-steam-deck/\">Luanti</a> doesn’t\nsupport gamepads by default, but it was super easy to set up Steam Input to fake\nkey presses and mouse movement to control Luanti. The experience is largely\nindistinguishable from the game itself supporting gamepads.</p>\n<figure class=\"undefined\"><a href=\"/static/img/08ygh99Yt3-700.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/jwe2n5JCX1-540.webp 540w\"><img src=\"/static/img/jwe2n5JCX1-540.png\" alt=\"Steam Input allows you to rebind the controls on any game, even those that don't support gamepads.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>Steam Input allows you to rebind the controls on any game, even those that don't support gamepads.</figcaption></figure>\n<p>As well as the two thumbsticks, the Deck has two trackpads. The right trackpad\nis commonly used to control the mouse or move the right thumbstick more\nprecisely. The left thumbstick is commonly used as a Virtual Menu / Radial Menu,\nallowing you to press and select options quickly. The radial menu feature is\ngreat in games that have a lot of keyboard shortcuts, such as RimWorld.</p>\n<figure class=\"undefined\"><a href=\"/static/img/fZQ0IMIsyQ-1280.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/ZbNfNz_NjF-540.webp 540w\"><img src=\"/static/img/ZbNfNz_NjF-540.png\" alt=\"A radial menu in RimWorld, accessed using the left trackpad.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>A radial menu in RimWorld, accessed using the left trackpad.</figcaption></figure>\n<p>The Deck also has 4 extra buttons on the back. Not many games use these\nbuttons - Aperture Desk Job and RimWorld were the only games I found\nthat did. You can set these buttons to perform your own actions, or just\nignore them.</p>\n<h3 id=\"linux-and-open-source\" tabindex=\"-1\">Linux and Open Source <a class=\"header-anchor\" href=\"#linux-and-open-source\">#</a></h3>\n<p>I’ve been using Linux as my primary OS for over 10 years. Linux performs better,\nis more flexible, and provides an alternative to the current duopoly of\noperating systems.</p>\n<p>When I first started using Linux, I dual-booted as most of my games didn’t work.\nValve’s work on Proton/Wine in preparation for Steam Deck has made Linux viable\nas a gaming platform. Now, all the games I’m interested in playing work on\nLinux.</p>\n<p>SteamOS is a very user-friendly introduction\nto Linux; you don’t need to use the terminal or any desktop features, and the\nread-only distro makes updating unlikely to cause issues.</p>\n<p>From Valve’s perspective, Linux provides an alternative to Windows that reduces\ntheir dependence on Microsoft. Valve started working on improving Linux as a\ngaming platform after Windows 8 required the Windows Store to install apps.</p>\n<!-- page.quotes.gabe -->\n<h3 id=\"a-dedicated-machine-for-gaming\" tabindex=\"-1\">A Dedicated Machine for Gaming <a class=\"header-anchor\" href=\"#a-dedicated-machine-for-gaming\">#</a></h3>\n<p>I work on my laptop. Having a dedicated machine makes gaming easier and puts me\nin the right mood for it. It’s portable but also easy to dock to a TV.</p>\n<h2 id=\"downsides\" tabindex=\"-1\">Downsides <a class=\"header-anchor\" href=\"#downsides\">#</a></h2>\n<p>But the Deck isn’t perfect. It’s still clearly a work in progress, I’ve found\nquite a few bugs in the Steam UI while using it. Valve releases updates pretty\nregularly, and all the bugs I’ve found have since been fixed.</p>\n<p>Another sticking point is that it’s not very clear what resolutions are being\nused when using an external display, but since I started writing this post Valve\nhas released an update to fix this.</p>\n<p>The Deck produces quite a lot of heat when playing demanding games, but this\ndoesn’t actually matter much as it doesn’t appear to be throttling or causing\nissues.</p>\n<p>The battery life varies depending on the game but generally tends to be from\n2.5 hours - 5 hours. This is fine for playing on the go, as I don’t tend to be\non public transport or a cafe for long, and at home I tend to play docked to my\nTV and charging.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The Steam Deck is the perfect gaming device for me. It’s open, flexible, and fun\nto use. Even if you don’t use Linux, Valve’s work for the Deck will benefit all\nLinux gamers. It’s truly the year of Linux on the <s>desktop</s> bus.</p>\n","url":"https://blog.rubenwardy.com/2022/10/14/steam-deck-is-awesome/","summary":"My Steam Deck arrived two months ago, and I've been having a blast ever since.","date_published":"2022-10-14T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/AuELbiV7yO-540.png","tags":["steamdeck","linux","reviews","opinion"]},{"id":"/2022/07/21/renewed-tab/","language":"en","title":"Why I created Renewed Tab, my New Tab extension","content_html":"<p>I created <a href=\"https://renewedtab.com/\">Renewed Tab</a> last year to fulfil my personal\nneeds, and have since expanded on it based on user feedback, focusing on rich\nwidgets, a clean user experience, and customisation.</p>\n<p>I wrote\n<a href=\"https://renewedtab.com/blog/2022/07/21/why-I-created-renewed-tab/\">an article on the Renewed Tab blog</a>\nwhere I explained what led me to create a New Tab extension and what the\ndesign requirements were.</p>\n","url":"https://blog.rubenwardy.com/2022/07/21/renewed-tab/","summary":"I wrote an article on the Renewed Tab blog where I explained what led me to create a New Tab extension and what the design requirements were.","date_published":"2022-07-21T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/BgkFRB4RA9-540.png","tags":["projects"]},{"id":"/2022/07/17/game-ai-for-colonists/","language":"en","title":"Creating worker NPCs using behavior trees","content_html":"<p>I’m a huge fan of <a href=\"https://rimworldgame.com/\">RimWorld</a>, a base building game\nwhere you manage a group of colonists. Rather than directly controlling the\ncolonists, you place blueprints for buildings, mark trees for cutting, and\nanimals for hunting. NPCs will then decide what to do automatically, based on\ntheir skills and priorities.</p>\n<p>I’ve made two games recently with similar mechanics. The first was\n<a href=\"/2025/02/12/rvwp/\">Ruben’s Virtual World Project (RVWP)</a>, a hybrid\nbasebuilder/topdown shooter. The second was\n<a href=\"/2022/03/19/ld48-tin-mining/\">Tin Mining</a>, a mining sim created as an entry to\nLudum Dare 48. Both of these games allowed placing building plans that NPC\nworkers would then build out.</p>\n<p>In this article, I will explain how I implemented the NPC AIs, and the problems\nI faced.</p>\n<h2 id=\"choosing-a-game-ai-framework\" tabindex=\"-1\">Choosing a Game AI framework <a class=\"header-anchor\" href=\"#choosing-a-game-ai-framework\">#</a></h2>\n<h3 id=\"state-machines\" tabindex=\"-1\">State Machines <a class=\"header-anchor\" href=\"#state-machines\">#</a></h3>\n<p>A common way to implement game AI is to use a finite state machine. Different\nthings an NPC could do would be modelled as states, and the NPC would transition\nbetween states based on conditions.</p>\n<figure class=\"undefined\"><a href=\"/static/img/tzA3MR9Bix-358.svg\"><picture><source type=\"image/webp\" srcset=\"/static/img/VR-Cl38wXf-540.webp 540w\"><img src=\"/static/img/VR-Cl38wXf-358.svg\" alt=\"An example of a state machine\" loading=\"lazy\" decoding=\"async\" width=\"358\" height=\"207\"></picture></a><figcaption>An example of a state machine</figcaption></figure>\n<p>One problem with state machines is that you need to program and design for all\nthe transitions. What if an NPC dies whilst performing work? It shouldn’t keep\nworking like a zombie until it’s finished.</p>\n<p>State machines work very well for simple behaviour, like that of an animal or\na dumb guard. But my NPCs need to be able to perform complex work, often with\nmultiple steps. The states would end up being pretty complicated.\nI wanted a more capable framework.</p>\n<p>Another concern was reuse. Lots of work involves the same actions - moving to a\nlocation, picking up an object, running an animation. It’s possible for states\nto reuse behaviour using utility functions, but this can be a bit painful.\nI wanted my game AI framework to make reuse as easy as possible.</p>\n<p>After researching game AI, the obvious choice for me was behavior trees.</p>\n<h3 id=\"behavior-trees-behaviour-trees\" tabindex=\"-1\">Behavior Trees / Behaviour Trees <a class=\"header-anchor\" href=\"#behavior-trees-behaviour-trees\">#</a></h3>\n<p>Explaining Behavior Trees in full is a bit out of scope for this article;\nI highly recommend reading\n<a href=\"https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work\">“Behavior trees for AI: How they work”</a>\nby Chris Simpson.\nHowever, I will try to explain the basics.</p>\n<p>Behaviour trees allow you to control an NPC’s decision making by combining\nreusable nodes rather than creating new states. They work best when the nodes\nare small and specific, for example, you might have nodes to check conditions or\nwalk to a position.</p>\n<p>Behaviour trees are basically a programming language in themselves, but for game\nAI. Execution starts at the top of the tree, and then works downwards based on\nthe rules of different nodes. A node is either running, succeed, or failed.</p>\n<figure class=\"undefined\"><a href=\"/static/img/0bObbIgJWS-448.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/MvgcjKcxIa-448.webp 448w\"><img src=\"/static/img/MvgcjKcxIa-448.png\" alt=\"A behaviour tree to make the NPC walk to a random position. Created using <a href=&quot;https://github.com/pruttned/owl-bt&quot;>owl-bt</a>, a browser-based BT editor.\" loading=\"lazy\" decoding=\"async\" width=\"448\" height=\"186\"></picture></a><figcaption>A behaviour tree to make the NPC walk to a random position. Created using <a href=\"https://github.com/pruttned/owl-bt\">owl-bt</a>, a browser-based BT editor.</figcaption></figure>\n<p>In the above tree, there is a Sequence node with two children. Sequences will\nrun each child one after another, until either a child fails or they have all\nsucceeded. The first child finds a random position, and writes it to a variable\ncalled <code>$target</code>. The second child walks the NPC to that position. If the NPC\nsuccessfully finds a position and walks to it, then the sequence succeeds. If\neither child fails, for example if there is something blocking the path, then\nthe sequence will also fail.</p>\n<p>To implement logic, you can use decorators to check conditions. If the\ncondition is true, execution continues to the decorator’s child. if the\ncondition is false, the decorator marks itself as failed and returns to the\nparent. This is very powerful when combined with a selector, a node that runs\nits children until the first succeeds.</p>\n<figure class=\"undefined\"><a href=\"/static/img/8f04VuaM1N-716.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/c6RGS7FtCR-540.webp 540w\"><img src=\"/static/img/c6RGS7FtCR-540.png\" alt=\"A behaviour tree to make the NPC do work, but prioritise dying or sleeping based on stats.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"293\"></picture></a><figcaption>A behaviour tree to make the NPC do work, but prioritise dying or sleeping based on stats.</figcaption></figure>\n<p>The above tree will check whether the NPC’s needs, such as health and energy,\nbefore doing work. This will be checked every time the tree updates, which\nmakes it possible for death or low energy to interrupt work.</p>\n<p>It’s possible for behavior trees to include other behavior trees. The\n“FindWork” node above does this in order to perform work; each work type has a\ntree. Here’s the tree for construction work:</p>\n<figure class=\"undefined\"><a href=\"/static/img/1DL2mKP6Uq-777.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Uq9orLBiq1-540.webp 540w\"><img src=\"/static/img/Uq9orLBiq1-540.png\" alt=\"Tree for construction work. &quot;Place Blueprint&quot; replaces the player order with a building plan, and the &quot;Work&quot; node plays an animation whilst incrementing a loading bar.\n\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"255\"></picture></a><figcaption>Tree for construction work. \"Place Blueprint\" replaces the player order with a building plan, and the \"Work\" node plays an animation whilst incrementing a loading bar.\n</figcaption></figure>\n<p>I implemented behavior trees in Tin Mining using the\n<a href=\"https://godotengine.org/asset-library/asset/837\">Behavior Tree</a> Godot\nplugin. I wrote my own implementation for RVWP as I couldn’t find a good Lua\nimplementation, it was fairly easy to implement.</p>\n <figure class=\"undefined\"><a href=\"/static/img/F7tGriAsaF-274.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Bfb8jqEBnC-274.webp 274w\"><img src=\"/static/img/Bfb8jqEBnC-274.png\" alt=\"A behaviour tree in the Godot Engine for moving items\" loading=\"lazy\" decoding=\"async\" width=\"274\" height=\"161\"></picture></a><figcaption>A behaviour tree in the Godot Engine for moving items</figcaption></figure>\n<h3 id=\"goal-oriented-action-planning-goap\" tabindex=\"-1\">Goal-Oriented Action Planning (GOAP) <a class=\"header-anchor\" href=\"#goal-oriented-action-planning-goap\">#</a></h3>\n<p>Just a quick mention of another framework that I considered.\n<a href=\"https://gamedevelopment.tutsplus.com/tutorials/goal-oriented-action-planning-for-a-smarter-ai--cms-20793\">GOAP</a>\nis an AI system that allows NPCs to work out how to achieve a goal based on\npossible actions. For example, the NPC knows that building a house requires\nwood, and cutting trees makes wood. GOAP allows it to put these two things\ntogether, and cut trees to get wood to build a house.</p>\n<p>I think this isn’t necessarily mutually exclusive to behavior trees, it\noperates at a higher level. You may use GOAP to decide what actions to do, and\nthen implement those actions using behavior trees.</p>\n<h2 id=\"work-manager\" tabindex=\"-1\">Work Manager <a class=\"header-anchor\" href=\"#work-manager\">#</a></h2>\n<h3 id=\"allocating-work\" tabindex=\"-1\">Allocating Work <a class=\"header-anchor\" href=\"#allocating-work\">#</a></h3>\n<p>Now that we have a framework for performing work, we need a way to decide what\nwork an NPC should pick up next.</p>\n<p>When a worker isn’t currently working on a task, it periodically asks the\nWork Manager for an available task. The Work Manager allocates tasks based on\ndistance and a heuristic weighting of the task. For example, moving dropped\nitems to the stockpile is weighted higher than mining or building, to avoid\nitems building up and clogging the walkways.</p>\n<p>In order to avoid NPCs working on the same thing, the Work Manager has a\nreservation system that allows NPCs to lock a tile, entity, or piece of work.</p>\n<h3 id=\"reachability\" tabindex=\"-1\">Reachability <a class=\"header-anchor\" href=\"#reachability\">#</a></h3>\n<p>It’s important that NPCs are only allocated to work that they can reach - this\nis called a reachability check, and is typically implemented using pathfinding.\nPathfinding tends to be quite expensive, but there are some quick checks that\ncan skip the effort in a lot of cases. My Tin Mining game uses a graph to\nrepresent all possible paths; if a tile isn’t walkable, it’s not on the graph.\nThis means that you can check whether work is probably unreachable by seeing\nif it’s on the graph, an <code>O(1)</code> check.</p>\n<figure class=\"undefined\"><a href=\"/static/img/0ZOaKd8uOQ-1182.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/FhW3MpsJHq-540.webp 540w\"><img src=\"/static/img/FhW3MpsJHq-540.png\" alt=\"Tin Mining's pathfinding graph\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"448\"></picture></a><figcaption>Tin Mining's pathfinding graph</figcaption></figure>\n<p>Pathfinding is still needed because it’s possible to have multiple disconnected\nsubgraphs on the map. Pathfinding to every possible work would be quite expensive,\nso I defer pathfinding until after the work is allocated.</p>\n<p>One problem with this approach is that it doesn’t know how close the work\nreally is to the NPC. The NPC might be three tiles from the work but 100 tiles\nto walk there due to obstacles in the way. A future improvement will be to use\nthe actual walking distance rather than the direct distance.</p>\n<h3 id=\"work-theft\" tabindex=\"-1\">Work theft <a class=\"header-anchor\" href=\"#work-theft\">#</a></h3>\n<p>When an NPC finishes mining a tile, it often reveals more tiles to be mined.\nIn the time between the NPC mining the tile and finishing the work, another NPC\nmay have been allocated to the neighbouring tile. This results in a lot of\ninefficiency, especially if the other worker is far away.</p>\n<figure class=\"undefined\"><a href=\"/static/img/9iZpPBVStF-700.webp\"><img src=\"/static/img/gW7bOdHkbC-700.webp\" alt=\"Two NPCs that keep stealing each other's jobs\" loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"700\"></a><figcaption>Two NPCs that keep stealing each other's jobs</figcaption></figure>\n<p>At this time, I don’t have a good fix for this due to the simple nature of my\nwork allocator. My workaround for the meantime is to avoid gaps between the tile\nbeing mined and the worker looking for work - but this doesn’t work with\nmultiple-step work, such as digging a tile and then placing a ladder.</p>\n<p>In the future, I’d like to implement a global work allocator algorithm. Instead\nof considering each worker’s request independently, it should keep track of idle\nworkers in each frame and allocate them all in one go.</p>\n<p>Another option would be to add some form of work queue or reservation. This is\nhow RimWorld resolves this problem.</p>\n<h3 id=\"adjusting-priorities\" tabindex=\"-1\">Adjusting Priorities <a class=\"header-anchor\" href=\"#adjusting-priorities\">#</a></h3>\n<p>Because the Work Manager uses a heuristic to allocate work, it’s possible to\nchange the heuristic to change how work is allocated.</p>\n<p>In my tin mining game, I was having an issue where the workers would keep mining\ntiles and leave all the rubble and ore on the ground. Ideally, workers mine for\na bit and then haul items back to the surface.</p>\n<p>The first change I made to fix this was to make the dropped entities appear\n20% closer than minable tiles, but also add an offset of 3 tiles so that\nworkers prioritise close tiles. This mostly worked, but when mining long\nsections you can still end up with a lot of dropped items.</p>\n<p>The second change was to make that 20% multiplier vary based on how many dropped\nitems there are. When there are not many dropped items, hauling items is a\nlower priority. The more dropped items there are per worker, the higher the\npriority becomes.</p>\n<pre class=\"language-py\"><code class=\"language-py\"><span class=\"token comment\"># Calculate multiplier based on number of dropped items</span>\ntargetDroppedItems <span class=\"token operator\">=</span> <span class=\"token builtin\">min</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span> <span class=\"token operator\">*</span> numberOfWorkers<span class=\"token punctuation\">,</span> <span class=\"token number\">200</span><span class=\"token punctuation\">)</span>\ndroppedItemsMultiplier <span class=\"token operator\">=</span> <span class=\"token number\">0.95</span>\n<span class=\"token keyword\">if</span> <span class=\"token builtin\">len</span><span class=\"token punctuation\">(</span>droppedItems<span class=\"token punctuation\">)</span> <span class=\"token operator\">></span> <span class=\"token number\">3</span><span class=\"token operator\">*</span>targetDroppedItems<span class=\"token punctuation\">:</span>\n    droppedItemsMultiplier <span class=\"token operator\">=</span> <span class=\"token number\">0.4</span>\n<span class=\"token keyword\">elif</span> <span class=\"token builtin\">len</span><span class=\"token punctuation\">(</span>droppedItems<span class=\"token punctuation\">)</span> <span class=\"token operator\">></span> <span class=\"token number\">2</span><span class=\"token operator\">*</span>targetDroppedItems<span class=\"token punctuation\">:</span>\n    droppedItemsMultiplier <span class=\"token operator\">=</span> <span class=\"token number\">0.65</span>\n<span class=\"token keyword\">elif</span> <span class=\"token builtin\">len</span><span class=\"token punctuation\">(</span>droppedItems<span class=\"token punctuation\">)</span> <span class=\"token operator\">></span> <span class=\"token number\">1</span><span class=\"token operator\">*</span>targetDroppedItems<span class=\"token punctuation\">:</span>\n    droppedItemsMultiplier <span class=\"token operator\">=</span> <span class=\"token number\">0.8</span>\n\n<span class=\"token comment\"># Calculate weight based on distance, the multiplier,</span>\n<span class=\"token comment\"># and an offset to prioritise nearby tiles</span>\nweight <span class=\"token operator\">=</span> droppedItemsMultiplier <span class=\"token operator\">*</span> \\\n    worker<span class=\"token punctuation\">.</span>position<span class=\"token punctuation\">.</span>distance_squared_to<span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">.</span>global_position<span class=\"token punctuation\">)</span> <span class=\"token operator\">+</span> <span class=\"token number\">3</span><span class=\"token operator\">*</span><span class=\"token number\">3</span></code></pre>\n<h2 id=\"debugging\" tabindex=\"-1\">Debugging <a class=\"header-anchor\" href=\"#debugging\">#</a></h2>\n<p>One of the hardest parts of designing complex systems is making it easy to\ndebug. You want to know what an NPC is ‘thinking’, and be able to trace why it\ndid something at a certain time. I use a combination of logging and UI debug\ntools to do this.</p>\n<p>I have UI debug tools for the pathfinder, for inspecting work and locks on a\ntile, for showing the NPC behavior tree and current work, and more.</p>\n<p>RVWP has an immediate mode based debug API that allows adding lines and labels\nto the world. This is very useful when designing game AIs, and can be seen in\nthe RVWP animation in the above section.</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">local</span> debug <span class=\"token operator\">=</span> rvwp<span class=\"token punctuation\">.</span><span class=\"token function\">get_debug</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\ndebug<span class=\"token punctuation\">:</span><span class=\"token function\">draw_line</span><span class=\"token punctuation\">(</span>from<span class=\"token punctuation\">,</span> next_pos<span class=\"token punctuation\">,</span> <span class=\"token string\">\"#fff\"</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">for</span> i<span class=\"token operator\">=</span>self<span class=\"token punctuation\">.</span>path_i <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">#</span>self<span class=\"token punctuation\">.</span>path <span class=\"token keyword\">do</span>\n    debug<span class=\"token punctuation\">:</span><span class=\"token function\">draw_line</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">[</span>i <span class=\"token operator\">-</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> self<span class=\"token punctuation\">.</span>path<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"#999\"</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span></code></pre>\n<h2 id=\"so-many-bugs\" tabindex=\"-1\">So many bugs <a class=\"header-anchor\" href=\"#so-many-bugs\">#</a></h2>\n<p>Creating complex systems from simple rules is a great way to get a lot of random\nbugs.</p>\n<p>Running the pathfinder every frame would be expensive, so instead paths are\ncached by the NPC. This resulted in NPCs not being aware of map changes, causing\nthem to fall down holes or get stuck behind new walls. The fix was to validate\nthe cached path against the navigation graph whilst moving.</p>\n <figure class=\"undefined\"><a href=\"/static/img/rqBOfWwQWK-696.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/UECPtCLvFm-540.webp 540w\"><img src=\"/static/img/UECPtCLvFm-540.png\" alt=\"Workers falling down a hole that was created after pathfinding\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"295\"></picture></a><figcaption>Workers falling down a hole that was created after pathfinding</figcaption></figure>\n<p>Another problem I kept running into were NPCs just doing nothing. One of the\ntimes this happened was because NPCs kept being allocated to work that would\nfail, perhaps it wasn’t reachable or wasn’t possible in some other way, Whilst I\ncould make the work manager check every precondition, it wouldn’t be very\nflexible. I’d rather preconditions be implemented by decorators on the behavior\ntree for each work type. My solution was to introduce failure lock outs: if a\npiece of work fails, the NPC won’t retry it for 10 seconds.</p>\n<h2 id=\"rimworld-regions\" tabindex=\"-1\">RimWorld Regions <a class=\"header-anchor\" href=\"#rimworld-regions\">#</a></h2>\n<p>The task of finding the nearest work by walking distance can be expensive.\nRimWorld has a fairly clever algorithm for doing this which I may investigate\nin the future.</p>\n<p>RimWorld uses a system of “regions” to make looking for work based on walking\ndistance super fast. Regions are essentially a higher-level pathfinding system -\ninstead of pathfinding based on nodes, it groups the map into regions at most\n16x16 tiles in size, but further divided by walls. It remembers how regions are\nconnected. To find the closest work, you can iterate through the current and\nnearby regions. The developer of RimWorld made\n<a href=\"https://www.youtube.com/watch?v=RMBQn_sg7DA\">an excellent video explaining regions</a>,\nI highly recommend watching it.</p>\n<figure class=\"undefined\"><a href=\"/static/img/n6HAYv4Xdv-926.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/PvC9R6TMcG-540.webp 540w\"><img src=\"/static/img/PvC9R6TMcG-540.png\" alt=\"RimWorld's <a href=&quot;https://www.youtube.com/watch?v=RMBQn_sg7DA&quot;>Region System</a>. Regions are at most 16x16 tiles, and then subdivided by walls. Connections between regions are stored.\n\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"408\"></picture></a><figcaption>RimWorld's <a href=\"https://www.youtube.com/watch?v=RMBQn_sg7DA\">Region System</a>. Regions are at most 16x16 tiles, and then subdivided by walls. Connections between regions are stored.\n</figcaption></figure>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>My system is surprisingly effective despite being made out of simple rules and\ncomponents. While in the future I’ll probably implement more complicated work\nallocation algorithms and a region system, it works well enough for now, allowing\nme to focus on implementing other systems and game play mechanics.</p>\n<h2 id=\"sources\" tabindex=\"-1\">Sources <a class=\"header-anchor\" href=\"#sources\">#</a></h2>\n<ul>\n<li><a href=\"https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work\">Behavior trees for AI: How they work</a></li>\n<li><a href=\"https://www.youtube.com/watch?v=RMBQn_sg7DA\">RimWorld Technology - Region System</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2022/07/17/game-ai-for-colonists/","summary":"A couple of my games have had NPCs that can build and perform work directed by the player, similar to RimWorld. In this article, I will explain how I implemented this.","date_published":"2022-07-17T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/YtFLC2_vHR-540.png","tags":["tutorials","gamedev","rvwp"]},{"id":"/2022/06/22/gpt-3-minetest-modding/","language":"en","title":"ChatGPT: An AI that can write Minetest mods... kinda","content_html":"<p class=\"alert is-warning\">\n\tThis article was written at a time when LLM AIs were brand new.\n\tAs such, this post is very outdated and does not represent my current\n\topinions.\n</p>\n<p>OpenAI’s <a href=\"https://en.wikipedia.org/wiki/GPT-3\">GPT-3</a> is a powerful new\nArtificial Intelligence model that uses deep learning to produce human-like\ntext. It was trained on a large body of text, with sources including websites,\nWikipedia, and books. It doesn’t just understand natural language, it can also\nwork with programming languages.</p>\n<p>This topic is especially relevant with the recent introduction of\n<a href=\"https://github.com/features/copilot\">GitHub Copilot</a>.\nCopilot is an “AI pair programmer” for your IDE that suggests code and entire\nnew functions. It’s based on same technology as GPT-3, but with a model derived\nfrom GPT-3 and optimised for code called Codex. This article will use GPT-3 and\nCodex, as I wasn’t able to get GitHub Copilot, but the results will be\nidentical.</p>\n<p>In this article, I will ask GPT-3 to write Minetest code and explore how much it\nknows about modding, creating simple and advanced Minetest mods. I will finish\nby using it to convert Minecraft mods to Minetest.</p>\n<p>So, what is <a href=\"https://www.minetest.net/\">Minetest</a>? Let’s ask GPT-3 to explain:</p>\n<p class=\"prompt\">\nExplain what Minetest is and what its benefits are\n</p>\n<blockquote>\n<p>Minetest is a free and open-source voxel game engine with easy modding and\ngame creation capabilities. It has a strong emphasis on community-created\ncontent and user-friendliness. Some of Minetest’s benefits include being able\nto run on a wide variety of operating systems and hardware, having a fully\ncustomizable game engine, and being able to create and share mods and other\ncontent easily.</p>\n</blockquote>\n<p>And how is GPT-3 able to write Minetest code?</p>\n<p class=\"prompt\">\nExplain how GPT-3 knows and is able to write Minetest code\n</p>\n<blockquote>\n<p>GPT-3 has been trained on a large number of code examples, including code for\nMinetest. As a result, it is able to identify patterns in the code and\ngenerate new code that is similar to the examples it has seen.</p>\n</blockquote>\n<p>GPT-3 has a random element, and so can generate a different answer each time.\nThis is expressed as temperature - the higher the <em>temperature</em>, the more\nrandomness and the more creative the answers will be. But higher temperatures\nare also less accurate.</p>\n<p>A common bias when people assess the output of GPT-3 is to select the best\nanswers. I’ll be using the first answer in most cases, and will try to keep\ntemperature low.</p>\n<h2 id=\"generating-simple-minetest-mods\" tabindex=\"-1\">Generating simple Minetest mods <a class=\"header-anchor\" href=\"#generating-simple-minetest-mods\">#</a></h2>\n<h3 id=\"registering-a-node\" tabindex=\"-1\">Registering a node <a class=\"header-anchor\" href=\"#registering-a-node\">#</a></h3>\n<p>A node in Minetest is a position in the 3d grid, it’s known as a Block in\nother games. For example, a node could be air, grass, dirt, or something else.</p>\n<p>Most Minetest modding tutorials start with registering a type of node, which\nmeans that there will be a lot of documentation on the Internet about doing\nthis. It seems likely that some of this information found its way into GPT-3’s\ntraining data.</p>\n<p class=\"prompt\">\nWrite Lua code that adds a node in Minetest\n</p>\n<blockquote class=\"my-0\">\nWrite Lua code that adds a node in Minetest<mark>'s world</mark>\n</blockquote>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">add_node</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>x<span class=\"token operator\">=</span><span class=\"token number\">1</span><span class=\"token punctuation\">,</span> y<span class=\"token operator\">=</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> z<span class=\"token operator\">=</span><span class=\"token number\">3</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>name<span class=\"token operator\">=</span><span class=\"token string\">\"default:dirt\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>Huh. It modified the prompt to be about adding a node to Minetest’s world,\nrather than registering a node. I’m using GPT-3 in autocomplete mode here, where\nit attempts to finish the prompt you give it. In this situation, it decided that\nchanging the request was the more likely completion. One way to fix this is by\nadding a full stop.</p>\n<p>Running the prompt again resulted in a valid node registration, so it can work\nout the meaning, but not very reliably. It doesn’t help that “add” is a bit\nambiguous. I changed “adds” to “registers” to be a bit more specific:</p>\n<p class=\"prompt\">\nWrite Lua code that registers a node in Minetest\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"my_node\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"My Node\"</span><span class=\"token punctuation\">,</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"my_node.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    groups <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>cracky<span class=\"token operator\">=</span><span class=\"token number\">3</span><span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>OK, cool it can do that. The node name doesn’t follow conventions, but that’s\nforgivable.</p>\n<h3 id=\"a-node-that-quacks\" tabindex=\"-1\">A node that quacks <a class=\"header-anchor\" href=\"#a-node-that-quacks\">#</a></h3>\n<p>But how much does it actually understand it? This code is very simple, and it\nwould be easy for it to just echo it from tutorials. Let’s change the spec a\nlittle bit, to make it more unique:</p>\n<p class=\"prompt\">\nWrite Lua code that registers a node in Minetest that says quack when you punch it\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"quack\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Quack\"</span><span class=\"token punctuation\">,</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"default_wood.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    on_punch <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> node<span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">,</span> pointed_thing<span class=\"token punctuation\">)</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Quack!\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/wpbqVWJIbQ-640.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/YccDTlAZB0-540.webp 540w\"><img src=\"/static/img/YccDTlAZB0-540.png\" alt=\"The quack node generated by GPT-3.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"291\"></picture></a><figcaption>The quack node generated by GPT-3.</figcaption></figure>\n<p>That’s cool. It’s able to understand that “say” means a chat message, and has\nalso inferred a node name and description based on the prompt. The node name\nstill doesn’t match the naming conventions, but the description is correctly in\ntitle case. GPT-3 is designed for finding patterns, so this isn’t too\nsurprising.</p>\n<h3 id=\"duck-goose-node\" tabindex=\"-1\">Duck-Goose node <a class=\"header-anchor\" href=\"#duck-goose-node\">#</a></h3>\n<p>This is still quite a simple example, let’s see how it does with a more\ncomplicated problem:</p>\n<p class=\"prompt\">\nWrite Lua code that registers a node in Minetest that says duck when you punch\nit once, and goose when you punch it twice\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token keyword\">function</span> <span class=\"token function\">duck_goose</span><span class=\"token punctuation\">(</span>self<span class=\"token punctuation\">,</span> clicker<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> clicker <span class=\"token keyword\">or</span> <span class=\"token keyword\">not</span> clicker<span class=\"token punctuation\">:</span><span class=\"token function\">is_player</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">if</span> self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">get_luaentity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>ducks <span class=\"token operator\">==</span> <span class=\"token number\">1</span> <span class=\"token keyword\">then</span>\n        self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">set_properties</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n            textures <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"goose.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">get_luaentity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>ducks <span class=\"token operator\">=</span> <span class=\"token number\">2</span>\n    <span class=\"token keyword\">elseif</span> self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">get_luaentity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>ducks <span class=\"token operator\">==</span> <span class=\"token number\">2</span> <span class=\"token keyword\">then</span>\n        self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">set_properties</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n            textures <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"duck.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n        self<span class=\"token punctuation\">.</span>object<span class=\"token punctuation\">:</span><span class=\"token function\">get_luaentity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>ducks <span class=\"token operator\">=</span> <span class=\"token number\">1</span>\n    <span class=\"token keyword\">end</span>\n<span class=\"token keyword\">end</span>\n\nminetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"example:duck\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Duck/Goose\"</span><span class=\"token punctuation\">,</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"duck.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    groups <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>choppy<span class=\"token operator\">=</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span>dig_immediate<span class=\"token operator\">=</span><span class=\"token number\">2</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    on_punch <span class=\"token operator\">=</span> duck_goose<span class=\"token punctuation\">,</span>\n    on_rightclick <span class=\"token operator\">=</span> duck_goose<span class=\"token punctuation\">,</span>\n    ducks <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>OK, this is interesting: it’s cargo-culting on steroids.\n<a href=\"https://en.wikipedia.org/wiki/Cargo_cult_programming\">Cargo-culting</a> is when\na programmer copies and combines code without understanding it. It’s mixed up\nthe entity and node APIs big time, and has also gotten “duck” and “goose” the\nwrong way around. It’s also changing the texture rather than saying something.\nTheoretically, the code almost makes sense though.</p>\n<p>A big part of the problem is <em>how</em> the prompt is phrased. Making the prompt a\nlot less ambiguous makes a big difference, unsurprisingly. I also switched to\nEdit mode, which I found worked a bit better.</p>\n<p class=\"prompt\">\n<b>Write this into Lua code for Minetest</b><br>\nA node that alternatives between saying duck and goose when punched\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"duck_goose:duck_goose\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Duck Goose\"</span><span class=\"token punctuation\">,</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"duck_goose.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    groups <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>cracky<span class=\"token operator\">=</span><span class=\"token number\">3</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    on_punch <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> node<span class=\"token punctuation\">,</span> puncher<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">local</span> meta <span class=\"token operator\">=</span> minetest<span class=\"token punctuation\">.</span><span class=\"token function\">get_meta</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">local</span> say <span class=\"token operator\">=</span> meta<span class=\"token punctuation\">:</span><span class=\"token function\">get_string</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"say\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> say <span class=\"token operator\">==</span> <span class=\"token string\">\"duck\"</span> <span class=\"token keyword\">then</span>\n            minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_all</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Goose!\"</span><span class=\"token punctuation\">)</span>\n            meta<span class=\"token punctuation\">:</span><span class=\"token function\">set_string</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"say\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"goose\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">else</span>\n            minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_all</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Duck!\"</span><span class=\"token punctuation\">)</span>\n            meta<span class=\"token punctuation\">:</span><span class=\"token function\">set_string</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"say\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"duck\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>This works, although isn’t entirely optimal. It would be better to use an\ninteger rather than a string to keep track.</p>\n<p>And yes, I did make a typo in the prompt.</p>\n<h2 id=\"explaining-code\" tabindex=\"-1\">Explaining code <a class=\"header-anchor\" href=\"#explaining-code\">#</a></h2>\n<h3 id=\"explaining-the-quack-node\" tabindex=\"-1\">Explaining the Quack node <a class=\"header-anchor\" href=\"#explaining-the-quack-node\">#</a></h3>\n<p>It would be nice to find out just how well GPT-3 can understand the code. GPT-3\nis very good at noticing patterns and regurgitating, but is it able to explain\nwhat the code is doing?</p>\n<p>Using edit mode, I asked it to add comments to the first Quack node:</p>\n<p class=\"prompt\">\nAdd comments to the code<br>\n<i class=\"snip\">&lt;Quack node code&gt;</i>\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token comment\">-- create a duck block</span>\nminetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"quack\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">-- set the description to 'Quack'</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Quack\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token comment\">-- use the default wood texture</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"default_wood.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token comment\">-- when the player punches the block, the chat will send them the string 'Quack!'</span>\n    on_punch <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> node<span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">,</span> pointed_thing<span class=\"token punctuation\">)</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Quack!\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>The results of this prompt varied quite a lot, from detailed comments to a\nsingle comment for the code. The above was the result of the first run, but in\nother runs GPT-3 was wrong (saying the node made a duck sound) or just unhelpful\n(writing “this is a comment”).</p>\n<p>Next, I thought I’d try getting it to explain the code as prose:</p>\n<p class=\"prompt\">\nExplain what this code does<br>\n<i class=\"snip\">&lt;Quack node code&gt;</i>\n</p>\n<blockquote>\n<p>This code registers a new node type, “quack”, which has the default wood\ntexture and sends the player the message “Quack!” when punched</p>\n</blockquote>\n<p>Full marks there, I guess.</p>\n<h3 id=\"explaining-actual-mod-code\" tabindex=\"-1\">Explaining actual mod code <a class=\"header-anchor\" href=\"#explaining-actual-mod-code\">#</a></h3>\n<p>The Quack node is a fairly simple example, exactly the sort of thing you’d find\nin a Minetest tutorial. How well does GPT-3 do on actual mod code?</p>\n<figure class=\"undefined\"><a href=\"/static/img/m-yjvXntFI-640.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/OMZEW6dnKn-540.webp 540w\"><img src=\"/static/img/OMZEW6dnKn-540.png\" alt=\"Units fighting in Conquer, my RTS mod for Minetest.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"289\"></picture></a><figcaption>Units fighting in Conquer, my RTS mod for Minetest.</figcaption></figure>\n<p>I asked GPT-3 to explain the\n<a href=\"https://gitlab.com/rubenwardy/conquer/-/blob/master/src/units/HealthBarEntity.lua\">code</a>\nfor health bars in\n<a href=\"https://content.minetest.net/packages/rubenwardy/conquer/\">Conquer</a>, my RTS\nmod for Minetest.</p>\n<p class=\"prompt\">\nExplain what this code does to a beginner<br>\n<a href=\"https://gitlab.com/rubenwardy/conquer/-/blob/master/src/units/HealthBarEntity.lua\" class=\"snip\">&lt;Conquer code&gt;</a>\n</p>\n<blockquote>\n<p>This code creates an entity called a “healthbar” which is used to show a\nplayer’s health in the game “conquer”. The entity is a sprite, which is an\nimage that is displayed in the game, and it is immortal, meaning it can not be\nkilled. The healthbar is updated based on the health of the player it is\nattached to. If the player’s health changes, the healthbar will change to\nreflect that. If the player dies, the healthbar will be removed.</p>\n</blockquote>\n<p>This is almost entirely correct, the only inaccuracy is that it’s attached to\na unit entity and not a player. There’s absolutely no way it could have known\nthis though, given the code snippet doesn’t mention units at all.</p>\n<h2 id=\"working-with-guis-formspecs\" tabindex=\"-1\">Working with GUIs (Formspecs) <a class=\"header-anchor\" href=\"#working-with-guis-formspecs\">#</a></h2>\n<h3 id=\"generating-a-simple-formspec\" tabindex=\"-1\">Generating a simple formspec <a class=\"header-anchor\" href=\"#generating-a-simple-formspec\">#</a></h3>\n<p>Lua is a fairly common programming language, used by many more games and\nprograms than Minetest. But what about formspecs, a domain-specific language for\ndefining GUIs in Minetest?</p>\n<p class=\"prompt\">\nWrite a Minetest formspec with a field and two buttons\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">form <span class=\"token operator\">=</span> <span class=\"token string\">\"size[8,8]\"</span> <span class=\"token operator\">..</span>\n\n<span class=\"token comment\">-- titlebar</span>\n<span class=\"token string\">\"button_exit[0,0;1,1;exit;X]\"</span> <span class=\"token operator\">..</span>\n\n<span class=\"token comment\">-- text input field</span>\n<span class=\"token string\">\"field[0.5,1.5;7,1;text;Type your text here:;]\"</span> <span class=\"token operator\">..</span>\n\n<span class=\"token comment\">-- buttons</span>\n<span class=\"token string\">\"button[3,3;2,1;button1;Button 1]\"</span> <span class=\"token operator\">..</span>\n<span class=\"token string\">\"button[3,4;2,1;button2;Button 2]\"</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/qcELBTK5FW-684.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Y2N-z6W6hN-540.webp 540w\"><img src=\"/static/img/Y2N-z6W6hN-540.png\" alt=\"A simple formspec generated by GPT-3.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"313\"></picture></a><figcaption>A simple formspec generated by GPT-3.</figcaption></figure>\n<p>It took some creative liberty by adding a close button to the top, but it has\nfulfilled the requirements.</p>\n<h3 id=\"manipulating-formspecs\" tabindex=\"-1\">Manipulating formspecs <a class=\"header-anchor\" href=\"#manipulating-formspecs\">#</a></h3>\n<p>What about formspec code that doesn’t have surrounding text? I provided it with\nthe following code snippet:</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">size<span class=\"token punctuation\">[</span><span class=\"token number\">8</span><span class=\"token punctuation\">,</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span>\nlabel<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">0.1</span><span class=\"token punctuation\">;</span>Hello everyone!<span class=\"token punctuation\">]</span>\nfield<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span><span class=\"token number\">3.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span>name<span class=\"token punctuation\">;</span>Name<span class=\"token punctuation\">;</span><span class=\"token punctuation\">]</span></code></pre>\n<p>I started by wondering if it could detect the language:</p>\n<p class=\"prompt\">\nWrite the name of the language in a comment<br>\n<i class=\"snip\">&lt;formspec code&gt;</i>\n</p>\n<pre class=\"language-c\"><code class=\"language-c\"><span class=\"token comment\">// Minetest Formspec</span>\n<span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span></code></pre>\n<p>That’s cool! Weird that it choose <code>//</code>, given that formspecs are written inside\nLua which uses <code>--</code>. I guess <code>//</code> is more common.</p>\n<p>What about manipulating the formspec?</p>\n<p class=\"prompt\">\nAdd another text box for age twice the width of the name field<br>\n<i class=\"snip\">&lt;formspec code&gt;</i>\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">size<span class=\"token punctuation\">[</span><span class=\"token number\">8</span><span class=\"token punctuation\">,</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span>\nlabel<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">0.1</span><span class=\"token punctuation\">;</span>Hello everyone!<span class=\"token punctuation\">]</span>\nfield<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span><span class=\"token number\">3.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span>name<span class=\"token punctuation\">;</span>Name<span class=\"token punctuation\">;</span><span class=\"token punctuation\">]</span>\nfield<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">2</span><span class=\"token punctuation\">;</span><span class=\"token number\">7</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span>age<span class=\"token punctuation\">;</span>Age<span class=\"token punctuation\">;</span><span class=\"token punctuation\">]</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/e6dAsFhlda-693.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/SQAWk4iWkG-540.webp 540w\"><img src=\"/static/img/SQAWk4iWkG-540.png\" alt=\"GPT-3 added an age field with double the width of Name.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"232\"></picture></a><figcaption>GPT-3 added an age field with double the width of Name.</figcaption></figure>\n<p>That’s correct, it’s also placed it without overlap.\nI used “text box” in the prompt to avoid biasing it by saying “field”.</p>\n<p>Does it understand directions?</p>\n<p class=\"prompt\">\nAdd another field for age to the left of the name field<br>\n<i class=\"snip\">&lt;formspec code&gt;</i>\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">size<span class=\"token punctuation\">[</span><span class=\"token number\">8</span><span class=\"token punctuation\">,</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span>\nlabel<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">0.25</span><span class=\"token punctuation\">;</span>Hello everyone!<span class=\"token punctuation\">]</span>\nfield<span class=\"token punctuation\">[</span><span class=\"token number\">0.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span><span class=\"token number\">1.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span>age<span class=\"token punctuation\">;</span>Age<span class=\"token punctuation\">;</span><span class=\"token punctuation\">]</span>\nfield<span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span><span class=\"token number\">3.5</span><span class=\"token punctuation\">,</span><span class=\"token number\">1</span><span class=\"token punctuation\">;</span>name<span class=\"token punctuation\">;</span>Name<span class=\"token punctuation\">;</span><span class=\"token punctuation\">]</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/igia820Wl7-699.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/gQMm5J7Fd--540.webp 540w\"><img src=\"/static/img/gQMm5J7Fd--540.png\" alt=\"GPT-3 moved the name field and added the Age field to left of Name.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"236\"></picture></a><figcaption>GPT-3 moved the name field and added the Age field to left of Name.</figcaption></figure>\n<p>It even moved the name field out of the way, rather than going into the\nnegatives.</p>\n<h3 id=\"guessing-game\" tabindex=\"-1\">Guessing game <a class=\"header-anchor\" href=\"#guessing-game\">#</a></h3>\n<p>Next, I wondered if I could get it to generate a guessing game formspec, with\na formspec submission callback.</p>\n<p class=\"prompt\">\n<b>Write into Lua as a Minetest mod</b><br>\nCreate a guessing game formspec\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_on_player_receive_fields</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">,</span> formname<span class=\"token punctuation\">,</span> fields<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> formname <span class=\"token operator\">~=</span> <span class=\"token string\">\"guess\"</span> <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">if</span> fields<span class=\"token punctuation\">.</span>quit <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">local</span> guess <span class=\"token operator\">=</span> <span class=\"token function\">tonumber</span><span class=\"token punctuation\">(</span>fields<span class=\"token punctuation\">.</span>guess<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> guess <span class=\"token operator\">==</span> <span class=\"token keyword\">nil</span> <span class=\"token keyword\">then</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Please enter a number.\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">if</span> guess <span class=\"token operator\">&lt;</span> <span class=\"token number\">1</span> <span class=\"token keyword\">or</span> guess <span class=\"token operator\">></span> <span class=\"token number\">100</span> <span class=\"token keyword\">then</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Please enter a number between 1 and 100.\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">if</span> guess <span class=\"token operator\">==</span> <span class=\"token number\">42</span> <span class=\"token keyword\">then</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"You win!\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">else</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Sorry, try again.\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span>\n<span class=\"token keyword\">end</span><span class=\"token punctuation\">)</span>\n\nminetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_chatcommand</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"guess\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    params <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Play a guessing game\"</span><span class=\"token punctuation\">,</span>\n    func <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">,</span> param<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">local</span> player <span class=\"token operator\">=</span> minetest<span class=\"token punctuation\">.</span><span class=\"token function\">get_player_by_name</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> player <span class=\"token operator\">==</span> <span class=\"token keyword\">nil</span> <span class=\"token keyword\">then</span>\n            <span class=\"token comment\">-- just a check to prevent the server crashing</span>\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">false</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Player not found\"</span>\n        <span class=\"token keyword\">end</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">show_formspec</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">,</span> <span class=\"token string\">\"guess\"</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string\">\"size[8,4]\"</span> <span class=\"token operator\">..</span>\n            <span class=\"token string\">\"label[0,0;I'm thinking of a number between 1 and 100.]\"</span> <span class=\"token operator\">..</span>\n            <span class=\"token string\">\"field[2.5,2;3,1;guess;Guess;]\"</span> <span class=\"token operator\">..</span>\n            <span class=\"token string\">\"button_exit[2.5,3;3,1;quit;Quit]\"</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/H39fA2q44Q-702.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/eO0D14Yg6z-540.webp 540w\"><img src=\"/static/img/eO0D14Yg6z-540.png\" alt=\"A guessing game generated by GPT-3. It's missing a submit button to make a guess, but works when this is added.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"292\"></picture></a><figcaption>A guessing game generated by GPT-3. It's missing a submit button to make a guess, but works when this is added.</figcaption></figure>\n<p>OK, now it’s scaring me.</p>\n<p>It’s missing a way to submit the guess - there’s only a quit button. It works if\nyou add this, though.</p>\n<p>I found the “just a check to prevent the server crashing” comment quite\ninteresting. I wondered whether this was taken straight out of Minetest mods.\nA quick <a href=\"https://github.com/search?l=Lua&amp;q=just+a+check+to+prevent+the+server+crashing&amp;type=Code\">search</a>\non GitHub revealed that this exact comment is used quite frequently by mods\nin chat commands.</p>\n<h3 id=\"list-of-fruit-editor\" tabindex=\"-1\">List of fruit editor <a class=\"header-anchor\" href=\"#list-of-fruit-editor\">#</a></h3>\n<p>Guessing games are fairly common, what about something more complicated?</p>\n<p class=\"prompt\">\n<b>Write into Lua as a Minetest mod</b><br>\nCreate a formspec that shows and allows maintaining a list of fruit\n</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_on_player_receive_fields</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">,</span> formname<span class=\"token punctuation\">,</span> fields<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> formname <span class=\"token operator\">~=</span> <span class=\"token string\">\"fruit_list\"</span> <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">if</span> fields<span class=\"token punctuation\">.</span>add <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">local</span> fruit <span class=\"token operator\">=</span> fields<span class=\"token punctuation\">.</span>fruit\n        <span class=\"token keyword\">if</span> fruit <span class=\"token operator\">~=</span> <span class=\"token string\">\"\"</span> <span class=\"token keyword\">then</span>\n            table<span class=\"token punctuation\">.</span><span class=\"token function\">insert</span><span class=\"token punctuation\">(</span>fruit_list<span class=\"token punctuation\">,</span> fruit<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">elseif</span> fields<span class=\"token punctuation\">.</span>remove <span class=\"token keyword\">then</span>\n        <span class=\"token keyword\">local</span> fruit <span class=\"token operator\">=</span> fields<span class=\"token punctuation\">.</span>fruit\n        <span class=\"token keyword\">if</span> fruit <span class=\"token operator\">~=</span> <span class=\"token string\">\"\"</span> <span class=\"token keyword\">then</span>\n            <span class=\"token keyword\">for</span> i<span class=\"token punctuation\">,</span> v <span class=\"token keyword\">in</span> <span class=\"token function\">ipairs</span><span class=\"token punctuation\">(</span>fruit_list<span class=\"token punctuation\">)</span> <span class=\"token keyword\">do</span>\n                <span class=\"token keyword\">if</span> v <span class=\"token operator\">==</span> fruit <span class=\"token keyword\">then</span>\n                    table<span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span>fruit_list<span class=\"token punctuation\">,</span> i<span class=\"token punctuation\">)</span>\n                    <span class=\"token keyword\">break</span>\n                <span class=\"token keyword\">end</span>\n            <span class=\"token keyword\">end</span>\n        <span class=\"token keyword\">end</span>\n    <span class=\"token keyword\">end</span>\n    <span class=\"token function\">show_formspec</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">show_formspec</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">local</span> formspec <span class=\"token operator\">=</span> <span class=\"token string\">\"size[8,9]\"</span> <span class=\"token operator\">..</span>\n        <span class=\"token string\">\"label[0,0;Fruit List]\"</span> <span class=\"token operator\">..</span>\n        <span class=\"token string\">\"field[0.3,1;4,1;fruit;Fruit;]\"</span> <span class=\"token operator\">..</span>\n        <span class=\"token string\">\"button[4.3,0.5;1,1;add;Add]\"</span> <span class=\"token operator\">..</span>\n        <span class=\"token string\">\"button[5.3,0.5;1,1;remove;Remove]\"</span>\n    <span class=\"token keyword\">for</span> i<span class=\"token punctuation\">,</span> v <span class=\"token keyword\">in</span> <span class=\"token function\">ipairs</span><span class=\"token punctuation\">(</span>fruit_list<span class=\"token punctuation\">)</span> <span class=\"token keyword\">do</span>\n        formspec <span class=\"token operator\">=</span> formspec <span class=\"token operator\">..</span> <span class=\"token string\">\"label[0,\"</span> <span class=\"token operator\">..</span> <span class=\"token punctuation\">(</span>i <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">..</span> <span class=\"token string\">\";\"</span> <span class=\"token operator\">..</span> v <span class=\"token operator\">..</span> <span class=\"token string\">\"]\"</span>\n    <span class=\"token keyword\">end</span>\n    minetest<span class=\"token punctuation\">.</span><span class=\"token function\">show_formspec</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"fruit_list\"</span><span class=\"token punctuation\">,</span> formspec<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span>\n\nminetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_chatcommand</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"fruit\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    params <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Show fruit list\"</span><span class=\"token punctuation\">,</span>\n    func <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">,</span> param<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">local</span> player <span class=\"token operator\">=</span> minetest<span class=\"token punctuation\">.</span><span class=\"token function\">get_player_by_name</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">if</span> player <span class=\"token operator\">==</span> <span class=\"token keyword\">nil</span> <span class=\"token keyword\">then</span>\n            <span class=\"token keyword\">return</span> <span class=\"token keyword\">false</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Player not found\"</span>\n        <span class=\"token keyword\">end</span>\n        <span class=\"token function\">show_formspec</span><span class=\"token punctuation\">(</span>player<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">true</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Fruit list shown\"</span>\n    <span class=\"token keyword\">end</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<figure class=\"undefined\"><a href=\"/static/img/4zttYByVsz-693.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/wmiIk-JTDw-540.webp 540w\"><img src=\"/static/img/wmiIk-JTDw-540.png\" alt=\"A fruit list formspec generated by GPT-3, it works.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"300\"></picture></a><figcaption>A fruit list formspec generated by GPT-3, it works.</figcaption></figure>\n<p>Wow. It’s just missing a definition of <code>fruit_list</code>, and should be using\n<code>minetest.formspec_escape</code> to handle the case where a fruit name has special\ncharacters. I think it’s interesting how it went with labels instead of a text\nlist.</p>\n<h2 id=\"minecraft-to-minetest-conversion\" tabindex=\"-1\">Minecraft to Minetest conversion <a class=\"header-anchor\" href=\"#minecraft-to-minetest-conversion\">#</a></h2>\n<p>One thing that could indicate deep understanding would be the ability to convert\nbetween different modding APIs. I found the following hello world example\nin a\n<a href=\"https://medium.com/@programmingfun2017/minecraft-modding-with-forge-tutorial-hello-world-828ece8db9a2\">Minecraft Forge modding tutorial</a>:</p>\n<pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">package</span> <span class=\"token namespace\">minecraftfun</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraft<span class=\"token punctuation\">.</span>util<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">TextComponentString</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraftforge<span class=\"token punctuation\">.</span>event<span class=\"token punctuation\">.</span>world<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">BlockEvent</span><span class=\"token punctuation\">.</span><span class=\"token class-name\">PlaceEvent</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraftforge<span class=\"token punctuation\">.</span>fml<span class=\"token punctuation\">.</span>common<span class=\"token punctuation\">.</span>eventhandler<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">SubscribeEvent</span></span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">BlockPlaceHandler</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token annotation punctuation\">@SubscribeEvent</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">void</span> <span class=\"token function\">onPlaceEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">PlaceEvent</span> event<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        event<span class=\"token punctuation\">.</span><span class=\"token function\">getPlayer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">sendMessage</span><span class=\"token punctuation\">(</span>\n            <span class=\"token keyword\">new</span> <span class=\"token class-name\">TextComponentString</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"You placed a block : \"</span> <span class=\"token operator\">+</span> event<span class=\"token punctuation\">.</span><span class=\"token function\">getPlacedBlock</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getBlock</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getLocalizedName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>The first prompt I tried ended up just changing the package name in the first\nline to <code>minetest</code> rather than rewriting it into Lua.</p>\n<p class=\"prompt\">\nRewrite the following code into Minetest<br>\n<i class=\"snip\">&lt;Java code&gt;</i>\n</p>\n<pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">package</span> <span class=\"token namespace\">minetestfun</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span></code></pre>\n<p>The second time, I explicitly mentioned Lua:</p>\n<p class=\"prompt\">\nWrite into Lua as a Minetest mod<br>\n<i class=\"snip\">&lt;Java code&gt;</i>\n</p>\n<pre class=\"language-java\"><code class=\"language-java\"><span class=\"token operator\">--</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span>\n    <span class=\"token class-name\">This</span> is a <span class=\"token class-name\">Minetest</span> mod that writes into a file when a block is <span class=\"token class-name\"><span class=\"token namespace\">placed<span class=\"token punctuation\">.</span></span>\n    It</span> is written in <span class=\"token class-name\">Lua</span><span class=\"token punctuation\">.</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span>\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraft<span class=\"token punctuation\">.</span>util<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">TextComponentString</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraftforge<span class=\"token punctuation\">.</span>event<span class=\"token punctuation\">.</span>world<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">BlockEvent</span><span class=\"token punctuation\">.</span><span class=\"token class-name\">PlaceEvent</span></span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token import\"><span class=\"token namespace\">net<span class=\"token punctuation\">.</span>minecraftforge<span class=\"token punctuation\">.</span>fml<span class=\"token punctuation\">.</span>common<span class=\"token punctuation\">.</span>eventhandler<span class=\"token punctuation\">.</span></span><span class=\"token class-name\">SubscribeEvent</span></span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">BlockPlaceHandler</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token annotation punctuation\">@SubscribeEvent</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">void</span> <span class=\"token function\">onPlaceEvent</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">PlaceEvent</span> event<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        event<span class=\"token punctuation\">.</span><span class=\"token function\">getPlayer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">sendMessage</span><span class=\"token punctuation\">(</span>\n            <span class=\"token keyword\">new</span> <span class=\"token class-name\">TextComponentString</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"You placed a block : \"</span> <span class=\"token operator\">+</span> event<span class=\"token punctuation\">.</span><span class=\"token function\">getPlacedBlock</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getBlock</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getLocalizedName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>No, adding a comment saying “It is written in Lua” doesn’t make it written in\nLua. The comment about writing to a file is also completely incorrect.</p>\n<p>Next, I went back to saying “Rewrite”:</p>\n<div class=\"prompt\">\n    Rewrite the following into Lua as a Minetest mod<br>\n    <i class=\"snip\">&lt;Java code&gt;</i>\n</div>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_on_placenode</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> newnode<span class=\"token punctuation\">,</span> placer<span class=\"token punctuation\">,</span> oldnode<span class=\"token punctuation\">,</span> itemstack<span class=\"token punctuation\">,</span> pointed_thing<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> placer<span class=\"token punctuation\">:</span><span class=\"token function\">is_player</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">then</span>\n        minetest<span class=\"token punctuation\">.</span><span class=\"token function\">chat_send_player</span><span class=\"token punctuation\">(</span>placer<span class=\"token punctuation\">:</span><span class=\"token function\">get_player_name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"You placed a block : \"</span><span class=\"token operator\">..</span>newnode<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">end</span>\n<span class=\"token keyword\">end</span><span class=\"token punctuation\">)</span></code></pre>\n<p>Woah! That’s exactly what I wanted. It even checks that <code>placer</code> is a player,\nwhich is something that many modders forget to do.</p>\n<p>The only thing it does do wrong is <code>newnode.name</code>. It should get the node’s\ndescription from the node definition, this can either be done using\n<code>ItemStack(newnode.name):get_description()</code> or by looking up the node def from\n<code>minetest.registered_nodes</code>. This mistake is understandable though given that\nthe Java code uses “name” for what Minetest calls “description”.</p>\n<p>As mentioned, GPT-3 includes randomness; it doesn’t always produce the same\nresult. I ran it again, with a lower temperature, and it made up an API function\ncalled <code>send_message</code>:</p>\n<div class=\"prompt\">\n    Rewrite the following into Lua as a Minetest mod<br>\n    <i class=\"snip\">&lt;Java code&gt;</i>\n</div>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_on_placenode</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">,</span> newnode<span class=\"token punctuation\">,</span> placer<span class=\"token punctuation\">,</span> oldnode<span class=\"token punctuation\">,</span> itemstack<span class=\"token punctuation\">,</span> pointed_thing<span class=\"token punctuation\">)</span>\n    placer<span class=\"token punctuation\">:</span><span class=\"token function\">send_message</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"You placed a block: \"</span><span class=\"token operator\">..</span>newnode<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">end</span><span class=\"token punctuation\">)</span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>GPT-3/Codex is surprisingly intelligent and can produce working Minetest mods and\nformspecs. However, it frequently produces results that look like\n<a href=\"https://en.wikipedia.org/wiki/Cargo_cult_programming\">cargo-culting</a> and making\nstuff up, and it can also produce vulnerable code. It also requires effort from\na human to write good prompts and identify problems.</p>\n<p>GitHub’s Copilot uses another AI model from OpenAI called Codex. It’s very\nclosely related to GPT-3 but specialises in code rather than natural language.\nCopilot uses this model in IDEs, such as VSCode, to suggest code.</p>\n<p>I’ll probably look into GitHub Copilot in the future, but I imagine it’ll\nproduce very similar results just with better IDE integration.</p>\n<p>GPT-3/Codex learned how to write Minetest code by reading code on the Internet.\nThis code may or may not be open source, and may or may not be permissively\nlicensed. These models tend to regurgitate code, which leads to license\nlaundering - open source code being turned into proprietary code, without\ncredit. These products benefit from the unpaid labour of the open-source\ncommunity. So whilst this technology is interesting, I’m not sure how much I\nagree with it ethically.</p>\n<p>You can try out GPT-3 and Codex for free on\n<a href=\"https://beta.openai.com/playground\">OpenAI’s website</a>, and\n<a href=\"https://github.com/features/copilot/\">GitHub Copilot</a> is now available\npublicly.</p>\n<h2 id=\"tl-dr\" tabindex=\"-1\">TL;DR <a class=\"header-anchor\" href=\"#tl-dr\">#</a></h2>\n<ul>\n<li>It can state some facts about Minetest</li>\n<li>It can create small mods based on human language descriptions:\n<ul>\n<li><a href=\"#a-node-that-quacks\">A node that says quack when you punch it</a></li>\n<li><a href=\"#duck-goose-node\">A node that alternates between saying duck and goose when punched</a></li>\n<li><a href=\"#guessing-game\">A guessing game GUI</a></li>\n<li><a href=\"#list-of-fruit-editor\">A GUI to manage and view a list of fruit</a></li>\n</ul>\n</li>\n<li>It can <a href=\"#explaining-code\">explain what Minetest code does</a> in comments or prose</li>\n<li>It can understand Minetest’s domain-specific language for GUIs (formspecs)\nand <a href=\"#working-with-guis-formspecs\">can manipulate them</a></li>\n<li>It can <a href=\"#minecraft-to-minetest-conversion\">convert Minecraft Java code into Minetest Lua code</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2022/06/22/gpt-3-minetest-modding/","summary":"An exploration of GPT-3/GitHub Copilot's knowledge of Minetest modding, with node registration, formspec manipulation, mod creation, and Minecraft to Minetest mod conversion.","date_published":"2022-06-22T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/9TbczvRoLI-540.png","tags":["luanti","computer_science","opinion"]},{"id":"/2022/03/24/contentdb/","language":"en","title":"Developing ContentDB","content_html":"<p>In 2018, I had the opportunity to create a\n<a href=\"https://content.minetest.net/\">web app</a> for University coursework, as a solo\nproject. I chose to create a package repository for Luanti, an open-source\nproject I help maintain.</p>\n<p><a href=\"https://www.minetest.net/\">Luanti (formerly Minetest)</a> is an open-source game engine with\nmillions of downloads and thousands of weekly players. The project has a very\nactive modding community, and many available games to run. There was one big\nissue - you had to manually install mods and games by unzipping their files into\na directory. This was a very poor user experience.</p>\n<p>The project aimed to make a website to hold the metadata of different types of\npackages. The website needed to have both an HTML front-end, and a JSON REST API\nto be used by the Minetest Engine and other software. Authors of packages should\nbe able to upload and maintain their packages, and Editors should be able to\nupload and maintain any package.</p>\n<p>The community commonly receives attacks from bots and malicious users, so the\nsystem needed sufficient moderator tools. New users should have any uploads or\nchanges checked before they’re published. There also needed be moderation tools,\nsuch as banning.</p>\n<p>The website needed to be stable and easy-to-use for content creators and users\nalike. This means that uploading packages needed to be as frictionless as\npossible, which calls for interesting features such as importing metadata from\nGitHub and other VCSes.</p>\n<h2 id=\"prior-attempts\" tabindex=\"-1\">Prior Attempts <a class=\"header-anchor\" href=\"#prior-attempts\">#</a></h2>\n<p>Creating a package repository for Minetest was by no means a brand new idea;\nthere had been many prior attempts, a few of which by me.</p>\n<h3 id=\"2012-minetest-extensions\" tabindex=\"-1\">2012: Minetest Extensions <a class=\"header-anchor\" href=\"#2012-minetest-extensions\">#</a></h3>\n<p>I created a PHP website called Minetest Extensions. It used a MySQL\ndatabase and implemented several proprietary APIs required by existing Minetest\npackage manager command-line tools. I wasn’t experienced with back-end\ndevelopment at the time, and so it suffered from bugs and security issues.</p>\n<h3 id=\"2013-minetest-mod-database\" tabindex=\"-1\">2013: Minetest Mod Database <a class=\"header-anchor\" href=\"#2013-minetest-mod-database\">#</a></h3>\n<p>Another user created a Python / Django package repository. It was made\nofficial and hosted by celeron55.</p>\n<p>It never had many mods due to a very manual data entry process, which could only\nbe performed by the mod author.</p>\n<p>It only supported mods and texture packs. Any mods were required to be mods to\nthe strict definition; Mod packs were not allowed. This was a huge problem given\nthat many of the most important mods are structured into mod packs.</p>\n<p>The website went offline due to the database breaking and the host being unable\nto fix it. It didn’t use containerisation and was hard to set up and develop.\nThe developer was also only sporadically available.</p>\n<h3 id=\"2015-minetest-bower\" tabindex=\"-1\">2015: Minetest Bower <a class=\"header-anchor\" href=\"#2015-minetest-bower\">#</a></h3>\n<p>Another user created a package repository based on <a href=\"https://bower.io\">Bower</a>, a Git-based\npackage manager for the web. Using it for Lua-based packages was a bit of an\nabuse of the tool, but it did work.</p>\n<p>The major issue was that it only supported content with a Git repository, and\nrequired the owner to update the repository to add a bower.json file. This\nresulted in even fewer mods being added than to the Minetest Mod Database.</p>\n<h3 id=\"2016-minetest-mods-android-app\" tabindex=\"-1\">2016: Minetest Mods Android App <a class=\"header-anchor\" href=\"#2016-minetest-mods-android-app\">#</a></h3>\n<p>I created <a href=\"/2022/10/22/Minetest-mods-app/\">an Android app</a> to\ninstall mods. It used a NodeJS backend that crawled the forums to get mod\ninformation. This worked to a degree, but the data was flawed and required me to\nmanually override a lot of information that the crawler didn’t detect properly.</p>\n<h3 id=\"2017-nodejs-based-contentdb\" tabindex=\"-1\">2017: NodeJS-based ContentDB <a class=\"header-anchor\" href=\"#2017-nodejs-based-contentdb\">#</a></h3>\n<p>I wrote a prototype using NodeJS, Sequelize, and PostgreSQL for a package\nrepository that had goals similar to ContentDB. It had support for Asynchronous\nTasks (Git importing and automatic releases), a user login system, and a\nREST API.</p>\n<p>This project allowed me to assess the suitability of NodeJS for the task; I\ngot negative reactions from other core developers. I also wasn’t a fan of\nSequelize as Python’s SQLAlchemy was so much better.</p>\n<h3 id=\"2017-minetest-content-database\" tabindex=\"-1\">2017: Minetest Content Database <a class=\"header-anchor\" href=\"#2017-minetest-content-database\">#</a></h3>\n<p>Another user created a package repository based on Python / Django.\nIt supported mods, games, texture packs, skins, and servers.</p>\n<p>Development faded out by the start of 2018.</p>\n<h2 id=\"client-requirements\" tabindex=\"-1\">Client Requirements <a class=\"header-anchor\" href=\"#client-requirements\">#</a></h2>\n<p>The client for this project was Perttu Ahola, aka celeron55, the original\ncreator of Minetest.</p>\n<p>I started by interviewing to ascertain the requirements of such a package\nrepository. The result was the following high-level requirements:</p>\n<ol>\n<li>It must be easy to deploy.</li>\n<li>It must have some kind of security model to spare most users from malicious mods.</li>\n<li>It must be practical to implement an interface to it from Minetest.</li>\n<li>It should be easy to add stuff, perhaps as simple as clicking a button to\nimport from GitHub.</li>\n<li>Should have good moderation tools and high attention to security.</li>\n</ol>\n<p>…and the following specific features:</p>\n<ul>\n<li>MUST: Users have one of the following ranks, which have different permissions:\nnew member, member, editor, moderator, admin.</li>\n<li>MUST: Users should be able to log in using username/password, GitHub, or the\nforums (phpBB).</li>\n<li>MUST: Packages are one of the following types: mod, game, texture pack.</li>\n<li>MUST: Packages can be created by any user but must be approved before they’re\nmade public.</li>\n<li>MUST: Packages have releases, which are immutable download-able versions.</li>\n<li>MUST: Edits to packages may need to be reviewed depending on user rank and whether they’re the author.</li>\n<li>MUST: A REST API to allow access to the data.</li>\n<li>MUST: A database to store the data.</li>\n<li>SHOULD: Packages have tags that can be used to filter them in search.</li>\n<li>SHOULD: Package metadata can be imported from GitHub.</li>\n<li>SHOULD: Moderation tools such as banning.</li>\n</ul>\n<h2 id=\"design\" tabindex=\"-1\">Design <a class=\"header-anchor\" href=\"#design\">#</a></h2>\n<p>With these requirements in mind, I created a design document. I made sure to\nkeep celeron55 in the loop; he signed off on the final version.</p>\n<h3 id=\"permissions-and-ranks\" tabindex=\"-1\">Permissions and Ranks <a class=\"header-anchor\" href=\"#permissions-and-ranks\">#</a></h3>\n<p>One of the most important system in ContentDB is the permission system. There\nare many named permissions, such as <code>EDIT_PACKAGE</code>, <code>DELETE_RELEASE</code>, and\n<code>APPROVE_SCREENSHOT</code>. When performing an action, ContentDB will check whether\nthe user has the required permission on the object in question.</p>\n<p>Each user has a rank. A rank determines the permissions of the user and\ninclude the permissions of lower ranks.</p>\n<ul>\n<li><strong>New Members</strong>: need to have any edits or new packages approved.</li>\n<li><strong>Members</strong>: can edit or upload packages by them, but edits or uploads for other authors need to be approved. Can approve edits to their packages.</li>\n<li><strong>Editors</strong>: can edit any package, upload for any user, and approve changes to any package. Can approve any releases.</li>\n<li><strong>Moderators</strong>: can ban users and delete mods.</li>\n<li><strong>Admin</strong>: can change passwords and create users.</li>\n</ul>\n<p>New packages must be approved by an Editor.</p>\n<h3 id=\"technology\" tabindex=\"-1\">Technology <a class=\"header-anchor\" href=\"#technology\">#</a></h3>\n<p>I chose to use Python/Flask with SQLAlchemy. I had prior experience with these\nframeworks, and Minetest already had a web app using Flask.</p>\n<h3 id=\"database\" tabindex=\"-1\">Database <a class=\"header-anchor\" href=\"#database\">#</a></h3>\n<p>I decided to use a relational database to store the data required for ContentDB\nto function. As data integrity was a concern, I spent extra effort on designing\ndatabase-level validation using constraints.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Yg8iy9JKGn-750.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/-A65xsysQf-540.webp 540w\"><img src=\"/static/img/-A65xsysQf-540.png\" alt=\"The current database relationship diagram\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"261\"></picture></a><figcaption>The current database relationship diagram</figcaption></figure>\n<h2 id=\"development-for-coursework\" tabindex=\"-1\">Development for Coursework <a class=\"header-anchor\" href=\"#development-for-coursework\">#</a></h2>\n<p>I was given five months to develop the project for the university coursework\nmodule.</p>\n<p>I started by setting up the database. I chose to use PostgreSQL, as it has\ngood support for validation constraints and migrations.</p>\n<p>Once the database was created, I worked on implementing a front-end template\nto allow further progress. I chose to use my own CSS to get the best possible\nscore. I wasn’t as good at CSS as I am <a href=\"https://renewedtab.com/\">today</a>, and\nmaking it work was considered more important than making it pretty.</p>\n<p>I used pre-existing libraries for user and login management for the coursework\nversion.</p>\n<p>Edit Requests were a feature that existed in early versions of ContentDB.\nIt allowed users to create requests to edit a package, which can then be\naccepted or rejected by the package’s owner or an Editor. It was removed after\nthe project was submitted due to changing priorities and difficulties in\nmaintaining the implementation.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/79HTaqUh4C-1290.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/yD06cEhobb-540.webp 540w\"><img src=\"/static/img/yD06cEhobb-540.jpeg\" alt=\"The ContentDB homepage when submitted\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"597\"></picture></a><figcaption>The ContentDB homepage when submitted</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/9CUviqzaiA-1123.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Php0MyIi4Y-540.webp 540w\"><img src=\"/static/img/Php0MyIi4Y-540.png\" alt=\"An early version of the package page\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"397\"></picture></a><figcaption>An early version of the package page</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/KxZxMjMiIh-1115.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/m48ZBA7foV-540.webp 540w\"><img src=\"/static/img/m48ZBA7foV-540.png\" alt=\"The Edit Request feature\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"228\"></picture></a><figcaption>The Edit Request feature</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"asynchronous-tasks-celery-and-git-support\" tabindex=\"-1\">Asynchronous Tasks, Celery, and Git Support <a class=\"header-anchor\" href=\"#asynchronous-tasks-celery-and-git-support\">#</a></h3>\n<p>ContentDB uses Celery to run asynchronous tasks, to avoid blocking the server\nthread. These tasks include importing meta from Git, creating releases, and\nfetching user info from the forums.</p>\n<p>Git support was the most tricky thing to implement, as it required acquiring a\nfairly in-depth knowledge of how Git works - and it’s not simple software. For\nexample, one bug I had was a very weird error when attempting to clone a\nparticular repo. It turned out that a tag existing on a commit that wasn’t on a\nbranch was the cause. Through the use of integration tests, I was able to nail\nthe implementation.</p>\n<h2 id=\"deployment\" tabindex=\"-1\">Deployment <a class=\"header-anchor\" href=\"#deployment\">#</a></h2>\n<p>I deployed ContentDB using Docker onto my dedicated server. Docker allows local\ndevelopment and production to match, making development much easier.</p>\n<figure class=\"undefined\"><a href=\"/static/img/nenVM-L0Nr-1914.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/n17oQi3S1--540.webp 540w\"><img src=\"/static/img/n17oQi3S1--540.png\" alt=\"ContentDB's deployment architecture\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"327\"></picture></a><figcaption>ContentDB's deployment architecture</figcaption></figure>\n<p>I implemented support for ContentDB in Minetest’s main menu, allowing\nit to install and update packages.</p>\n<figure class=\"undefined\"><a href=\"/static/img/EumUIGqAHH-700.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/E9PBiIazxs-540.webp 540w\"><img src=\"/static/img/E9PBiIazxs-540.png\" alt=\"The ContentDB integration in Minetest's mainmenu, using the REST API\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"332\"></picture></a><figcaption>The ContentDB integration in Minetest's mainmenu, using the REST API</figcaption></figure>\n<p>I added Prometheus support with a\n<a href=\"https://monitor.rubenwardy.com/d/3ELzFy3Wz/contentdb\">Grafana dashboard</a>\nto show statistics, such as downloads and total users.</p>\n<h2 id=\"post-submission\" tabindex=\"-1\">Post-submission <a class=\"header-anchor\" href=\"#post-submission\">#</a></h2>\n<p>In the four years since ContentDB was submitted as coursework, I have continued\nto develop it. One of the first changes I made was switching from custom CSS to\nBootstrap CSS, which made future development easier.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Gzlzxf7l2_-1920.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/FbXcLoaS63-540.webp 540w\"><img src=\"/static/img/FbXcLoaS63-540.png\" alt=\"The current package page design, using bootstrap\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>The current package page design, using bootstrap</figcaption></figure>\n<p>The submitted version just had a read-only API for getting information about\npackages and updates. I added support for API access tokens and extended\n<a href=\"https://content.minetest.net/help/api/\">the API</a> to allow for editing packages\nand making releases.</p>\n<p>Other improvements include:</p>\n<ul>\n<li>Threads and comments</li>\n<li>Ratings and reviews</li>\n<li>Email notifications</li>\n<li>Package videos</li>\n<li>Improved tools for Editors and users</li>\n<li>Package .zip validation</li>\n<li>Release creation on Webhooks from GitHub and GitLab</li>\n<li>Git update detection: ContentDB will check repos to see if there’s been any\nnew commits or tags</li>\n<li>Support for dependency installation</li>\n<li>Translation / internationalisation</li>\n</ul>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>One of the main problems with ContentDB was ensuring maintainability as it grew.\nSince submission, I’ve done several refactors to improve this, such as\nseparating business logic from the app’s routes. This separation allows the\nfront-end, REST API, and async tasks to share the same code, which reduces risk.</p>\n<p>Another problem was insufficient UI tests. I used Flask’s test context feature\nto check the response of queries. There are tests for core behaviour, such as\nuser logins and package pages, but they’re not particularly comprehensive. One\nreason is that the testing tools used were insufficient; I have since invested\ntime in learning how to better test web front-ends, using Selenium, and have\napplied that to more recent projects.</p>\n<p><a href=\"https://content.minetest.net/\">ContentDB</a> is one of the largest projects I’ve\ndeveloped, and one of the longest running.</p>\n","url":"https://blog.rubenwardy.com/2022/03/24/contentdb/","summary":"How I created ContentDB, the website that allows players to install mods, games, and texture packs from within Luanti (formerly Minetest).","date_published":"2022-03-24T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/CCdENRY3Uf-540.png","tags":["projects","webdev","luanti"]},{"id":"/2022/03/19/ld48-tin-mining/","language":"en","title":"Tin Mining - Ludum Dare 48 post-mortem","content_html":"<p>In April 2021, I participated in my first game jam, Ludum Dare 48.\nLudum Dare is a popular online game jam; this event received over 3800\nsubmissions. The theme was “Deeper and Deeper,” and I created a game\nwhere you manage a tin mine.</p>\n<p>The year is 1790, and the Cornish tin industry is booming. You are a\nbusinessperson who has just secured investment to build a mine. The area is\nknown to be rich in tin, which is in high demand.</p>\n<p>Rather than controlling your workers directly, you drag out plans for tiles to\nbe mined and built. The workers will mine tunnels and build where ordered.\nThey will carry mined resources to the surface to be sold.</p>\n<figure class=\"undefined\"><a href=\"/static/img/6czJ2rVSyu-960.gif\"><img src=\"/static/img/6EVfY4m0n6-960.gif\" alt=\"Gif of the Tin Mining game.\" loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"540\"></a><figcaption>Gif of the Tin Mining game.</figcaption></figure>\n<h2 id=\"development\" tabindex=\"-1\">Development <a class=\"header-anchor\" href=\"#development\">#</a></h2>\n<p>I started this a day late and had to work on Monday, so I was only able\nto implement the bare minimum viable gameplay. I had planned to add hazards,\nbuildings to prevent these hazards, and a tech tree. For example, gas explosions,\ntoxic gas, and flooding were huge problems in mining.</p>\n<p>The map is procedurally-generated, using a combination of multiple simplex\nnoises. The ores are randomly placed in veins, with the likelihood based on\ndepth.</p>\n<p>I made all the art myself, using GIMP and the\n<a href=\"https://lospec.com/palette-list/endesga-32\">Endesga 32</a> color palette.\nI choose to make the art super low resolution - 8x8 pixels - to make the art\neasier to make, and hopefully hide my lack of artistic skills.</p>\n<figure class=\"undefined\"><a href=\"/static/img/tsuH3VY2dO-1920.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/2m1NnZ_3tW-540.webp 540w\"><img src=\"/static/img/2m1NnZ_3tW-540.png\" alt=\"Mining deeper\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>Mining deeper</figcaption></figure>\n<p>I created the game using TypeScript and Phaser 3. Phaser 3 was an absolute pain\nto use. It was poorly documented, and I spent most of the time fighting against\nthe API. One of the main things I’ve learned from doing this game is that I\nnever want to use Phaser 3 again.</p>\n<h3 id=\"game-ai\" tabindex=\"-1\">Game AI <a class=\"header-anchor\" href=\"#game-ai\">#</a></h3>\n<p>I choose to use\n<a href=\"https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work\">behaviour trees</a>\nto implement the worker AI. Behaviour trees make it easier to implement extendable\nNPC behaviour, which will be useful when working on more complex behaviour in the\npost-jam version.</p>\n<p>When a worker isn’t currently working on a task, it periodically asks the\nWorkManager for an available task. The WorkManager allocates tasks based on\ndistance and a heuristic weighting of the task. For example, moving dropped\nitems to the stockpile is weighted higher than mining or building, to avoid\nitems building up and clogging the walkways.</p>\n<p>Workers shouldn’t get stuck on work that isn’t possible, such as trying to mine\ntiles that are not yet exposed. The way I implemented this was a bit hacky:\nworkers have “work failure lockouts.” If a worker attempts work and fails, then\nthat worker won’t attempt that piece of work again for 10 seconds. The work\nmanager will allocate tasks without checking their accessibility, and the worker\nis responsible for triggering a lockout if the work isn’t reachable.</p>\n<p>There were many bugs in the AI system, some of which were only found after\nsubmission. Luckily, Ludum Dare allows releasing bug fixes after the deadline,\nso I was able to patch most of the issues. The remaining issues just couldn’t be\nfixed due to architectural issues, or performance issues; the pathfinder was\npretty naive and slow.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>The main reason I entered a game jam is that I wanted to finish a game,\nat least to the minimum viable gameplay. It forces you to focus on making the\ngame, rather than making the code perfect, which is something I’ve not been\nthe best at in the past. This project certainly achieved these goals. Whilst\nI didn’t implement all the features I wanted, I still finished it sufficiently\nfor a small game.</p>\n<p>I decided to learn a new framework for making games for the next game jam. I\nchose to learn the <a href=\"https://godotengine.org/\">Godot Engine</a>, and have since\nwrote several games in it, including a post-jam version of Tin Miner and an\nentry for Ludum Dare 49 - which I’ll write about at some point.</p>\n<p>I look forward to working on the post-jam game in the future. I think this idea\nmay be a good game to work towards publishing on Steam, due to the smaller scope\ncompared to some of the other games I’ve made.</p>\n<h2 id=\"post-jam-version\" tabindex=\"-1\">Post-jam version <a class=\"header-anchor\" href=\"#post-jam-version\">#</a></h2>\n<p>As mentioned, Phaser 3 was an absolute pain to use. Therefore, I decided to\nrewrite the game using the <a href=\"https://godotengine.org/\">Godot Engine</a>. This was a\npleasant experience, as Godot’s API is very nice to use.</p>\n<figure class=\"undefined\"><a href=\"/static/img/65GuyNBJ2W-1920.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/TBtb4vevF--540.webp 540w\"><img src=\"/static/img/TBtb4vevF--540.png\" alt=\"A post-jam version of the game, written using Godot.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"337\"></picture></a><figcaption>A post-jam version of the game, written using Godot.</figcaption></figure>\n<p>It took a bit longer to make, as I focused more on doing things correctly.\nWhilst this was mostly differences in maintainability, I also snuck in some\nusability improvements:</p>\n<figure class=\"undefined\"><a href=\"/static/img/Fcf0-qBlOq-700.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/tMJJhoBcvG-540.webp 540w\"><img src=\"/static/img/tMJJhoBcvG-540.png\" alt=\"Post-jam user improvements\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"344\"></picture></a><figcaption>Post-jam user improvements</figcaption></figure>\n<p>The pathfinder in the LD version reads the tiles directly, which is slow as\nthere needs to be a lot of navigation logic. The new Godot version stores a\nnavigation graph to represent possible routes, which increases performance by\na large factor.</p>\n<p>One nice thing about the new graph-based pathfinder is that it’s quick to check\nwhether a tile is on the network. This acts as a nice optimization for checking\nwhether a tile can be accessed to perform work.</p>\n<p>Another difference is the new version contains better debug tools.\nFor example, a debug menu to edit the world, navigation graph debugging,\nand graphs.</p>\n<figure class=\"undefined\"><a href=\"/static/img/CZV7NZVgIT-1335.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/KEW6gRDkd7-540.webp 540w\"><img src=\"/static/img/KEW6gRDkd7-540.png\" alt=\"Post-jam navigation mesh and debug graphs. Hovering over a tile with a job highlights the allocated NPC with a circle.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"332\"></picture></a><figcaption>Post-jam navigation mesh and debug graphs. Hovering over a tile with a job highlights the allocated NPC with a circle.</figcaption></figure>\n<h2 id=\"play-the-game\" tabindex=\"-1\">Play the game <a class=\"header-anchor\" href=\"#play-the-game\">#</a></h2>\n<p>You can play the Ludum Dare version\n<a href=\"https://rubenwardy.com/ld48_cornish_mines/\">online</a>.\nThe post-jam version is currently in development and will be made available\nwhen ready.</p>\n<h3 id=\"controls\" tabindex=\"-1\">Controls <a class=\"header-anchor\" href=\"#controls\">#</a></h3>\n<ul>\n<li>Pan: Right-click drag / middle-click drag / arrow keys to pan</li>\n<li>Zoom: scroll wheel</li>\n<li>Select tools from build menu, and then:\n<ul>\n<li>Left-click and drag to place orders.</li>\n<li>Esc to cancel the tool.</li>\n</ul>\n</li>\n<li>Tip: you can order workers to mine and build a ladder on the same tile, and they will mine and then build a ladder.</li>\n</ul>\n","url":"https://blog.rubenwardy.com/2022/03/19/ld48-tin-mining/","summary":"In April 2021, I participated in my first game jam. I made a Cornish tin mining simulation game, where you place out orders that workers follow.","date_published":"2022-03-19T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/MP8mC6KDi7-540.png","tags":["projects","gamedev","ludum_dare","jams"]},{"id":"/2022/03/17/plant-monitor/","language":"en","title":"IoT Plant Monitor using ESP32","content_html":"<p>I have a lot of houseplants, but I often forget to water them.\nI’ve been getting into electronics and thought this would be a great\nopportunity to make something.</p>\n<p>I made a plant monitor, which measures soil moisture, temperature, and humidity,\nand reports these things to a cloud IoT service called Thinger.io.</p>\n<h2 id=\"the-case\" tabindex=\"-1\">The Case <a class=\"header-anchor\" href=\"#the-case\">#</a></h2>\n<p>I wanted the monitor to be subtle and hidden, so I designed a case to fit\nbetween the inner planter and the outer decorative pot. I printed it using my\nbrand new Prusa Mini+. It took many iterations to get it perfect. The\nelectronics fit snug in the case, with ports for sensors and a microUSB charger.</p>\n<p>The case was the first thing I made using Computer-Aided Design (CAD),\nand was a particularly painful task as the case is curved to fit the 23cm\ndiameter pot.</p>\n<p>I’ve put the files up on\n<a href=\"https://www.thingiverse.com/thing:5234381\">Thingiverse</a> and\n<a href=\"https://www.prusaprinters.org/prints/130542-clip-on-plant-sensor-case\">PrusaPrinters</a>.</p>\n<figure class=\"undefined\"><a href=\"/static/img/xFr84pXF5T-537.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/NdYJB3nloy-537.webp 537w\"><img src=\"/static/img/NdYJB3nloy-537.png\" alt=\"The curved case in CAD\" loading=\"lazy\" decoding=\"async\" width=\"537\" height=\"699\"></picture></a><figcaption>The curved case in CAD</figcaption></figure>\n<h2 id=\"electronics\" tabindex=\"-1\">Electronics <a class=\"header-anchor\" href=\"#electronics\">#</a></h2>\n<h3 id=\"components\" tabindex=\"-1\">Components <a class=\"header-anchor\" href=\"#components\">#</a></h3>\n<p>The first problem I had was finding working soil moisture sensors. I bought\nsome capacitive soil moisture sensors from eBay, but they were very slow\nreacting - they took over 3 minutes to respond to changes in moisture.\nAfter some research, I found out that some low-quality sensors from China\ncontain a capacitor in the wrong place, which smooths out the output\nvoltage to an unusable degree. I replaced the sensors with ones bought from\nthe Pi Hut, which worked considerably better - they respond immediately to\nchanges in moisture. This event put me off buying from eBay for a while.</p>\n<figure class=\"undefined\"><a href=\"/static/img/jZ-3-HLHgX-700.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/teFu5CzC7t-540.webp 540w\"><img src=\"/static/img/teFu5CzC7t-540.png\" alt=\"Graph showing the slow reaction of the faulty soil moisture sensor\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"293\"></picture></a><figcaption>Graph showing the slow reaction of the faulty soil moisture sensor</figcaption></figure>\n<p>I decided to use an ESP32 as the microcontroller, as they seemed easy-to-use,\nabundant, and cheap. I choose a cheap ESP32-WROOM-32 devkit from a trusted\neBay seller.</p>\n<h3 id=\"making-a-permanent-circuit-using-stripboard\" tabindex=\"-1\">Making a permanent circuit using stripboard <a class=\"header-anchor\" href=\"#making-a-permanent-circuit-using-stripboard\">#</a></h3>\n<p>This is my first proper electronics project, and the first time I’ve made\na ‘permanent’ circuit. The soldering was a bit messy but functional\n(I blame the flux for spitting at me, but really it’s just a lack of practice).</p>\n<p>In order to use stripboard, you need to be able to break the copper tracks to\nallow the circuit to be correctly formed. I bought a Stripboard Track Cutter\nfrom the Pi Hut for this job, and it turned out to be incredibly ill-suited.\nIt took 40 seconds of hard pressure to break a track. I’ve since bought a new\ntrack cutter from eBay that only takes a few seconds of basically no pressure,\nso I’ll definitely be using that for future projects.</p>\n<figure class=\"undefined\"><a href=\"/static/img/assG3FbzhC-1000.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/CtYOGKf-EF-540.webp 540w\"><img src=\"/static/img/CtYOGKf-EF-540.jpeg\" alt=\"The electronics, on a stripboard\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"268\"></picture></a><figcaption>The electronics, on a stripboard</figcaption></figure>\n<p>The soil moisture sensor is a simple analog 3-pin device - two for power, one for a\nvoltage reading. The output voltage decreases with moisture.\nThis sensor connects to the board using a 3-pin male header.</p>\n<p>The temperature/humidity sensor uses Inter-Integrated Circuit (I2C), a low-level\nserial communication bus, to communicate with the microcontroller. I2C requires\n4-pins - two for power, and two for the bus.</p>\n<p>I included connectors for two I2C devices, with space on the board for up to four\ndevices. I only use one I2C device currently, but I’m likely to add more devices\nsuch as a light intensity (lux) sensor in the future.</p>\n<p>I bought some 4-pin JST PH connectors for I2C, but ended up using\nfemale headers instead as JST PH’s 2mm spacing doesn’t fit the stripboard’s\n3mm spacing. Perhaps in the future I’ll either make a PCB or create an adapter,\nallowing JST PH to be used.</p>\n<figure class=\"undefined\"><a href=\"/static/img/tfbB-H3Kob-1308.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/crYP__TrLA-540.webp 540w\"><img src=\"/static/img/crYP__TrLA-540.png\" alt=\"Electronics schematic, created using KiCAD\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"326\"></picture></a><figcaption>Electronics schematic, created using KiCAD</figcaption></figure>\n<h2 id=\"internet-of-things-with-thinger-io\" tabindex=\"-1\">Internet of Things with Thinger.IO <a class=\"header-anchor\" href=\"#internet-of-things-with-thinger-io\">#</a></h2>\n<p>I was tempted to implement my own cloud IoT platform, but then I reminded myself\nthat the point of this project was to learn electronics and 3d printing, not\nto spend hours reinventing the wheel.</p>\n<p>I did some research on existing IoT platforms, and choose Thinger.IO as it is\nopen source and seemed to have the right balance of simplicity vs capability.</p>\n<p>Integrating with Thinger.IO with the microcontroller was as simple as including\ntheir SDK and writing up a few lines to publish the sensor values.</p>\n<p>Setting up the dashboard on Thinger.IO was a bit more complicated. It took a\nwhile to work out how to record data rather than just show it, I needed to use\na data bucket with manually posted values, and then write to it every 5 minutes\nfrom the microcontroller. I would have preferred that the microcontroller\njust expose the sensor values, and not care about what the IoT platform does\nwith it.</p>\n<figure class=\"undefined\"><a href=\"/static/img/w3K6NY3gYR-700.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/Gy_qZ4yCjg-540.webp 540w\"><img src=\"/static/img/Gy_qZ4yCjg-540.jpeg\" alt=\"The dashboard on Thinger.io, showing the collected data\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"465\"></picture></a><figcaption>The dashboard on Thinger.io, showing the collected data</figcaption></figure>\n<h2 id=\"source-code\" tabindex=\"-1\">Source code <a class=\"header-anchor\" href=\"#source-code\">#</a></h2>\n<p>The source code is pretty basic, this was definitely more of an electronics\nproject than a programming one!</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;Arduino.h></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;Adafruit_SHT31.h></span></span>\n\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">define</span> <span class=\"token macro-name\">THINGER_SERIAL_DEBUG</span></span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;ThingerESP32.h></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">\"secrets.hpp\"</span></span>\n\nThingerESP32 <span class=\"token function\">thing</span><span class=\"token punctuation\">(</span>USERNAME<span class=\"token punctuation\">,</span> DEVICE_ID<span class=\"token punctuation\">,</span> DEVICE_CREDENTIAL<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">define</span> <span class=\"token macro-name\">MOIS_LOW</span> <span class=\"token expression\"><span class=\"token number\">1.8f</span></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">define</span> <span class=\"token macro-name\">MOIS_HIGH</span> <span class=\"token expression\"><span class=\"token number\">2.2f</span></span></span>\n\n\n<span class=\"token keyword\">int</span> moistureReadPin <span class=\"token operator\">=</span> <span class=\"token number\">35</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">int</span> moistureEnPin <span class=\"token operator\">=</span> <span class=\"token number\">32</span><span class=\"token punctuation\">;</span>\n\nAdafruit_SHT31 sht31 <span class=\"token operator\">=</span> <span class=\"token function\">Adafruit_SHT31</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n\n<span class=\"token keyword\">float</span> <span class=\"token function\">measureSoil</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    Serial<span class=\"token punctuation\">.</span><span class=\"token function\">println</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Measuring soil\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token function\">digitalWrite</span><span class=\"token punctuation\">(</span>moistureEnPin<span class=\"token punctuation\">,</span> HIGH<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">delay</span><span class=\"token punctuation\">(</span><span class=\"token number\">1000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">int</span> raw <span class=\"token operator\">=</span> <span class=\"token function\">analogRead</span><span class=\"token punctuation\">(</span>moistureReadPin<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">float</span> moisture <span class=\"token operator\">=</span> raw <span class=\"token operator\">*</span> <span class=\"token number\">3.3f</span> <span class=\"token operator\">/</span> <span class=\"token number\">4095.0f</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">digitalWrite</span><span class=\"token punctuation\">(</span>moistureEnPin<span class=\"token punctuation\">,</span> LOW<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token number\">100.f</span> <span class=\"token operator\">-</span> <span class=\"token number\">100.f</span> <span class=\"token operator\">*</span> <span class=\"token punctuation\">(</span>moisture <span class=\"token operator\">-</span> MOIS_LOW<span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token punctuation\">(</span>MOIS_HIGH <span class=\"token operator\">-</span> MOIS_LOW<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n\n<span class=\"token keyword\">void</span> <span class=\"token function\">setup</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    Serial<span class=\"token punctuation\">.</span><span class=\"token function\">begin</span><span class=\"token punctuation\">(</span><span class=\"token number\">9600</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token function\">pinMode</span><span class=\"token punctuation\">(</span>moistureReadPin<span class=\"token punctuation\">,</span> INPUT<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">pinMode</span><span class=\"token punctuation\">(</span>moistureEnPin<span class=\"token punctuation\">,</span> OUTPUT<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>sht31<span class=\"token punctuation\">.</span><span class=\"token function\">begin</span><span class=\"token punctuation\">(</span><span class=\"token number\">0x44</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        Serial<span class=\"token punctuation\">.</span><span class=\"token function\">println</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Couldn't find SHT31\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token function\">delay</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n\n    sht31<span class=\"token punctuation\">.</span><span class=\"token function\">heater</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    thing<span class=\"token punctuation\">.</span><span class=\"token function\">add_wifi</span><span class=\"token punctuation\">(</span>SSID<span class=\"token punctuation\">,</span> SSID_PASSWORD<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    thing<span class=\"token punctuation\">[</span><span class=\"token string\">\"data\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">>></span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">(</span>pson <span class=\"token operator\">&amp;</span>out<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        out<span class=\"token punctuation\">[</span><span class=\"token string\">\"temperature\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> sht31<span class=\"token punctuation\">.</span><span class=\"token function\">readTemperature</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        out<span class=\"token punctuation\">[</span><span class=\"token string\">\"humidity\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> sht31<span class=\"token punctuation\">.</span><span class=\"token function\">readHumidity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        out<span class=\"token punctuation\">[</span><span class=\"token string\">\"soil_moisture\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">measureSoil</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n    thing<span class=\"token punctuation\">.</span><span class=\"token function\">handle</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    thing<span class=\"token punctuation\">.</span><span class=\"token function\">write_bucket</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Plants\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"data\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    ESP<span class=\"token punctuation\">.</span><span class=\"token function\">deepSleep</span><span class=\"token punctuation\">(</span><span class=\"token number\">60</span> <span class=\"token operator\">*</span> <span class=\"token number\">1000000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n\n<span class=\"token keyword\">void</span> <span class=\"token function\">loop</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// Never executed due to deep sleep</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>This was a very nice project to learn electronics and 3d printed, I certainly\nlearned a lot. I still forget to water my plants, but at least I do so in a\nhigh-tech fashion!</p>\n<p>I look forward to working on more complicated projects in the future, from\nelectronics to mechanical design.</p>\n<p>In the future, I’d like to add a light intensity (lux) sensor to the monitor. I\nneed to think about where to put the sensor, as putting it on top of the pot\nwill result in an inaccurate reading. This is due to sunlight being obstructed\nby the pot, and the difference in height to the leaves.</p>\n<p>This project has seeded many ideas for future projects, such as self-watering\nplants, a weather station, a monitored indoor greenhouse, and hydroponics. I\nplan to seed basil and a few other herbs soon, having a mini indoors controlled\ngreenhouse with grow lights could be a cool project.</p>\n","url":"https://blog.rubenwardy.com/2022/03/17/plant-monitor/","summary":"I made a plant monitor, which measures soil moisture, temperature, and humidity, and reports these things to a cloud IoT service called Thinger.io.","date_published":"2022-03-17T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/beRfMhh0s7-540.png","tags":["projects","electronics","3d_printing","cpp"]},{"id":"/2021/05/08/mozilla-bleach-markdown/","language":"en","title":"Securing Markdown user content with Mozilla Bleach","content_html":"<p>Markdown is a common choice for rich text formatting due to its readability and\nease-of-use. Unlike a lot of markup, it aims to match natural text. It’s even\neasy for beginner users, and there are WYSIWYG editors available.</p>\n<p>We will be using the <a href=\"https://python-markdown.github.io/\">Python Markdown</a>\nlibrary to convert Markdown to HTML. Markdown doesn’t have a well-defined\nstandard. The library aims to comply with what little is defined by the\n<a href=\"https://daringfireball.net/projects/markdown/syntax\">Markdown syntax specification</a>,\nmeaning that it is also often stricter than other parsers.</p>\n<p>To convert Markdown to HTML:</p>\n<pre class=\"language-py\"><code class=\"language-py\"><span class=\"token keyword\">from</span> markdown <span class=\"token keyword\">import</span> Markdown\nmd <span class=\"token operator\">=</span> Markdown<span class=\"token punctuation\">(</span>extensions<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"fenced_code\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"tables\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> output_format<span class=\"token operator\">=</span><span class=\"token string\">\"html5\"</span><span class=\"token punctuation\">)</span>\nhtml <span class=\"token operator\">=</span> md<span class=\"token punctuation\">.</span>convert<span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">)</span></code></pre>\n<p>You can use another library to interpret Markdown, if you wish.\nThe rest of the code will be dealing with the HTML output, so is independent of\nMarkdown parsers.</p>\n<h3 id=\"avoid-xss-attacks\" tabindex=\"-1\">Avoid XSS attacks <a class=\"header-anchor\" href=\"#avoid-xss-attacks\">#</a></h3>\n<p>When allowing user submitted content, it’s important to sanitise it to avoid\nCross-Site Scripting attacks (XSS). If you don’t sanitise user input, then\nan attacker will be able to add HTML tags to run JavaScript when other users\nview your website. This can be used to steal login credentials, run bitcoin\nmining malware, or deface your website. So not ideal.</p>\n<p><a href=\"https://github.com/mozilla/bleach\">Bleach</a>, by Mozilla, is library to sanitised\nuntrusted HTML. It works based on a whitelist of tags and their attributes.\nI have based my list on the\n<a href=\"https://github.com/Wenzil/mdx_bleach/blob/master/mdx_bleach/whitelist.py\">mdx_bleach extension</a>,\nwhich you could use directly with the markdown library - but I prefer to use\nthe Bleach library directly after generating the HTML as dependencies have a\ntendency to break.</p>\n<p>Another thing that Bleach does is safely linkify text - it can convert text\nresembling a URL into a link.</p>\n<pre class=\"language-py\"><code class=\"language-py\"><span class=\"token comment\"># List of allowed HTML tags</span>\nALLOWED_TAGS <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token string\">\"h1\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h2\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h3\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h4\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h5\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h6\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"hr\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"ul\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ol\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"li\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"p\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"br\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"pre\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"code\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"blockquote\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"strong\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"em\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"a\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"img\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"b\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"i\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"table\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"thead\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"tbody\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"tr\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"th\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"td\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span>\n\n<span class=\"token comment\"># A map of HTML tags to allowed attributes</span>\n<span class=\"token comment\"># If a tag isn't here, then no attributes are allowed</span>\nALLOWED_ATTRIBUTES <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token string\">\"h1\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"id\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h2\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"id\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"h3\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"id\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>  <span class=\"token string\">\"h4\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"id\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"a\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"href\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"title\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"img\"</span><span class=\"token punctuation\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"src\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"title\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"alt\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\"># Allowed protocols in links.</span>\nALLOWED_PROTOCOLS <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"http\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"https\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mailto\"</span><span class=\"token punctuation\">]</span>\n\nmd <span class=\"token operator\">=</span> Markdown<span class=\"token punctuation\">(</span>output_format<span class=\"token operator\">=</span><span class=\"token string\">\"html5\"</span><span class=\"token punctuation\">)</span>\n\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">render_markdown</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    html <span class=\"token operator\">=</span> md<span class=\"token punctuation\">.</span>convert<span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">)</span>\n\n    cleaner <span class=\"token operator\">=</span> Cleaner<span class=\"token punctuation\">(</span>\n            tags<span class=\"token operator\">=</span>ALLOWED_TAGS<span class=\"token punctuation\">,</span>\n            attributes<span class=\"token operator\">=</span>ALLOWED_ATTRIBUTES<span class=\"token punctuation\">,</span>\n            protocols<span class=\"token operator\">=</span>ALLOWED_PROTOCOLS<span class=\"token punctuation\">,</span>\n            filters<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span>partial<span class=\"token punctuation\">(</span>LinkifyFilter<span class=\"token punctuation\">,</span> callbacks<span class=\"token operator\">=</span>bleach<span class=\"token punctuation\">.</span>linkifier<span class=\"token punctuation\">.</span>DEFAULT_CALLBACKS<span class=\"token punctuation\">)</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">return</span> cleaner<span class=\"token punctuation\">.</span>clean<span class=\"token punctuation\">(</span>html<span class=\"token punctuation\">)</span></code></pre>\n<h3 id=\"supporting-code-highlighting\" tabindex=\"-1\">Supporting code highlighting <a class=\"header-anchor\" href=\"#supporting-code-highlighting\">#</a></h3>\n<p>The CodeHilite extension for Python-Markdown uses\n<a href=\"https://pygments.org/\">Pygments</a> to provide syntax highlighting.\nYou can enable the extension by adding it to the extensions list.</p>\n<pre class=\"language-py\"><code class=\"language-py\">md <span class=\"token operator\">=</span> Markdown<span class=\"token punctuation\">(</span>extensions<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"fenced_code\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"tables\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"codehilite\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> output_format<span class=\"token operator\">=</span><span class=\"token string\">\"html5\"</span><span class=\"token punctuation\">)</span></code></pre>\n<p>You will also need to provide the <code>.css</code> files for the\n<a href=\"https://pygments.org/docs/styles/#builtin-styles\">style</a>\n(<a href=\"https://pygments.org/demo/\">demo</a>) you choose.\nI ended up going with <a href=\"https://draculatheme.com/pygments/\">Darcula</a> due to\npersonal preference.</p>\n<p>Bleach will strip attributes that aren’t whitelisted, including the class names\nneeded by code highlighting. It’s important that you don’t just allow any\n<code>class</code> attribute values to be used, as this would allow malicious users to use\nany CSS class and deface your website. Instead, we will provide a function to\nthe <code>ALLOWED_ATTRIBUTES</code> dictionary, which will check whether the provided\nvalues are allowed.</p>\n<pre class=\"language-py\"><code class=\"language-py\">ALLOWED_TAGS <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token comment\"># ...</span>\n\n    <span class=\"token string\">\"div\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"span\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span>\n\nALLOWED_CSS_CLASSES <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token string\">\"highlight\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"codehilite\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"hll\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"c\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"err\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"g\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"k\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"l\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"n\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"o\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"x\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"p\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ch\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"cm\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"cp\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"cpf\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"c1\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"cs\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"gd\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ge\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gr\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gh\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gi\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"go\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gp\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gs\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gu\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"gt\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kc\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kd\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kn\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kp\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"kr\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"kt\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ld\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"m\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"s\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"na\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nb\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nc\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"no\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nd\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ni\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ne\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nf\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nl\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nn\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nx\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"py\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nt\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"nv\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ow\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"w\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mb\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mf\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mh\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mi\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"mo\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sa\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sb\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sc\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"dl\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sd\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"s2\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"se\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sh\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"si\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sx\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"sr\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"s1\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ss\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"bp\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"fm\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"vc\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"vg\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"vi\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"vm\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"il\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span>\n\n<span class=\"token keyword\">def</span> <span class=\"token function\">allow_class</span><span class=\"token punctuation\">(</span>_tag<span class=\"token punctuation\">,</span> name<span class=\"token punctuation\">,</span> value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">return</span> name <span class=\"token operator\">==</span> <span class=\"token string\">\"class\"</span> <span class=\"token keyword\">and</span> value <span class=\"token keyword\">in</span> ALLOWED_CSS_CLASSES\n\nALLOWED_ATTRIBUTES <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\"># etc</span>\n    <span class=\"token string\">\"code\"</span><span class=\"token punctuation\">:</span> allow_class<span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"div\"</span><span class=\"token punctuation\">:</span> allow_class<span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"span\"</span><span class=\"token punctuation\">:</span> allow_class<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span>\n\nmd <span class=\"token operator\">=</span> Markdown<span class=\"token punctuation\">(</span>output_format<span class=\"token operator\">=</span><span class=\"token string\">\"html5\"</span><span class=\"token punctuation\">)</span></code></pre>\n<p>And there you are! You can now render untrusted user markdown safely, with\ncode highlighting and linkify.</p>\n","url":"https://blog.rubenwardy.com/2021/05/08/mozilla-bleach-markdown/","summary":"When accepting user content, it's important to sanitize user input to avoid Cross Site Scripting (XSS) and other attacks.","date_published":"2021-05-08T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/uJ0aif-HGT-540.png","tags":["tutorials","webdev"]},{"id":"/2020/09/13/return-to-android-dev/","language":"en","title":"ForumMate: My return to Android app development","content_html":"<p>I worked as an Android developer just over two years ago, creating native apps\nfor clients using Java and Kotlin. During that time, Kotlin was gaining\nprominence and had just been made official by Google. Google also introduced\nArchitecture Components that year, later renamed to JetPack. Since then, the\nAndroid ecosystem has changed significantly, with Kotlin and JetPack gaining\nsignificant maturity and development. Out with Realm, Activities, and\nModel-View-Presenter (MVP), in with Room, fragment-based architecture, and MVVM.\n<a href=\"/2020/05/06/android-complete-recyclerview-data-binding/\">Data-binding</a> and\nMVVM are pretty awesome and breathe a whole new life into Android app development.</p>\n<h2 id=\"what-is-forummate\" tabindex=\"-1\">What is ForumMate? <a class=\"header-anchor\" href=\"#what-is-forummate\">#</a></h2>\n<p>In order to get back up to speed with Android development, I needed a project to\nwork on. I decided to make a forum/bulletin board aggregator app, which uses\nHTML parsing to get data from legacy forum websites and display it using a nice\nuser interface.</p>\n<p><a href=\"https://gitlab.com/rubenwardy/forummate\">ForumMate</a> allows the user to log into\na phpBB forum using a WebView, where the app captures the session cookies. The\nuser can either select a forum from a list fetched using a REST API, or log into\nany forum by navigating to it on Google search.</p>\n<p>These session cookies are stored and used in future HTTP requests, where they are\nparsed using JSoup. Results are stored in a Room database, with the interface\nsubscribing to LiveData.</p>\n<h3 id=\"screenshots\" tabindex=\"-1\">Screenshots <a class=\"header-anchor\" href=\"#screenshots\">#</a></h3>\n<p>The user interface is a still a work in progress, but it works.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/MbopOpLpvX-255.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/SFIlXoCVRj-255.webp 255w\"><img src=\"/static/img/SFIlXoCVRj-255.png\" alt=\"List of available forum topics\" loading=\"lazy\" decoding=\"async\" width=\"255\" height=\"453\"></picture></a><figcaption>List of available forum topics</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/KUKc2q3LXK-255.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/Mm8-doWTE0-255.webp 255w\"><img src=\"/static/img/Mm8-doWTE0-255.png\" alt=\"Unread Topics\" loading=\"lazy\" decoding=\"async\" width=\"255\" height=\"453\"></picture></a><figcaption>Unread Topics</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/PNsJIFd7li-255.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/odDk8SyiX4-255.webp 255w\"><img src=\"/static/img/odDk8SyiX4-255.png\" alt=\"Posting a comment to a thread\" loading=\"lazy\" decoding=\"async\" width=\"255\" height=\"453\"></picture></a><figcaption>Posting a comment to a thread</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/hP4fjEoiLY-255.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/EIcedpq1Wf-255.webp 255w\"><img src=\"/static/img/EIcedpq1Wf-255.png\" alt=\"Accounts\" loading=\"lazy\" decoding=\"async\" width=\"255\" height=\"453\"></picture></a><figcaption>Accounts</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"architecture-and-technical-information\" tabindex=\"-1\">Architecture and Technical information <a class=\"header-anchor\" href=\"#architecture-and-technical-information\">#</a></h2>\n<p>The app is architectured using MVVM, with\n<a href=\"/2020/05/06/android-complete-recyclerview-data-binding/\">two-way data-binding</a>,\ndependency injection, and Navigation components.</p>\n<p>The code has 5 main zones: Views (ie: fragments), ViewModels, Repositories,\nDatabase (ie: Room), and the Backend. The backend consists of interfaces with an\nimplementation per forum software, used to interact with forums. The\nForumLoginStrategy interface is used to detect the forum software using the\n<a href=\"https://en.wikipedia.org/wiki/Strategy_pattern\">Strategy Pattern</a> and obtain\naccess information.</p>\n<figure class=\"undefined\"><a href=\"/static/img/o4BdQ4E-74-1308.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/K1dSqW5Gub-540.webp 540w\"><img src=\"/static/img/K1dSqW5Gub-540.png\" alt=\"Diagram showing the zones used by the login flow. Repositories are also used to access the database.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"54\"></picture></a><figcaption>Diagram showing the zones used by the login flow. Repositories are also used to access the database.</figcaption></figure>\n<h2 id=\"why-i-haven-t-published-it-on-google-play\" tabindex=\"-1\">Why I haven’t published it on Google Play <a class=\"header-anchor\" href=\"#why-i-haven-t-published-it-on-google-play\">#</a></h2>\n<p>I’ve decided not to publish ForumMate for two reasons:</p>\n<ul>\n<li><strong>Crawling raises legal concerns:</strong>\nWebsite owners are unlikely to like their sites being access by automated\ntools. There’s also a risk of it being considered copyright infringement,\nI’d have to look into this.</li>\n<li><strong>The difficulty of crawling takes too much focus:</strong>\nForum software varies massively, with different themes and backing software,\nwhich makes the parsing software quite brittle. Fixing this would take too\nmuch focus away from app development and places it on HTML parsing, which is\nnot what I want to achieve with this project.</li>\n</ul>\n<p>I’ve achieved what I intended to do with ForumMate - I’ve gotten back up to\nspeed with Android development.</p>\n","url":"https://blog.rubenwardy.com/2020/09/13/return-to-android-dev/","summary":"I created a forum aggregator app to relearn Android devevelopment, including databinding, View Models, LiveData, and more.","date_published":"2020-09-13T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/4gitHYSQ7m-540.png","tags":["projects","android","opinion"]},{"id":"/2020/07/26/sol3-script-sandbox/","language":"en","title":"Writing a Lua sandbox using sol2 / sol3","content_html":"<p>Sandboxing can protect the user’s computer from malicious or buggy scripts.\nBut sandboxes are difficult to get right; you need to be very careful with what\nyou expose, and make sure you test for vulnerabilities.\nThe <a href=\"http://lua-users.org/wiki/SandBoxes\">Sandboxes</a> on the Lua wiki is required\nreading, as it contains very helpful advice.</p>\n<h2 id=\"environments\" tabindex=\"-1\">Environments <a class=\"header-anchor\" href=\"#environments\">#</a></h2>\n<p>An environment is a table that stores the global variables available to a function.\nEach function will have an environment assigned to it, and we can use this to sandbox\ncode. To manage environments in sol3, you will use\n<a href=\"https://sol2.readthedocs.io/en/latest/api/environment.html\">sol::environment</a>.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token comment\">// lua is a `sol::state` or `sol::state_view`</span>\n\n<span class=\"token comment\">// Create new blank environment</span>\n<span class=\"token keyword\">auto</span> env <span class=\"token operator\">=</span> sol<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">environment</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>create<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Set global variable for globals</span>\nenv<span class=\"token punctuation\">[</span><span class=\"token string\">\"_G\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> env<span class=\"token punctuation\">;</span></code></pre>\n<p>To sandbox, you will want to create an environment with only whitelisted,\nsafe functions. Let’s list all the safe global functions:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>vector<span class=\"token operator\">&lt;</span>std<span class=\"token double-colon punctuation\">::</span>string<span class=\"token operator\">></span> whitelisted <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token string\">\"assert\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"ipairs\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"next\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"pairs\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"pcall\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"print\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"select\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"tonumber\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"tostring\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"type\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"unpack\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"_VERSION\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"xpcall\"</span><span class=\"token punctuation\">,</span>\n\n    <span class=\"token comment\">// These functions are unsafe as they can bypass or change metatables,</span>\n    <span class=\"token comment\">// but they are required to implement classes.</span>\n    <span class=\"token string\">\"rawequal\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"rawget\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"rawset\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"setmetatable\"</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Now, let’s copy the whitelisted globals into the environment:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> <span class=\"token keyword\">auto</span> <span class=\"token operator\">&amp;</span>name <span class=\"token operator\">:</span> whitelisted<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    env<span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Next, you’ll want to define and copy whitelisted modules.\nWe didn’t include these in the above list, as we want to copy the tables themselves.\nThis prevents changes that untrusted code makes to modules from affecting trusted code.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">std<span class=\"token double-colon punctuation\">::</span>vector<span class=\"token operator\">&lt;</span>std<span class=\"token double-colon punctuation\">::</span>string<span class=\"token operator\">></span> safeLibraries <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token string\">\"coroutine\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"string\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"table\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"math\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> <span class=\"token keyword\">auto</span> <span class=\"token operator\">&amp;</span>name <span class=\"token operator\">:</span> safeLibraries<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    sol<span class=\"token double-colon punctuation\">::</span>table <span class=\"token function\">copy</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>create<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">auto</span> pair <span class=\"token operator\">:</span> lua<span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token comment\">// first is the name of a function in module, second is the function</span>\n        copy<span class=\"token punctuation\">[</span>pair<span class=\"token punctuation\">.</span>first<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> pair<span class=\"token punctuation\">.</span>second<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    env<span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> copy<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Finally, you’ll want to partially copy modules that contain unsafe functions:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">sol<span class=\"token double-colon punctuation\">::</span>table <span class=\"token function\">os</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>create<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nos<span class=\"token punctuation\">[</span><span class=\"token string\">\"clock\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">[</span><span class=\"token string\">\"os\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"clock\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\nos<span class=\"token punctuation\">[</span><span class=\"token string\">\"date\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">[</span><span class=\"token string\">\"os\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"date\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\nos<span class=\"token punctuation\">[</span><span class=\"token string\">\"difftime\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">[</span><span class=\"token string\">\"os\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"difftime\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\nos<span class=\"token punctuation\">[</span><span class=\"token string\">\"time\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">[</span><span class=\"token string\">\"os\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"time\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\nenv<span class=\"token punctuation\">[</span><span class=\"token string\">\"os\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> os<span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"safe-loadstring-loadfile-and-dofile\" tabindex=\"-1\">Safe loadstring, loadfile, and dofile <a class=\"header-anchor\" href=\"#safe-loadstring-loadfile-and-dofile\">#</a></h2>\n<h3 id=\"what\" tabindex=\"-1\">What? <a class=\"header-anchor\" href=\"#what\">#</a></h3>\n<p>Let’s first revise what each function does:</p>\n<ul>\n<li><a href=\"https://www.lua.org/manual/5.1/manual.html#pdf-loadstring\">loadstring</a>(string [, chunkname])\n<ul>\n<li>Attempts to load <code>string</code> as a function.</li>\n<li>Returns either the function, or <code>nil, error_msg</code>.</li>\n</ul>\n</li>\n<li><a href=\"https://www.lua.org/manual/5.1/manual.html#pdf-loadfile\">loadfile</a>(path)\n<ul>\n<li>Attempts to load file <code>file</code> as a function.</li>\n<li>Returns either the function, or <code>nil, error_msg</code>.</li>\n</ul>\n</li>\n<li><a href=\"https://www.lua.org/manual/5.1/manual.html#pdf-dofile\">dofile</a>(path)\n<ul>\n<li>Loads from the file, and executes it. Returns the return value of the file.</li>\n<li>If there is an error, it is not caught by <code>dofile</code>.</li>\n</ul>\n</li>\n</ul>\n<p>We need to provide safe implementations of each of these functions.\nWe will do this be making sure that the following things are checked:</p>\n<ol>\n<li>The environment is set on any functions. The default <code>loadstring</code> will set the\nglobal environment on the returned function, which allows escaping the sandbox.</li>\n<li>Bytecode cannot be loaded, as it can be used to escape the sandbox.</li>\n<li>In loadfile, we check that the file path is within the sandbox path.</li>\n</ol>\n<h3 id=\"loadstring\" tabindex=\"-1\">loadstring <a class=\"header-anchor\" href=\"#loadstring\">#</a></h3>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">std<span class=\"token double-colon punctuation\">::</span>tuple<span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token operator\">></span> <span class=\"token class-name\">LuaSecurity</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">loadstring</span><span class=\"token punctuation\">(</span>\n        <span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token operator\">&amp;</span>str<span class=\"token punctuation\">,</span> <span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token operator\">&amp;</span>chunkname<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>str<span class=\"token punctuation\">.</span><span class=\"token function\">empty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&amp;&amp;</span> str<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">==</span> LUA_SIGNATURE<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_tuple</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>nil<span class=\"token punctuation\">,</span>\n                sol<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_object</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> <span class=\"token string\">\"Bytecode prohibited by Lua sandbox\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>load_result result <span class=\"token operator\">=</span> lua<span class=\"token punctuation\">.</span><span class=\"token function\">load</span><span class=\"token punctuation\">(</span>str<span class=\"token punctuation\">,</span> chunkname<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>load_mode<span class=\"token double-colon punctuation\">::</span>text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">.</span><span class=\"token function\">valid</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        sol<span class=\"token double-colon punctuation\">::</span>function func <span class=\"token operator\">=</span> result<span class=\"token punctuation\">;</span>\n        env<span class=\"token punctuation\">.</span><span class=\"token function\">set_on</span><span class=\"token punctuation\">(</span>func<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">return</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_tuple</span><span class=\"token punctuation\">(</span>func<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>nil<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_tuple</span><span class=\"token punctuation\">(</span>\n                sol<span class=\"token double-colon punctuation\">::</span>nil<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_object</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>error<span class=\"token punctuation\">)</span>result<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">what</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p><code>LUA_SIGNATURE</code> is the character used to indicate that some source code is\nbytecode.</p>\n<p><code>env.set_on(func)</code> is used to set the environment.</p>\n<h3 id=\"loadfile\" tabindex=\"-1\">loadfile <a class=\"header-anchor\" href=\"#loadfile\">#</a></h3>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">std<span class=\"token double-colon punctuation\">::</span>tuple<span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token operator\">></span> <span class=\"token class-name\">LuaSecurity</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">loadfile</span><span class=\"token punctuation\">(</span>\n        <span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token operator\">&amp;</span>path<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span><span class=\"token function\">checkPath</span><span class=\"token punctuation\">(</span>path<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_tuple</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>nil<span class=\"token punctuation\">,</span>\n                sol<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">make_object</span><span class=\"token punctuation\">(</span>\n                        lua<span class=\"token punctuation\">,</span> <span class=\"token string\">\"Path is not allowed by the Lua sandbox\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    std<span class=\"token double-colon punctuation\">::</span>ifstream <span class=\"token function\">t</span><span class=\"token punctuation\">(</span>path<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token function\">str</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">istreambuf_iterator</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">char</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>t<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n            std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">istreambuf_iterator</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">char</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">loadstring</span><span class=\"token punctuation\">(</span>str<span class=\"token punctuation\">,</span> <span class=\"token string\">\"@\"</span> <span class=\"token operator\">+</span> path<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>checkPath is a method that will be used later to verify that <code>path</code> is allowed,\nfor now it can be defined to always return true.</p>\n<h3 id=\"dofile\" tabindex=\"-1\">dofile <a class=\"header-anchor\" href=\"#dofile\">#</a></h3>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">sol<span class=\"token double-colon punctuation\">::</span>object <span class=\"token class-name\">LuaSecurity</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">dofile</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token operator\">&amp;</span>path<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    std<span class=\"token double-colon punctuation\">::</span>tuple<span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>object<span class=\"token operator\">></span> ret <span class=\"token operator\">=</span> <span class=\"token function\">loadfile</span><span class=\"token punctuation\">(</span>path<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">get</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token number\">0</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>ret<span class=\"token punctuation\">)</span> <span class=\"token operator\">==</span> sol<span class=\"token double-colon punctuation\">::</span>nil<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">throw</span> sol<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">error</span><span class=\"token punctuation\">(</span>std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">get</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token number\">1</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>ret<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token generic-function\"><span class=\"token function\">as</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span>std<span class=\"token double-colon punctuation\">::</span>string<span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>unsafe_function func <span class=\"token operator\">=</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">get</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token number\">0</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>ret<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">func</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p><code>dofile</code> will need to check the load result, and run the function in unsafe mode.</p>\n<h3 id=\"adding-to-the-environment\" tabindex=\"-1\">Adding to the environment <a class=\"header-anchor\" href=\"#adding-to-the-environment\">#</a></h3>\n<p>Don’t forget to actually set them on the environment!</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">env<span class=\"token punctuation\">.</span><span class=\"token function\">set_function</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"loadstring\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>LuaSecurity<span class=\"token double-colon punctuation\">::</span>loadstring<span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nenv<span class=\"token punctuation\">.</span><span class=\"token function\">set_function</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"loadfile\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>LuaSecurity<span class=\"token double-colon punctuation\">::</span>loadfile<span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nenv<span class=\"token punctuation\">.</span><span class=\"token function\">set_function</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"dofile\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>LuaSecurity<span class=\"token double-colon punctuation\">::</span>dofile<span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"running-scripts-safely\" tabindex=\"-1\">Running scripts safely <a class=\"header-anchor\" href=\"#running-scripts-safely\">#</a></h2>\n<p>The easiest way to run a script safely is to pass the environment into <code>script_file</code>:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">lua<span class=\"token punctuation\">.</span><span class=\"token function\">script_file</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"mods/mymod/init.lua\"</span><span class=\"token punctuation\">,</span> security<span class=\"token operator\">-></span><span class=\"token function\">getEnvironment</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"setting-the-global-environment\" tabindex=\"-1\">Setting the global environment <a class=\"header-anchor\" href=\"#setting-the-global-environment\">#</a></h2>\n<p>In order to safely execute our scripts, we need to remember to set the safe\nenvironment. Wouldn’t it be nicer to change the default environment in Lua?</p>\n<p>The default environment in Lua is stored in a registry value, and so can be assigned\nlike so:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">if</span> <span class=\"token expression\">LUA_VERSION_NUM <span class=\"token operator\">>=</span> <span class=\"token number\">502</span></span></span>\n    <span class=\"token comment\">// Get environment registry index</span>\n    <span class=\"token function\">lua_rawgeti</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> LUA_REGISTRYINDEX<span class=\"token punctuation\">,</span> env<span class=\"token punctuation\">.</span><span class=\"token function\">registry_index</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Set the global environment</span>\n    <span class=\"token function\">lua_rawseti</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> LUA_REGISTRYINDEX<span class=\"token punctuation\">,</span> LUA_RIDX_GLOBALS<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">else</span></span>\n    <span class=\"token comment\">// Get main thread</span>\n    <span class=\"token keyword\">int</span> is_main <span class=\"token operator\">=</span> <span class=\"token function\">lua_pushthread</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>is_main<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">int</span> thread <span class=\"token operator\">=</span> <span class=\"token function\">lua_gettop</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Get environment registry index</span>\n    <span class=\"token function\">lua_rawgeti</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> LUA_REGISTRYINDEX<span class=\"token punctuation\">,</span> env<span class=\"token punctuation\">.</span><span class=\"token function\">registry_index</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Set the global environment</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span><span class=\"token function\">lua_setfenv</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> thread<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">throw</span> <span class=\"token function\">ModException</span><span class=\"token punctuation\">(</span>\n                <span class=\"token string\">\"Security: Unable to set environment of the main Lua thread!\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">lua_pop</span><span class=\"token punctuation\">(</span>lua<span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Pop thread</span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">endif</span></span></code></pre>\n<p>Unfortunately, the preprocessors to support multiple versions of Lua makes this\nugly. If you’re only targeting a specific version, you can remove the unused branch.</p>\n<p>We can now safely load scripts directly, without specifying the environment:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">lua<span class=\"token punctuation\">.</span><span class=\"token function\">script_file</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"mods/mymod/init.lua\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<h2 id=\"checking-file-paths\" tabindex=\"-1\">Checking file paths <a class=\"header-anchor\" href=\"#checking-file-paths\">#</a></h2>\n<p>Ideally, you’d not allow any file system access to untrusted scripts. You can\nuse virtual file systems to load all allowed resources into memory, and then only\nread from memory.</p>\n<p>However, sometimes the scripts aren’t totally untrusted, and you would like to allow\nsome access to the file system. To do this, you can check the path to make sure it’s\nin an allowed location. Note that this isn’t completely safe, symlinks can be used\nto escaped the allowed path - however, if scripts can’t make symlinks then\nit’s the user’s stupidity.</p>\n<p>C++17’s filesystem provides useful path-parsing methods:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">bool</span> <span class=\"token class-name\">LuaSecurity</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">checkPath</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> std<span class=\"token double-colon punctuation\">::</span>string <span class=\"token operator\">&amp;</span>filepath<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>basePath<span class=\"token punctuation\">.</span><span class=\"token function\">empty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">auto</span> base <span class=\"token operator\">=</span> std<span class=\"token double-colon punctuation\">::</span>filesystem<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">absolute</span><span class=\"token punctuation\">(</span>basePath<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">lexically_normal</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">auto</span> path <span class=\"token operator\">=</span> std<span class=\"token double-colon punctuation\">::</span>filesystem<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">absolute</span><span class=\"token punctuation\">(</span>filepath<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">lexically_normal</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">auto</span> <span class=\"token punctuation\">[</span>rootEnd<span class=\"token punctuation\">,</span> nothing<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span>\n            std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">mismatch</span><span class=\"token punctuation\">(</span>base<span class=\"token punctuation\">.</span><span class=\"token function\">begin</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> base<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> path<span class=\"token punctuation\">.</span><span class=\"token function\">begin</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> rootEnd <span class=\"token operator\">==</span> base<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>You may wish to extend this to add multiple base paths, and also add a way\nto make some paths readonly.</p>\n<h2 id=\"preventing-infinite-loops\" tabindex=\"-1\">Preventing infinite loops <a class=\"header-anchor\" href=\"#preventing-infinite-loops\">#</a></h2>\n<p>You can use <code>lua_sethook</code> to run a callback after a number of instructions,\nand then raise a Lua error.</p>\n<p><a href=\"https://stackoverflow.com/questions/2777527/stopping-a-runaway-lua-subprocess\">https://stackoverflow.com/questions/2777527/stopping-a-runaway-lua-subprocess</a></p>\n<h2 id=\"summary\" tabindex=\"-1\">Summary <a class=\"header-anchor\" href=\"#summary\">#</a></h2>\n<p>I hope you found this article useful. This doesn’t cover all possible exploits -\nuntrusted code may still crash or freeze the program - but it aims to at least\nprotect the host from the code.</p>\n<p>I’d like to finish by reminding you to add sandbox unit tests to make sure that\nit’s working correctly, and you don’t accidentally break it. This can be as\nsimple as some <code>assert</code>s in a builtin Lua file somewhere.</p>\n","url":"https://blog.rubenwardy.com/2020/07/26/sol3-script-sandbox/","summary":"Sandboxing Lua protects the user's computer from malicious or buggy scripts. This tutorial shows how to do this using sol2 v3.0.","date_published":"2020-07-26T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/ux578zbL2b-540.png","tags":["tutorials","gamedev","cpp"]},{"id":"/2020/07/22/sol3-vector-extension/","language":"en","title":"Extending sol3's implicit type conversion","content_html":"<p>Many APIs in my game push <code>Vector3</code>s to and from Lua. It’s such a common operation,\nthat most of my functions used to look like this:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">sol<span class=\"token double-colon punctuation\">::</span>table <span class=\"token function\">add</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>table tPos<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    Vector3f pos <span class=\"token operator\">=</span> <span class=\"token function\">TableToPos</span><span class=\"token punctuation\">(</span>tPos<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// do something</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">PosToTable</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>One of the benefits of sol is that it is able to bind Lua arguments to C++ function\narguments, converting types implicitly. Having to convert from a table to a vector\nourselves is quite annoying. It would be much nicer to have sol do it for us.\nLuckily, sol allows you to customise how types are retrieved and pushed to Lua\nusing <a href=\"https://sol2.readthedocs.io/en/latest/tutorial/customization.html\">Customisation Points</a>.</p>\n<p>When trying to convert a type from Lua to C++, sol will call certain templated functions.\nWe will be customisating sol’s behaviour using a technique called template specialization,\nwhich allows us to specialise a specific instance of the templated functions and structs.\nBy the end of this article, we’ll be able to use <code>Vector3</code> directly when using sol,\nallowing the above code to be turned into this:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">Vector3f <span class=\"token function\">add</span><span class=\"token punctuation\">(</span>Vector3f pos<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// do something</span>\n\n    <span class=\"token keyword\">return</span> pos<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"sol-lua-get\" tabindex=\"-1\">sol_lua_get <a class=\"header-anchor\" href=\"#sol-lua-get\">#</a></h2>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">namespace</span> sol <span class=\"token punctuation\">{</span>\n\n<span class=\"token keyword\">template</span> <span class=\"token operator\">&lt;</span><span class=\"token keyword\">typename</span> <span class=\"token class-name\">T</span><span class=\"token operator\">></span>\n<span class=\"token keyword\">inline</span> Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span> <span class=\"token function\">sol_lua_get</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>types<span class=\"token operator\">&lt;</span>Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">>></span><span class=\"token punctuation\">,</span> lua_State <span class=\"token operator\">*</span>L<span class=\"token punctuation\">,</span> <span class=\"token keyword\">int</span> index<span class=\"token punctuation\">,</span>\n        sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span>record <span class=\"token operator\">&amp;</span>tracking<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">int</span> absoluteIndex <span class=\"token operator\">=</span> <span class=\"token function\">lua_absindex</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> index<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>table table <span class=\"token operator\">=</span> sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">get</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>table<span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> absoluteIndex<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    T x <span class=\"token operator\">=</span> table<span class=\"token punctuation\">[</span><span class=\"token string\">\"x\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    T y <span class=\"token operator\">=</span> table<span class=\"token punctuation\">[</span><span class=\"token string\">\"y\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    T z <span class=\"token operator\">=</span> table<span class=\"token punctuation\">[</span><span class=\"token string\">\"z\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n    tracking<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span> x<span class=\"token punctuation\">,</span> y<span class=\"token punctuation\">,</span> z <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p><code>sol_lua_get</code> is the function used to convert from Lua to C++.</p>\n<p>The first argument of this function is a dummy argument used by the meta-programming\nto select the correct function to use. The tracking record argument is used to tell\nsol what you did in this function. In this particular case, we only read a single\nargument from the stack, so we call:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">tracking<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>The example in the sol tutorial uses two arguments, and so calls <code>use(2)</code>.</p>\n<p>Note that it’s important to do this specialisation inside the same namespace\nas the original templated function. This is something that’s missed inside the\nsol tutorial.</p>\n<h2 id=\"sol-lua-push\" tabindex=\"-1\">sol_lua_push <a class=\"header-anchor\" href=\"#sol-lua-push\">#</a></h2>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">template</span> <span class=\"token operator\">&lt;</span><span class=\"token keyword\">typename</span> <span class=\"token class-name\">T</span><span class=\"token operator\">></span>\n<span class=\"token keyword\">inline</span> <span class=\"token keyword\">int</span> <span class=\"token function\">sol_lua_push</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>types<span class=\"token operator\">&lt;</span>Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">>></span><span class=\"token punctuation\">,</span> lua_State <span class=\"token operator\">*</span>L<span class=\"token punctuation\">,</span> <span class=\"token keyword\">const</span> Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span> <span class=\"token operator\">&amp;</span>pos<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">lua_createtable</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token function\">lua_getglobal</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token string\">\"Vector\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">lua_setmetatable</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">2</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>stack_table <span class=\"token function\">vec</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    vec<span class=\"token punctuation\">[</span><span class=\"token string\">\"x\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> pos<span class=\"token punctuation\">.</span>x<span class=\"token punctuation\">;</span>\n    vec<span class=\"token punctuation\">[</span><span class=\"token string\">\"y\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> pos<span class=\"token punctuation\">.</span>y<span class=\"token punctuation\">;</span>\n    vec<span class=\"token punctuation\">[</span><span class=\"token string\">\"z\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> pos<span class=\"token punctuation\">.</span>z<span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p><code>sol_lua_push</code> is the function used to convert from C++ to Lua.</p>\n<p>Notice how stack_table is used to modify the table created by <code>lua_createtable</code>.\nThis code also sets the global Vector as a metatable on the table, this is useful\nif you have a Lua Vector class.</p>\n<h2 id=\"lua-type-of\" tabindex=\"-1\">lua_type_of <a class=\"header-anchor\" href=\"#lua-type-of\">#</a></h2>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">template</span> <span class=\"token operator\">&lt;</span><span class=\"token keyword\">typename</span> <span class=\"token class-name\">T</span><span class=\"token operator\">></span>\n<span class=\"token keyword\">struct</span> <span class=\"token class-name\">lua_type_of</span><span class=\"token operator\">&lt;</span>Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">>></span>\n        <span class=\"token operator\">:</span> std<span class=\"token double-colon punctuation\">::</span>integral_constant<span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>type<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>type<span class=\"token double-colon punctuation\">::</span>table<span class=\"token operator\">></span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n<p>This is a <a href=\"https://sol2.readthedocs.io/en/latest/api/types.html#type-traits\">type trait</a>\nused to tell sol that the Lua type should be a table.</p>\n<h2 id=\"sol-lua-check\" tabindex=\"-1\">sol_lua_check <a class=\"header-anchor\" href=\"#sol-lua-check\">#</a></h2>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">template</span> <span class=\"token operator\">&lt;</span><span class=\"token keyword\">typename</span> <span class=\"token class-name\">Handler</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">typename</span> <span class=\"token class-name\">T</span><span class=\"token operator\">></span>\n<span class=\"token keyword\">inline</span> <span class=\"token keyword\">bool</span> <span class=\"token function\">sol_lua_check</span><span class=\"token punctuation\">(</span>sol<span class=\"token double-colon punctuation\">::</span>types<span class=\"token operator\">&lt;</span>Vector3<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">>></span><span class=\"token punctuation\">,</span> lua_State <span class=\"token operator\">*</span>L<span class=\"token punctuation\">,</span> <span class=\"token keyword\">int</span> index<span class=\"token punctuation\">,</span>\n        Handler <span class=\"token operator\">&amp;&amp;</span>handler<span class=\"token punctuation\">,</span> sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span>record <span class=\"token operator\">&amp;</span>tracking<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">int</span> absoluteIndex <span class=\"token operator\">=</span> <span class=\"token function\">lua_absindex</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> index<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">check</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span>sol<span class=\"token double-colon punctuation\">::</span>table<span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> absoluteIndex<span class=\"token punctuation\">,</span> handler<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        tracking<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">return</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">get_field</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token string\">\"x\"</span><span class=\"token punctuation\">,</span> absoluteIndex<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">bool</span> x <span class=\"token operator\">=</span> sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">check</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">float</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">get_field</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token string\">\"y\"</span><span class=\"token punctuation\">,</span> absoluteIndex<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">bool</span> y <span class=\"token operator\">=</span> sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">check</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">float</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">get_field</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token string\">\"z\"</span><span class=\"token punctuation\">,</span> absoluteIndex<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">bool</span> z <span class=\"token operator\">=</span> sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">check</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">float</span><span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token operator\">-</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    sol<span class=\"token double-colon punctuation\">::</span>stack<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">pop_n</span><span class=\"token punctuation\">(</span>L<span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    tracking<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> x <span class=\"token operator\">&amp;&amp;</span> y <span class=\"token operator\">&amp;&amp;</span> z<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p><code>sol_lua_check</code> is the function used to determine whether a stack value is\nof the correct type, and can be converted. In this case, we check that it’s\na table and it has the required fields.</p>\n<h2 id=\"wait-how-do-i-actually-use-this\" tabindex=\"-1\">Wait, how do I actually use this? <a class=\"header-anchor\" href=\"#wait-how-do-i-actually-use-this\">#</a></h2>\n<p>It should all be defined in a header after you include <code>sol.hpp</code>.</p>\n<p>I like to have a <code>Lua.hpp</code> header with the following content:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">pragma</span> <span class=\"token expression\">once</span></span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">define</span> <span class=\"token macro-name\">SOL_ALL_SAFETIES_ON</span> <span class=\"token expression\"><span class=\"token number\">1</span></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;sol/sol.hpp></span></span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;types/Vector3.hpp></span></span>\n\n<span class=\"token keyword\">namespace</span> sol <span class=\"token punctuation\">{</span>\n\n<span class=\"token comment\">// All the specialisations here:</span>\n<span class=\"token comment\">//   sol_lua_get, sol_lua_push sol_lua_check, lua_type_of</span>\n\n<span class=\"token punctuation\">}</span>\n</code></pre>\n<p>Instead of including <code>sol.hpp</code> in other files, I include <code>Lua.hpp</code>. This makes sure\nthat sol receives the same defines and the same specialisations each time.</p>\n","url":"https://blog.rubenwardy.com/2020/07/22/sol3-vector-extension/","summary":"How to let sol3 automatically create Lua types based on C++ types when binding to a function. This is super useful for a Vector3 type.","date_published":"2020-07-22T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/Zgf-JpNBdO-540.png","tags":["tutorials","gamedev","cpp"]},{"id":"/2020/05/12/sfml-drop-shadow/","language":"en","title":"Drop shadows in SFML","content_html":"<p>Drop shadows are created using a\n<a href=\"https://en.wikipedia.org/wiki/Gaussian_blur\">Gaussian blur</a>\ndrawn underneath the original element. You can do this using either a fixed\ntexture or a post-processing fragment shader.</p>\n<h2 id=\"texture-based-approach\" tabindex=\"-1\">Texture-based Approach <a class=\"header-anchor\" href=\"#texture-based-approach\">#</a></h2>\n<p>If you do not need to have dynamic drop shadows, you should consider baking the\nshadow into a texture and directly rendering that. However, a static shadow is\nnot suitable for things like text drop shadows, which will change drastically\ndepending on user input.</p>\n<h2 id=\"shader-based-approach\" tabindex=\"-1\">Shader-based Approach <a class=\"header-anchor\" href=\"#shader-based-approach\">#</a></h2>\n<p>The blur is done in two passes - horizontal and vertical. A pass blurs each\npixel using the weighted sum of its neighbours along a direction, up to some\nradius. Each pass is done using the same shader, which is given different values\nfor direction.</p>\n<figure class=\"undefined\"><a href=\"/static/img/6dQ-80Q1rs-537.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/cIZdCJBTdw-537.webp 537w\"><img src=\"/static/img/cIZdCJBTdw-537.png\" alt=\"A text drawn with and without a drop shadow\" loading=\"lazy\" decoding=\"async\" width=\"537\" height=\"288\"></picture></a><figcaption>A text drawn with and without a drop shadow</figcaption></figure>\n<pre class=\"language-glsl\"><code class=\"language-glsl\"><span class=\"token keyword\">uniform</span> <span class=\"token keyword\">sampler2D</span> source<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">uniform</span> <span class=\"token keyword\">vec4</span> mask<span class=\"token punctuation\">;</span> <span class=\"token comment\">// shadow color</span>\n<span class=\"token keyword\">uniform</span> <span class=\"token keyword\">vec2</span> direction<span class=\"token punctuation\">;</span> <span class=\"token comment\">// step size</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">vec2</span> textureCoordinates <span class=\"token operator\">=</span> gl_TexCoord<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>xy<span class=\"token punctuation\">;</span>\n\n\t<span class=\"token keyword\">vec4</span> color <span class=\"token operator\">=</span> <span class=\"token keyword\">vec4</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">-</span> <span class=\"token number\">4.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.0162162162</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">-</span> <span class=\"token number\">3.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.0540540541</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">-</span> <span class=\"token number\">2.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.1216216216</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">-</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.1945945946</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.2270270270</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">+</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.1945945946</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">+</span> <span class=\"token number\">2.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.1216216216</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">+</span> <span class=\"token number\">3.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.0540540541</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">+=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> textureCoordinates <span class=\"token operator\">+</span> <span class=\"token number\">4.0</span> <span class=\"token operator\">*</span> direction<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> <span class=\"token number\">0.0162162162</span><span class=\"token punctuation\">;</span>\n\tcolor <span class=\"token operator\">=</span> mask <span class=\"token operator\">*</span> <span class=\"token keyword\">vec4</span><span class=\"token punctuation\">(</span><span class=\"token number\">100.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">100.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">100.0</span><span class=\"token punctuation\">,</span> color<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">100.0</span><span class=\"token punctuation\">;</span>\n\n\tgl_FragColor <span class=\"token operator\">=</span> color<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>This needs to be done using post-processing. The way to do this in SFML\nis to render your scene to a RenderTexture, which you then render to the screen\nusing a fragment shader like so:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token comment\">// This is an example, `texture` is a RenderTexture</span>\nsf<span class=\"token double-colon punctuation\">::</span>Sprite <span class=\"token function\">sprite</span><span class=\"token punctuation\">(</span>texture<span class=\"token punctuation\">.</span><span class=\"token function\">getTexture</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nshadowShader<span class=\"token operator\">-></span><span class=\"token function\">setUniform</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"mask\"</span><span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span><span class=\"token class-name\">Glsl</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Vec4</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">50</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nshadowShader<span class=\"token operator\">-></span><span class=\"token function\">setUniform</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"direction\"</span><span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Vector2f</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> pixelSize<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nsf<span class=\"token double-colon punctuation\">::</span>RenderStates states <span class=\"token operator\">=</span> baseStates<span class=\"token punctuation\">;</span>\nstates<span class=\"token punctuation\">.</span>shader <span class=\"token operator\">=</span> shadowShader<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nstates<span class=\"token punctuation\">.</span>transform<span class=\"token punctuation\">.</span><span class=\"token function\">translate</span><span class=\"token punctuation\">(</span>pos<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\ntarget<span class=\"token punctuation\">.</span><span class=\"token function\">draw</span><span class=\"token punctuation\">(</span>sprite<span class=\"token punctuation\">,</span> states<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>The source drawable should be rendered to a temporary RenderTexture. This\nRenderTexture is then blurred in one direction to another RenderTexture.\nThis final RenderTexture is then blurred in the other direction to the screen.\nThe reason that you render the drawable to a RenderTexture <em>before</em> you first\nblur it is to make the fragment shader act like a post-processing shader - you\ndon’t want to act on the source drawable’s faces, but on the resulting pixels.</p>\n<p>Full example source code can be found here, with a helper class:</p>\n","url":"https://blog.rubenwardy.com/2020/05/12/sfml-drop-shadow/","summary":"Drop shadows are created using a Gaussian blur drawn underneath the original element. There's two methods to do this in SFML, depending on your requirements.","date_published":"2020-05-12T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/6dQ-80Q1rs-537.png","tags":["tutorials","sfml","cpp","gamedev"]},{"id":"/2020/05/06/android-complete-recyclerview-data-binding/","language":"en","title":"Android: Complete, generic data-binding RecyclerView adapter","content_html":"<p>Data binding greatly reduces the amount of code you need to connect user-interfaces\nwith ViewModels. It keeps Activity and Fragment code small, and makes it easier\nto manage lifecycles.</p>\n<pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>EditText</span>\n    <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@+id/username<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>wrap_content<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>match_parent<span class=\"token punctuation\">\"</span></span>\n    <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@={viewModel.username}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">/></span></span></code></pre>\n<p>I discovered that there was no attribute to bind the elements in a RecyclerView,\ndue to the fact that a RecyclerView needs an adapter to be able to create\nelement views. It would also be nice to automatically use data binding to create\nthe viewholders. There are a number of guides to do both of these halves, but I\nnow present the code to do the whole.</p>\n<p>This guide assumes you have at least a beginner’s knowledge of data binding,\nand have it enabled for your project.</p>\n<h2 id=\"1-generic-data-binding-recyclerview-adapter\" tabindex=\"-1\">1. Generic data-binding RecyclerView adapter <a class=\"header-anchor\" href=\"#1-generic-data-binding-recyclerview-adapter\">#</a></h2>\n<p>The BindingRecyclerAdapter is a generic class which allows binding item\nholder views to the item ViewModel, and has a property to set the contained data.\nThis property will be used by data binding later.</p>\n<pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> BindingRecyclerAdapter<span class=\"token operator\">&lt;</span>T<span class=\"token punctuation\">,</span> V<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token keyword\">val</span> viewModel<span class=\"token operator\">:</span> V<span class=\"token punctuation\">,</span> <span class=\"token annotation builtin\">@LayoutRes</span> <span class=\"token keyword\">val</span> layout<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> RecyclerView<span class=\"token punctuation\">.</span>Adapter<span class=\"token operator\">&lt;</span>BindingRecyclerAdapter<span class=\"token punctuation\">.</span>ViewHolder<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">var</span> <span class=\"token keyword\">data</span><span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span> <span class=\"token operator\">=</span> <span class=\"token function\">emptyList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">set</span><span class=\"token punctuation\">(</span>v<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            field <span class=\"token operator\">=</span> v\n            <span class=\"token function\">notifyDataSetChanged</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">class</span> <span class=\"token function\">ViewHolder</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">val</span> binding<span class=\"token operator\">:</span> ViewDataBinding<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> RecyclerView<span class=\"token punctuation\">.</span><span class=\"token function\">ViewHolder</span><span class=\"token punctuation\">(</span>binding<span class=\"token punctuation\">.</span>root<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">onCreateViewHolder</span><span class=\"token punctuation\">(</span>parent<span class=\"token operator\">:</span> ViewGroup<span class=\"token punctuation\">,</span> viewType<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> ViewHolder <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">val</span> layoutInflater <span class=\"token operator\">=</span> LayoutInflater<span class=\"token punctuation\">.</span><span class=\"token function\">from</span><span class=\"token punctuation\">(</span>parent<span class=\"token punctuation\">.</span>context<span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">val</span> binding <span class=\"token operator\">=</span> DataBindingUtil<span class=\"token punctuation\">.</span><span class=\"token function\">inflate</span><span class=\"token punctuation\">(</span>layoutInflater<span class=\"token punctuation\">,</span> layout<span class=\"token punctuation\">,</span> parent<span class=\"token punctuation\">,</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> <span class=\"token function\">ViewHolder</span><span class=\"token punctuation\">(</span>binding<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">getItemCount</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Int <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">data</span><span class=\"token punctuation\">.</span>size\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">onBindViewHolder</span><span class=\"token punctuation\">(</span>holder<span class=\"token operator\">:</span> ViewHolder<span class=\"token punctuation\">,</span> position<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        holder<span class=\"token punctuation\">.</span>binding<span class=\"token punctuation\">.</span><span class=\"token function\">setVariable</span><span class=\"token punctuation\">(</span>BR<span class=\"token punctuation\">.</span>item<span class=\"token punctuation\">,</span> <span class=\"token keyword\">data</span><span class=\"token punctuation\">.</span><span class=\"token function\">getOrNull</span><span class=\"token punctuation\">(</span>position<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n        holder<span class=\"token punctuation\">.</span>binding<span class=\"token punctuation\">.</span><span class=\"token function\">setVariable</span><span class=\"token punctuation\">(</span>BR<span class=\"token punctuation\">.</span>viewModel<span class=\"token punctuation\">,</span> viewModel<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"2-binding-adapter\" tabindex=\"-1\">2. Binding adapter <a class=\"header-anchor\" href=\"#2-binding-adapter\">#</a></h2>\n<p>Now, we need to create a binding adapter to handle the <code>data</code> attribute:</p>\n<pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token annotation builtin\">@BindingAdapter</span><span class=\"token punctuation\">(</span><span class=\"token string-literal singleline\"><span class=\"token string\">\"data\"</span></span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">fun</span> <span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span> <span class=\"token function\">setRecyclerViewProperties</span><span class=\"token punctuation\">(</span>recyclerView<span class=\"token operator\">:</span> RecyclerView<span class=\"token punctuation\">,</span> <span class=\"token keyword\">data</span><span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span><span class=\"token operator\">?</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">data</span> <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>recyclerView<span class=\"token punctuation\">.</span>adapter <span class=\"token keyword\">is</span> BindingRecyclerAdapter<span class=\"token operator\">&lt;</span><span class=\"token operator\">*</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">*</span><span class=\"token operator\">></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token punctuation\">(</span>recyclerView<span class=\"token punctuation\">.</span>adapter <span class=\"token keyword\">as</span> BindingRecyclerAdapter<span class=\"token operator\">&lt;</span>T<span class=\"token punctuation\">,</span> <span class=\"token operator\">*</span><span class=\"token operator\">></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>data <span class=\"token operator\">=</span> <span class=\"token keyword\">data</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"3-parent-view\" tabindex=\"-1\">3. Parent View <a class=\"header-anchor\" href=\"#3-parent-view\">#</a></h2>\n<p>First, you will need to add the ViewModel to the layout data section.\nLayouts with data-binding need to have <code>&lt;layout&gt;</code> as the outer-most view.</p>\n<pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>layout</span> <span class=\"token attr-name\"><span class=\"token namespace\">xmlns:</span>android</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.android.com/apk/res/android<span class=\"token punctuation\">\"</span></span>\n        <span class=\"token attr-name\"><span class=\"token namespace\">xmlns:</span>tools</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.android.com/tools<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>data</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>variable</span>\n                <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>viewModel<span class=\"token punctuation\">\"</span></span>\n                <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.example.app.viewmodels.MyListViewModel<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>data</span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>androidx.recyclerview.widget.RecyclerView</span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@+id/my_list<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>scrollbars</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>vertical<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>match_parent<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>match_parent<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">app:</span>data</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@{viewModel.items}<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>layout</span><span class=\"token punctuation\">></span></span></code></pre>\n<p>Next, let’s set up our RecyclerView to use the the new BindingRecyclerAdapter:</p>\n<pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">onViewCreated</span><span class=\"token punctuation\">(</span>view<span class=\"token operator\">:</span> View<span class=\"token punctuation\">,</span> savedInstanceState<span class=\"token operator\">:</span> Bundle<span class=\"token operator\">?</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">super</span><span class=\"token punctuation\">.</span><span class=\"token function\">onViewCreated</span><span class=\"token punctuation\">(</span>view<span class=\"token punctuation\">,</span> savedInstanceState<span class=\"token punctuation\">)</span>\n\n    binding<span class=\"token punctuation\">.</span><span class=\"token function\">apply</span> <span class=\"token punctuation\">{</span>\n        myList<span class=\"token punctuation\">.</span><span class=\"token function\">apply</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token function\">setHasFixedSize</span><span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span>\n            adapter <span class=\"token operator\">=</span> BindingRecyclerAdapter<span class=\"token operator\">&lt;</span>Item<span class=\"token operator\">></span><span class=\"token punctuation\">(</span>R<span class=\"token punctuation\">.</span>layout<span class=\"token punctuation\">.</span>item<span class=\"token punctuation\">)</span>\n            layoutManager <span class=\"token operator\">=</span> <span class=\"token function\">LinearLayoutManager</span><span class=\"token punctuation\">(</span>context<span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token comment\">// This is needed to subscribe to LiveData updates</span>\n        lifecycleOwner <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token label symbol\">@MyListFragment</span>\n        viewModel <span class=\"token operator\">=</span> myListViewModel\n        <span class=\"token function\">invalidateAll</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"4-item-layout\" tabindex=\"-1\">4. Item Layout <a class=\"header-anchor\" href=\"#4-item-layout\">#</a></h2>\n<pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>layout</span> <span class=\"token attr-name\"><span class=\"token namespace\">xmlns:</span>android</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.android.com/apk/res/android<span class=\"token punctuation\">\"</span></span>\n        <span class=\"token attr-name\"><span class=\"token namespace\">xmlns:</span>app</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.android.com/apk/res-auto<span class=\"token punctuation\">\"</span></span>\n        <span class=\"token attr-name\"><span class=\"token namespace\">xmlns:</span>tools</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>http://schemas.android.com/tools<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>data</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>import</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>android.view.View<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>variable</span>\n                <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>item<span class=\"token punctuation\">\"</span></span>\n                <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.example.app.models.My<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>variable</span>\n                <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>viewModel<span class=\"token punctuation\">\"</span></span>\n                <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.example.app.viewmodels.MyListViewModel<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>data</span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>TextView</span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@+id/user<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token special-attr\"><span class=\"token attr-name\">style</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span><span class=\"token value css language-css\">?attr/titleTextAppearance</span><span class=\"token punctuation\">\"</span></span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_width</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>match_parent<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_height</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>wrap_content<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>lines</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>1<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>layout_marginStart</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>16dp<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>@{item.author}<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">android:</span>textStyle</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>bold<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\"><span class=\"token namespace\">tools:</span>text</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Username<span class=\"token punctuation\">\"</span></span> <span class=\"token punctuation\">/></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>androidx.constraintlayout.widget.ConstraintLayout</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>layout</span><span class=\"token punctuation\">></span></span></code></pre>\n<h2 id=\"appendix-a-extending-to-add-an-empty-view\" tabindex=\"-1\">Appendix A: Extending to add an Empty View <a class=\"header-anchor\" href=\"#appendix-a-extending-to-add-an-empty-view\">#</a></h2>\n<p>You may find it useful to add an empty view.</p>\n<p>Add the following property to BindingRecyclerAdapter</p>\n<pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> BindingRecyclerAdapter<span class=\"token operator\">&lt;</span>T<span class=\"token punctuation\">,</span> V<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token keyword\">val</span> viewModel<span class=\"token operator\">:</span> V<span class=\"token punctuation\">,</span> <span class=\"token annotation builtin\">@LayoutRes</span> <span class=\"token keyword\">val</span> layout<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> RecyclerView<span class=\"token punctuation\">.</span>Adapter<span class=\"token operator\">&lt;</span>BindingRecyclerAdapter<span class=\"token punctuation\">.</span>ViewHolder<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">var</span> emptyView<span class=\"token operator\">:</span> View<span class=\"token operator\">?</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">null</span>\n        <span class=\"token keyword\">set</span><span class=\"token punctuation\">(</span>v<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            field <span class=\"token operator\">=</span> v\n            <span class=\"token function\">updateEmptyView</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">var</span> <span class=\"token keyword\">data</span><span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>T<span class=\"token operator\">></span> <span class=\"token operator\">=</span> <span class=\"token function\">emptyList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">set</span><span class=\"token punctuation\">(</span>v<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            field <span class=\"token operator\">=</span> v\n            <span class=\"token function\">notifyDataSetChanged</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n            <span class=\"token function\">updateEmptyView</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">updateEmptyView</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        emptyView<span class=\"token operator\">?</span><span class=\"token punctuation\">.</span>visibility <span class=\"token operator\">=</span> <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">data</span><span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> View<span class=\"token punctuation\">.</span>VISIBLE <span class=\"token keyword\">else</span> View<span class=\"token punctuation\">.</span>GONE\n    <span class=\"token punctuation\">}</span></code></pre>\n","url":"https://blog.rubenwardy.com/2020/05/06/android-complete-recyclerview-data-binding/","summary":"Data binding greatly reduces the amount of code you need to connect user-interfaces with ViewModels.","date_published":"2020-05-06T00:00:00.000Z","tags":["snippet","tutorials","android"]},{"id":"/2020/05/06/why-i-switched-to-gitlab/","language":"en","title":"Why I switched from GitHub to GitLab","content_html":"<p>Since switching to GitLab, a few people have asked me why. There are several\ndraws towards GitLab, and several pushes from GitHub.</p>\n<h2 id=\"1-pipelines\" tabindex=\"-1\">1. Pipelines <a class=\"header-anchor\" href=\"#1-pipelines\">#</a></h2>\n<h3 id=\"1-a-superior-continuous-integration-ci-delivery-cd\" tabindex=\"-1\">1.a. Superior Continuous Integration (CI) / Delivery (CD) <a class=\"header-anchor\" href=\"#1-a-superior-continuous-integration-ci-delivery-cd\">#</a></h3>\n<p>I don’t usually like it when services are bloated by having a large amount of\nfunctionality that would be better as separate programs. But I am a\npragmatist, and the ease of use of GitLab more than makes it up for me.\nThe ability to use <a href=\"https://gitlab.com/rubenwardy/docker-sfml-rvwp\">Docker images</a>\nin CI is so much better than what I was doing before.</p>\n<p>I’ve experimented with a few different CI/CD and GitLab Pipelines are by far the\neasiest I’ve found to set up and get working. It is also a lot faster than\nTravis, and allows you to host your own private Runners for projects, which is\nsuper nice.</p>\n<h3 id=\"1-b-better-static-pages\" tabindex=\"-1\">1.b. Better Static Pages <a class=\"header-anchor\" href=\"#1-b-better-static-pages\">#</a></h3>\n<p>GitLab Pages are published using their Pipelines system. This means that you\nhave a complete Linux container available to generate your static website,\nallowing custom plugins and code far beyond what GitHub Pages allows you. I have\na dedicated server, which means that I don’t need GitLab/GitHub pages as a host,\ninstead, I use them as a nice way to generate and make updating my static sites\neasy.</p>\n<h2 id=\"2-free-and-open-source\" tabindex=\"-1\">2. Free and Open Source <a class=\"header-anchor\" href=\"#2-free-and-open-source\">#</a></h2>\n<p>GitLab is free and open source; GitHub is not. I prefer to use free software\nwhere possible, which is reason enough. With GitLab, I can export my information\nin full from GitLab and move it to my own instance. Whilst GitHub does allow\nthis using its API, it would not be able to migrate to a self-hosted instance of\nthe same software.</p>\n<h2 id=\"3-i-ll-be-getting-used-to-gitlab-anyway\" tabindex=\"-1\">3. I’ll be getting used to GitLab, anyway <a class=\"header-anchor\" href=\"#3-i-ll-be-getting-used-to-gitlab-anyway\">#</a></h2>\n<p>A common argument against migrating is centered around GitHub being perceived as\neasier to use. Whilst the GitLab interface is slightly more cluttered, I think\nthat the perceived difference in use is much more due to users being used to the\nGitHub interface. This is no longer an issue for me as I will be getting used to\nit at my job.</p>\n<h2 id=\"4-no-microsoft\" tabindex=\"-1\">4. No Microsoft <a class=\"header-anchor\" href=\"#4-no-microsoft\">#</a></h2>\n<p>My main website and blog are in private repositories, and I wanted to keep it\nthat way. This would require paying for the GitHub Pro plan, but I do not want\nto directly contribute to Microsoft’s wealth. This was the final push towards\nGitLab.</p>\n","url":"https://blog.rubenwardy.com/2020/05/06/why-i-switched-to-gitlab/","summary":"Since switching to GitLab, a few people have asked me why. There are several draws towards GitLab, and several pushes from GitHub.","date_published":"2020-05-06T00:00:00.000Z","tags":["reviews","opinion"]},{"id":"/2020/01/21/creating-game-guis-in-sfml/","language":"en","title":"A Comparison of SFML GUI Libraries: TGUI vs SFGUI vs ImGui","content_html":"<p>SFML is an excellent library that can be used to create 2D games and similar\napplications in C++. It’s an abstraction over OpenGL and various system APIs,\npresenting a consistent and easy-to-use interface.</p>\n<p>Providing a Graphical User Interface (GUI / UI) API is out of scope for SFML.\nGUIs are complicated, and there’s no single good way to implement them.\nThe S in SFML stands for Simple but GUI code rarely is.</p>\n<p>There are many different options to choose from when making GUIs. This article\nis an in-depth comparison of the options for making GUIs in SFML, discussing\ntheir pros and cons.</p>\n<h2 id=\"homegrown-solution\" tabindex=\"-1\">Homegrown Solution <a class=\"header-anchor\" href=\"#homegrown-solution\">#</a></h2>\n<ul class=\"factlist\"><li class=\"good\">\n\t\t\tSimple\n\t\t</li><li class=\"bad\">\n\t\t\tReinventing the wheel\n\t\t</li></ul>\n<p>A full GUI library is likely to be overkill for most simple uses, such as those\nconsisting of buttons and text boxes. These can be implemented rather simply\nusing SFML’s graphics and input APIs. SFML already provides a nice\ncross-platform API for text input and clipboards.</p>\n<a class=\"btn is-secondary\" href=\"https://www.binpress.com/creating-city-building-game-with-sfml-gui-system/\">\n\tCreating a GUI system with SFML\n</a>\n<h2 id=\"dear-imgui\" tabindex=\"-1\">Dear ImGui <a class=\"header-anchor\" href=\"#dear-imgui\">#</a></h2>\n<ul class=\"factlist\"><li class=\"good\">\n\t\t\tEasy API\n\t\t</li><li class=\"good\">\n\t\t\tWell-maintained\n\t\t</li><li class=\"warning\">\n\t\t\tPoor theming\n\t\t</li><li class=\"info\">\n\t\t\tDesigned for prototypes and tools\n\t\t</li></ul>\n<p>ImGui is a very easy-to-use library designed for use in prototyping and tools,\nbut not for GUIs which are used by a typical end user, such as those that appear\nduring gameplay.</p>\n<p>It enables fast iteration, and prefers ease-of-use and simplicity over\nperformance and customisability.</p>\n<p>The <a href=\"https://en.wikipedia.org/wiki/Immediate_mode_GUI\">Immediate Mode</a> GUI\npattern combines the rendering and the event handling of a GUI element. This is\nin contrast to event-based GUI libraries, where you first set up the elements\nand then subscribe to receive events on them.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token class-name\">ImGui</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Text</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello, world %d\"</span><span class=\"token punctuation\">,</span> <span class=\"token number\">123</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token class-name\">ImGui</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Button</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Save\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token function\">MySaveFunction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token class-name\">ImGui</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">InputText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"string\"</span><span class=\"token punctuation\">,</span> buf<span class=\"token punctuation\">,</span> <span class=\"token function\">IM_ARRAYSIZE</span><span class=\"token punctuation\">(</span>buf<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token class-name\">ImGui</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">SliderFloat</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"float\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>f<span class=\"token punctuation\">,</span> <span class=\"token number\">0.0f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p class=\"btn-row\">\n\t<a class=\"btn is-secondary\" href=\"https://edw.is/using-imgui-with-sfml-pt1/\">ImGui Tutorial</a>\n\t<a class=\"btn is-secondary\" href=\"https://github.com/ocornut/imgui\">GitHub</a>\n</p>\n<h2 id=\"simple-fast-gui-sfgui\" tabindex=\"-1\">Simple Fast GUI (SFGUI) <a class=\"header-anchor\" href=\"#simple-fast-gui-sfgui\">#</a></h2>\n<ul class=\"factlist\"><li class=\"good\">\n\t\t\tWell-designed API\n\t\t</li><li class=\"warning\">\n\t\t\tPoor documentation\n\t\t</li><li class=\"bad\">\n\t\t\tUnmaintained\n\t\t</li><li class=\"info\">\n\t\t\tContainer-based\n\t\t</li></ul>\n<p>SFGUI provides a fully-functional automatic layouting system, with element\nbounds being allocated using an approach using\n<a href=\"https://github.com/TankOs/SFGUI/wiki/Size-policies\">requisition and allocation</a>.\nPut simply: child elements request a minimum size called a requisition, and then\nparents allocate the final size for their children. This approach makes making\nresponsive GUIs much easier.</p>\n<p>SFGUI doesn’t provide much documentation other than the tutorials. The source\nhas Doxygen documentation, but this isn’t hosted anywhere. Additionally, SFGUI\nonly receives the bare-minimum maintenance to remain working. The last update\nwhich implemented a feature or fixed a bug was in June 2018. Admittedly, SFGUI\nis pretty stable with very few noticeable bugs, but has some gaping omissions\nsuch as tooltip and copy+paste support.</p>\n<p>I didn’t get far enough to investigate the theming capabilities of SFGUI.</p>\n<p class=\"btn-row\">\n    <a class=\"btn is-secondary\" href=\"https://github.com/TankOs/SFGUI/\">GitHub</a>\n</p>\n<h2 id=\"texus-gui-tgui\" tabindex=\"-1\">Texus GUI (TGUI) <a class=\"header-anchor\" href=\"#texus-gui-tgui\">#</a></h2>\n<ul class=\"factlist\"><li class=\"good\">\n\t\t\tEasy API\n\t\t</li><li class=\"good\">\n\t\t\tWYSIWYG editor\n\t\t</li><li class=\"warning\">\n\t\t\tBad theming\n\t\t</li><li class=\"warning\">\n\t\t\tCustom DSLs\n\t\t</li><li class=\"warning\">\n\t\t\tPoor documentation\n\t\t</li><li class=\"warning\">\n\t\t\tSingle maintainer\n\t\t</li><li class=\"info\">\n\t\t\tConstraint-based\n\t\t</li></ul>\n<p>While Texus GUI does also provide some container-based formatting, such as a\ngrid element, it is predominantly a constraint-based method of implementing\nGUIs. The position and size of elements is controlled using a custom\ndomain-specific language (DSL).</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">widget<span class=\"token operator\">-></span><span class=\"token function\">setPosition</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token string\">\"ButtonName.right + 50\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"ButtonName.top\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nwidget<span class=\"token operator\">-></span><span class=\"token function\">setSize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token string\">\"min(&amp;.w, &amp;.h * 4/3)\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"min(&amp;.h, &amp;.w * 3/4)\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n<p>Styling is done using stylesheets written using another custom domain-specific\nlanguage. These stylesheets set up\n<a href=\"https://tgui.eu/tutorials/0.9/renderers/\">renderers</a> to contain properties\nabout the widget’s appearance.</p>\n<pre class=\"language-lua\"><code class=\"language-lua\"><span class=\"token function\">Button</span> <span class=\"token punctuation\">{</span>\n    Texture         <span class=\"token operator\">=</span> <span class=\"token string\">\"button.png\"</span> <span class=\"token function\">Part</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">104</span><span class=\"token punctuation\">,</span> <span class=\"token number\">190</span><span class=\"token punctuation\">,</span> <span class=\"token number\">49</span><span class=\"token punctuation\">)</span> <span class=\"token function\">Middle</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">170</span><span class=\"token punctuation\">,</span> <span class=\"token number\">29</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextureHover    <span class=\"token operator\">=</span> <span class=\"token string\">\"button.png\"</span> <span class=\"token function\">Part</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">155</span><span class=\"token punctuation\">,</span> <span class=\"token number\">190</span><span class=\"token punctuation\">,</span> <span class=\"token number\">45</span><span class=\"token punctuation\">)</span> <span class=\"token function\">Middle</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">170</span><span class=\"token punctuation\">,</span> <span class=\"token number\">25</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextureDown     <span class=\"token operator\">=</span> <span class=\"token string\">\"button.png\"</span> <span class=\"token function\">Part</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">202</span><span class=\"token punctuation\">,</span> <span class=\"token number\">190</span><span class=\"token punctuation\">,</span> <span class=\"token number\">45</span><span class=\"token punctuation\">)</span> <span class=\"token function\">Middle</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">170</span><span class=\"token punctuation\">,</span> <span class=\"token number\">25</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextureFocused  <span class=\"token operator\">=</span> <span class=\"token string\">\"button.png\"</span> <span class=\"token function\">Part</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">155</span><span class=\"token punctuation\">,</span> <span class=\"token number\">190</span><span class=\"token punctuation\">,</span> <span class=\"token number\">45</span><span class=\"token punctuation\">)</span> <span class=\"token function\">Middle</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">170</span><span class=\"token punctuation\">,</span> <span class=\"token number\">25</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextureDisabled <span class=\"token operator\">=</span> <span class=\"token string\">\"button.png\"</span> <span class=\"token function\">Part</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">195</span><span class=\"token punctuation\">,</span> <span class=\"token number\">49</span><span class=\"token punctuation\">)</span> <span class=\"token function\">Middle</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">10</span><span class=\"token punctuation\">,</span> <span class=\"token number\">175</span><span class=\"token punctuation\">,</span> <span class=\"token number\">29</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextColor       <span class=\"token operator\">=</span> <span class=\"token function\">rgb</span><span class=\"token punctuation\">(</span><span class=\"token number\">250</span><span class=\"token punctuation\">,</span> <span class=\"token number\">250</span><span class=\"token punctuation\">,</span> <span class=\"token number\">250</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    TextColorDisabled <span class=\"token operator\">=</span> <span class=\"token function\">rgb</span><span class=\"token punctuation\">(</span><span class=\"token number\">100</span><span class=\"token punctuation\">,</span> <span class=\"token number\">100</span><span class=\"token punctuation\">,</span> <span class=\"token number\">100</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>TGUI does appear to lack some code reuse for each element, which can make\ntheming hit and miss. For example, I discovered that not all elements which have\nbackgrounds support the same style properties to customise the background.</p>\n<p>TGUI also only has a single active maintainer, the original creator Texus, who\nis very responsive to bug and feature requests.</p>\n<p>TGUI follows the source code approach to documentation - the docs are generated\nfrom Doxygen comments in the source code, detailing what each thing does.</p>\n<p class=\"btn-row\">\n    <a class=\"btn is-secondary\" href=\"https://tgui.eu/\">Website</a>\n    <a class=\"btn is-secondary\" href=\"https://github.com/texus/TGUI\">GitHub</a>\n    <a class=\"btn is-secondary\" href=\"https://tgui.eu/documentation/0.8/classtgui_1_1Grid.html\">Doxygen</a>\n    <a class=\"btn is-secondary\" href=\"https://tgui.eu/tutorials/0.9/\">Tutorials</a>\n    <a class=\"btn is-secondary\" href=\"https://discordapp.com/invite/Msf4vyx\">Discord</a>\n</p>\n<h2 id=\"crazy-eddy-gui-cegui\" tabindex=\"-1\">Crazy Eddy GUI (CEGUI) <a class=\"header-anchor\" href=\"#crazy-eddy-gui-cegui\">#</a></h2>\n<ul class=\"factlist\"><li class=\"good\">\n\t\t\tWell-maintained\n\t\t</li><li class=\"bad\">\n\t\t\tComplex\n\t\t</li><li class=\"bad\">\n\t\t\tUgly, Old C++\n\t\t</li></ul>\n<p>CEGUI is a library which isn’t specific to SFML, and because of this has a much\nlarger user base than any of the other options. It has a team of maintainers,\nrather than a single person.</p>\n<p>CEGUI uses outdated C++, and it shows in the API. There’s a lot of manual\npointer use and a lot of ugly APIs. This was an immediate killer for me, so I\ndidn’t go further than this when investigating this library.</p>\n<p class=\"btn-row\">\n    <a class=\"btn is-secondary\" href=\"https://github.com/cegui/cegui\">BitBucket</a>\n    <a class=\"btn is-secondary\" href=\"http://static.cegui.org.uk/docs/0.8.7/\">Docs</a>\n</p>\n<h2 id=\"opengl-gui-libraries\" tabindex=\"-1\">OpenGL GUI Libraries <a class=\"header-anchor\" href=\"#opengl-gui-libraries\">#</a></h2>\n<p>It’s possible to make use of a much larger ecosystem by looking for GUI\nlibraries that target OpenGL. You can use a <code>sf::RenderTexture</code> as a target for\nthe raw OpenGL calls, and then render that texture to the screen.</p>\n<h2 id=\"conclusion\" tabindex=\"-1\">Conclusion <a class=\"header-anchor\" href=\"#conclusion\">#</a></h2>\n<p>Whilst I prefer the container-based requisition-allocation approach of SFGUI,\nthe benefits of a better-maintained library are apparent which is why I use\nTGUI. TGUI feels badly designed and incomplete.</p>\n<p>I am considering switching either switching back to SFGUI, and maintaining one\nmyself, or switching the graphics backend completely in order to use a\nbetter-maintained library like nanogui, but fear that this is too much of a\ndistraction from making my game.</p>\n<p>In this post, I tried to collect and convey the merits of each approach but I\nmay have missed some things out. Feel free to comment below.</p>\n","url":"https://blog.rubenwardy.com/2020/01/21/creating-game-guis-in-sfml/","summary":"An in-depth comparison of the options for making Graphical User Interfaces (GUIs/UIs) in SFML, discussing their pros and cons.","date_published":"2020-01-21T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/HoaaJMJEti-540.png","tags":["tutorials","reviews","sfml","cpp","gamedev"]},{"id":"/2019/04/24/rendering-multi-z-layer-topdown-map-using-SFML/","language":"en","title":"Rendering a topdown world with layers and z-levels using SFML","content_html":"<p><a href=\"/2025/02/12/rvwp/\">Ruben’s Virtual World Project</a>\nis a game I’ve been working on for almost 4 years now.\nRecently I rewrote the rendering code to support voxel lighting and multiple\nz-level - heights of the map.</p>\n<figure class=\"undefined\"><a href=\"/static/img/OTyw-uIQ7E-400.gif\"><img src=\"/static/img/ogXwDoCf6g-400.gif\" alt=\"Demonstration of multiple z-layers. The world is rendered as a cross-section, with lower layers shown with a slight fade. This is why you can see below the grass when the players drops down a layer.\" loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"300\"></a><figcaption>Demonstration of multiple z-layers. The world is rendered as a cross-section, with lower layers shown with a slight fade. This is why you can see below the grass when the players drops down a layer.</figcaption></figure>\n<h2 id=\"world-layers\" tabindex=\"-1\">World Layers <a class=\"header-anchor\" href=\"#world-layers\">#</a></h2>\n<figure class=\"float-right mt-0 mr-0 ml-6 mb-2\"><a href=\"/static/img/55gltIwd2x-200.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/T1kby6HgoT-200.webp 200w\"><img src=\"/static/img/T1kby6HgoT-200.png\" alt=\"The two layers\" loading=\"lazy\" decoding=\"async\" width=\"200\" height=\"92\"></picture></a><figcaption>The two layers</figcaption></figure>\n<p>Each z-level has two layers - a tile layer and a floor/terrain layer.\nEach of these layers has a mesh (VertexArray) which are created in slightly\ndifferent ways.\nThe floor layer is totally populated, meaning that every position has a quad\nrepresenting it. The tile layer is sparsely populated, meaning that only positions\nwhich have a tile have a matching mesh quad.</p>\n<p>Multiple z-levels are rendered one after another, with hidden z-levels not\nrendered at all.</p>\n<h2 id=\"lighting-and-shaders\" tabindex=\"-1\">Lighting and Shaders <a class=\"header-anchor\" href=\"#lighting-and-shaders\">#</a></h2>\n<p>Lighting is performed by a shader on each mesh, and takes in a\nthree different textures - diffuse, normal, and lightmap.\nThe mesh contains UV co-ordinates which are used to index the diffuse map\nand normal map, as both are dependent on the type of tile and not the position.\nThe lightmap is indexed using the position.</p>\n<figure class=\"undefined\"><a href=\"/static/img/9zEcfJbFKO-526.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/YNGRSXbNBi-526.webp 526w\"><img src=\"/static/img/YNGRSXbNBi-526.png\" alt=\"The result and the 3 sources to the lighting shader. The weird breaks on the dirt wall is due to the walls not yet connecting themselves to each other.\" loading=\"lazy\" decoding=\"async\" width=\"526\" height=\"389\"></picture></a><figcaption>The result and the 3 sources to the lighting shader. The weird breaks on the dirt wall is due to the walls not yet connecting themselves to each other.</figcaption></figure>\n<p>Each position has 3x3 pixels in the lightmap, representing the lighting above\nand from each of the four sides.</p>\n<p>A vertex shader is needed to export a relative position to the fragment shader:</p>\n<pre class=\"language-glsl\"><code class=\"language-glsl\"><span class=\"token keyword\">varying</span> <span class=\"token keyword\">vec4</span> relativePosition<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    relativePosition <span class=\"token operator\">=</span> gl_Vertex<span class=\"token punctuation\">;</span>\n\n    gl_Position <span class=\"token operator\">=</span> gl_ModelViewProjectionMatrix <span class=\"token operator\">*</span> gl_Vertex<span class=\"token punctuation\">;</span>\n    gl_TexCoord<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> gl_TextureMatrix<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">*</span> gl_MultiTexCoord0<span class=\"token punctuation\">;</span>\n    gl_FrontColor <span class=\"token operator\">=</span> gl_Color<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>The bulk of the work is done in the fragment shader:</p>\n<pre class=\"language-glsl\"><code class=\"language-glsl\"><span class=\"token keyword\">uniform</span> <span class=\"token keyword\">sampler2D</span> source<span class=\"token punctuation\">;</span> <span class=\"token comment\">// diffuse map</span>\n<span class=\"token keyword\">uniform</span> <span class=\"token keyword\">sampler2D</span> lightmap<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">uniform</span> <span class=\"token keyword\">sampler2D</span> normalmap<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">uniform</span> <span class=\"token keyword\">float</span> factor<span class=\"token punctuation\">;</span> <span class=\"token comment\">// Lower z-levels from cross-section have higher factors</span>\n<span class=\"token keyword\">varying</span> <span class=\"token keyword\">vec4</span> relativePosition<span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// From a normal, calculate how much comes from each direction</span>\n<span class=\"token keyword\">vec4</span> <span class=\"token function\">getComponents</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">vec4</span> normal<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">vec2</span> rot <span class=\"token operator\">=</span> normal<span class=\"token punctuation\">.</span>xy<span class=\"token operator\">*</span><span class=\"token number\">2.f</span> <span class=\"token operator\">-</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">vec4</span><span class=\"token punctuation\">(</span>\n        <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span>rot<span class=\"token punctuation\">.</span>y<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span>rot<span class=\"token punctuation\">.</span>x<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span>rot<span class=\"token punctuation\">.</span>y<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span>rot<span class=\"token punctuation\">.</span>x<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// Get light values for each direction</span>\n    <span class=\"token keyword\">vec2</span> rel        <span class=\"token operator\">=</span> <span class=\"token function\">floor</span><span class=\"token punctuation\">(</span>relativePosition<span class=\"token punctuation\">.</span>xy <span class=\"token operator\">/</span> <span class=\"token number\">64.f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> lightAbove <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>lightmap<span class=\"token punctuation\">,</span> rel <span class=\"token operator\">+</span> <span class=\"token number\">0.5f</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> lightUp    <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>lightmap<span class=\"token punctuation\">,</span> rel <span class=\"token operator\">+</span> <span class=\"token keyword\">vec2</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.5f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> lightRight <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>lightmap<span class=\"token punctuation\">,</span> rel <span class=\"token operator\">+</span> <span class=\"token keyword\">vec2</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.8f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.5f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> lightDown  <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>lightmap<span class=\"token punctuation\">,</span> rel <span class=\"token operator\">+</span> <span class=\"token keyword\">vec2</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.5f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.8f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> lightLeft  <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>lightmap<span class=\"token punctuation\">,</span> rel <span class=\"token operator\">+</span> <span class=\"token keyword\">vec2</span><span class=\"token punctuation\">(</span><span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.5f</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">16.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Get normal and weighting for each direction</span>\n    <span class=\"token keyword\">vec4</span> normal <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>normalmap<span class=\"token punctuation\">,</span> gl_TexCoord<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>xy<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">vec4</span> rot    <span class=\"token operator\">=</span> <span class=\"token function\">getComponents</span><span class=\"token punctuation\">(</span>normal<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Leak sides to the above, to make underground wall tops visible</span>\n    lightAbove <span class=\"token operator\">=</span> lightAbove <span class=\"token operator\">+</span> <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span>lightUp <span class=\"token operator\">+</span> lightRight <span class=\"token operator\">+</span> lightDown <span class=\"token operator\">+</span> lightLeft<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.6f</span><span class=\"token punctuation\">)</span>\n    lightAbove <span class=\"token operator\">=</span> <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span>lightAbove<span class=\"token punctuation\">,</span> <span class=\"token number\">0.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Calculate final light level</span>\n    <span class=\"token keyword\">vec4</span> lightV <span class=\"token operator\">=</span> rot<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token operator\">*</span>lightUp <span class=\"token operator\">+</span> rot<span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token operator\">*</span>lightRight <span class=\"token operator\">+</span> rot<span class=\"token punctuation\">[</span><span class=\"token number\">2</span><span class=\"token punctuation\">]</span><span class=\"token operator\">*</span>lightDown <span class=\"token operator\">+</span> rot<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span><span class=\"token operator\">*</span>lightLeft <span class=\"token operator\">+</span> <span class=\"token punctuation\">(</span>normal<span class=\"token punctuation\">.</span>z<span class=\"token operator\">*</span><span class=\"token number\">2.f</span> <span class=\"token operator\">-</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token operator\">*</span>lightAbove<span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Just support 1D lighting for now</span>\n    <span class=\"token keyword\">float</span> light <span class=\"token operator\">=</span> lightV<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token comment\">// Calculate color</span>\n    <span class=\"token keyword\">vec4</span> color <span class=\"token operator\">=</span> <span class=\"token function\">texture2D</span><span class=\"token punctuation\">(</span>source<span class=\"token punctuation\">,</span> gl_TexCoord<span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>xy<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>color<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">&lt;</span> <span class=\"token number\">0.1</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        gl_FragColor <span class=\"token operator\">=</span> color<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">float</span> u         <span class=\"token operator\">=</span> <span class=\"token number\">1.0</span> <span class=\"token operator\">-</span> <span class=\"token function\">clamp</span><span class=\"token punctuation\">(</span>factor<span class=\"token punctuation\">,</span> <span class=\"token number\">0.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> <span class=\"token keyword\">vec4</span> BLUE <span class=\"token operator\">=</span> <span class=\"token keyword\">vec4</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0.75</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        gl_FragColor    <span class=\"token operator\">=</span> <span class=\"token function\">mix</span><span class=\"token punctuation\">(</span>BLUE<span class=\"token punctuation\">,</span> color<span class=\"token punctuation\">,</span> u<span class=\"token punctuation\">)</span> <span class=\"token operator\">*</span> u <span class=\"token operator\">*</span> u <span class=\"token operator\">*</span> light <span class=\"token operator\">*</span> light<span class=\"token punctuation\">;</span>\n        gl_FragColor<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> color<span class=\"token punctuation\">[</span><span class=\"token number\">3</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>This probably isn’t the best way to do it. This is one of the first shaders\nI’ve ever written, and graphics isn’t my thing.</p>\n","url":"https://blog.rubenwardy.com/2019/04/24/rendering-multi-z-layer-topdown-map-using-SFML/","summary":"A topdown game with Z-levels is 3d despite being rendered in 2d. This technique has 3d lighting and normal mapping, with multiple layers per tile for effects.","date_published":"2019-04-24T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/OTyw-uIQ7E-400.png","tags":["tutorials","sfml","cpp","gamedev","rvwp"]},{"id":"/2019/04/16/youtube-music-are-you-still-listening-fix/","language":"en","title":"Getting rid of YouTube Music's \"Are you still listening?\" dialog","content_html":"<p>YouTube Music is a great way to listen for music for free, and with no adverts\nif you use an adblocker. There is one annoying problem however: after listening\nfor a while, Youtube will keep pausing the music to show a dialog which says\n“Are you still listening?”. This article will show how to automatically confirm\nthe dialog.</p>\n<p>First, you need to install a plugin to allow running custom JavaScript on\nweb pages. I used Greasemonkey/Tampermonkey\n(<a href=\"https://addons.mozilla.org/en-GB/firefox/addon/greasemonkey/\">Firefox</a> |\n<a href=\"https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en\">Chrome</a>),\nit seems to be the most popular plugin for this task.</p>\n<p>Open up the plugin’s menu, and click “new user script”.\nOpen up the menu again, select the script and click “edit”.\nPaste the following code as the source:</p>\n<pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// ==UserScript==</span>\n<span class=\"token comment\">// @name     Youtube Music Listening Clicker</span>\n<span class=\"token comment\">// @version  1</span>\n<span class=\"token comment\">// @grant    none</span>\n<span class=\"token comment\">// ==/UserScript==</span>\n\n<span class=\"token function\">setInterval</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"paper-dialog.ytmusic-popup-container\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getAttribute</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"aria-hidden\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    document<span class=\"token punctuation\">.</span><span class=\"token function\">querySelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\".ytmusic-you-there-renderer .style-blue-text .size-default\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nconsole<span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Youtube Music Listening Clicker loaded\"</span><span class=\"token punctuation\">)</span></code></pre>\n<p>Save and go back to the plugin’s menu, select the script, and click “User script options”.\nAdd “music.youtube.com” to the scripts include textbox and check “Only use these” below.</p>\n<p>Reload youtube music if you have it open. Enjoy no more annoying dialogs!</p>\n<p>Please note: the music may still pause for a second or so if the tab isn’t\nfocused, due to Firefox/Chrome limiting JavaScript in the backend. There’s\nno real fix for this. One way to reduce the dialog from appearing at all is\nto just play another playlist when the dialog first appears, otherwise it will\njust keep asking.</p>\n","url":"https://blog.rubenwardy.com/2019/04/16/youtube-music-are-you-still-listening-fix/","summary":"After listening for a while, YouTube will keep pausing the music. You can suppress this using a browser plugin and some JavaScript","date_published":"2019-04-16T00:00:00.000Z","tags":["tutorials"]},{"id":"/2019/02/17/cpp-self-registering-test-macros/","language":"en","title":"C++: Self-registering functions using macros for test libraries","content_html":"<p>Google’s C++ testing library has a nice syntax for registering tests,\nwithout needing to remember to add the tests to some central index.\nThis article will show how to use macros to allow the creation of tests\nusing only the following code:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token function\">Test</span><span class=\"token punctuation\">(</span>IntegerComparisonTest<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">int</span> a <span class=\"token operator\">=</span> <span class=\"token number\">3</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>a <span class=\"token operator\">==</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Whilst this gives the appearance of GTest adding a new keyword,\n<code>Test()</code> is actually a macro. After the preprocessor stage, the code\nwill look something like this:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">Test_MyTest</span> <span class=\"token punctuation\">{</span>\n<span class=\"token keyword\">public</span><span class=\"token operator\">:</span>\n    <span class=\"token keyword\">void</span> <span class=\"token function\">execute</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">static</span> Test_MyTest <span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">return</span> <span class=\"token function\">Test_MyTest</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">static</span> <span class=\"token keyword\">bool</span> registered <span class=\"token operator\">=</span> <span class=\"token class-name\">TestFactory</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Register</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"MyTest\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>Test_MyTest<span class=\"token double-colon punctuation\">::</span>create<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token class-name\">Test_MyTest</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">execute</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">int</span> a <span class=\"token operator\">=</span> <span class=\"token number\">3</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>a <span class=\"token operator\">==</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>The thing to note here is that the test body isn’t actually part of the macro,\nand that it utilises a split declaration and definition.</p>\n<h2 id=\"simpler-approach\" tabindex=\"-1\">Simpler Approach <a class=\"header-anchor\" href=\"#simpler-approach\">#</a></h2>\n<p>Now, this may be necessary in GTest’s use case to store some additional meta data\nin the class, but it’s not always necessary. It would be much cleaner to cut\nout the static create function and the unnecessary class.\nThere’s a solution to this: function pointers!</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">bool</span> <span class=\"token function\">test_MyTest</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">static</span> <span class=\"token keyword\">bool</span> test_MyTest_registered <span class=\"token operator\">=</span> <span class=\"token class-name\">TestFactory</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Register</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"MyTest\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>test_MyTest<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">bool</span> <span class=\"token function\">test_MyTest</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">int</span> a <span class=\"token operator\">=</span> <span class=\"token number\">3</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>a <span class=\"token operator\">==</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"implementation\" tabindex=\"-1\">Implementation <a class=\"header-anchor\" href=\"#implementation\">#</a></h2>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token comment\">// ---- .hpp ----</span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;functional></span></span>\n\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">define</span> <span class=\"token macro-name function\">Test</span><span class=\"token expression\"><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">)</span> </span><span class=\"token punctuation\">\\</span>\n    <span class=\"token expression\"><span class=\"token keyword\">void</span> test_</span><span class=\"token punctuation\">##</span><span class=\"token expression\"><span class=\"token function\">name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> </span><span class=\"token punctuation\">\\</span>\n    <span class=\"token expression\"><span class=\"token keyword\">static</span> <span class=\"token keyword\">bool</span> test_</span><span class=\"token punctuation\">##</span><span class=\"token expression\">name</span><span class=\"token punctuation\">##</span><span class=\"token expression\">_registered <span class=\"token operator\">=</span> <span class=\"token class-name\">TestFactory</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Register</span><span class=\"token punctuation\">(</span>#name<span class=\"token punctuation\">,</span> <span class=\"token operator\">&amp;</span>test_</span><span class=\"token punctuation\">##</span><span class=\"token expression\">name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> </span><span class=\"token punctuation\">\\</span>\n    <span class=\"token expression\"><span class=\"token keyword\">void</span> test_</span><span class=\"token punctuation\">##</span><span class=\"token expression\"><span class=\"token function\">name</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></span></span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">TestFactory</span> <span class=\"token punctuation\">{</span>\n<span class=\"token keyword\">public</span><span class=\"token operator\">:</span>\n    <span class=\"token keyword\">static</span> <span class=\"token keyword\">bool</span> <span class=\"token function\">Register</span><span class=\"token punctuation\">(</span>std<span class=\"token double-colon punctuation\">::</span>string name<span class=\"token punctuation\">,</span> std<span class=\"token double-colon punctuation\">::</span>function<span class=\"token operator\">&lt;</span><span class=\"token keyword\">bool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">></span> func<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">private</span><span class=\"token operator\">:</span>\n    <span class=\"token keyword\">static</span> std<span class=\"token double-colon punctuation\">::</span>map<span class=\"token operator\">&lt;</span>std<span class=\"token double-colon punctuation\">::</span>string<span class=\"token punctuation\">,</span> std<span class=\"token double-colon punctuation\">::</span>function<span class=\"token operator\">&lt;</span><span class=\"token keyword\">bool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">>></span> Tests<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// ---- .cpp ----</span>\n\nstd<span class=\"token double-colon punctuation\">::</span>map<span class=\"token operator\">&lt;</span>std<span class=\"token double-colon punctuation\">::</span>string<span class=\"token punctuation\">,</span> std<span class=\"token double-colon punctuation\">::</span>function<span class=\"token operator\">&lt;</span><span class=\"token keyword\">bool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">>></span> TestRunner<span class=\"token double-colon punctuation\">::</span>Tests<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">bool</span> <span class=\"token class-name\">TestFactory</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Register</span><span class=\"token punctuation\">(</span>std<span class=\"token double-colon punctuation\">::</span>string name<span class=\"token punctuation\">,</span> std<span class=\"token double-colon punctuation\">::</span>function<span class=\"token operator\">&lt;</span><span class=\"token keyword\">bool</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">></span> func<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">auto</span> it <span class=\"token operator\">=</span> Tests<span class=\"token punctuation\">.</span><span class=\"token function\">find</span><span class=\"token punctuation\">(</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>it <span class=\"token operator\">==</span> Tests<span class=\"token punctuation\">.</span><span class=\"token function\">end</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        Tests<span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">move</span><span class=\"token punctuation\">(</span>func<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">return</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<h2 id=\"sources\" tabindex=\"-1\">Sources <a class=\"header-anchor\" href=\"#sources\">#</a></h2>\n<ul>\n<li><a href=\"https://www.bfilipek.com/2018/02/factory-selfregister.html\">Factory With Self-Registering Types</a></li>\n</ul>\n","url":"https://blog.rubenwardy.com/2019/02/17/cpp-self-registering-test-macros/","summary":"It's important that unit tests are easy to write. Rather than having to register every unit test, it would be nice to have them detected automatically.","date_published":"2019-02-17T00:00:00.000Z","tags":["tutorials","cpp"]},{"id":"/2019/02/15/porting-cpp-to-windows-vcpkg/","language":"en","title":"Porting C++ programs from Linux to Windows using vcpkg, CMake, and Visual Studio","content_html":"<p>Recently I had to reinstall Windows to debug a hardware issue.\nI decided to try to make the most of this by trying to build\n<a href=\"/2025/02/12/rvwp/\">my game</a> on Windows.</p>\n<h2 id=\"installing-vcpkg\" tabindex=\"-1\">Installing vcpkg <a class=\"header-anchor\" href=\"#installing-vcpkg\">#</a></h2>\n<p>First, you need to clone vcpkg.\nI installed the git bash program from <a href=\"https://git-scm.com/downloads\">the git website</a></p>\n<pre class=\"language-sh\"><code class=\"language-sh\"><span class=\"token function\">git</span> clone https://github.com/Microsoft/vcpkg.git\n<span class=\"token builtin class-name\">cd</span> <span class=\"token function\">vcpkg</span>\n./bootstrap-vcpkg.bat</code></pre>\n<p>Next, you need to decide whether to install 32-bit (x86) or 64-bit (x64) dependencies,\nor both, then run one or both of the following commands:</p>\n<pre class=\"language-sh\"><code class=\"language-sh\"><span class=\"token assign-left variable\">VCPKG_DEFAULT_TRIPLET</span><span class=\"token operator\">=</span>x86-windows ./vcpkg.exe <span class=\"token function\">install</span> DEPS\n<span class=\"token assign-left variable\">VCPKG_DEFAULT_TRIPLET</span><span class=\"token operator\">=</span>x64-windows ./vcpkg.exe <span class=\"token function\">install</span> DEPS</code></pre>\n<p>where <code>DEPS</code> is a space-deliminated list of package names.</p>\n<p>You can search for packages like so:</p>\n<pre class=\"language-sh\"><code class=\"language-sh\">./vcpkg.exe search query</code></pre>\n<p>If you’re unable to find a dependency, you may have to write your own “port”\nfile to add it to vcpkg.</p>\n<p>Finally, to let Visual Studio know about the dependencies, run this:</p>\n<pre class=\"language-sh\"><code class=\"language-sh\">./vcpkg.exe integrate <span class=\"token function\">install</span></code></pre>\n<h2 id=\"building\" tabindex=\"-1\">Building <a class=\"header-anchor\" href=\"#building\">#</a></h2>\n<h3 id=\"toolchain\" tabindex=\"-1\">Toolchain <a class=\"header-anchor\" href=\"#toolchain\">#</a></h3>\n<p>Import your program into Visual Studio as a cmake project.\nWhen generating the CMake cache, you’ll probably receive “not found” errors.</p>\n<p>Right click on CMakeLists.txt and select “Open CMake Settings”.\nA file called CMakeSettings.json should appear, with content like the following:</p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"configurations\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"x64-Release\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"generator\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Ninja\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"configurationType\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"RelWithDebInfo\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"inheritEnvironments\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n        <span class=\"token string\">\"msvc_x64_x64\"</span>\n      <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"buildRoot\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"${env.USERPROFILE}\\\\CMakeBuilds\\\\${workspaceHash}\\\\build\\\\${name}\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"installRoot\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"${env.USERPROFILE}\\\\CMakeBuilds\\\\${workspaceHash}\\\\install\\\\${name}\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"cmakeCommandArgs\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"buildCommandArgs\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"-v\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"ctestCommandArgs\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Add the vcpkg toolchain file to <code>cmakeCommandArgs</code></p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token property\">\"cmakeCommandArgs\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"-DCMAKE_TOOLCHAIN_FILE=\\\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\scripts\\\\buildsystems\\\\vcpkg.cmake\\\"\"</span></code></pre>\n<p>Clear the CMake cache by doing CMake &gt; Delete Cache Folders &gt; CMakeLists.txt,\nthen click the Generate button on the CMake changes detected banner.</p>\n<p>This <em>should</em> be enough to get Visual Studio to find the dependencies, but I\nfound that this wouldn’t work when you have custom CMake find files which\nprobably don’t support the toolchain.</p>\n<h3 id=\"explicit-dependency-paths\" tabindex=\"-1\">Explicit dependency paths <a class=\"header-anchor\" href=\"#explicit-dependency-paths\">#</a></h3>\n<p>I’ve yet to find out how to modify the find modules to use vcpkg’s toolchain\ncleanly, but in the meantime I set the directories manually by adding\nCMake flags and environment variables.</p>\n<p>You should prefer more general settings to individual include/lib settings.\nFor example, findX.cmake files may allow you to specify a single environment\nvariable to the install root instead of separate <code>X_INCLUDE_DIR</code> and <code>X_LIBRARY</code>\nCMake variables. This is shown below, with <code>ENETDIR</code> and <code>SFML_ROOT</code>.\nDon’t be too afraid to modify any embedded find scripts to support environment\nvariables.</p>\n<pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"environments\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"ENETDIR\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\installed\\\\x64-windows\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"SFML_ROOT\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\installed\\\\x64-windows\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"SFGUI_ROOT\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\installed\\\\x64-windows\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"THOR_ROOT\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\installed\\\\x64-windows\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"LUA_ROOT\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\installed\\\\x64-windows\"</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"configurations\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"cmakeCommandArgs\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"-DCMAKE_TOOLCHAIN_FILE=\\\"C:\\\\Users\\\\ruben\\\\vcpkg\\\\scripts\\\\buildsystems\\\\vcpkg.cmake\\\"\"</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">]</span>\n<span class=\"token punctuation\">}</span></code></pre>\n<p>Note that you may need to clear the CMake cache and regenerate for changes to\ntake affect.</p>\n<h2 id=\"common-mistakes\" tabindex=\"-1\">Common mistakes <a class=\"header-anchor\" href=\"#common-mistakes\">#</a></h2>\n<h3 id=\"console-program-vs-windows-program\" tabindex=\"-1\">Console program vs Windows program <a class=\"header-anchor\" href=\"#console-program-vs-windows-program\">#</a></h3>\n<p>By default, MSVC will compile your program as a console program.\nThis mode results in Windows allocating and showing a console for you when starting\nthe program up. This console will <strong>require a redraw on every std::cerr or std::cout print,\nresulting in massive performance issues</strong>.</p>\n<p>If you program shows a graphical window, then you should change it to a Windows\nprogram.</p>\n<p>There are three methods to do this.</p>\n<ol>\n<li>\n<p>The first option is to set the executable type to WIN32 in CMake:</p>\n<pre class=\"language-cmake\"><code class=\"language-cmake\"><span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span><span class=\"token variable\">WIN32</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">add_executable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">${</span>EXECUTABLE_NAME<span class=\"token punctuation\">}</span> <span class=\"token variable\">WIN32</span> <span class=\"token punctuation\">${</span>SRC<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">else</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">add_executable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">${</span>EXECUTABLE_NAME<span class=\"token punctuation\">}</span> <span class=\"token punctuation\">${</span>SRC<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">endif</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre>\n</li>\n<li>\n<p>Second, by setting linker flags using CMake:</p>\n<pre class=\"language-cmake\"><code class=\"language-cmake\"><span class=\"token keyword\">if</span><span class=\"token punctuation\">(</span><span class=\"token variable\">WIN32</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">set</span><span class=\"token punctuation\">(</span><span class=\"token variable\">CMAKE_EXE_LINKER_FLAGS</span> <span class=\"token string\">\"<span class=\"token interpolation\"><span class=\"token punctuation\">${</span><span class=\"token variable\">CMAKE_EXE_LINKER_FLAGS</span><span class=\"token punctuation\">}</span></span> /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup\"</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">endif</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre>\n</li>\n<li>\n<p>The final option is to set the linker flags using a pragma directive:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">ifdef</span> <span class=\"token expression\">_WIN32</span></span>\n    <span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">pragma</span> <span class=\"token expression\"><span class=\"token function\">comment</span><span class=\"token punctuation\">(</span>linker<span class=\"token punctuation\">,</span> </span><span class=\"token string\">\"/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup\"</span><span class=\"token expression\"><span class=\"token punctuation\">)</span></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">endif</span></span></code></pre>\n</li>\n</ol>\n<p>The ENTRY parameter allows you to use the standard <code>main()</code> entrypoint instead\nof the non-standard <code>WinMain()</code>.\nYou may need to clear and regenerate the CMake cache.</p>\n<h2 id=\"profit\" tabindex=\"-1\">Profit! <a class=\"header-anchor\" href=\"#profit\">#</a></h2>\n<p>Hopefully that should be enough to get it work.\nPlease <a href=\"https://rubenwardy.com/contact/\">contact me</a> if you know of any ways to make this cleaner or more robust.</p>\n","url":"https://blog.rubenwardy.com/2019/02/15/porting-cpp-to-windows-vcpkg/","summary":"Recently I had to reinstall Windows to debug a hardware issue. I decided to try to make the most of this by trying to build my game on Windows.","date_published":"2019-02-15T00:00:00.000Z","tags":["tutorials","cpp"]},{"id":"/2019/01/25/rimworld-progress-renderer-to-video/","language":"en","title":"RimWorld: Create timelapse from Progress Renderer on Linux","content_html":"<p>Progress Renderer\n(<a href=\"https://steamcommunity.com/sharedfiles/filedetails/?id=1438693028\">Steam</a> |\n<a href=\"https://github.com/Lanilor/Progress-Renderer\">GitHub</a>)\nis a brilliant mod that takes a screenshot once a day, and dumps it into a folder.</p>\n<p>The following bash script can be used to generate an mp4 timelapse of the screenshots.\nSimply specify a region to cut out of the screenshots, and some other parameters,\nand run the script in the working directory.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token comment\">###########################</span>\n\n<span class=\"token comment\"># Video resolution (must be even)</span>\n<span class=\"token assign-left variable\">SIZE</span><span class=\"token operator\">=</span>2272x1472\n\n<span class=\"token comment\"># Video FPS</span>\n<span class=\"token assign-left variable\">FPS</span><span class=\"token operator\">=</span><span class=\"token number\">10</span>\n\n<span class=\"token comment\"># Region to export</span>\n<span class=\"token assign-left variable\">CROP</span><span class=\"token operator\">=</span>4544x2944+4064+3360\n\n<span class=\"token comment\"># Output file</span>\n<span class=\"token assign-left variable\">OUT</span><span class=\"token operator\">=~</span>/Videos/rimworldvid.mp4\n\n<span class=\"token comment\"># TMP Location</span>\n<span class=\"token assign-left variable\">TMP</span><span class=\"token operator\">=</span>/tmp/rimworldvid\n\n<span class=\"token comment\">###########################</span>\n\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"================= CONFIG =====================\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Region: <span class=\"token variable\">$CROP</span>\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Output: <span class=\"token variable\">$OUT</span>\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Resolu: <span class=\"token variable\">$SIZE</span> @ <span class=\"token variable\">$FPS</span> fps\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Tmp:    <span class=\"token variable\">$TMP</span>/\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"=============================================\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> <span class=\"token variable\">$TMP</span>\n\n<span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">filename</span> <span class=\"token keyword\">in</span> *.jpg<span class=\"token punctuation\">;</span> <span class=\"token keyword\">do</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token operator\">!</span> <span class=\"token parameter variable\">-f</span> <span class=\"token string\">\"<span class=\"token variable\">$TMP</span>/<span class=\"token variable\">$filename</span>\"</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n        <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Cropping <span class=\"token variable\">$filename</span>\"</span>\n        convert <span class=\"token variable\">$filename</span> <span class=\"token parameter variable\">-crop</span> <span class=\"token variable\">$CROP</span> <span class=\"token parameter variable\">-resize</span> <span class=\"token variable\">$SIZE</span> <span class=\"token string\">\"<span class=\"token variable\">$TMP</span>/<span class=\"token variable\">$filename</span>\"</span> <span class=\"token operator\">||</span> <span class=\"token builtin class-name\">exit</span> <span class=\"token variable\">$?</span>\n    <span class=\"token keyword\">fi</span>\n<span class=\"token keyword\">done</span> <span class=\"token operator\">||</span> <span class=\"token builtin class-name\">exit</span> <span class=\"token variable\">$?</span>\n\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Rendering mp4\"</span>\n\n<span class=\"token builtin class-name\">cd</span> <span class=\"token variable\">$TMP</span>\nffmpeg <span class=\"token parameter variable\">-framerate</span> <span class=\"token variable\">$FPS</span> <span class=\"token parameter variable\">-pattern_type</span> glob <span class=\"token parameter variable\">-i</span> <span class=\"token string\">'*.jpg'</span> <span class=\"token string\">\"<span class=\"token variable\">$OUT</span>\"</span></code></pre>\n","url":"https://blog.rubenwardy.com/2019/01/25/rimworld-progress-renderer-to-video/","summary":"Progress Renderer is a brilliant mod that takes a screenshot once a day, and dumps it into a folder. Here's a script to turn that into a video.","date_published":"2019-01-25T00:00:00.000Z","tags":["gaming","tutorials"]},{"id":"/2018/09/07/raytracer-rasteriser/","language":"en","title":"Raytracer and Rasteriser","content_html":"<p>I wrote a raytracer and a rasteriser as part of my university course.\nThe raytracer supported features such as indirect lighting, reflection,\nrefraction, and a photon mapper capable of simulating the final positions of\n60,000,000 photons in a few minutes (and quite a few GBs of RAM).</p>\n<h2 id=\"raytracer\" tabindex=\"-1\">Raytracer <a class=\"header-anchor\" href=\"#raytracer\">#</a></h2>\n<h3 id=\"features\" tabindex=\"-1\">Features <a class=\"header-anchor\" href=\"#features\">#</a></h3>\n<ul>\n<li>Direct, specular, and ambient lighting.</li>\n<li>Soft shadows.</li>\n<li>Reflection.</li>\n<li>Refraction with Snell’s law, Fresnel’s law,</li>\n<li>Total Internal Reflection, and Beer’s law.</li>\n<li>Material roughness and rough reflections.</li>\n<li>Anti-aliasing.</li>\n<li>Obj 3D model importer.</li>\n<li>Ability to switch to and from rasteriser using F1 key. Other F keys can be used to enable/disable certain features, see README.</li>\n<li>OpenMP support</li>\n<li>Undersampling - render at a lower resolution than the window.</li>\n</ul>\n<h3 id=\"direct-illumination-shadows-and-reflection\" tabindex=\"-1\">Direct Illumination, Shadows, and Reflection <a class=\"header-anchor\" href=\"#direct-illumination-shadows-and-reflection\">#</a></h3>\n<p>Once anti-aliasing is implemented, the triangle normal need only be randomised for each sample, in order to acquire roughness. For each sample, the rotation of the triangle normal - for the triangle which the ray intersects with - is given a random offset. The magnitude of this random offset is dictated by the roughness attribute of the triangle. This gives a rough, blurred, look to rough mirrors. It also makes specularity look more realistic.</p>\n<p>Reflections can be implemented simply by firing another ray from the intersection\nposition in the right direction, and using the colour it returns as the pixel colour.\nThis colour is then blended with the diffuse color depending on material properties.\nYou get rough mirrors for free by having roughness.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/qKskBSYM8r-955.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/nCjCgaX20d-540.webp 540w\"><img src=\"/static/img/nCjCgaX20d-540.png\" alt=\"Direct illumination with shadows.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"410\"></picture></a><figcaption>Direct illumination with shadows.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/Z2NsALWgoE-860.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/bCdDO1pQE2-540.webp 540w\"><img src=\"/static/img/bCdDO1pQE2-540.png\" alt=\"Rough reflection and soft shadows.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"401\"></picture></a><figcaption>Rough reflection and soft shadows.</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"refraction\" tabindex=\"-1\">Refraction <a class=\"header-anchor\" href=\"#refraction\">#</a></h3>\n<p>Refraction is achieved by firing rays through the material, accounting for the difference\nin refractive index. The amount of light which is refracted vs diffused depends on the\ntransparency of the material.</p>\n<p>If a ray hits a transparent triangle on the opposite side to the normal, then the\nrefractive index is <code>1/R</code>, where R is the index of the material. Otherwise it’s just\n<code>R</code>.</p>\n<p>Fresnel’s Law determines how much of a ray is reflected when it hits a change\nin refractive index. Above a certain angle - the critical angle - the rays totally\nreflect, causing Total Internal Reflection.</p>\n<p>Beer’s Law describes a property of semi-transparent materials where colour is absorbed from\nlight per length it passes through the material. The application of Beer’s law gives a jelly-like\neffect.</p>\n<pre class=\"language-c\"><code class=\"language-c\">refractedColor <span class=\"token operator\">=</span> incomingColor <span class=\"token operator\">*</span> <span class=\"token function\">exp</span><span class=\"token punctuation\">(</span><span class=\"token operator\">-</span>absorbedColor <span class=\"token operator\">*</span> lengthInMaterial<span class=\"token punctuation\">)</span></code></pre>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/Wp35LoxACg-857.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/6A7saBzkyy-540.webp 540w\"><img src=\"/static/img/6A7saBzkyy-540.png\" alt=\"Pure refraction with Snell's law, Fresnel's law, and TIR.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"400\"></picture></a><figcaption>Pure refraction with Snell's law, Fresnel's law, and TIR.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/RqWTJ_aR0d-857.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/_rnPYcwqZi-540.webp 540w\"><img src=\"/static/img/_rnPYcwqZi-540.png\" alt=\"Refraction with beers law - colour of material seeps in depending on length of ray.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"403\"></picture></a><figcaption>Refraction with beers law - colour of material seeps in depending on length of ray.</figcaption></figure></div>\n\t\t\t</div>\n<h3 id=\"photon-mapper\" tabindex=\"-1\">Photon Mapper <a class=\"header-anchor\" href=\"#photon-mapper\">#</a></h3>\n<p>Millions of photons are simulated after being emitted from the light,\nand then their final positions are recorded. The record of all the final\npositions is called a photon map. I used an OctTree\n(like a quad tree, but 2x2x2 rather than 2x2) to enable efficient querying\nof photon positions.</p>\n<p>Hitting any surface will result in a certain percentage of the photon’s\ncolor being reflected, which results in indirect lighting. Photons will keep\nbouncing until their color is low enough to be negligible in which case they’ll\nstop. The percentage of color which isn’t reflected will be stored in the\nphoton map as a final photon position (ie: diffuse) or sent split into colors\nand refracted if the triangle is transparent.</p>\n<p>Warning: the default settings for the photon map require 4GB of free RAM.\nReduce the photon count in drawRaytracer() if you don’t have that available.</p>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/GRlnqyx5nC-848.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/96s1ZIyGKa-540.webp 540w\"><img src=\"/static/img/96s1ZIyGKa-540.png\" alt=\"Note the light from the sphere on the walls, and the double shadow from indirect lighting on the small cube.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"408\"></picture></a><figcaption>Note the light from the sphere on the walls, and the double shadow from indirect lighting on the small cube.</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/8-fg9cmF7H-859.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/GSdU1ENv3z-540.webp 540w\"><img src=\"/static/img/GSdU1ENv3z-540.png\" alt=\"A re-production of Newton’s light experiment.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"401\"></picture></a><figcaption>A re-production of Newton’s light experiment.</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"rasteriser\" tabindex=\"-1\">Rasteriser <a class=\"header-anchor\" href=\"#rasteriser\">#</a></h2>\n<h3 id=\"features-1\" tabindex=\"-1\">Features <a class=\"header-anchor\" href=\"#features-1\">#</a></h3>\n<ul>\n<li>Direct and ambient lighting.</li>\n<li>Perspective Correct Interpolation.</li>\n<li>“Real Time” Shadow Mapping.</li>\n<li>Obj 3D model importer.</li>\n<li>Ability to switch to and from rasteriser using F1 key. Other F keys can be used to enable/disable certain features, see README.</li>\n<li>Undersampling - render at a lower resolution than the window.</li>\n</ul>\n<figure class=\"undefined\"><a href=\"/static/img/ty4GmDyk6X-1356.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/YWv0Wp9NXX-540.webp 540w\"><img src=\"/static/img/YWv0Wp9NXX-540.png\" alt=\"Rasteriser with Shadows\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"540\"></picture></a><figcaption>Rasteriser with Shadows</figcaption></figure>\n","url":"https://blog.rubenwardy.com/2018/09/07/raytracer-rasteriser/","summary":"I wrote a raytracer and a rasteriser for a university module. The raytracer supported features such as indirect lighting, reflection, refraction, and a photon mapper","date_published":"2018-09-07T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/GRlnqyx5nC-540.png","tags":["computer_science","projects","gamedev","cpp"]},{"id":"/2018/08/24/commit-sorter-changelog/","language":"en","title":"Interactive Commit Sorter for Changelogs","content_html":"<p>I wrote a Bash script to sort git commits into buckets, to be used as the first\nstep of making a change log. It supports rewording commit messages, can be\nstopped and resumed, and supports automatic filtering based on keywords.</p>\n<p>License: MIT</p>\n<h2 id=\"example\" tabindex=\"-1\">Example <a class=\"header-anchor\" href=\"#example\">#</a></h2>\n<pre><code>$ commitsorter ~/Documents HEAD~10..HEAD\n\nOutputting to /home/ruben/Documents/commitsorter\n\n[bugfix / feature / csm / ssm / other / delete / reword]\n\nFix something (''rubenwardy'')\n: b  (auto)\n\nAdd foo (''rubenwardy'')\n: f\n\nAdd bnrre mispelt (''rubenwardy'')\n: r\n\nNew message: Add bar (''rubenwardy'')\n\nAdd bar (''rubenwardy'')\n: f\n\nSummary:\n- Bugfixes: 1\n- Features: 2\n- CSM     : 0\n- SSM     : 0\n- Others  : 0\n- Deleted : 0\n</code></pre>\n<h2 id=\"the-script\" tabindex=\"-1\">The Script <a class=\"header-anchor\" href=\"#the-script\">#</a></h2>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span> <span class=\"token variable\">$#</span> <span class=\"token parameter variable\">-ne</span> <span class=\"token number\">2</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Usage: commitsorter output REF1..REF2\"</span>\n\t<span class=\"token builtin class-name\">exit</span> <span class=\"token number\">1</span>\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token comment\">### Define new results here ... ###</span>\n<span class=\"token function-name function\">makebuckets</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Bug fixes and Improvements ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/b.txt\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Features ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/f.txt\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Client Modding ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/c.txt\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Server Modding ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/m.txt\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Other / Misc ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/o.txt\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"## Deleted ##<span class=\"token entity\" title=\"\\n\">\\n</span>\"</span> <span class=\"token operator\">></span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>/d.txt\"</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">### ... and also add them to help ###</span>\n<span class=\"token function-name function\">help</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"[<span class=\"token entity\" title=\"\\e\">\\e</span>[4mb<span class=\"token entity\" title=\"\\e\">\\e</span>[0mugfix / <span class=\"token entity\" title=\"\\e\">\\e</span>[4mf<span class=\"token entity\" title=\"\\e\">\\e</span>[0meature / <span class=\"token entity\" title=\"\\e\">\\e</span>[4mc<span class=\"token entity\" title=\"\\e\">\\e</span>[0msm / ss<span class=\"token entity\" title=\"\\e\">\\e</span>[4mm<span class=\"token entity\" title=\"\\e\">\\e</span>[0m / <span class=\"token entity\" title=\"\\e\">\\e</span>[4mo<span class=\"token entity\" title=\"\\e\">\\e</span>[0mther / <span class=\"token entity\" title=\"\\e\">\\e</span>[4md<span class=\"token entity\" title=\"\\e\">\\e</span>[0melete / <span class=\"token entity\" title=\"\\e\">\\e</span>[4mr<span class=\"token entity\" title=\"\\e\">\\e</span>[0meword]\"</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token assign-left variable\">output</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$1</span>/commitsorter\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Outputting to <span class=\"token variable\">$output</span>\"</span>\n\n<span class=\"token comment\">### Uncomment to backup and start again instead of resuming ###</span>\n<span class=\"token comment\"># if [ -d \"$output\" ]; then</span>\n<span class=\"token comment\"># \tDATE=$(date +%Y_%m_%d_%H_%M_%S)</span>\n<span class=\"token comment\"># \tbackup=\"${output}_$DATE\"</span>\n<span class=\"token comment\"># \techo \"Backing up $output to $backup\"</span>\n<span class=\"token comment\"># \tmv \"$output\" \"$backup\"</span>\n<span class=\"token comment\"># fi</span>\n\n<span class=\"token comment\"># Create directories and buckets</span>\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\"<span class=\"token variable\">$1</span>\"</span>\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\"<span class=\"token variable\">$output</span>\"</span>\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\"<span class=\"token variable\">$output</span>/cache\"</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token operator\">!</span> <span class=\"token parameter variable\">-f</span> <span class=\"token variable\">$output</span>/b.txt <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\tmakebuckets <span class=\"token variable\">$output</span>\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token comment\"># Get log</span>\n<span class=\"token assign-left variable\">entries</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">`</span><span class=\"token function\">git</span> log <span class=\"token parameter variable\">--pretty</span><span class=\"token operator\">=</span>format:<span class=\"token string\">\"%h %s (''%an'')\"</span> <span class=\"token string\">\"<span class=\"token variable\">$2</span>\"</span><span class=\"token variable\">`</span></span>\n\n<span class=\"token comment\"># Print help</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n<span class=\"token builtin class-name\">help</span>\n\n<span class=\"token comment\"># Loop</span>\n<span class=\"token assign-left variable\"><span class=\"token environment constant\">IFS</span></span><span class=\"token operator\">=</span><span class=\"token string\">$'<span class=\"token entity\" title=\"\\n\">\\n</span>'</span>\n<span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">line</span> <span class=\"token keyword\">in</span> <span class=\"token variable\">$entries</span>\n<span class=\"token keyword\">do</span>\n\t<span class=\"token assign-left variable\">hash</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -c1-7<span class=\"token variable\">)</span></span>\n\t<span class=\"token assign-left variable\">message</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> <span class=\"token parameter variable\">-c</span> <span class=\"token number\">9</span>- <span class=\"token variable\">)</span></span>\n\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"<span class=\"token variable\">$message</span>\"</span>\n\n\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span> <span class=\"token parameter variable\">-f</span> <span class=\"token variable\">$output</span>/cache/<span class=\"token variable\">$hash</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t<span class=\"token assign-left variable\">input</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token function\">cat</span> $output/cache/$hash<span class=\"token variable\">)</span></span>\n\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\": <span class=\"token variable\">$input</span>  (already sorted)\"</span>\n\t<span class=\"token keyword\">elif</span> <span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token parameter variable\">-iq</span> <span class=\"token string\">\"fix\\(ed\\)\\? \"</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\": b  (auto)\"</span>\n\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span> <span class=\"token operator\">>></span> <span class=\"token variable\">$output</span>/b.txt\n\t\t<span class=\"token builtin class-name\">echo</span> b <span class=\"token operator\">></span> <span class=\"token variable\">$output</span>/cache/<span class=\"token variable\">$hash</span>\n\t<span class=\"token keyword\">elif</span> <span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token parameter variable\">-iq</span> <span class=\"token string\">\"translated using weblate\"</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\": d  (auto)\"</span>\n\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span> <span class=\"token operator\">>></span> <span class=\"token variable\">$output</span>/d.txt\n\t\t<span class=\"token builtin class-name\">echo</span> d <span class=\"token operator\">></span> <span class=\"token variable\">$output</span>/cache/<span class=\"token variable\">$hash</span>\n\t<span class=\"token keyword\">else</span>\n\t\t<span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">do</span>\n\t\t\t<span class=\"token builtin class-name\">read</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\": \"</span> <span class=\"token parameter variable\">-rsn1</span> input\n\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$input</span>\n\n\t\t\t<span class=\"token assign-left variable\"><span class=\"token environment constant\">LC_ALL</span></span><span class=\"token operator\">=</span>C <span class=\"token comment\"># Turn off locale.</span>\n\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span> <span class=\"token variable\">$input</span> <span class=\"token operator\">==</span> r <span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n\t\t\t\t<span class=\"token builtin class-name\">read</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\"New message: \"</span> line2\n\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span> <span class=\"token operator\">!</span> <span class=\"token parameter variable\">-z</span> <span class=\"token variable\">$message2</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t\t\t\t<span class=\"token assign-left variable\">line</span><span class=\"token operator\">=</span><span class=\"token variable\">$message2</span>\n\t\t\t\t<span class=\"token keyword\">fi</span>\n\t\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n\t\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span>\n\t\t\t<span class=\"token keyword\">elif</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span> <span class=\"token variable\">$input</span> <span class=\"token operator\">==</span> <span class=\"token punctuation\">[</span>a-z<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t\t\t<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token parameter variable\">-f</span> <span class=\"token variable\">$output</span>/<span class=\"token variable\">$input</span>.txt <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n\t\t\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$message</span> <span class=\"token operator\">>></span> <span class=\"token variable\">$output</span>/<span class=\"token variable\">$input</span>.txt\n\t\t\t\t\t<span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$input</span> <span class=\"token operator\">></span> <span class=\"token variable\">$output</span>/cache/<span class=\"token variable\">$hash</span>\n\t\t\t\t\t<span class=\"token builtin class-name\">break</span>\n\t\t\t\t<span class=\"token keyword\">else</span>\n\t\t\t\t\t<span class=\"token builtin class-name\">help</span>\n\t\t\t\t<span class=\"token keyword\">fi</span>\n\t\t\t<span class=\"token keyword\">else</span>\n\t\t\t\t<span class=\"token builtin class-name\">help</span>\n\t\t\t<span class=\"token keyword\">fi</span>\n\t\t<span class=\"token keyword\">done</span>\n\t<span class=\"token keyword\">fi</span>\n<span class=\"token keyword\">done</span>\n\n\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"\"</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Summary:\"</span>\n<span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">filename</span> <span class=\"token keyword\">in</span> <span class=\"token variable\">$output</span>/*.txt<span class=\"token punctuation\">;</span> <span class=\"token keyword\">do</span>\n\t<span class=\"token assign-left variable\">title</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token function\">head</span> <span class=\"token parameter variable\">-n1</span> $filename <span class=\"token operator\">|</span> <span class=\"token function\">sed</span> <span class=\"token string\">'s/^## \\([^#]*\\) ##$/\\1/'</span><span class=\"token variable\">)</span></span>\n\t<span class=\"token assign-left variable\">count</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token function\">cat</span> $filename <span class=\"token operator\">|</span> <span class=\"token function\">tail</span> -n+2 <span class=\"token operator\">|</span> <span class=\"token function\">sed</span> <span class=\"token string\">'/^\\s*$/d'</span> <span class=\"token operator\">|</span> <span class=\"token function\">wc</span> <span class=\"token parameter variable\">-l</span><span class=\"token variable\">)</span></span>\n\t<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"- <span class=\"token variable\">$title</span>: <span class=\"token variable\">$count</span>\"</span>\n<span class=\"token keyword\">done</span></code></pre>\n","url":"https://blog.rubenwardy.com/2018/08/24/commit-sorter-changelog/","summary":"I wrote a Bash script to sort git commits into buckets, to be used as the first step of making a change log.","date_published":"2018-08-24T00:00:00.000Z","tags":["snippet","linux","projects"]},{"id":"/2018/05/07/mingw-copy-dlls/","language":"en","title":"Finding and copying DLLs from MinGW-W64 directories to bin directory","content_html":"<p>After cross-compiling your project for Windows, you find that it crashes due\nto missing DLLs. I will show how to identify any required DLLs using objdump,\nand copy them to your build directory.</p>\n<p>mingw32-objdump can be used to analysis an executable or object. Among other\nthings, it lists the .DLLs which are required by the .EXE. Call it by passing\nthe path to the executable and the <code>-p</code> flag:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\">x86_64-w64-mingw32-objdump <span class=\"token parameter variable\">-p</span> project/bin/project.exe</code></pre>\n<p>This will output a ton of content, so you’ll want to pass the result\nthrough grep and sed to get just the DLL names:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\">x86_64-w64-mingw32-objdump <span class=\"token parameter variable\">-p</span> project/bin/project.exe <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token string\">'DLL Name:'</span> <span class=\"token operator\">|</span> <span class=\"token function\">sed</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"s/<span class=\"token entity\" title=\"\\t\">\\t</span>*DLL Name: //g\"</span></code></pre>\n<p>The output will look a bit like this:</p>\n<pre><code>libgcc_s_seh-1.dll\nKERNEL32.dll\nmsvcrt.dll\nlibwinpthread-1.dll\nlibstdc++-6.dll\nWSOCK32.dll\nsfgui.dll\nsfml-graphics-2.dll\nsfml-network-2.dll\nsfml-system-2.dll\nsfml-window-2.dll\nlibthor.dll\n</code></pre>\n<p>Now we want to iterate over the result of this, and try and find the DLL\nin a number of search paths. Here’s the full script to do that:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token assign-left variable\">BINDIR</span><span class=\"token operator\">=</span><span class=\"token string\">\"project/bin\"</span>\n<span class=\"token assign-left variable\">EXE</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$BINDIR</span>/project.exe\"</span>\n<span class=\"token assign-left variable\">PREFIX</span><span class=\"token operator\">=</span><span class=\"token string\">\"x86_64-w64-mingw32\"</span>\n\n<span class=\"token assign-left variable\">paths</span><span class=\"token operator\">=</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/usr/local/mingw64/bin\"</span>\n    <span class=\"token string\">\"/usr/local/mingw64/bin/x64\"</span>\n     <span class=\"token string\">\"/usr/<span class=\"token variable\">$PREFIX</span>/bin\"</span>\n    <span class=\"token string\">\"/usr/lib/gcc/<span class=\"token variable\">$PREFIX</span>/7.3-posix\"</span>\n    <span class=\"token string\">\"/usr/<span class=\"token variable\">$PREFIX</span>/lib\"</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function-name function\">findAndCopyDLL</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">i</span> <span class=\"token keyword\">in</span> <span class=\"token string\">\"<span class=\"token variable\">${paths<span class=\"token punctuation\">[</span>@<span class=\"token punctuation\">]</span>}</span>\"</span>\n    <span class=\"token keyword\">do</span>\n        <span class=\"token assign-left variable\">FILE</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$i</span>/<span class=\"token variable\">$1</span>\"</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token parameter variable\">-f</span> <span class=\"token variable\">$FILE</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n           <span class=\"token function\">cp</span> <span class=\"token variable\">$FILE</span> <span class=\"token variable\">$BINDIR</span>\n           <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Found <span class=\"token variable\">$1</span> in <span class=\"token variable\">$i</span>\"</span>\n           copyForOBJ <span class=\"token variable\">$FILE</span>\n           <span class=\"token builtin class-name\">return</span> <span class=\"token number\">0</span>\n        <span class=\"token keyword\">fi</span>\n    <span class=\"token keyword\">done</span>\n\n    <span class=\"token builtin class-name\">return</span> <span class=\"token number\">1</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function-name function\">copyForOBJ</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token assign-left variable\">dlls</span><span class=\"token operator\">=</span><span class=\"token variable\"><span class=\"token variable\">`</span>$PREFIX-objdump <span class=\"token parameter variable\">-p</span> $1 <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token string\">'DLL Name:'</span> <span class=\"token operator\">|</span> <span class=\"token function\">sed</span> <span class=\"token parameter variable\">-e</span> <span class=\"token string\">\"s/<span class=\"token entity\" title=\"\\t\">\\t</span>*DLL Name: //g\"</span><span class=\"token variable\">`</span></span>\n    <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> <span class=\"token parameter variable\">-r</span> filename<span class=\"token punctuation\">;</span> <span class=\"token keyword\">do</span>\n        findAndCopyDLL <span class=\"token variable\">$filename</span> <span class=\"token operator\">||</span> <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Unable to find <span class=\"token variable\">$filename</span>\"</span>\n    <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;&lt;&lt;</span> <span class=\"token string\">\"<span class=\"token variable\">$dlls</span>\"</span>\n<span class=\"token punctuation\">}</span>\n\ncopyForOBJ <span class=\"token variable\">$EXE</span></code></pre>\n<p>The output will look like this:</p>\n<pre><code>Found libgcc_s_seh-1.dll in /usr/x86_64-w64-mingw32/bin\nUnable to find KERNEL32.dll\nUnable to find msvcrt.dll\nFound libwinpthread-1.dll in /usr/x86_64-w64-mingw32/bin\nFound libstdc++-6.dll in /usr/x86_64-w64-mingw32/bin\nUnable to find WSOCK32.dll\nFound sfgui.dll in /usr/local/mingw64/bin\nFound sfml-graphics-2.dll in /usr/local/mingw64/bin\nFound sfml-network-2.dll in /usr/local/mingw64/bin\nFound sfml-system-2.dll in /usr/local/mingw64/bin\nFound sfml-window-2.dll in /usr/local/mingw64/bin\nFound libthor.dll in /usr/local/mingw64/bin\n</code></pre>\n<p>Some DLLs won’t be found as they’re provided as part of Windows.</p>\n","url":"https://blog.rubenwardy.com/2018/05/07/mingw-copy-dlls/","summary":"After cross-compiling your project for Windows, you find that it crashes due to missing DLLs. I will show how to detect and copy required DLLs.","date_published":"2018-05-07T00:00:00.000Z","tags":["snippet","tutorials","cpp"]},{"id":"/2018/04/05/simple-kernel-in-c/","language":"en","title":"Rubix: My Simple Kernel written in C for arm","content_html":"<p>During the second year of university, I created a kernel for the ARMv7\ninstruction set. I went above and beyond what was required on this project,\nachieving a clean design and features such as a blocked process queue, piping,\nkill, and a simple filesystem. This was my favourite coursework so far. I found\nit very interesting to learn about and implement the things that we take for granted\nas programmers.</p>\n<p>I tried to stick to POSIX as much as possible, and stuck to the Linux method of\nhaving everything as either a file or process. Because pipes and standard in/out\nwere both “files”, I was able to implement both popen and piping of the output\nof a process to another process.</p>\n<h2 id=\"features\" tabindex=\"-1\">Features <a class=\"header-anchor\" href=\"#features\">#</a></h2>\n<h3 id=\"system-calls-and-helper-functions\" tabindex=\"-1\">System Calls and Helper Functions <a class=\"header-anchor\" href=\"#system-calls-and-helper-functions\">#</a></h3>\n<p>All system calls conform to the equivalent POSIX standard, except for\n<code>close</code> which will close the appropriate resource (no need for <code>fclose</code> or <code>pclose</code>)\nand <code>set_nonblocking</code> which is custom.</p>\n<ul>\n<li><code>yield</code> - ends the current time slice.</li>\n<li><code>write</code> - writes to an open file descriptor.</li>\n<li><code>read</code>  - reads from an file descriptor. Returns length of read, 0 on EOF.\nMay blocking - see <code>set_nonblocking</code>.</li>\n<li><code>close</code> - closes a file descriptor.</li>\n<li><code>dup2</code>  - duplicates fd from <code>old</code> to <code>new</code>. <code>new</code> is closed if it already exists.</li>\n<li><code>pipe</code>  - creates a pipe. fd[0] is read, fd[1] is write.</li>\n<li><code>fopen</code> - open file. Not quite POSIX, as it’s non-blocking</li>\n<li><code>fork</code>  - clones process.\nReturn value is 0 if child, PID of child if parent, -1 if error.</li>\n<li><code>exec</code>  - replaces the process with another program. PID is kept.\nStack and FDs (except in/out/err) are destroyed.</li>\n<li><code>exit</code>  - exits with exit code.</li>\n<li><code>wait</code>  - waits for a child program to exit, and gives exit code.</li>\n<li><code>kill</code>  - sends a kill signal to a process. Killed processes will not return an exit code.\n<code>signal</code> is not yet implemented.</li>\n<li><code>setpriority</code> - set priority of child process.</li>\n<li><code>set_nonblocking</code> - is not POSIX, unfortunately. Set pipe non-blocking.</li>\n</ul>\n<p>The following functions use system calls to provide a higher function:</p>\n<ul>\n<li><code>popen</code> - opens a process and returns a FD. Uses <code>fork</code>, <code>pipe</code>, <code>exec</code>, and <code>dup2</code>.</li>\n<li><code>wait</code>/<code>waitpid</code> - both use the <code>wait</code> syscall.</li>\n</ul>\n<h3 id=\"processes\" tabindex=\"-1\">Processes <a class=\"header-anchor\" href=\"#processes\">#</a></h3>\n<ul>\n<li><strong>time slicing</strong>      - timer based timer slices.</li>\n<li><strong>priority-based</strong>    - priority(P) = priority_base(P) + slices_since_last_ran(P)</li>\n<li><strong>blocked queue</strong>     - for processes waiting for a process or file resource.</li>\n<li><strong>process ownership</strong> - processes have a parent, which can kill/wait them.</li>\n<li><strong>process groups</strong>    - a limited type of process group, where all processes\nthat share a parent and the parent itself are in a group.</li>\n</ul>\n<h3 id=\"files\" tabindex=\"-1\">Files <a class=\"header-anchor\" href=\"#files\">#</a></h3>\n<p>The kernel allows the use of <strong>file descriptors</strong> to refer to resources. They\nare implemented under the hood using function pointers, which means that the main\npart of the kernel doesn’t even know what type of file they are. Can be blocking or not.</p>\n<p>Types:</p>\n<ul>\n<li><strong>pipe</strong>  - Pointed to by a FD.</li>\n<li><strong>in/out/err</strong> - these are “files” too!</li>\n<li><strong>filesystem</strong> - Files are limited to 256 bytes with a maximum of 10 files.</li>\n</ul>\n","url":"https://blog.rubenwardy.com/2018/04/05/simple-kernel-in-c/","summary":"I created an kernel with support for multiprocessing, pipes, kill, and a simple filesystem.","date_published":"2018-04-05T00:00:00.000Z","tags":["projects","computer_science","linux","cpp"]},{"id":"/2017/10/29/freenode-live/","language":"en","title":"Minetest at Freenode Live 2017","content_html":"<p>Shara and I exhibited on behalf of Minetest at Freenode #Live. Our stall had a\ncomputer where visitors could play Minetest. We talked about it and handed out\nleaflets.</p>\n<p>If you’d like to support Minetest, then please <a href=\"https://www.minetest.net/get-involved/\">get involved</a>\nand consider donating:</p>\n<ul>\n<li><a href=\"https://www.minetest.net/get-involved/#donate\">celeron55</a></li>\n<li><a href=\"https://rubenwardy.com/donate/\">rubenwardy (me)</a></li>\n</ul>\n<h2 id=\"photos\" tabindex=\"-1\">Photos <a class=\"header-anchor\" href=\"#photos\">#</a></h2>\n<div class=\"row row-wrap gap-4 figure-grid\">\n\t\t\t<div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/T3VooHHCPq-500.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/gJH-XJUkED-500.webp 500w\"><img src=\"/static/img/gJH-XJUkED-500.jpeg\" alt=\"Our stall\" loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"281\"></picture></a><figcaption>Our stall</figcaption></figure></div><div class=\"col-md\"><figure class=\"undefined\"><a href=\"/static/img/D0M_23dKTc-500.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/aQCb_RmDrm-500.webp 500w\"><img src=\"/static/img/aQCb_RmDrm-500.jpeg\" alt=\"People playing MT\" loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"281\"></picture></a><figcaption>People playing MT</figcaption></figure></div>\n\t\t\t</div>\n<h2 id=\"resources\" tabindex=\"-1\">Resources <a class=\"header-anchor\" href=\"#resources\">#</a></h2>\n<ul>\n<li><a href=\"https://rwdy.uk/freenode-live/\">Flip book</a></li>\n<li><a href=\"/static/files/freenode-leaflet.pdf\">Resources leaflet</a></li>\n</ul>\n<h2 id=\"useful-links\" tabindex=\"-1\">Useful Links <a class=\"header-anchor\" href=\"#useful-links\">#</a></h2>\n<ul>\n<li>General\n<ul>\n<li><a href=\"https://www.minetest.net\">Minetest Website</a></li>\n<li><a href=\"https://forum.minetest.net\">Minetest Forums</a></li>\n<li>#minetest on irc.freenode.net</li>\n<li><a href=\"https://wiki.minetest.net/Setting_up_a_server\">Setting up a server</a></li>\n</ul>\n</li>\n<li>Modding and Development\n<ul>\n<li><a href=\"https://dev.minetest.net\">Modding and Developer Wiki</a></li>\n<li><a href=\"https://rubenwardy.com/minetest_modding_book\">Minetest Modding Book</a></li>\n<li><a href=\"https://api.minetest.net/\">API Reference</a></li>\n<li>#minetest-dev on irc.freenode.net</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"licenses-and-credits\" tabindex=\"-1\">Licenses and Credits <a class=\"header-anchor\" href=\"#licenses-and-credits\">#</a></h2>\n<p>Materials copyright © 2017 to rubenwardy and Shara.\nLicense: <a href=\"https://creativecommons.org/licenses/by-sa/4.0/\">CC-BY-SA 4.0</a></p>\n<p>The following are the copyright owners of the screenshots,\nnot the mods involved.</p>\n<ul>\n<li>Flipbook Images\n<ul>\n<li>3D Armor © Stu.</li>\n<li>Bags © Shara.</li>\n<li>Caverealms Lite © Shara.</li>\n<li>CTF © Shara.</li>\n<li>Ethereal © Shara.</li>\n<li>HomeDecor © Shara.</li>\n<li>Lightning © sofar.</li>\n<li>Mesecons © unk.</li>\n<li>Mobs Redo © Shara.</li>\n<li>MoreBlocks © Shara and shivajiva.</li>\n<li>Other Worlds © Shara.</li>\n<li>Protector © Shara.</li>\n<li>Simple Skins © Shara.</li>\n<li>SkyBlock © shivajiva.</li>\n<li>Snow drift © paramat.</li>\n<li>Technic © Mr_Pardison.</li>\n<li>Unified Inventory © Shara.</li>\n<li>Warp Potions © Shara.</li>\n<li>WorldEdit © shivajiva</li>\n</ul>\n</li>\n<li>Leaflet Images\n<ul>\n<li>Front/back cover photo © Shara, using Ethereal.</li>\n<li>Inside left stone © Minetest, from minetest.net.</li>\n<li>Small photo left © Shara, from RedCat Creative.</li>\n<li>Small photo right © sfan5, from <a href=\"https://github.com/minetest/minetest.github.io/issues/78#issuecomment-266480046\">a Github issue.</a></li>\n<li>Inside right water © Shara, from RedCat Creative.</li>\n</ul>\n</li>\n</ul>\n<p>Have I missed you out? Please contact me.</p>\n","url":"https://blog.rubenwardy.com/2017/10/29/freenode-live/","summary":"I ran an exhibit for Minetest at Freenode Live. Our stall had a computer where visitors could play Minetest. We talked about it and handed out leaflets.","date_published":"2017-10-29T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/T3VooHHCPq-500.png","tags":["luanti"]},{"id":"/2017/09/30/install-autodesk-maya-ubuntu-17-04/","language":"en","title":"Install Autodesk Maya on Ubuntu 17.04","content_html":"<p>Whilst Maya claims to have Linux support, it only officially supports RedHat\nand CentOS Linux. It is, however, still possible to install it on Debian and\nUbuntu based systems with some leg work.</p>\n<h2 id=\"key-points\" tabindex=\"-1\">Key points <a class=\"header-anchor\" href=\"#key-points\">#</a></h2>\n<p>To make this work, you need to:</p>\n<ul>\n<li>Install apt dependencies, including fonts.</li>\n<li>Manually install the deb for libpng12, an obsolete package that Maya depends on.</li>\n<li>Convert the .rpm files to .deb using <a href=\"https://help.ubuntu.com/community/RPM/AlienHowto\">Alien</a>,\nand install them separately from the GUI installer.\nPlease note that the conversion will probably take quite a while.</li>\n<li>Add environment variables to stop segfaults and errors.</li>\n</ul>\n<h2 id=\"steps\" tabindex=\"-1\">Steps <a class=\"header-anchor\" href=\"#steps\">#</a></h2>\n<h3 id=\"1-install-as-root\" tabindex=\"-1\">1. Install as Root <a class=\"header-anchor\" href=\"#1-install-as-root\">#</a></h3>\n<p>If you have already downloaded maya, then <code>cd</code> to the containing folder.\nIf you haven’t, don’t worry. This script will download it.</p>\n<p>Don’t worry if the Autodesk installer claims the installation was unsuccessful,\nit <em>may</em> still have worked.</p>\n<p>Run the following script as root:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token comment\"># Make sure we’re running with root permissions.</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token variable\"><span class=\"token variable\">`</span><span class=\"token function\">whoami</span><span class=\"token variable\">`</span></span> <span class=\"token operator\">!=</span> root <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Please run this script using sudo\"</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Just type “sudo !!”\"</span>\n    <span class=\"token builtin class-name\">exit</span> <span class=\"token number\">1</span>\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token comment\"># Check for 64-bit arch</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span>uname <span class=\"token parameter variable\">-m</span> <span class=\"token operator\">!=</span> x86_64<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Maya will only run on 64-bit linux.\"</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Please install the 64-bit ubuntu and try again.\"</span>\n    <span class=\"token builtin class-name\">exit</span> <span class=\"token number\">1</span>\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token comment\"># Detect existing download, else download from scratch</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token parameter variable\">-f</span> <span class=\"token string\">\"setup\"</span> <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Found existing Maya installer\"</span>\n<span class=\"token keyword\">else</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"No Maya installer in current directory\"</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Press [ENTER] to download and extract Maya 2017, or ctrl+C to cancel\"</span>\n    <span class=\"token builtin class-name\">read</span> <span class=\"token parameter variable\">-n</span> <span class=\"token number\">1</span> <span class=\"token parameter variable\">-s</span> <span class=\"token parameter variable\">-r</span> <span class=\"token parameter variable\">-p</span> <span class=\"token string\">\"\"</span>\n\n    <span class=\"token comment\">## Create Download Directory</span>\n    <span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> maya2017Install\n    <span class=\"token builtin class-name\">cd</span> maya2017Install\n\n    <span class=\"token comment\">## Download Maya Install Files</span>\n    <span class=\"token function\">wget</span> https://edutrial.autodesk.com/NET17SWDLD/2017/MAYA/ESD/Autodesk_Maya_2017_EN_JP_ZH_Linux_64bit.tgz\n    <span class=\"token function\">tar</span> xvf Autodesk_Maya_2017_EN_JP_ZH_Linux_64bit.tgz\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token comment\"># Install Dependencies</span>\n<span class=\"token function\">wget</span> https://launchpadlibrarian.net/183708483/libxp6_1.0.2-2_amd64.deb\n<span class=\"token function\">apt-get</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">-y</span> libssl1.0.0 gcc libssl-dev  libjpeg62  alien  csh  tcsh libaudiofile-dev  libglw1-mesa elfutils libglw1-mesa-dev mesa-utils xfstt ttf-liberation xfonts-100dpi xfonts-75dpi ttf-mscorefonts-installer libfam0 libfam-dev libgstreamer-plugins-base0.10-0\n\n<span class=\"token comment\"># Fix .so libs</span>\n<span class=\"token function\">wget</span> https://mirrors.kernel.org/ubuntu/pool/main/libp/libpng/libpng12-0_1.2.54-1ubuntu1_amd64.deb\n<span class=\"token function\">ln</span> <span class=\"token parameter variable\">-s</span> /usr/lib/x86_64-linux-gnu/libtiff.so /usr/lib/x86_64-linux-gnu/libtiff.so.3\n<span class=\"token function\">ln</span> <span class=\"token parameter variable\">-s</span> /usr/lib/x86_64-linux-gnu/libcrypto.so /usr/lib/x86_64-linux-gnu/libcrypto.so.10\n<span class=\"token function\">ln</span> <span class=\"token parameter variable\">-s</span> /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/x86_64-linux-gnu/libssl.so.10\n\n<span class=\"token comment\"># Convert .rpm to .dev</span>\nalien <span class=\"token parameter variable\">-cv</span> *.rpm\n\n<span class=\"token comment\"># Install .deb</span>\ndpkg <span class=\"token parameter variable\">-i</span> *.deb\n\n<span class=\"token comment\"># Run installer (setup executable)</span>\n<span class=\"token function\">mv</span> /usr/bin/rpm /usr/bin/rpm_backup\n<span class=\"token function\">ln</span> <span class=\"token parameter variable\">-s</span> /bin/true /usr/bin/rpm\n<span class=\"token function\">chmod</span> +x ./setup\n./setup\n<span class=\"token function\">rm</span> /usr/bin/rpm\n<span class=\"token function\">mv</span> /usr/bin/rpm_backup /usr/bin/rpm\n\n<span class=\"token comment\"># Make tmp dir for Maya</span>\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> /usr/tmp\n\n<span class=\"token comment\"># This is fine, as it's just another /tmp dir (which is 777 too)</span>\n<span class=\"token function\">chmod</span> <span class=\"token number\">777</span> /usr/tmp\n\n<span class=\"token comment\"># Maya Camera Modifier Key</span>\ngsettings <span class=\"token builtin class-name\">set</span> org.gnome.desktop.wm.preferences mouse-button-modifier <span class=\"token string\">\"&lt;Super>\"</span>\n\n<span class=\"token comment\"># Ensure that Fonts are Loaded</span>\nxset +fp /usr/share/fonts/X11/100dpi/\nxset +fp /usr/share/fonts/X11/75dpi/\nxset fp rehash\n\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Maya was installed successfully.\"</span></code></pre>\n<h3 id=\"2-modifications-for-each-user\" tabindex=\"-1\">2. Modifications for each user <a class=\"header-anchor\" href=\"#2-modifications-for-each-user\">#</a></h3>\n<p>Then run the following script for each user you want to run Maya:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#!/bin/bash</span>\n\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token variable\"><span class=\"token variable\">`</span><span class=\"token function\">whoami</span><span class=\"token variable\">`</span></span> <span class=\"token operator\">==</span> root <span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token keyword\">then</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Please run this script using the user you want to run Maya with.\"</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"I really hope that isn't root!\"</span>\n    <span class=\"token builtin class-name\">exit</span> <span class=\"token number\">1</span>\n<span class=\"token keyword\">fi</span>\n\n<span class=\"token function\">mkdir</span> <span class=\"token parameter variable\">-p</span> ~/maya/2017/\n\n<span class=\"token comment\"># Fix Segmentation Fault Error</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"MAYA_DISABLE_CIP=1\"</span> <span class=\"token operator\">>></span> ~/maya/2017/Maya.env\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"LC_ALL=C\"</span> <span class=\"token operator\">>></span> ~/maya/2017/Maya.env</code></pre>\n<h2 id=\"troubleshooting\" tabindex=\"-1\">Troubleshooting <a class=\"header-anchor\" href=\"#troubleshooting\">#</a></h2>\n<p>The above scripts are heavily dependent on your OS version and state,\nso you’re likely to have one of the following errors.</p>\n<h3 id=\"segmentation-fault-or-color-management-error-dialog\" tabindex=\"-1\">Segmentation Fault or Color Management Error Dialog <a class=\"header-anchor\" href=\"#segmentation-fault-or-color-management-error-dialog\">#</a></h3>\n<p>Make sure you’re running Maya with the following environment variables.\nYou can do this by adding the following lines to <code>~/maya/2017/Maya.env</code>:</p>\n<p>MAYA_DISABLE_CIP=1\nLC_ALL=C</p>\n<h3 id=\"lib-so-cannot-open-shared-object-file\" tabindex=\"-1\">lib*.so.*: cannot open shared object file <a class=\"header-anchor\" href=\"#lib-so-cannot-open-shared-object-file\">#</a></h3>\n<p>This means the an compile lib object file cannot be found when running it.\nTo fix this:</p>\n<ul>\n<li>\n<p>Check for an existing file with the same first segment in <code>/usr/lib/x86_64-linux-gnu</code>.\nFor example, if it complains about <code>libssl.so.12</code> then look for <code>libssl.so</code> in the\ndirectory.</p>\n</li>\n<li>\n<p>If the file exists, then create a soft symlink like so:</p>\n<p>ln -s /usr/lib/x86_64-linux-gnu/EXISTING.so /usr/lib/x86_64-linux-gnu/NOTFOUND.so</p>\n<p>For example,</p>\n<p>ln -s /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/x86_64-linux-gnu/libssl.so.12</p>\n</li>\n</ul>\n<h3 id=\"failed-to-install-maya-in-autodesk-gui\" tabindex=\"-1\">Failed to install Maya in Autodesk GUI <a class=\"header-anchor\" href=\"#failed-to-install-maya-in-autodesk-gui\">#</a></h3>\n<p>Don’t worry about this, it may have still worked. Check it by running <code>maya</code> -\nbut make sure to click finish on the installer and run the user script first.</p>\n<h2 id=\"references-and-sources\" tabindex=\"-1\">References and Sources <a class=\"header-anchor\" href=\"#references-and-sources\">#</a></h2>\n<ul>\n<li>Script modified from <a href=\"https://gist.github.com/borgfriend/b83467639cb8039dc79974bf780a4994\">https://gist.github.com/borgfriend/b83467639cb8039dc79974bf780a4994</a>.</li>\n<li><a href=\"https://forums.autodesk.com/t5/maya-forum/render-crash-on-linux/m-p/5608552\">Render crash on Linux</a>, Autodesk forums.</li>\n<li>libpng12 on packages.ubuntu.com</li>\n</ul>\n","url":"https://blog.rubenwardy.com/2017/09/30/install-autodesk-maya-ubuntu-17-04/","summary":"Whilst Maya claims to have Linux support, it only officially supports RedHat and CentOS Linux. Here's how to install it on Debian and Ubuntu based systems.","date_published":"2017-09-30T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/DDe817RZiW-540.png","tags":["snippet","linux","tutorials"]},{"id":"/2017/08/13/multiplayer-topdown-sandbox-game/","language":"en","title":"RVWP: Multiplayer Topdown Sandbox Game in C++","content_html":"<p>For the last two years, I have been working on a\n<a href=\"/2025/02/12/rvwp/\">very ambitious game</a>.\nThe game is a top-down sandbox with multiplayer support. I’m aiming towards a\ncity-based game, where players can wander around a procedurally generated city.\nOne of the main reasons I started creating this game is to learn about multiplayer\nnetworking at a low level - client-side prediction, server-side reconcilliation,\ncheat preventation, and reducing the visual effect of latency.</p>\n<figure class=\"undefined\"><a href=\"/static/img/Tg1WyD7YGt-450.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/WHovSDoaCg-450.webp 450w\"><img src=\"/static/img/WHovSDoaCg-450.png\" alt=\"A recent video demonstrating the current state of the game.\" loading=\"lazy\" decoding=\"async\" width=\"450\" height=\"253\"></picture></a><figcaption>A recent video demonstrating the current state of the game.</figcaption></figure>\n<p>The project is written in C++ using SFML. It’s the biggest project I’ve ever created,\nwith 9,000 lines of code and 64 classes.</p>\n<p>I decided to split the project into several milestones, each on iterating on the\nlast and moving it towards a more complete game.</p>\n<h3 id=\"features\" tabindex=\"-1\">Features <a class=\"header-anchor\" href=\"#features\">#</a></h3>\n<ul>\n<li>Infinite map</li>\n<li>Multiplayer</li>\n<li>Block placement</li>\n<li>Multiple map z-levels (can go up and down stairs)</li>\n<li>Doors and stairs</li>\n<li>Player chat</li>\n<li>Game controller support (only tested with Xbox 360 controller)</li>\n<li>Inventory and dual wielding</li>\n<li>Weapons (guns and melee)</li>\n<li>Main menu</li>\n</ul>\n<h3 id=\"milestone-1-foundations\" tabindex=\"-1\">Milestone 1 - Foundations <a class=\"header-anchor\" href=\"#milestone-1-foundations\">#</a></h3>\n<p>MS1 adds a foundation for future work, including: networking,\nMVC based architecture, basic entities, raytracer, materials to specify how to\ndraw something, collison, and loads more.</p>\n<p>I started off by writing the basic world model and spatial paritioning. I then\nwrote the client/server handshake.</p>\n<figure class=\"undefined\"><a href=\"/static/img/UKPZ4c3uWJ-1024.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/O6lk_qGiac-540.webp 540w\"><img src=\"/static/img/O6lk_qGiac-540.png\" alt=\"Just some basic code with socket communication.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"293\"></picture></a><figcaption>Just some basic code with socket communication.</figcaption></figure>\n<p>After that, I added rendering so I could see the world, and start adding\nactual game features.</p>\n<figure class=\"undefined\"><a href=\"/static/img/zdu7QGC21q-1024.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/_PY7dxDGIB-540.webp 540w\"><img src=\"/static/img/_PY7dxDGIB-540.png\" alt=\"The first ever screenshot, just after adding rendering.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"343\"></picture></a><figcaption>The first ever screenshot, just after adding rendering.</figcaption></figure>\n<p>The next step is to add player movement and entity synchronisation, and then\nnode placement:</p>\n<figure class=\"undefined\"><a href=\"/static/img/CRyTOhcDFq-1024.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/CoJoNnbndZ-540.webp 540w\"><img src=\"/static/img/CoJoNnbndZ-540.png\" alt=\"Player movement and node placement.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"394\"></picture></a><figcaption>Player movement and node placement.</figcaption></figure>\n<p>I also decided to switch to Kenney’s texture pack during this time.</p>\n<figure class=\"undefined\"><a href=\"/static/img/l-AAtVi8-m-1024.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/8KYRfzTZjx-540.webp 540w\"><img src=\"/static/img/8KYRfzTZjx-540.png\" alt=\"Switched to using one of Kenney's texture packs.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"352\"></picture></a><figcaption>Switched to using one of Kenney's texture packs.</figcaption></figure>\n<h3 id=\"milestone-2-and-3-doors-entity-damage-and-interaction\" tabindex=\"-1\">Milestone 2 and 3 - Doors, Entity Damage, and Interaction <a class=\"header-anchor\" href=\"#milestone-2-and-3-doors-entity-damage-and-interaction\">#</a></h3>\n<p>Adds the ability to open doors, highlight entities, and damage them.</p>\n<h3 id=\"milestone-4-gui\" tabindex=\"-1\">Milestone 4 - GUI <a class=\"header-anchor\" href=\"#milestone-4-gui\">#</a></h3>\n<p>Next I needed a way to interact with the inventory, so I added an MVC framework\nto switch between windows and the game, and an inventory window to use the inventory.</p>\n<figure class=\"undefined\"><a href=\"/static/img/gCsWYpxvKw-779.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/1kIDr6cqm1-540.webp 540w\"><img src=\"/static/img/1kIDr6cqm1-540.jpeg\" alt=\"Inventory GUI added, and the inv item position synchronisation.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"363\"></picture></a><figcaption>Inventory GUI added, and the inv item position synchronisation.</figcaption></figure>\n<p>I also added the ability for entities and players to die, and for players to be\nrespawned.</p>\n<h3 id=\"milestone-5-weapons-and-tool-specs\" tabindex=\"-1\">Milestone 5 - Weapons and Tool Specs <a class=\"header-anchor\" href=\"#milestone-5-weapons-and-tool-specs\">#</a></h3>\n<p>I allowed items to provide a ‘tool spec’. A tool spec is a struct which\nspecifies what functionalities an item has - for example, whether it can\ndamage tiles and entities, whether it can shoot bullets, and how much damage it\ngives.</p>\n<h3 id=\"milestone-6-playability\" tabindex=\"-1\">Milestone 6 - Playability <a class=\"header-anchor\" href=\"#milestone-6-playability\">#</a></h3>\n<p>This was quite a big milestone. I added the following features:</p>\n<ul>\n<li>Support for multiple Z-levels (ie: going up and down stairs).</li>\n<li>Infinite map by adding chunk loading, and keeping track of whether a player knows about a chunk.</li>\n<li>A main menu with the ability to type in a server URL and port.</li>\n<li>Chat.</li>\n<li>Xbox controller support.</li>\n<li>Visual bullets using particles.</li>\n</ul>\n<figure class=\"undefined\"><a href=\"/static/img/zf-Dk4eMTR-1200.jpeg\"><picture><source type=\"image/webp\" srcset=\"/static/img/W4cVCdy-O3-540.webp 540w\"><img src=\"/static/img/W4cVCdy-O3-540.jpeg\" alt=\"Controller support.\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"303\"></picture></a><figcaption>Controller support.</figcaption></figure>\n<h3 id=\"where-now\" tabindex=\"-1\">Where now? <a class=\"header-anchor\" href=\"#where-now\">#</a></h3>\n<p>Since the last milestone, I have been working on increasing code coverage.\nThe world logic is now at 97% covered, however the client (rendering, input, packet handling)\nand the server (packet handling, profiles, interaction logic) still need tests.</p>\n<p>The client and server are both designed in an MVP style, which means that it\nshouldn’t be too hard to isolate and test them using dependency injection,\nwhich is something I’d like to get around to.</p>\n<p>As for actual game progress, I’d like to work on making the server more\nscalable. I’m thinking of implementing a distributed architecture with\nchunk workers - processes that assigned to be the “truth” of a group\nof chunks, to update them based on logic,  and to respond to any requests\nrelating to them.</p>\n<p>Another thing I need to investigate is lighting, as it’s good example of an\nalgorithm that needs to propogate through chunk boundaries and so will help\nwith making sure my workers can handle it.</p>\n","url":"https://blog.rubenwardy.com/2017/08/13/multiplayer-topdown-sandbox-game/","summary":"The game is a top-down sandbox with multiplayer support. I'm aiming towards a city-based game, where players can wander around a procedurally generated city.","date_published":"2017-08-13T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/zf-Dk4eMTR-540.png","tags":["projects","sfml","cpp","gamedev","rvwp"]},{"id":"/2017/03/07/mingw-w64-cmake-unrecognised-option-rdynamic-ubuntu/","language":"en","title":"Mingw-w64 and CMake: unrecognised option -rdynamic on Ubuntu","content_html":"<p>I had an issue where CMake was failing on a compiler test with the\nfollowing error:</p>\n<pre><code>error: unrecognized option '-rdynamic'\n</code></pre>\n<p>The problem was that CMake caches settings such as compiler flags\nin CMakeCache.txt, so you need to clear the cache when changing the\nplatform. Do this by deleting CMakeFiles and CMakeCache.txt</p>\n","url":"https://blog.rubenwardy.com/2017/03/07/mingw-w64-cmake-unrecognised-option-rdynamic-ubuntu/","summary":"How to solve \"error: unrecognized option '-rdynamic'\" on Ubuntu.","date_published":"2017-03-07T00:00:00.000Z","tags":["snippet","tutorials","cpp"]},{"id":"/2016/09/30/opencv-combine-images-into-strip/","language":"en","title":"OpenCV: compile images (Mats) into a strip","content_html":"<p>I created a very short C++ snippet to accumulate a series of <code>Mat</code>s into\na single <code>Mat</code> strip. It works like <code>acc = acc + m</code> - a new mat is added to the\naccumulator each time, then stored in the accumulator again.</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">pragma</span> <span class=\"token expression\">once</span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;opencv2/opencv.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;opencv2/core/core.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;opencv2/imgcodecs.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;assert.h></span></span>\n\n<span class=\"token keyword\">using</span> <span class=\"token keyword\">namespace</span> cv<span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">MatStrip</span>\n<span class=\"token punctuation\">{</span>\n<span class=\"token keyword\">public</span><span class=\"token operator\">:</span>\n    Mat current<span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">float</span> scale<span class=\"token punctuation\">;</span>\n\n    <span class=\"token function\">MatStrip</span><span class=\"token punctuation\">(</span>Mat start<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> scale<span class=\"token operator\">=</span><span class=\"token number\">0.3f</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span>\n        <span class=\"token function\">scale</span><span class=\"token punctuation\">(</span>scale<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token function\">resize</span><span class=\"token punctuation\">(</span>start<span class=\"token punctuation\">,</span> current<span class=\"token punctuation\">,</span> <span class=\"token function\">Size</span><span class=\"token punctuation\">(</span>start<span class=\"token punctuation\">.</span>cols <span class=\"token operator\">*</span> scale<span class=\"token punctuation\">,</span> start<span class=\"token punctuation\">.</span>rows <span class=\"token operator\">*</span> scale<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">,</span> INTER_CUBIC<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">void</span> <span class=\"token function\">add</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> Mat <span class=\"token operator\">&amp;</span>in_o<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        Mat in<span class=\"token punctuation\">;</span>\n        <span class=\"token function\">resize</span><span class=\"token punctuation\">(</span>in_o<span class=\"token punctuation\">,</span> in<span class=\"token punctuation\">,</span> <span class=\"token function\">Size</span><span class=\"token punctuation\">(</span>in_o<span class=\"token punctuation\">.</span>cols <span class=\"token operator\">*</span> scale<span class=\"token punctuation\">,</span> in_o<span class=\"token punctuation\">.</span>rows <span class=\"token operator\">*</span> scale<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.0</span><span class=\"token punctuation\">,</span> INTER_CUBIC<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>in<span class=\"token punctuation\">.</span><span class=\"token function\">type</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">==</span> current<span class=\"token punctuation\">.</span><span class=\"token function\">type</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Size sz1 <span class=\"token operator\">=</span> current<span class=\"token punctuation\">.</span><span class=\"token function\">size</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Size sz2 <span class=\"token operator\">=</span> in<span class=\"token punctuation\">.</span><span class=\"token function\">size</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Mat <span class=\"token function\">im3</span><span class=\"token punctuation\">(</span>sz1<span class=\"token punctuation\">.</span>height<span class=\"token punctuation\">,</span> sz1<span class=\"token punctuation\">.</span>width<span class=\"token operator\">+</span>sz2<span class=\"token punctuation\">.</span>width<span class=\"token punctuation\">,</span> current<span class=\"token punctuation\">.</span><span class=\"token function\">type</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Mat <span class=\"token function\">left</span><span class=\"token punctuation\">(</span>im3<span class=\"token punctuation\">,</span> <span class=\"token function\">Rect</span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> sz1<span class=\"token punctuation\">.</span>width<span class=\"token punctuation\">,</span> sz1<span class=\"token punctuation\">.</span>height<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        current<span class=\"token punctuation\">.</span><span class=\"token function\">copyTo</span><span class=\"token punctuation\">(</span>left<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        Mat <span class=\"token function\">right</span><span class=\"token punctuation\">(</span>im3<span class=\"token punctuation\">,</span> <span class=\"token function\">Rect</span><span class=\"token punctuation\">(</span>sz1<span class=\"token punctuation\">.</span>width<span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> sz2<span class=\"token punctuation\">.</span>width<span class=\"token punctuation\">,</span> sz2<span class=\"token punctuation\">.</span>height<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        in<span class=\"token punctuation\">.</span><span class=\"token function\">copyTo</span><span class=\"token punctuation\">(</span>right<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        current <span class=\"token operator\">=</span> im3<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span></code></pre>\n","url":"https://blog.rubenwardy.com/2016/09/30/opencv-combine-images-into-strip/","summary":"I created a very short C++ snippet to accumulate a series of Mats into a single Mat strip.","date_published":"2016-09-30T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/yixVzf1FwF-540.png","tags":["snippet","tutorials","cpp"]},{"id":"/2016/09/12/sfml-thor-emit-a-single-particle/","language":"en","title":"How to emit a single particle using SFML's Thor Particle System","content_html":"<p>Usecase: shells dropping in sync with firing, fake bullets, etc</p>\n<p>You must use a particle emitter to create particles, however this doesn’t mean\nit’s impossible to create single particles on command. You can create a particle\nemitter which simply adds particles from a queue to the system</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">pragma</span> <span class=\"token expression\">once</span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;SFML/Graphics.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;Thor/Math.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;Thor/Graphics.hpp></span></span>\n<span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">&lt;Thor/Particles.hpp></span></span>\n\n<span class=\"token keyword\">namespace</span> sfext <span class=\"token punctuation\">{</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name\">SingleParticleEmitter</span>\n<span class=\"token punctuation\">{</span>\n    std<span class=\"token double-colon punctuation\">::</span>vector<span class=\"token operator\">&lt;</span>thor<span class=\"token double-colon punctuation\">::</span>Particle<span class=\"token operator\">></span> <span class=\"token operator\">*</span>particles<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">public</span><span class=\"token operator\">:</span>\n    <span class=\"token function\">SingleParticleEmitter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">SingleParticleEmitter</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> SingleParticleEmitter<span class=\"token operator\">&amp;</span> that<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">void</span> <span class=\"token keyword\">operator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span>thor<span class=\"token double-colon punctuation\">::</span>EmissionInterface<span class=\"token operator\">&amp;</span> system<span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span>Time dt<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">void</span> <span class=\"token function\">push</span><span class=\"token punctuation\">(</span>thor<span class=\"token double-colon punctuation\">::</span>Particle particle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">void</span> <span class=\"token function\">push</span><span class=\"token punctuation\">(</span>sf<span class=\"token double-colon punctuation\">::</span>Vector2f position<span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span>Vector2f velocity<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> lifetime<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> rot<span class=\"token operator\">=</span><span class=\"token number\">0</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> rotsp<span class=\"token operator\">=</span><span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// end namesapce sfext</span></code></pre>\n<p>The above allows you to use the push functions to emit particles.\nHere is an example of it in use:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\">SingleParticleEmitter single_emitter<span class=\"token punctuation\">;</span>\nthor<span class=\"token double-colon punctuation\">::</span>ParticleSystem particle_system<span class=\"token punctuation\">;</span>\nparticle_system<span class=\"token punctuation\">.</span><span class=\"token function\">addEmitter</span><span class=\"token punctuation\">(</span>single_emitter<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// When you need to emit a shell:</span>\n\nsingle_emitter<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>origin<span class=\"token punctuation\">,</span> velocity<span class=\"token punctuation\">,</span> <span class=\"token number\">1.0f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// lifetime of the particle in seconds --^</span></code></pre>\n<p>Here is the cpp file:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token macro property\"><span class=\"token directive-hash\">#</span><span class=\"token directive keyword\">include</span> <span class=\"token string\">\"singleparticleemitter.hpp\"</span></span>\n\n<span class=\"token keyword\">using</span> <span class=\"token keyword\">namespace</span> sfext<span class=\"token punctuation\">;</span>\n\n<span class=\"token class-name\">SingleParticleEmitter</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">SingleParticleEmitter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    particles <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> std<span class=\"token double-colon punctuation\">::</span><span class=\"token generic-function\"><span class=\"token function\">vector</span><span class=\"token generic class-name\"><span class=\"token operator\">&lt;</span>thor<span class=\"token double-colon punctuation\">::</span>Particle<span class=\"token operator\">></span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token class-name\">SingleParticleEmitter</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">SingleParticleEmitter</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> SingleParticleEmitter<span class=\"token operator\">&amp;</span> that<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    particles <span class=\"token operator\">=</span> that<span class=\"token punctuation\">.</span>particles<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token class-name\">SingleParticleEmitter</span><span class=\"token double-colon punctuation\">::</span><span class=\"token keyword\">operator</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">(</span>thor<span class=\"token double-colon punctuation\">::</span>EmissionInterface<span class=\"token operator\">&amp;</span> system<span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span>Time dt<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">auto</span> particle <span class=\"token operator\">:</span> <span class=\"token operator\">*</span>particles<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        system<span class=\"token punctuation\">.</span><span class=\"token function\">emitParticle</span><span class=\"token punctuation\">(</span>particle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    particles<span class=\"token operator\">-></span><span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token class-name\">SingleParticleEmitter</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>thor<span class=\"token double-colon punctuation\">::</span>Particle particle<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    particles<span class=\"token operator\">-></span><span class=\"token function\">push_back</span><span class=\"token punctuation\">(</span>particle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">void</span> <span class=\"token class-name\">SingleParticleEmitter</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>sf<span class=\"token double-colon punctuation\">::</span>Vector2f position<span class=\"token punctuation\">,</span> sf<span class=\"token double-colon punctuation\">::</span>Vector2f velocity<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> lifetime<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> rot<span class=\"token punctuation\">,</span> <span class=\"token keyword\">float</span> rotsp<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">{</span>\n    thor<span class=\"token double-colon punctuation\">::</span>Particle <span class=\"token function\">particle</span><span class=\"token punctuation\">(</span>sf<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">seconds</span><span class=\"token punctuation\">(</span>lifetime<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>position <span class=\"token operator\">=</span> position<span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>velocity <span class=\"token operator\">=</span> velocity<span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>rotation <span class=\"token operator\">=</span> rot<span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>rotationSpeed <span class=\"token operator\">=</span> rotsp<span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>scale <span class=\"token operator\">=</span> sf<span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Vector2f</span><span class=\"token punctuation\">(</span><span class=\"token number\">1.f</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1.f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>color <span class=\"token operator\">=</span> sf<span class=\"token double-colon punctuation\">::</span>Color<span class=\"token double-colon punctuation\">::</span>White<span class=\"token punctuation\">;</span>\n    particle<span class=\"token punctuation\">.</span>textureIndex <span class=\"token operator\">=</span> <span class=\"token number\">0u</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">push</span><span class=\"token punctuation\">(</span>particle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n</code></pre>\n","url":"https://blog.rubenwardy.com/2016/09/12/sfml-thor-emit-a-single-particle/","summary":"You must use a particle emitter to create particles, however this doesn't mean it's impossible to create single particles on command.","date_published":"2016-09-12T00:00:00.000Z","tags":["snippet","tutorials","sfml","cpp","gamedev"]},{"id":"/2016/08/16/sfgui-center-window-container/","language":"en","title":"How to center a SFGUI window (sfg::Window)","content_html":"<p>Simply get the SFGUI window size using GetAllocation,\nthe sfml window size using getSize, then do this arithmetic:</p>\n<pre class=\"language-cpp\"><code class=\"language-cpp\"><span class=\"token keyword\">auto</span> window <span class=\"token operator\">=</span> sfg<span class=\"token double-colon punctuation\">::</span><span class=\"token class-name\">Window</span><span class=\"token double-colon punctuation\">::</span><span class=\"token function\">Create</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">auto</span> win_rect <span class=\"token operator\">=</span> window<span class=\"token operator\">-></span><span class=\"token function\">GetAllocation</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nsf<span class=\"token double-colon punctuation\">::</span>Vector2f <span class=\"token function\">size</span><span class=\"token punctuation\">(</span>win_rect<span class=\"token punctuation\">.</span>width<span class=\"token punctuation\">,</span> win_rect<span class=\"token punctuation\">.</span>height<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nwindow<span class=\"token operator\">-></span><span class=\"token function\">SetPosition</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>sf<span class=\"token double-colon punctuation\">::</span>Vector2f<span class=\"token punctuation\">)</span>rwindow<span class=\"token operator\">-></span><span class=\"token function\">getSize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-</span> size<span class=\"token punctuation\">)</span> <span class=\"token operator\">/</span> <span class=\"token number\">2.0f</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre>\n","url":"https://blog.rubenwardy.com/2016/08/16/sfgui-center-window-container/","summary":"Simply get the SFGUI window size using GetAllocation, the sfml window size using getSize, then do some arithmetic.","date_published":"2016-08-16T00:00:00.000Z","tags":["snippet","tutorials","sfml","cpp","gamedev"]},{"id":"/2016/07/21/rimworld-setup-monodevelop/","language":"en","title":"Setting up a MonoDevelop solution and project for RimWorld modding","content_html":"<p>This tutorial will show you how to create a new MonoDevelop solution and project,\nand how to properly connect it to RimWorld. You’ll need to have an installation\nof Mono that supports .NET 3.5 -\n<a href=\"/2016/07/20/rimworld-install-monodevelop-with-dot-net-3.5/\">here is a tutorial on how to install it</a>.</p>\n<ul>\n<li>Open Monodevelop (<a href=\"/2016/07/20/rimworld-install-monodevelop-with-dot-net-3.5/\">see this guide to install with .NET 3.5 support</a>)</li>\n<li>Create a new solution\n<ul>\n<li>Click File &gt; New &gt; Solution</li>\n<li>Select the C# tab</li>\n<li>Select Library (C#)</li>\n<li>Enter your mod’s name into the <code>name</code> field.</li>\n<li>Set the location to “Source” in your mod’s directory. Eg, RimWorld/mods/ModName/Source.</li>\n<li><em>Optional:</em> Untick “Create a directory for solution”/“Create a project within the solution directory”.</li>\n</ul>\n</li>\n<li>Add references to the RimWorld DLL\n<ul>\n<li>In the left panel, right click ModName &gt; ModName &gt; References. Select Edit References.</li>\n<li>Go to the .NET Assembly tab</li>\n<li>Click browse and go to RimWorld/RimWorldLinux_Data/Managed.\nSelect Assembly-CSharp.dll and UnityEngine.dll</li>\n<li>Click open and then exit the edit references dialog.</li>\n<li>For each reference, right click and decheck “local copy”.</li>\n</ul>\n</li>\n<li>Set .NET framework to .NET 3.5\n<ul>\n<li>Right click project (ModName &gt; Modname) in the left panel and select options</li>\n<li>Go to Build &gt; General</li>\n<li>Select Mono / .NET 3.5 in the target framework</li>\n</ul>\n</li>\n<li>Set output directory\n<ul>\n<li>Go to Build &gt; Output</li>\n<li>Set the output folder to RimWorld/mods/ModName/Assemblies.</li>\n</ul>\n</li>\n<li>Profit!</li>\n</ul>\n<p>Some of this information was learned from\n<a href=\"https://rimworldwiki.com/wiki/Modding_Tutorials/Setting_up_a_solution\">this wiki page</a>.</p>\n","url":"https://blog.rubenwardy.com/2016/07/21/rimworld-setup-monodevelop/","summary":"This tutorial will show you how to create a new MonoDevelop solution and project, and how to properly connect it to RimWorld.","date_published":"2016-07-21T00:00:00.000Z","tags":["linux","tutorials"]},{"id":"/2016/07/20/rimworld-install-monodevelop-with-dot-net-3.5/","language":"en","title":"Install Monodevelop to build for .NET 3.5 (using Mono 3.14) - RimWorld","content_html":"<p>This tutorial will show you how to install Mono and Monodevelop in order to\ndevelop .NET 3.5 projects. This is useful when writing C# assembly mods\nfor Unity engine based games, such as RimWorld, as they tend to require\n.NET 3.5.</p>\n<p>If you don’t use .NET 3.5, you may get errors including but not limited to:</p>\n<p>System.TypeLoadException: Could not load type ‘System.Action’ from assembly ‘ModName’.</p>\n<p>That error is commonly thrown when versions such as .NET or .DLLs mismatch.</p>\n<p>Mono 4.0 and later <a href=\"https://www.mono-project.com/docs/about-mono/releases/4.0.0/#dropped-support-for-old-frameworks\">don’t support .NET 3.5</a>.\nTherefore, we’ll need to install an older version.</p>\n<p>I couldn’t find many posts about this, doesn’t seem that many people develop RimWorld on Linux.</p>\n<p>After this tutorial, MonoDevelop will support the following target frameworks:\nMono / .NET 2.0, Mono / .NET 3.0, .NET 3.5 Client Profile, Mono / .NET 3.5,\n.NET 4.0 Client Profile, Mono / .NET 4.0, Mono / .NET 4.5, .NET Framework 4.5.1</p>\n<h2 id=\"uninstall-any-installed-mono-versions\" tabindex=\"-1\">Uninstall any installed Mono versions <a class=\"header-anchor\" href=\"#uninstall-any-installed-mono-versions\">#</a></h2>\n<p>Unfortunately I do not know of a way to keep multiple versions of Mono and\nMonodevelop installed at a time, so you’ll need to uninstall them:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">sudo</span> <span class=\"token function\">apt-get</span> remove mono-complete mono-devel monodevelop\n<span class=\"token function\">sudo</span> <span class=\"token function\">apt</span> autoremove</code></pre>\n<p>Now check that mono is not installed:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\">mono <span class=\"token parameter variable\">--version</span>\n<span class=\"token comment\"># you should get something like command not found</span></code></pre>\n<h2 id=\"add-ppa-for-mono-3-12-x\" tabindex=\"-1\">Add PPA for Mono 3.12.x <a class=\"header-anchor\" href=\"#add-ppa-for-mono-3-12-x\">#</a></h2>\n<p>We need to add a snapshot PPA, as described\n<a href=\"https://www.mono-project.com/docs/getting-started/install/linux/#accessing-older-releases\">in the mono-project docs</a>.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">sudo</span> apt-key adv <span class=\"token parameter variable\">--keyserver</span> hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"deb https://download.mono-project.com/repo/debian wheezy/snapshots/3.12.0 main\"</span> <span class=\"token operator\">|</span> <span class=\"token function\">sudo</span> <span class=\"token function\">tee</span> /etc/apt/sources.list.d/mono-xamarin.list\n<span class=\"token function\">sudo</span> <span class=\"token function\">apt-get</span> update</code></pre>\n<h2 id=\"install-mono-and-monodevelop\" tabindex=\"-1\">Install Mono and Monodevelop <a class=\"header-anchor\" href=\"#install-mono-and-monodevelop\">#</a></h2>\n<p>Now we need to install Mono 3.14 and Monodevelop 5.7. We also need to lock the\nversions of the installed packages, to stop apt-get upgrade from installing Mono 4.0+\nand reversing our efforts. We use apt-mark hold to do this.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">sudo</span> <span class=\"token function\">apt-get</span> <span class=\"token function\">install</span> build-essential cli-common libgtk2.0-cil-dev libglade2.0-cil-dev libgnome2.0-cil-dev libgconf2.0-cil-dev\n<span class=\"token function\">sudo</span> <span class=\"token function\">apt-get</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">-t</span> wheezy/snapshots/3.12.0 mono-complete\n<span class=\"token function\">sudo</span> apt-mark hold mono-complete\n<span class=\"token function\">sudo</span> apt-mark hold mono-devel\n<span class=\"token function\">sudo</span> apt-mark hold libmono*\n<span class=\"token function\">sudo</span> <span class=\"token function\">apt-get</span> <span class=\"token function\">install</span> <span class=\"token parameter variable\">-t</span> wheezy/snapshots/3.12.0 monodevelop\n<span class=\"token function\">sudo</span> apt-mark hold monodevelop</code></pre>\n<p>For more information, see <a href=\"https://superuser.com/questions/124174/how-can-i-specify-the-repository-from-which-a-package-will-be-installed-emacs\">this thread about installing from a specific repository</a>\nand <a href=\"https://askubuntu.com/questions/18654/how-to-prevent-updating-of-a-specific-package\">this thread about locking package versions</a>.</p>\n<h2 id=\"profit\" tabindex=\"-1\">Profit! <a class=\"header-anchor\" href=\"#profit\">#</a></h2>\n<pre class=\"language-bash\"><code class=\"language-bash\">mono <span class=\"token parameter variable\">--version</span></code></pre>\n<p>Should give something like:</p>\n<p>Mono JIT compiler version 3.12.1 (tarball Fri Mar  6 19:12:47 UTC 2015)\nCopyright © 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com</p>\n<p>And monodevelop should be in the start menu.\nNote that you may need to restart your computer, depending on your window manager.</p>\n<p>Next: <a href=\"/2016/07/21/rimworld-setup-monodevelop/\">Setting up a MonoDevelop solution and project for RimWorld modding</a>.</p>\n<h2 id=\"appendix\" tabindex=\"-1\">Appendix <a class=\"header-anchor\" href=\"#appendix\">#</a></h2>\n<p>Here is an error you may get when implementing a JobDriver using .NET 4.0+</p>\n<p>Exception in SetupToil(pawn=Kimmy, job=Accident): System.TypeLoadException: Could not load type ‘System.Action’ from assembly ‘toilets’.\nat Verse.AI.JobDriver.SetupToils()\nVerse.Log:Error(string)\nVerse.AI.JobDriver:SetupToil()\nVerse.AI.Pawn_JobTracker:StatJob(Job, JobCondition, ThinkNode, Boolean, Boolean, ThinkTreeDef)\nVerse.AI.Pawn_JobTracker:TryFindAndStartJob()\nVerse.AI.Pawn_JobTracter:EndCurrentJob(JobCondition)</p>\n","url":"https://blog.rubenwardy.com/2016/07/20/rimworld-install-monodevelop-with-dot-net-3.5/","summary":"This tutorial will show you how to install Mono and Monodevelop in order to develop .NET 3.5 projects.","date_published":"2016-07-20T00:00:00.000Z","tags":["linux","tutorials"]},{"id":"/2016/06/02/move-private-repos-from-bitbucket-to-github/","language":"en","title":"Move private repos from Bitbucket to GitHub","content_html":"<p>Now that Github supports unlimited private repos in all plans, you might as well\nkeep things all together (although definitely have backups elsewhere incase\nGithub is DDOS’d again, dies or goes evil). Simply change “rubenwardy” to your\nusername and “XXXX” to a\n<a href=\"https://github.com/settings/tokens\">personal access token</a> with “repo” checked.\nAlso make sure you have an <a href=\"https://github.com/settings/keys\">SSH key</a> for Github.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Clone from bitbucket</span>\n<span class=\"token function\">git</span> clone git@github.com:samkuehn/bitbucket-backup.git\n<span class=\"token function\">mkdir</span> bk\n<span class=\"token builtin class-name\">cd</span> bitbucket-backup\n./bitbucket-backup <span class=\"token parameter variable\">-u</span> rubenwardy <span class=\"token parameter variable\">--mirror</span> <span class=\"token parameter variable\">-l</span> <span class=\"token punctuation\">..</span>/bk\n\n<span class=\"token comment\"># Upload to GitHub</span>\n<span class=\"token builtin class-name\">cd</span> <span class=\"token punctuation\">..</span>/bk\n<span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">D</span> <span class=\"token keyword\">in</span> *<span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">do</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token variable\">$D</span>\n    <span class=\"token builtin class-name\">cd</span> <span class=\"token variable\">$D</span>\n    <span class=\"token assign-left variable\">data</span><span class=\"token operator\">=</span><span class=\"token string\">\"{<span class=\"token entity\" title=\"\\&quot;\">\\\"</span>name<span class=\"token entity\" title=\"\\&quot;\">\\\"</span>: <span class=\"token entity\" title=\"\\&quot;\">\\\"</span><span class=\"token variable\">$D</span><span class=\"token entity\" title=\"\\&quot;\">\\\"</span>, <span class=\"token entity\" title=\"\\&quot;\">\\\"</span>auto_init<span class=\"token entity\" title=\"\\&quot;\">\\\"</span>: false, <span class=\"token entity\" title=\"\\&quot;\">\\\"</span>private<span class=\"token entity\" title=\"\\&quot;\">\\\"</span>: true }\"</span>\n    <span class=\"token function\">curl</span> <span class=\"token parameter variable\">-i</span> <span class=\"token parameter variable\">-H</span> <span class=\"token string\">'Authorization: token XXXX'</span> <span class=\"token parameter variable\">-d</span> <span class=\"token string\">\"<span class=\"token variable\">$data</span>\"</span> https://api.github.com/user/repos\n    <span class=\"token function\">git</span> push <span class=\"token parameter variable\">--mirror</span> git@github.com:rubenwardy/<span class=\"token variable\">$D</span>.git\n    <span class=\"token builtin class-name\">cd</span> <span class=\"token punctuation\">..</span>/\n<span class=\"token keyword\">done</span>\n<span class=\"token builtin class-name\">cd</span> <span class=\"token punctuation\">..</span>/</code></pre>\n","url":"https://blog.rubenwardy.com/2016/06/02/move-private-repos-from-bitbucket-to-github/","summary":"Now that Github supports unlimited private repos in all plans, you might as well keep things all together","date_published":"2016-06-02T00:00:00.000Z","tags":["snippet","tutorials"]},{"id":"/2016/05/24/xubuntu-xfce-sync-whisker-menu-favourites/","language":"en","title":"Xubuntu / Xfce sync whisker menu favourites","content_html":"<p>I use a dual monitor setup. For each monitor I have a panel, and each panel has\na whisker menu. I found that the favourites section of each of these panels\nis not synchronised. To fix this, I wrote a simple script.</p>\n<p>First, you have to decide which whisker config to keep.</p>\n<figure class=\"highlight\"><pre><code class=\"language-bash\" data-lang=\"c\"><span class=\"n\">$ cd ~/.config/xfce4/panel/</span>\n<span class=\"n\">$ ls</span>\n<span class=\"c1\">whiskermenu-1.rc  whiskermenu-9.rc</span></code></pre></figure>\n<p>The favourites menu is stored at the top of each file, like this:</p>\n<figure class=\"highlight\"><pre><code class=\"language-bash\" data-lang=\"c\"><span class=\"n\">$ head -n1 whiskermenu-1.rc</span>\n<span class=\"c1\">favorites=firefox.desktop,google-chrome.desktop,hexchat.desktop,exo-file-manager.desktop,libreoffice-writer.desktop,libreoffice-calc.desktop,exo-terminal-emulator.desktop</span></code></pre></figure>\n<p>Find the one you want to keep. From this point on,\nreplace <code>whiskermenu-1.rc</code> with the config you want to keep and\n<code>whiskermenu-9.rc</code> with the one you want to ditch.</p>\n<p>The script is as simple as this:</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token shebang important\">#! /bin/bash</span>\n\n<span class=\"token function\">rm</span> <span class=\"token parameter variable\">-f</span> ~/.config/xfce4/panel/whiskermenu-9.rc\n<span class=\"token function\">cp</span> ~/.config/xfce4/panel/whiskermenu-1.rc ~/.config/xfce4/panel/whiskermenu-9.rc\nxfce4-panel <span class=\"token parameter variable\">-r</span></code></pre>\n<p>If you save that to /usr/local/bin/syncwhisker, then you can use “syncwhisker”\nin the terminal to keep your menus the same.</p>\n","url":"https://blog.rubenwardy.com/2016/05/24/xubuntu-xfce-sync-whisker-menu-favourites/","summary":"When using multiple Xfce panels, it's annoying that the favourites aren't synchronised. Here's a script to fix that","date_published":"2016-05-24T00:00:00.000Z","tags":["snippet","linux","tutorials"]},{"id":"/2016/05/21/dual-boot-ubuntu-on-asus-x555la-uefi-laptop/","language":"en","title":"Dual boot Ubuntu on ASUS X555LA UEFI laptop alongside Windows 10","content_html":"<p>I recently successfully dual booted Ubuntu on an ASUS X555LA laptop,\nthe X555LAB variety. This guide will work on most Ubuntu derivatives,\nI installed Xubuntu using these steps. The only things that are different\nbetween them are window managers and preinstalled software.</p>\n<p>Specs:</p>\n<blockquote>\n<p>Intel® Core™ i5-5200U CPU @ 2.20GHz, max: 2.70GHz<br>\nIntel® HD Graphics 5500<br>\n8GB RAM, 1TB HDD<br>\n1920x1080 screen, 2xUSB3.0, 1xUSB2.0</p>\n</blockquote>\n<p>Here’s a step by step guide to what I needed to do.</p>\n<h2 id=\"download-ubuntu\" tabindex=\"-1\">Download Ubuntu <a class=\"header-anchor\" href=\"#download-ubuntu\">#</a></h2>\n<ol>\n<li>You <strong>must</strong> use a 64bit version.</li>\n<li>You <strong>should</strong> use 16.04 or later\n(earlier versions may work but may require different instructions).</li>\n</ol>\n<h2 id=\"burn-to-a-dvd-r\" tabindex=\"-1\">Burn to a DVD-R <a class=\"header-anchor\" href=\"#burn-to-a-dvd-r\">#</a></h2>\n<ol>\n<li>Double click the .iso file.</li>\n<li>Windows Disc Burner should open, if using Windows.</li>\n<li>Burn to a disc, I suggest enabling the “verify disc after burn” option.</li>\n</ol>\n<h2 id=\"disable-fast-startup-in-windows-10\" tabindex=\"-1\">Disable fast startup in Windows 10 <a class=\"header-anchor\" href=\"#disable-fast-startup-in-windows-10\">#</a></h2>\n<ol>\n<li>Search for “power options” using search.</li>\n<li>Click “change what the buttons do”.</li>\n<li>Click something like “show hidden/disabled settings”. It will be a\nlink or button at the top with a UAC badge next to it.</li>\n<li>Uncheck the fast startup checkbox near the bottom of the page.</li>\n<li>Apply changes.</li>\n</ol>\n<h2 id=\"disable-fast-boot-in-uefi-settings\" tabindex=\"-1\">Disable fast boot in UEFI settings <a class=\"header-anchor\" href=\"#disable-fast-boot-in-uefi-settings\">#</a></h2>\n<ol>\n<li>Click Windows flag, click power options, click “restart” whilst holding shift.</li>\n<li>Wait for computer to go into an options menu.</li>\n<li>Click trouble shooting.</li>\n<li>Click advanced.</li>\n<li>Click change UEFI settings.</li>\n<li>Go to boot tab.</li>\n<li>Disable “fast boot”.</li>\n<li>You do not need to disable secure boot.</li>\n</ol>\n<h2 id=\"check-boot-order\" tabindex=\"-1\">Check boot order <a class=\"header-anchor\" href=\"#check-boot-order\">#</a></h2>\n<p>The only option should be: “Windows Boot Manager”.</p>\n<h2 id=\"install-ubuntu\" tabindex=\"-1\">Install Ubuntu <a class=\"header-anchor\" href=\"#install-ubuntu\">#</a></h2>\n<ol>\n<li>Insert disc and restart computer</li>\n<li>When booting, a blue menu should come up. It’ll have a Windows Boot Manager option,\na disc option, and an UEFI settings option. Select the disc option.\nIf this menu does not appear, restart and press esc during the “ASUS” logo.</li>\n<li>A GRUB menu screen will now appear. <strong>If it does not appear, then the disc hasn’t\nbeen loaded in UEFI mode.</strong></li>\n<li>Select “Install Ubuntu”.</li>\n<li>Install as normal. You’ll need to disable “install sources from third party providers”\nas that will disable secure boot and break windows - don’t worry,\nyou can install many of these things later.</li>\n<li>On the partitions manager, Files (sda5) is the Windows partition,\nand Ubuntu (sda6) is the Ubuntu partition.\nNote that they won’t add up to a full 1TB, as the laptop comes with two\npartitions already.</li>\n</ol>\n<h2 id=\"problems\" tabindex=\"-1\">Problems <a class=\"header-anchor\" href=\"#problems\">#</a></h2>\n<p>Here are some problems.</p>\n<ul>\n<li>The touchpad was super sensitive and I often clicked whilst typing, deleting text.\n<ul>\n<li>Solution: add touchpad delay (Settings -&gt; Mouse -&gt; Touchpad)</li>\n</ul>\n</li>\n<li>Occasional WiFi problems\n<ul>\n<li><code>sudo service network-manager restart</code></li>\n</ul>\n</li>\n<li>Fixed as of 16.10 <s>Locking causes the mouse to disappear when unlocking. Work arounds:</s>\n<ul>\n<li><s>ctrl+alt+f1 then ctrl+alt+f7</s></li>\n<li><s>Move mouse between monitor boundaries if dual booting</s></li>\n</ul>\n</li>\n<li>Fixed as of 16.10 <s>Occasionally when web browsing, tears appear making some text unreadable.</s>\n<ul>\n<li><s>A work around is to press ctrl+a, which causes the page to re-render.</s></li>\n</ul>\n</li>\n</ul>\n<p>These problems are made up for that fact that 1. you now have a real terminal\nand 2. double tapping doesn’t break like on Windows (damn ASUS Smart Touch).</p>\n<h2 id=\"see-also\" tabindex=\"-1\">See also <a class=\"header-anchor\" href=\"#see-also\">#</a></h2>\n<p>Thanks for reading this. For more info, see:\n<a href=\"https://help.ubuntu.com/community/UEFI\">ubuntu.com/community/UEFI/</a>.</p>\n","url":"https://blog.rubenwardy.com/2016/05/21/dual-boot-ubuntu-on-asus-x555la-uefi-laptop/","summary":"I recently successfully dual booted Ubuntu on an ASUS X555LA laptop, the X555LAB variety.","date_published":"2016-05-21T00:00:00.000Z","tags":["linux","tutorials"]},{"id":"/2016/05/07/2100-link-external-phpbb-forum/","language":"en","title":"Linking a user account to an external phpBB forum","content_html":"<p>This article will show you how to verify a user’s identity by letting them\nassociate their account with an external third party phpBB account.\nI used Python and Flask to achieve this, however any language and framework\nshould work, and shouldn’t be too hard to port to.</p>\n<p>I wrote a <a href=\"/2016/05/07/1600-python-phpbb-parser/\">Python library to parse phpBB profiles</a>\nwhich I will be using throughout this tutorial.</p>\n<p>First, you’ll need to install some packages:</p>\n<p>pip install beautifulsoup4 phpbb-parser</p>\n<p>I’m using Flask, Flask-User and Flask-SQLAlchemy.</p>\n<p>pip install flask flask-user Flask-SQLAlchemy</p>\n<p>You need to add a <code>forum</code> field to the user model:</p>\n<pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">class</span> <span class=\"token class-name\">User</span><span class=\"token punctuation\">(</span>db<span class=\"token punctuation\">.</span>Model<span class=\"token punctuation\">,</span> UserMixin<span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token comment\"># ...</span>\n\n    <span class=\"token comment\"># Forum</span>\n    forum <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span>Column<span class=\"token punctuation\">(</span>db<span class=\"token punctuation\">.</span>String<span class=\"token punctuation\">(</span><span class=\"token number\">100</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> nullable<span class=\"token operator\">=</span><span class=\"token boolean\">True</span><span class=\"token punctuation\">)</span></code></pre>\n<h2 id=\"basic-idea-forum-signature-linking\" tabindex=\"-1\">Basic Idea: Forum signature linking <a class=\"header-anchor\" href=\"#basic-idea-forum-signature-linking\">#</a></h2>\n<ol>\n<li>Server generates random token.</li>\n<li>User adds token to signature.</li>\n<li>User gives forum username.</li>\n<li>Server looks up signature from forum username.</li>\n<li>If match, link accounts</li>\n</ol>\n<h2 id=\"template-link-forum-html\" tabindex=\"-1\">Template: link_forum.html <a class=\"header-anchor\" href=\"#template-link-forum-html\">#</a></h2>\n<p>This is a page that gives the user the option to link to a forum.</p>\n<pre class=\"language-html\"><code class=\"language-html\">\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>box box_grey<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h2</span><span class=\"token punctuation\">></span></span>Link forum account<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h2</span><span class=\"token punctuation\">></span></span>\n\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>form</span> <span class=\"token attr-name\">method</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>post<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">action</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{{ url_for('link_forum_page') }}<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n            Enter your forum username here:\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>forum_username<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{{ forum_username }}<span class=\"token punctuation\">\"</span></span>\n            <span class=\"token attr-name\">required</span> <span class=\"token attr-name\">placeholder</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Forum username<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n            Go to\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>a</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{{ forum_url }}/ucp.php?i=profile&amp;mode=signature<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n                User Control Panel > Profile > Edit signature\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>a</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n            Paste this into your signature:\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>{{ key }}<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">readonly</span> <span class=\"token attr-name\">size</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span>32</span><span class=\"token punctuation\">></span></span>\n\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n            Click next so we can check it.\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>\n            Don't worry, you can remove it after this is done.\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>input</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>submit<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">value</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>Next<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>form</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n</code></pre>\n<h2 id=\"token-cache\" tabindex=\"-1\">Token cache <a class=\"header-anchor\" href=\"#token-cache\">#</a></h2>\n<p>I used a memcache in order to temporarily associate an IP with a random token</p>\n<pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">from</span> werkzeug<span class=\"token punctuation\">.</span>contrib<span class=\"token punctuation\">.</span>cache <span class=\"token keyword\">import</span> SimpleCache\ncache <span class=\"token operator\">=</span> SimpleCache<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre>\n<h2 id=\"view-link-forum-page\" tabindex=\"-1\">View: link_forum_page <a class=\"header-anchor\" href=\"#view-link-forum-page\">#</a></h2>\n<pre class=\"language-python\"><code class=\"language-python\"><span class=\"token comment\"># Project components</span>\n<span class=\"token keyword\">from</span> app <span class=\"token keyword\">import</span> app\n<span class=\"token keyword\">from</span> models <span class=\"token keyword\">import</span> <span class=\"token operator\">*</span>\n\n<span class=\"token comment\"># Dependencies</span>\n<span class=\"token keyword\">from</span> flask <span class=\"token keyword\">import</span> <span class=\"token operator\">*</span>\n<span class=\"token keyword\">from</span> flask_user <span class=\"token keyword\">import</span> <span class=\"token operator\">*</span>\n<span class=\"token keyword\">import</span> phpbb_parser <span class=\"token keyword\">as</span> parser\n\n<span class=\"token comment\"># GET link_forum_page</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">link_forum_get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token comment\"># Used to automatically fill out the forms in the template with a forum username</span>\n    forum_username <span class=\"token operator\">=</span> request<span class=\"token punctuation\">.</span>args<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">or</span> <span class=\"token string\">\"\"</span>\n\n    <span class=\"token comment\"># Create random token</span>\n    <span class=\"token keyword\">import</span> uuid\n    key <span class=\"token operator\">=</span> uuid<span class=\"token punctuation\">.</span>uuid4<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token builtin\">hex</span>\n    cache<span class=\"token punctuation\">.</span><span class=\"token builtin\">set</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"forum_claim_key_\"</span> <span class=\"token operator\">+</span> request<span class=\"token punctuation\">.</span>remote_addr<span class=\"token punctuation\">,</span> key<span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token operator\">*</span><span class=\"token number\">60</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token keyword\">return</span> render_template<span class=\"token punctuation\">(</span><span class=\"token string\">'link_forum.html'</span><span class=\"token punctuation\">,</span> key<span class=\"token operator\">=</span>key<span class=\"token punctuation\">,</span>\n        forum_username<span class=\"token operator\">=</span>forum_username<span class=\"token punctuation\">,</span> forum_url<span class=\"token operator\">=</span><span class=\"token string\">\"https://example.com\"</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\"># POST link_forum_page</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">link_forum_post</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    forum_username <span class=\"token operator\">=</span> request<span class=\"token punctuation\">.</span>form<span class=\"token punctuation\">[</span><span class=\"token string\">\"forum_username\"</span><span class=\"token punctuation\">]</span>\n\n    <span class=\"token comment\"># Get profile</span>\n    profile <span class=\"token operator\">=</span> parser<span class=\"token punctuation\">.</span>get_profile<span class=\"token punctuation\">(</span><span class=\"token string\">\"https://example.com\"</span><span class=\"token punctuation\">,</span> forum_username<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> profile<span class=\"token punctuation\">:</span>\n        flash<span class=\"token punctuation\">(</span><span class=\"token string\">\"Unable to find your forum user.\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> redirect<span class=\"token punctuation\">(</span>url_for<span class=\"token punctuation\">(</span><span class=\"token string\">\"link_forum_page\"</span><span class=\"token punctuation\">,</span> user<span class=\"token operator\">=</span>forum_username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\"># Get stored key</span>\n    stored_key <span class=\"token operator\">=</span> cache<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"forum_claim_key_\"</span> <span class=\"token operator\">+</span> request<span class=\"token punctuation\">.</span>remote_addr<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> stored_key<span class=\"token punctuation\">:</span>\n        flash<span class=\"token punctuation\">(</span><span class=\"token string\">\"Failed to get key\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> redirect<span class=\"token punctuation\">(</span>url_for<span class=\"token punctuation\">(</span><span class=\"token string\">\"link_forum_page\"</span><span class=\"token punctuation\">,</span> user<span class=\"token operator\">=</span>forum_username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\"># Get token in signature</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> stored_key <span class=\"token keyword\">in</span> profile<span class=\"token punctuation\">.</span>signature<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">:</span>\n        flash<span class=\"token punctuation\">(</span><span class=\"token string\">\"Could not find the key in your signature!\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"error\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> redirect<span class=\"token punctuation\">(</span>url_for<span class=\"token punctuation\">(</span><span class=\"token string\">\"link_forum_page\"</span><span class=\"token punctuation\">,</span> user<span class=\"token operator\">=</span>forum_username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\"># Get user from database</span>\n    user <span class=\"token operator\">=</span> User<span class=\"token punctuation\">.</span>query<span class=\"token punctuation\">.</span>filter_by<span class=\"token punctuation\">(</span>username<span class=\"token operator\">=</span>current_user<span class=\"token punctuation\">.</span>username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>first<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">if</span> <span class=\"token keyword\">not</span> user<span class=\"token punctuation\">:</span>\n        flash<span class=\"token punctuation\">(</span><span class=\"token string\">\"Could not find a user of that name!\"</span><span class=\"token punctuation\">)</span>\n        <span class=\"token keyword\">return</span> redirect<span class=\"token punctuation\">(</span>url_for<span class=\"token punctuation\">(</span><span class=\"token string\">\"link_forum_page\"</span><span class=\"token punctuation\">,</span> user<span class=\"token operator\">=</span>forum_username<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    user<span class=\"token punctuation\">.</span>forum <span class=\"token operator\">=</span> forum_username\n    db<span class=\"token punctuation\">.</span>session<span class=\"token punctuation\">.</span>commit<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token comment\"># SUCCESS! Redirect to index page</span>\n    <span class=\"token keyword\">return</span> redirect<span class=\"token punctuation\">(</span>url_for<span class=\"token punctuation\">(</span><span class=\"token string\">\"index_page\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token comment\"># Register link_forum_page</span>\n<span class=\"token decorator annotation punctuation\">@app<span class=\"token punctuation\">.</span>route</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/user/claim/\"</span><span class=\"token punctuation\">,</span> methods<span class=\"token operator\">=</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"POST\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"GET\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n<span class=\"token decorator annotation punctuation\">@login_required</span>\n<span class=\"token keyword\">def</span> <span class=\"token function\">link_forum_page</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">if</span> request<span class=\"token punctuation\">.</span>method <span class=\"token operator\">==</span> <span class=\"token string\">\"GET\"</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">return</span> link_forum_get<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">else</span><span class=\"token punctuation\">:</span>\n        <span class=\"token keyword\">return</span> link_forum_post<span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre>\n<p>Function split up to make it easier to read.</p>\n<h2 id=\"signing-in-with-a-forum-account\" tabindex=\"-1\">Signing in with a forum account <a class=\"header-anchor\" href=\"#signing-in-with-a-forum-account\">#</a></h2>\n<p>The above is only good for linking, and not for actually signing in.\nTo allow signing in, you could read public profile data off of the forum\nprofile and then use OAuth to sign in with those.</p>\n<p>For example, if the user has their Github listed on their forum profile,\nyou can sign them in using that Github account.</p>\n<ol>\n<li>User gives forum username</li>\n<li>Server looks up Github field in forum username</li>\n<li>User signs in using Github to confirm identity\n<ol>\n<li>Server redirects user to github authentication.</li>\n<li>Github redirects user to github callback with access token.</li>\n<li>Server looks up Github username using access token.</li>\n<li>Access token added to user model and accounts linked.</li>\n<li>If not logged in, server logs user into the account associated with\nthat Github username.</li>\n</ol>\n</li>\n</ol>\n","url":"https://blog.rubenwardy.com/2016/05/07/2100-link-external-phpbb-forum/","summary":"This article will show you how to verify a user's identity by letting them associate their account with an external third party phpBB account.","date_published":"2016-05-07T00:00:00.000Z","tags":["snippet","projects","tutorials","webdev"]},{"id":"/2016/05/07/1600-python-phpbb-parser/","language":"en","title":"Getting user profile data from external phpBB forums","content_html":"<p>I recently wrote and released a python module to allow fetching of profile data.</p>\n<p>You can install it using pip:</p>\n<p>pip install beautifulsoup4 phpbb-parser</p>\n<p>Here’s how you import and use a profile:</p>\n<pre class=\"language-python\"><code class=\"language-python\"><span class=\"token keyword\">import</span> phpbb_parser <span class=\"token keyword\">as</span> parser\n\nusername <span class=\"token operator\">=</span> <span class=\"token string\">\"rubenwardy\"</span>\nprofile <span class=\"token operator\">=</span> parser<span class=\"token punctuation\">.</span>get_profile<span class=\"token punctuation\">(</span><span class=\"token string\">\"https://forum.minetest.net\"</span><span class=\"token punctuation\">,</span> username<span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">if</span> profile<span class=\"token punctuation\">:</span>\n    signature <span class=\"token operator\">=</span> profile<span class=\"token punctuation\">.</span>signature<span class=\"token punctuation\">.</span>text\n    location <span class=\"token operator\">=</span> profile<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"location\"</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">or</span> <span class=\"token string\">\"unknown\"</span>\n    github <span class=\"token operator\">=</span> profile<span class=\"token punctuation\">.</span>get<span class=\"token punctuation\">(</span><span class=\"token string\">\"github\"</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">or</span> <span class=\"token string\">\"none\"</span>\n\n    <span class=\"token keyword\">print</span><span class=\"token punctuation\">(</span>username <span class=\"token operator\">+</span> <span class=\"token string\">\" from \"</span> <span class=\"token operator\">+</span> location <span class=\"token operator\">+</span> <span class=\"token string\">\" has github \"</span> <span class=\"token operator\">+</span> github<span class=\"token punctuation\">)</span>\n    <span class=\"token keyword\">print</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Signatue: \"</span> <span class=\"token operator\">+</span> signature<span class=\"token punctuation\">.</span>text<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">else</span><span class=\"token punctuation\">:</span>\n    <span class=\"token keyword\">print</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Could not get profile!\"</span><span class=\"token punctuation\">)</span></code></pre>\n<p>profile.signature is a beautifulsoup4 object.</p>\n<p>Next: <a href=\"/2016/05/07/2100-link-external-phpbb-forum/\">Linking a user account to an external phpBB forum</a>.</p>\n","url":"https://blog.rubenwardy.com/2016/05/07/1600-python-phpbb-parser/","summary":"I recently wrote and released a python module to allow fetching of profile data.","date_published":"2016-05-07T00:00:00.000Z","tags":["snippet","projects","tutorials","webdev"]},{"id":"/2016/02/24/university/","language":"en","title":"1st Semester of University","content_html":"<p>To get more than 50% on our coursework, you had to submit extensions.\nHere are some of my favourite ones.</p>\n<h2 id=\"pong\" tabindex=\"-1\">Pong <a class=\"header-anchor\" href=\"#pong\">#</a></h2>\n<figure class=\"undefined\"><a href=\"/static/img/QtCuGu5nxO-640.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/gX3zEW41Cd-540.webp 540w\"><img src=\"/static/img/gX3zEW41Cd-540.png\" alt=\"Screenshot of Pong\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Screenshot of Pong</figcaption></figure>\n<p>I created a Pong clone using C and SDL.</p>\n<h2 id=\"chess-ai\" tabindex=\"-1\">Chess AI <a class=\"header-anchor\" href=\"#chess-ai\">#</a></h2>\n<figure class=\"undefined\"><a href=\"/static/img/bQtUGgNTu6-922.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/9oDEZXA2vV-540.webp 540w\"><img src=\"/static/img/9oDEZXA2vV-540.png\" alt=\"Screenshot of Chess\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"245\"></picture></a><figcaption>Screenshot of Chess</figcaption></figure>\n<p>I created a Chess game and Chess minimax implementation in C.</p>\n<h2 id=\"infinite-and-generic-game-of-life-and-more\" tabindex=\"-1\">Infinite and Generic Game of Life, and more <a class=\"header-anchor\" href=\"#infinite-and-generic-game-of-life-and-more\">#</a></h2>\n<figure class=\"undefined\"><a href=\"/static/img/3Pygyqy5r1-640.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/H4CxJQA2bV-540.webp 540w\"><img src=\"/static/img/H4CxJQA2bV-540.png\" alt=\"Screenshot of the Game of Life\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"405\"></picture></a><figcaption>Screenshot of the Game of Life</figcaption></figure>\n<p>Features:</p>\n<ul>\n<li>Chunk based infinite universe. Automatically expands.</li>\n<li>Interactive player and universe editor using SDL.</li>\n<li>Can set the Born/Survive ruleset code (from command line).</li>\n<li>Can choose the number to steps to take before opening the player\nor outputting to stdout (from command line).</li>\n<li>Has tests.</li>\n</ul>\n","url":"https://blog.rubenwardy.com/2016/02/24/university/","summary":"To get more than 50% on our coursework, you had to submit extensions. Here are some of my favourite ones.","date_published":"2016-02-24T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/3Pygyqy5r1-540.png","tags":["computer_science","cpp","projects"]},{"id":"/2015/12/10/etms/","language":"en","title":"Employee Task Management System","content_html":"<p>I was contacted by a client to create a system which calculates the workload\nfor employees based on their assignment to tasks and appointments.</p>\n<p>The system needs to solve two problems:\nFirstly, different staff members work different numbers of hours, which\nmakes it hard to allocate tasks fairly and proportionally.\nSecondly, the client wanted to use the system to analyse past workloads\nand to anticipate future workload, in order to improve her system of work.</p>\n<h2 id=\"implementation\" tabindex=\"-1\">Implementation <a class=\"header-anchor\" href=\"#implementation\">#</a></h2>\n<figure class=\"undefined\"><a href=\"/static/img/Z_c9MEb_v--793.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/SojqU7lw8u-540.webp 540w\"><img src=\"/static/img/SojqU7lw8u-540.png\" alt=\"Screenshot of ETMS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"247\"></picture></a><figcaption>Screenshot of ETMS</figcaption></figure>\n<p>The solution contains an algorithm to suggest new employees to tasks, based on\nthe type of tasks they are currently assigned to, how many hours they should\nwork a week, and how what they are doing at the time.</p>\n<p>The solution was written as a web application, using Python, Flask and SQLAlchemy.</p>\n<figure class=\"undefined\"><a href=\"/static/img/pDfv1W1JnM-1281.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/_GtUcTkAhv-540.webp 540w\"><img src=\"/static/img/_GtUcTkAhv-540.png\" alt=\"Server side rendered graphs in ETMS\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"248\"></picture></a><figcaption>Server side rendered graphs in ETMS</figcaption></figure>\n<p>Originally I created the graphs using a HTML5 canvas. However, during the\nfirst client feedback sesson, I found out they used Internet Explorer with\nJavaScript disabled. This meant that I needed to move all of the graph\nrendering to the server. I used the Python Imaging Library (PIL) to do this.</p>\n","url":"https://blog.rubenwardy.com/2015/12/10/etms/","summary":"I was contacted by a client to create a system which calculates the workload for employees based on their assignment to tasks and appointments.","date_published":"2015-12-10T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/pDfv1W1JnM-540.png","tags":["projects","webdev"]},{"id":"/2015/04/07/convert-folder-of-images-to-pdf-with-filenames/","language":"en","title":"Convert folder of images to PDF with filenames","content_html":"<p>Here is a shell script specific for GNU/Linux based operating systems to use.\nOn different operating systems the convert commands will be the same, but\nthe for loop will be different due to a different batch file syntax.</p>\n<pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token function\">rm</span> /tmp/imageex <span class=\"token parameter variable\">-r</span>\n<span class=\"token function\">mkdir</span> /tmp/imageex\n<span class=\"token keyword\">for</span> <span class=\"token for-or-select variable\">filename</span> <span class=\"token keyword\">in</span> *.png<span class=\"token punctuation\">;</span> <span class=\"token keyword\">do</span>\n    <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Processing <span class=\"token variable\">$filename</span>\"</span>\n    convert <span class=\"token variable\">$filename</span> <span class=\"token parameter variable\">-background</span> White <span class=\"token punctuation\">\\</span>\n        label:<span class=\"token variable\">$filename</span> <span class=\"token parameter variable\">-gravity</span> Center <span class=\"token punctuation\">\\</span>\n         <span class=\"token parameter variable\">-append</span> <span class=\"token parameter variable\">-pointsize</span> <span class=\"token number\">14</span> <span class=\"token punctuation\">\\</span>\n        /tmp/imageex/<span class=\"token variable\">$filename</span>.png\n<span class=\"token keyword\">done</span>\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Exporting to PDF...\"</span>\nconvert /tmp/imageex/*.png output.pdf\n<span class=\"token builtin class-name\">echo</span> <span class=\"token string\">\"Done.\"</span></code></pre>\n","url":"https://blog.rubenwardy.com/2015/04/07/convert-folder-of-images-to-pdf-with-filenames/","summary":"Here is a shell script specific for GNU/Linux based operating systems to use. On different operating systems the convert commands will be the same.","date_published":"2015-04-07T00:00:00.000Z","tags":["snippet","linux","tutorials"]},{"id":"/2015/01/08/3d-projection/","language":"en","title":"3D Projection","content_html":"<p>Hello 2015! Recently I have created an implementation of the\n<a href=\"https://en.wikipedia.org/wiki/3D_projection#Perspective_projection\" target=\"_blank\">3D projection</a> algorithm.\nIt is just wireframe models. It works pretty well, except it doesn’t do frustum culling.\nYou still see things that are behind you, but upside down.</p>\n<p>The source code of this implementation is available under the WTFPL or CC0 licenses -\nyou can choose which one you want to use.\nUse WASD to move, arrow keys to rotate, space to ascend and shift to descend.</p>\n","url":"https://blog.rubenwardy.com/2015/01/08/3d-projection/","summary":"Recently I have created an implementation of the 3d projection algorithm. It is just wireframe models. It works well, except it doesn't do frustum culling.","date_published":"2015-01-08T00:00:00.000Z","tags":["computer_science","projects","webdev","gamedev"]},{"id":"/2014/12/16/lotka-volterra/","language":"en","title":"Lotka Volterra - Predator Prey","content_html":"<p>Hi all! I’m back again for another post.\nThis time I am going to show off a project I have had for quite a while -\nit is a cellular automaton which simulates the Lotka Volterra equations.</p>\n<p><canvas id=\"foxrabbit\" style=\"background: green;\"></canvas></p>\n<script src=\"/static/files/foxrabbit.js\"></script>\n<p>Orange for foxes, brown for rabbits.</p>\n<p>The Lotka Volterra equations, also known as the predator prey equations,\nare used to model the populations of two species over time - a predator\nand a prey.</p>\n<figure class=\"undefined\"><a href=\"/static/img/HRnpJGAZSf-1512.png\"><picture><source type=\"image/webp\" srcset=\"/static/img/pFag-Z7ZtZ-540.webp 540w\"><img src=\"/static/img/pFag-Z7ZtZ-540.png\" alt=\"Graph of my result (left) next to the graph of the expected result (right).\" loading=\"lazy\" decoding=\"async\" width=\"540\" height=\"189\"></picture></a><figcaption>Graph of my result (left) next to the graph of the expected result (right).</figcaption></figure>\n","url":"https://blog.rubenwardy.com/2014/12/16/lotka-volterra/","summary":"This time I am going to show off a project I have had for quite a while - it is a cellular automaton which simulates the Lotka Volterra equations.","date_published":"2014-12-16T00:00:00.000Z","tags":["projects","webdev","gamedev"]},{"id":"/2014/11/07/lichess-widgets/","language":"en","title":"Lichess Widgets","content_html":"<p>Just a short post this time - I have created some widgets for the chess website\nLichess. I was quite surprised that these did not exist yet, in any form.\nLuckily Lichess exposes an API to use. You can have a look at them by clicking\nthe link below.</p>\n","url":"https://blog.rubenwardy.com/2014/11/07/lichess-widgets/","summary":"Just a short post this time - I have created some widgets for the chess website Lichess.","date_published":"2014-11-07T00:00:00.000Z","tags":["projects","webdev"]},{"id":"/2014/10/26/lichess/","language":"en","title":"Lichess","content_html":"<p>Lichess is an online chess game and community. It is free and open source,\nad-free and subscription-free. You can play against your friends, random\nstrangers and the computer. You can create teams to compete in tournaments.\nThere are tournaments going on all the time.</p>\n<p>There are different game modes available, but I don’t really touch these.\nChess960, King of the Hill, Three-check, From position.\nYou can request that your game is analysed by a chess engine, it gives you\nsuggestions on what you should have done.\nI like how it shows a graph of how much of an advantage one player has over\none another.</p>\n<p>Lichess is also good for becoming better at chess.\nChess puzzles are available in the training section,\nwhere you have to choose the best move in a given situation,\nand follow it through to checkmate.\nPlayers can create their own puzzles.</p>\n<p>It is truly better than the alternatives.\nChess.com is cluttered with ads and has an uglier design - minimalism is the way forward.\nLichess is free and open source, and always will be.</p>\n","url":"https://blog.rubenwardy.com/2014/10/26/lichess/","summary":"Lichess is an online chess game and community. It is free and open source, ad-free and subscription-free.","date_published":"2014-10-26T00:00:00.000Z","image":"https://blog.rubenwardy.com/static/img/ktL-TIXRBI-540.png","tags":["reviews"]},{"id":"/2014/10/22/bytecode-and-compilers/","language":"en","title":"Bytecode, Compilers and Interpreters","content_html":"<p>Recently I have been looking at languages and compilation: VMs, parse trees,\nlexers, and interpreters. <a href=\"https://www.nand2tetris.org\">Nand to tetris</a> is a\npretty awesome guide to how the CPU executes programs - from logic gates to high\nlevel languages.</p>\n<h2 id=\"assembler-and-bytecode-vm\" tabindex=\"-1\">Assembler and Bytecode VM <a class=\"header-anchor\" href=\"#assembler-and-bytecode-vm\">#</a></h2>\n<p>I created an assembler and Virtual Machine to run assembly style instructions.\nI haven’t released it as it isn’t as complete as I want it yet.</p>\n<h2 id=\"parse-trees-and-reverse-polish-notation-rpn\" tabindex=\"-1\">Parse Trees and Reverse Polish Notation (RPN) <a class=\"header-anchor\" href=\"#parse-trees-and-reverse-polish-notation-rpn\">#</a></h2>\n<p>Turns a string such as “( 0 - (6) + ( 6 ^ 2 - 4 * 1 * 5 ) ^ (1 / 2) ) / ( 2 * 1)”\ninto a binary syntax tree, and then into Reverse Polish Notation, and then executes it.</p>\n<h2 id=\"lexical-analyser\" tabindex=\"-1\">Lexical Analyser <a class=\"header-anchor\" href=\"#lexical-analyser\">#</a></h2>\n<p>I have also experimented with a simple Lexical Analysisor, however it’s not at a state\nthat I’d like to release.</p>\n<h2 id=\"rufunge\" tabindex=\"-1\">Rufunge <a class=\"header-anchor\" href=\"#rufunge\">#</a></h2>\n<p>Befunge is an <a href=\"https://en.wikipedia.org/wiki/Esoteric_programming_language\" target=\"_blank\">esoteric programming language</a> -\na language which isn’t meant for pratical use, but rather to test programming\nuse. The language consists of a two dimensional grid of characters. Each\ncharacter is an individual instruction. I was disappointed that the authors\ndidn’t take it seriously enough, and didn’t add input and output to it. So, I\ncreated my own! I called it ‘Rufunge’. It’s not finished yet, but can currently\nrun most Befunge programs.</p>\n<h2 id=\"mesecode\" tabindex=\"-1\">Mesecode <a class=\"header-anchor\" href=\"#mesecode\">#</a></h2>\n<p>Mesecode is a language which compiles to Lua. It’s purpose it to make\ndefinitions in Minetest easier to read.</p>\n<p>Instead of:</p>\n<pre class=\"language-lua\"><code class=\"language-lua\">minetest<span class=\"token punctuation\">.</span><span class=\"token function\">register_node</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"default:stone\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    description <span class=\"token operator\">=</span> <span class=\"token string\">\"Stone\"</span><span class=\"token punctuation\">,</span>\n    tiles <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"default_stone.png\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    is_ground_content <span class=\"token operator\">=</span> <span class=\"token keyword\">true</span><span class=\"token punctuation\">,</span>\n    groups <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>cracky<span class=\"token operator\">=</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> stone<span class=\"token operator\">=</span><span class=\"token number\">1</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    drop <span class=\"token operator\">=</span> <span class=\"token string\">'default:cobble'</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre>\n<p>You can write:</p>\n<pre><code>mod default\n\nnode Stone\n    is ground, cracky=3, stone\n    drops Cobble\n</code></pre>\n","url":"https://blog.rubenwardy.com/2014/10/22/bytecode-and-compilers/","summary":"Recently I have been looking at languages and compilation: VMs, parse trees, lexers, and interpreters","date_published":"2014-10-22T00:00:00.000Z","tags":["computer_science","projects","cpp","luanti"]}]}
