commit first major batch of work
This commit is contained in:
parent
561aa08d8a
commit
2c68f44e88
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.nyc_output
|
||||||
|
pnpm-lock.yaml
|
||||||
|
public/build
|
||||||
|
public/bundle.*
|
3
.storybook/main.js
Normal file
3
.storybook/main.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
stories: [ '../src/**/*stories.js' ]
|
||||||
|
};
|
142
package.json
142
package.json
@ -1,12 +1,136 @@
|
|||||||
{
|
{
|
||||||
"name": "shipyard",
|
"author": "Yanick Champoux <yanick@babyl.ca>",
|
||||||
"version": "1.0.0",
|
"dependencies": {
|
||||||
"description": "",
|
"@babel/cli": "^7.10.1",
|
||||||
"main": "index.js",
|
"@babel/core": "^7.10.2",
|
||||||
"scripts": {
|
"@babel/node": "^7.10.1",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"@material/animation": "^7.0.0",
|
||||||
|
"@material/base": "^7.0.0",
|
||||||
|
"@material/density": "^7.0.0",
|
||||||
|
"@material/feature-targeting": "^7.0.0",
|
||||||
|
"@material/floating-label": "^7.0.0",
|
||||||
|
"@material/line-ripple": "^7.0.0",
|
||||||
|
"@material/notched-outline": "^7.0.0",
|
||||||
|
"@material/ripple": "^7.0.0",
|
||||||
|
"@material/rtl": "^7.0.0",
|
||||||
|
"@material/shape": "^7.0.0",
|
||||||
|
"@material/textfield": "^6.0.0",
|
||||||
|
"@material/theme": "^7.0.0",
|
||||||
|
"@material/typography": "^7.0.0",
|
||||||
|
"@smui/floating-label": "^1.0.0-beta.21",
|
||||||
|
"@smui/line-ripple": "^1.0.0-beta.21",
|
||||||
|
"@smui/notched-outline": "^1.0.0-beta.21",
|
||||||
|
"@smui/textfield": "^1.0.0-beta.21",
|
||||||
|
"@storybook/svelte": "^5.3.19",
|
||||||
|
"babel-loader": "^8.1.0",
|
||||||
|
"css-loader": "^3.6.0",
|
||||||
|
"dart-sass": "^1.25.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
|
"node-sass": "^4.14.1",
|
||||||
|
"prettier": "2.0.5",
|
||||||
|
"prettier-plugin-svelte": "1.1.0",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"reselect": "^4.0.0",
|
||||||
|
"rollup-plugin-css-only": "^2.1.0",
|
||||||
|
"rollup-plugin-postcss": "^3.1.2",
|
||||||
|
"rollup-plugin-scss": "^2.5.0",
|
||||||
|
"sass": "^1.26.9",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"sirv-cli": "^0.4.4",
|
||||||
|
"style-loader": "^1.2.1",
|
||||||
|
"svelte-loader": "^2.13.6",
|
||||||
|
"svelte3-redux": "^0.3.0",
|
||||||
|
"ts-action": "^11.0.0",
|
||||||
|
"updeep": "^1.2.0",
|
||||||
|
"updux": "^2.1.0",
|
||||||
|
"webpack": "^4.43.0",
|
||||||
|
"webpack-cli": "^3.3.12",
|
||||||
|
"webpack-dev-server": "^3.11.0"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"description": "Full Thrust ship builder",
|
||||||
"author": "",
|
"devDependencies": {
|
||||||
"license": "ISC"
|
"@rollup/plugin-alias": "^3.1.1",
|
||||||
|
"@rollup/plugin-commonjs": "^12.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^8.0.0",
|
||||||
|
"eslint": "7.4.0",
|
||||||
|
"eslint-config-prettier": "6.11.0",
|
||||||
|
"eslint-plugin-babel": "5.3.1",
|
||||||
|
"eslint-plugin-lodash": "^7.1.0",
|
||||||
|
"eslint-plugin-prettier": "3.1.4",
|
||||||
|
"eslint-plugin-svelte3": "2.7.3",
|
||||||
|
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"rollup": "^2.3.4",
|
||||||
|
"rollup-plugin-livereload": "^1.0.0",
|
||||||
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
|
"rollup-plugin-terser": "^5.1.2",
|
||||||
|
"svelte": "^3.0.0",
|
||||||
|
"svelte-material-ui": "^1.0.0-beta.21",
|
||||||
|
"tap": "^14.10.7"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"env": {
|
||||||
|
"amd": true,
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"prettier",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:you-dont-need-lodash-underscore/compatible",
|
||||||
|
"plugin:lodash/recommended"
|
||||||
|
],
|
||||||
|
"ignorePatterns": [
|
||||||
|
"src/node_modules"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"*.svelte"
|
||||||
|
],
|
||||||
|
"processor": "svelte3/svelte3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"modules": true
|
||||||
|
},
|
||||||
|
"ecmaVersion": "2020",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"svelte3"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"lodash/prefer-lodash-method": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"game"
|
||||||
|
],
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "index.js",
|
||||||
|
"name": "shipyard",
|
||||||
|
"prettier": {
|
||||||
|
"svelteSortOrder": "markup-scripts-styles",
|
||||||
|
"svelteStrictMode": false
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_ENV=production webpack",
|
||||||
|
"lint": "npm-run-all --parallel \"lint:prettier -- {@}\" \"lint:eslint -- {@}\" --",
|
||||||
|
"lint:eslint": "./scripts/eslint",
|
||||||
|
"lint:eslint:fix": "npm run lint:eslint -- --fix",
|
||||||
|
"lint:fix": "npm-run-all \"lint:eslint:fix -- {@}\" \"lint:prettier:fix -- {@}\" --",
|
||||||
|
"lint:prettier": "./scripts/prettier",
|
||||||
|
"lint:prettier:fix": "npm run lint:prettier -- --fix",
|
||||||
|
"package": "./scripts/merge_package.pl",
|
||||||
|
"start": "webpack-dev-server --open --content-base public",
|
||||||
|
"test": "tap 'src/**test.js' --no-coverage"
|
||||||
|
},
|
||||||
|
"tap": {
|
||||||
|
"coverage": false
|
||||||
|
},
|
||||||
|
"version": "0.0.1"
|
||||||
}
|
}
|
||||||
|
63
package.yml
Normal file
63
package.yml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
_merge:
|
||||||
|
- version
|
||||||
|
- dependencies
|
||||||
|
- devDependencies
|
||||||
|
|
||||||
|
name: shipyard
|
||||||
|
description: Full Thrust ship builder
|
||||||
|
|
||||||
|
author: 'Yanick Champoux <yanick@babyl.ca>'
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
build: NODE_ENV=production webpack
|
||||||
|
start: webpack-dev-server --open --content-base public
|
||||||
|
|
||||||
|
package: ./scripts/merge_package.pl
|
||||||
|
|
||||||
|
test: tap 'src/**test.js' --no-coverage
|
||||||
|
|
||||||
|
"lint:prettier": ./scripts/prettier
|
||||||
|
"lint:eslint": ./scripts/eslint
|
||||||
|
lint: npm-run-all --parallel "lint:prettier -- {@}" "lint:eslint -- {@}" --
|
||||||
|
|
||||||
|
"lint:prettier:fix": npm run lint:prettier -- --fix
|
||||||
|
"lint:eslint:fix": npm run lint:eslint -- --fix
|
||||||
|
"lint:fix": npm-run-all "lint:eslint:fix -- {@}" "lint:prettier:fix -- {@}" --
|
||||||
|
|
||||||
|
eslintConfig:
|
||||||
|
env:
|
||||||
|
amd: true
|
||||||
|
browser: true
|
||||||
|
es6: true
|
||||||
|
node: true
|
||||||
|
extends:
|
||||||
|
- prettier
|
||||||
|
- eslint:recommended
|
||||||
|
- plugin:you-dont-need-lodash-underscore/compatible
|
||||||
|
- plugin:lodash/recommended
|
||||||
|
ignorePatterns:
|
||||||
|
- src/node_modules
|
||||||
|
overrides:
|
||||||
|
- files:
|
||||||
|
- '*.svelte'
|
||||||
|
processor: svelte3/svelte3
|
||||||
|
parserOptions:
|
||||||
|
ecmaFeatures:
|
||||||
|
modules: true
|
||||||
|
ecmaVersion: '2020'
|
||||||
|
sourceType: module
|
||||||
|
plugins:
|
||||||
|
- svelte3
|
||||||
|
rules:
|
||||||
|
lodash/prefer-lodash-method: off
|
||||||
|
keywords: [ game ]
|
||||||
|
license: ISC
|
||||||
|
main: index.js
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
svelteSortOrder: markup-scripts-styles
|
||||||
|
svelteStrictMode: false
|
||||||
|
|
||||||
|
tap:
|
||||||
|
coverage: false
|
76
public/global.css
Normal file
76
public/global.css
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
:root {
|
||||||
|
--font-scale-8: calc(1rem/1.125/1.125);
|
||||||
|
--font-scale-9: calc(1rem/1.125);
|
||||||
|
--font-scale-10: 1rem;
|
||||||
|
--font-scale-11: calc(1rem * 1.125);
|
||||||
|
--font-scale-12: calc(1rem * 1.125 * 1.125);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(0,100,200);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: rgb(0,80,160);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button, select, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin: 0 0 0.5em 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #333;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:disabled):active {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
border-color: #666;
|
||||||
|
}
|
30
public/index.html
Normal file
30
public/index.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
|
<title>Svelte app</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,600,700">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Mono">
|
||||||
|
|
||||||
|
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||||
|
<link rel='stylesheet' href='/global.css'>
|
||||||
|
<link rel='stylesheet' href='/bundle.css'>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<link rel='stylesheet' href='/materialize/materialize.css'>
|
||||||
|
<script src="/materialize/materialize.js"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
process = { env: { NODE_ENV: 'production' } };
|
||||||
|
</script>
|
||||||
|
<script defer src='/bundle.js'></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
public/mass.svg
Normal file
5
public/mass.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version='1.0' encoding='iso-8859-1'?>
|
||||||
|
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512.003 512.003" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 512.003 512.003">
|
||||||
|
<path d="m510.838,471.439l-80-288c-3.852-13.852-16.461-23.438-30.836-23.438h-112v-6.781c28-12.379 48-40.498 48-73.219 0-44.109-35.891-80-80-80s-80,35.891-80,80c0,32.721 20,60.84 48,73.219v6.781h-112c-14.375,0-26.984,9.586-30.836,23.438l-80,288c-2.672,9.633-0.695,19.969 5.359,27.93 6.055,7.961 15.477,12.633 25.477,12.633h448c10,0 19.422-4.672 25.477-12.633 6.054-7.961 8.03-18.297 5.359-27.93zm-254.836-407.437c8.82-1.42109e-14 16,7.18 16,16s-7.18,16-16,16c-8.82,0-16-7.18-16-16s7.179-16 16-16z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 823 B |
102
rollup.config.js
Normal file
102
rollup.config.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import svelte from 'rollup-plugin-svelte';
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import alias from '@rollup/plugin-alias';
|
||||||
|
import livereload from 'rollup-plugin-livereload';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import css from 'rollup-plugin-css-only';
|
||||||
|
import postcss from 'rollup-plugin-postcss';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const postcssOptions = () => ({
|
||||||
|
extensions: ['.scss', '.sass'],
|
||||||
|
extract: false,
|
||||||
|
minimize: true,
|
||||||
|
use: [
|
||||||
|
['sass', {
|
||||||
|
includePaths: [
|
||||||
|
'./sass',
|
||||||
|
'./node_modules',
|
||||||
|
// This is only needed because we're using a local module. :-/
|
||||||
|
// Normally, you would not need this line.
|
||||||
|
path.resolve(__dirname, '..', 'node_modules')
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/main.js',
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: 'iife',
|
||||||
|
name: 'app',
|
||||||
|
file: 'public/build/bundle.js'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
alias({
|
||||||
|
entries: [
|
||||||
|
{ find: '~', replacement: './src' },
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
// scss(),
|
||||||
|
css({ output: 'public/build/import-bundle.css' }),
|
||||||
|
svelte({
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
dev: !production,
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file - better for performance
|
||||||
|
css: css => {
|
||||||
|
css.write('public/build/bundle.css');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
postcss(postcssOptions()),
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration -
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ['svelte']
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
|
||||||
|
// In dev mode, call `npm run start` once
|
||||||
|
// the bundle has been generated
|
||||||
|
!production && serve(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload('public'),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser()
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
chokidar: {
|
||||||
|
usePolling: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function serve() {
|
||||||
|
let started = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
writeBundle() {
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
|
||||||
|
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||||
|
stdio: ['ignore', 'inherit', 'inherit'],
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
36
scripts/eslint
Executable file
36
scripts/eslint
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
# check by default
|
||||||
|
my @mode;
|
||||||
|
|
||||||
|
my @files = grep {
|
||||||
|
$_ ne '--fix'or not (@mode = ('--fix'));
|
||||||
|
} @ARGV;
|
||||||
|
|
||||||
|
@files = qw/ src / unless @files;
|
||||||
|
|
||||||
|
my @ignore = (
|
||||||
|
qr/\.map$/,
|
||||||
|
qr!^docs/!,
|
||||||
|
qr/\.(css|html|md)$/,
|
||||||
|
qr/package\.(json|yaml)/,
|
||||||
|
qr#^scripts/#,
|
||||||
|
qr#^\.#,
|
||||||
|
qr#\.json$#
|
||||||
|
);
|
||||||
|
|
||||||
|
sub ignored {
|
||||||
|
my $file = shift;
|
||||||
|
|
||||||
|
for my $pattern ( @ignore ) {
|
||||||
|
return 1 if $file =~ $pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
exec 'eslint', @mode, grep {
|
||||||
|
not ignored($_)
|
||||||
|
} @files;
|
59
scripts/merge_package.pl
Executable file
59
scripts/merge_package.pl
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
I'm slightly fed up with `package.json`, because:
|
||||||
|
|
||||||
|
* the keys are ordering alphabetically, not logically.
|
||||||
|
* no comments.
|
||||||
|
* friggin' no trailing comma. And the quotes, my lord, the bloody
|
||||||
|
quotes...
|
||||||
|
|
||||||
|
Hence this script, which takes a `package.yaml`, and convert it
|
||||||
|
to `package.json`.
|
||||||
|
Sections, like `dependencies`, `devDependencies' and 'version' that we want to preserve can be given `_merge`. All data structures under the keys
|
||||||
|
given in `_merge` will have their `yaml` and `json` values merged,
|
||||||
|
with the `json` having priority.
|
||||||
|
|
||||||
|
By default `package.json` will be updated (like, that's the point isn't?).
|
||||||
|
For a dry run pass in a `-n` argument.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
use 5.24.0;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use File::Serialize;
|
||||||
|
use Hash::Merge qw/ merge /;
|
||||||
|
|
||||||
|
use feature qw/ postderef /;
|
||||||
|
use YAML::XS;
|
||||||
|
use Path::Tiny;
|
||||||
|
use JSON::PP qw//;
|
||||||
|
use JSON qw/ to_json /;
|
||||||
|
|
||||||
|
{
|
||||||
|
no warnings;
|
||||||
|
$YAML::XS::Boolean = 'JSON::PP';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $yaml = deserialize_file 'package.yml';
|
||||||
|
my $json = deserialize_file 'package.json';
|
||||||
|
|
||||||
|
my $mergers = delete $yaml->{_merge};
|
||||||
|
|
||||||
|
my $result = merge( { $json->%{@$mergers} }, $yaml );
|
||||||
|
|
||||||
|
my $encoder = JSON->new->pretty->canonical->space_before(0);
|
||||||
|
|
||||||
|
$json = $encoder->encode($result);
|
||||||
|
|
||||||
|
$json =~ s/(^|\G) / /g;
|
||||||
|
|
||||||
|
if( grep { $_ eq '-n' } @ARGV ) {
|
||||||
|
print $json;
|
||||||
|
} else {
|
||||||
|
path('package.json')->spew($json);
|
||||||
|
say "regenerated package.json"
|
||||||
|
}
|
16
scripts/prettier
Executable file
16
scripts/prettier
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
# check by default
|
||||||
|
my $mode = '-c';
|
||||||
|
|
||||||
|
my @files = grep {
|
||||||
|
$_ ne '--fix'or not ($mode = '--write');
|
||||||
|
} @ARGV;
|
||||||
|
|
||||||
|
@files = qw/ src / unless @files;
|
||||||
|
|
||||||
|
@files = grep { !/\.gitignore/ } @files;
|
||||||
|
|
||||||
|
exec 'prettier', '--plugin-search-dir=.', $mode, @files;
|
41
src/App-real.svelte
Normal file
41
src/App-real.svelte
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
|
||||||
|
import shipStore from './stores/ship';
|
||||||
|
|
||||||
|
import Ftl from './components/Ftl/index.svelte';
|
||||||
|
import Engine from './components/Engine/index.svelte';
|
||||||
|
import Identification from './components/Identification.svelte';
|
||||||
|
|
||||||
|
const ship = shipStore();
|
||||||
|
|
||||||
|
setContext('ship',ship);
|
||||||
|
|
||||||
|
let ship_name = $ship.ship_type.name;
|
||||||
|
|
||||||
|
const change_name = event => ship.dispatch(
|
||||||
|
ship.actions.set_name( event.target.value) );
|
||||||
|
|
||||||
|
const change_mass = ({target: {value}}) =>
|
||||||
|
ship.dispatch(ship.actions.set_ship_mass(parseInt(value)));
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
Potato?
|
||||||
|
<Identification />
|
||||||
|
|
||||||
|
ship class: <input value={$ship.ship_type.name} on:change={change_name} />
|
||||||
|
|
||||||
|
<input type="number" value={$ship.ship_type.mass} on:change={change_mass} />
|
||||||
|
|
||||||
|
|
||||||
|
<Ftl />
|
||||||
|
|
||||||
|
<Engine />
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
93
src/App.svelte
Normal file
93
src/App.svelte
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script>
|
||||||
|
import { setContext } from 'svelte';
|
||||||
|
|
||||||
|
import shipStore from './stores/ship';
|
||||||
|
|
||||||
|
import ShipCost from './components/ShipCost.svelte';
|
||||||
|
import Hull from './components/Hull.svelte';
|
||||||
|
import Identification from './components/Identification.svelte';
|
||||||
|
import Firecons from './components/Firecons.svelte';
|
||||||
|
import AddWeapon from './components/Weapons/Add.svelte';
|
||||||
|
import Propulsion from './components/Propulsion/index.svelte';
|
||||||
|
import Section from '~C/Section';
|
||||||
|
import Weapon from '~C/Weapon';
|
||||||
|
|
||||||
|
const ship = shipStore();
|
||||||
|
|
||||||
|
setContext('ship',ship);
|
||||||
|
|
||||||
|
let ship_name = $ship.general.name;
|
||||||
|
|
||||||
|
const change_name = event => ship.dispatch(
|
||||||
|
ship.actions.set_name( event.target.value) );
|
||||||
|
|
||||||
|
const change_mass = ({target: {value}}) => ship.dispatch(ship.actions.set_ship_mass(parseInt(value)));
|
||||||
|
|
||||||
|
const add_weapon = () => ship.dispatch.add_weapon();
|
||||||
|
|
||||||
|
const change_ftl = ({detail}) => ship.dispatch.set_ftl(detail);
|
||||||
|
const change_engine = ({detail}) => ship.dispatch.set_engine(detail);
|
||||||
|
const change_hull = ({detail}) => ship.dispatch.set_hull(detail);
|
||||||
|
const change_firecons = ({detail}) => ship.dispatch.set_firecons(detail);
|
||||||
|
const change_weapon = ({detail}) => ship.dispatch.set_weapon(detail);
|
||||||
|
const remove_weapon = ({detail}) => ship.dispatch.remove_weapon(detail);
|
||||||
|
|
||||||
|
let weapons = [];
|
||||||
|
$: console.log(weapons);
|
||||||
|
$: weapons= $ship.weaponry.weapons;
|
||||||
|
|
||||||
|
const reset = ship.dispatch.reset;
|
||||||
|
|
||||||
|
const add_screen = ({detail}) => ship.dispatch.add_screen(detail);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<input type="button" value="reset" on:click={reset} />
|
||||||
|
|
||||||
|
<Identification />
|
||||||
|
|
||||||
|
<ShipCost />
|
||||||
|
|
||||||
|
<Propulsion
|
||||||
|
ftl={$ship.ftl}
|
||||||
|
engine={$ship.engine}
|
||||||
|
on:change_ftl={change_ftl}
|
||||||
|
on:change_engine={change_engine}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Hull ship_mass={$ship.general.mass}
|
||||||
|
{ ... $ship.structure.hull }
|
||||||
|
on:change_hull={change_hull}
|
||||||
|
screens={ $ship.structure.screens}
|
||||||
|
on:add_screen={add_screen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
<Section label="weaponry">
|
||||||
|
<Firecons { ... $ship.weaponry.firecons }
|
||||||
|
on:change_firecons={ change_firecons }/>
|
||||||
|
|
||||||
|
<input type="button" value="add weapon" on:click={add_weapon}/>
|
||||||
|
|
||||||
|
{#each weapons as weapon (weapon.id)}
|
||||||
|
<Weapon {...weapon} on:change_weapon={change_weapon}
|
||||||
|
on:remove_weapon={remove_weapon} />
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
display: grid;
|
||||||
|
width: 60em;
|
||||||
|
grid-template-columns: 4fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
main :global(> *) {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
</style>
|
19
src/components/CostMass.svelte
Normal file
19
src/components/CostMass.svelte
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<div class="mass">{ mass }</div>
|
||||||
|
<div class="cost">{ cost }</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let mass;
|
||||||
|
export let cost;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cost { grid-column: 3; }
|
||||||
|
.mass { grid-column: 2; }
|
||||||
|
img {
|
||||||
|
width: 0.75em;
|
||||||
|
}
|
||||||
|
.cost:after { content: '\00A4'; margin-left: 0.5em; }
|
||||||
|
.mass:after { content: url("/mass.svg"); width: 0.75em; display:
|
||||||
|
inline-block; margin-left: 0.5em; }
|
||||||
|
</style>
|
38
src/components/Engine/index.svelte
Normal file
38
src/components/Engine/index.svelte
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<ShipItem {cost} {mass}>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Field label="thrust rating">
|
||||||
|
<input type="number" bind:value={ rating }
|
||||||
|
min="0" max="20" step="1" />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<label><input type="checkbox" bind:checked={advanced} /> advanced</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Field from '~C/Field';
|
||||||
|
import ShipItem from '~C/ShipItem';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let cost;
|
||||||
|
export let mass;
|
||||||
|
export let advanced = false;
|
||||||
|
export let rating = 0;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
console.log(advanced);
|
||||||
|
$: dispatch( 'change_engine', { rating, advanced } );
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
label { margin-left: 2em; }
|
||||||
|
</style>
|
5
src/components/Field/BasicStory.svelte
Normal file
5
src/components/Field/BasicStory.svelte
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<Field label="the label" />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Field from './index.svelte';
|
||||||
|
</script>
|
20
src/components/Field/index.svelte
Normal file
20
src/components/Field/index.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<div>
|
||||||
|
{#if label}
|
||||||
|
<label>{label}</label>
|
||||||
|
{/if}
|
||||||
|
<slot>
|
||||||
|
<input type="text" {placeholder} {value} on:change />
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let label = "";
|
||||||
|
export let value = "";
|
||||||
|
export let placeholder;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
font-size: var(--font-scale-8);
|
||||||
|
}
|
||||||
|
</style>
|
12
src/components/Field/stories.js
Normal file
12
src/components/Field/stories.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Component from './index.svelte';
|
||||||
|
import BasicStory from './BasicStory.svelte';
|
||||||
|
|
||||||
|
import '../../../public/global.css';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Field'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const basic = () => ({
|
||||||
|
Component: BasicStory,
|
||||||
|
});
|
20
src/components/Firecons.svelte
Normal file
20
src/components/Firecons.svelte
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<ShipItem {cost} {mass}>
|
||||||
|
<Field label="firecons">
|
||||||
|
<input type="number" bind:value={nbr} />
|
||||||
|
</Field>
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ShipItem from '~C/ShipItem';
|
||||||
|
import Field from '~C/Field';
|
||||||
|
|
||||||
|
export let nbr, cost, mass = (0,0,0);
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
$: dispatch( 'change_firecons', nbr);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
35
src/components/Ftl/index.svelte
Normal file
35
src/components/Ftl/index.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import ShipItem from '../ShipItem/index.svelte';
|
||||||
|
import Field from '../Field/index.svelte';
|
||||||
|
|
||||||
|
export let type = 'none';
|
||||||
|
export let cost = 0;
|
||||||
|
export let mass = 0;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const change = () => dispatch( 'change_ftl', type );
|
||||||
|
|
||||||
|
const types = [ 'none', 'standard', 'advanced' ];
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ShipItem {mass} {cost}>
|
||||||
|
<Field label="FTL drive">
|
||||||
|
|
||||||
|
{#each types as t (t)}
|
||||||
|
<label><input type="radio" bind:group={type} value={t}
|
||||||
|
on:change={change} /> {t} </label>
|
||||||
|
{/each}
|
||||||
|
</Field>
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: inline;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
13
src/components/Ftl/stories.js
Normal file
13
src/components/Ftl/stories.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export default {
|
||||||
|
title: 'FTL Drive'
|
||||||
|
};
|
||||||
|
|
||||||
|
import Component from '.';
|
||||||
|
import shipStore from '../../stores/ship.js';
|
||||||
|
|
||||||
|
export const basic = () => ({
|
||||||
|
Component,
|
||||||
|
props: {
|
||||||
|
ship: shipStore()
|
||||||
|
}
|
||||||
|
});
|
39
src/components/Hull.svelte
Normal file
39
src/components/Hull.svelte
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<Section label="hull">
|
||||||
|
|
||||||
|
<ShipItem {cost} {mass} >
|
||||||
|
<Field label="integrity">
|
||||||
|
<input
|
||||||
|
bind:value={rating}
|
||||||
|
type="number" {min} {max} />
|
||||||
|
</Field>
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
|
||||||
|
<Screens {screens} on:add_screen />
|
||||||
|
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import Section from '~C/Section';
|
||||||
|
import Field from '~C/Field';
|
||||||
|
import ShipItem from '~C/ShipItem';
|
||||||
|
import Screens from './Screens';
|
||||||
|
|
||||||
|
export let cost, mass, ship_mass, rating, screens = (
|
||||||
|
0, 0, 10, 1, []
|
||||||
|
);
|
||||||
|
|
||||||
|
let min, max;
|
||||||
|
$: min = Math.ceil(ship_mass / 10);
|
||||||
|
$: max = ship_mass;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
$: dispatch( 'change_hull', { rating } );
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
49
src/components/Identification.svelte
Normal file
49
src/components/Identification.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<div class="identification">
|
||||||
|
|
||||||
|
<Field label="ship class" value={general.ship_class}
|
||||||
|
on:change={change_class} />
|
||||||
|
|
||||||
|
<Field label="ship type">
|
||||||
|
<select value={ship_type} on:change={change_ship_type}>
|
||||||
|
{#each ship_types as type (type)}
|
||||||
|
<option>{type}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
import Field from './Field/index.svelte';
|
||||||
|
import { candidate_ship_types } from '~/dux/ship_types';
|
||||||
|
|
||||||
|
export let ship = getContext('ship');
|
||||||
|
let general;
|
||||||
|
$: general = $ship.general;
|
||||||
|
|
||||||
|
const change_class = (event) => ship.dispatch(
|
||||||
|
ship.actions.set_ship_class(event.target.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
let ship_type;
|
||||||
|
$: ship_type = $ship.general.ship_type;
|
||||||
|
|
||||||
|
const change_ship_type = ({ target: {value}}) =>
|
||||||
|
ship.dispatch.set_ship_type(value);
|
||||||
|
|
||||||
|
let ship_types;
|
||||||
|
$: ship_types = candidate_ship_types($ship.general.mass,false).map(
|
||||||
|
({name}) => name
|
||||||
|
);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.identification {
|
||||||
|
grid-column: span 3;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
</style>
|
18
src/components/Propulsion/index.svelte
Normal file
18
src/components/Propulsion/index.svelte
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
<Section label="propulsion">
|
||||||
|
|
||||||
|
<Ftl {...ftl} on:change_ftl />
|
||||||
|
|
||||||
|
<Engine {...engine} on:change_engine />
|
||||||
|
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Ftl from '../Ftl/index.svelte';
|
||||||
|
import Engine from '../Engine/index.svelte';
|
||||||
|
import Section from '../Section/index.svelte';
|
||||||
|
|
||||||
|
export let ftl = {};
|
||||||
|
export let engine = {};
|
||||||
|
|
||||||
|
</script>
|
31
src/components/Screens/index.svelte
Normal file
31
src/components/Screens/index.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
<input type="button" value="add screen" on:click={() => add_screen()} />
|
||||||
|
<input type="button" value="add advanced screen"
|
||||||
|
on:click={ () => add_screen(true) }/>
|
||||||
|
|
||||||
|
<ShipItem cost="10" mass="10">
|
||||||
|
{ nbr_regular } { nbr_advanced }
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import Section from '~C/Section';
|
||||||
|
import Field from '~C/Field';
|
||||||
|
import ShipItem from '~C/ShipItem';
|
||||||
|
|
||||||
|
export let screens = [];
|
||||||
|
|
||||||
|
$: if( !Array.isArray(screens) ) screens = [];
|
||||||
|
|
||||||
|
let nbr_regular, nbr_advanced;
|
||||||
|
$: {
|
||||||
|
nbr_regular = screens.filter( ({advanced}) => !advanced ).length;
|
||||||
|
nbr_advanced = screens.length - nbr_regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
const add_screen = (advanced) => {
|
||||||
|
console.log(advanced); return dispatch( 'add_screen', advanced ); };
|
||||||
|
|
||||||
|
</script>
|
8
src/components/Section/index.svelte
Normal file
8
src/components/Section/index.svelte
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<fieldset>
|
||||||
|
<legend>{label}</legend>
|
||||||
|
<slot />
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let label;
|
||||||
|
</script>
|
58
src/components/ShipCost.svelte
Normal file
58
src/components/ShipCost.svelte
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<div class="ship_cost">
|
||||||
|
<div>
|
||||||
|
<label>Ship tonnage</label>
|
||||||
|
<input
|
||||||
|
value={$ship.general.mass}
|
||||||
|
on:change={change_tonnage}
|
||||||
|
type="number" min="10" max="300" />
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div class="note" class:warning={!within_budget}>
|
||||||
|
{#if within_budget}
|
||||||
|
mass unused: { mass_unused }
|
||||||
|
{:else}
|
||||||
|
excessive mass: { -mass_unused }
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Ship cost</label>
|
||||||
|
<div>{$ship.general.cost}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getContext } from 'svelte';
|
||||||
|
|
||||||
|
export let ship = getContext('ship');
|
||||||
|
|
||||||
|
const change_tonnage =
|
||||||
|
({target: {value}}) =>
|
||||||
|
ship.dispatch(ship.actions.set_ship_mass(parseInt(value)));
|
||||||
|
|
||||||
|
let mass_unused;
|
||||||
|
$: mass_unused = $ship.general.mass - $ship.general.used_mass;
|
||||||
|
|
||||||
|
let within_budget = true;
|
||||||
|
|
||||||
|
$: within_budget = mass_unused >= 0;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ship_cost {
|
||||||
|
display: flex;
|
||||||
|
grid-column: span 3;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.note {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
</style>
|
29
src/components/ShipItem/index.svelte
Normal file
29
src/components/ShipItem/index.svelte
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<div class="ship-item">
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<div class="mass">{ mass }</div>
|
||||||
|
<div class="cost">{ cost }</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export let mass;
|
||||||
|
export let cost;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ship-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ship-item :global(> *) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cost,.mass { width: 4em; flex: inherit; }
|
||||||
|
img {
|
||||||
|
width: 0.75em;
|
||||||
|
}
|
||||||
|
.cost:after { content: '\00A4'; margin-left: 0.5em; }
|
||||||
|
.mass:after { content: url("/mass.svg"); width: 0.75em; display:
|
||||||
|
inline-block; margin-left: 0.5em; }
|
||||||
|
</style>
|
210
src/components/Weapon/index.svelte
Normal file
210
src/components/Weapon/index.svelte
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
<ShipItem {cost} {mass}>
|
||||||
|
|
||||||
|
<div class="remove" on:click={remove}>X</div>
|
||||||
|
|
||||||
|
<Field label="weapon type">
|
||||||
|
<select bind:value={weapon_type}>
|
||||||
|
<option>beam</option>
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="weapon class">
|
||||||
|
<select bind:value={weapon_class}>
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="arcs">
|
||||||
|
<select bind:value={nbr_arcs}>
|
||||||
|
{#each arc_options[weapon_class]||[] as nbr_arcs (nbr_arcs)}
|
||||||
|
<option>{nbr_arcs}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
|
||||||
|
<svg width="60px" height="60px">
|
||||||
|
{#each all_arcs as arc (arc)}
|
||||||
|
<Arc {arc} radius={30}
|
||||||
|
active={arcs.includes(arc)}
|
||||||
|
on:click={()=>click_arc(arc)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<circle cx="30" cy="30" r="15" />
|
||||||
|
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
</ShipItem>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Arc from '../Weapons/Arc.svelte';
|
||||||
|
import { weapon_cost_mass } from '../../dux/weapons/rules.js';
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ShipItem from '~C/ShipItem';
|
||||||
|
import Field from '~C/Field';
|
||||||
|
|
||||||
|
const all_arcs = [ 'FS', 'F', 'FP', 'AP', 'A', 'AS' ];
|
||||||
|
|
||||||
|
export let weapon_type;
|
||||||
|
export let weapon_class;
|
||||||
|
export let id;
|
||||||
|
export let arcs = [];
|
||||||
|
export let cost;
|
||||||
|
export let mass;
|
||||||
|
|
||||||
|
let arc_options = {
|
||||||
|
1: [ 6],
|
||||||
|
2: [ 3, 6 ],
|
||||||
|
3: [ 1, 2, 3, 4, 5, 6, 'broadside' ],
|
||||||
|
4: [ 1, 2, 3, 4, 5, 6, 'broadside' ]
|
||||||
|
};
|
||||||
|
|
||||||
|
let nbr_arcs = 6;
|
||||||
|
$: console.log(weapon_class);
|
||||||
|
$: nbr_arcs = arc_options[weapon_class][0];
|
||||||
|
|
||||||
|
$: console.log({arcs,nbr_arcs})
|
||||||
|
|
||||||
|
$: if ( arcs.length !== nbr_arcs ) {
|
||||||
|
if( nbr_arcs === 'broadside' ) {
|
||||||
|
arcs = all_arcs.filter( arc => arc.length === 1 )
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
let first_index = all_arcs.findIndex( arc => arcs[0] );
|
||||||
|
if( first_index === -1 ) first_index = 0;
|
||||||
|
|
||||||
|
const new_arcs = [];
|
||||||
|
|
||||||
|
_.range(nbr_arcs).forEach( i => {
|
||||||
|
new_arcs.push( all_arcs[first_index] )
|
||||||
|
first_index = ( first_index + 1 ) % all_arcs.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
arcs = new_arcs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const click_arc = (first_arc) => {
|
||||||
|
if( nbr_arcs === 'broadside' ) return;
|
||||||
|
|
||||||
|
let first_index = all_arcs.findIndex( arc => arc === first_arc );
|
||||||
|
|
||||||
|
const new_arcs = [];
|
||||||
|
|
||||||
|
_.range(nbr_arcs).forEach( i => {
|
||||||
|
new_arcs.push( all_arcs[first_index] );
|
||||||
|
first_index = ( first_index + 1 ) % all_arcs.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
arcs = new_arcs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
$: if(weapon_class) i = 1;
|
||||||
|
|
||||||
|
$: console.log( "id", id);
|
||||||
|
$: console.log( "weapon_class", weapon_class);
|
||||||
|
$: console.log( "weapon_type", weapon_type);
|
||||||
|
$: console.log( "arcs", arcs);
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
let cache = '';
|
||||||
|
$: cache = arcs.join(":");
|
||||||
|
$: {
|
||||||
|
//console.log( { id, weapon_class, weapon_type, arcs: cache.split(":") });
|
||||||
|
dispatch( 'change_weapon', { id, weapon_class, weapon_type, arcs:
|
||||||
|
cache.split(":") });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const remove = () => dispatch( 'remove_weapon', id );
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.weapon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon > * {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.arc input {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.arc.F {
|
||||||
|
grid-column: 2 / span 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.FS {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.FP {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.AS {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.AP {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 3 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.A {
|
||||||
|
grid-column: 2 / span 2;
|
||||||
|
grid-row: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-weapon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove {
|
||||||
|
width: 1em;
|
||||||
|
flex: 0;
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
203
src/components/Weapons/Add.svelte
Normal file
203
src/components/Weapons/Add.svelte
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<input class="add-weapon" type="button" value="add"
|
||||||
|
on:click={add} />
|
||||||
|
|
||||||
|
<div class="weapon">
|
||||||
|
|
||||||
|
<select bind:value={weapon_type}>
|
||||||
|
<option>beam</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select bind:value={weapon_class}>
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select bind:value={nbr_arcs}>
|
||||||
|
{#each arc_options[weapon_class] as opt (opt)}
|
||||||
|
<option>{opt}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<svg width="60px" height="60px">
|
||||||
|
{#each arcs as arc (arc)}
|
||||||
|
<Arc {arc} radius={30}
|
||||||
|
active={selected_arc[arc]}
|
||||||
|
on:click={()=>click_arc(arc)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<circle cx="30" cy="30" r="15" />
|
||||||
|
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div>{weapon.cost}</div>
|
||||||
|
<div>{weapon.mass}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let weapon_type = 'beam';
|
||||||
|
let weapon_class = 1;
|
||||||
|
let nbr_arcs = 6;
|
||||||
|
$: nbr_arcs = arc_options[weapon_class][0];
|
||||||
|
|
||||||
|
import Arc from './Arc.svelte';
|
||||||
|
import { weapon_cost_mass } from '../../dux/weapons/rules.js';
|
||||||
|
|
||||||
|
const arcs = [
|
||||||
|
'FS', 'F', 'FP', 'AP', 'A', 'AS'
|
||||||
|
];
|
||||||
|
|
||||||
|
import fp from 'lodash/fp';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
let arc_options = {
|
||||||
|
1: [ 6],
|
||||||
|
2: [ 3, 6 ],
|
||||||
|
3: [ 1, 2, 3, 4, 5, 6, 'broadside' ],
|
||||||
|
4: [ 1, 2, 3, 4, 5, 6, 'broadside' ]
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected_arc = Object.fromEntries(
|
||||||
|
arcs.map( arc => [ arc, false ] )
|
||||||
|
);
|
||||||
|
|
||||||
|
const nbr_selected_arcs = () => Object.values(selected_arc).filter(
|
||||||
|
x => x ).length;
|
||||||
|
|
||||||
|
$: if ( nbr_selected_arcs() !== nbr_arcs ) {
|
||||||
|
if( nbr_arcs === 'broadside' ) {
|
||||||
|
const new_arcs = {};
|
||||||
|
arcs.forEach( arc => new_arcs[arc] = true );
|
||||||
|
new_arcs.A = false;
|
||||||
|
new_arcs.F = false;
|
||||||
|
selected_arc = new_arcs;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
let first_index = arcs.findIndex( arc => selected_arc[arc] );
|
||||||
|
if( first_index === -1 ) first_index = 0;
|
||||||
|
|
||||||
|
const new_arcs = {};
|
||||||
|
arcs.forEach( arc => new_arcs[arc] = false );
|
||||||
|
|
||||||
|
_.range(nbr_arcs).forEach( i => {
|
||||||
|
new_arcs[ arcs[first_index] ] = true;
|
||||||
|
first_index = ( first_index + 1 ) % arcs.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
selected_arc = new_arcs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const click_arc = (first_arc) => {
|
||||||
|
if( nbr_arcs === 'broadside' ) return;
|
||||||
|
|
||||||
|
let first_index = arcs.findIndex( arc => arc === first_arc );
|
||||||
|
|
||||||
|
const new_arcs = {};
|
||||||
|
arcs.forEach( arc => new_arcs[arc] = false );
|
||||||
|
|
||||||
|
_.range(nbr_arcs).forEach( i => {
|
||||||
|
console.log(first_index);
|
||||||
|
console.log(selected_arc);
|
||||||
|
new_arcs[ arcs[first_index] ] = true;
|
||||||
|
first_index = ( first_index + 1 ) % arcs.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
selected_arc = new_arcs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let weapon = {};
|
||||||
|
$: weapon= {
|
||||||
|
weapon_type,
|
||||||
|
weapon_class,
|
||||||
|
arcs,
|
||||||
|
...weapon_cost_mass({ weapon_type, weapon_class, arcs: arcs.filter(
|
||||||
|
arc => selected_arc[arc]
|
||||||
|
) })
|
||||||
|
};
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
const add = () => {
|
||||||
|
dispatch('add_weapon', weapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.weapon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weapon > * {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 1fr 1fr 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.arc input {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.arc.F {
|
||||||
|
grid-column: 2 / span 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.FS {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.FP {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 1 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.AS {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.AP {
|
||||||
|
grid-column: 4;
|
||||||
|
grid-row: 3 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc.A {
|
||||||
|
grid-column: 2 / span 2;
|
||||||
|
grid-row: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-weapon {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
50
src/components/Weapons/Arc.svelte
Normal file
50
src/components/Weapons/Arc.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
export let arc;
|
||||||
|
export let radius;
|
||||||
|
export let active = false;
|
||||||
|
|
||||||
|
const rotation = {
|
||||||
|
F: 0,
|
||||||
|
FS: 300,
|
||||||
|
AS: 240,
|
||||||
|
A: 180,
|
||||||
|
AP: 120,
|
||||||
|
FP: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
let y, x_delta;
|
||||||
|
$: y = Math.round( radius * ( 1 - Math.sin(60/180*Math.PI) ) );
|
||||||
|
$: x_delta = Math.round( radius*Math.cos(60/180*Math.PI) );
|
||||||
|
|
||||||
|
let path;
|
||||||
|
$: path = `M ${radius},${radius} L ${radius-x_delta},${y} A ${radius},${radius} 0 0 1 ${radius+x_delta},${y} Z`;
|
||||||
|
|
||||||
|
let transform;
|
||||||
|
$: transform = `rotate(${rotation[arc]},${radius},${radius})`
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<g { transform }>
|
||||||
|
<path d={path} class:active on:click />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
path {
|
||||||
|
fill: lightgrey;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
path:hover {
|
||||||
|
fill: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.active:hover {
|
||||||
|
fill: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.active {
|
||||||
|
fill: #313131;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
10
src/components/Weapons/stories.js
Normal file
10
src/components/Weapons/stories.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import AddWeapon from './Add.svelte';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "add weapon",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const beam = () => ({
|
||||||
|
Component: AddWeapon,
|
||||||
|
});
|
||||||
|
|
30
src/dux/calc_ship_cost_mass.test.js
Normal file
30
src/dux/calc_ship_cost_mass.test.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import tap from "tap";
|
||||||
|
|
||||||
|
import { calc_ship_req } from "./utils";
|
||||||
|
|
||||||
|
const sample = {
|
||||||
|
general: {
|
||||||
|
mass: 10,
|
||||||
|
used_mass: 0,
|
||||||
|
cost: 10,
|
||||||
|
},
|
||||||
|
ftl: {
|
||||||
|
cost: 4,
|
||||||
|
mass: 2,
|
||||||
|
},
|
||||||
|
structure: {
|
||||||
|
hull: {
|
||||||
|
cost: 2,
|
||||||
|
mass: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
tap.same(
|
||||||
|
calc_ship_req(sample),
|
||||||
|
{
|
||||||
|
cost: 16,
|
||||||
|
mass: 3,
|
||||||
|
},
|
||||||
|
"sample ship"
|
||||||
|
);
|
27
src/dux/engine/index.js
Normal file
27
src/dux/engine/index.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
const set_engine = action('set_engine',payload());
|
||||||
|
const set_drive_reqs = action('set_drive_reqs',payload());
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
mass: 1,
|
||||||
|
cost: 2,
|
||||||
|
rating: 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dux.addMutation(set_engine, engine => u(engine));
|
||||||
|
dux.addMutation(set_drive_reqs, rate => u(rate));
|
||||||
|
|
||||||
|
export function calc_drive_reqs(ship_mass,rating,advanced=false) {
|
||||||
|
const mass = Math.ceil(rating * 0.05 * ship_mass);
|
||||||
|
const cost = mass * ( advanced ? 3 : 2 );
|
||||||
|
|
||||||
|
return { mass, cost };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default dux.asDux;
|
23
src/dux/ftl/index.js
Normal file
23
src/dux/ftl/index.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Updux from 'updux';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { calc_ftl_reqs } from './rules';
|
||||||
|
|
||||||
|
// 'none' | 'standard' | 'advanced'
|
||||||
|
const set_ftl = action('set_ftl',payload());
|
||||||
|
|
||||||
|
const set_ftl_reqs = action('set_ftl_reqs', payload() );
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
mass: 0,
|
||||||
|
cost: 0,
|
||||||
|
type: 'none'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dux.addMutation(set_ftl, type => u({type}));
|
||||||
|
dux.addMutation(set_ftl_reqs, reqs => u(reqs) );
|
||||||
|
|
||||||
|
export default dux.asDux;
|
10
src/dux/ftl/rules.js
Normal file
10
src/dux/ftl/rules.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export function calc_ftl_reqs(type,ship_mass) {
|
||||||
|
if(type==="none") return { cost: 0, mass: 0 };
|
||||||
|
|
||||||
|
const mass = Math.ceil(ship_mass / 10);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mass,
|
||||||
|
cost: mass * ( type === 'advanced' ? 3 : 2 ),
|
||||||
|
}
|
||||||
|
}
|
7
src/dux/ftl/test.js
Normal file
7
src/dux/ftl/test.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
import { calc_ftl_rate } from './rules';
|
||||||
|
|
||||||
|
tap.same( calc_ftl_rate(111,'standard'), { mass: 12, cost: 24 },
|
||||||
|
'mass rounded up' );
|
||||||
|
|
143
src/dux/index.js
Normal file
143
src/dux/index.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import Updux from "updux";
|
||||||
|
import { action, payload } from "ts-action";
|
||||||
|
import u from "updeep";
|
||||||
|
import { createSelector } from "reselect";
|
||||||
|
|
||||||
|
import ftl from "./ftl";
|
||||||
|
import engine, { calc_drive_reqs } from "./engine";
|
||||||
|
import weaponry from './weaponry';
|
||||||
|
import { calc_ftl_reqs } from "./ftl/rules";
|
||||||
|
import { calc_ship_req } from "./utils";
|
||||||
|
import { candidate_ship_types } from './ship_types';
|
||||||
|
|
||||||
|
const set_ship_mass = action("set_ship_mass", payload());
|
||||||
|
const set_name = action("set_name", payload());
|
||||||
|
|
||||||
|
const set_ship_reqs = action("set_ship_reqs", payload());
|
||||||
|
const set_hull = action("set_hull", payload());
|
||||||
|
|
||||||
|
const set_ship_type = action('set_ship_type',payload());
|
||||||
|
|
||||||
|
const reset = action('reset' );
|
||||||
|
|
||||||
|
const initial = {
|
||||||
|
general: {
|
||||||
|
ship_class: "",
|
||||||
|
name: "",
|
||||||
|
ship_type: "",
|
||||||
|
mass: 10,
|
||||||
|
used_mass: 0,
|
||||||
|
cost: 10,
|
||||||
|
},
|
||||||
|
structure: {
|
||||||
|
mass: 0,
|
||||||
|
cost: 0,
|
||||||
|
hull: {
|
||||||
|
rating: 1,
|
||||||
|
advanced: false,
|
||||||
|
cost: 2,
|
||||||
|
mass: 1,
|
||||||
|
},
|
||||||
|
screens: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: { ftl, engine, weaponry, },
|
||||||
|
initial
|
||||||
|
});
|
||||||
|
|
||||||
|
const add_screen = action( 'add_screen', payload() );
|
||||||
|
|
||||||
|
|
||||||
|
dux.addMutation(add_screen, advanced => u.updateIn( 'structure.screens', state => {
|
||||||
|
return [ ...(state||[]), { advanced } ]
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
dux.addMutation( reset, () => () => initial );
|
||||||
|
|
||||||
|
dux.addMutation(set_hull, ({rating}) => (state) => {
|
||||||
|
return u.updateIn("structure.hull", {
|
||||||
|
cost: 2 * rating,
|
||||||
|
rating,
|
||||||
|
mass: rating,
|
||||||
|
})(state);
|
||||||
|
});
|
||||||
|
dux.addMutation(set_ship_mass, (mass) => u.updateIn("general", { mass }));
|
||||||
|
dux.addMutation(set_name, (name) => u.updateIn("general", { name }));
|
||||||
|
|
||||||
|
dux.addMutation( action('set_ship_class',payload() ),
|
||||||
|
ship_class => u.updateIn('general',{ship_class})
|
||||||
|
);
|
||||||
|
|
||||||
|
dux.addMutation(set_ship_reqs, ({ cost, mass: used_mass }) =>
|
||||||
|
u.updateIn("general", { cost, used_mass })
|
||||||
|
);
|
||||||
|
|
||||||
|
// set ship's req
|
||||||
|
dux.addSubscription((store) =>
|
||||||
|
createSelector(calc_ship_req, (reqs) => store.dispatch(set_ship_reqs(reqs)))
|
||||||
|
);
|
||||||
|
|
||||||
|
dux.addSubscription((store) =>
|
||||||
|
createSelector(
|
||||||
|
store => store.general.mass,
|
||||||
|
store => store.general.ship_type,
|
||||||
|
(mass,type) => {
|
||||||
|
const candidates = candidate_ship_types(mass,false);
|
||||||
|
console.log({candidates});
|
||||||
|
if( candidates.length === 0 ) return;
|
||||||
|
|
||||||
|
if( candidates.find( ({name}) => name === type ) ) return;
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
store.actions.set_ship_type(
|
||||||
|
candidates[0].name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
dux.addMutation(set_ship_type, type => u.updateIn('general.ship_type',type) );
|
||||||
|
|
||||||
|
dux.addSubscription((store) =>
|
||||||
|
createSelector(
|
||||||
|
[(ship) => ship.general.mass, (ship) => ship.ftl.type],
|
||||||
|
(ship_mass, type) =>
|
||||||
|
store.dispatch(ftl.actions.set_ftl_reqs(calc_ftl_reqs(type,ship_mass)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
dux.addSubscription((store) =>
|
||||||
|
createSelector(
|
||||||
|
[
|
||||||
|
(ship) => ship.general.mass,
|
||||||
|
(ship) => ship.engine.rating,
|
||||||
|
(ship) => ship.engine.advanced,
|
||||||
|
],
|
||||||
|
(ship_mass, rating, advanced) =>
|
||||||
|
store.dispatch(
|
||||||
|
dux.actions.set_drive_reqs(calc_drive_reqs(ship_mass, rating, advanced))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const calc_firecons_reqs = (nbr) => ({
|
||||||
|
cost: 4 * nbr,
|
||||||
|
mass: nbr,
|
||||||
|
});
|
||||||
|
|
||||||
|
const set_firecons = action("set_firecons", payload());
|
||||||
|
dux.addMutation(set_firecons, (nbr) =>
|
||||||
|
u.updateIn("weaponry.firecons", {
|
||||||
|
nbr,
|
||||||
|
...calc_firecons_reqs(nbr),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default dux.asDux;
|
||||||
|
|
||||||
|
export const actions = dux.actions;
|
26
src/dux/ship_types.js
Normal file
26
src/dux/ship_types.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const ship_types = [
|
||||||
|
{ name: "Scout", mass: [4, 10], abbrev: "SC" },
|
||||||
|
{ name: "Courier", mass: [4, 10], abbrev: "SC" },
|
||||||
|
{ name: "Corvette", mass: [8, 16], abbrev: "CT" },
|
||||||
|
{ name: "Frigate", mass: [14, 28], abbrev: "FF" },
|
||||||
|
{ name: "Destroyer", mass: [24, 36], abbrev: "DD" },
|
||||||
|
{ name: "Heavy Destroyer", mass: [30, 40], abbrev: "DH" },
|
||||||
|
{ name: "Light Cruiser", mass: [40, 60], abbrev: "CL" },
|
||||||
|
{ name: "Patrol", mass: [50, 70], abbrev: "CE" },
|
||||||
|
{ name: "Escort Cruiser", mass: [50, 70], abbrev: "CE" },
|
||||||
|
{ name: "Heavy Cruiser", mass: [60, 90], abbrev: "CA" },
|
||||||
|
{ name: "Battlecruiser", mass: [80, 110], abbrev: "BC" },
|
||||||
|
{ name: "Battleship", mass: [100, 140], abbrev: "BB" },
|
||||||
|
{ name: "Heavy Battleship", mass: [120, 160], abbrev: "BDN" },
|
||||||
|
{ name: "Dreadnought", mass: [140, 180], abbrev: "DN" },
|
||||||
|
{ name: "Superdreadnought", mass: [160, 300], abbrev: "SDN" },
|
||||||
|
{ name: "Escort Carrier", mass: [60, 140], abbrev: "CVE", carrier: true },
|
||||||
|
{ name: "Light Carrier", mass: [120, 180], abbrev: "CVL", carrier: true },
|
||||||
|
{ name: "Heavy Carrier", mass: [160, 300], abbrev: "CVH", carrier: true },
|
||||||
|
{ name: "Attack Carrier", mass: [150, 300], abbrev: "CVA", carrier: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function candidate_ship_types(mass = 0, carrier = false) {
|
||||||
|
console.log(mass);
|
||||||
|
return ship_types.filter((c) => carrier == !!c.carrier).filter((c) => c.mass[0] <= mass).filter((c) => c.mass[1] >= mass);
|
||||||
|
}
|
17
src/dux/test.js
Normal file
17
src/dux/test.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
|
||||||
|
import ship, { actions } from '.';
|
||||||
|
|
||||||
|
ship.dispatch( actions.set_ship_mass(100));
|
||||||
|
|
||||||
|
ship.dispatch( actions.set_ftl('standard'));
|
||||||
|
|
||||||
|
tap.match(
|
||||||
|
ship.getState(),
|
||||||
|
{
|
||||||
|
ship_type: { mass: 100 },
|
||||||
|
ftl: { type: 'standard', cost: 20, mass: 10 },
|
||||||
|
},
|
||||||
|
'standard ftl drive'
|
||||||
|
);
|
||||||
|
|
30
src/dux/utils.js
Normal file
30
src/dux/utils.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
|
const expand_cost_mass = (obj={}) => ([
|
||||||
|
fp.pick(['cost','mass'],obj),
|
||||||
|
...Object.values(obj || {}).filter( val => typeof val === 'object' ).map(
|
||||||
|
val => expand_cost_mass(val)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function calc_ship_req(ship) {
|
||||||
|
|
||||||
|
console.log(ship);
|
||||||
|
let { general, ...rest } = ship;
|
||||||
|
|
||||||
|
//if(!general) general = {};
|
||||||
|
|
||||||
|
const items = fp.flow(
|
||||||
|
expand_cost_mass,
|
||||||
|
fp.flattenDeep,
|
||||||
|
fp.filter(fp.has('cost'))
|
||||||
|
)({
|
||||||
|
...rest,
|
||||||
|
cost: general.mass, mass: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
mass: fp.sumBy('mass',items),
|
||||||
|
cost: fp.sumBy('cost',items),
|
||||||
|
}
|
||||||
|
}
|
19
src/dux/weaponry/index.js
Normal file
19
src/dux/weaponry/index.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Updux from "updux";
|
||||||
|
import { action, payload } from "ts-action";
|
||||||
|
import u from "updeep";
|
||||||
|
import { createSelector } from "reselect";
|
||||||
|
|
||||||
|
import weapons from './weapons';
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
firecons: {
|
||||||
|
nbr: 0, cost: 0, mass: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
weapons,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default dux.asDux;
|
25
src/dux/weaponry/weapons/index.js
Normal file
25
src/dux/weaponry/weapons/index.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Updux from "updux";
|
||||||
|
import { action, payload } from "ts-action";
|
||||||
|
import u from "updeep";
|
||||||
|
import { createSelector } from "reselect";
|
||||||
|
|
||||||
|
import weapon from './weapon';
|
||||||
|
|
||||||
|
const add_weapon = action('add_weapon');
|
||||||
|
const remove_weapon = action('remove_weapon', payload());
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: [],
|
||||||
|
subduxes: { '*': weapon }
|
||||||
|
})
|
||||||
|
|
||||||
|
dux.addMutation( add_weapon, () => state => {
|
||||||
|
const id = 1 + Math.max( 0, ... state.map(({id})=>id) );
|
||||||
|
return [ ...state, { ...weapon.initial, id }];
|
||||||
|
});
|
||||||
|
|
||||||
|
dux.addMutation( remove_weapon, id => state =>
|
||||||
|
state.filter( w => w.id !== id )
|
||||||
|
);
|
||||||
|
|
||||||
|
export default dux.asDux;
|
30
src/dux/weaponry/weapons/weapon/index.js
Normal file
30
src/dux/weaponry/weapons/weapon/index.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import Updux from "updux";
|
||||||
|
import { action, payload } from "ts-action";
|
||||||
|
import u from "updeep";
|
||||||
|
import { createSelector } from "reselect";
|
||||||
|
import { weapon_cost_mass } from '../../../weapons/rules';
|
||||||
|
|
||||||
|
const uu = transform => state => transform(state)(state)
|
||||||
|
|
||||||
|
const with_reqs = uu( weapon => u(weapon_cost_mass(weapon)));
|
||||||
|
|
||||||
|
const initial = with_reqs({
|
||||||
|
weapon_type: 'beam',
|
||||||
|
weapon_class: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(initial);
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial
|
||||||
|
});
|
||||||
|
|
||||||
|
const set_weapon = action('set_weapon',payload());
|
||||||
|
|
||||||
|
dux.addMutation(
|
||||||
|
set_weapon, payload => u.if( s => s.id === payload.id,
|
||||||
|
state => with_reqs(u(payload,state))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export default dux.asDux;
|
59
src/dux/weapons/rules.js
Normal file
59
src/dux/weapons/rules.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
export function weapon_cost_mass(weapon){
|
||||||
|
let cost = 0;
|
||||||
|
let mass = 0;
|
||||||
|
|
||||||
|
if( weapon.weapon_type === 'beam' ) {
|
||||||
|
return beam_cost_mass(weapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return { cost, mass };
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_broadside = arcs => {
|
||||||
|
if( arcs.length !== 4 ) return false;
|
||||||
|
|
||||||
|
// that'd be A or F
|
||||||
|
return !arcs.some( a => a.length === 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
function beam_cost_mass({weapon_class, arcs}) {
|
||||||
|
let mass;
|
||||||
|
if( weapon_class === 1 ) {
|
||||||
|
mass = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( weapon_class == 2 ) {
|
||||||
|
mass = 2 + (arcs.length > 3 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( weapon_class == 3 ) {
|
||||||
|
mass = 4;
|
||||||
|
|
||||||
|
if( is_broadside(arcs) ) {
|
||||||
|
mass += 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mass += arcs.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( weapon_class == 4 ) {
|
||||||
|
mass = 8;
|
||||||
|
|
||||||
|
if( is_broadside(arcs) ) {
|
||||||
|
mass += 4;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mass += 2*(arcs.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mass, cost: 3 * mass
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
29
src/dux/weapons/rules.test.js
Normal file
29
src/dux/weapons/rules.test.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import { weapon_cost_mass } from './rules';
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
[{
|
||||||
|
weapon_type: "beam",
|
||||||
|
weapon_class: 2,
|
||||||
|
arcs: [ 'AP', 'A', 'AF' ]
|
||||||
|
},{
|
||||||
|
cost: 6,
|
||||||
|
mass: 2,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{
|
||||||
|
weapon_type: "beam",
|
||||||
|
weapon_class: 1,
|
||||||
|
arcs: [ 'AP', 'A', 'AF' ]
|
||||||
|
},{
|
||||||
|
mass: 1,
|
||||||
|
cost: 3,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[{ weapon_type: "beam", weapon_class: 3, arcs: [ 'AP', 'A', 'AF' ] },{ mass: 6, cost: 18, } ],
|
||||||
|
[{ weapon_type: "beam", weapon_class: 4, arcs: [ 'AP', 'A', 'AF' ] },{ mass: 12, cost: 36, } ],
|
||||||
|
]
|
||||||
|
|
||||||
|
cases.forEach( ([weapon,expected]) =>
|
||||||
|
tap.match(
|
||||||
|
weapon_cost_mass(weapon), expected, JSON.stringify(weapon) ) );
|
10
src/main.js
Normal file
10
src/main.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import App from './App.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
props: {
|
||||||
|
name: 'world'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
58
src/stores/ship.js
Normal file
58
src/stores/ship.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { readable } from "svelte/store";
|
||||||
|
import { compose, applyMiddleware } from "redux";
|
||||||
|
|
||||||
|
import { calc_ship_req } from "../dux/utils";
|
||||||
|
|
||||||
|
const composeEnhancers =
|
||||||
|
(window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
|
||||||
|
|
||||||
|
import shipDux from "../dux";
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
let saved = window && window.localStorage.getItem('aotds-shipyard');
|
||||||
|
if( saved ) {
|
||||||
|
saved = JSON.parse(saved);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
saved = undefined;
|
||||||
|
}
|
||||||
|
console.log(saved);
|
||||||
|
|
||||||
|
|
||||||
|
const duxStore = shipDux.createStore(saved, (mw) =>
|
||||||
|
composeEnhancers(applyMiddleware(mw))
|
||||||
|
);
|
||||||
|
|
||||||
|
duxStore.dispatch(
|
||||||
|
duxStore.actions.set_ship_reqs(calc_ship_req(duxStore.getState()))
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.entries(duxStore.actions).forEach( ([type,action]) => {
|
||||||
|
duxStore.dispatch[ type ] = payload => duxStore.dispatch( action(payload) )
|
||||||
|
});
|
||||||
|
|
||||||
|
let previous = undefined;
|
||||||
|
duxStore.subscribe( () => {
|
||||||
|
let current = duxStore.getState();
|
||||||
|
if ( previous === current ) return;
|
||||||
|
previous = current;
|
||||||
|
console.log(current);
|
||||||
|
|
||||||
|
window && window.localStorage.setItem(
|
||||||
|
'aotds-shipyard', JSON.stringify(current)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = readable(duxStore.getState(), (set) =>
|
||||||
|
duxStore.subscribe(() => {
|
||||||
|
set(duxStore.getState());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: state.subscribe,
|
||||||
|
dispatch: duxStore.dispatch,
|
||||||
|
actions: duxStore.actions,
|
||||||
|
selectors: duxStore.selectors,
|
||||||
|
};
|
||||||
|
};
|
79
webpack.config.js
Normal file
79
webpack.config.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const mode = process.env.NODE_ENV || "development";
|
||||||
|
const prod = mode === "production";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
bundle: ["./src/main.js"],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
svelte: path.resolve("node_modules", "svelte"),
|
||||||
|
'~': path.resolve(__dirname, 'src/'),
|
||||||
|
'~C': path.resolve(__dirname, 'src/components/'),
|
||||||
|
},
|
||||||
|
extensions: [".mjs", ".js", ".svelte"],
|
||||||
|
mainFields: ["svelte", "browser", "module", "main"],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: __dirname + "/public",
|
||||||
|
filename: "[name].js",
|
||||||
|
chunkFilename: "[name].[id].js",
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.svelte$/,
|
||||||
|
use: {
|
||||||
|
loader: "svelte-loader",
|
||||||
|
options: {
|
||||||
|
emitCss: true,
|
||||||
|
hotReload: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [
|
||||||
|
/**
|
||||||
|
* MiniCssExtractPlugin doesn't support HMR.
|
||||||
|
* For developing, use 'style-loader' instead.
|
||||||
|
* */
|
||||||
|
prod ? MiniCssExtractPlugin.loader : "style-loader",
|
||||||
|
"css-loader",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.s[ac]ss$/i,
|
||||||
|
use: [
|
||||||
|
// Creates `style` nodes from JS strings
|
||||||
|
"style-loader",
|
||||||
|
// Translates CSS into CommonJS
|
||||||
|
"css-loader",
|
||||||
|
// Compiles Sass to CSS
|
||||||
|
{
|
||||||
|
loader: "sass-loader",
|
||||||
|
options: {
|
||||||
|
webpackImporter: false,
|
||||||
|
//implementation: require("node-sass"),
|
||||||
|
implementation: require("sass"),
|
||||||
|
sassOptions: {
|
||||||
|
indentWidth: 4,
|
||||||
|
includePaths: ["sass", "node_modules"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mode,
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: "[name].css",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
devtool: prod ? false : "source-map",
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user