<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nenad Nikolić</title>
    <description>The latest articles on DEV Community by Nenad Nikolić (@turnkit-dev).</description>
    <link>https://dev.to/turnkit-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814850%2Fa928178e-53d9-40f4-a1fe-112bb780e92d.png</url>
      <title>DEV Community: Nenad Nikolić</title>
      <link>https://dev.to/turnkit-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/turnkit-dev"/>
    <language>en</language>
    <item>
      <title>I Built a Multiplayer Card Game to Test My Own Game Backend SaaS</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:56:00 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/i-built-a-multiplayer-card-game-to-test-my-own-game-backend-saas-241e</link>
      <guid>https://dev.to/turnkit-dev/i-built-a-multiplayer-card-game-to-test-my-own-game-backend-saas-241e</guid>
      <description>&lt;p&gt;I recently released &lt;strong&gt;Meksiko&lt;/strong&gt;, an Android version of a traditional multiplayer card game.&lt;/p&gt;

&lt;p&gt;The game itself was not the only goal.&lt;/p&gt;

&lt;p&gt;I built it as a real production client for &lt;strong&gt;TurnKit&lt;/strong&gt;, my backend SaaS for multiplayer and connected games. Instead of testing the platform only through isolated demos, I wanted to build a complete game and discover which parts of the SDK worked well, which parts were awkward, and which features were still missing.&lt;/p&gt;

&lt;p&gt;Google Play:&lt;br&gt;
&lt;a href="https://play.google.com/store/apps/details?id=com.turnkit.meksiko" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.turnkit.meksiko&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why build a complete game?
&lt;/h2&gt;

&lt;p&gt;Small technical demos are useful, but they usually follow the ideal path.&lt;/p&gt;

&lt;p&gt;A real game introduces problems that are easy to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;players disconnect&lt;/li&gt;
&lt;li&gt;server events arrive while animations are running&lt;/li&gt;
&lt;li&gt;turns time out&lt;/li&gt;
&lt;li&gt;lists change remotely&lt;/li&gt;
&lt;li&gt;UI state becomes stale&lt;/li&gt;
&lt;li&gt;users close and reopen the application&lt;/li&gt;
&lt;li&gt;authentication behaves differently in production&lt;/li&gt;
&lt;li&gt;game rules require features the original API did not anticipate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Meksiko became both a game and an integration test for the platform.&lt;/p&gt;
&lt;h2&gt;
  
  
  The game architecture
&lt;/h2&gt;

&lt;p&gt;The server is authoritative over the multiplayer session.&lt;/p&gt;

&lt;p&gt;It controls important state such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connected players&lt;/li&gt;
&lt;li&gt;player order&lt;/li&gt;
&lt;li&gt;the current turn&lt;/li&gt;
&lt;li&gt;game phase&lt;/li&gt;
&lt;li&gt;submitted actions&lt;/li&gt;
&lt;li&gt;votes and decisions&lt;/li&gt;
&lt;li&gt;timeout handling&lt;/li&gt;
&lt;li&gt;reconnection state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Unity client receives updates and presents them through the UI, animations and local game objects.&lt;/p&gt;

&lt;p&gt;In theory, this sounds straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive server state.&lt;/li&gt;
&lt;li&gt;Update the local model.&lt;/li&gt;
&lt;li&gt;Refresh the UI.&lt;/li&gt;
&lt;li&gt;Play the appropriate animation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In practice, coordinating these steps was one of the more difficult parts of the project.&lt;/p&gt;
&lt;h2&gt;
  
  
  AI was useful, but struggled with event-driven state
&lt;/h2&gt;

&lt;p&gt;I used AI-assisted development during parts of the project.&lt;/p&gt;

&lt;p&gt;It worked well for relatively isolated tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;writing asynchronous functions&lt;/li&gt;
&lt;li&gt;generating request and response models&lt;/li&gt;
&lt;li&gt;creating utility methods&lt;/li&gt;
&lt;li&gt;reducing repetitive integration code&lt;/li&gt;
&lt;li&gt;implementing clearly defined transformations&lt;/li&gt;
&lt;li&gt;suggesting error-handling paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was much less reliable when reasoning about state that changed through server events.&lt;/p&gt;

&lt;p&gt;A common incorrect assumption was that receiving a response or updating one object would instantly update every related collection and UI element.&lt;/p&gt;

&lt;p&gt;For example, when the server changed the player list, the client still needed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;receive the relevant event&lt;/li&gt;
&lt;li&gt;deserialize the new state&lt;/li&gt;
&lt;li&gt;replace or mutate the local collection&lt;/li&gt;
&lt;li&gt;notify the presentation layer&lt;/li&gt;
&lt;li&gt;rebuild or update the UI&lt;/li&gt;
&lt;li&gt;handle removed and newly added players&lt;/li&gt;
&lt;li&gt;avoid updating objects that were currently being animated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI-generated code often skipped one or more of these steps.&lt;/p&gt;

&lt;p&gt;It understood an asynchronous function such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;PlayerList&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPlayersAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;more reliably than a distributed event flow where several systems needed to react to the same update.&lt;/p&gt;

&lt;p&gt;The difficult part was not writing &lt;code&gt;async&lt;/code&gt; code. The difficult part was defining ownership of the state and deciding exactly which system was responsible for applying each update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server events should not directly control the UI
&lt;/h2&gt;

&lt;p&gt;One important lesson was to avoid letting network callbacks manipulate the UI directly.&lt;/p&gt;

&lt;p&gt;A tempting implementation looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PlayerJoined&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;playerListView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPlayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works initially, but becomes fragile once the game has animations, reconnects, scene changes and multiple event types.&lt;/p&gt;

&lt;p&gt;A better flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server event
    ↓
Network adapter
    ↓
Local game state
    ↓
State-change notification
    ↓
Presenter
    ↓
View
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The event updates the local model first. The UI then renders the resulting state.&lt;/p&gt;

&lt;p&gt;This creates a single source of truth and makes it easier to handle reconnects or rebuild the entire interface from a server snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async operations were easier to reason about
&lt;/h2&gt;

&lt;p&gt;Request-response operations were generally much easier to implement and test.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;MoveResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;gameClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SubmitMoveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ShowError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;ApplyServerState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GameState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code has a clear beginning and end.&lt;/p&gt;

&lt;p&gt;The caller knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the operation started&lt;/li&gt;
&lt;li&gt;when it completed&lt;/li&gt;
&lt;li&gt;whether it failed&lt;/li&gt;
&lt;li&gt;which state was returned&lt;/li&gt;
&lt;li&gt;when the UI may continue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events are different. They can arrive at any point, may be duplicated, and may refer to state that has already changed.&lt;/p&gt;

&lt;p&gt;That does not mean events should be avoided. Multiplayer games need them. But event-driven systems require stricter rules around ordering, ownership and idempotency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features I discovered were missing
&lt;/h2&gt;

&lt;p&gt;Building Meksiko exposed several features that were either missing or not flexible enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing turns
&lt;/h3&gt;

&lt;p&gt;The original turn system assumed that a player would perform an action before the next player became active.&lt;/p&gt;

&lt;p&gt;Meksiko needed a proper &lt;strong&gt;pass&lt;/strong&gt; action.&lt;/p&gt;

&lt;p&gt;Passing is not merely an empty move. It can affect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether the player may act again&lt;/li&gt;
&lt;li&gt;when a bidding phase ends&lt;/li&gt;
&lt;li&gt;which player wins the current decision&lt;/li&gt;
&lt;li&gt;what happens when every player passes&lt;/li&gt;
&lt;li&gt;whether the server should immediately advance the turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This required more explicit support in the turn API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Google Play integration
&lt;/h3&gt;

&lt;p&gt;Authentication worked, but the complete Google Play experience required more than signing in.&lt;/p&gt;

&lt;p&gt;A production Android game also needs to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;silent sign-in&lt;/li&gt;
&lt;li&gt;account linking&lt;/li&gt;
&lt;li&gt;guest-account upgrades&lt;/li&gt;
&lt;li&gt;restoring an existing account&lt;/li&gt;
&lt;li&gt;profile names and avatars&lt;/li&gt;
&lt;li&gt;Play Games Services behavior after reinstalling&lt;/li&gt;
&lt;li&gt;login cancellation and failure states&lt;/li&gt;
&lt;li&gt;keeping the game usable without Google sign-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This showed me that authentication should be treated as a complete user flow, not simply an SDK method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconnection and state recovery
&lt;/h3&gt;

&lt;p&gt;A reconnecting client should not try to replay every event it missed.&lt;/p&gt;

&lt;p&gt;It should request or receive an authoritative snapshot and rebuild the local state.&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events describe changes.
Snapshots describe truth.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Events are useful while the client is connected. Snapshots are safer after reconnecting or when the client suspects its local state is inconsistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked well
&lt;/h2&gt;

&lt;p&gt;The project also validated several parts of the platform.&lt;/p&gt;

&lt;p&gt;The asynchronous API was generally straightforward to consume from Unity.&lt;/p&gt;

&lt;p&gt;The backend could manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiplayer sessions&lt;/li&gt;
&lt;li&gt;turn order&lt;/li&gt;
&lt;li&gt;server-side game state&lt;/li&gt;
&lt;li&gt;player replacement with bots&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;reconnection&lt;/li&gt;
&lt;li&gt;remote actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using a real game also helped simplify parts of the SDK that had previously been designed too generically.&lt;/p&gt;

&lt;p&gt;A feature may appear flexible when designed in isolation but still feel awkward when used repeatedly inside gameplay code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest lesson
&lt;/h2&gt;

&lt;p&gt;The most important lesson was that a multiplayer SDK should not only expose network operations.&lt;/p&gt;

&lt;p&gt;It should help developers maintain a consistent local representation of server state.&lt;/p&gt;

&lt;p&gt;The difficult questions are usually not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do I send this request?&lt;/li&gt;
&lt;li&gt;How do I deserialize this response?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns this state?&lt;/li&gt;
&lt;li&gt;Can this event arrive twice?&lt;/li&gt;
&lt;li&gt;Can it arrive out of order?&lt;/li&gt;
&lt;li&gt;What happens if the scene changes before it completes?&lt;/li&gt;
&lt;li&gt;Should the client apply a delta or replace the state?&lt;/li&gt;
&lt;li&gt;Can the same operation be safely repeated?&lt;/li&gt;
&lt;li&gt;What happens when an animation is still using the previous state?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions determine whether the client remains stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  I may open-source the game
&lt;/h2&gt;

&lt;p&gt;I am considering open-sourcing the Unity client later.&lt;/p&gt;

&lt;p&gt;The project could serve as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a complete multiplayer Unity example&lt;/li&gt;
&lt;li&gt;a reference integration for TurnKit&lt;/li&gt;
&lt;li&gt;an example of server-authoritative turn-based architecture&lt;/li&gt;
&lt;li&gt;a practical demonstration of reconnects, events and async operations&lt;/li&gt;
&lt;li&gt;a starting point for developers building card or board games&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before releasing it, I would likely separate game-specific code from reusable SDK integration and remove production configuration and credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Building a complete game was much more valuable than creating another isolated SDK demo.&lt;/p&gt;

&lt;p&gt;It exposed missing features, unclear abstractions and assumptions that only became visible under real gameplay conditions.&lt;/p&gt;

&lt;p&gt;AI-assisted development helped with local and well-defined asynchronous tasks. It was less effective at reasoning about distributed event-driven state, especially when server updates needed to propagate through several client systems.&lt;/p&gt;

&lt;p&gt;The result is not only a released Android game. It is also a much better understanding of what the backend platform needs to provide.&lt;/p&gt;

&lt;p&gt;I plan to continue improving both the game and the SDK, and I am interested in eventually publishing the client as an open-source reference project.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>gamedev</category>
      <category>saas</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Whisper on Mobile Is Better Than I Expected, So I Built an Offline Notes App</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Sat, 18 Jul 2026 13:54:50 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/whisper-on-mobile-is-better-than-i-expected-so-i-built-an-offline-notes-app-41d7</link>
      <guid>https://dev.to/turnkit-dev/whisper-on-mobile-is-better-than-i-expected-so-i-built-an-offline-notes-app-41d7</guid>
      <description>&lt;p&gt;I expected offline speech to text on mobile to be too slow, too heavy, or too unreliable for a real app.&lt;/p&gt;

&lt;p&gt;After testing Whisper locally on Android, I changed my mind.&lt;/p&gt;

&lt;p&gt;For long meetings or perfect transcription, small models still have limits. But for short voice notes, they are already practical.&lt;/p&gt;

&lt;p&gt;That led me to build &lt;strong&gt;Hands Free Notes&lt;/strong&gt;, an Android app for saving notes by voice with speech processed on device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why offline matters
&lt;/h2&gt;

&lt;p&gt;Voice notes can contain private thoughts, tasks, work details, names, reminders, and personal information.&lt;/p&gt;

&lt;p&gt;So the product promise is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No account. No server. Speech processed on your phone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is easier for users to understand than a generic “AI powered notes app” pitch.&lt;/p&gt;

&lt;p&gt;It also avoids backend cost, API keys, rate limits, and internet dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Whisper fits this use case
&lt;/h2&gt;

&lt;p&gt;For mobile notes, I do not need perfect transcription of a two hour recording.&lt;/p&gt;

&lt;p&gt;I need useful transcription for short clips like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buy eggs and milk tomorrow&lt;/li&gt;
&lt;li&gt;Remember to send invoice to Mark&lt;/li&gt;
&lt;li&gt;Idea for cooking app: add voice controlled timers&lt;/li&gt;
&lt;li&gt;Call dentist about appointment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where small Whisper models make sense.&lt;/p&gt;

&lt;p&gt;The input is short. The context is simple. The user can edit the result if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core app flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User speaks&lt;/li&gt;
&lt;li&gt;Audio is recorded locally&lt;/li&gt;
&lt;li&gt;Whisper transcribes it on device&lt;/li&gt;
&lt;li&gt;Text note is saved locally&lt;/li&gt;
&lt;li&gt;User can edit, search, copy, share, favorite, or delete it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important part is that the app should not feel like a transcription demo.&lt;/p&gt;

&lt;p&gt;It should feel like a normal notes app where voice is the fastest input method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product decisions that mattered
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Short recordings work best
&lt;/h3&gt;

&lt;p&gt;The app is designed for quick capture, not long interviews.&lt;/p&gt;

&lt;p&gt;Short clips keep processing predictable and make retry painless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure needs a simple path
&lt;/h3&gt;

&lt;p&gt;Speech recognition will fail sometimes.&lt;/p&gt;

&lt;p&gt;Bad UX:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transcription failed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Better UX:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Could not understand clearly. Try again or type the note manually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For notes, failure is acceptable if editing and retry are easy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the AI boundary small
&lt;/h3&gt;

&lt;p&gt;Whisper only does speech to text.&lt;/p&gt;

&lt;p&gt;Everything else is normal app logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recording state&lt;/li&gt;
&lt;li&gt;Silence detection&lt;/li&gt;
&lt;li&gt;Countdown&lt;/li&gt;
&lt;li&gt;Transcription status&lt;/li&gt;
&lt;li&gt;Local storage&lt;/li&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Editing&lt;/li&gt;
&lt;li&gt;Sharing&lt;/li&gt;
&lt;li&gt;Monetization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the app easier to debug and less dependent on AI behaving perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy is a real feature
&lt;/h3&gt;

&lt;p&gt;Offline transcription is not just a technical detail.&lt;/p&gt;

&lt;p&gt;It is a product feature users understand immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your voice stays on your phone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a notes app, that matters more than adding another cloud sync feature too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture is intentionally boring:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Flutter UI&lt;/li&gt;
&lt;li&gt;Native audio recording&lt;/li&gt;
&lt;li&gt;Local WAV file&lt;/li&gt;
&lt;li&gt;Whisper native layer&lt;/li&gt;
&lt;li&gt;Transcription result&lt;/li&gt;
&lt;li&gt;SQLite/local storage&lt;/li&gt;
&lt;li&gt;Notes UI&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model converts audio to text. The app handles everything around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monetization
&lt;/h2&gt;

&lt;p&gt;I kept monetization simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free daily notes&lt;/li&gt;
&lt;li&gt;Rewarded ad unlock&lt;/li&gt;
&lt;li&gt;One time unlimited purchase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small utility app, a subscription felt wrong. A permanent unlock fits better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Whisper on mobile makes sense
&lt;/h2&gt;

&lt;p&gt;Good fits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Voice notes&lt;/li&gt;
&lt;li&gt;Quick reminders&lt;/li&gt;
&lt;li&gt;Personal logs&lt;/li&gt;
&lt;li&gt;Hands free checklists&lt;/li&gt;
&lt;li&gt;Cooking notes&lt;/li&gt;
&lt;li&gt;Field worker notes&lt;/li&gt;
&lt;li&gt;Offline forms&lt;/li&gt;
&lt;li&gt;Simple journaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weak fits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long meetings&lt;/li&gt;
&lt;li&gt;Noisy multi speaker audio&lt;/li&gt;
&lt;li&gt;Perfect legal or medical transcription&lt;/li&gt;
&lt;li&gt;Instant command systems that need very low latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best use cases are short, focused, and tolerant of small mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Whisper on mobile is already good enough for useful offline apps if the workflow is narrow.&lt;/p&gt;

&lt;p&gt;For voice notes, the value is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Speak for a few seconds&lt;/li&gt;
&lt;li&gt;Get local transcription&lt;/li&gt;
&lt;li&gt;Save the note&lt;/li&gt;
&lt;li&gt;Edit if needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No account. No server. No complicated workflow.&lt;/p&gt;

&lt;p&gt;Just faster note capture when typing is inconvenient.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>android</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Hands Free Notes Offline: Voice to Notes App Triggered by “Pickle”</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Fri, 29 May 2026 13:19:00 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/hands-free-notes-offline-voice-to-notes-app-triggered-by-pickle-4p99</link>
      <guid>https://dev.to/turnkit-dev/hands-free-notes-offline-voice-to-notes-app-triggered-by-pickle-4p99</guid>
      <description>&lt;p&gt;Built an offline, privacy-focused Android app that lets you capture ideas instantly by saying one word — no phone in hand.&lt;/p&gt;

&lt;p&gt;The Problem&lt;br&gt;
I kept losing good ideas while driving, cooking, or multitasking because existing voice note apps required too many steps.&lt;/p&gt;

&lt;p&gt;The Solution&lt;br&gt;
Hands Free Notes Offline lets you activate once, say “Pickle” (or “Pico”), speak naturally, and it instantly saves both transcribed text and the full audio recording.&lt;/p&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-word wake word activation&lt;/li&gt;
&lt;li&gt;Fully offline &amp;amp; private (no forced cloud)&lt;/li&gt;
&lt;li&gt;Lightweight and battery efficient&lt;/li&gt;
&lt;li&gt;Free with rewarded ads + $4.99 one-time lifetime unlock&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical Approach&lt;br&gt;
I used sherpa onnx for KWS detection and whisper.cpp for local transcription. The main challenges were reliable background listening, minimizing false positives, and handling battery optimizations.&lt;/p&gt;

&lt;p&gt;Try it for free on: &lt;a href="https://play.google.com/store/apps/details?id=com.handsfree.notes" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.handsfree.notes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>android</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Kill the 'Double Coding Tax': A Hybrid Approach to Authoritative Multiplayer Turn-Based Games</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Fri, 29 May 2026 01:08:02 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/kill-the-double-coding-tax-a-hybrid-approach-to-authoritative-multiplayer-turn-based-games-1pn8</link>
      <guid>https://dev.to/turnkit-dev/kill-the-double-coding-tax-a-hybrid-approach-to-authoritative-multiplayer-turn-based-games-1pn8</guid>
      <description>&lt;p&gt;Making a multiplayer turn based game means making client side code (Unity, Godot, or whatever engine you use) and the server side. This leads to remaking same systems for all turn based games, turn enforcment and for most games some hand hiding system, in addition to networking, authentication and other must have features. Also validating every function on server side, so doubling the work.&lt;/p&gt;

&lt;p&gt;Using Mirror, FishNet or similar frameworks helps reusing code, but it forces you into one game server for every game and that becomes expensive fast. Turn based server could handle thousands of games if its well optimised.&lt;/p&gt;

&lt;p&gt;Using Relay servers can speed up development, but you still implement turn  enforcment, hand hiding and they leave you open to hacking and players ruining your games.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Hybrid Approach That Cuts the Workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A specialized turn-based server handles the generic but critical parts that every game needs. Your client can simply react to clean events like “your turn started” or “turn changed”.&lt;/li&gt;
&lt;li&gt;Hand hiding (and other hidden data) is solved in a generic way: you define lists and visibility rules via client-side configuration. The server automatically creates and filters those lists so each player only sees what they’re allowed to see.&lt;/li&gt;
&lt;li&gt;Reconnecting players and other core features are handled reliably by the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leaves you with only the specific game rules, which some simple games might not even need on the server. For more complex rules, you can use client voting consensus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every client validates moves in the background (players don’t have to click anything). When a move arrives as an event, other clients check if it’s legal based on the game rules you already wrote for the client. The server accepts the move if enough clients agree.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I haven’t seen any solution that supports this hybrid pattern, so I built my own. It’s probably too niche for big companies, but it fits indie and solo developers really well.&lt;/p&gt;

&lt;p&gt;You can try it yourself with the live demo:&lt;br&gt;
&lt;a href="https://turnkit.dev/live-demo" rel="noopener noreferrer"&gt;https://turnkit.dev/live-demo&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Or check the docs at: &lt;a href="https://turnkit.dev/docs" rel="noopener noreferrer"&gt;https://turnkit.dev/docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This aproach, like any, does have its trade offs. Multiple client can hack together and outvote an honest player, but a match with multiple hackers is rare and already ruined usually. In a 1x1 match hacker can vote fail at end of the game when he is about to lose, so some reputation tracking system would be needed in this case.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>godot</category>
      <category>multiplayer</category>
    </item>
    <item>
      <title>How I Built a Lightweight Turn-Based Relay Server with Java 25 Virtual Threads And Reconnect Handling</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Sun, 19 Apr 2026 22:54:27 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/how-i-built-a-lightweight-turn-based-relay-server-with-java-25-virtual-threads-and-reconnect-2jg2</link>
      <guid>https://dev.to/turnkit-dev/how-i-built-a-lightweight-turn-based-relay-server-with-java-25-virtual-threads-and-reconnect-2jg2</guid>
      <description>&lt;p&gt;Turn-based multiplayer is deceptively hard. Here's how I designed a simple, fast, and cost-effective relay backend that auto reconnects dropped clients and keeps egress + resource usage low.&lt;/p&gt;

&lt;p&gt;Building reliable turn-based multiplayer is painful. Enforcing turns, hiding hands/decks, handling votes, and especially managing disconnects and crashes can eat weeks of dev time.&lt;br&gt;
I wanted something lean that "just works" for Unity and other clients without massive infrastructure costs. So I built TurnKit Relay around a few core principles:&lt;/p&gt;

&lt;p&gt;Forward-only design (no complex state rollback)&lt;br&gt;
Java 25 virtual threads for massive concurrency with low overhead&lt;br&gt;
Move-based delta reconnects instead of full resyncs&lt;br&gt;
Aggressive egress and memory optimization&lt;/p&gt;

&lt;p&gt;The result? A relay that handles temporary drops and full game crashes gracefully while staying extremely cheap to run at scale.&lt;/p&gt;

&lt;p&gt;Most real-time servers struggle with reconnects because they either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resend the entire game state (high bandwidth)&lt;/li&gt;
&lt;li&gt;Or require complex session management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose a simpler path: everything is forward-only. The server only replays missed moves since the client's last acknowledged move number.&lt;/p&gt;

&lt;p&gt;Core Technical Choices&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Java 25 Virtual Threads&lt;/strong&gt;&lt;br&gt;
Virtual threads made it possible to handle thousands of concurrent matches with near native thread per connection simplicity, without the overhead of traditional thread pools or reactive complexity. This keeps the code readable while scaling horizontally very cheaply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Forward Only + Move-Based Deltas&lt;br&gt;
On reconnect, the client sends &lt;code&gt;&lt;/code&gt;RECONNECT { lastMoveNumber }&lt;code&gt;&lt;/code&gt;. The server replays only the missed OnMoveMade events. No full state snapshots, no diffing engines — just sequential moves. This dramatically cuts egress costs and simplifies the backend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other Optimizations for Low Egress &amp;amp; High Density&lt;br&gt;
Server managed lists with data masking (hands/decks stay hidden server side). Minimal JSON payloads. Early termination of stale matches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These choices let a single modest instance support many more simultaneous matches than a typical WebSocket heavy setup.&lt;/p&gt;

&lt;p&gt;If you're building a turn-based game and tired of wrestling with matchmaking, turns, hidden state, and reconnect logic, TurnKit handles the hard parts out of the box.&lt;/p&gt;

&lt;p&gt;The biggest win wasn't just the performance, it was how much simpler the client code became. Developers only need to handle a single callback and save two small values for crash recovery if app crashed, if connection dropped it automatically reconnects and resyncs.&lt;/p&gt;

&lt;p&gt;For full client implementation details (Unity + generic clients), check the official docs: &lt;a href="https://turnkit.dev/docs/client-reconnection" rel="noopener noreferrer"&gt;https://turnkit.dev/docs/client-reconnection&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm actively improving TurnKit and feedback is always welcome.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>java</category>
      <category>unity3d</category>
      <category>multiplayer</category>
    </item>
    <item>
      <title>Turn-Based Game Server Options in 2026: An Honest Comparison for Indie Developers</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Wed, 15 Apr 2026 21:32:47 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/turn-based-game-server-options-in-2026-an-honest-comparison-for-indie-developers-4ona</link>
      <guid>https://dev.to/turnkit-dev/turn-based-game-server-options-in-2026-an-honest-comparison-for-indie-developers-4ona</guid>
      <description>&lt;p&gt;Full disclosure: I'm the creator of TurnKit, a purpose-built authoritative relay for turn-based games. I've tried to keep this objective based on real development experience and public pricing (as of April 2026), let me know if I got something wrong or missed something.&lt;/p&gt;

&lt;p&gt;For indie turn-based multiplayer, the core trade-off is development speed versus authority (cheat protection, turn enforcement, hidden data handling). The practical paths break down into four distinct approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom Backend: Build full authority from scratch (e.g., Node.js/Go + database + WebSockets).&lt;/li&gt;
&lt;li&gt;Per-Match Servers: Spawn headless game instances per match (Unity Netcode, Mirror, Godot equivalents).&lt;/li&gt;
&lt;li&gt;Basic Relays: Pure message forwarding (Unity Relay, Photon PUN).&lt;/li&gt;
&lt;li&gt;Authoritative Relay: Lightweight server that enforces turns and filters data without running the full game engine (TurnKit is currently the dedicated implementation in this niche).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;General tools like Nakama or Colyseus require you to implement turn validation, hidden state, and signed results manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F064d7tkhgjwkhb01lamt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F064d7tkhgjwkhb01lamt.png" alt=" " width="768" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pricing comparisson:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtbaw6isdv2ja9lc3c7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtbaw6isdv2ja9lc3c7r.png" alt=" " width="774" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basic relays tend to be the cheapest option at low scale, but they can become less attractive as player numbers grow or when cheating risk increases. An authoritative relay approach adds built-in protections while aiming to keep operational costs and complexity relatively low.&lt;/p&gt;

&lt;p&gt;Like any solution, this approach has limitations. In 1v1 matches, client voting systems can allow griefing where a losing player falsely votes to end the match. Similar risks of collusion exist in games with three or more players. Because of these trust boundaries, this type of lightweight authoritative relay is generally not recommended for games involving real money&lt;/p&gt;

&lt;p&gt;What backend or approach are you using (or considering) for your turn-based game? What has been your biggest pain point so far? Feel free to share in the comments. Did I get any of the pricings wrong or missed some option?&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>gamedev</category>
      <category>networking</category>
    </item>
    <item>
      <title>How to Add a Leaderboard to Your Unity Game for Free (No Monthly Fees)</title>
      <dc:creator>Nenad Nikolić</dc:creator>
      <pubDate>Mon, 09 Mar 2026 13:41:14 +0000</pubDate>
      <link>https://dev.to/turnkit-dev/how-to-add-a-leaderboard-to-your-unity-game-for-free-no-monthly-fees-2828</link>
      <guid>https://dev.to/turnkit-dev/how-to-add-a-leaderboard-to-your-unity-game-for-free-no-monthly-fees-2828</guid>
      <description>&lt;h1&gt;
  
  
  How to Add a Leaderboard to Your Unity Game for Free (No Monthly Fees)
&lt;/h1&gt;

&lt;p&gt;Every game is better with a leaderboard. But the moment you go looking for a leaderboard backend, you hit a wall.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Existing Options
&lt;/h2&gt;

&lt;p&gt;The popular choices all have tradeoffs that don't suit indie developers well.&lt;/p&gt;

&lt;p&gt;**Firebase now has dedicated leaderboard support, but you're still on Google's infrastructure with Google's pricing — and no control over what changes next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LootLocker&lt;/strong&gt; has a genuinely generous free tier and is worth looking at. But you're still trusting a third party with your player data, and you have no control over pricing changes down the road.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nakama&lt;/strong&gt; is open source and free to self-host — but it's a full game server platform. If all you need is a leaderboard, the setup complexity is significant. It's a serious piece of infrastructure built for serious use cases.&lt;/p&gt;

&lt;p&gt;The underlying frustration is simple: a leaderboard is not a complicated thing. It's a sorted list of scores. Why does setting one up take an afternoon?&lt;/p&gt;




&lt;h2&gt;
  
  
  What If You Just Hosted It Yourself?
&lt;/h2&gt;

&lt;p&gt;Self-hosting sounds scary but the actual requirements for a leaderboard backend are modest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small server to run the API&lt;/li&gt;
&lt;li&gt;A PostgreSQL database to store scores&lt;/li&gt;
&lt;li&gt;A way to query rankings efficiently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No real-time sync, no complex state, no websockets. A leaderboard is fundamentally a read-heavy REST API backed by a database with a good index.&lt;/p&gt;

&lt;p&gt;The total hosting cost for this on free infrastructure: &lt;strong&gt;$0/month&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing RankDrop
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Brainzy/rankdrop" rel="noopener noreferrer"&gt;RankDrop&lt;/a&gt; is an open source, self-hosted leaderboard backend built specifically for this use case.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free forever&lt;/strong&gt; — Apache 2.0, no licensing fees, no usage limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production ready&lt;/strong&gt; — caching, connection pooling, atomic writes, health checks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiny footprint&lt;/strong&gt; — GraalVM native image, ~118MB Docker image, ~50ms startup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with any HTTP client&lt;/strong&gt; — Unity, Godot, mobile, web, desktop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple leaderboard types&lt;/strong&gt; — all-time, daily, weekly, monthly with automatic resets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible scoring&lt;/strong&gt; — high score wins, lowest time wins, or cumulative totals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Player moderation&lt;/strong&gt; — ban players, remove individual scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook notifications&lt;/strong&gt; — Discord or Slack alerts when top scores are beaten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You deploy it once, you own it forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Long Does This Actually Take?
&lt;/h2&gt;

&lt;p&gt;Here's an honest comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Deploy&lt;/th&gt;
&lt;th&gt;Unity integration&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manual self-host (this tutorial)&lt;/td&gt;
&lt;td&gt;~20 min&lt;/td&gt;
&lt;td&gt;60 min&lt;/td&gt;
&lt;td&gt;~1.5 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RankDrop Unity Asset&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;td&gt;included&lt;/td&gt;
&lt;td&gt;60 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This tutorial covers the manual path — it's straightforward and completely free. If you'd rather skip the hour entirely, the &lt;a href="https://assetstore.unity.com/packages/tools/integration/rankdrop-leaderboards-in-60-seconds-366688" rel="noopener noreferrer"&gt;RankDrop Unity Asset&lt;/a&gt; handles both deploy and Unity integration in 60 seconds. Or use hosted one with free 20 CCU at &lt;a href="https://turnkit.dev" rel="noopener noreferrer"&gt;turnkit.dev&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying for Free on Koyeb + Aiven
&lt;/h2&gt;

&lt;p&gt;The easiest zero-cost path is &lt;strong&gt;Koyeb&lt;/strong&gt; (free app hosting) + &lt;strong&gt;Aiven&lt;/strong&gt; (free managed PostgreSQL). Both have permanent free tiers — not trials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Create a free PostgreSQL database on Aiven
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://aiven.io" rel="noopener noreferrer"&gt;aiven.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new &lt;strong&gt;PostgreSQL&lt;/strong&gt; service — select the free tier&lt;/li&gt;
&lt;li&gt;Once provisioned, copy the connection string from the service dashboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;defaultdb&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;sslmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;require&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Deploy to Koyeb
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://koyeb.com" rel="noopener noreferrer"&gt;koyeb.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new app → select &lt;strong&gt;Docker&lt;/strong&gt; → use the image &lt;code&gt;ghcr.io/brainzy/rankdrop:latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add your environment variables from &lt;code&gt;.env.example&lt;/code&gt;, substituting the Aiven connection string for the database URL&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Koyeb will give you a public URL like &lt;code&gt;https://your-app.koyeb.app&lt;/code&gt;. That's your leaderboard API endpoint.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Koyeb's free tier sleeps after 60 minutes of inactivity and wakes in about 3 seconds on the next request. For most indie games this is fine — your leaderboard loads on the main menu, which wakes it up before the player notices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3 — Verify it's running
&lt;/h3&gt;

&lt;p&gt;Open your Koyeb URL + &lt;code&gt;/swagger-ui/index.html&lt;/code&gt; in a browser. You'll see the full interactive API docs. Create a leaderboard, submit a test score, query the rankings — all from the browser before writing a single line of Unity code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Calling the API From Unity
&lt;/h2&gt;

&lt;p&gt;RankDrop exposes a standard REST API. From Unity you call it with &lt;code&gt;UnityWebRequest&lt;/code&gt; — the same way you'd call any HTTP endpoint.&lt;/p&gt;

&lt;p&gt;The key endpoints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Submit a score&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api/v1/leaderboards/{slug}/scores&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get top N scores&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /api/v1/leaderboards/{slug}/top?limit=10&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get a player's rank + neighbours&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /api/v1/leaderboards/{slug}/players/{alias}?surrounding=3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top scores + player context in one call&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /api/v1/leaderboards/{slug}/combined&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;combined&lt;/code&gt; endpoint is the one you'll use most — it returns both the top of the leaderboard and the current player's position in a single request, which is exactly what a leaderboard UI needs.&lt;/p&gt;

&lt;p&gt;Full request/response schemas are in the Swagger UI once deployed.&lt;/p&gt;

&lt;p&gt;This is also where the hour goes in the manual path — writing the &lt;code&gt;UnityWebRequest&lt;/code&gt; wrappers, handling errors and timeouts, managing the game key, building a reusable service class. It's not hard, just time consuming. The &lt;a href="https://turnkit.dev" rel="noopener noreferrer"&gt;Unity Asset&lt;/a&gt; ships all of this pre-built.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiple Leaderboard Types
&lt;/h2&gt;

&lt;p&gt;One of the most useful features for games is automatic periodic resets. RankDrop supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All-time&lt;/strong&gt; — scores persist forever&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily&lt;/strong&gt; — resets every day at midnight UTC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly&lt;/strong&gt; — resets every Monday&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly&lt;/strong&gt; — resets on the 1st of each month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can run multiple leaderboards simultaneously — one all-time, one weekly, one for a limited-time event — each with its own slug, scoring strategy, and reset schedule.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scoring Strategies
&lt;/h2&gt;

&lt;p&gt;Three modes out of the box:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best only&lt;/strong&gt; — only the player's highest score is kept. Perfect for high score games.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple entries&lt;/strong&gt; - Store every score submission as separate entries&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cumulative&lt;/strong&gt; — scores add up over time. Perfect for daily challenge points or XP totals.&lt;/p&gt;




&lt;h2&gt;
  
  
  Define more parameters if you want
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Sorting by lowest time or highest value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;min and max scores&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;archive options and many others&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Outgrow the Free Tier
&lt;/h2&gt;

&lt;p&gt;Koyeb + Aiven handles the vast majority of indie games comfortably. If you do hit the free tier limits, the next step is any VPS with Docker — Hetzner, DigitalOcean, Fly.io. RankDrop is a single Docker container and runs anywhere.&lt;/p&gt;

&lt;p&gt;For serious scale, Oracle Cloud's free tier gives you 4 OCPU and 24GB RAM permanently — but most indie games will never need it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;RankDrop is the leaderboard foundation of &lt;strong&gt;&lt;a href="https://turnkit.dev" rel="noopener noreferrer"&gt;TurnKit&lt;/a&gt;&lt;/strong&gt; — a no-code multiplayer backend for turn-based games I'm building, including relay, matchmaking, and player economy. If you're making a card game, board game, or any turn-based multiplayer game, you can use authorative relay for matches and leaderboards automatically scored by the server, try it on &lt;a href="https://turnkit.dev" rel="noopener noreferrer"&gt;turnkit.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The RankDrop repo is at &lt;a href="https://github.com/Brainzy/rankdrop" rel="noopener noreferrer"&gt;github.com/Brainzy/rankdrop&lt;/a&gt;. Stars, issues, and feedback all welcome.&lt;/p&gt;

&lt;p&gt;If you run into any issues deploying, drop a comment below or join the &lt;a href="https://discord.gg/SqMVU5xex3" rel="noopener noreferrer"&gt;TurnKit Discord&lt;/a&gt; — happy to help you get it running.&lt;/p&gt;

&lt;p&gt;If you are interested in other options for leaderboards check out &lt;a href="https://www.turnkit.dev/leaderboard-options" rel="noopener noreferrer"&gt;https://www.turnkit.dev/leaderboard-options&lt;/a&gt; .&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #gamedev #unity #opensource #java&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>opensource</category>
      <category>java</category>
    </item>
  </channel>
</rss>
