diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..dd87e2d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +build diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..1b03b6e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,26 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "no-magic-numbers": "off", + "no-console": "warn", + "no-warning-comments": [ + "warn", + { + "terms": ["TODO"], + "location": "start" + } + ], + "no-alert": "error", + "no-duplicate-imports": "error", + "no-unused-private-class-members": "error", + "camelcase": "error", + "eqeqeq": "error" + } +} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 07e8c7c..f80aea0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,7 +4,7 @@ on: branches: [master] jobs: - build-test: + lint-test-build: runs-on: ubuntu-latest steps: - name: Checkout @@ -15,7 +15,9 @@ jobs: node-version: "18.x" - name: Install dependencies run: npm install - - name: Build package - run: npm run build + - name: Lint + run: npm run lint - name: Run tests run: npm test + - name: Build package + run: npm run build diff --git a/package-lock.json b/package-lock.json index 3851ebb..94c2942 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ant-colony-simulation", - "version": "0.0.5", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ant-colony-simulation", - "version": "0.0.5", + "version": "0.0.4", "license": "MIT", "devDependencies": { "@testing-library/jest-dom": "^6.1.0", @@ -16,7 +16,10 @@ "@types/p5": "^1.6.2", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", "css-loader": "^6.8.1", + "eslint": "^8.48.0", "gh-pages": "^5.0.0", "html-webpack-plugin": "^5.5.3", "jest": "^29.6.2", @@ -33,6 +36,15 @@ "webpack-dev-server": "^4.15.1" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", @@ -719,6 +731,140 @@ "node": ">=10.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1198,6 +1344,41 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1650,6 +1831,12 @@ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, "node_modules/@types/send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", @@ -1725,6 +1912,224 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", + "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/type-utils": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", + "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", + "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.5.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -1977,6 +2382,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -3071,6 +3485,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -3178,6 +3598,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -3196,6 +3628,18 @@ "node": ">=6" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -3420,65 +3864,295 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.48.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "node_modules/eslint/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" + "argparse": "^2.0.1" }, "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -3494,6 +4168,18 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -3665,12 +4351,34 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -3680,6 +4388,15 @@ "node": ">= 4.9.1" } }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -3701,6 +4418,18 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", @@ -3802,6 +4531,26 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -4075,6 +4824,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -4460,6 +5215,40 @@ "postcss": "^8.1.0" } }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -4747,6 +5536,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", @@ -6178,6 +6976,12 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6190,6 +6994,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6211,6 +7021,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -6248,6 +7067,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6287,6 +7119,12 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6398,6 +7236,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -6746,6 +7593,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6826,6 +7690,18 @@ "tslib": "^2.0.3" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6914,6 +7790,15 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7089,6 +7974,15 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -7218,6 +8112,26 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7467,6 +8381,16 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7482,6 +8406,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8207,6 +9154,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -8306,6 +9259,18 @@ "node": ">=0.8.0" } }, + "node_modules/ts-api-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.2.tgz", + "integrity": "sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-jest": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", @@ -8390,6 +9355,18 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", diff --git a/package.json b/package.json index 226572e..264b939 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ant-colony-simulation", - "version": "0.0.5", + "version": "0.0.4", "description": "Ant colony simulation with TypeScript and p5.js", "homepage": "https://hasnainroopawalla.github.io/ant-colony-simulation", "private": true, @@ -10,7 +10,9 @@ "predeploy": "npm run build", "deploy": "gh-pages -d build", "test": "jest", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "lint": "eslint . --ext .ts,.tsx", + "lint:fix": "eslint . --fix --ext .ts,.tsx" }, "keywords": [], "author": "", @@ -23,7 +25,10 @@ "@types/p5": "^1.6.2", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", "css-loader": "^6.8.1", + "eslint": "^8.48.0", "gh-pages": "^5.0.0", "html-webpack-plugin": "^5.5.3", "jest": "^29.6.2", @@ -39,4 +44,4 @@ "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" } -} +} \ No newline at end of file diff --git a/src/aco/ant.ts b/src/aco/ant.ts index 4a67340..76b2e8f 100644 --- a/src/aco/ant.ts +++ b/src/aco/ant.ts @@ -3,7 +3,7 @@ import { World } from "./world"; import { Colony } from "./colony"; import { config } from "./config"; import { IPheromoneType, Pheromone } from "./pheromone"; -import { areCirclesOverlapping, distance, randomFloat } from "./utils"; +import { areCirclesIntersecting, distance, randomFloat } from "./utils"; import { Vector, fromAngle } from "./vector"; export enum IAntState { @@ -11,11 +11,18 @@ export enum IAntState { SearchingForFood, } +type IAntennas = { + leftAntenna: Vector; + frontAntenna: Vector; + rightAntenna: Vector; +}; + export class Ant { p: p5; world: World; position: Vector; velocity: Vector; + desiredVelocity: Vector; acceleration: Vector; angle: number; wanderAngle: number; @@ -32,122 +39,125 @@ export class Ant { this.wanderAngle = 0; this.angle = randomFloat(0, this.p.TWO_PI); this.velocity = fromAngle(this.angle); + this.desiredVelocity = this.velocity.copy(); this.acceleration = new Vector(); this.searchingForFood(); } - private approachTarget(target: Vector) { - const speedControl = target - .sub(this.position) - .setMagnitude(config.antMaxSpeed); - return speedControl.sub(this.velocity).limit(config.antSteeringLimit); - } - - private applyForce(force: Vector) { - this.acceleration.add(force, true); + private approachTarget(target: Vector): void { + this.desiredVelocity = target.sub(this.position).normalize(); } - private getAntennas(): { - leftAntenna: Vector; - forwardAntenna: Vector; - rightAntenna: Vector; - } { - const leftAntenna = this.position.add(this.velocity.rotate(150).mult(20)); - const forwardAntenna = this.position.add(this.velocity.mult(20)); - const rightAntenna = this.position.add(this.velocity.rotate(-150).mult(20)); + private getAntennas(): IAntennas { + const leftAntenna = this.position.add( + this.desiredVelocity + .rotate(-config.antAntennaRotation) + .mult(config.antAntennaRange) + ); + const frontAntenna = this.position.add( + this.desiredVelocity.mult(config.antAntennaRange) + ); + const rightAntenna = this.position.add( + this.desiredVelocity + .rotate(config.antAntennaRotation) + .mult(config.antAntennaRange) + ); - config.showAntAntenna && + config.showAntAntennas && this.p.circle(leftAntenna.x, leftAntenna.y, config.antAntennaRadius) && - this.p.circle( - forwardAntenna.x, - forwardAntenna.y, - config.antAntennaRadius - ) && + this.p.circle(frontAntenna.x, frontAntenna.y, config.antAntennaRadius) && this.p.circle(rightAntenna.x, rightAntenna.y, config.antAntennaRadius); - return { leftAntenna, forwardAntenna, rightAntenna }; + return { leftAntenna, frontAntenna, rightAntenna }; } - private handleEdgeCollision() { - // left / right - if (this.position.x > this.p.windowWidth - 10 || this.position.x < 10) { - this.velocity.x *= -1; - } - // top / bottom - if (this.position.y > this.p.windowHeight - 10 || this.position.y < 10) { - this.velocity.y *= -1; - } - // TODO: ants should not be rendered over colonies + private getPerception(): Vector { + return this.position.add(this.velocity.mult(config.antPerceptionRange)); + } + + private handleObstacles(): void { + let obstacleInRange: boolean; + do { + const perception = this.position.add( + this.desiredVelocity.mult(config.antPerceptionRange * 2) + ); + obstacleInRange = this.world.isObstacleInAntPerceptionRange( + this.position, + perception + ); + if (obstacleInRange) { + // randomly set positive or negative angleRange + // TODO: turn left/right based on the angle of collision + this.desiredVelocity.rotate( + Math.random() < 0.5 + ? config.antObstacleAngleRange + : -config.antObstacleAngleRange, + true + ); + } + } while (obstacleInRange); } private handleWandering() { - this.wanderAngle += randomFloat(-0.5, 0.5); - const circlePos = this.velocity - .setMagnitude(config.antPerceptionRange) - .add(this.position); - const circleOffset = fromAngle( - this.wanderAngle + this.velocity.heading() - ).mult(config.antWanderStrength); - const target = circlePos.add(circleOffset); - const wander = this.approachTarget(target); - this.applyForce(wander); + const angle = randomFloat(-1, 1); + this.desiredVelocity.rotate(angle * config.antWanderStrength, true); } private handleAntennaSteering(pheromoneType: IPheromoneType) { const antennas = this.getAntennas(); const [leftAntenna, frontAntenna, rightAntenna] = - this.world.antennaPheromoneValues( - [antennas.leftAntenna, antennas.forwardAntenna, antennas.rightAntenna], + this.world.computeAntAntennasPheromoneValues( + [antennas.leftAntenna, antennas.frontAntenna, antennas.rightAntenna], + config.antAntennaRadius, pheromoneType ); if (frontAntenna > leftAntenna && frontAntenna > rightAntenna) { // do nothing } else if (leftAntenna > rightAntenna) { - this.applyForce(this.approachTarget(antennas.leftAntenna)); + this.desiredVelocity.rotate(-config.antAntennaRotation, true); } else if (rightAntenna > leftAntenna) { - this.applyForce(this.approachTarget(antennas.rightAntenna)); + this.desiredVelocity.rotate(config.antAntennaRotation, true); } } private handleSearchingForFood() { // check if food item exists within perception range if (!this.targetFoodItem) { - this.targetFoodItem = this.world.getFoodItemInPerceptionRange( - this.position + this.targetFoodItem = this.world.getFoodItemInAntPerceptionRange( + this.getPerception(), + config.antPerceptionRange ); } - if (this.targetFoodItem) { + if (!this.targetFoodItem) { + // follow food pheromones if no food item is found within perception range + this.handleAntennaSteering(IPheromoneType.Food); + } else { // check if reserved food item is picked up if (this.targetFoodItem.collide(this.position)) { // rotate 180 degrees - this.velocity.rotate(this.p.PI, true); + this.desiredVelocity.rotate(Math.PI, true); this.targetFoodItem.pickedUp(); this.returningHome(); + } else { + this.approachTarget(this.targetFoodItem.position); } - const approachFood = this.approachTarget(this.targetFoodItem.position); - this.applyForce(approachFood); - } else { - // follow food pheromones if no food item is found within perception range - this.handleAntennaSteering(IPheromoneType.Food); } } private handleReturningHome() { if (this.colonyInPerceptionRange()) { - const approachColony = this.approachTarget(this.colony.position); - this.applyForce(approachColony); + this.approachTarget(this.colony.position); // check if food item is delivered to colony if (this.colony.collide(this.position)) { // rotate 180 degrees - this.velocity.rotate(this.p.PI, true); + this.velocity.rotate(Math.PI, true); this.targetFoodItem.delivered(); this.targetFoodItem = null; this.colony.incrementFoodCount(); this.searchingForFood(); - return; } } else { // follow home pheromones to deliver food @@ -156,7 +166,7 @@ export class Ant { } private colonyInPerceptionRange(): boolean { - return areCirclesOverlapping( + return areCirclesIntersecting( this.position, config.antPerceptionRange * 2, this.colony.position, @@ -187,12 +197,14 @@ export class Ant { } private updatePosition() { - this.velocity.add(this.acceleration, true); - this.position.add(this.velocity, true); - this.acceleration.set(0); + const subtracted = this.desiredVelocity.sub(this.velocity); + const desiredSteer = subtracted.mult(config.antSteeringLimit); + const acceleration = desiredSteer.limit(config.antSteeringLimit); + + this.velocity.add(acceleration, true).limit(config.antMaxSpeed); + this.position.add(this.velocity.mult(config.antMaxSpeed), true); } - // TODO: Create a wrapper for render methods to handle push/pop logic private renderAnt() { this.p.push(); this.p.strokeWeight(config.antStrokeWeight); @@ -216,11 +228,8 @@ export class Ant { this.p.push(); this.p.strokeWeight(config.antPerceptionStrokeWeight); this.p.fill(config.antPerceptionColorGray, config.antPerceptionColorAlpha); - this.p.circle( - this.position.x, - this.position.y, - config.antPerceptionRange * 2 - ); + const perception = this.getPerception(); + this.p.circle(perception.x, perception.y, config.antPerceptionRange * 2); this.p.pop(); } @@ -241,7 +250,7 @@ export class Ant { } public update() { - this.handleEdgeCollision(); + this.handleObstacles(); this.handlePheromoneDeposit(); this.isSearchingForFood() && this.handleSearchingForFood(); diff --git a/src/aco/config.ts b/src/aco/config.ts index 38d7272..74a449a 100644 --- a/src/aco/config.ts +++ b/src/aco/config.ts @@ -3,35 +3,41 @@ export const config = { frameRate: 60, // world worldBackground: "#78624f", + foodClusterSize: 7, // food item - foodItemSize: 2, // diameter + foodItemSize: 4, // diameter foodItemColor: "#39FF14", foodItemStrokeWeight: 0, // food cluster - foodClusterSpacing: 7, + foodClusterSpacing: 10, // colony colonySize: 70, // diameter colonyColor: "#ffffff", colonyStrokeWeight: 1, colonyTextSize: 20, - antWanderStrength: 1.5, + // ant + antWanderStrength: 0.2, antMaxSpeed: 2.5, - antSteeringLimit: 0.3, - antSize: 1, + antSteeringLimit: 0.1, + antSize: 4, antColor: "#000000", antStrokeWeight: 2, showAntPerceptionRange: false, - antPerceptionRange: 50, //radius + antPerceptionRange: 35, //radius antPerceptionColorGray: 255, antPerceptionColorAlpha: 30, antPerceptionStrokeWeight: 1, - showAntAntenna: false, - antAntennaRadius: 20, + showAntAntennas: false, + antAntennaRadius: 70, + antAntennaRange: 90, + antAntennaRotation: 1.1, + antObstacleAngleRange: 0.7, // pheromone - pheromoneSize: 2, + pheromoneSize: 4, pheromoneStrokeWeight: 0, - pheromoneDistanceBetween: 400, - pheromoneEvaporationRate: 1, + pheromoneDistanceBetween: 200, + pheromoneEvaporationRate: 0.3, + pheromoneInitialStrength: 500, showHomePheromones: false, homePheromoneColorRGB: [26, 166, 236], showFoodPheromones: true, diff --git a/src/aco/obstacle.ts b/src/aco/obstacle.ts new file mode 100644 index 0000000..4b9143f --- /dev/null +++ b/src/aco/obstacle.ts @@ -0,0 +1,13 @@ +export class Obstacle { + x1: number; + y1: number; + x2: number; + y2: number; + + constructor(x1: number, y1: number, x2: number, y2: number) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } +} diff --git a/src/aco/pheromone.ts b/src/aco/pheromone.ts index b4135ff..8aba600 100644 --- a/src/aco/pheromone.ts +++ b/src/aco/pheromone.ts @@ -16,7 +16,7 @@ export class Pheromone { this.p = p; this.position = position; this.type = type; - this.strength = 500; + this.strength = config.pheromoneInitialStrength; } private evaporate() { diff --git a/src/aco/quadtree.ts b/src/aco/quadtree.ts index 4e884e8..a63258d 100644 --- a/src/aco/quadtree.ts +++ b/src/aco/quadtree.ts @@ -7,7 +7,7 @@ type IQuadtree = { shouldBeDestroyed: () => boolean; update: () => void; render: () => void; -} & Record; +}; class Circle { x: number; @@ -145,10 +145,11 @@ export class Quadtree { } private updateAndRenderPoints() { - this.points.map((point) => { + for (let i = 0; i < this.points.length; i++) { + const point = this.points[i]; point.update(); point.render(); - }); + } } private highlightQuadtree() { @@ -203,11 +204,12 @@ export class Quadtree { // Highlight the quadtrees in the perception range of the ant config.showHighlightedQuadtree && this.highlightQuadtree(); - this.points.map((point) => { + for (let i = 0; i < this.points.length; i++) { + const point = this.points[i]; if (range.contains(point)) { found.push(point); } - }); + } if (this.divided) { this.topLeft.query(range, found); diff --git a/src/aco/sketch.ts b/src/aco/sketch.ts index 9d6b6a3..76923ac 100644 --- a/src/aco/sketch.ts +++ b/src/aco/sketch.ts @@ -17,10 +17,14 @@ export const setCanvasInteraction = (interactionEnabled: boolean) => export const sketch = (p: p5) => { p.setup = () => { p.createCanvas(p.windowWidth, p.windowHeight); + p.frameRate(config.frameRate); world = new World(p); for (let i = 0; i < numAnts; i++) { world.createAnt(); } + world.createFoodCluster(250, 250, config.foodClusterSize); + world.createFoodCluster(1000, 150, config.foodClusterSize); + world.createFoodCluster(1020, 550, config.foodClusterSize); }; p.draw = () => { @@ -31,6 +35,6 @@ export const sketch = (p: p5) => { if (!canvasInteractionEnabled) { return; } - world.createFoodCluster(5); + world.createFoodCluster(p.mouseX, p.mouseY, config.foodClusterSize); }; }; diff --git a/src/aco/utils.ts b/src/aco/utils.ts index 06b44c5..145038c 100644 --- a/src/aco/utils.ts +++ b/src/aco/utils.ts @@ -1,3 +1,4 @@ +import { Obstacle } from "./obstacle"; import { Vector } from "./vector"; export function distanceSquared(position1: Vector, position2: Vector): number { @@ -26,7 +27,7 @@ export function pointInCircle( ); } -export function areCirclesOverlapping( +export function areCirclesIntersecting( circle1Position: Vector, circle1Radius: number, circle2Position: Vector, @@ -42,3 +43,21 @@ export function areCirclesOverlapping( export function randomFloat(min: number, max: number): number { return Math.random() * (max - min) + min; } + +// TODO: add unit tests +export function areLinesIntersecting( + line1: Obstacle, + line2: Obstacle +): boolean { + const uA = + ((line2.x2 - line2.x1) * (line1.y1 - line2.y1) - + (line2.y2 - line2.y1) * (line1.x1 - line2.x1)) / + ((line2.y2 - line2.y1) * (line1.x2 - line1.x1) - + (line2.x2 - line2.x1) * (line1.y2 - line1.y1)); + const uB = + ((line1.x2 - line1.x1) * (line1.y1 - line2.y1) - + (line1.y2 - line1.y1) * (line1.x1 - line2.x1)) / + ((line2.y2 - line2.y1) * (line1.x2 - line1.x1) - + (line2.x2 - line2.x1) * (line1.y2 - line1.y1)); + return uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1; +} diff --git a/src/aco/vector.ts b/src/aco/vector.ts index 3caabe3..074fd87 100644 --- a/src/aco/vector.ts +++ b/src/aco/vector.ts @@ -69,6 +69,12 @@ export class Vector { return Math.atan2(this.y, this.x); } + public normalize(assign?: boolean): Vector { + const result = this.mult(1 / this.getMagnitude()); + assign && this.assign(result); + return result; + } + public copy(): Vector { return new Vector(this.x, this.y); } diff --git a/src/aco/world.ts b/src/aco/world.ts index f3dc446..d3373e8 100644 --- a/src/aco/world.ts +++ b/src/aco/world.ts @@ -5,14 +5,18 @@ import { config } from "./config"; import { IPheromoneType, Pheromone } from "./pheromone"; import { quadtreeCircle, Quadtree, Rectangle } from "./quadtree"; import { Vector } from "./vector"; +import { areLinesIntersecting } from "./utils"; +import { Obstacle } from "./obstacle"; export class World { p: p5; + loop: boolean = true; foodItemsQuadtree: Quadtree; homePheromoneQuadtree: Quadtree; foodPheromoneQuadtree: Quadtree; ants: Ant[]; colonies: Colony[]; + obstacles: Obstacle[]; constructor(p: p5) { this.p = p; @@ -45,14 +49,33 @@ export class World { ); this.ants = []; this.colonies = [new Colony(this.p)]; + this.obstacles = [ + { x1: 0, y1: 0, x2: this.p.windowWidth, y2: 0 }, + { + x1: 0, + y1: this.p.windowHeight, + x2: this.p.windowWidth, + y2: this.p.windowHeight, + }, + { x1: 0, y1: 0, x2: 0, y2: this.p.windowHeight }, + { + x1: this.p.windowWidth, + y1: 0, + x2: this.p.windowWidth, + y2: this.p.windowHeight, + }, + ]; } public createAnt() { this.ants.push(new Ant(this.p, this.colonies[0], this)); } - public createFoodCluster(clusterSize: number = 5) { - const [spawnX, spawnY] = [this.p.mouseX, this.p.mouseY]; + public createFoodCluster( + spawnX: number, + spawnY: number, + clusterSize: number = 5 + ) { for (let i = 0; i < clusterSize; i++) { for (let j = 0; j < clusterSize; j++) { const foodItem = new FoodItem( @@ -73,9 +96,11 @@ export class World { pheromoneQuadtree.insert(pheromone); } - // TODO: this method should limit the perception to only in FRONT of the ant - public getFoodItemInPerceptionRange(antPosition: Vector): FoodItem | null { - quadtreeCircle.set(antPosition.x, antPosition.y, config.antPerceptionRange); + public getFoodItemInAntPerceptionRange( + antPosition: Vector, + antPerceptionRange: number + ): FoodItem | null { + quadtreeCircle.set(antPosition.x, antPosition.y, antPerceptionRange); const found = this.foodItemsQuadtree.query(quadtreeCircle); for (let i = 0; i < found.length; i++) { const foodItem = found[i]; @@ -86,11 +111,12 @@ export class World { } } - public antennaPheromoneValues( + public computeAntAntennasPheromoneValues( antennas: Vector[], + antAntennaRadius: number, pheromoneType: IPheromoneType ): number[] { - let antennaScores: number[] = []; + const antennaScores: number[] = []; const pheromoneQuadtree = pheromoneType === IPheromoneType.Food ? this.foodPheromoneQuadtree @@ -99,17 +125,40 @@ export class World { for (let i = 0; i < antennas.length; i++) { const antenna = antennas[i]; let antennaScore = 0; - quadtreeCircle.set(antenna.x, antenna.y, config.antAntennaRadius); + quadtreeCircle.set(antenna.x, antenna.y, antAntennaRadius); const pheromones = pheromoneQuadtree.query(quadtreeCircle); - pheromones.map((pheromone) => { - antennaScore += pheromone.strength; - }); + for (let j = 0; j < pheromones.length; j++) { + antennaScore += + pheromones[j].strength / config.pheromoneInitialStrength; + } antennaScores.push(antennaScore); } - return antennaScores; } + public isObstacleInAntPerceptionRange( + antPosition: Vector, + antPerception: Vector + ): boolean { + for (let i = 0; i < this.obstacles.length; i++) { + const obstacle = this.obstacles[i]; + if ( + areLinesIntersecting( + { + x1: antPosition.x, + y1: antPosition.y, + x2: antPerception.x, + y2: antPerception.y, + }, + { x1: obstacle.x1, y1: obstacle.y1, x2: obstacle.x2, y2: obstacle.y2 } + ) + ) { + return true; + } + } + return false; + } + private updateAndRenderAnts() { for (let i = 0; i < this.ants.length; i++) { const ant = this.ants[i]; @@ -126,7 +175,8 @@ export class World { } private updateAndRenderQuadtrees() { - this.foodItemsQuadtree.updateAndRender(config.showFoodItemsQuadtree); + this.loop && + this.foodItemsQuadtree.updateAndRender(config.showFoodItemsQuadtree); this.homePheromoneQuadtree.updateAndRender( config.showHomePheromonesQuadtree ); diff --git a/src/components/app.tsx b/src/components/app.tsx index 7eb2bba..4305d3e 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -1,4 +1,3 @@ -// TODO: Remove React import import React from "react"; import { ControlPanel } from "./control-panel"; import { Sketch } from "./sketch-renderer"; diff --git a/src/components/control-panel/checkbox.tsx b/src/components/control-panel/checkbox.tsx index 42f970d..e955caf 100644 --- a/src/components/control-panel/checkbox.tsx +++ b/src/components/control-panel/checkbox.tsx @@ -19,7 +19,6 @@ export const Checkbox = (props: ICheckboxProps) => { setValue(newValue); }; - // TODO: Change checked color to blue return ( { const { hideControlPanel, updateAcoConfig } = props; return ( -
- - × - - {/* TODO: Pass list of SettingItem as a prop */} - - } - /> - - } - /> - - } - /> - - } - checkbox={ - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> +
+
+
+ Ant Colony Simulation +
+ +
+
+ + } + /> + + } + /> + + } + /> + + } + checkbox={ + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> +
); }; diff --git a/src/components/control-panel/setting-item.tsx b/src/components/control-panel/setting-item.tsx index 36b4a3e..8c7bc72 100644 --- a/src/components/control-panel/setting-item.tsx +++ b/src/components/control-panel/setting-item.tsx @@ -1,5 +1,22 @@ import React, { ReactNode } from "react"; +const styles = { + settingItem: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + padding: "8px", + }, + settingTitle: { + color: "#fff", + }, + settingComponent: { + display: "flex", + flexDirection: "row", + gap: "10px", + }, +} as const; + type ISettingItemProps = { title: string; slider?: ReactNode; @@ -10,9 +27,11 @@ export const SettingItem = (props: ISettingItemProps) => { const { title, slider, checkbox } = props; return ( -
- {title} -
+
+ + {title} + +
{checkbox} {slider}
diff --git a/src/components/control-panel/slider.tsx b/src/components/control-panel/slider.tsx index c120558..a5f137e 100644 --- a/src/components/control-panel/slider.tsx +++ b/src/components/control-panel/slider.tsx @@ -1,6 +1,16 @@ import React from "react"; import { IConfig, IUpdateAcoConfig } from "../../aco"; +const styles = { + sliderOutput: { + color: "#fff", + backgroundColor: "#3691ec", + borderRadius: "7px", + alignItems: "center", + padding: "4px 6px 4px 6px", + }, +} as const; + type ISliderProps = { configParam: keyof IConfig; min: number; @@ -30,8 +40,6 @@ export const Slider = (props: ISliderProps) => { <> { step={step} onInput={(event) => updateValue(event)} /> - + {formatValue()} diff --git a/src/components/control-panel/toggle.tsx b/src/components/control-panel/toggle.tsx index bfd3bfd..37b3db9 100644 --- a/src/components/control-panel/toggle.tsx +++ b/src/components/control-panel/toggle.tsx @@ -1,5 +1,18 @@ import React from "react"; +const styles = { + toggleButtonContainer: { + display: "flex", + justifyContent: "left", + }, + toggleButton: { + color: "#fff", + position: "absolute", + margin: "20px 0 0 20px", + cursor: "pointer", + }, +} as const; + type IControlPanelToggleProps = { showControlPanel: () => void; }; @@ -8,11 +21,15 @@ export const ControlPanelToggle = (props: IControlPanelToggleProps) => { const { showControlPanel } = props; return ( -
+
diff --git a/src/components/sketch-renderer.tsx b/src/components/sketch-renderer.tsx index df3e24d..1c4af92 100644 --- a/src/components/sketch-renderer.tsx +++ b/src/components/sketch-renderer.tsx @@ -1,5 +1,4 @@ -import React from "react"; -import { useEffect, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import { sketch } from "../aco"; import p5 from "p5"; diff --git a/src/index.html b/src/index.html index e35bacc..019d119 100644 --- a/src/index.html +++ b/src/index.html @@ -1,6 +1,14 @@ + ); diff --git a/src/styles/style.css b/src/styles/style.css deleted file mode 100644 index 30c2c92..0000000 --- a/src/styles/style.css +++ /dev/null @@ -1,71 +0,0 @@ -body { - margin: 0px; - font-family: Tahoma; - font-size: 13px; - letter-spacing: 0.5px; -} - -.control-panel-toggle-button-container { - display: flex; - justify-content: left; -} - -.control-panel-toggle-button { - color: #fff; - position: absolute; - margin-top: 20px; - margin-left: 20px; - /* TODO: transitions */ - transition: 2.1s; - cursor: pointer; -} - -.control-panel-content { - width: 350px; - max-height: 60%; - position: absolute; - margin-top: 20px; - margin-left: 20px; - border-radius: 0.6rem; - background-color: rgba(0, 0, 0, 0.7); - overflow: auto; - padding-top: 60px; - transition: 0.2s; -} - -.control-panel-content .close-button { - padding: 8px; - text-decoration: none; - display: block; - color: #fff; - position: absolute; - top: 0; - right: 10px; - font-size: 30px; - cursor: pointer; -} - -.control-panel-content .slider-container { - display: flex; - flex-direction: row; - justify-content: space-between; - padding: 8px; -} - -.control-panel-content .slider-title { - color: #fff; -} - -.control-panel-content .slider-output { - color: #fff; - background-color: #3691ec; - border-radius: 7px; - align-items: center; - padding: 4px 6px 4px 6px; -} - -.control-panel-content .slider-component { - display: flex; - flex-direction: row; - gap: 10px; -}