<?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: SnowRSPS</title>
    <description>The latest articles on DEV Community by SnowRSPS (@snowrsps).</description>
    <link>https://dev.to/snowrsps</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%2F4216359%2F7dca37a2-4159-4ad4-9c5c-3d2b66fb324b.png</url>
      <title>DEV Community: SnowRSPS</title>
      <link>https://dev.to/snowrsps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/snowrsps"/>
    <language>en</language>
    <item>
      <title>Designing an idle RPG economy in vanilla JS: lessons from Snow317.io</title>
      <dc:creator>SnowRSPS</dc:creator>
      <pubDate>Fri, 30 Oct 2026 06:15:31 +0000</pubDate>
      <link>https://dev.to/snowrsps/designing-an-idle-rpg-economy-in-vanilla-js-lessons-from-snow317io-39o4</link>
      <guid>https://dev.to/snowrsps/designing-an-idle-rpg-economy-in-vanilla-js-lessons-from-snow317io-39o4</guid>
      <description>&lt;p&gt;Snow317.io is a small 8-bit clicker RPG we're building in plain JavaScript: no framework, no engine, one page. You click ATTACK, monsters drop coins, and coins buy weapons, armour and pets. Pets deal damage for you. It's a spin-off of SnowRSPS, a custom game server we've run since 2012.&lt;/p&gt;

&lt;p&gt;The code is easy. The &lt;strong&gt;economy&lt;/strong&gt; is the hard part. Here's what we've learned so far.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pick your growth curves before you write content
&lt;/h2&gt;

&lt;p&gt;Every incremental game comes down to two curves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Income growth&lt;/strong&gt;: how fast coins per second rise&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost growth&lt;/strong&gt;: how fast prices rise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If costs grow faster than income, the player hits a wall. If income grows faster, the game is "solved" in an afternoon. The usual pattern is exponential costs and multiplicative income:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;owned&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tune &lt;code&gt;rate&lt;/code&gt; per item category. Pets (passive DPS) should get steeper curves than weapons (which only help when you click), or idle play makes clicking pointless.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tiny numbers need a plan
&lt;/h2&gt;

&lt;p&gt;Auto Attack in Snow317.io unlocks at &lt;strong&gt;0.0001 coins&lt;/strong&gt;, so the first purchase is practically free. That's deliberate: it teaches "the game plays itself" within seconds. But tiny decimals in plain JS floats mean display bugs, like &lt;code&gt;0.30000000000000004&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What helped us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the internal value raw, and &lt;strong&gt;format only at render time&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Use one formatter for every number on screen (decimals at the start, suffixes like K/M later).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;notation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Idle needs a manual counterweight
&lt;/h2&gt;

&lt;p&gt;If pets do everything, the player stops coming back. We kept a few reasons to click:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Critical hits&lt;/strong&gt; on manual attacks&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Heal&lt;/strong&gt; button, because HP matters and the monster hits back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waves and Cycles&lt;/strong&gt; as visible milestones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule of thumb: active play should earn noticeably more per minute than idle, but idle should never earn zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Resets are part of the economy
&lt;/h2&gt;

&lt;p&gt;Our reset costs you your coins &lt;strong&gt;and half your pets&lt;/strong&gt;. A reset is an economic sink: it removes value so prices make sense again. If it's too soft, nothing matters. If it's too harsh, players quit. We're still tuning it, and it's the number we argue about most.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Save to localStorage, defensively
&lt;/h2&gt;

&lt;p&gt;No accounts means localStorage is the save file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s317&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* quota or private mode: fail quietly */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s317&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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;Version your save format from day one. Your economy &lt;strong&gt;will&lt;/strong&gt; change, and old saves need a migration path, not a crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Drop tables are an economy too
&lt;/h2&gt;

&lt;p&gt;Each monster in Snow317.io has its own drops popup. Showing players the odds makes the grind feel fair, which is a lesson we took straight from running the full server: players accept grind when they can see the math.&lt;/p&gt;

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

&lt;p&gt;Music, a gentler early cost curve, and original art. If you've shipped an incremental game, how did you decide your cost multiplier? Was it spreadsheets, or vibes and playtesting?&lt;/p&gt;

&lt;p&gt;Try the work-in-progress build at &lt;strong&gt;snowrsps.com/io&lt;/strong&gt; (free, no sign-up). If you like the custom-loot flavour, the full SnowRSPS server is at snowrsps.com.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>gamedev</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How we bundled Java 8 into a one-click Windows zip for our game</title>
      <dc:creator>SnowRSPS</dc:creator>
      <pubDate>Fri, 30 Oct 2026 03:53:06 +0000</pubDate>
      <link>https://dev.to/snowrsps/how-we-bundled-java-8-into-a-one-click-windows-zip-for-our-game-2j02</link>
      <guid>https://dev.to/snowrsps/how-we-bundled-java-8-into-a-one-click-windows-zip-for-our-game-2j02</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclosure: I'm Snow, and I run SnowRSPS, a free custom 317 game server online since 2012.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our game client is a Java 8 jar. For years the #1 support question was "it won't open." Players had no Java, the wrong Java, or a 32-bit PC. Here's what we shipped instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SnowRSPS/
  Play SnowRSPS.bat   &amp;lt;- the only thing players click
  SnowRSPS.jar
  java64/             &amp;lt;- Java 8 runtime for 64-bit Windows 7/8/10/11
  java32/             &amp;lt;- Java 8 runtime for old 32-bit Windows
  README-EN.txt
  README-CN.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What the launcher does
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Picks the right runtime.&lt;/strong&gt; It checks whether Windows is 64-bit and uses &lt;code&gt;java64&lt;/code&gt;, otherwise &lt;code&gt;java32&lt;/code&gt;. No system Java needed, no PATH changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survives odd folder names.&lt;/strong&gt; Many of our players unzip to folders with spaces or Chinese characters, so every path is quoted and the script works from its own directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries with less memory.&lt;/strong&gt; If the JVM can't reserve the heap on a weak PC, it relaunches with a smaller &lt;code&gt;-Xmx&lt;/code&gt; instead of failing silently.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why a zip and not an exe
&lt;/h2&gt;

&lt;p&gt;Unsigned &lt;code&gt;.exe&lt;/code&gt; downloads get blocked more often by browsers. A zip with a .bat inside goes through more easily. Players still see Windows "protected your PC"; the README says: &lt;em&gt;More info → Run anyway&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Compile with compliance &lt;strong&gt;1.8&lt;/strong&gt; so the bundled runtime can run it.&lt;/li&gt;
&lt;li&gt;Eclipse → Export → Runnable JAR → "Package required libraries into generated JAR".&lt;/li&gt;
&lt;li&gt;Ship two READMEs (English + Simplified Chinese). It cut down our support questions a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Bundle the runtime, make the first click obvious, and add a fallback for weak machines.&lt;/p&gt;

&lt;p&gt;If you want to see it in action: &lt;a href="https://snowrsps.com/play/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=snowrsps-promo" rel="noopener noreferrer"&gt;download the SnowRSPS client&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>windows</category>
      <category>gamedev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
