The three laws of incremental games
The following are the three laws of incremental games:
- It should have at least one way to generate a resource
- It should have at least one thing to spend that resource
- It should have at least some way to automate the generation of that resource
I consider these to be the essential features that a game must implement in order to be considered an incremental game.
Demo #
Here's an example of a very small game that implements the laws.
Play the game:
Click at least three times.Good! Now try to get your number up to 100. Great job! Can you make it to 1,000?
Total clicks ever: ; Auto Clickers: ( clicks/second)
Source code for the game
<style>
.fadedout {
opacity: 0;
}
.fadein {
opacity: 1;
}
.remove {
display:none;
}
.fadedout, .fadein {
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
-ms-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
/* CSS */
.some-button {
align-items: center;
appearance: none;
background-color: #FCFCFD;
border-radius: 4px;
border-width: 0;
box-shadow: rgba(45, 35, 66, 0.4) 0 2px 4px,rgba(45, 35, 66, 0.3) 0 7px 13px -3px,#D6D6E7 0 -3px 0 inset;
box-sizing: border-box;
color: #36395A;
cursor: pointer;
display: inline-flex;
height: 48px;
font-family: Consolas, monospace;
justify-content: center;
line-height: 1;
list-style: none;
overflow: hidden;
padding-left: 16px;
padding-right: 16px;
position: relative;
text-align: left;
text-decoration: none;
transition: box-shadow .15s,transform .15s;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
white-space: nowrap;
will-change: box-shadow,transform;
font-size: 18px;
}
.some-button:focus {
box-shadow: #D6D6E7 0 0 0 1.5px inset, rgba(45, 35, 66, 0.4) 0 2px 4px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset;
}
.some-button:hover {
box-shadow: rgba(45, 35, 66, 0.4) 0 4px 8px, rgba(45, 35, 66, 0.3) 0 7px 13px -3px, #D6D6E7 0 -3px 0 inset;
transform: translateY(-2px);
}
.some-button:active {
box-shadow: #D6D6E7 0 3px 7px inset;
transform: translateY(2px);
}
.gamecontainer {
display:flex;
flex-direction: column;
align-content: stretch;
}
</style>
<div class="gamecontainer">
<p><button id="somebutton" class="some-button">Add <span id="increment-value"></span> to this → <span id="somecounter">0</span></button> <button id="someupgrade-button" class="some-button fadedout">Spend <span id="upgrade-cost"></span> for +1/click</button> <button id="buyAutoClickerButtonElement" class="some-button fadedout">Spend <span id="buyAutoClickerButtonUpgradeCostElement"></span> for +1 click/second</button></p>
<p id="helpTextContainer"><span id="helptext1">Click at least three times.</span><span id="helptext2" class="fadedout">Good! Now try to get your number up to 100.</span><span id="helptext3" class="fadedout"> Great job! Can you make it to 1,000?</span><div id="autoClickersStatsContainer" class="fadedout">Total clicks ever: <span id="totalClicksEver"></span>; Auto Clickers: <span id="numAutoClickers"></span> (<span id="autoClickerCPS"></span> clicks/second)</div></p>
</div>
<script type="text/javascript">
var totalClicksEver = 0;
var e1value = 0;
var incrementValue = 1;
var upgradeCost = 1;
var buyAutoClickerButtonUpgradeCost = 100;
const setInterval2 = (fn,time) => {
// A place to store the timeout Id (later)
let timeout = null;
// calculate the time of the next execution
let nextAt = Date.now() + time;
const wrapper = () => {
// calculate the time of the next execution:
nextAt += time;
// set a timeout for the next execution time:
timeout = setTimeout(wrapper, nextAt - Date.now());
// execute the function:
return fn();
};
// Set the first timeout, kicking off the recursion:
timeout = setTimeout(wrapper, nextAt - Date.now());
// A way to stop all future executions:
const cancel = () => clearTimeout(timeout);
// Return an object with a way to halt executions:
return { cancel };
};
const generators =
{
autoClickers: {
name_singular: 'Auto Clicker',
name_plural: 'Auto Clickers',
numPurchased: 0,
upgradeCost: 100,
unlocked: true,
purchased: false, // Really just lets us know the first one has been purchased
unlock: function() {
unlocked = true;
},
canAfford: function() { return this.upgradeCost <= e1value },
buyOne: function() {
if(!this.purchased) { this.purchased = true; }
e1value -= this.upgradeCost;
this.numPurchased += 1;
this.upgradeCost += 115;
},
update: function() { // Called once every second IF purchased
handleBulkClicks(this.numPurchased);
}
}
}
const triggers = [
{ // Show some help text to the user at the beginning
fired: false,
condition: function() { return e1value >= 100 },
event: function() {
fadeOut('helptext2');
fadeOut('helptext2');
fadeIn('helptext3');
}
},
{ // Show the auto clickers count label
fired: false,
condition: function() { return generators.autoClickers.numPurchased > 0 },
event: function() {
fadeIn('autoClickersStatsContainer');
fadeOut('helpTextContainer');
}
},
{
fired: false,
condition: function() { return e1value >= 3 },
event: function() {
fadeIn('someupgrade-button');
fadeOut('helptext1');
fadeIn('helptext2');
}
},
{
fired: false,
condition: function() { return upgradeCost >= 5 },
event: function() {
generators.autoClickers.unlocked = true;
fadeIn('buyAutoClickerButtonElement');
}
}
]
var e1valueElement = document.getElementById('somecounter');
var upgradeCostElement = document.getElementById('upgrade-cost');
var incrementValueElement = document.getElementById('increment-value')
var buyAutoClickerButtonUpgradeCostElement = document.getElementById('buyAutoClickerButtonUpgradeCostElement')
function fadeIn(someElementId) {
document.getElementById(someElementId).classList.add('fadein');
}
function fadeOut(someElementId) {
document.getElementById(someElementId).classList.add('fadedout');
document.getElementById(someElementId).classList.add('remove');
document.getElementById(someElementId).classList.remove('fadein');
}
function handleClick() {
totalClicksEver += incrementValue;
e1value += incrementValue;
e1valueElement.textContent = parseInt(e1value);
updateAll();
}
function handleBulkClicks(clicks) {
totalClicksEver += (incrementValue * clicks);
e1value += (incrementValue * clicks);
e1valueElement.textContent = parseInt(e1value);
updateAll();
}
function handleBuy() {
e1value -= upgradeCost;
upgradeCost += Math.floor(incrementValue / 2);
incrementValue += 1;
updateAll();
}
function buyAutoClicker() {
generators.autoClickers.buyOne();
updateAll();
}
function updateAll() {
updatePrices();
checkUnlocks();
updateButtonClickability();
}
function updateGenerators() {
if(generators.autoClickers.purchased) { generators.autoClickers.update()}
}
function updatePrices() { // Should be "updatePricesAndStats"
e1valueElement.textContent = parseInt(e1value);
upgradeCostElement.textContent = parseInt(upgradeCost);
incrementValueElement.textContent = parseInt(incrementValue);
buyAutoClickerButtonUpgradeCostElement.textContent = generators.autoClickers.upgradeCost;
document.getElementById('totalClicksEver').textContent = parseInt(totalClicksEver);
document.getElementById('numAutoClickers').textContent = parseInt(generators.autoClickers.numPurchased);
document.getElementById('autoClickerCPS').textContent = parseInt(generators.autoClickers.numPurchased*incrementValue);
}
function a() {
for(let a =0; a<100; a++) {
handleClick();
}
}
function checkUnlocks() {
triggers.forEach( (t) => {
if(t.condition()) {
t.fired = true;
t.event();
}
});
}
function updateButtonClickability() {
document.getElementById('someupgrade-button').disabled = (e1value <= upgradeCost) ? true : false;
document.getElementById('buyAutoClickerButtonElement').disabled = (e1value <= generators.autoClickers.upgradeCost) ? true : false;
}
document.getElementById('somebutton').addEventListener('click', handleClick, false);
document.getElementById('someupgrade-button').addEventListener('click', handleBuy, false);
document.getElementById('buyAutoClickerButtonElement').addEventListener('click', buyAutoClicker, false);
updatePrices();
setInterval2(updateGenerators,1000);
</script>
<noscript>
You'll need to enable JavaScript and use a browser that supports it to play this demo.
</noscript>