hltb android app post

This commit is contained in:
Wouter Groeneveld 2023-02-17 10:09:31 +01:00
parent 2235aec1c1
commit b3444bbdef
4 changed files with 70 additions and 17 deletions

View File

@ -0,0 +1,52 @@
---
title: How Long To Beat That Game Boy Cartridge?
date: 2023-02-17T09:26:00+01:00
categories:
- programming
tags:
- kotlin
- android
---
L[ast June](/post/2022/07/june-2022/), as part of a new course called Android App Development, our 2nd bachelor Engineering Technology students had to demonstrate their creative app development skills. As it was a new course---but a small one, at 3 ETCS points---I had to dig through Android and Kotlin docs to try to decide what to include and what to leave out. The end result is [yet another Hugo-generated online course](https://kuleuven-diepenbeek.github.io/appdev-course/) that is divided into two big parts:
1. Language & support tools---A quick Kotlin 101 with ample examples from the Java world; how to TDD in the mobile world; a quick peek at multi-platform mobile development.
1. Android essentials---the Android life cycle: activities; messaging between activities/apps: intents; typical layouts and their adapters: views; data storage options; complex layouting with fragments; listening to and processing hardware signals; security by design.
The Kotlin language is completely new for students, although Java shouldn't be. Since `90%` of all Stack Overflow Android searches yield Kotlin examples, and Google officially recommends Android developers to write in Kotlin, we decided to integrate that into the course as well. As an additional bonus, Kotlin now is also used in another course; _Software Engineering Skills_; in that same semester.
It's quite a challenge to fit so many framework-heavy concepts into such as small course. The question becomes: what are we trying to teach students here? How to debug problems in case you're stuck? How to efficiently look up information in API docs? It certainly wasn't how to create accessible and easy-to-use mobile apps---the framework-fight was more than difficult enough.
I needed a cool example, something that would spark motivation and engage students in discovering what you could do with a mobile app. I came up with the [How Long To Beat Android Demo App](https://git.brainbaking.com/wgroeneveld/howlongtobeat-android) that incorporates everything the AppDev course has to offer, giving students a taste of what was possible within a (very) limited time frame using their newly acquired knowledge. It contains activities, a navigation drawer component, segments, `RecyclerView` lists, permissions and intents, multiple data storage implementations, and a few HTTPS API calls to demonstrate external data retrieval.
What does it do, you might wonder? Good question, here's the answer:
{{< video "https://git.brainbaking.com/wgroeneveld/howlongtobeat-android/raw/branch/main/hltb.mp4" >}}
In case that still wasn't clear, the app:
- ... recognizes Game Boy and Game Boy Color cartridges based on a snapshot you take from your retro games;
- ... downloads art data and checks on `howlongtobeat.com` how long it takes for an average player to beat the game;
- ... saves this information in your personal game database if selected;
- ... shows some basic statistics on your game completion progress.
Cartridges are "recognized" by using [Google's Vision API](https://cloud.google.com/vision/)[^gvision] that returns OCR-recognized text from the uploaded camera snapshot. We're interested in the so-called cartridge code on the left of the cart sticker that starts with `DMG-` (Dot Matrix Game) if it's an old skool Game Boy cart and `CGB-` (Color Game Boy) if it's a Game Boy Color cart. For example, here's the _Wario Land: Super Mario Land 3_ cartridge:
[^gvision]: The easiest way to do this right now would probably be Android's [Text Recognition ML Kit](https://developers.google.com/ml-kit/vision/text-recognition/android) that didn't exist in 2021 when I started development of the project. This exposes the biggest problem with Android development: the API changes much _too_ frequently. I was often confused, the docs weren't up to date, answers posted online didn't match with my library versions, ...
![](../warioland.jpg "The Wario Land cartridge that has 'DMG-WJ-USA' printed on sideways.")
The Vision API will probably return something along the lines of `"DMG-WJ-USA MADE IN JAPAN THIS SIDE OUT"`. The `DMG-WJ-USA` code uniquely identifies each GB game. Vision isn't smart enough to detect the fancy lettering in the Wario Land logo, sadly.
Next, based on that code, the app searchers for the matching game title, either in a pre-defined repository (an embedded .csv file based on the database of Joonas Javanainen's amazing [Game Boy Hardware Database](https://gbhwdb.gekkio.fi/)), or if it was not found, fires off a DuckDuckGo search to try and interpret its results as a game title. Note that there is no real DuckDuckGo API: the app employs HTML scraping, which will be probably broken by now.
Lastly, if a game title could be found, it is fed to [howlongtobeat.com](https://www.howlongtobeat.com/)---also using (admittedly a bit advanced) HTML scraping---to finally return the results in a list for a RecyclerView to display to the user. If at any step something goes wrong, a correct error message is displayed, and the user can retry the whole process. Since the MS acquisition, the HLTB [REST API frequently changes](https://github.com/danbush/hltb-alfred-workflow/pull/2), and I don't think we're supposed to "use" it this way, but hey, it's just a demo.
Selecting a result from the HLTB results list automatically adds that game to your local database. You can "finish" the game by checking a checkbox in the game detail screen, meaning it took you x hours to finish. The statistics dialog box shows how long you still have to play to finish your entire collection.
It's been a lot of fun to develop the app and [I highly enjoyed writing in Kotlin](/post/2021/08/kotlin-is-java-2/) compared to plain old Java. As for practical use: in reality, it's obviously much quicker to simply surf to `howlongtobeat.com`. Some carts still aren't recognized: the cartridge codes are region-dependent (hence the above `-USA` suffix), and in some regions, the middle part `WJ` changes, making predictions harder. Furthermore, it doesn't support anything else besides GB and GBC games.
![](../hltbandroid.jpg "After scanning the Mario Golf GBC cart.")
If you're interested in the technical details, I segregated and released the demo project into another repository under the MIT license: https://git.brainbaking.com/wgroeneveld/howlongtobeat-android. The Vision API requiers a `GoogleVisionApiKey` in your `apikeys.properties` that can be fetched from [console.cloud.google.com](https://console.cloud.google.com/).

View File

@ -14,23 +14,24 @@ Most of these contributions are small and humble adjustments. For those interest
| Title | Year | Language |
|-------|------|----------|
| [Gobot](https://github.com/hybridgroup/gobot/), a Go framework for robotics, SBCs, and IoT | 2023 | <kbd>Go</kbd> |
| [Adafruit_Blinka](https://github.com/adafruit/Adafruit_Blinka), the CircuitPython API for MicroPython devices | 2023 | <kbd>Python</kbd> |
| [hltb-alfred-workflow](https://github.com/danbush/hltb-alfred-workflow), an Alfred workflow for the HLTB site | 2022 | <kbd>Ruby</kbd> |
| [Yarn Berry](https://github.com/yarnpkg/berry) documentation update | 2021 | <kbd>HTML</kbd> |
| [Commento](https://gitlab.com/commento/commento), a privacy-focused commenting platform | 2020 | <kbd>JS</kbd> |
| [Gobot](https://github.com/hybridgroup/gobot/), a Go framework for robotics, SBCs, and IoT | 2023 | [<kbd>Go</kbd>](/tags/go) |
| [Adafruit_Blinka](https://github.com/adafruit/Adafruit_Blinka), the CircuitPython API for MicroPython devices | 2023 | [<kbd>Python</kbd>](/tags/python) |
| [hltb-alfred-workflow](https://github.com/danbush/hltb-alfred-workflow), an Alfred workflow for the HLTB site | 2022 | [<kbd>Ruby</kbd>](/tags/ruby) |
| [Yarn Berry](https://github.com/yarnpkg/berry) documentation update | 2021 | [<kbd>HTML</kbd>](/tags/html) |
| [Commento](https://gitlab.com/commento/commento), a privacy-focused commenting platform | 2020 | [<kbd>Go</kbd>](/tags/go) [<kbd>JS</kbd>](/tags/javascript) |
| [vlaamseprogrammeerwedstrijd](https://github.com/vlaamseprogrammeerwedstrijd/), the university collab programming contests | 2020 | Various |
| [FieldsLinker](https://github.com/PhilippeMarcMeyer/FieldsLinker), a visual link matcher | 2019 | <kbd>JS</kbd> |
| [opbasm](https://github.com/kevinpt/opbasm), the Open PicoBlaze Assembler | 2019 | <kbd>Nim</kbd> |
| [gba-tileeditor](https://github.com/IanFinlayson/gba-tileeditor), a Qt-based tile editor for GBA dev. | 2018 | <kbd>C++</kbd> |
| [png2gba](https://github.com/IanFinlayson/png2gba), A GBA dev. image conversion utility | 2018 | <kbd>C++</kbd> |
| [ExtJSWebdriver](https://github.com/pratoservices/extjswebdriver), make WebDriver scenario testing for ExtJS less painful | 2017 | <kbd>C#</kbd> |
| [Gulp-svn](https://github.com/yasinkocak/gulp-svn), a Subversion plugin for Gulp JS | 2017 | <kbd>JS</kbd> |
| [GoHugo](https://github.com/gohugoio/), the Hugo (static site generator in Go) docs | 2017 | <kbd>HTML</kbd> |
| [Sausage](https://github.com/jlipps/sausage), a Selenium REST API | 2015 | <kbd>PHP</kbd> |
| [net-deep-copy](https://github.com/Burtsev-Alexey/net-object-deep-copy), a fast object cloning utility (_unmerged_) | 2014 | <kbd>C#</kbd> |
| [NHibernate](https://github.com/nhibernate/nhibernate-core/) & NHibernate-Caches, a DB ORM framework (_[rejected](https://github.com/nhibernate/nhibernate-core/pull/284)_) | 2014 | <kbd>C#</kbd> |
| [GhostDriver](https://github.com/detro/ghostdriver), a remote WebDriver protocol using PhantomJS | 2013 | <kbd>JS</kbd> |
| [boostrap-wysihtml5](https://github.com/jhollingworth/bootstrap-wysihtml5), a WYSIWYG editor in Bootstrap (_unmerged_) | 2013 | <kbd>HTML</kbd> |
| [FieldsLinker](https://github.com/PhilippeMarcMeyer/FieldsLinker), a visual link matcher | 2019 | [<kbd>JS</kbd>](/tags/javascript) |
| [opbasm](https://github.com/kevinpt/opbasm), the Open PicoBlaze Assembler | 2019 | [<kbd>Nim</kbd>](/tags/nim) |
| [gba-tileeditor](https://github.com/IanFinlayson/gba-tileeditor), a Qt-based tile editor for GBA dev. | 2018 | [<kbd>C++</kbd>](/tags/c++) |
| [png2gba](https://github.com/IanFinlayson/png2gba), A GBA dev. image conversion utility | 2018 | [<kbd>C++</kbd>](/tags/c++) |
| [ExtJSWebdriver](https://github.com/pratoservices/extjswebdriver), make WebDriver scenario testing for ExtJS less painful | 2017 | [<kbd>C#</kbd>](/tags/csharp) |
| [PratoGame](https://github.com/pratoservices/PratoGame), a Phaser-based HTML5 game by devs for devs | 2017 | [<kbd>JS</kbd>](/tags/javascript) |
| [Gulp-svn](https://github.com/yasinkocak/gulp-svn), a Subversion plugin for Gulp JS | 2017 | [<kbd>JS</kbd>](/tags/javascript) |
| [GoHugo](https://github.com/gohugoio/), the Hugo (static site generator in Go) docs | 2017 | [<kbd>HTML</kbd>](/tags/html) |
| [Sausage](https://github.com/jlipps/sausage), a Selenium REST API | 2015 | [<kbd>PHP</kbd>](/tags/php) |
| [net-deep-copy](https://github.com/Burtsev-Alexey/net-object-deep-copy), a fast object cloning utility (_unmerged_) | 2014 | [<kbd>C#</kbd>](/tags/csharp) |
| [NHibernate](https://github.com/nhibernate/nhibernate-core/) & NHibernate-Caches, a DB ORM framework (_[rejected](https://github.com/nhibernate/nhibernate-core/pull/284)_) | 2014 | [<kbd>C#</kbd>](/tags/csharp) |
| [GhostDriver](https://github.com/detro/ghostdriver), a remote WebDriver protocol using PhantomJS | 2013 | [<kbd>JS</kbd>](/tags/javascript) |
| [boostrap-wysihtml5](https://github.com/jhollingworth/bootstrap-wysihtml5), a WYSIWYG editor in Bootstrap (_unmerged_) | 2013 | [<kbd>HTML</kbd>](/tags/html) |
</small>

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB