NovaKun logo novakun

Learning Vs Executing

Written on June 11, 2025

A couple of weeks ago I wanted to experiment a bit with Hotwire Native. I came across a video about it by the superrails youtube channel guy and got curious how fast one would be into having a rails website into GooglePlay.
In order to test it fast I just prompted like a mad person and ended up with a simple workouts website that contained:

  • - a simple exercise DB which can be created by any user.
  • - a workouts DB, which can be put together by any user.
  • - a way to track your workouts and see your history.

Nothing really fancy. Since the point of this application was for me to learn about Hotwire Native and not to continue practicing my basic coding skills, I got all the code through prompts.
You can check the results at recomplab.com

The good, the bad and the ugly.

The good:
Hotwire Native is very powerful and extremely easy to implement. I managed to go through the setup tutorial at https://native.hotwired.dev/ in less than an hour. One can have their example app running in under ten minutes, but if you're not very familiar with Android Studio, Kotlin, and need to read things at least three times to be sure you're putting things in the right folders, etc., it might stretch to an hour.
In no time, I was able to see my website running in the emulator and that was very, very cool.
 Also, there were no problems with submissions into Google Play. I was a bit scared about the whole “running a website inside an app” thing  and I know Google doesn’t mess around when they ban you. I’ve experienced firsthand at previous companies how a clerical mistake can get your account banned for good, so I was a bit nervous about it.

The bad:
I’m still unsure how much extra work one would have to do when going for a commercial app. You’d probably need to build the shop natively in Kotlin in order to have it go through the Google Play Store… unless there are workarounds I’m unaware of.
I only did the main navigation menu as a native bar in Kotlin, since I found the navigation bar inside Hotwire Native was not as fluent as I’d like. Same goes for the Google Sign-Up button. I learned the hard way: Google authentication requires a different key/secret when run through an Android app, so I had to create a new button that would only show up when in the Android environment to pass the correct keys.
That took me quite a while because I got Google Sign-Up and Google Play Sign-In mixed up and had to redo everything a couple of times.

The ugly:
Doing everything with LLMs gets you into a “braindead vibe” where you just shut down your brain and keep pulling the slot machine until you hit the jackpot. I think it still works miracles when it comes to frontend stuff, but once you have three environments working together and a relatively big context, things start failing.
Nothing that a lot of stubbornness and patience can’t get you through but certainly far from a pleasurable experience.
That being said, it is still a tiny miracle machine that empowers you to do end to end app development with very little knowledge. (Whether that’s a good or bad thing, only time will tell.)
After finishing the first pass for recomplab.com and getting a feel for the complexity and requirements of releasing a Rails website as an app, I decided to get back into “learning mode.”

I advanced a bit more in my Codecademy class about Ruby and Rails and started feeling more confident, there were already some things I wanted to try out by myself without any LLM training wheels.
Coincidentally, this last weekend, a friend of mine gave us an old board game they no longer needed for my 1.5-year-old daughter. The game is a very nice, simple “fruit harvesting game” where you have to collect all the fruits from the trees before a crow arrives and eats them all. The fruits and crow are made out of wood and feel very nice in your hand, so I understand why my daughter loved it so much.
Since there’s no complex decision-making involved and the core mechanic is so simple, I decided to make a tiny tribute by trying to create a digital version of it.
Erster Obstgarten by Haba


The rules are very simple:
  • You have 4 pieces of 4 different types of fruits on 4 different trees.
  • You have a 6-face dice: 4 faces are reserved for the fruits, one for the crow, and one for a fruit basket.
  • You have a crow and 4 tiles + one tile that looks like a gate (entrance to the farm).
  • Players roll the dice in turn. If they get a fruit (and it's still on the board), they can pick it up.
  • If the crow appears, it advances one tile.
  • If the fruit basket appears, you can pick any remaining fruit.
  • Collect all fruits before the crow reaches the gate = you win.
  • Crow reaches the gate before all fruits are collected = crow wins.

It’s a very simple game, and the way I did each mechanic is far from being efficient or clean — but I did it all by myself (okay, well, not the art — that was generated).
Splash Screen, yay!


What was my approach?
For the dice, I created an array with numbers 1 to 6.
 (I’m sure there’s a math method to do this, but I just didn’t know one. Last time I played with this stuff was years ago in Unity with Math.random() in C#.)
Then I made a function using .sample to pick a value randomly from the array.

 At first, I just printed the result to the console to test it. Once it worked, I tried to render it in the view and... I got stuck.
The method worked — but @rolled_dice wasn’t showing. I debugged it and everything was running, but the variable just wouldn’t render. Turns out the value was getting lost after the page refresh. I naïvely thought I needed a Turbo Frame to preserve it (because I had used them before on mientrenadorpersonal.com to render some calculated stats).
I foolishly assumed that must be the “standard Rails way” to persist a value. I wrapped my view in a Turbo Frame, got frustrated — this couldn’t be the correct way to render a number. It just felt wrong.

So I broke my "no LLMs" rule and asked one for help — but only to confirm whether Turbo was the right approach. It reassured me that yes, it was.
(They even understood my frustration, coming from engines like Unity where rendering a value is just an FPS tick away.)
Midway through trying to hunt flies with a cannon, a friend of mine — a seasoned full-stack dev — called to talk about work. Lucky me. After our chat, I told him about my problem. He chuckled and explained that many frontend-minded devs misuse Turbo. “Turbo is there to improve UX — not to replace your HTML.”

 “Your page needs to work without Turbo first. Then you enhance it with Turbo.”
 He pointed me toward Flash messages and reminded me that if I want to manipulate variables across requests, I should figure out how I want to do it.
After the call, I learned about Flash and how it can survive a single page refresh — enough to show the result of the dice roll.
def roll_dice
  @dice = [1, 2, 3, 4, 5, 6]
  @rolled_dice = @dice.sample
  case @rolled_dice
  when 1
    @dice_image = "apple_dice"
  when 2
    @dice_image = "plum_dice"
  when 3
    @dice_image = "pear_dice"
  when 4
    @dice_image = "banana_dice"
  when 5
    @dice_image = "crow_dice"
  else
    @dice_image = "basket_dice"
  end

  flash[:dice_image] = @dice_image
  flash[:rolled_dice] = @rolled_dice.to_s
  redirect_to projects_farm_dice_path
end

I remembered there’s a way to store values locally, but didn’t recall how. What I did know was how to use the DB — so I decided to track everything through a GameState model. 🙂
class CreateGameStates < ActiveRecord::Migration[8.0]
  def change
    create_table :game_states do |t|
      t.integer :garden_apples, default: 4
      t.integer :garden_pears, default: 4
      t.integer :garden_bananas, default: 4
      t.integer :garden_plums, default: 4
      t.integer :player_apples, default: 0
      t.integer :player_pears, default: 0
      t.integer :player_bananas, default: 0
      t.integer :player_plums, default: 0
      t.integer :crow, default: 0
      t.boolean :game_over, default: false
      t.boolean :crow_wins, default: false
      t.boolean :player_wins, default: false
      t.timestamps
    end
  end
end

After that, the rest was easy. Far from elegant, but I was proud to see the crow “hop” from tile to tile and the fruits being taken from trees.
def roll_dice
  @dice = [1, 2, 3, 4, 5, 6]
  @rolled_dice = @dice.sample
  case @rolled_dice
  when 1
    @dice_image = "apple_dice"
    @game.garden_apples -= 1
    @game.player_apples += 1
  when 2
    @dice_image = "plum_dice"
    @game.garden_plums -= 1
    @game.player_plums += 1
  when 3
    @dice_image = "pear_dice"
    @game.garden_pears -= 1
    @game.player_pears += 1
  when 4
    @dice_image = "banana_dice"
    @game.garden_bananas -= 1
    @game.player_bananas += 1
  when 5
    @dice_image = "crow_dice"
    @game.crow += 1
  else
    @dice_image = "basket_dice"
    flash[:choose_fruit] = true
  end

  @game.save
  is_end_game
  flash[:dice_image] = @dice_image
  flash[:rolled_dice] = @rolled_dice.to_s
  redirect_to projects_farm_dice_path
end

I’m sure a real developer would cry looking at that. But I was so happy to see it work.
Checking if the game was over:
def is_end_game
  puts "Apples: #{@game.player_apples}, Bananas: #{@game.player_bananas}, Pears: #{@game.player_pears}, Plums: #{@game.player_plums}, Crow: #{@game.crow}"

  if @game.player_apples >= 4 &&
     @game.player_bananas >= 4 &&
     @game.player_pears >= 4 &&
     @game.player_plums >= 4
    player_victory
  elsif @game.crow >= 5
    crow_victory
  end
end

And then reset the DB on win/lose:
def player_victory
  @game.game_over = true
  @game.player_wins = true
  @game.save
  puts "Players win!"
end

def crow_victory
  @game.game_over = true
  @game.crow_wins = true
  @game.save
  puts "Crow wins"
end

Yeah — clearly not the “right tool” for the job, but hey… whatever works, right?

I should still polish a bunch of things, like showing which fruits have been collected or adding some drama when the crow moves. Maybe some JS tweens. But I don’t really feel like learning that right now — still focusing on more “pure” Ruby learning.


Final thoughts.
I can’t express how proud I am of this experiment. I wasn’t sure if the past month of learning was really sticking or if I’d be able to build anything without LLMs. This project gave me a huge confidence boost.

Conclusion from both experiments:
  • Building recomplab.com was interesting from a product perspective. It’s wild how fast you can go from idea to app in the store.
    But it felt like a chore, frustrating and not much fun.
  • Building Experiment 5 was a blast. I got stuck, cursed, probably has still mistakes in it, but figuring things out on my own made the suffering worth it and the satisfaction of being able to do it yourself is just priceless. (Plus I'm looking forward revisiting the code in some days and see if I can refactor it a bit)
    A satisfied crow
I half-remember a tweet or post from DHH saying:
“It’s more fun to be competent.”
I'm starting to understand what he meant.