From 2c68f44e882fe399ca7ae083bf0cea5c5b6b2d46 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Sun, 19 Jul 2020 16:21:28 -0400 Subject: [PATCH] commit first major batch of work --- .gitignore | 5 + .storybook/main.js | 3 + package.json | 144 ++++++++++++++-- package.yml | 63 +++++++ public/global.css | 76 ++++++++ public/index.html | 30 ++++ public/mass.svg | 5 + rollup.config.js | 102 +++++++++++ scripts/eslint | 36 ++++ scripts/merge_package.pl | 59 +++++++ scripts/prettier | 16 ++ src/App-real.svelte | 41 +++++ src/App.svelte | 93 ++++++++++ src/components/CostMass.svelte | 19 ++ src/components/Engine/index.svelte | 38 ++++ src/components/Field/BasicStory.svelte | 5 + src/components/Field/index.svelte | 20 +++ src/components/Field/stories.js | 12 ++ src/components/Firecons.svelte | 20 +++ src/components/Ftl/index.svelte | 35 ++++ src/components/Ftl/stories.js | 13 ++ src/components/Hull.svelte | 39 +++++ src/components/Identification.svelte | 49 ++++++ src/components/Propulsion/index.svelte | 18 ++ src/components/Screens/index.svelte | 31 ++++ src/components/Section/index.svelte | 8 + src/components/ShipCost.svelte | 58 +++++++ src/components/ShipItem/index.svelte | 29 ++++ src/components/Weapon/index.svelte | 210 +++++++++++++++++++++++ src/components/Weapons/Add.svelte | 203 ++++++++++++++++++++++ src/components/Weapons/Arc.svelte | 50 ++++++ src/components/Weapons/stories.js | 10 ++ src/dux/calc_ship_cost_mass.test.js | 30 ++++ src/dux/engine/index.js | 27 +++ src/dux/ftl/index.js | 23 +++ src/dux/ftl/rules.js | 10 ++ src/dux/ftl/test.js | 7 + src/dux/index.js | 143 +++++++++++++++ src/dux/ship_types.js | 26 +++ src/dux/test.js | 17 ++ src/dux/utils.js | 30 ++++ src/dux/weaponry/index.js | 19 ++ src/dux/weaponry/weapons/index.js | 25 +++ src/dux/weaponry/weapons/weapon/index.js | 30 ++++ src/dux/weapons/rules.js | 59 +++++++ src/dux/weapons/rules.test.js | 29 ++++ src/main.js | 10 ++ src/stores/ship.js | 58 +++++++ webpack.config.js | 79 +++++++++ 49 files changed, 2152 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 .storybook/main.js create mode 100644 package.yml create mode 100644 public/global.css create mode 100644 public/index.html create mode 100644 public/mass.svg create mode 100644 rollup.config.js create mode 100755 scripts/eslint create mode 100755 scripts/merge_package.pl create mode 100755 scripts/prettier create mode 100644 src/App-real.svelte create mode 100644 src/App.svelte create mode 100644 src/components/CostMass.svelte create mode 100644 src/components/Engine/index.svelte create mode 100644 src/components/Field/BasicStory.svelte create mode 100644 src/components/Field/index.svelte create mode 100644 src/components/Field/stories.js create mode 100644 src/components/Firecons.svelte create mode 100644 src/components/Ftl/index.svelte create mode 100644 src/components/Ftl/stories.js create mode 100644 src/components/Hull.svelte create mode 100644 src/components/Identification.svelte create mode 100644 src/components/Propulsion/index.svelte create mode 100644 src/components/Screens/index.svelte create mode 100644 src/components/Section/index.svelte create mode 100644 src/components/ShipCost.svelte create mode 100644 src/components/ShipItem/index.svelte create mode 100644 src/components/Weapon/index.svelte create mode 100644 src/components/Weapons/Add.svelte create mode 100644 src/components/Weapons/Arc.svelte create mode 100644 src/components/Weapons/stories.js create mode 100644 src/dux/calc_ship_cost_mass.test.js create mode 100644 src/dux/engine/index.js create mode 100644 src/dux/ftl/index.js create mode 100644 src/dux/ftl/rules.js create mode 100644 src/dux/ftl/test.js create mode 100644 src/dux/index.js create mode 100644 src/dux/ship_types.js create mode 100644 src/dux/test.js create mode 100644 src/dux/utils.js create mode 100644 src/dux/weaponry/index.js create mode 100644 src/dux/weaponry/weapons/index.js create mode 100644 src/dux/weaponry/weapons/weapon/index.js create mode 100644 src/dux/weapons/rules.js create mode 100644 src/dux/weapons/rules.test.js create mode 100644 src/main.js create mode 100644 src/stores/ship.js create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f105aaf --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +.nyc_output +pnpm-lock.yaml +public/build +public/bundle.* diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000..458d05e --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,3 @@ +module.exports = { + stories: [ '../src/**/*stories.js' ] +}; diff --git a/package.json b/package.json index 5db48ea..c758ddb 100644 --- a/package.json +++ b/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" - }, - "keywords": [], - "author": "", - "license": "ISC" + "author": "Yanick Champoux ", + "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" + }, + "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" } diff --git a/package.yml b/package.yml new file mode 100644 index 0000000..fb50fa5 --- /dev/null +++ b/package.yml @@ -0,0 +1,63 @@ +--- +_merge: + - version + - dependencies + - devDependencies + +name: shipyard +description: Full Thrust ship builder + +author: 'Yanick Champoux ' + +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 diff --git a/public/global.css b/public/global.css new file mode 100644 index 0000000..57b9a99 --- /dev/null +++ b/public/global.css @@ -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; +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..e84a6cb --- /dev/null +++ b/public/index.html @@ -0,0 +1,30 @@ + + + + + + + Svelte app + + + + + + + + + + + + + + + + + + diff --git a/public/mass.svg b/public/mass.svg new file mode 100644 index 0000000..9eaff30 --- /dev/null +++ b/public/mass.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..b3fd9c0 --- /dev/null +++ b/rollup.config.js @@ -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 + }); + } + } + }; +} diff --git a/scripts/eslint b/scripts/eslint new file mode 100755 index 0000000..985209b --- /dev/null +++ b/scripts/eslint @@ -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; diff --git a/scripts/merge_package.pl b/scripts/merge_package.pl new file mode 100755 index 0000000..d38297c --- /dev/null +++ b/scripts/merge_package.pl @@ -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" +} diff --git a/scripts/prettier b/scripts/prettier new file mode 100755 index 0000000..4847d6e --- /dev/null +++ b/scripts/prettier @@ -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; diff --git a/src/App-real.svelte b/src/App-real.svelte new file mode 100644 index 0000000..c6d2171 --- /dev/null +++ b/src/App-real.svelte @@ -0,0 +1,41 @@ + + +
+ + Potato? + + + ship class: + + + + + + + + +
+ + diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..6393dc1 --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,93 @@ + + +
+ + + + + + + + + + + +
+ + + + + {#each weapons as weapon (weapon.id)} + + {/each} + + +
+ +
+ + diff --git a/src/components/CostMass.svelte b/src/components/CostMass.svelte new file mode 100644 index 0000000..12ae058 --- /dev/null +++ b/src/components/CostMass.svelte @@ -0,0 +1,19 @@ +
{ mass }
+
{ cost }
+ + + + diff --git a/src/components/Engine/index.svelte b/src/components/Engine/index.svelte new file mode 100644 index 0000000..6002086 --- /dev/null +++ b/src/components/Engine/index.svelte @@ -0,0 +1,38 @@ + + +
+ + + + + +
+ +
+ + + + + diff --git a/src/components/Field/BasicStory.svelte b/src/components/Field/BasicStory.svelte new file mode 100644 index 0000000..e20b928 --- /dev/null +++ b/src/components/Field/BasicStory.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/components/Field/index.svelte b/src/components/Field/index.svelte new file mode 100644 index 0000000..81e939a --- /dev/null +++ b/src/components/Field/index.svelte @@ -0,0 +1,20 @@ +
+{#if label} + +{/if} + + + +
+ + + + diff --git a/src/components/Field/stories.js b/src/components/Field/stories.js new file mode 100644 index 0000000..313c746 --- /dev/null +++ b/src/components/Field/stories.js @@ -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, +}); diff --git a/src/components/Firecons.svelte b/src/components/Firecons.svelte new file mode 100644 index 0000000..bbe1ac1 --- /dev/null +++ b/src/components/Firecons.svelte @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/src/components/Ftl/index.svelte b/src/components/Ftl/index.svelte new file mode 100644 index 0000000..c84f6e2 --- /dev/null +++ b/src/components/Ftl/index.svelte @@ -0,0 +1,35 @@ + + + + + + {#each types as t (t)} + + {/each} + + + + diff --git a/src/components/Ftl/stories.js b/src/components/Ftl/stories.js new file mode 100644 index 0000000..72af859 --- /dev/null +++ b/src/components/Ftl/stories.js @@ -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() + } +}); diff --git a/src/components/Hull.svelte b/src/components/Hull.svelte new file mode 100644 index 0000000..11aea4f --- /dev/null +++ b/src/components/Hull.svelte @@ -0,0 +1,39 @@ +
+ + + + + + + + + + +
+ + + + diff --git a/src/components/Identification.svelte b/src/components/Identification.svelte new file mode 100644 index 0000000..5c860b9 --- /dev/null +++ b/src/components/Identification.svelte @@ -0,0 +1,49 @@ +
+ + + + + + + +
+ + + + diff --git a/src/components/Propulsion/index.svelte b/src/components/Propulsion/index.svelte new file mode 100644 index 0000000..ee84b36 --- /dev/null +++ b/src/components/Propulsion/index.svelte @@ -0,0 +1,18 @@ + +
+ + + + + +
+ + diff --git a/src/components/Screens/index.svelte b/src/components/Screens/index.svelte new file mode 100644 index 0000000..0dee681 --- /dev/null +++ b/src/components/Screens/index.svelte @@ -0,0 +1,31 @@ + + add_screen()} /> + add_screen(true) }/> + + + { nbr_regular } { nbr_advanced } + + + diff --git a/src/components/Section/index.svelte b/src/components/Section/index.svelte new file mode 100644 index 0000000..e25a39e --- /dev/null +++ b/src/components/Section/index.svelte @@ -0,0 +1,8 @@ +
+ {label} + +
+ + diff --git a/src/components/ShipCost.svelte b/src/components/ShipCost.svelte new file mode 100644 index 0000000..5098761 --- /dev/null +++ b/src/components/ShipCost.svelte @@ -0,0 +1,58 @@ +
+
+ + +
+ +
+ {#if within_budget} + mass unused: { mass_unused } + {:else} + excessive mass: { -mass_unused } + + {/if} +
+ +
+
+ +
+ +
{$ship.general.cost}
+
+
+ + + + diff --git a/src/components/ShipItem/index.svelte b/src/components/ShipItem/index.svelte new file mode 100644 index 0000000..24c6e6c --- /dev/null +++ b/src/components/ShipItem/index.svelte @@ -0,0 +1,29 @@ +
+ + +
{ mass }
+
{ cost }
+
+ + + + diff --git a/src/components/Weapon/index.svelte b/src/components/Weapon/index.svelte new file mode 100644 index 0000000..33ef9f2 --- /dev/null +++ b/src/components/Weapon/index.svelte @@ -0,0 +1,210 @@ + + +
X
+ + + + + + + + + + + + + + + +{#each all_arcs as arc (arc)} + click_arc(arc)} + /> +{/each} + + + + + +
+ + + + diff --git a/src/components/Weapons/Add.svelte b/src/components/Weapons/Add.svelte new file mode 100644 index 0000000..62ed44a --- /dev/null +++ b/src/components/Weapons/Add.svelte @@ -0,0 +1,203 @@ + + +
+ + + + + + + + +{#each arcs as arc (arc)} + click_arc(arc)} + /> +{/each} + + + + + +
{weapon.cost}
+
{weapon.mass}
+ +
+ + + + diff --git a/src/components/Weapons/Arc.svelte b/src/components/Weapons/Arc.svelte new file mode 100644 index 0000000..792a068 --- /dev/null +++ b/src/components/Weapons/Arc.svelte @@ -0,0 +1,50 @@ + + + + + + + + diff --git a/src/components/Weapons/stories.js b/src/components/Weapons/stories.js new file mode 100644 index 0000000..4f49d42 --- /dev/null +++ b/src/components/Weapons/stories.js @@ -0,0 +1,10 @@ +import AddWeapon from './Add.svelte'; + +export default { + title: "add weapon", +}; + +export const beam = () => ({ + Component: AddWeapon, +}); + diff --git a/src/dux/calc_ship_cost_mass.test.js b/src/dux/calc_ship_cost_mass.test.js new file mode 100644 index 0000000..02087c5 --- /dev/null +++ b/src/dux/calc_ship_cost_mass.test.js @@ -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" +); diff --git a/src/dux/engine/index.js b/src/dux/engine/index.js new file mode 100644 index 0000000..9701b9f --- /dev/null +++ b/src/dux/engine/index.js @@ -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; diff --git a/src/dux/ftl/index.js b/src/dux/ftl/index.js new file mode 100644 index 0000000..b84c1f9 --- /dev/null +++ b/src/dux/ftl/index.js @@ -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; diff --git a/src/dux/ftl/rules.js b/src/dux/ftl/rules.js new file mode 100644 index 0000000..46e600a --- /dev/null +++ b/src/dux/ftl/rules.js @@ -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 ), + } +} diff --git a/src/dux/ftl/test.js b/src/dux/ftl/test.js new file mode 100644 index 0000000..4361695 --- /dev/null +++ b/src/dux/ftl/test.js @@ -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' ); + diff --git a/src/dux/index.js b/src/dux/index.js new file mode 100644 index 0000000..3569c99 --- /dev/null +++ b/src/dux/index.js @@ -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; diff --git a/src/dux/ship_types.js b/src/dux/ship_types.js new file mode 100644 index 0000000..21eb107 --- /dev/null +++ b/src/dux/ship_types.js @@ -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); +} diff --git a/src/dux/test.js b/src/dux/test.js new file mode 100644 index 0000000..43b3f13 --- /dev/null +++ b/src/dux/test.js @@ -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' +); + diff --git a/src/dux/utils.js b/src/dux/utils.js new file mode 100644 index 0000000..69795ad --- /dev/null +++ b/src/dux/utils.js @@ -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), + } +} diff --git a/src/dux/weaponry/index.js b/src/dux/weaponry/index.js new file mode 100644 index 0000000..8e3120d --- /dev/null +++ b/src/dux/weaponry/index.js @@ -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; diff --git a/src/dux/weaponry/weapons/index.js b/src/dux/weaponry/weapons/index.js new file mode 100644 index 0000000..c831760 --- /dev/null +++ b/src/dux/weaponry/weapons/index.js @@ -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; diff --git a/src/dux/weaponry/weapons/weapon/index.js b/src/dux/weaponry/weapons/weapon/index.js new file mode 100644 index 0000000..849a495 --- /dev/null +++ b/src/dux/weaponry/weapons/weapon/index.js @@ -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; diff --git a/src/dux/weapons/rules.js b/src/dux/weapons/rules.js new file mode 100644 index 0000000..f0fe61b --- /dev/null +++ b/src/dux/weapons/rules.js @@ -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 + } + + +} diff --git a/src/dux/weapons/rules.test.js b/src/dux/weapons/rules.test.js new file mode 100644 index 0000000..58c6f2b --- /dev/null +++ b/src/dux/weapons/rules.test.js @@ -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) ) ); diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..e7e7816 --- /dev/null +++ b/src/main.js @@ -0,0 +1,10 @@ +import App from './App.svelte'; + +const app = new App({ + target: document.body, + props: { + name: 'world' + } +}); + +export default app; diff --git a/src/stores/ship.js b/src/stores/ship.js new file mode 100644 index 0000000..ea2596b --- /dev/null +++ b/src/stores/ship.js @@ -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, + }; +}; diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..e51f5b0 --- /dev/null +++ b/webpack.config.js @@ -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", +};