Compare commits
No commits in common. "main" and "2025" have entirely different histories.
|
@ -1,9 +1,9 @@
|
||||||
root = true
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
end_of_line = lf
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'@vue/eslint-config-typescript/recommended',
|
|
||||||
'@vue/eslint-config-prettier',
|
|
||||||
],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['cypress/e2e/**.{cy,spec}.{js,ts,jsx,tsx}'],
|
|
||||||
extends: ['plugin:cypress/recommended'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// https://typescript-eslint.io/docs/linting/troubleshooting/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
|
||||||
files: ['*.vue'],
|
|
||||||
rules: {
|
|
||||||
'no-undef': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['*.story.vue', '*.story.controls.vue'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'vue/require-v-for-key': 'off',
|
|
||||||
'vue/no-mutating-props': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
// see https://vuejs.org/guide/extras/reactivity-transform.html
|
|
||||||
'vue/no-setup-props-destructure': 'off',
|
|
||||||
// TODO: discuss if we want to force this
|
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
// see https://eslint.org/docs/latest/rules/no-prototype-builtins#when-not-to-use-it
|
|
||||||
'no-prototype-builtins': 'off',
|
|
||||||
// as long as it is explicit, it is fine to use any
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
},
|
|
||||||
}
|
|
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
9
.gitignore
vendored
|
@ -8,20 +8,23 @@ pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# old source code for reference?!
|
*.tsbuildinfo
|
||||||
src/old
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true
|
|
||||||
}
|
|
6
.prettierrc.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
9
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"vitest.explorer",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
48
README.md
|
@ -1,29 +1,45 @@
|
||||||
# building-game
|
# vue-shovel
|
||||||
|
|
||||||
> A blocky, side-scrolling, building and exploration game
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
This version of DIG! is reimplemented with Vue3 and Typescript. To see the old (and probably broken) version, check the vue2 branch.
|
## Recommended IDE Setup
|
||||||
|
|
||||||
## Build Setup
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
``` bash
|
## Type Support for `.vue` Imports in TS
|
||||||
# install dependencies
|
|
||||||
yarn
|
|
||||||
|
|
||||||
# serve with hot reload at localhost:8080
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||||
yarn dev
|
|
||||||
|
|
||||||
# build for production with minification
|
## Customize configuration
|
||||||
yarn build
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
## Credits
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
Art, Music and all from [OpenGameArt](https://opengameart.org/). More specifically:
|
```sh
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
* https://opengameart.org/content/voxel-pack (Kenneys Game Art is just incredible!)
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
* https://opengameart.org/content/oves-essential-game-audio-pack-collection-160-files-updated
|
|
||||||
|
|
||||||
and partially adapted to my needs.
|
```sh
|
||||||
|
pnpm test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm lint
|
||||||
|
```
|
||||||
|
|
BIN
dist/bedrock.png
vendored
Before Width: | Height: | Size: 1.2 KiB |
7
dist/build.js
vendored
1
dist/build.js.map
vendored
BIN
dist/dwarf_left.png
vendored
Before Width: | Height: | Size: 607 B |
BIN
dist/dwarf_right.png
vendored
Before Width: | Height: | Size: 629 B |
BIN
dist/grass01.png
vendored
Before Width: | Height: | Size: 614 B |
BIN
dist/rock.png
vendored
Before Width: | Height: | Size: 1.2 KiB |
BIN
dist/soil01.png
vendored
Before Width: | Height: | Size: 586 B |
BIN
dist/soil_gravel01.png
vendored
Before Width: | Height: | Size: 940 B |
BIN
dist/tree_crown_left.png
vendored
Before Width: | Height: | Size: 719 B |
BIN
dist/tree_crown_left_mixed.png
vendored
Before Width: | Height: | Size: 1.4 KiB |
BIN
dist/tree_crown_middle.png
vendored
Before Width: | Height: | Size: 1.5 KiB |
BIN
dist/tree_crown_right.png
vendored
Before Width: | Height: | Size: 1 KiB |
BIN
dist/tree_crown_right_mixed.png
vendored
Before Width: | Height: | Size: 1.3 KiB |
BIN
dist/tree_root_left.png
vendored
Before Width: | Height: | Size: 340 B |
BIN
dist/tree_root_left_mixed.png
vendored
Before Width: | Height: | Size: 539 B |
BIN
dist/tree_root_middle.png
vendored
Before Width: | Height: | Size: 938 B |
BIN
dist/tree_root_right.png
vendored
Before Width: | Height: | Size: 280 B |
BIN
dist/tree_root_right_mixed.png
vendored
Before Width: | Height: | Size: 539 B |
BIN
dist/tree_top_left.png
vendored
Before Width: | Height: | Size: 139 B |
BIN
dist/tree_top_left_mixed.png
vendored
Before Width: | Height: | Size: 257 B |
BIN
dist/tree_top_middle.png
vendored
Before Width: | Height: | Size: 747 B |
BIN
dist/tree_top_right.png
vendored
Before Width: | Height: | Size: 205 B |
BIN
dist/tree_top_right_mixed.png
vendored
Before Width: | Height: | Size: 257 B |
BIN
dist/tree_trunk_left.png
vendored
Before Width: | Height: | Size: 735 B |
BIN
dist/tree_trunk_left_mixed.png
vendored
Before Width: | Height: | Size: 1.1 KiB |
BIN
dist/tree_trunk_middle.png
vendored
Before Width: | Height: | Size: 1.2 KiB |
BIN
dist/tree_trunk_right.png
vendored
Before Width: | Height: | Size: 750 B |
BIN
dist/tree_trunk_right_mixed.png
vendored
Before Width: | Height: | Size: 1.1 KiB |
7
env.d.ts
vendored
|
@ -1,8 +1 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module "*.vue" {
|
|
||||||
import { DefineComponent } from "vue";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
|
||||||
}
|
|
||||||
|
|
32
eslint.config.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
||||||
|
import pluginVitest from '@vitest/eslint-plugin'
|
||||||
|
import oxlint from 'eslint-plugin-oxlint'
|
||||||
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
|
||||||
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
|
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
||||||
|
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
||||||
|
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'app/files-to-ignore',
|
||||||
|
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
|
||||||
|
},
|
||||||
|
|
||||||
|
pluginVue.configs['flat/essential'],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
|
||||||
|
{
|
||||||
|
...pluginVitest.configs.recommended,
|
||||||
|
files: ['src/**/__tests__/*'],
|
||||||
|
},
|
||||||
|
...oxlint.configs['flat/recommended'],
|
||||||
|
skipFormatting,
|
||||||
|
)
|
3
foo/.vscode/extensions.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Vue 3 + TypeScript + Vite
|
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 and TypeScript 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.
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
|
|
||||||
## Type Support For `.vue` Imports in TS
|
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
|
||||||
|
|
||||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
|
||||||
|
|
||||||
1. Disable the built-in TypeScript Extension
|
|
||||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
|
||||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
|
||||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"name": "foo",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vue-tsc && vite build",
|
|
||||||
"preview": "vite preview"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vue": "^3.2.45"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
|
||||||
"typescript": "^4.9.3",
|
|
||||||
"vite": "^4.1.0",
|
|
||||||
"vue-tsc": "^1.0.24"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 |
|
@ -1,30 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
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>
|
|
|
@ -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 |
|
@ -1,38 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
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/johnsoncodehk/volar" 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>
|
|
|
@ -1,80 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
<link rel="preload" as="image" href="/Tiles/dirt.png" />
|
<link rel="preload" as="image" href="/Tiles/dirt.png" />
|
||||||
<link rel="preload" as="image" href="/Tiles/dirt_grass.png" />
|
<link rel="preload" as="image" href="/Tiles/dirt_grass.png" />
|
||||||
|
@ -15,8 +16,8 @@
|
||||||
<link rel="preload" as="image" href="/Tiles/trunk_mid.png" />
|
<link rel="preload" as="image" href="/Tiles/trunk_mid.png" />
|
||||||
<link rel="preload" as="image" href="/Tiles/trunk_bottom.png" />
|
<link rel="preload" as="image" href="/Tiles/trunk_bottom.png" />
|
||||||
<link rel="preload" as="image" href="/Tiles/leaves_transparent.png" />
|
<link rel="preload" as="image" href="/Tiles/leaves_transparent.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Dig!</title>
|
<title>Vue Shovel</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--block-size: 64px;
|
--block-size: 64px;
|
||||||
|
|
62
package.json
|
@ -1,33 +1,55 @@
|
||||||
{
|
{
|
||||||
"name": "DIG",
|
"name": "vue-shovel",
|
||||||
"description": "A blocky, side-scrolling, digging and exploration game",
|
"version": "0.0.0",
|
||||||
"version": "0.0.1",
|
|
||||||
"author": "koehr <n@koehr.in>",
|
|
||||||
"license": "MIT",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc && vite build",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix"
|
"test:unit": "vitest",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --build",
|
||||||
|
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||||
|
"lint:eslint": "eslint . --fix",
|
||||||
|
"lint": "run-s lint:*",
|
||||||
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"alea": "^1.0.1",
|
"alea": "^1.0.1",
|
||||||
"simplex-noise": "^4.0.1",
|
"simplex-noise": "^4.0.3",
|
||||||
"vue": "^3.4.22"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.10.2",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
"@typescript-eslint/parser": "^7.7.0",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@types/node": "^22.13.9",
|
||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vitest/eslint-plugin": "^1.1.36",
|
||||||
"eslint": "^9.0.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
"eslint-plugin-vue": "^9.25.0",
|
"@vue/eslint-config-typescript": "^14.5.0",
|
||||||
"typescript": "^5.4.5",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"unplugin-vue-macros": "^2.9.1",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"vite": "^5.2.9",
|
"eslint": "^9.21.0",
|
||||||
"vue-tsc": "^2.0.13"
|
"eslint-plugin-oxlint": "^0.15.13",
|
||||||
|
"eslint-plugin-vue": "~10.0.0",
|
||||||
|
"jiti": "^2.4.2",
|
||||||
|
"jsdom": "^26.0.0",
|
||||||
|
"npm-run-all2": "^7.0.2",
|
||||||
|
"oxlint": "^0.15.13",
|
||||||
|
"prettier": "3.5.3",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
|
"vite": "^6.2.1",
|
||||||
|
"vite-plugin-vue-devtools": "^7.7.2",
|
||||||
|
"vitest": "^3.0.8",
|
||||||
|
"vue-tsc": "^2.2.8"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"ignoredBuiltDependencies": [
|
||||||
|
"esbuild"
|
||||||
|
],
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4232
pnpm-lock.yaml
generated
Normal file
14
simplex.html
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Simplex Noise Test</title>
|
|
||||||
<style>
|
|
||||||
body, html { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; background: black; }
|
|
||||||
canvas { display: block; width: 1024px; height: 768px; margin: calc(50vh - 768px / 2) auto 0; border: 2px solid #333; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<canvas></canvas>
|
|
||||||
<script type="module" src="/src/simplex-demo.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
91
src/App.vue
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import Help from './screens/help.vue'
|
import Help from './screens/help.vue'
|
||||||
import Inventory from './screens/inventory.vue'
|
import Inventory from './screens/inventory.vue'
|
||||||
|
|
||||||
|
@ -31,10 +31,23 @@ const x = ref(0)
|
||||||
const y = ref(0)
|
const y = ref(0)
|
||||||
const floorX = computed(() => Math.floor(x.value))
|
const floorX = computed(() => Math.floor(x.value))
|
||||||
const floorY = computed(() => Math.floor(y.value))
|
const floorY = computed(() => Math.floor(y.value))
|
||||||
const tx = computed(() => (x.value - floorX.value) * -BLOCK_SIZE)
|
const fracX = computed(() => x.value - floorX.value)
|
||||||
const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE)
|
const fracY = computed(() => y.value - floorY.value)
|
||||||
const rows = computed(() => level.grid(floorX.value, floorY.value))
|
const tx = computed(() => fracX.value * -BLOCK_SIZE)
|
||||||
const lightBarrier = computed(() => level.sunLight(floorX.value))
|
const ty = computed(() => fracY.value * -BLOCK_SIZE)
|
||||||
|
const px = computed(() => Math.round(player.x + fracX.value))
|
||||||
|
const py = computed(() => Math.round(player.y + fracY.value))
|
||||||
|
|
||||||
|
const lightBarrier = computed<number[]>(() => {
|
||||||
|
const _update = mapUpdateCount.value // reactivity trigger
|
||||||
|
return level.sunLight(floorX.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapUpdateCount = ref(0)
|
||||||
|
const mapGrid = computed<Block[][]>(() => {
|
||||||
|
const _update = mapUpdateCount.value // reactivity trigger
|
||||||
|
return level.grid(floorX.value, floorY.value, true)
|
||||||
|
})
|
||||||
|
|
||||||
const arriving = ref(true)
|
const arriving = ref(true)
|
||||||
const walking = ref(false)
|
const walking = ref(false)
|
||||||
|
@ -48,25 +61,32 @@ type Surroundings = {
|
||||||
down: Block,
|
down: Block,
|
||||||
}
|
}
|
||||||
const surroundings = computed<Surroundings>(() => {
|
const surroundings = computed<Surroundings>(() => {
|
||||||
const px = player.x
|
const _update = mapUpdateCount.value // reactivity trigger
|
||||||
const py = player.y
|
const x = px.value
|
||||||
const row = rows.value
|
const y = py.value
|
||||||
|
const rows = mapGrid.value
|
||||||
|
|
||||||
|
const rowY = rows[y]
|
||||||
|
const rowYp = rows[y - 1]
|
||||||
|
const rowYn = rows[y + 1]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
at: row[py][px],
|
at: rowY[x],
|
||||||
left: row[py][px - 1],
|
left: rowY[x - 1],
|
||||||
right: row[py][px + 1],
|
right: rowY[x + 1],
|
||||||
up: row[py - 1][px],
|
up: rowYp[x],
|
||||||
down: row[py + 1][px],
|
down: rowYn[x],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const blocked = computed(() => {
|
const blocked = computed(() => {
|
||||||
const { left, right, up, down } = surroundings.value
|
const { left, right, up, down } = surroundings.value
|
||||||
|
const fx = fracX.value
|
||||||
|
const fy = fracY.value
|
||||||
return {
|
return {
|
||||||
left: !left.walkable,
|
left: !left.walkable && fx < 0.8 && fx > 0.7,
|
||||||
right: !right.walkable,
|
right: !right.walkable && fx > 0.2 && fx < 0.3,
|
||||||
up: !up.walkable,
|
up: !up.walkable,
|
||||||
down: !down.walkable,
|
down: !down.walkable && fy > 0.8,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -84,6 +104,7 @@ function dig(blockX: number, blockY: number, block: Block) {
|
||||||
y: floorY.value + blockY,
|
y: floorY.value + blockY,
|
||||||
newType: 'air'
|
newType: 'air'
|
||||||
})
|
})
|
||||||
|
mapUpdateCount.value = mapUpdateCount.value + 1
|
||||||
|
|
||||||
// anything to pick up?
|
// anything to pick up?
|
||||||
if (block.drops) {
|
if (block.drops) {
|
||||||
|
@ -103,12 +124,14 @@ function build(blockX: number, blockY: number, block: InventoryItem) {
|
||||||
y: floorY.value + blockY,
|
y: floorY.value + blockY,
|
||||||
newType: blockToBuild
|
newType: blockToBuild
|
||||||
})
|
})
|
||||||
|
mapUpdateCount.value = mapUpdateCount.value + 1
|
||||||
|
|
||||||
const newAmount = unpocket(block)
|
const newAmount = unpocket(block)
|
||||||
if (newAmount < 1) inventorySelection.value = player.inventory[0]
|
if (newAmount < 1) inventorySelection.value = player.inventory[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
function interactWith(blockX: number, blockY: number, block: Block) {
|
function interactWith(blockX: number, blockY: number, block: Block) {
|
||||||
|
console.log('interact with', blockX, blockY, block.type)
|
||||||
// § 4 ArbZG
|
// § 4 ArbZG
|
||||||
if (paused.value) return
|
if (paused.value) return
|
||||||
|
|
||||||
|
@ -123,12 +146,6 @@ function interactWith(blockX: number, blockY: number, block: Block) {
|
||||||
} else if (toolInHand && !emptyBlock) {
|
} else if (toolInHand && !emptyBlock) {
|
||||||
dig(blockX, blockY, block)
|
dig(blockX, blockY, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This feels like cheating, but it makes Vue recalculate floorX
|
|
||||||
// which then recalculates the blocks, so that the changes are
|
|
||||||
// applied. Otherwise, they wouldn't be visible before moving
|
|
||||||
x.value = x.value + 0.01
|
|
||||||
x.value = x.value - 0.01
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastTimeUpdate = 0
|
let lastTimeUpdate = 0
|
||||||
|
@ -196,12 +213,16 @@ function selectTool(item: InventoryItem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const canvas = lightMapEl.value!
|
if (lightMapEl.value) {
|
||||||
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
|
const canvas = lightMapEl.value
|
||||||
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
|
canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
|
||||||
const ctx = canvas.getContext('2d')!
|
canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
|
||||||
|
const ctx = canvas.getContext('2d')!
|
||||||
updateLightMap = useLightMap(ctx, floorX, floorY, tx, ty, time, lightBarrier)
|
updateLightMap = useLightMap(ctx, floorX, floorY, tx, ty, time, lightBarrier)
|
||||||
|
} else {
|
||||||
|
console.warn('lightmap deactivated')
|
||||||
|
updateLightMap = (() => {}) as ReturnType<typeof useLightMap>
|
||||||
|
}
|
||||||
lastTick = performance.now()
|
lastTick = performance.now()
|
||||||
move(lastTick)
|
move(lastTick)
|
||||||
})
|
})
|
||||||
|
@ -209,19 +230,19 @@ onMounted(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="field" :class="timeOfDay">
|
<div id="field" :class="timeOfDay">
|
||||||
|
|
||||||
<div id="blocks" :style="{transform: `translate(${tx}px, ${ty}px)`}">
|
<div id="blocks" :style="{transform: `translate(${tx}px, ${ty}px)`}">
|
||||||
<template v-for="(row, y) in rows">
|
<template v-for="(row, y) in mapGrid">
|
||||||
<div v-for="(block, x) in row"
|
<div v-for="(block, x) in row"
|
||||||
:class="['block', block.type, {
|
:class="['block', block.type, { highlight: x === px && y === py }]"
|
||||||
highlight: x === player.x && y == player.y
|
|
||||||
}]"
|
|
||||||
@click="interactWith(x, y, block)"
|
@click="interactWith(x, y, block)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="player" :class="[direction, { walking }]" @click="inventory = !inventory">
|
<div id="player"
|
||||||
|
:class="[direction, { walking, running, sitting: paused }]"
|
||||||
|
@click="inventory = !inventory"
|
||||||
|
>
|
||||||
<div class="head"></div>
|
<div class="head"></div>
|
||||||
<div class="body"></div>
|
<div class="body"></div>
|
||||||
<div class="legs">
|
<div class="legs">
|
||||||
|
@ -241,6 +262,8 @@ onMounted(() => {
|
||||||
x:{{ floorX }}, y:{{ floorY }}
|
x:{{ floorX }}, y:{{ floorY }}
|
||||||
<template v-if="paused">(PAUSED)</template>
|
<template v-if="paused">(PAUSED)</template>
|
||||||
<template v-else>({{ clock }})</template>
|
<template v-else>({{ clock }})</template>
|
||||||
|
<br/>
|
||||||
|
Map Changes: {{ mapUpdateCount }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Inventory :shown="inventory"
|
<Inventory :shown="inventory"
|
||||||
|
|
|
@ -101,7 +101,7 @@
|
||||||
|
|
||||||
#field .block:hover,
|
#field .block:hover,
|
||||||
#field .block.block.highlight {
|
#field .block.block.highlight {
|
||||||
filter: brightness(1.2) grayscale(1.0);
|
filter: brightness(1.2) saturate(1.2);
|
||||||
outline: 1px solid white;
|
outline: 1px solid white;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
@ -159,4 +159,4 @@
|
||||||
height: calc(100% + var(--block-size) * 2);
|
height: calc(100% + var(--block-size) * 2);
|
||||||
mix-blend-mode: multiply;
|
mix-blend-mode: multiply;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
--player-height: 76px;
|
--player-height: 76px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(var(--field-width) / 2);
|
left: calc(var(--field-width) / 2);
|
||||||
top: calc(var(--field-height) / 2 - 10px);
|
top: calc(var(--field-height) / 2);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
width: var(--player-width);
|
width: var(--player-width);
|
||||||
|
@ -58,6 +58,21 @@
|
||||||
#player.walking > .legs > div.left {
|
#player.walking > .legs > div.left {
|
||||||
animation: dangle .3s linear infinite alternate;
|
animation: dangle .3s linear infinite alternate;
|
||||||
}
|
}
|
||||||
|
#player.running > .legs > div.right {
|
||||||
|
animation: gillop .2s linear infinite alternate;
|
||||||
|
}
|
||||||
|
#player.running > .legs > div.left {
|
||||||
|
animation: gallop .2s linear infinite alternate;
|
||||||
|
}
|
||||||
|
#player.sitting {
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
#player.right.sitting {
|
||||||
|
transform: scaleX(-1) translateY(10px);
|
||||||
|
}
|
||||||
|
#player.sitting > .legs > div {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
#player > .arms {
|
#player > .arms {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
|
@ -70,6 +85,9 @@
|
||||||
#player.walking > .arms {
|
#player.walking > .arms {
|
||||||
animation: dangle .3s linear infinite alternate;
|
animation: dangle .3s linear infinite alternate;
|
||||||
}
|
}
|
||||||
|
#player.running > .arms {
|
||||||
|
animation: gallop .2s linear infinite alternate;
|
||||||
|
}
|
||||||
#player > .arms > .item {
|
#player > .arms > .item {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
@ -89,6 +107,14 @@
|
||||||
from { transform: rotate(-20deg); }
|
from { transform: rotate(-20deg); }
|
||||||
to { transform: rotate(20deg); }
|
to { transform: rotate(20deg); }
|
||||||
}
|
}
|
||||||
|
@keyframes gillop {
|
||||||
|
from { transform: rotate(35deg); }
|
||||||
|
to { transform: rotate(-35deg); }
|
||||||
|
}
|
||||||
|
@keyframes gallop {
|
||||||
|
from { transform: rotate(-35deg); }
|
||||||
|
to { transform: rotate(35deg); }
|
||||||
|
}
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
from { opacity: .3; }
|
from { opacity: .3; }
|
||||||
to { opacity: 1.0; }
|
to { opacity: 1.0; }
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default function createLevel(width: number, height: number, seed = 'extre
|
||||||
// takes the current columnOffset and generates all blocks from the very top
|
// takes the current columnOffset and generates all blocks from the very top
|
||||||
// until a block is generated that blocks light. The height of that block is
|
// until a block is generated that blocks light. The height of that block is
|
||||||
// stored in the lightBarrier list
|
// stored in the lightBarrier list
|
||||||
function calcLightBarrier(columnOffset: number) {
|
function calcLightBarrier(columnOffset: number): void {
|
||||||
let previousBlock: Block = T.air
|
let previousBlock: Block = T.air
|
||||||
|
|
||||||
for (let col = 0; col < width; col++) {
|
for (let col = 0; col < width; col++) {
|
||||||
|
@ -84,7 +84,7 @@ export default function createLevel(width: number, height: number, seed = 'extre
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate(columnOffset: number, levelOffset: number) {
|
function generate(columnOffset: number, levelOffset: number): void {
|
||||||
for (let i = 0; i < height; i++) {
|
for (let i = 0; i < height; i++) {
|
||||||
const level = levelOffset + i
|
const level = levelOffset + i
|
||||||
const row: Block[] = Array(width)
|
const row: Block[] = Array(width)
|
||||||
|
@ -98,13 +98,21 @@ export default function createLevel(width: number, height: number, seed = 'extre
|
||||||
applyPlayerChanges(columnOffset, levelOffset)
|
applyPlayerChanges(columnOffset, levelOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sunLight(columnOffset: number) {
|
function sunLight(columnOffset: number): number[] {
|
||||||
calcLightBarrier(columnOffset)
|
calcLightBarrier(columnOffset)
|
||||||
return _lightBarrier
|
return _lightBarrier
|
||||||
}
|
}
|
||||||
|
|
||||||
function grid(x: number, y: number) {
|
let lastGenX = 0
|
||||||
generate(x, y)
|
let lastGenY = 0
|
||||||
|
generate(0, 0)
|
||||||
|
|
||||||
|
function grid(x: number, y: number, force: false): Block[][] {
|
||||||
|
if (force || lastGenX !== x || lastGenY !== y) {
|
||||||
|
generate(x, y)
|
||||||
|
lastGenX = x
|
||||||
|
lastGenY = y
|
||||||
|
}
|
||||||
return _grid
|
return _grid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
src/main.ts
|
@ -1,7 +1,7 @@
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue"
|
||||||
import "./assets/field.css";
|
import "./assets/field.css"
|
||||||
import "./assets/player.css";
|
import "./assets/player.css"
|
||||||
import "./assets/items.css";
|
import "./assets/items.css"
|
||||||
import App from "./App.vue";
|
import App from "./App.vue"
|
||||||
|
|
||||||
createApp(App).mount("#app");
|
createApp(App).mount("#app")
|
||||||
|
|
32
src/old/App.vue
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div id="building-game">
|
||||||
|
<Field />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Field from './Field'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'building-game',
|
||||||
|
components: { Field },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html,body,#app {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: black;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
72
src/old/Background.vue
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<canvas ref="canvas" id="background"></canvas>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import solarQuartet from './solar-quartet'
|
||||||
|
import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT } from './level/def'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'background',
|
||||||
|
props: {
|
||||||
|
x: Number,
|
||||||
|
time: Number
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
redraw: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// x () { this.refresh() },
|
||||||
|
time () { this.refresh() }
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const canvas = this.$refs.canvas
|
||||||
|
const godraysCanvas = document.createElement('canvas')
|
||||||
|
canvas.width = STAGE_WIDTH * BLOCK_SIZE
|
||||||
|
canvas.height = STAGE_HEIGHT * BLOCK_SIZE
|
||||||
|
godraysCanvas.width = ~~(canvas.width / 8.0)
|
||||||
|
godraysCanvas.height = ~~(canvas.height / 8.0)
|
||||||
|
this.redraw = solarQuartet.bind(
|
||||||
|
null,
|
||||||
|
canvas, canvas.getContext('2d'), ~~(canvas.width / 2.0), ~~(canvas.height / 2.0),
|
||||||
|
godraysCanvas, godraysCanvas.getContext('2d'), godraysCanvas.width, godraysCanvas.height,
|
||||||
|
)
|
||||||
|
this.refresh()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/* time value to sun position conversion
|
||||||
|
*
|
||||||
|
* The time value rotates from 0 to 1000
|
||||||
|
* sunY convertes it to values between 0 and -100,
|
||||||
|
* while -100 is high sun position (aka day)
|
||||||
|
* and 0 is low (aka night).
|
||||||
|
* My adaption of Solar Quartet renders a static night sky from -30 upwards
|
||||||
|
* and a static day at -70 or lower
|
||||||
|
*/
|
||||||
|
sunY () {
|
||||||
|
// time is between 0 and 1000
|
||||||
|
const p = Math.PI / 1000
|
||||||
|
return Math.sin(this.time * p) * -100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh () {
|
||||||
|
// console.time('draw background')
|
||||||
|
this.redraw(this.x, this.sunY)
|
||||||
|
// console.timeEnd('draw background')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#background {
|
||||||
|
display: block;
|
||||||
|
width: var(--field-width);
|
||||||
|
height: var(--field-height);
|
||||||
|
object-fit: contain;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
</style>
|
35
src/old/Block.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<template>
|
||||||
|
<div class="block" :class="type"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'block',
|
||||||
|
props: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.block {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-color: #6DA956;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.block.air { background-color: #33A; }
|
||||||
|
.block.grass { background-color: #33A; height: 28px; border-bottom: 2px solid #0A0; }
|
||||||
|
.block.soil { background-color: #543; }
|
||||||
|
.block.gravel { background-color: #665; }
|
||||||
|
.block.stone { background-color: #555; }
|
||||||
|
.block.bedrock { background-color: #444; }
|
||||||
|
.block:hover {
|
||||||
|
border-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
</style>
|
216
src/old/Field.vue
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
<template>
|
||||||
|
<div id="field" :class="daytimeClass">
|
||||||
|
<input v-keep-focussed type="text"
|
||||||
|
@keydown.up="inputY = -1"
|
||||||
|
@keydown.down="inputY = 1"
|
||||||
|
@keydown.right="inputX = -1"
|
||||||
|
@keydown.left="inputX = 1"
|
||||||
|
@keyup.up="inputY = inputY === -1 ? 0 : 1"
|
||||||
|
@keyup.down="inputY = inputY === 1 ? 0 : 1"
|
||||||
|
@keyup.right="inputX = inputX === -1 ? 0 : 1"
|
||||||
|
@keyup.left="inputX = inputX === 1 ? 0: -1"
|
||||||
|
@keypress.p="togglePause"
|
||||||
|
@keydown.space="digging = true"
|
||||||
|
@keyup.space="digging = false"
|
||||||
|
/>
|
||||||
|
<mountain-background :x="128 + x / 8" :time="time" />
|
||||||
|
<div id="wrap" :style="{transform: `translate(${tx}px, ${ty}px)`}">
|
||||||
|
<template v-for="(row, y) in rows">
|
||||||
|
<div v-for="(block, x) in row" class="block" :class="[block.type]" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div id="player" :class="[player.direction]" />
|
||||||
|
<div id="level-indicator">
|
||||||
|
x:{{ floorX }}, y:{{ floorY }}
|
||||||
|
<template v-if="moving !== false">({{clock}})</template>
|
||||||
|
<template v-else>(PAUSED)</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import throttle from 'lodash/throttle'
|
||||||
|
import MountainBackground from './Background'
|
||||||
|
import Level from './level'
|
||||||
|
import { Moveable } from './physics'
|
||||||
|
import {
|
||||||
|
BLOCK_SIZE,
|
||||||
|
RECIPROCAL,
|
||||||
|
STAGE_WIDTH,
|
||||||
|
STAGE_HEIGHT,
|
||||||
|
PLAYER_X,
|
||||||
|
PLAYER_Y
|
||||||
|
} from './level/def'
|
||||||
|
|
||||||
|
const level = new Level(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)
|
||||||
|
const player = new Moveable(PLAYER_X, PLAYER_Y)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'field',
|
||||||
|
components: { MountainBackground },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
player,
|
||||||
|
x: 0,
|
||||||
|
y: 12,
|
||||||
|
inputX: 0,
|
||||||
|
inputY: 0,
|
||||||
|
time: 250,
|
||||||
|
moving: false,
|
||||||
|
lastTick: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.lastTick = performance.now()
|
||||||
|
this.move(this.lastTick)
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rows () { return level.grid(this.floorX, this.floorY) },
|
||||||
|
surroundings () {
|
||||||
|
const px = PLAYER_X
|
||||||
|
const py = PLAYER_Y
|
||||||
|
const at = this.rows[py][px]
|
||||||
|
const left = this.rows[py][px]
|
||||||
|
const right = this.rows[py][px + 1]
|
||||||
|
const up = this.rows[py - 1][px] || at
|
||||||
|
const down = this.rows[py + 1][px]
|
||||||
|
|
||||||
|
return { at, left, right, up, down }
|
||||||
|
},
|
||||||
|
blocked () {
|
||||||
|
const { at, left, right, up, down } = this.surroundings
|
||||||
|
|
||||||
|
return {
|
||||||
|
at: !at.walkable,
|
||||||
|
left: !left.walkable,
|
||||||
|
right: !right.walkable,
|
||||||
|
up: !up.walkable,
|
||||||
|
down: !down.walkable
|
||||||
|
}
|
||||||
|
},
|
||||||
|
floorX () { return Math.floor(this.x) },
|
||||||
|
floorY () { return Math.floor(this.y) },
|
||||||
|
tx () { return (this.x - this.floorX) * -BLOCK_SIZE },
|
||||||
|
ty () { return (this.y - this.floorY) * -BLOCK_SIZE },
|
||||||
|
daytimeClass () {
|
||||||
|
const t = this.time
|
||||||
|
if (t >= 900 || t < 80) return "night"
|
||||||
|
|
||||||
|
if (t >= 80 && t < 120) return "morning0"
|
||||||
|
if (t >= 120 && t < 150) return "morning1"
|
||||||
|
if (t >= 150 && t < 240) return "morning2"
|
||||||
|
|
||||||
|
if (t >= 700 && t < 800) return "evening0"
|
||||||
|
if (t >= 800 && t < 850) return "evening1"
|
||||||
|
if (t >= 850 && t < 900) return "evening2"
|
||||||
|
|
||||||
|
return "day"
|
||||||
|
},
|
||||||
|
clock () {
|
||||||
|
const t = this.time * 86.4 // 1000 ticks to 86400 seconds (per day)
|
||||||
|
const h = ~~(t / 3600.0)
|
||||||
|
const m = ~~((t / 3600.0 - h) * 60.0)
|
||||||
|
return `${(h + 2) % 24}:${m < 10 ? '0' : ''}${m}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
move (thisTick) {
|
||||||
|
this.moving = requestAnimationFrame(this.move)
|
||||||
|
|
||||||
|
// keep roughly 20 fps
|
||||||
|
if (thisTick - this.lastTick < 50) return
|
||||||
|
|
||||||
|
// set time of day in ticks
|
||||||
|
this.time = (this.time + 0.1) % 1000
|
||||||
|
|
||||||
|
const player = this.player
|
||||||
|
const x = player.x
|
||||||
|
const y = player.y
|
||||||
|
|
||||||
|
let dx = player.vx * player.dir * RECIPROCAL
|
||||||
|
let dy = player.vy * RECIPROCAL
|
||||||
|
|
||||||
|
// don't walk / fall into blocks
|
||||||
|
if (dx > 0 && this.blocked.right) dx = 0
|
||||||
|
if (dx < 0 && this.blocked.left) dx = 0
|
||||||
|
if (dy > 0 && this.blocked.down) dy = 0
|
||||||
|
if (dy < 0 && this.blocked.up) dy = 0
|
||||||
|
|
||||||
|
// don't walk, work!
|
||||||
|
if (!this.inputY && this.digging) {
|
||||||
|
dx = 0
|
||||||
|
this.dig()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.x += dx
|
||||||
|
this.y += dy
|
||||||
|
this.lastTick = thisTick
|
||||||
|
},
|
||||||
|
dig () {
|
||||||
|
console.log('dig', this.playerDirection, this.surroundings[this.playerDirection])
|
||||||
|
// lets not bother with invincible blocks (like air or cave)
|
||||||
|
if (this.surroundings[this.playerDirection].hp >= Infinity) return
|
||||||
|
|
||||||
|
const px = this.floorX + PLAYER_X
|
||||||
|
const py = this.floorY + PLAYER_Y
|
||||||
|
const block = {...this.surroundings[this.playerDirection]}
|
||||||
|
|
||||||
|
block.hp--
|
||||||
|
level.change(py, px, block)
|
||||||
|
},
|
||||||
|
togglePause () {
|
||||||
|
if (this.moving === false) { // is paused
|
||||||
|
this.move()
|
||||||
|
} else {
|
||||||
|
cancelAnimationFrame(this.moving)
|
||||||
|
this.moving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style src="./assets/field.css" />
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--block-size: 32px;
|
||||||
|
--field-width: 1024px;
|
||||||
|
--field-height: 576px;
|
||||||
|
--spare-blocks: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#level-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#player {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(var(--field-width) / 2);
|
||||||
|
top: calc(var(--field-height) / 2);
|
||||||
|
background-image: url(./assets/dwarf_right.png);
|
||||||
|
}
|
||||||
|
#player.right { background-image: url(./assets/dwarf_right.png); }
|
||||||
|
#player.left { background-image: url(./assets/dwarf_left.png); }
|
||||||
|
#player.up { background-image: url(./assets/dwarf_back.png); }
|
||||||
|
#player.down { background-image: url(./assets/dwarf_back.png); }
|
||||||
|
#player, .block {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: var(--block-size);
|
||||||
|
height: var(--block-size);
|
||||||
|
background-color: transparent;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
#wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||||
|
left: calc(var(--block-size) * (var(--spare-blocks) / -2));
|
||||||
|
width: calc(var(--field-width) + var(--spare-blocks) * var(--block-size));
|
||||||
|
height: calc(var(--field-height) + var(--spare-blocks) * var(--block-size));
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
</style>
|
66
src/old/level/def.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
export const BLOCK_SIZE = 32 // each block is 32̨̣̌̇x32 pixel in size and equals 1m
|
||||||
|
export const RECIPROCAL = 1 / BLOCK_SIZE
|
||||||
|
|
||||||
|
export const STAGE_WIDTH = 32 // 32*32 = 1024 pixel wide stage
|
||||||
|
export const STAGE_HEIGHT = ~~(STAGE_WIDTH * 0.5625) // 16:9 😎
|
||||||
|
|
||||||
|
// the player position is fixed to the middle of the x axis
|
||||||
|
export const PLAYER_X = ~~(STAGE_WIDTH / 2) + 1
|
||||||
|
export const PLAYER_Y = ~~(STAGE_HEIGHT * 0.5) // fall from the center
|
||||||
|
|
||||||
|
export const GRAVITY = 10 // blocks per second
|
||||||
|
|
||||||
|
export const type = {
|
||||||
|
air: {type: 'air', hp: Infinity, walkable: true},
|
||||||
|
grass: {type: 'grass', hp: 1, walkable: false},
|
||||||
|
|
||||||
|
tree_top_left: {type: 'tree_top_left', hp: 5, walkable: true},
|
||||||
|
tree_top_middle: {type: 'tree_top_middle', hp: 5, walkable: true},
|
||||||
|
tree_top_right: {type: 'tree_top_right', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
tree_crown_left: {type: 'tree_crown_left', hp: 5, walkable: true},
|
||||||
|
tree_crown_middle: {type: 'tree_crown_middle', hp: 5, walkable: true, climbable: true},
|
||||||
|
tree_crown_right: {type: 'tree_crown_right', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
tree_trunk_left: {type: 'tree_trunk_left', hp: 5, walkable: true},
|
||||||
|
tree_trunk_middle: {type: 'tree_trunk_middle', hp: 5, walkable: true, climbable: true},
|
||||||
|
tree_trunk_right: {type: 'tree_trunk_right', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
tree_root_left: {type: 'tree_root_left', hp: 5, walkable: true},
|
||||||
|
tree_root_middle: {type: 'tree_root_middle', hp: 5, walkable: true, climbable: true},
|
||||||
|
tree_root_right: {type: 'tree_root_right', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
tree_top_left_mixed: {type: 'tree_top_left_mixed', hp: 5, walkable: true},
|
||||||
|
tree_crown_left_mixed: {type: 'tree_crown_left_mixed', hp: 5, walkable: true},
|
||||||
|
tree_trunk_left_mixed: {type: 'tree_trunk_left_mixed', hp: 5, walkable: true},
|
||||||
|
tree_root_left_mixed: {type: 'tree_root_left_mixed', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
tree_top_right_mixed: {type: 'tree_top_right_mixed', hp: 5, walkable: true},
|
||||||
|
tree_crown_right_mixed: {type: 'tree_crown_right_mixed', hp: 5, walkable: true},
|
||||||
|
tree_trunk_right_mixed: {type: 'tree_trunk_right_mixed', hp: 5, walkable: true},
|
||||||
|
tree_root_right_mixed: {type: 'tree_root_right_mixed', hp: 5, walkable: true},
|
||||||
|
|
||||||
|
soil: {type: 'soil', hp: 2, walkable: false},
|
||||||
|
soil_gravel: {type: 'soil_gravel', hp: 5, walkable: false},
|
||||||
|
stone_gravel: {type: 'stone_gravel', hp: 5, walkable: false},
|
||||||
|
stone: {type: 'stone', hp: 10, walkable: false},
|
||||||
|
bedrock: {type: 'bedrock', hp: 25, walkable: false},
|
||||||
|
cave: {type: 'cave', hp: Infinity, walkable: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const level = {
|
||||||
|
treeTop: 24,
|
||||||
|
ground: 28,
|
||||||
|
rock: 32,
|
||||||
|
underground: 48,
|
||||||
|
cave_max: 250
|
||||||
|
}
|
||||||
|
|
||||||
|
export const probability = {
|
||||||
|
tree: 0.2,
|
||||||
|
soil_hole: 0.3,
|
||||||
|
soil_gravel: 0.2,
|
||||||
|
stone_gravel: 0.1,
|
||||||
|
cave: 0.5,
|
||||||
|
fray: 0.4
|
||||||
|
}
|
58
src/old/level/first-iteration.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import {type as T, level as L, probability as P} from './def'
|
||||||
|
|
||||||
|
export default class BlockGen {
|
||||||
|
constructor (noiseGen) {
|
||||||
|
this.rand = (x, y) => 0.5 + 0.5 * noiseGen.noise2D(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
level (level, column, row, previousRow) {
|
||||||
|
for (let i = 0; i < row.length; i++) {
|
||||||
|
row[i] = this.block(level, column + i, row[i], row[i - 1], previousRow[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block (level, column, current, before, above) {
|
||||||
|
if (level < L.peak) return this.air()
|
||||||
|
|
||||||
|
const r = this.rand(level, column)
|
||||||
|
if (level < L.ground) {
|
||||||
|
if (level === L.treeTop) return this.treeTop(r)
|
||||||
|
return this.air()
|
||||||
|
}
|
||||||
|
if (level < L.rock) return this.ground(r)
|
||||||
|
if (level < L.underground) return this.rock(r)
|
||||||
|
return this.underground(r, above, before, level - L.underground)
|
||||||
|
}
|
||||||
|
|
||||||
|
// always returns air
|
||||||
|
air () {
|
||||||
|
return T.air
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns mostly air, but sometimes starts a tree
|
||||||
|
treeTop (r) {
|
||||||
|
if (r < P.tree) return T.tree_top_middle
|
||||||
|
return T.air
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns mostly soil and grass, sometimes gravel and sometimes air
|
||||||
|
ground (r) {
|
||||||
|
if (r < P.soil_gravel) return T.soil_gravel
|
||||||
|
return T.soil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns mostly stones, sometimes gravel
|
||||||
|
rock (r) {
|
||||||
|
return r < P.stone_gravel ? T.stone_gravel : T.stone
|
||||||
|
}
|
||||||
|
|
||||||
|
// return mostly bedrock, sometimes caves, depending on the level
|
||||||
|
underground (r, above, before, level) {
|
||||||
|
// the probability for a cave rises with the level
|
||||||
|
const a = P.cave / L.cave_max**2
|
||||||
|
const p = Math.min(P.cave, a * level**2)
|
||||||
|
|
||||||
|
if (r < p) return T.cave
|
||||||
|
return T.bedrock
|
||||||
|
}
|
||||||
|
}
|
44
src/old/level/index.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import SeedRng from 'seedrandom'
|
||||||
|
import SimplexNoise from 'open-simplex-noise'
|
||||||
|
|
||||||
|
import {type as T, level as L} from './def'
|
||||||
|
import BlockGen from './first-iteration'
|
||||||
|
import BlockExt from './second-iteration'
|
||||||
|
import PlayerChanges from './third-iteration'
|
||||||
|
|
||||||
|
export default class Level {
|
||||||
|
constructor (width, height, seed = 'super random seed') {
|
||||||
|
const random = SeedRng(seed)
|
||||||
|
const noiseGen = new SimplexNoise(parseInt(seed, 32))
|
||||||
|
this._w = width
|
||||||
|
this._h = height
|
||||||
|
this._grid = new Array(this._h)
|
||||||
|
this.blockGen = new BlockGen(noiseGen)
|
||||||
|
this.blockExt = new BlockExt(noiseGen)
|
||||||
|
this.playerChanges = new PlayerChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
change (level, column, newBlock) {
|
||||||
|
if (newBlock.hp <= 0) {
|
||||||
|
newBlock = level > L.rock ? { ...T.cave } : { ...T.air }
|
||||||
|
}
|
||||||
|
this.playerChanges.apply(level, column, newBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
grid (x, y) {
|
||||||
|
this.generate(x, y, this._w, this._h)
|
||||||
|
return this._grid
|
||||||
|
}
|
||||||
|
|
||||||
|
generate (column, y, width, height) {
|
||||||
|
for (let i = 0; i < height; i++) {
|
||||||
|
const level = y + i
|
||||||
|
const row = Array(width)
|
||||||
|
const previousRow = this._grid[i - 1] || Array()
|
||||||
|
this.blockGen.level(level, column, row, previousRow)
|
||||||
|
this.blockExt.level(level, column, row, previousRow)
|
||||||
|
this.playerChanges.level(level, column, row)
|
||||||
|
this._grid[i] = row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
src/old/level/second-iteration.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import {type as T, level as L, probability as P} from './def'
|
||||||
|
|
||||||
|
export default class BlockExt {
|
||||||
|
constructor (noiseGen) {
|
||||||
|
this.rand = (x, y) => 0.5 + 0.5 * noiseGen.noise2D(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
level (level, column, row, previousRow) {
|
||||||
|
for (let i = 0; i < row.length; i++) {
|
||||||
|
const r = Math.abs(this.rand(level, column + i))
|
||||||
|
|
||||||
|
if (level < L.ground) this.trees(r, i, row, previousRow, level)
|
||||||
|
else if (level < L.rock) this.ground(r, i, row, previousRow)
|
||||||
|
else if (level < L.underground) this.rock(r, i, row, previousRow)
|
||||||
|
else this.underground(r, i, row, previousRow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trees (r, i, row, previousRow, level) {
|
||||||
|
const max = row.length - 1
|
||||||
|
|
||||||
|
if (row[i] === T.tree_top_middle) {
|
||||||
|
if (i) {
|
||||||
|
if (row[i - 1] === T.tree_top_right) row[i - 1] = T.tree_top_left_mixed
|
||||||
|
else row[i - 1] = T.tree_top_left
|
||||||
|
}
|
||||||
|
if (i < max) row[i + 1] = T.tree_top_right
|
||||||
|
|
||||||
|
} else if (previousRow[i] === T.tree_top_middle) {
|
||||||
|
row[i] = T.tree_crown_middle
|
||||||
|
if (i) {
|
||||||
|
if (row[i - 1] === T.tree_crown_right) row[i - 1] = T.tree_crown_left_mixed
|
||||||
|
else row[i - 1] = T.tree_crown_left
|
||||||
|
}
|
||||||
|
if (i < max) row[i + 1] = T.tree_crown_right
|
||||||
|
} else if (previousRow[i] === T.tree_crown_middle) {
|
||||||
|
row[i] = T.tree_trunk_middle
|
||||||
|
if (i) {
|
||||||
|
if (row[i - 1] === T.tree_trunk_right) row[i - 1] = T.tree_trunk_left_mixed
|
||||||
|
else row[i - 1] = T.tree_trunk_left
|
||||||
|
}
|
||||||
|
if (i < max) row[i + 1] = T.tree_trunk_right
|
||||||
|
} else if (previousRow[i] === T.tree_trunk_middle) {
|
||||||
|
row[i] = T.tree_root_middle
|
||||||
|
if (i) {
|
||||||
|
if (row[i - 1] === T.tree_root_right) row[i - 1] = T.tree_root_left_mixed
|
||||||
|
else row[i - 1] = T.tree_root_left
|
||||||
|
}
|
||||||
|
if (i < max) row[i + 1] = T.tree_root_right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ground (r, i, row, previousRow) {
|
||||||
|
const tree_parts = [T.tree_root_left, T.tree_root_middle, T.tree_root_right]
|
||||||
|
if (previousRow[i] === T.air) {
|
||||||
|
if (r < P.soil_hole) row[i] = T.air
|
||||||
|
if (row[i] === T.soil) row[i] = T.grass
|
||||||
|
} else if (tree_parts.indexOf(previousRow[i]) >= 0) {
|
||||||
|
if (row[i] === T.soil) row[i] = T.grass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rock (r, i, row, previousRow) {
|
||||||
|
if (previousRow[i] === T.soil && r < P.fray) row[i] = T.soil
|
||||||
|
}
|
||||||
|
|
||||||
|
underground (r, i, row, previousRow) {
|
||||||
|
if (previousRow[i] === T.stone && r < P.fray) row[i] = T.stone
|
||||||
|
}
|
||||||
|
}
|
23
src/old/level/third-iteration.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
export default class PlayerChanges {
|
||||||
|
constructor () {
|
||||||
|
this.changes = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey (level, column) {
|
||||||
|
return `${column}.${level}`
|
||||||
|
}
|
||||||
|
|
||||||
|
apply (level, column, newBlock) {
|
||||||
|
const key = this.getKey(level, column)
|
||||||
|
this.changes[key] = newBlock
|
||||||
|
console.log('applied', level, column, newBlock, this.changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
level (level, column, row) {
|
||||||
|
for (let i = 0; i < row.length; i++) {
|
||||||
|
const key = this.getKey(level - 1, column + i)
|
||||||
|
const change = this.changes[key]
|
||||||
|
if (change) row[i] = change
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/old/main.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
Vue.directive('keep-focussed', {
|
||||||
|
inserted (el, binding) {
|
||||||
|
el.focus()
|
||||||
|
el.addEventListener('blur', () => el.focus())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
22
src/old/physics.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { GRAVITY } from './level/def'
|
||||||
|
|
||||||
|
/** physics gets input like
|
||||||
|
instance of Moveable,
|
||||||
|
position: [x, y],
|
||||||
|
surroundings: [top, right, bottom, left] where each is a block type
|
||||||
|
and updates the Moveable instance values accordingly
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class Moveable {
|
||||||
|
constructor (x, y, direction = 1) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.dir = direction
|
||||||
|
this.vx = 0
|
||||||
|
this.vy = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get direction () {
|
||||||
|
return this.dir > 0 ? 'left' : 'right'
|
||||||
|
}
|
||||||
|
}
|
130
src/old/solar-quartet.js
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/* Adapted from the original "Solar Quartet" by y0natan
|
||||||
|
* https://codepen.io/y0natan/pen/MVvxBM
|
||||||
|
* https://js1k.com/2018-coins/demo/3075
|
||||||
|
*/
|
||||||
|
|
||||||
|
// sunY sets the height of the sun and with this the time of the day
|
||||||
|
// where 0 is lowest (night) and -100 is highest (day), other values are possible
|
||||||
|
// but don't make much sense / difference
|
||||||
|
export default function drawFrame (canvas, ctx, width, height, grCanvas, grCtx, grWidth, grHeight, frame, sunY) {
|
||||||
|
// reset canvas state
|
||||||
|
canvas.width = width
|
||||||
|
canvas.height = height
|
||||||
|
grCanvas.width = grWidth
|
||||||
|
grCanvas.height = grHeight
|
||||||
|
|
||||||
|
const sunCenterX = grWidth / 2
|
||||||
|
const sunCenterY = grHeight / 2 + sunY
|
||||||
|
// Set the godrays' context fillstyle to a newly created gradient
|
||||||
|
// which we also run through our abbreviator.
|
||||||
|
let emissionGradient = grCtx.createRadialGradient(
|
||||||
|
sunCenterX, sunCenterY, // The sun's center.
|
||||||
|
0, // Start radius.
|
||||||
|
sunCenterX, sunCenterY, // The sun's center.
|
||||||
|
44 // End radius.
|
||||||
|
)
|
||||||
|
grCtx.fillStyle = emissionGradient
|
||||||
|
|
||||||
|
// Now we addColorStops. This needs to be a dark gradient because our
|
||||||
|
// godrays effect will basically overlay it on top of itself many many times,
|
||||||
|
// so anything lighter will result in lots of white.
|
||||||
|
// If you're not space-bound you can add another stop or two, maybe fade out to black,
|
||||||
|
// but this actually looks good enough.
|
||||||
|
|
||||||
|
// a black "gradient" means no emission, so we fade to black as transition to night or day
|
||||||
|
let emissionStrength = 1.0
|
||||||
|
if (sunY > -30) emissionStrength -= Math.max((30 + sunY) / 5, 0.0)
|
||||||
|
else if (sunY < -60) emissionStrength += Math.min(1 + (60 + sunY) / 5, 0.0)
|
||||||
|
|
||||||
|
emissionGradient.addColorStop(.1, `hsl(30,50%,${3.1 * emissionStrength}%)`) // pixels in radius 0 to 4.4 (44 * .1).
|
||||||
|
emissionGradient.addColorStop(.2, `hsl(12,71%,${1.4 * emissionStrength}%)`) // pixels in radius 0 to 4.4 (44 * .1).
|
||||||
|
|
||||||
|
// Now paint the gradient all over our godrays canvas.
|
||||||
|
grCtx.fillRect(0, 0, grWidth, grHeight)
|
||||||
|
|
||||||
|
// And set the fillstyle to black, we'll use it to paint our occlusion (mountains).
|
||||||
|
grCtx.fillStyle = '#000'
|
||||||
|
|
||||||
|
// Paint the sky
|
||||||
|
const skyGradient = ctx.createLinearGradient(0, 0, 0, height)
|
||||||
|
|
||||||
|
// hue from blue to red depending on the suns position
|
||||||
|
const skyHue = 360 + sunY
|
||||||
|
// lesser saturation at day so that the red fades away
|
||||||
|
const skySaturation = 100 + sunY
|
||||||
|
// darker at night
|
||||||
|
const skyLightness = Math.min(sunY * -1 - 10, 55)
|
||||||
|
|
||||||
|
const skyHSLTop = `hsl(220, 70%, ${skyLightness}%)`
|
||||||
|
const skyHSLBottom = `hsl(${skyHue}, ${skySaturation}%, ${skyLightness}%)`
|
||||||
|
skyGradient.addColorStop(0, skyHSLTop)
|
||||||
|
skyGradient.addColorStop(.7, skyHSLBottom)
|
||||||
|
|
||||||
|
ctx.fillStyle = skyGradient
|
||||||
|
ctx.fillRect(0, 0, width, height)
|
||||||
|
|
||||||
|
// Our mountains will be made by summing up sine waves of varying frequencies and amplitudes.
|
||||||
|
function mountainHeight(position, roughness) {
|
||||||
|
// Our frequencies (prime numbers to avoid extra repetitions).
|
||||||
|
// TODO: play with the numbers
|
||||||
|
let frequencies = [1721, 947, 547, 233, 73, 31, 7]
|
||||||
|
// Add them up.
|
||||||
|
return frequencies.reduce((height, freq) => height * roughness - Math.cos(freq * position), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw 4 layers of mountains.
|
||||||
|
for(let i = 0; i < 4; i++) {
|
||||||
|
// Set the main canvas fillStyle to a shade of green-brown with variable lightness
|
||||||
|
// depending on sunY and depth
|
||||||
|
|
||||||
|
if (sunY > -60) {
|
||||||
|
ctx.fillStyle = `hsl(5, 23%, ${33*emissionStrength - i*6*emissionStrength}%)`
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = `hsl(${220 - i*40}, 23%, ${33-i*6}%)`
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each column in our canvas...
|
||||||
|
for(let x = width; x--;) {
|
||||||
|
// Ok, I don't really remember the details here, basically the (frame+frame*i*i) makes the
|
||||||
|
// near mountains move faster than the far ones. We divide by large numbers because our
|
||||||
|
// mountains repeat at position 1/7*Math.PI*2 or something like that...
|
||||||
|
let mountainPosition = (frame * 2 * i**2) / 1000 + x / 2000;
|
||||||
|
// Make further mountains more jagged, adds a bit of realism and also makes the godrays
|
||||||
|
// look nicer.
|
||||||
|
let mountainRoughness = i / 19 - .5;
|
||||||
|
// 128 is the middle, i * 25 moves the nearer mountains lower on the screen.
|
||||||
|
let y = 128 + i * 25 + mountainHeight(mountainPosition, mountainRoughness) * 45;
|
||||||
|
// Paint a 1px-wide rectangle from the mountain's top to below the bottom of the canvas.
|
||||||
|
ctx.fillRect(x, y, 1, 999); // 999 can be any large number...
|
||||||
|
// Paint the same thing in black on the godrays emission canvas, which is 1/4 the size,
|
||||||
|
// and move it one pixel down (otherwise there can be a tiny underlit space between the
|
||||||
|
// mountains and the sky).
|
||||||
|
grCtx.fillRect(x/4, y/4+1, 1, 999);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The godrays are generated by adding up RGB values, gCt is the bane of all js golfers -
|
||||||
|
// globalCompositeOperation. Set it to 'lighter' on both canvases.
|
||||||
|
ctx.globalCompositeOperation = grCtx.globalCompositeOperation = 'lighter';
|
||||||
|
|
||||||
|
// NOW - let's light this motherfucker up! We'll make several passes over our emission canvas,
|
||||||
|
// each time adding an enlarged copy of it to itself so at the first pass we get 2 copies, then 4,
|
||||||
|
// then 8, then 16 etc... We square our scale factor at each iteration.
|
||||||
|
for (let scaleFactor = 1.07; scaleFactor < 5; scaleFactor *= scaleFactor) {
|
||||||
|
// The x, y, width and height arguments for drawImage keep the light source in the same
|
||||||
|
// spot on the enlarged copy. It basically boils down to multiplying a 2D matrix by itself.
|
||||||
|
// There's probably a better way to do this, but I couldn't figure it out.
|
||||||
|
// For reference, here's an AS3 version (where BitmapData:draw takes a matrix argument):
|
||||||
|
// https://github.com/yonatan/volumetrics/blob/d3849027213e9499742cc4dfd2838c6032f4d9d3/src/org/zozuar/volumetrics/EffectContainer.as#L208-L209
|
||||||
|
grCtx.drawImage(
|
||||||
|
grCanvas,
|
||||||
|
(grWidth - grWidth * scaleFactor) / 2,
|
||||||
|
(grHeight - grHeight * scaleFactor) / 2 - sunY * scaleFactor + sunY,
|
||||||
|
grWidth * scaleFactor,
|
||||||
|
grHeight * scaleFactor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw godrays to output canvas (whose globalCompositeOperation is already set to 'lighter').
|
||||||
|
ctx.drawImage(grCanvas, 0, 0, width, height);
|
||||||
|
}
|
54
src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
export type ItemQuality = 'wood' | 'iron' | 'silver' | 'gold' | 'diamond'
|
||||||
|
export type ItemType = 'tool' | 'weapon' | 'block' | 'ore'
|
||||||
|
|
||||||
|
export type DropItem =
|
||||||
|
| 'Shovel' | 'Pick Axe' | 'Sword'
|
||||||
|
| 'leaves' | 'dirt' | 'wood' | 'stone' | 'gravel'
|
||||||
|
| 'coal' | 'iron' | 'silver' | 'gold' | 'ruby' | 'diamond' | 'emerald'
|
||||||
|
|
||||||
|
export interface Item {
|
||||||
|
name: DropItem
|
||||||
|
type: ItemType
|
||||||
|
icon: string
|
||||||
|
hasQuality?: boolean
|
||||||
|
builds?: BlockType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Block = {
|
||||||
|
type: string, // what is it?
|
||||||
|
hp: number, // how long do I need to hit it?
|
||||||
|
walkable: boolean, // can I walk through it?
|
||||||
|
climbable?: boolean, // can I climb it?
|
||||||
|
transparent?: boolean, // can I see through it?
|
||||||
|
illuminated?: boolean, // is it glowing?
|
||||||
|
drops?: DropItem, // what do I get, when loot it?
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlockType =
|
||||||
|
| 'air' | 'grass'
|
||||||
|
| 'treeCrown' | 'treeLeaves' | 'treeTrunk' | 'treeRoot'
|
||||||
|
| 'soil' | 'soilGravel' | 'stone' | 'stoneGravel'
|
||||||
|
| 'bedrock' | 'cave'
|
||||||
|
| 'brickWall'
|
||||||
|
|
||||||
|
export interface InventoryItem extends Item {
|
||||||
|
amount: number
|
||||||
|
quality: ItemQuality | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Moveable {
|
||||||
|
x: number // position on x-axis (fixed for the player)
|
||||||
|
y: number // position on y-axis (fixed for the player)
|
||||||
|
lastDir: number // store last face direction
|
||||||
|
vx: number // velocity on the x-axis
|
||||||
|
vy: number // velocity on the y-axis
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Npc extends Moveable {
|
||||||
|
hostile: boolean
|
||||||
|
inventory: InventoryItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Player extends Moveable {
|
||||||
|
inventory: InventoryItem[]
|
||||||
|
}
|
15
tsconfig.app.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"],
|
||||||
|
"~cmp/*": ["./src/components/*"],
|
||||||
|
"~use/*": ["./src/composables/*"],
|
||||||
|
"~asset/*": ["./src/assets/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +1,14 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Node",
|
|
||||||
"strict": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"lib": [
|
|
||||||
"ESNext",
|
|
||||||
"DOM"
|
|
||||||
],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"types": [
|
|
||||||
"unplugin-vue-macros/macros-global"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"src/**/*.d.ts",
|
|
||||||
"src/**/*.tsx",
|
|
||||||
"src/**/*.vue"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"src/old",
|
|
||||||
],
|
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
{
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"types": ["node"]
|
||||||
},
|
}
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
}
|
||||||
|
|
11
tsconfig.vitest.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||||
|
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,21 @@
|
||||||
import { defineConfig } from 'vite'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
import VueMacros from 'unplugin-vue-macros/vite'
|
|
||||||
import Vue from '@vitejs/plugin-vue'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
VueMacros({
|
vue(),
|
||||||
plugins: {
|
vueDevTools(),
|
||||||
vue: Vue(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
'~cmp': fileURLToPath(new URL('./src/components', import.meta.url)),
|
||||||
|
'~use': fileURLToPath(new URL('./src/composables', import.meta.url)),
|
||||||
|
'~asset': fileURLToPath(new URL('./src/assets', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
14
vitest.config.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|