Mostly functional implementation using redux
This commit is contained in:
@@ -6,8 +6,9 @@ indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
quote=singleQuote
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
483
.eslintrc.js
483
.eslintrc.js
@@ -1,241 +1,264 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
react: true,
|
||||
},
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
extends: ['plugin:react/recommended', 'eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint'],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"accessor-pairs": "error",
|
||||
"array-bracket-newline": "error",
|
||||
"array-bracket-spacing": "error",
|
||||
"array-callback-return": "error",
|
||||
"array-element-newline": 0,
|
||||
"arrow-body-style": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": "error",
|
||||
"callback-return": "error",
|
||||
// note you must disable the base rule as it can report incorrect errors
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'react/prop-types': 0,
|
||||
'accessor-pairs': 'error',
|
||||
'array-bracket-newline': 'error',
|
||||
'array-bracket-spacing': 'error',
|
||||
'array-callback-return': 'error',
|
||||
'array-element-newline': 0,
|
||||
'arrow-body-style': 'error',
|
||||
'arrow-parens': 0,
|
||||
'arrow-spacing': 'error',
|
||||
'block-scoped-var': 'error',
|
||||
'block-spacing': 'error',
|
||||
'brace-style': 'error',
|
||||
'callback-return': 'error',
|
||||
camelcase: 0,
|
||||
"capitalized-comments": 0,
|
||||
"class-methods-use-this": "error",
|
||||
"comma-dangle": 0,
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
complexity: "error",
|
||||
"computed-property-spacing": "error",
|
||||
"consistent-return": "error",
|
||||
"consistent-this": "error",
|
||||
curly: "error",
|
||||
"default-case": "error",
|
||||
"default-case-last": "error",
|
||||
"default-param-last": "error",
|
||||
"dot-location": "error",
|
||||
"dot-notation": "error",
|
||||
"eol-last": "error",
|
||||
eqeqeq: "error",
|
||||
"func-call-spacing": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-names": "error",
|
||||
"func-style": "error",
|
||||
"function-call-argument-newline": 0,
|
||||
"function-paren-newline": "error",
|
||||
"generator-star-spacing": "error",
|
||||
"global-require": "error",
|
||||
"grouped-accessor-pairs": "error",
|
||||
"guard-for-in": "error",
|
||||
"handle-callback-err": "error",
|
||||
"id-blacklist": "error",
|
||||
"id-denylist": "error",
|
||||
"id-length": "error",
|
||||
"id-match": "error",
|
||||
"implicit-arrow-linebreak": "error",
|
||||
indent: ["error", 2],
|
||||
"indent-legacy": ["error", 2],
|
||||
"init-declarations": "error",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "error",
|
||||
"keyword-spacing": "error",
|
||||
"line-comment-position": "error",
|
||||
"linebreak-style": "error",
|
||||
"lines-around-comment": "error",
|
||||
"lines-around-directive": "error",
|
||||
"lines-between-class-members": "error",
|
||||
"max-classes-per-file": "error",
|
||||
"max-depth": "error",
|
||||
"max-len": ["error", 180],
|
||||
"max-lines": "error",
|
||||
"max-lines-per-function": "error",
|
||||
"max-nested-callbacks": "error",
|
||||
"max-params": "error",
|
||||
"max-statements": 0,
|
||||
"max-statements-per-line": "error",
|
||||
"multiline-comment-style": 0,
|
||||
"multiline-ternary": "error",
|
||||
"new-cap": 0,
|
||||
"new-parens": "error",
|
||||
"newline-after-var": 0,
|
||||
"newline-before-return": 0,
|
||||
"newline-per-chained-call": 0,
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-await-in-loop": "error",
|
||||
"no-bitwise": "error",
|
||||
"no-buffer-constructor": "error",
|
||||
"no-caller": "error",
|
||||
"no-catch-shadow": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-console": "warn",
|
||||
"no-constructor-return": "error",
|
||||
"no-continue": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty-function": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-extra-parens": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-invalid-this": "error",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-magic-numbers": 0,
|
||||
"no-mixed-operators": "error",
|
||||
"no-mixed-requires": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-multiple-empty-lines": "error",
|
||||
"no-native-reassign": "error",
|
||||
"no-negated-condition": "error",
|
||||
"no-negated-in-lhs": "error",
|
||||
"no-nested-ternary": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-object": "error",
|
||||
"no-new-require": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-path-concat": "error",
|
||||
"no-plusplus": "error",
|
||||
"no-process-env": "error",
|
||||
"no-process-exit": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"no-proto": "error",
|
||||
"no-restricted-exports": "error",
|
||||
"no-restricted-globals": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-restricted-modules": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-return-await": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "error",
|
||||
"no-spaced-func": "error",
|
||||
"no-sync": "error",
|
||||
"no-tabs": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-ternary": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": 0,
|
||||
"no-underscore-dangle": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unreachable-loop": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"nonblock-statement-body-position": "error",
|
||||
"object-curly-newline": "error",
|
||||
"object-curly-spacing": 0,
|
||||
"object-property-newline": "error",
|
||||
"object-shorthand": "error",
|
||||
"one-var": 0,
|
||||
"one-var-declaration-per-line": 0,
|
||||
"operator-assignment": "error",
|
||||
"operator-linebreak": "error",
|
||||
"padded-blocks": 0,
|
||||
"padding-line-between-statements": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-destructuring": "error",
|
||||
"prefer-exponentiation-operator": "error",
|
||||
"prefer-named-capture-group": "error",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-object-spread": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-reflect": "error",
|
||||
"prefer-regex-literals": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"quote-props": ["error", "as-needed"],
|
||||
quotes: ["error", "single"],
|
||||
radix: "error",
|
||||
"require-atomic-updates": "error",
|
||||
"require-await": "error",
|
||||
"require-jsdoc": "error",
|
||||
"require-unicode-regexp": "error",
|
||||
"rest-spread-spacing": "error",
|
||||
semi: "error",
|
||||
"semi-spacing": "error",
|
||||
"semi-style": "error",
|
||||
"sort-imports": 0,
|
||||
"sort-keys": 0,
|
||||
"sort-vars": 0,
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": "error",
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
strict: "error",
|
||||
"switch-colon-spacing": "error",
|
||||
"symbol-description": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"template-tag-spacing": "error",
|
||||
"unicode-bom": "error",
|
||||
"valid-jsdoc": "error",
|
||||
"vars-on-top": "error",
|
||||
"wrap-iife": "error",
|
||||
"wrap-regex": "error",
|
||||
"yield-star-spacing": "error",
|
||||
yoda: "error",
|
||||
'capitalized-comments': 0,
|
||||
'class-methods-use-this': 'error',
|
||||
'comma-dangle': 0,
|
||||
'comma-spacing': 'error',
|
||||
'comma-style': 'error',
|
||||
complexity: 'error',
|
||||
'computed-property-spacing': 'error',
|
||||
'consistent-return': 'error',
|
||||
'consistent-this': 'error',
|
||||
curly: 'error',
|
||||
'default-case': 'error',
|
||||
'default-case-last': 'error',
|
||||
'default-param-last': 'error',
|
||||
'dot-location': 0,
|
||||
'dot-notation': 'error',
|
||||
'eol-last': 'error',
|
||||
eqeqeq: 'error',
|
||||
'func-call-spacing': 'error',
|
||||
'func-name-matching': 'error',
|
||||
'func-names': 'error',
|
||||
'func-style': 'error',
|
||||
'function-call-argument-newline': 0,
|
||||
'function-paren-newline': 0,
|
||||
'generator-star-spacing': 0,
|
||||
'global-require': 'error',
|
||||
'grouped-accessor-pairs': 'error',
|
||||
'guard-for-in': 'error',
|
||||
'handle-callback-err': 'error',
|
||||
'id-blacklist': 'error',
|
||||
'id-denylist': 'error',
|
||||
'id-length': 'error',
|
||||
'id-match': 'error',
|
||||
'implicit-arrow-linebreak': 0,
|
||||
indent: 0,
|
||||
// indent: [
|
||||
// 'error',
|
||||
// 2,
|
||||
// {
|
||||
// SwitchCase: 1,
|
||||
// MemberExpression: 1,
|
||||
// CallExpression: { arguments: 1 },
|
||||
// offsetTernaryExpressions: true,
|
||||
// flatTernaryExpressions: true,
|
||||
// ignoredNodes: ['ConditionalExpression'],
|
||||
// },
|
||||
// ],
|
||||
'indent-legacy': 0,
|
||||
'init-declarations': 'error',
|
||||
'jsx-quotes': 'error',
|
||||
'key-spacing': 'error',
|
||||
'keyword-spacing': 'error',
|
||||
'line-comment-position': 'error',
|
||||
'linebreak-style': 'error',
|
||||
'lines-around-comment': 0,
|
||||
'lines-around-directive': 'error',
|
||||
'lines-between-class-members': 'error',
|
||||
'max-classes-per-file': 'error',
|
||||
'max-depth': 'error',
|
||||
'max-len': ['error', 180],
|
||||
'max-lines': 'error',
|
||||
'max-lines-per-function': 0,
|
||||
'max-nested-callbacks': 'error',
|
||||
'max-params': 0,
|
||||
'max-statements': 0,
|
||||
'max-statements-per-line': 'error',
|
||||
'multiline-comment-style': 0,
|
||||
'multiline-ternary': 0,
|
||||
'new-cap': 0,
|
||||
'new-parens': 'error',
|
||||
'newline-after-var': 0,
|
||||
'newline-before-return': 0,
|
||||
'newline-per-chained-call': 0,
|
||||
'no-alert': 'error',
|
||||
'no-array-constructor': 'error',
|
||||
'no-await-in-loop': 'error',
|
||||
'no-bitwise': 'error',
|
||||
'no-buffer-constructor': 'error',
|
||||
'no-caller': 'error',
|
||||
'no-catch-shadow': 'error',
|
||||
'no-confusing-arrow': 0,
|
||||
'no-console': 'warn',
|
||||
'no-constructor-return': 'error',
|
||||
'no-continue': 0,
|
||||
'no-div-regex': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-else-return': 'error',
|
||||
'no-empty-function': 'error',
|
||||
'no-eq-null': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-extra-bind': 'error',
|
||||
'no-extra-label': 'error',
|
||||
'no-extra-parens': 0,
|
||||
'no-floating-decimal': 'error',
|
||||
'no-implicit-coercion': 'error',
|
||||
'no-implicit-globals': 'error',
|
||||
'no-implied-eval': 'error',
|
||||
'no-inline-comments': 'error',
|
||||
'no-invalid-this': 'error',
|
||||
'no-iterator': 'error',
|
||||
'no-label-var': 'error',
|
||||
'no-labels': 'error',
|
||||
'no-lone-blocks': 'error',
|
||||
'no-lonely-if': 'error',
|
||||
'no-loop-func': 'error',
|
||||
'no-loss-of-precision': 'error',
|
||||
'no-magic-numbers': 0,
|
||||
'no-mixed-operators': 0,
|
||||
'no-mixed-requires': 'error',
|
||||
'no-multi-assign': 'error',
|
||||
'no-multi-spaces': 'error',
|
||||
'no-multi-str': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'no-native-reassign': 'error',
|
||||
'no-negated-condition': 'error',
|
||||
'no-negated-in-lhs': 'error',
|
||||
'no-nested-ternary': 0,
|
||||
'no-new': 'error',
|
||||
'no-new-func': 'error',
|
||||
'no-new-object': 'error',
|
||||
'no-new-require': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-octal-escape': 'error',
|
||||
'no-param-reassign': 'error',
|
||||
'no-path-concat': 'error',
|
||||
'no-plusplus': 'error',
|
||||
'no-process-env': 'error',
|
||||
'no-process-exit': 'error',
|
||||
'no-promise-executor-return': 'error',
|
||||
'no-proto': 'error',
|
||||
'no-restricted-exports': 'error',
|
||||
'no-restricted-globals': 'error',
|
||||
'no-restricted-imports': 'error',
|
||||
'no-restricted-modules': 'error',
|
||||
'no-restricted-properties': 'error',
|
||||
'no-restricted-syntax': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-return-await': 'error',
|
||||
'no-script-url': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-shadow': 'error',
|
||||
'no-spaced-func': 'error',
|
||||
'no-sync': 'error',
|
||||
'no-tabs': 'error',
|
||||
'no-template-curly-in-string': 'error',
|
||||
'no-ternary': 0,
|
||||
'no-throw-literal': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-undef-init': 0,
|
||||
'no-undefined': 0,
|
||||
'no-underscore-dangle': 'error',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unneeded-ternary': 'error',
|
||||
'no-unreachable-loop': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-useless-backreference': 'error',
|
||||
'no-useless-call': 'error',
|
||||
'no-useless-computed-key': 'error',
|
||||
'no-useless-concat': 'error',
|
||||
'no-useless-constructor': 'error',
|
||||
'no-useless-rename': 'error',
|
||||
'no-useless-return': 'error',
|
||||
'no-var': 'error',
|
||||
'no-void': 'error',
|
||||
'no-warning-comments': 'error',
|
||||
'no-whitespace-before-property': 'error',
|
||||
'nonblock-statement-body-position': 'error',
|
||||
'object-curly-newline': 'error',
|
||||
'object-curly-spacing': 0,
|
||||
'object-property-newline': 0,
|
||||
'object-shorthand': 'error',
|
||||
'one-var': 0,
|
||||
'one-var-declaration-per-line': 0,
|
||||
'operator-assignment': 'error',
|
||||
'operator-linebreak': 'error',
|
||||
'padded-blocks': 0,
|
||||
'padding-line-between-statements': 'error',
|
||||
'prefer-arrow-callback': 'error',
|
||||
'prefer-const': 'error',
|
||||
'prefer-destructuring': 'error',
|
||||
'prefer-exponentiation-operator': 'error',
|
||||
'prefer-named-capture-group': 'error',
|
||||
'prefer-numeric-literals': 'error',
|
||||
'prefer-object-spread': 'error',
|
||||
'prefer-promise-reject-errors': 'error',
|
||||
'prefer-reflect': 'error',
|
||||
'prefer-regex-literals': 'error',
|
||||
'prefer-rest-params': 'error',
|
||||
'prefer-spread': 'error',
|
||||
'prefer-template': 'error',
|
||||
'quote-props': ['error', 'as-needed'],
|
||||
quotes: ['error', 'single'],
|
||||
radix: 'error',
|
||||
'require-atomic-updates': 'error',
|
||||
'require-await': 'error',
|
||||
'require-jsdoc': 0,
|
||||
'require-unicode-regexp': 'error',
|
||||
'rest-spread-spacing': 'error',
|
||||
semi: 'error',
|
||||
'semi-spacing': 'error',
|
||||
'semi-style': 'error',
|
||||
'sort-imports': 0,
|
||||
'sort-keys': 0,
|
||||
'sort-vars': 0,
|
||||
'space-before-blocks': 'error',
|
||||
'space-before-function-paren': 0,
|
||||
'space-in-parens': 'error',
|
||||
'space-infix-ops': 'error',
|
||||
'space-unary-ops': 'error',
|
||||
'spaced-comment': 'error',
|
||||
strict: 'error',
|
||||
'switch-colon-spacing': 'error',
|
||||
'symbol-description': 'error',
|
||||
'template-curly-spacing': 'error',
|
||||
'template-tag-spacing': 'error',
|
||||
'unicode-bom': 'error',
|
||||
'valid-jsdoc': 'error',
|
||||
'vars-on-top': 'error',
|
||||
'wrap-iife': 'error',
|
||||
'wrap-regex': 'error',
|
||||
'yield-star-spacing': 'error',
|
||||
yoda: 'error',
|
||||
},
|
||||
};
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
*bak
|
||||
|
||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"arrowParens": "avoid",
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
22
package.json
22
package.json
@@ -1,14 +1,25 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "parcel serve src/index.htm"
|
||||
"start": "parcel serve src/index.htm",
|
||||
"type": "tsc --noEmit",
|
||||
"lint": "eslint --ext .ts,.tsx ./src",
|
||||
"lint:fix": "eslint --ext .ts,.tsx ./src --fix",
|
||||
"prettier": "prettier --write 'src/**/*.{ts,tsx,json,md,js,scss}'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.0.5",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/styled-components": "^5.1.7",
|
||||
"@types/react-redux": "^7.1.14",
|
||||
"eslint": "^7.13.0"
|
||||
"@types/styled-components": "^5.1.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
||||
"@typescript-eslint/parser": "^4.11.1",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"typescript": "^4.0.5",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.5.0",
|
||||
@@ -17,7 +28,6 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"styled-components": "^5.2.1",
|
||||
"tslib": "^2.0.3"
|
||||
"styled-components": "^5.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import React, { FC } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import { AppDispatch, AppState, CellValue, Coordinate, dig } from "../store";
|
||||
|
||||
interface StateProps {
|
||||
size: number;
|
||||
board: CellValue[];
|
||||
digs: Record<number, number[]>;
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
dig(coordinate: Coordinate): void;
|
||||
}
|
||||
|
||||
type Props = ActionProps & StateProps;
|
||||
|
||||
const Cell = styled.div`
|
||||
display: inline-flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
${Cell} {
|
||||
border-left: 1px solid silver;
|
||||
border-top: 1px solid silver;
|
||||
|
||||
&:nth-last-child(1) {
|
||||
border-right: 1px solid silver;
|
||||
}
|
||||
}
|
||||
&:nth-last-child(1) {
|
||||
${Cell} {
|
||||
border-bottom: 1px solid silver;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Board: FC<Props> = ({ dig, digs, board, size }) => {
|
||||
return (
|
||||
<>
|
||||
{[...Array(size).keys()].map((row) => (
|
||||
<Row key={`row-${row}`} id={`row-${row}`}>
|
||||
{[...Array(size).keys()].map((col) => (
|
||||
<Cell
|
||||
id={`row-${row}-col-${col}`}
|
||||
key={`row-${row}-col-${col}`}
|
||||
onClick={() => {
|
||||
dig({ row, col });
|
||||
}}
|
||||
>
|
||||
{/*digs[row]?.includes(col) && board[row * size + col][0] */}
|
||||
{digs[row]?.includes(col)
|
||||
? "D"
|
||||
: board[row * size + col].toString()}
|
||||
</Cell>
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
size: state.size,
|
||||
board: state.board,
|
||||
digs: state.digs,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch): ActionProps => ({
|
||||
dig: (coordinate: Coordinate) => dispatch(dig(coordinate)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Board);
|
||||
90
src/components/GameBoard.tsx
Normal file
90
src/components/GameBoard.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { FC } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { selectBoardRows } from '../selectors';
|
||||
import { AppDispatch, AppState, dig as digAction } from '../store';
|
||||
import { Cell, Coordinate, GameStatus } from '../types';
|
||||
|
||||
interface StateProps {
|
||||
size: number;
|
||||
board: Cell[][];
|
||||
status: GameStatus;
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
dig(coordinate: Coordinate): void;
|
||||
}
|
||||
|
||||
type Props = ActionProps & StateProps;
|
||||
|
||||
const Cell = styled.div<{ cell: Cell; status: GameStatus }>`
|
||||
display: inline-flex;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid silver;
|
||||
margin: 1px;
|
||||
user-select: none;
|
||||
|
||||
/* Value based style */
|
||||
${({ cell }) => (cell.value === 'B' ? 'color: red;' : cell.value > 0 ? 'color: teal;' : 'color: white;')}
|
||||
|
||||
/* Reveal based style */
|
||||
${({ cell }) =>
|
||||
cell.reveal
|
||||
? `background-color: transparent;
|
||||
cursor: default;`
|
||||
: `background-color: silver;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #838484;
|
||||
}
|
||||
`}
|
||||
|
||||
/* Digged based style */
|
||||
${({ cell }) => cell.digged && 'border-color: green;'}
|
||||
|
||||
/* Digged based style */
|
||||
${({ cell }) => !cell.reveal && 'text-indent: -9999px;'} /* ${({ cell }) => !cell.reveal && 'color: rgba(0,0,0,.1)'} */
|
||||
|
||||
${({ status }) =>
|
||||
status !== 'RUNNING' &&
|
||||
`
|
||||
cursor: default;
|
||||
&:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
`;
|
||||
|
||||
const GameBoard: FC<Props> = ({ dig, board, status }) => (
|
||||
<>
|
||||
{board.map((row, rowIndex) => (
|
||||
<Row key={`row-${rowIndex}`}>
|
||||
{row.map(cell => (
|
||||
<Cell key={`cell-${cell.row}-${cell.col}`} cell={cell} status={status} onClick={() => dig(cell)}>
|
||||
{cell.value}
|
||||
</Cell>
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
size: state.size,
|
||||
board: selectBoardRows(state),
|
||||
status: state.status,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch): ActionProps => ({
|
||||
dig: (coordinate: Coordinate) => dispatch(digAction(coordinate)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(GameBoard);
|
||||
@@ -1,39 +1,39 @@
|
||||
import React, { FC, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { AppDispatch, AppState, startGame } from "../store";
|
||||
import React, { FC, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppDispatch, AppState, startGame as startGameAction } from '../store';
|
||||
|
||||
interface StateProps {
|
||||
size: number;
|
||||
numberOfMines: number;
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
startGame(size: number): void;
|
||||
startGame(size: number, mines: number): void;
|
||||
}
|
||||
|
||||
type Props = ActionProps & StateProps;
|
||||
|
||||
const StartGame: FC<Props> = ({ startGame, size }) => {
|
||||
const StartGame: FC<Props> = ({ startGame, size, numberOfMines }) => {
|
||||
const [userSize, setUserSize] = useState(size);
|
||||
const [userMines, setUserMines] = useState(numberOfMines);
|
||||
return (
|
||||
<>
|
||||
<label htmlFor="size">Size:</label>
|
||||
<input
|
||||
type="number"
|
||||
name="size"
|
||||
value={userSize}
|
||||
onChange={(e) => setUserSize(parseInt(e.target.value, 10))}
|
||||
/>
|
||||
<button onClick={() => startGame(userSize)}>Start</button>
|
||||
<input type="number" name="size" value={userSize} onChange={ev => setUserSize(parseInt(ev.target.value, 10))} />
|
||||
<label htmlFor="size">Mines:</label>
|
||||
<input type="number" name="mines" value={userMines} onChange={ev => setUserMines(parseInt(ev.target.value, 10))} />
|
||||
<button onClick={() => startGame(userSize, userMines)}>Start</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
size: state.size,
|
||||
numberOfMines: state.numberOfMines,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch): ActionProps => ({
|
||||
startGame: (size: number) => dispatch(startGame(size)),
|
||||
startGame: (size: number, numberOfMines: number) => dispatch(startGameAction({ size, numberOfMines })),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StartGame);
|
||||
|
||||
30
src/game.tsx
30
src/game.tsx
@@ -1,27 +1,29 @@
|
||||
import React, { FC } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import Board from "./components/Board";
|
||||
import StartGame from "./components/StartGame";
|
||||
import { AppState } from "./store";
|
||||
import React, { FC } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import GameBoard from './components/GameBoard';
|
||||
import StartGame from './components/StartGame';
|
||||
import { AppState } from './store';
|
||||
import { GameStatus } from './types';
|
||||
|
||||
interface StateProps {
|
||||
started: boolean;
|
||||
status: GameStatus;
|
||||
}
|
||||
|
||||
type Props = StateProps;
|
||||
|
||||
const Game: FC<Props> = ({ started }) => {
|
||||
return (
|
||||
<>
|
||||
<h1>Minesweeper</h1>
|
||||
{!started && <StartGame />}
|
||||
{started && <Board />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
const Game: FC<Props> = ({ started, status }) => (
|
||||
<>
|
||||
<h1>Minesweeper</h1>
|
||||
<h2>{status}</h2>
|
||||
{!started && <StartGame />}
|
||||
{started && <GameBoard />}
|
||||
</>
|
||||
);
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
started: state.started,
|
||||
status: state.status,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Game);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./store";
|
||||
import Game from "./game";
|
||||
import GlobalStyle from "./styling/GlobalStyle";
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './store';
|
||||
import Game from './Game';
|
||||
import GlobalStyle from './styling/GlobalStyle';
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<GlobalStyle />
|
||||
<Game />
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
14
src/selectors.ts
Normal file
14
src/selectors.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { AppState } from './store';
|
||||
import { Cell } from './types';
|
||||
|
||||
export const selectAppState = (state: AppState): AppState => state;
|
||||
|
||||
export const selectBoard = createSelector(selectAppState, (state: AppState): Cell[] => state.board);
|
||||
|
||||
export const selectBoardRows = createSelector(selectBoard, (board: Cell[]): Cell[][] =>
|
||||
board.reduce((rows, cell) => {
|
||||
rows[cell.row] = [...(rows[cell.row] || []), cell];
|
||||
return rows;
|
||||
}, [] as Cell[][])
|
||||
);
|
||||
217
src/store.ts
217
src/store.ts
@@ -1,98 +1,185 @@
|
||||
import { configureStore, createAction, createReducer } from "@reduxjs/toolkit";
|
||||
import { configureStore, createAction, createReducer } from '@reduxjs/toolkit';
|
||||
import { Cell, Coordinate, GameStatus } from './types';
|
||||
import { range, rangeValues, shuffle } from './utils';
|
||||
|
||||
export type CellValue = "E" | "B" | number;
|
||||
export interface Coordinate {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
/* ********* TYPES ********************************************************************************** */
|
||||
|
||||
export interface AppState {
|
||||
started: boolean;
|
||||
status: GameStatus;
|
||||
size: number;
|
||||
board: CellValue[];
|
||||
digs: Record<number, number[]>;
|
||||
numberOfMines: number;
|
||||
board: Cell[];
|
||||
}
|
||||
|
||||
const initialBoardSize = 10;
|
||||
/* ********* FUNCTIONS ****************************************************************************** */
|
||||
|
||||
const random = (min: number, max: number): number =>
|
||||
min + Math.floor(Math.random() * (max - min));
|
||||
const sameCoordinate = (coordinate1: Coordinate, coordinate2: Coordinate): boolean =>
|
||||
coordinate1.row === coordinate2.row && coordinate1.col === coordinate2.col;
|
||||
|
||||
const createBoard = (size: number, bombs: number = 5): CellValue[] => {
|
||||
const board: CellValue[] = Array.from(new Array(size * size), () => "E");
|
||||
const getIndexFromCoordinate = (coordinate: Coordinate, size: number) => coordinate.row * size + coordinate.col;
|
||||
|
||||
let bombsLeft = bombs;
|
||||
while (bombsLeft) {
|
||||
const row = random(0, size);
|
||||
const cell = random(0, size);
|
||||
if (board[row * size + cell] === "B") {
|
||||
const neighbouringIndexes = function* neighbouringIndexes(size: number, coordinate: Coordinate): Generator<number> {
|
||||
for (const row of range(Math.max(coordinate.row - 1, 0), Math.min(coordinate.row + 2, size))) {
|
||||
for (const col of range(Math.max(coordinate.col - 1, 0), Math.min(coordinate.col + 2, size))) {
|
||||
if (row !== coordinate.row || col !== coordinate.col) {
|
||||
yield getIndexFromCoordinate({ row, col }, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const neighbouringCells = function* neighbouringCells(
|
||||
board: Cell[],
|
||||
size: number,
|
||||
coordinate: Coordinate
|
||||
): Generator<Cell> {
|
||||
for (const index of neighbouringIndexes(size, coordinate)) {
|
||||
if (board[index] === undefined) {
|
||||
continue;
|
||||
}
|
||||
board[row * size + cell] = "B";
|
||||
bombsLeft -= 1;
|
||||
yield board[index];
|
||||
}
|
||||
board.forEach((cell, index) => {
|
||||
if (cell === "B") {
|
||||
return;
|
||||
}
|
||||
const row = Math.floor(index / size);
|
||||
const col = index - row * size;
|
||||
let bombCount = 0;
|
||||
for (let y = Math.max(0, row - 1); y <= Math.min(row + 1, size); y += 1) {
|
||||
for (let x = Math.max(0, col - 1); x <= Math.min(col + 1, size); x += 1) {
|
||||
if (board[y * size + x] === "B") {
|
||||
bombCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
board[index] = bombCount;
|
||||
});
|
||||
|
||||
return board;
|
||||
return false;
|
||||
};
|
||||
|
||||
const sameCoordinate = (c1: Coordinate, c2: Coordinate): boolean =>
|
||||
c1.col === c2.col && c1.row === c2.row;
|
||||
const createMines = (size: number, numberOfMines: number): boolean[] =>
|
||||
shuffle([...rangeValues(0, numberOfMines, true), ...rangeValues(0, size ** 2 - numberOfMines, false)]);
|
||||
|
||||
const dig = (
|
||||
board: CellValue[],
|
||||
digs: Record<number, number[]>,
|
||||
{ row, col }: Coordinate
|
||||
) => {
|
||||
const size = Math.sqrt(board.length);
|
||||
for (let y = Math.max(0, row - 1); y <= Math.min(row + 1, size); y += 1) {
|
||||
for (let x = Math.max(0, col - 1); x <= Math.min(col + 1, size); x += 1) {
|
||||
if (board[y * size + x] === "B") {
|
||||
const createBoard = (size: number, numberOfMines: number): Cell[] => {
|
||||
const mines = createMines(size, numberOfMines);
|
||||
|
||||
const countMines = (row: number, col: number): number =>
|
||||
[...neighbouringIndexes(size, { row, col })].reduce((bombs, index) => bombs + (mines[index] ? 1 : 0), 0);
|
||||
|
||||
return range(0, size).reduce(
|
||||
(board, row, rowIndex) => [
|
||||
...board,
|
||||
...range(0, size).map(
|
||||
(col, colIndex) =>
|
||||
({
|
||||
row,
|
||||
col,
|
||||
value: mines[rowIndex * size + colIndex] ? 'B' : countMines(row, col),
|
||||
digged: false,
|
||||
reveal: false,
|
||||
} as Cell)
|
||||
),
|
||||
],
|
||||
[] as Cell[]
|
||||
);
|
||||
};
|
||||
|
||||
const allNeighbouringCells = function* allNeighbouringCells(
|
||||
board: Cell[],
|
||||
size: number,
|
||||
coordinate: Coordinate,
|
||||
include: (cell: Cell) => boolean
|
||||
): Generator<Cell> {
|
||||
const visited: number[] = [];
|
||||
const todo: Cell[] = [...neighbouringCells(board, size, coordinate)];
|
||||
let cell: Cell | undefined = undefined;
|
||||
while ((cell = todo.pop())) {
|
||||
const index = getIndexFromCoordinate(cell, size);
|
||||
if (visited.includes(index)) {
|
||||
continue;
|
||||
}
|
||||
visited.push(index);
|
||||
if (include(cell)) {
|
||||
yield cell;
|
||||
if (cell.value === 0) {
|
||||
todo.push(...neighbouringCells(board, size, cell));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* ********* DEFAULTS ******************************************************************************* */
|
||||
|
||||
const INITIAL_BOARD_SIZE = 10;
|
||||
const INITIAL_NUMBER_OF_MINES = 8;
|
||||
|
||||
const defaultAppState: AppState = {
|
||||
started: true,
|
||||
size: initialBoardSize,
|
||||
board: createBoard(initialBoardSize),
|
||||
digs: [],
|
||||
status: 'RUNNING',
|
||||
size: INITIAL_BOARD_SIZE,
|
||||
numberOfMines: INITIAL_NUMBER_OF_MINES,
|
||||
board: createBoard(INITIAL_BOARD_SIZE, INITIAL_NUMBER_OF_MINES),
|
||||
};
|
||||
|
||||
export const startGame = createAction<number>("START_GAME");
|
||||
export const dig = createAction<Coordinate>("DIG");
|
||||
/* ********* ACTIONS ******************************************************************************** */
|
||||
|
||||
const game = createReducer(defaultAppState, (builder) => {
|
||||
export const startGame = createAction<{ size: number; numberOfMines: number }>('START_GAME');
|
||||
export const dig = createAction<Coordinate>('DIG');
|
||||
|
||||
/* ********* REDUCER ******************************************************************************** */
|
||||
|
||||
const game = createReducer(defaultAppState, builder => {
|
||||
builder
|
||||
.addCase(startGame, (state, action) => ({
|
||||
...state,
|
||||
started: true,
|
||||
board: createBoard(action.payload),
|
||||
size: action.payload,
|
||||
digs: [],
|
||||
}))
|
||||
.addCase(startGame, (state, { payload: { size, numberOfMines } }) =>
|
||||
/* Set the initial state for a new game */
|
||||
({
|
||||
...state,
|
||||
started: true,
|
||||
status: 'RUNNING',
|
||||
board: createBoard(size, numberOfMines),
|
||||
numberOfMines,
|
||||
size,
|
||||
})
|
||||
)
|
||||
.addCase(dig, (state, { payload: { row, col } }) => {
|
||||
/* Do nothing if the game is not running */
|
||||
if (state.status !== 'RUNNING') {
|
||||
return state;
|
||||
}
|
||||
|
||||
/* Find the cell that is clicked on */
|
||||
const cell = state.board.find(boardCell => sameCoordinate(boardCell, { row, col }));
|
||||
|
||||
/* If the clicked cell is a bomb then the game is over */
|
||||
if (cell?.value === 'B') {
|
||||
return {
|
||||
...state,
|
||||
status: 'LOST',
|
||||
board: state.board.map(boardCell => ({
|
||||
...boardCell,
|
||||
reveal: boardCell.reveal || boardCell.value === 'B',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/* Based on the clicked cell find all the neighbouring cells that can be revealed */
|
||||
const revealingCells =
|
||||
cell?.value === 0
|
||||
? [
|
||||
...allNeighbouringCells(
|
||||
state.board,
|
||||
state.size,
|
||||
cell,
|
||||
neighbouringCell => !neighbouringCell.reveal && neighbouringCell.value !== 'B'
|
||||
),
|
||||
]
|
||||
: [];
|
||||
|
||||
/* Update the board with the state after the selected cell is clicked */
|
||||
const updatedBoard = state.board.map(boardCell =>
|
||||
sameCoordinate(boardCell, { row, col })
|
||||
? { ...boardCell, digged: true, reveal: true }
|
||||
: revealingCells.some(
|
||||
revealingCell => revealingCell.row === boardCell.row && revealingCell.col === boardCell.col
|
||||
)
|
||||
? { ...boardCell, reveal: true }
|
||||
: boardCell
|
||||
);
|
||||
|
||||
/* Check if there are still moves left */
|
||||
const areMovesLeft = updatedBoard.some(boardCell => typeof boardCell.value === 'number' && !boardCell.reveal);
|
||||
|
||||
return {
|
||||
...state,
|
||||
digs: state.digs[row]?.includes(col)
|
||||
? state.digs
|
||||
: { ...state.digs, [row]: [...(state.digs[row] || []), col] },
|
||||
status: areMovesLeft ? 'RUNNING' : 'WON',
|
||||
board: areMovesLeft ? updatedBoard : updatedBoard.map(boardCell => ({ ...boardCell, reveal: true })),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
export const GlobalStyle = createGlobalStyle`
|
||||
html, body {
|
||||
background: black;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
1
src/types/index.ts
Normal file
1
src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './types';
|
||||
13
src/types/types.ts
Normal file
13
src/types/types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type GameStatus = 'RUNNING' | 'WON' | 'LOST';
|
||||
export type CellValue = 'B' | number;
|
||||
|
||||
export interface Coordinate {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
export interface Cell extends Coordinate {
|
||||
value: CellValue;
|
||||
digged: boolean;
|
||||
reveal: boolean;
|
||||
}
|
||||
30
src/utils.ts
Normal file
30
src/utils.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const random = (min: number, max: number): number => min + Math.floor(Math.random() * (max - min));
|
||||
|
||||
export const range = function range(from: number, to: number): number[] {
|
||||
const rangeGenerator = function* rangeGenerator(fromGen: number, toGen: number): Generator<number> {
|
||||
for (let value = fromGen; value < toGen; value += 1) {
|
||||
yield value;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return [...rangeGenerator(from, to)];
|
||||
};
|
||||
|
||||
export const rangeValues = function rangeValues<T>(from: number, to: number, value: T): T[] {
|
||||
const rangeValuesGenerator = function* rangeValuesGenerator(fromGen: number, toGen: number, valueGen: T): Generator<T> {
|
||||
for (let index = fromGen; index < toGen; index += 1) {
|
||||
yield valueGen;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return [...rangeValuesGenerator(from, to, value)];
|
||||
};
|
||||
|
||||
export const shuffle = <T extends any>(array: T[]): T[] => {
|
||||
const shuffledArray = [...array];
|
||||
for (let index1 = shuffledArray.length - 1; index1 > 0; index1 -= 1) {
|
||||
const index2 = Math.floor(Math.random() * (index1 + 1));
|
||||
[shuffledArray[index1], shuffledArray[index2]] = [shuffledArray[index2], shuffledArray[index1]];
|
||||
}
|
||||
return shuffledArray;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"target": "ES5",
|
||||
"lib": ["DOM", "ES2017"],
|
||||
"jsx": "react",
|
||||
"module": "esnext",
|
||||
@@ -14,6 +14,7 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"downlevelIteration": true,
|
||||
"noEmit": true,
|
||||
// "baseUrl": "./src",
|
||||
"importHelpers": true,
|
||||
|
||||
Reference in New Issue
Block a user