final beta
This commit is contained in:
parent
5e2755faec
commit
0949e23199
@ -1,7 +1,4 @@
|
||||
# Vue 3 + Vite
|
||||
# Clicker42
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
This is the source code for https://clicker.burble.dn42
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
12
index.html
12
index.html
@ -2,12 +2,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
<title>Clicker42</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<nav class="navbar bg-dark mb-1 p-0">
|
||||
<span class="navbar-brand ms-3 p-2 text-light fs-2">Clicker42</span>
|
||||
<span class="navbar-text p-0"><img src="/dn42.svg" height="60" class="p-0" style="width: 300px; object-fit: cover" alt="dn42"/></span>
|
||||
</nav>
|
||||
<div id="clicker42"></div>
|
||||
<script type="module" src="/src/clicker42.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -9,6 +9,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.2.3",
|
||||
"bootstrap-icons": "^1.10.3",
|
||||
"pinia": "^2.0.33",
|
||||
"vue": "^3.2.47"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
30
public/dn42.svg
Normal file
30
public/dn42.svg
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 595.3 841.9" style="enable-background:new 0 0 595.3 841.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#3C3C3B;}
|
||||
.st1{fill:#575756;}
|
||||
.st2{fill:#706F6F;}
|
||||
.st3{fill:#9D9D9C;}
|
||||
.st4{fill:#878787;}
|
||||
</style>
|
||||
<polygon points="358.4,408.2 326.2,408.2 310.1,436.1 326.2,464 358.4,464 374.5,436.1 "/>
|
||||
<polygon class="st0" points="408.7,436.7 376.5,436.7 360.4,464.6 376.5,492.5 408.7,492.5 424.8,464.6 "/>
|
||||
<polygon class="st1" points="408.7,379 376.5,379 360.4,406.9 376.5,434.8 408.7,434.8 424.8,406.9 "/>
|
||||
<polygon class="st2" points="458.8,408.2 426.6,408.2 410.5,436.1 426.6,464 458.8,464 474.9,436.1 "/>
|
||||
<polygon class="st3" points="509.7,436.7 477.5,436.7 461.4,464.6 477.5,492.5 509.7,492.5 525.8,464.6 "/>
|
||||
<polygon class="st4" points="458.8,350.2 426.6,350.2 410.5,378.1 426.6,405.9 458.8,405.9 474.9,378.1 "/>
|
||||
<g>
|
||||
<path d="M106.7,382.1h10.8v82.8H94.5c-8.1,0-14.5-2.5-19.3-7.6S68,445.3,68,436.8c0-8,2.5-14.6,7.6-19.8c5-5.2,11.5-7.8,19.3-7.8
|
||||
c3.6,0,7.6,0.8,11.9,2.3V382.1z M106.7,455.7v-34.6c-3.4-1.7-6.8-2.5-10.2-2.5c-5.4,0-9.7,1.8-12.8,5.3c-3.2,3.5-4.8,8.3-4.8,14.2
|
||||
c0,5.6,1.4,9.9,4.1,13c1.7,1.8,3.4,3,5.3,3.7c1.9,0.6,5.2,0.9,10,0.9H106.7z"/>
|
||||
<path d="M143,410.4v6.9c4.8-5.3,10.3-8,16.4-8c3.4,0,6.6,0.9,9.5,2.6c2.9,1.8,5.1,4.2,6.7,7.2c1.5,3.1,2.3,7.9,2.3,14.5v31.2H167
|
||||
v-31.1c0-5.6-0.9-9.6-2.5-11.9c-1.7-2.4-4.5-3.6-8.5-3.6c-5.1,0-9.4,2.5-13,7.6v38.9h-11v-54.5H143z"/>
|
||||
<path d="M228.3,381h4.9v46.5h9.2v10.1h-9.2v27.3h-11.6v-27.3h-35v-5.1L228.3,381z M221.6,427.5v-24l-19.2,24H221.6z"/>
|
||||
<path d="M270.9,453.5h31.5v11.4h-52.4v-0.8l5-5.9c7.8-9.6,14-17.8,18.6-24.5c4.6-6.7,7.6-11.8,9-15.2c1.4-3.4,2.1-6.8,2.1-10.2
|
||||
c0-4.7-1.3-8.4-4-11.2c-2.6-2.8-6.2-4.2-10.5-4.2c-3.3,0-6.6,1-9.8,2.9c-3.2,2-6.1,4.7-8.7,8.3v-15.1c6.4-5.3,13.1-7.9,19.9-7.9
|
||||
c7.2,0,13.2,2.4,17.9,7.2c4.7,4.8,7,10.9,7,18.4c0,3.3-0.6,6.9-1.7,10.6c-1.2,3.8-3.2,8.1-6.2,13c-3,4.9-8,11.6-15.1,20.1
|
||||
L270.9,453.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
Before Width: | Height: | Size: 1.5 KiB |
30
src/App.vue
30
src/App.vue
@ -1,30 +0,0 @@
|
||||
<script setup>
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
|
||||
</a>
|
||||
</div>
|
||||
<HelloWorld msg="Vite + Vue" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
</style>
|
63
src/Clicker42.vue
Normal file
63
src/Clicker42.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import LevelPanel from './components/LevelPanel.vue'
|
||||
import BWPanel from './components/BWPanel.vue'
|
||||
import ServicesPanel from './components/ServicesPanel.vue'
|
||||
import SettingsPanel from './components/SettingsPanel.vue'
|
||||
import UpgradeIcon from './components/UpgradeIcon.vue'
|
||||
import Elapsed from './components/Elapsed.vue'
|
||||
import { useState } from './model/state.js'
|
||||
import { nstr } from './model/numbers';
|
||||
const state = useState()
|
||||
|
||||
// navigation tab
|
||||
const tab = ref("levels")
|
||||
|
||||
// install timer tick
|
||||
let timerID
|
||||
onMounted(() => {
|
||||
state.restore()
|
||||
timerID = setInterval(() => state.tick(state, 1), 1000)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval(timerID)
|
||||
})
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-3 py-0">
|
||||
<div class="row p-0 m-1">
|
||||
<div class="col" v-html="state.ui.notice"></div>
|
||||
<div class="col-md-auto text-nowrap"><Elapsed></Elapsed></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" :class="{ active: tab == 'levels' }" @click="tab = 'levels'">
|
||||
<UpgradeIcon :active="false"></UpgradeIcon> Network {{ nstr(state.levels[0].count) }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" :class="{ active: tab == 'bw' }" @click="tab = 'bw'">
|
||||
<UpgradeIcon :active="state.bandwidth.canUpgrade(state)"></UpgradeIcon> Bandwidth {{ nstr(state.bandwidth.count) }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" :class="{ active: tab == 'services' }" @click="tab = 'services'">
|
||||
<UpgradeIcon :active="false"></UpgradeIcon> Services
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" :class="{ active: tab == 'settings' }" @click="tab = 'settings'"><i>Settings</i></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<LevelPanel v-if="tab == 'levels'"></LevelPanel>
|
||||
<BWPanel v-if="tab == 'bw'"></BWPanel>
|
||||
<ServicesPanel v-if="tab == 'services'"></ServicesPanel>
|
||||
<SettingsPanel v-if="tab == 'settings'"></SettingsPanel>
|
||||
</div></template>
|
||||
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
Before Width: | Height: | Size: 496 B |
20
src/clicker42.js
Normal file
20
src/clicker42.js
Normal file
@ -0,0 +1,20 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||
import Clicker42 from './Clicker42.vue'
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const pinia = createPinia()
|
||||
const clicker = createApp(Clicker42)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
clicker.use(pinia)
|
||||
clicker.mount('#clicker42')
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
27
src/components/BWInfo.vue
Normal file
27
src/components/BWInfo.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { computed } from 'vue';
|
||||
import { useState } from '../model/state.js'
|
||||
import { nstr } from '../model/numbers.js'
|
||||
import UpgradeBuyer from './UpgradeBuyer.vue'
|
||||
|
||||
const state = useState()
|
||||
const bandwidth = computed(() => state.bandwidth)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-5">
|
||||
<h1>Bandwidth Stats</h1>
|
||||
<p>You have {{ nstr(bandwidth.count) }}b available.</p>
|
||||
<p>Your current bandwidth rate is {{ nstr(bandwidth.rate) }}b/second
|
||||
(+{{ nstr(bandwidth.serviceBonus(state)) }}b/second bonus).</p>
|
||||
<hr />
|
||||
<h2>Upgrades</h2>
|
||||
<h3 class="pt-3">Upstream Upgrade</h3>
|
||||
<p>Improving your upstream links increases your bandwidth rate.</p>
|
||||
<UpgradeBuyer v-bind:cost="bandwidth.rateCost" v-bind:available="state.levels[0].count"
|
||||
v-bind:units="state.levels[0].units" @buy="bandwidth.upgrade(state)"></UpgradeBuyer>
|
||||
</div>
|
||||
</template>
|
14
src/components/BWPanel.vue
Normal file
14
src/components/BWPanel.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import BWInfo from './BWInfo.vue'
|
||||
import { useState } from '../model/state.js'
|
||||
const state = useState()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<BWInfo></BWInfo>
|
||||
</div>
|
||||
</template>
|
32
src/components/Buyer.vue
Normal file
32
src/components/Buyer.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { nstr } from '../model/numbers.js'
|
||||
|
||||
const props = defineProps(['max'])
|
||||
const emits = defineEmits(['buy'])
|
||||
|
||||
function third() { return (props.max / 3n) }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container text-center">
|
||||
<div class="row border border-secondary p-0" v-if="max == 0">
|
||||
<div class="col text-secondary p-1">
|
||||
Can't add
|
||||
</div>
|
||||
</div>
|
||||
<div class="row border border-dark p-0" v-else>
|
||||
<div class="col border p-1" @click="$emit('buy', BigInt(1))">
|
||||
Add 1
|
||||
</div>
|
||||
<div class="col border p-1" @click="$emit('buy', third())" v-if="third() > 1">
|
||||
Add {{ nstr(third()) }}
|
||||
</div>
|
||||
<div class="col border p-1" @click="$emit('buy', max)" v-if="max > 1">
|
||||
Add {{ nstr(max) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
36
src/components/Elapsed.vue
Normal file
36
src/components/Elapsed.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { computed } from 'vue';
|
||||
import { useState } from '../model/state.js'
|
||||
const state = useState()
|
||||
|
||||
const seconds = computed(() => {
|
||||
return state.ui.elapsed % 60
|
||||
})
|
||||
|
||||
const minutes = computed(() => {
|
||||
return Math.floor(state.ui.elapsed / 60) % 60
|
||||
})
|
||||
|
||||
const hours = computed(() => {
|
||||
return Math.floor(state.ui.elapsed / 3600)
|
||||
})
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<span v-if="state.ui.elapsed >= 3600">
|
||||
{{ hours }}h
|
||||
</span>
|
||||
<span v-if="state.ui.elapsed >= 60">
|
||||
{{ minutes }}m
|
||||
</span>
|
||||
{{ seconds }}s wasted
|
||||
<span class="p-2">
|
||||
<i class="bi bi-box-arrow-down text-success" v-if="state.ui.save"></i>
|
||||
<span style="padding-left: 12px" v-else> </span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
@ -1,40 +0,0 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Install
|
||||
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
|
||||
in your IDE for a better DX
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
53
src/components/LevelInfo.vue
Normal file
53
src/components/LevelInfo.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { computed } from 'vue';
|
||||
import { useState } from '../model/state.js'
|
||||
import { nstr } from '../model/numbers.js'
|
||||
import Buyer from './Buyer.vue'
|
||||
import UpgradeBuyer from './UpgradeBuyer.vue'
|
||||
|
||||
const state = useState()
|
||||
const data = computed(() => state.levels[state.ui.selectedLevel])
|
||||
const previous = computed(() => state.levels[state.ui.selectedLevel - 1])
|
||||
const next = computed(() => state.levels[state.ui.selectedLevel + 1])
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>{{ data.name }}</h1>
|
||||
<p class="pb-2">{{ data.blurb }}</p>
|
||||
<p class="m-0">You have {{ nstr(data.count) }} {{ data.units }}</p>
|
||||
<div v-if="state.ui.selectedLevel != 0">
|
||||
<p class="m-0">Each one costs {{ nstr(data.cost) }} {{ previous.units }}
|
||||
and produces {{ nstr(data.multiplier) }} {{ previous.units }} per second.</p>
|
||||
<p class="m-0">In total your {{ data.units }} produce
|
||||
{{ nstr(data.multiplier * data.count) }} {{ previous.units }} per second.</p>
|
||||
</div>
|
||||
<p v-if="state.ui.selectedLevel != (state.count - 1)">You earn
|
||||
{{ nstr(next.multiplier * next.count) }} {{ data.units }} per second.</p>
|
||||
<hr />
|
||||
<p>You can buy {{ nstr(data.efficiency*(state.bandwidth.rate + state.bandwidth.serviceBonus(state))) }} {{ data.units }} per second.</p>
|
||||
<p class="m-0">Adding the maximum of {{ nstr(data.maxCanBuy(state)) }} {{ data.units }} will cost:</p>
|
||||
<ul class="pb-2 ps-4">
|
||||
<li v-if="state.ui.selectedLevel != 0">{{ data.calcUnitCostPercent(state) }}% of your {{ previous.units }}</li>
|
||||
<li>{{ data.calcBWCostPercent(state) }}% of your available bandwidth.</li>
|
||||
</ul>
|
||||
<Buyer :max="data.maxCanBuy(state)" @buy="(amount) => data.buy(state, amount)" />
|
||||
<hr />
|
||||
<h2>Upgrades</h2>
|
||||
<template v-if="(state.ui.selectedLevel != 0)">
|
||||
<h3 class="pt-3">Power Up</h3>
|
||||
<p>Each power up doubles the number of {{ previous.units }} created every second</p>
|
||||
<UpgradeBuyer :cost="data.multiplierCost" :available="data.count"
|
||||
:units="data.units" @buy="data.upgradeMultiplier()"></UpgradeBuyer>
|
||||
</template>
|
||||
<template v-if="(state.ui.selectedLevel != (state.count - 1)) && next.count >= 1">
|
||||
<h3 class="pt-3">Bandwidth Efficiency</h3>
|
||||
<p>Upgrading bandwidth efficiency doubles the number of {{ data.units }} per unit of bandwidth</p>
|
||||
<UpgradeBuyer :cost="data.efficiencyCost" :available="next.count"
|
||||
:units="next.units" @buy="data.upgradeEfficiency(state)"></UpgradeBuyer>
|
||||
</template>
|
||||
</div></template>
|
||||
|
23
src/components/LevelPanel.vue
Normal file
23
src/components/LevelPanel.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { ref } from 'vue'
|
||||
import LevelSelector from './LevelSelector.vue'
|
||||
import LevelInfo from './LevelInfo.vue'
|
||||
import { useState } from '../model/state.js'
|
||||
const state = useState()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container p-2">
|
||||
<div class="row justify-content-start">
|
||||
<div class="col-md-auto p-0">
|
||||
<LevelSelector></LevelSelector>
|
||||
</div>
|
||||
<div class="col p-0 ps-2">
|
||||
<LevelInfo></LevelInfo>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
44
src/components/LevelSelector.vue
Normal file
44
src/components/LevelSelector.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { useState } from '../model/state.js'
|
||||
import { nstr } from '../model/numbers.js'
|
||||
import UpgradeIcon from './UpgradeIcon.vue';
|
||||
|
||||
const emits = defineEmits(['select-level'])
|
||||
const state = useState()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// is 'l' the selected level ?
|
||||
function isSelected(l) { return state.ui.selectedLevel == l }
|
||||
|
||||
// return the level indices in reverse order
|
||||
function reverseLevels() {
|
||||
var count = state.count
|
||||
return Array.from({ length: count }, (x, i) => count - i - 1)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table>
|
||||
<template v-for="l in reverseLevels()">
|
||||
<tr class="py-1 px-2 m-0 text-primary" :class="{ 'bg-light': isSelected(l),
|
||||
'border-bottom': isSelected(l), 'text-dark': isSelected(l) }"
|
||||
v-if="state.levels[l].unlocked"
|
||||
@click="state.ui.selectedLevel = l">
|
||||
<td class="text-center p-1">
|
||||
<UpgradeIcon :active="(l != 0) && state.levels[l].canUpgrade()"></UpgradeIcon>
|
||||
</td>
|
||||
<td class="text-nowrap text-start p-1">
|
||||
{{ state.levels[l].name }}
|
||||
</td>
|
||||
<td class="text-nowrap text-end p-1">
|
||||
{{ nstr(state.levels[l].count) }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
|
57
src/components/ServicesPanel.vue
Normal file
57
src/components/ServicesPanel.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { computed } from 'vue';
|
||||
import { useState } from '../model/state.js'
|
||||
import { nstr } from '../model/numbers.js'
|
||||
import UpgradeBuyer from './UpgradeBuyer.vue'
|
||||
|
||||
const state = useState()
|
||||
const time = computed(() => state.services.time)
|
||||
const auto = computed(() => state.services.auto)
|
||||
const anyUnlocked = computed(() => {
|
||||
return (state.services.time.unlocked || state.services.auto.unlocked)
|
||||
})
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-5">
|
||||
<h1>Services</h1>
|
||||
<p>Gain upgrades by introducing new services.</p>
|
||||
<template v-if="anyUnlocked">
|
||||
<div v-if="time.unlocked">
|
||||
<h2 class="mt-3">Time Control</h2>
|
||||
<p v-if="time.level >= 2">You have purchased all the time based upgrades.</p>
|
||||
<template v-else>
|
||||
<p>Install an NTP service to get time based upgrades.</p>
|
||||
<p class="m-0" v-if="time.level == 0">Accurate clocks add bonus bandwidth the longer you've been in a level.</p>
|
||||
<p class="m-0" v-if="time.level == 1">Precision timekeeping adds bandwidth based on time wasted.</p>
|
||||
<UpgradeBuyer :cost="state.services.tCost()" :available="state.services.tValue(state)"
|
||||
:units="state.services.tUnits(state)" @buy="state.services.tUpgrade(state)"></UpgradeBuyer>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="auto.unlocked">
|
||||
<h2 class="mt-3">Automation</h2>
|
||||
<p v-if="auto.level >= 2">You have purchased all the automation upgrades.</p>
|
||||
<template v-else>
|
||||
<p>Install ansible to automate your nodes.</p>
|
||||
<p class="m-0" v-if="auto.level == 0">Add runbooks to manage bandwidth.</p>
|
||||
<p class="m-0" v-if="auto.level == 1">Node configuration scripts to manage your network.</p>
|
||||
<UpgradeBuyer :cost="state.services.aCost()" :available="state.services.aValue(state)"
|
||||
:units="state.services.aUnits(state)" @buy="state.services.aUpgrade(state)"></UpgradeBuyer>
|
||||
</template>
|
||||
<p class="mt-3" v-if="auto.level > 0">Configure autobuyers:</p>
|
||||
<p class="m-0 ps-4" v-if="auto.level > 0"><input class="form-check-input me-2"
|
||||
type="checkbox" id="enablebwauto" v-model="state.autobuyer.bandwidth"><label class="form-check-label"
|
||||
for="enablebwauto">Enable bandwidth autobuyer</label></p>
|
||||
<p class="m-0 ps-4" v-if="auto.level > 1"><input class="form-check-input me-2"
|
||||
type="checkbox" id="enablelevelauto" v-model="state.autobuyer.levels"><label class="form-check-label"
|
||||
for="enablelevelauto">Enable network autobuyer</label></p>
|
||||
</div>
|
||||
</template>
|
||||
<p v-else>
|
||||
No services are currently available, come back later for more.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
21
src/components/SettingsPanel.vue
Normal file
21
src/components/SettingsPanel.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { useState } from '../model/state.js'
|
||||
const state = useState()
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<h1>Settings</h1>
|
||||
<hr />
|
||||
<div>
|
||||
<h5>Clear all settings and reset</h5>
|
||||
<p class="text-danger"><b>*DANGER*</b> This will clear all progress and data! <b>*DANGER*</b></p>
|
||||
<button type="button" class="btn btn-danger p-3" @click="state.reset()"><i
|
||||
class="bi bi-exclamation-triangle-fill"></i> Reset</button>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</template>
|
25
src/components/UpgradeBuyer.vue
Normal file
25
src/components/UpgradeBuyer.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
import { nstr } from '../model/numbers.js'
|
||||
|
||||
const props = defineProps(['cost', 'available', 'units'])
|
||||
const emits = defineEmits(['buy'])
|
||||
|
||||
function completed() {
|
||||
if (props.available > props.cost) { return 100 }
|
||||
return Number((props.available * 100n) / props.cost)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p>Next upgrade costs {{ nstr(cost) }} {{ units }}</p>
|
||||
<div class="progress" style="height: 32px" v-if="available < cost">
|
||||
<div class="progress-bar" role="progressbar" :style="{ 'width': completed() + '%' }" :aria-valuenow="completed()"
|
||||
aria-valuemin="0" aria-valuemax="100">{{ completed() }}%</div>
|
||||
</div>
|
||||
<div class="text-center text-light rounded bg-success" style="height: 32px" @click="$emit('buy')" v-else>
|
||||
<span class="align-middle">Buy</span>
|
||||
</div>
|
||||
</template>
|
12
src/components/UpgradeIcon.vue
Normal file
12
src/components/UpgradeIcon.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup>
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const props = defineProps(['active'])
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<i class="bi bi-wifi" v-if="active == true"></i>
|
||||
<span style="padding-left: 12px" v-else> </span>
|
||||
</template>
|
@ -1,5 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
42
src/model/autobuyer.js
Normal file
42
src/model/autobuyer.js
Normal file
@ -0,0 +1,42 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// level functions
|
||||
|
||||
export class Autobuyer {
|
||||
constructor() {
|
||||
this.levels = false
|
||||
this.bandwidth = false
|
||||
}
|
||||
|
||||
// save data
|
||||
save() {
|
||||
return {
|
||||
levels: this.levels,
|
||||
bandwidth: this.bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
restore(save_data) {
|
||||
Object.assign(this, save_data)
|
||||
}
|
||||
|
||||
// timer tick
|
||||
tick(state, interval) {
|
||||
|
||||
if (this.levels) {
|
||||
state.levels.forEach(l => {
|
||||
if (l.shouldAutobuy()) { l.upgradeMultiplier() }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.bandwidth) {
|
||||
|
||||
if (state.bandwidth.canUpgrade(state)) {
|
||||
state.bandwidth.upgrade(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
81
src/model/bandwidth.js
Normal file
81
src/model/bandwidth.js
Normal file
@ -0,0 +1,81 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Bandwidth functions
|
||||
|
||||
export class Bandwidth {
|
||||
|
||||
constructor() {
|
||||
this.count = BigInt(0)
|
||||
this.rate = BigInt(1)
|
||||
this.rateCost = BigInt(321)
|
||||
}
|
||||
|
||||
// save data
|
||||
save() {
|
||||
let save_data = {}
|
||||
let props = [
|
||||
'count',
|
||||
'rate', 'rateCost'
|
||||
]
|
||||
|
||||
props.forEach((prop) => {
|
||||
save_data[prop] = this[prop]
|
||||
})
|
||||
return save_data
|
||||
}
|
||||
|
||||
restore(save_data) {
|
||||
Object.assign(this, save_data)
|
||||
}
|
||||
|
||||
// handle upgrades
|
||||
canUpgrade(state) {
|
||||
return this.rateCost <= state.levels[0].count
|
||||
}
|
||||
|
||||
upgrade(state) {
|
||||
// use up packets
|
||||
state.levels[0].count -= this.rateCost
|
||||
|
||||
// increase bandwidth rate and cost
|
||||
if (this.rate < 10) {
|
||||
this.rate += 1n
|
||||
this.rateCost *= 2n
|
||||
}
|
||||
else {
|
||||
this.rate *= 2n
|
||||
this.rateCost += this.rateCost * this.rate
|
||||
}
|
||||
}
|
||||
|
||||
serviceBonus(state) {
|
||||
let bonus = 0n
|
||||
|
||||
if (state.services.time.level > 0) {
|
||||
let i
|
||||
for(i = state.levels.length - 1; i >= 0; i--) {
|
||||
if (state.levels[i].unlocked) {
|
||||
let multiplier = state.ui.elapsed - state.levels[i].unlockTime
|
||||
// multiplier = multiplier > 7200 ? 7200 : multiplier
|
||||
bonus += (this.rate * BigInt(multiplier)) / BigInt(7200 / i)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (state.services.time.level > 1) {
|
||||
//let multiplier = state.ui.elapsed > 172800 ? 86400 : state.ui.elapsed
|
||||
let multiplier = state.ui.elapsed
|
||||
bonus += (this.rate * BigInt(multiplier)) / BigInt(86400/i)
|
||||
}
|
||||
}
|
||||
|
||||
return bonus
|
||||
}
|
||||
|
||||
// regular updates
|
||||
tick(state, interval) {
|
||||
this.count += this.rate * interval + this.serviceBonus(state)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
89
src/model/leveldefs.js
Normal file
89
src/model/leveldefs.js
Normal file
@ -0,0 +1,89 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import { LevelTemplate } from './levels.js'
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export const LevelDefs = [
|
||||
new LevelTemplate({
|
||||
name: 'Newcomer',
|
||||
blurb: "Your PR has been merged and it's time to start pushing packets.",
|
||||
units: 'packets',
|
||||
cost: BigInt(1),
|
||||
unlocked: true
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Peering Pro',
|
||||
blurb: 'Increase the number of peers to get more packets.',
|
||||
units: 'peers',
|
||||
cost: BigInt(10)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'IPv6 Rollout',
|
||||
blurb: 'Rollout IPv6 across your network.',
|
||||
units: 'link local addresses',
|
||||
cost: BigInt(100)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Routing Updates',
|
||||
blurb: 'Optimise your bird config to get ROA filters (look, no-one said this should make sense).',
|
||||
units: 'ROA filters',
|
||||
cost: BigInt(1000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Node Operator',
|
||||
blurb: 'Expand your global footprint with new nodes.',
|
||||
units: 'low end vps',
|
||||
cost: BigInt(10000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Security Guru',
|
||||
blurb: 'Improve your security by implementing firewalls.',
|
||||
units: 'firewalls',
|
||||
cost: BigInt(100000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'File Sharing',
|
||||
blurb: 'Spread your packets around.',
|
||||
units: 'files transferred',
|
||||
cost: BigInt(1000000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Anycast Deployment',
|
||||
blurb: 'Create resilient global services.',
|
||||
units: 'paths',
|
||||
cost: BigInt(10000000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Community Member',
|
||||
blurb: 'Help others by contributing to the wiki.',
|
||||
units: 'wiki edits',
|
||||
cost: BigInt(100000000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'Multicast Streamer',
|
||||
blurb: 'Use up all your bandwidth streaming music and video.',
|
||||
units: 'streams',
|
||||
cost: BigInt(1000000000)
|
||||
}),
|
||||
|
||||
new LevelTemplate({
|
||||
name: 'DN42 Master',
|
||||
blurb: 'The network must grow !',
|
||||
units: 'rp_filters disabled',
|
||||
cost: BigInt(10000000000)
|
||||
})
|
||||
|
||||
]
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
141
src/model/levels.js
Normal file
141
src/model/levels.js
Normal file
@ -0,0 +1,141 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// level functions
|
||||
|
||||
export class LevelTemplate {
|
||||
constructor(data) {
|
||||
|
||||
// common variables
|
||||
this.count = BigInt(0)
|
||||
this.multiplier = BigInt(1)
|
||||
this.multiplierCost = BigInt(10)
|
||||
this.efficiency = BigInt(1)
|
||||
this.efficiencyCost = BigInt(1)
|
||||
this.unlocked = false
|
||||
this.unlockTime = 0
|
||||
|
||||
// copy level specific data in to object
|
||||
Object.assign(this, data)
|
||||
}
|
||||
|
||||
// save data
|
||||
save() {
|
||||
let save_data = { }
|
||||
let props = [
|
||||
'index', 'count',
|
||||
'multiplier', 'multiplierCost',
|
||||
'efficiency', 'efficiencyCost',
|
||||
'unlocked', 'unlockTime'
|
||||
]
|
||||
|
||||
props.forEach((prop) => {
|
||||
save_data[prop] = this[prop]
|
||||
})
|
||||
return save_data
|
||||
}
|
||||
|
||||
restore(save_data) {
|
||||
Object.assign(this, save_data)
|
||||
}
|
||||
|
||||
// unlock this level
|
||||
unlock(elapsed) {
|
||||
this.unlocked = true
|
||||
this.unlockTime = elapsed
|
||||
}
|
||||
|
||||
// upgrade functions
|
||||
canUpgrade() {
|
||||
return this.count >= this.multiplierCost
|
||||
}
|
||||
|
||||
shouldAutobuy() {
|
||||
return this.count >= (this.multiplierCost * 2n)
|
||||
}
|
||||
|
||||
upgradeMultiplier() {
|
||||
this.count -= this.multiplierCost
|
||||
this.multiplier *= 2n
|
||||
this.multiplierCost *= 12n
|
||||
}
|
||||
|
||||
upgradeEfficiency(state) {
|
||||
// am I the last level ?
|
||||
if (this.index == (state.count - 1)) { return }
|
||||
|
||||
let nextLevel = state.levels[this.index + 1]
|
||||
nextLevel.count -= this.efficiencyCost
|
||||
this.efficiency *= 2n
|
||||
this.efficiencyCost *= 11n
|
||||
}
|
||||
|
||||
// calculate max unit cost as percentage of previous level units
|
||||
calcUnitCostPercent(state) {
|
||||
if (this.index == 0) { return 100 }
|
||||
|
||||
let max = this.maxCanBuyInternal(state)
|
||||
let units = state.levels[this.index - 1].count
|
||||
if (units == 0) { return 100 }
|
||||
return Number((max * this.cost * 100n) / units)
|
||||
}
|
||||
|
||||
// calculate max unit cost as percentage of available bandwidth
|
||||
calcBWCostPercent(state) {
|
||||
let max = this.maxCanBuyInternal(state)
|
||||
let bw = state.bandwidth.count
|
||||
if (bw == 0) { return 100 }
|
||||
return Number((max * 100n) / bw)
|
||||
}
|
||||
|
||||
// unit purchasing
|
||||
buy(state, amount) {
|
||||
|
||||
// take cost based on amount less the efficiency
|
||||
let a = amount / this.efficiency
|
||||
state.bandwidth.count -= a
|
||||
if (this.index != 0) {
|
||||
state.levels[this.index - 1].count -= a * this.cost
|
||||
}
|
||||
|
||||
// increase units
|
||||
this.count += amount
|
||||
}
|
||||
|
||||
// calculate the maximum number of units that can be bought
|
||||
maxCanBuyInternal(state) {
|
||||
let max
|
||||
|
||||
if (this.index == 0) {
|
||||
// level zero is limited by bandwidth only
|
||||
max = state.bandwidth.count
|
||||
|
||||
} else {
|
||||
// otherwise its the maximum of bw and previous level
|
||||
max = state.levels[this.index - 1].count / this.cost
|
||||
if (max > state.bandwidth.count) {
|
||||
max = state.bandwidth.count
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
maxCanBuy(state) {
|
||||
return this.maxCanBuyInternal(state) * this.efficiency
|
||||
}
|
||||
|
||||
// timer tick
|
||||
tick(state, interval) {
|
||||
// am I the last level ?
|
||||
if (this.index == (state.count - 1)) { return }
|
||||
|
||||
// add units to this level
|
||||
let nextLevel = state.levels[this.index + 1]
|
||||
this.count += nextLevel.count * nextLevel.multiplier
|
||||
|
||||
// unlock next level if possible
|
||||
if ((this.count > 0) && (!nextLevel.unlocked)) { nextLevel.unlock(state.ui.elapsed) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
43
src/model/numbers.js
Normal file
43
src/model/numbers.js
Normal file
@ -0,0 +1,43 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// number functions
|
||||
|
||||
const SISym = [
|
||||
'', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'
|
||||
]
|
||||
|
||||
export function nstr(big) {
|
||||
|
||||
let m = 0
|
||||
let r = 0
|
||||
const onek = BigInt(1000)
|
||||
|
||||
while (big >= onek) {
|
||||
r = Number(big % onek)
|
||||
big /= onek
|
||||
m++
|
||||
}
|
||||
|
||||
// convert to fraction
|
||||
let n = Number(big)
|
||||
let f = n + (r / 1000)
|
||||
|
||||
if (m == 0) {
|
||||
// simple number
|
||||
return n.toString()
|
||||
} else if (m < SISym.length) {
|
||||
// with SI suffix
|
||||
return f.toFixed(2) + SISym[m]
|
||||
} else {
|
||||
// scientific notation
|
||||
m *= 3
|
||||
while (f > 10) {
|
||||
f /= 10
|
||||
m++
|
||||
}
|
||||
return f.toFixed(2) + 'e' + m
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
90
src/model/services.js
Normal file
90
src/model/services.js
Normal file
@ -0,0 +1,90 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// level functions
|
||||
|
||||
export class Services {
|
||||
constructor() {
|
||||
|
||||
this.time = {
|
||||
unlocked: false,
|
||||
level: 0,
|
||||
|
||||
}
|
||||
this.auto = {
|
||||
unlocked: false,
|
||||
level: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// save data
|
||||
save() {
|
||||
let save_data = {
|
||||
time: this.time,
|
||||
auto: this.auto
|
||||
}
|
||||
return save_data
|
||||
}
|
||||
|
||||
restore(save_data) {
|
||||
Object.assign(this, save_data)
|
||||
}
|
||||
|
||||
tick(state, interval) {
|
||||
if (state.levels[2].count > 0) { this.auto.unlocked = true }
|
||||
if (state.levels[3].count > 0) { this.time.unlocked = true }
|
||||
}
|
||||
|
||||
// time costs
|
||||
tCost() {
|
||||
switch (this.time.level) {
|
||||
case 0:
|
||||
return 1000000n
|
||||
case 1:
|
||||
return 1000000000n
|
||||
default:
|
||||
return 0n
|
||||
}
|
||||
}
|
||||
|
||||
tUnits(state) {
|
||||
return state.levels[(this.time.level*2)+3].units
|
||||
}
|
||||
|
||||
tValue(state) {
|
||||
return state.levels[(this.time.level*2)+3].count
|
||||
}
|
||||
|
||||
tUpgrade(state) {
|
||||
state.levels[(this.time.level*2)+3].count -= this.tCost()
|
||||
this.time.level++
|
||||
}
|
||||
|
||||
// auto costs
|
||||
aCost() {
|
||||
switch (this.auto.level) {
|
||||
case 0:
|
||||
return 1000000n
|
||||
case 1:
|
||||
return 1000000000n
|
||||
default:
|
||||
return 0n
|
||||
}
|
||||
}
|
||||
|
||||
aUnits(state) {
|
||||
return state.levels[(this.auto.level*2)+2].units
|
||||
}
|
||||
|
||||
aValue(state) {
|
||||
return state.levels[(this.auto.level*2)+2].count
|
||||
}
|
||||
|
||||
aUpgrade(state) {
|
||||
state.levels[(this.auto.level*2)+2].count -= this.aCost()
|
||||
this.auto.level++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
133
src/model/state.js
Normal file
133
src/model/state.js
Normal file
@ -0,0 +1,133 @@
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import { defineStore } from "pinia"
|
||||
import { computed, reactive } from "vue"
|
||||
import { LevelDefs } from './leveldefs.js'
|
||||
import { Bandwidth } from './bandwidth.js'
|
||||
import { Autobuyer } from "./autobuyer.js"
|
||||
import { Services } from "./services.js"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export const useState = defineStore('state', () => {
|
||||
|
||||
// timer tick
|
||||
function tick(state, i) {
|
||||
let interval = BigInt(i)
|
||||
ui.elapsed += i
|
||||
|
||||
// update bandwidth
|
||||
bandwidth.tick(state, interval)
|
||||
|
||||
// update levels
|
||||
levels.forEach((l) => {
|
||||
l.tick(state, interval)
|
||||
})
|
||||
|
||||
ui.save = ((ui.elapsed % 30) == 0)
|
||||
if (ui.save) { save() }
|
||||
|
||||
autobuyer.tick(state, i)
|
||||
services.tick(state, i)
|
||||
}
|
||||
|
||||
// save and restore
|
||||
function reset() {
|
||||
localStorage.clear()
|
||||
location.reload()
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
let save_data = {
|
||||
version: version,
|
||||
elapsed: ui.elapsed,
|
||||
levels: Array.from(levels, (l) => l.save()),
|
||||
bandwidth: bandwidth.save(),
|
||||
services: services.save(),
|
||||
autobuyer: autobuyer.save()
|
||||
}
|
||||
|
||||
// replacer func to handle big ints
|
||||
const replacer = (key, value) =>
|
||||
typeof value === 'bigint' ? '__big__' + value.toString() : value
|
||||
let encoded = JSON.stringify(save_data, replacer)
|
||||
|
||||
localStorage.setItem('clicker42', encoded)
|
||||
}
|
||||
|
||||
function restore() {
|
||||
let encoded = localStorage.getItem('clicker42')
|
||||
if (!encoded) { return }
|
||||
|
||||
// reviver func to handle big ints
|
||||
const reviver = (key, value) =>
|
||||
((typeof value === 'string') && (value.startsWith('__big__'))) ? BigInt(value.substring(7)) : value;
|
||||
let save_data = JSON.parse(encoded, reviver)
|
||||
|
||||
// don't load older versions
|
||||
if (version > save_data.version) { return }
|
||||
|
||||
ui.elapsed = save_data.elapsed
|
||||
|
||||
// restore levels
|
||||
save_data.levels.forEach((l) => {
|
||||
let index = l.index
|
||||
levels[index].restore(l)
|
||||
})
|
||||
|
||||
// and bandwidth
|
||||
bandwidth.restore(save_data.bandwidth)
|
||||
services.restore(save_data.services)
|
||||
autobuyer.restore(save_data.autobuyer)
|
||||
}
|
||||
|
||||
function noticeClear() {
|
||||
ui.notice = '<span class="p-2"> </span>'
|
||||
}
|
||||
|
||||
function noticeTemplate(style, text) {
|
||||
ui.notice = '<span class="badge p-2 rounded-pill ' + style + '">' + text + '</span>'
|
||||
}
|
||||
|
||||
function noticeSuccess(text) { noticeTemplate('bg-success', text) }
|
||||
function noticeDanger(text) { noticeTemplate('bg-danger', text) }
|
||||
|
||||
// game data
|
||||
const version = 0.1
|
||||
const ui = reactive({
|
||||
selectedLevel: 0,
|
||||
elapsed: 0,
|
||||
notice: '<span class="p-2"> </span>',
|
||||
save: false
|
||||
})
|
||||
const autobuyer = reactive(new Autobuyer())
|
||||
|
||||
// level data
|
||||
const levels = reactive(LevelDefs)
|
||||
levels.forEach((l, ix) => {
|
||||
l.index = ix
|
||||
l.multiplier = BigInt(ix)
|
||||
})
|
||||
|
||||
const bandwidth = reactive(new Bandwidth())
|
||||
const services = reactive(new Services())
|
||||
|
||||
// computed functions
|
||||
const count = computed(() => levels.length)
|
||||
|
||||
// return the things that will form part of the store
|
||||
return {
|
||||
// data
|
||||
version, ui, levels, bandwidth, services, autobuyer,
|
||||
// computed
|
||||
count,
|
||||
// functions
|
||||
tick, reset, save, restore,
|
||||
noticeClear, noticeSuccess, noticeDanger
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// end of file
|
@ -1,89 +0,0 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
28
yarn.lock
28
yarn.lock
@ -164,6 +164,11 @@
|
||||
"@vue/compiler-dom" "3.2.47"
|
||||
"@vue/shared" "3.2.47"
|
||||
|
||||
"@vue/devtools-api@^6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
|
||||
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||
|
||||
"@vue/reactivity-transform@3.2.47":
|
||||
version "3.2.47"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz#e45df4d06370f8abf29081a16afd25cffba6d84e"
|
||||
@ -212,6 +217,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.47.tgz#e597ef75086c6e896ff5478a6bfc0a7aa4bbd14c"
|
||||
integrity sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==
|
||||
|
||||
bootstrap-icons@^1.10.3:
|
||||
version "1.10.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.10.3.tgz#c587b078ca6743bef4653fe90434b4aebfba53b2"
|
||||
integrity sha512-7Qvj0j0idEm/DdX9Q0CpxAnJYqBCFCiUI6qzSPYfERMcokVuV9Mdm/AJiVZI8+Gawe4h/l6zFcOzvV7oXCZArw==
|
||||
|
||||
bootstrap@^5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b"
|
||||
integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==
|
||||
|
||||
csstype@^2.6.8:
|
||||
version "2.6.21"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
|
||||
@ -296,6 +311,14 @@ picocolors@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
pinia@^2.0.33:
|
||||
version "2.0.33"
|
||||
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.33.tgz#b70065be697874d5824e9792f59bd5d87ddb5e7d"
|
||||
integrity sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
vue-demi "*"
|
||||
|
||||
postcss@^8.1.10, postcss@^8.4.21:
|
||||
version "8.4.21"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
|
||||
@ -353,6 +376,11 @@ vite@^4.2.0:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vue-demi@*:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
|
||||
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
|
||||
|
||||
vue@^3.2.47:
|
||||
version "3.2.47"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.47.tgz#3eb736cbc606fc87038dbba6a154707c8a34cff0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user