From b33dd3ffa7f4acc6d0897d14cba4d5f5eff14fdf Mon Sep 17 00:00:00 2001 From: Bridget Bailey Date: Wed, 25 Dec 2019 22:54:31 -0800 Subject: [PATCH] Add run until constraints algo --- package-lock.json | 105 +++++++++++ package.json | 3 +- src/App.css | 47 +++-- src/Components/Algorithms.jsx | 47 +++++ src/Components/Constraints.jsx | 120 +++++++++++++ src/Components/DataEntry.jsx | 178 ++++++++++++------- src/Components/Details.jsx | 2 +- src/Components/Options.jsx | 2 +- src/Components/Results.jsx | 5 +- src/Components/Subcomponents/SingleInput.jsx | 2 + src/Components/Tables.jsx | 2 +- src/worker.js | 127 +++++++++---- 12 files changed, 522 insertions(+), 118 deletions(-) create mode 100644 src/Components/Algorithms.jsx create mode 100644 src/Components/Constraints.jsx diff --git a/package-lock.json b/package-lock.json index 1952e01..52b27e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3118,6 +3118,16 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -3907,6 +3917,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -4903,6 +4921,35 @@ "bser": "^2.0.0" } }, + "fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + } + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -7304,6 +7351,15 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -8695,6 +8751,15 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, "node-forge": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", @@ -11431,6 +11496,41 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.0.tgz", "integrity": "sha512-akMy/BQT5m1J3iJIHkSb4qycq2wzllWsmmolaaFVnb+LPV9cIJ/nTud40ZsiiT0H3P+/wXIdbjx2fzF61OaeOQ==" }, + "react-radio-buttons": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/react-radio-buttons/-/react-radio-buttons-1.2.2.tgz", + "integrity": "sha512-GPk8gLEdOoC/41NQ3CrkAQv8jZSo2BojvEszQqS/16XTICe8a+H2EyOk3qJQLHzLe7+YABzyjZoZAOeeQ1QFxA==", + "requires": { + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-dom": "^15.3.2" + }, + "dependencies": { + "react": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "requires": { + "create-react-class": "^15.6.0", + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + }, + "react-dom": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", + "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + } + } + } + }, "react-scripts": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.1.tgz", @@ -13747,6 +13847,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "ua-parser-js": { + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" + }, "uglify-js": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", diff --git a/package.json b/package.json index 0f769d8..2cadf86 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "react": "^16.7.0", "react-checkbox-group": "^4.0.1", "react-dom": "^16.7.0", + "react-radio-buttons": "^1.2.2", "react-scripts": "2.1.1" }, "scripts": { @@ -14,7 +15,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "deploy" : "npm run build && gh-pages -d build" + "deploy": "npm run build && gh-pages -d build" }, "eslintConfig": { "extends": "react-app" diff --git a/src/App.css b/src/App.css index 47870bf..b16dd97 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,65 @@ .App { - /*background-color: #282c34;*/ text-align: center; } .App-body { min-height: 100vh; display: flex; - /*align-items: center;*/ justify-content: space-around; } .left-pane, .right-pane { width: 50%; + padding: 0 1rem; } .right-pane { text-align: left; } -.right-pane h2 { - text-align: cetner; - } +.radio-button { + width: 70%; +} -.options { +.column { display: flex; flex-direction: column; align-items: center; } +.column-right { + display: flex; + flex-direction: column; +} + +.heading { + align-self: center; + margin: 0 1rem; +} + +.data-entry-section { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.data-entry-section-with-subsections { + display: flex; + flex-direction: row; +} + +.data-entry-subsection { + width: 50%; + padding: 0 0.25rem; +} + .checkbox-group { display: flex; flex-direction: column; align-items: flex-start; + text-align: left; } .form-group { @@ -44,19 +71,13 @@ } .form-input[type='number'] { - width: 2rem; + width: 3rem; } .game-input { width: 8rem; } -.tables { - display: flex; - flex-direction: column; - align-items: center; -} - .table-list { padding-left: 0; margin: 0; diff --git a/src/Components/Algorithms.jsx b/src/Components/Algorithms.jsx new file mode 100644 index 0000000..4685c78 --- /dev/null +++ b/src/Components/Algorithms.jsx @@ -0,0 +1,47 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { RadioGroup, RadioButton } from 'react-radio-buttons'; +import Constraints from './Constraints'; + +class Algorithms extends Component { + static propTypes = { + algorithmChoice: PropTypes.string.isRequired, + totalRounds: PropTypes.number.isRequired, + options: PropTypes.array.isRequired, + maxPlayedWithAllowed: PropTypes.number.isRequired, + maxAveragePlayedWithAllowed: PropTypes.number.isRequired, + minUniqueTablesAllowed: PropTypes.number.isRequired, + maxRuns: PropTypes.number.isRequired, + handleAlgorithmChange: PropTypes.func.isRequired, + handleNumberChange: PropTypes.func.isRequired + } + + render() { + return ( +
+

Algorithm Options

+ + + Best of X Random Runs + + + Run Until Custom Constraints Met + + + +
+ ); + } + +} +export default Algorithms; diff --git a/src/Components/Constraints.jsx b/src/Components/Constraints.jsx new file mode 100644 index 0000000..ab68b3c --- /dev/null +++ b/src/Components/Constraints.jsx @@ -0,0 +1,120 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import SingleInput from './Subcomponents/SingleInput'; + +class Constraints extends Component { + // keep maxPlayedWithCount under _ + // keep averageMaxPlayedWithCount under _ + // minUniqueTablesVisited over _ + static propTypes = { + algorithmChoice: PropTypes.string.isRequired, + totalRounds: PropTypes.number.isRequired, + options: PropTypes.array.isRequired, + numTimesToRun: PropTypes.number.isRequired, + maxPlayedWithAllowed: PropTypes.number.isRequired, + maxAveragePlayedWithAllowed: PropTypes.number.isRequired, + minUniqueTablesAllowed: PropTypes.number.isRequired, + maxRuns: PropTypes.number.isRequired, + handleNumberChange: PropTypes.func.isRequired + } + + renderRunUntilConstraints() { + const {options} = this.props; + const changePeopleOption = options.includes('changePeople'); + const changeTablesOption = options.includes('changeTables'); + return ( +
+ {changePeopleOption && this.renderMaxPlayedWithAllowedInput()} + {changePeopleOption && this.renderMaxAveragePlayedWithAllowedInput()} + {changeTablesOption && this.renderMinUniqueTablesAllowedInput()} + {changeTablesOption && this.renderMaxRunsInput()} +
+ ); + } + + renderMaxPlayedWithAllowedInput() { + const {totalRounds, maxPlayedWithAllowed, handleNumberChange} = this.props; + return ( + handleNumberChange(e, 'maxPlayedWithAllowed')} + content={maxPlayedWithAllowed} + max={totalRounds} + min={1} + /> + ); + } + + renderMaxAveragePlayedWithAllowedInput() { + const {totalRounds, maxAveragePlayedWithAllowed, handleNumberChange} = this.props; + return ( + handleNumberChange(e, 'maxAveragePlayedWithAllowed')} + content={maxAveragePlayedWithAllowed} + max={totalRounds} + min={1} + /> + ); + } + + renderMinUniqueTablesAllowedInput() { + const {totalRounds, minUniqueTablesAllowed, handleNumberChange} = this.props; + return ( + handleNumberChange(e, 'minUniqueTablesAllowed')} + content={minUniqueTablesAllowed} + max={totalRounds} + min={1} + /> + ); + } + + renderMaxRunsInput() { + const {maxRuns, handleNumberChange} = this.props; + return ( + handleNumberChange(e, 'maxRuns')} + content={maxRuns} + max={10000} + min={1} + /> + ); + } + + renderRunXTimesInput() { + const {numTimesToRun, handleNumberChange} = this.props; + return ( + handleNumberChange(e, 'numTimesToRun')} + content={numTimesToRun} + max={10000} + min={1} + /> + ); + } + + render() { + return ( +
+ {this.props.algorithmChoice === 'runUntilConstraints' && this.renderRunUntilConstraints()} + {this.props.algorithmChoice === 'runRandomXTimes' && this.renderRunXTimesInput()} +
+ ); + } + +} +export default Constraints; diff --git a/src/Components/DataEntry.jsx b/src/Components/DataEntry.jsx index d350e94..eb404e4 100644 --- a/src/Components/DataEntry.jsx +++ b/src/Components/DataEntry.jsx @@ -1,8 +1,50 @@ import React, { Component } from 'react'; -import Options from './Options.jsx'; -import Details from './Details.jsx'; -import Tables from './Tables.jsx'; -import { runOrganizer } from '../worker.js'; +import Options from './Options'; +import Details from './Details'; +import Algorithms from './Algorithms'; +import Tables from './Tables'; +import { runOrganizer } from '../worker'; + +const defaultData = { + firstRun: true, + options: ['changePeople', 'changeTables'], + totalPlayers: 10, + totalRounds: 4, + totalKids: 0, + algorithmChoice: 'runRandomXTimes', + numTimesToRun: 500, + maxPlayedWithAllowed: 4, + maxAveragePlayedWithAllowed: 4, + minUniqueTablesAllowed: 1, + maxRuns: 10000, + tables: [ + { + id: 1, + name: 'Table 1', + size: 2, + games: [] + }, + { + id: 2, + name: 'Table 2', + size: 2, + games: [] + }, + { + id: 3, + name: 'Table 3', + size: 3, + games: [] + }, + { + id: 4, + name: 'Table 4', + size: 3, + games: [] + }, + ], + result: {} +}; class DataEntry extends Component { constructor(props) { @@ -11,47 +53,16 @@ class DataEntry extends Component { this.handleNumPlayersChange = this.handleNumPlayersChange.bind(this); this.handleNumRoundsChange = this.handleNumRoundsChange.bind(this); this.handleNumKidsChange = this.handleNumKidsChange.bind(this); + this.handleNumberChange = this.handleNumberChange.bind(this); + this.handleAlgorithmChange = this.handleAlgorithmChange.bind(this); this.handleTablesChange = this.handleTablesChange.bind(this); this.handleClearForm = this.handleClearForm.bind(this); this.handleFormSubmit = this.handleFormSubmit.bind(this); - this.state = {//TODO put back to normal - firstRun: true, - options: ['changePeople', 'changeTables'], - totalPlayers: 10, - totalRounds: 4, - totalKids: 0, - tables: [ - { - id: 1, - name: 'Table 1', - size: 2, - games: [] - }, - { - id: 2, - name: 'Table 2', - size: 2, - games: [] - }, - { - id: 3, - name: 'Table 3', - size: 3, - games: [] - }, - { - id: 4, - name: 'Table 4', - size: 3, - games: [] - }, - ], - result: {} - }; + this.state = JSON.parse(JSON.stringify(defaultData)); } -//TODO add games list to table object -//TODO handle other options + + //TODO handle other options handleOptionsChange = (newOptions) => { this.setState({ options: newOptions @@ -75,6 +86,19 @@ class DataEntry extends Component { totalKids: parseInt(e.target.value) ? parseInt(e.target.value) : 0 }); } +//todo use this for other number values +//todo variable defaults if this is an error? + handleNumberChange = (e, property) => { + this.setState({ + [property]: parseInt(e.target.value) ? parseInt(e.target.value) : 0 + }); + } + + handleAlgorithmChange = (newAlgo) => { + this.setState({ + algorithmChoice: newAlgo + }); + } handleTablesChange = (newTables) => { this.setState({ @@ -84,18 +108,7 @@ class DataEntry extends Component { handleClearForm = (e) => { e.preventDefault(); - this.setState({ - options: ['changePeople'], - totalPlayers: 0, - totalRounds: 0, - totalKids: 0, - tables: [{ - id: 1, - name: 'Table 1', - size: 4 - }], - result: {} - }); + this.setState(JSON.parse(JSON.stringify(defaultData))); } handleFormSubmit = (e) => { @@ -108,6 +121,12 @@ class DataEntry extends Component { totalPlayers: this.state.totalPlayers, totalRounds: this.state.totalRounds, totalKids: this.state.options.includes('kidsTable') ? this.state.totalKids : 0, + algorithmChoice: this.state.algorithmChoice, + numTimesToRun: this.state.numTimesToRun, + maxPlayedWithAllowed: this.state.maxPlayedWithAllowed, + maxAveragePlayedWithAllowed: this.state.maxAveragePlayedWithAllowed, + minUniqueTablesAllowed: this.state.minUniqueTablesAllowed, + maxRuns: this.state.maxRuns, numTables: this.state.tables.length, tables: this.state.tables }; @@ -120,7 +139,6 @@ class DataEntry extends Component { throw new Error('Number of players must match number of table spots'); } else { let result = runOrganizer(formPayload); - // this.handleClearForm(e); this.props.handleTablesReady(this.state.tables); this.props.handleResultReady(result); this.setState({firstRun: false}); @@ -128,25 +146,53 @@ class DataEntry extends Component { } render() { - const {options, totalPlayers, totalRounds, totalKids, tables} = this.state; + const { + options, + totalPlayers, + totalRounds, + totalKids, + algorithmChoice, + numTimesToRun, + maxPlayedWithAllowed, + maxAveragePlayedWithAllowed, + minUniqueTablesAllowed, + maxRuns, + tables} = this.state; return ( -
-

Enter Your Data

- -
+

Enter Your Data

+
+ +
+
+ + options={options} + numTimesToRun={numTimesToRun} + maxPlayedWithAllowed={maxPlayedWithAllowed} + maxAveragePlayedWithAllowed={maxAveragePlayedWithAllowed} + minUniqueTablesAllowed={minUniqueTablesAllowed} + maxRuns={maxRuns} + handleAlgorithmChange={this.handleAlgorithmChange} + handleNumberChange={this.handleNumberChange} /> +
); } diff --git a/src/Components/Details.jsx b/src/Components/Details.jsx index 4e24f78..e22d2d8 100644 --- a/src/Components/Details.jsx +++ b/src/Components/Details.jsx @@ -29,7 +29,7 @@ class Details extends Component { render() { const {totalPlayers, totalRounds, handleNumPlayersChange, handleNumRoundsChange, isKidsTable} = this.props; return ( -
+

Details

+

Options

-

Results

+
+

Results

Max of Everyone's Max Times Played With Someone: {result.maxPlayedWithCount}

Average of Everyone's Max Times Played With Someone: {Math.round(result.averageMaxPlayedWithCount * 100) / 100}

Minimum Number of Unique Tables Visited: {result.minUniqueTablesVisited}

diff --git a/src/Components/Subcomponents/SingleInput.jsx b/src/Components/Subcomponents/SingleInput.jsx index bc4a1a7..20f7307 100644 --- a/src/Components/Subcomponents/SingleInput.jsx +++ b/src/Components/Subcomponents/SingleInput.jsx @@ -8,6 +8,8 @@ const SingleInput = (props) => ( className={`form-input ${props.classNames}`} name={props.name} type={props.inputType} + min={props.min} + max={props.max} value={props.content} onChange={props.controlFunc} placeholder={props.placeholder} /> diff --git a/src/Components/Tables.jsx b/src/Components/Tables.jsx index a49ca86..108e9bf 100644 --- a/src/Components/Tables.jsx +++ b/src/Components/Tables.jsx @@ -119,7 +119,7 @@ class Tables extends Component { return (

Tables

-

Please enter a comma-separated list of games, with the same number of games as rounds

+

(Optional) Please enter a comma-separated list of games for each round

{this.renderTableRows()}
diff --git a/src/worker.js b/src/worker.js index 99fad63..b22d4f4 100644 --- a/src/worker.js +++ b/src/worker.js @@ -4,10 +4,12 @@ let totalRounds = 0; let totalPlayers = 0; let tableDetails = []; let options = {}; +let algorithmChoice = ''; +let algorithmDetails = {}; export function runOrganizer(userInput) { initializeData(userInput); - return runAlgorithm(userInput.totalRounds); + return runAlgorithm(); } function initializeData(userInput) { @@ -18,9 +20,17 @@ function initializeData(userInput) { kidsTable: userInput.kidsTable, changePeople: userInput.changePeople, changeTables: userInput.changeTables - } + }; playerList = getEmptyPlayerList(); tableList = getEmptyTableList(); + algorithmChoice = userInput.algorithmChoice; + algorithmDetails = { + numTimesToRun: userInput.numTimesToRun, + maxPlayedWithAllowed: userInput.maxPlayedWithAllowed, + maxAveragePlayedWithAllowed: userInput.maxAveragePlayedWithAllowed, + minUniqueTablesAllowed: userInput.minUniqueTablesAllowed, + maxRuns: userInput.maxRuns + }; } function getEmptyPlayerList() { @@ -49,7 +59,7 @@ function resetRunData() { } // Randomly pick for each round then chose the best overall run -function runAlgorithm(rounds) { +function runAlgorithm() { // Reset data between rounds tableList = getEmptyTableList(); @@ -57,13 +67,64 @@ function runAlgorithm(rounds) { playerList.sort(function(a, b){return 0.5 - Math.random()}); tableList.sort(function(a, b){return 0.5 - Math.random()}); - //Different Run Options - const result = runRandomXTimes(500, totalRounds); // 500 is a good number + // Different Run Options + let result; + switch (algorithmChoice) { + case 'runRandomXTimes': + result = runRandomXTimes(algorithmDetails.numTimesToRun); // 500 is a good number + break; + case 'runUntilConstraints': + result = runUntilConstraints(); + break; + default: + result = runRandomXTimes(algorithmDetails.numTimesToRun); // 500 is a good number + break; + } console.log('RESULT', result); return result; } -function runRandomXTimes(numRuns, numRounds) { +// todo add max times to run +function runUntilConstraints() { + let constraintsMet = false; + let result = { + playerList: [], + maxPlayedWithCount: 100, + averageMaxPlayedWithCount: 100, + minUniqueTablesVisited: 0 + }; + let runCount = 0; + while (!constraintsMet && (runCount < algorithmDetails.maxRuns)) { + result = runRandomAndChooseBest(result); + constraintsMet = checkConstraints(result); + runCount++; + } + return result; +} + +function checkConstraints(result) { + const {maxPlayedWithAllowed, maxAveragePlayedWithAllowed, minUniqueTablesAllowed} = algorithmDetails; + const maxPlayedWithCount = getMaxPlayedWithCount(result.playerList); + const averageMaxPlayedWithCount = getAverageMaxPlayedWithCount(result.playerList); + const minUniqueTablesVisited = getMinUniqueTablesVisited(result.playerList); + const constraintsMet = + maxPlayedWithCount <= maxPlayedWithAllowed + && averageMaxPlayedWithCount <= maxAveragePlayedWithAllowed + && minUniqueTablesVisited >= minUniqueTablesAllowed; + return constraintsMet; +} + +function runRandomAndChooseBest(currentBestRun) { + // Reset data between runs + resetRunData(); + + const resultPlayerList = chooseRandomly(totalRounds);//returns a cloned version of the global playerList + + // Replace result if better + return compareResults(resultPlayerList, currentBestRun); +} + +function runRandomXTimes(numRuns) { let bestRun = { playerList: [], maxPlayedWithCount: 100, @@ -71,31 +132,7 @@ function runRandomXTimes(numRuns, numRounds) { minUniqueTablesVisited: 0 }; for (var i = 0; i < numRuns; i++) { - // Reset data between runs - resetRunData(); - let resultPlayerList = []; - let maxPlayedWithCount = 100; - let averageMaxPlayedWithCount = 100; - let minUniqueTablesVisited = 0; - - resultPlayerList = chooseRandomly(numRounds);//returns a cloned version of the global playerList - // minimum number of unique tables visited - to help with the changeTables constraint - // calculated by creating an array of unique tables for each player, getting the lengths of those lists , then finding the minimum value among the players - minUniqueTablesVisited = Math.min(...[...new Set(resultPlayerList.map( - (player) => [...new Set(player.assignedTables)].length))]); - // max times anyone played with anyone else - // calculated by finding the max playedWith count for each player then taking the max of those values - maxPlayedWithCount = Math.max(...resultPlayerList.map( - (player) => Math.max(...player.playedWith) - )); - // average of max times everyone played with a specific person - // calculated by finding the maxPlayedWithCount for each player and then taking the average of those values - averageMaxPlayedWithCount = average(resultPlayerList.map( - (player) => Math.max(...player.playedWith) - )); - - // Replace result if better - bestRun = compareResults(resultPlayerList, minUniqueTablesVisited, maxPlayedWithCount, averageMaxPlayedWithCount, bestRun); + bestRun = runRandomAndChooseBest(bestRun); } return bestRun; } @@ -104,7 +141,33 @@ function average(list) { return list.reduce((a,b) => b+=a) / list.length; } -function compareResults(resultPlayerList, minUniqueTablesVisited, maxPlayedWithCount, averageMaxPlayedWithCount, bestRun) { +function getMinUniqueTablesVisited(resultPlayerList) { + // minimum number of unique tables visited - to help with the changeTables constraint + // calculated by creating an array of unique tables for each player, getting the lengths of those lists , then finding the minimum value among the players + return Math.min(...[...new Set(resultPlayerList.map( + (player) => [...new Set(player.assignedTables)].length))]); +} + +function getMaxPlayedWithCount(resultPlayerList) { + // max times anyone played with anyone else + // calculated by finding the max playedWith count for each player then taking the max of those values + return Math.max(...resultPlayerList.map( + (player) => Math.max(...player.playedWith) + )); +} + +function getAverageMaxPlayedWithCount(resultPlayerList) { + // average of max times everyone played with a specific person + // calculated by finding the maxPlayedWithCount for each player and then taking the average of those values + return average(resultPlayerList.map( + (player) => Math.max(...player.playedWith) + )); +} + +function compareResults(resultPlayerList, bestRun) { + const minUniqueTablesVisited = getMinUniqueTablesVisited(resultPlayerList); + const maxPlayedWithCount = getMaxPlayedWithCount(resultPlayerList); + const averageMaxPlayedWithCount = getAverageMaxPlayedWithCount(resultPlayerList); const maxPlayedWithCountCheck = options.changePeople ? (maxPlayedWithCount <= bestRun.maxPlayedWithCount) : true; const averageMaxPlayedWithCountCheck = options.changePeople ? (averageMaxPlayedWithCount <= bestRun.averageMaxPlayedWithCount) : true; const minUniqueTablesVisitedCheck = options.changeTables ? (minUniqueTablesVisited > bestRun.minUniqueTablesVisited) : true;