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",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"author": "Yanick Champoux <yanick@babyl.ca>",
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.10.1",
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/node": "^7.10.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": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
"description": "Full Thrust ship builder",
|
||||
"devDependencies": {
|
||||
"@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