├── 201810-maze ├── maze.gif ├── timl │ └── index.js ├── example │ └── index.js ├── package.json ├── maze.js ├── mitchell │ └── index.js ├── jean-baudrillard │ └── index.js ├── noviny │ ├── scratchings.js │ ├── maze.js │ └── stolen-a-star.js ├── nathan │ └── index.js ├── SimonBot │ └── index.js ├── README.md └── jesstelford │ └── index.js ├── 201808-sorting ├── charles │ ├── package.json │ ├── README.md │ ├── sortLegend.txt │ ├── utils.js │ ├── selectionSort.js │ ├── mergeSort.js │ └── index.js ├── tici │ ├── README.md │ ├── MergeSort.js │ └── SelectionSort.js ├── README.md └── timl │ └── index.js ├── 201904-transpiler ├── package.json ├── README.md ├── mitchellhamilton │ └── transpiler.js └── charles │ └── transpiler.example.js ├── 201808-coup ├── JossM │ ├── utils.js │ ├── constants.js │ ├── index.js │ ├── helpers.js │ └── logic.js ├── coup.js ├── package.json ├── helper.js ├── constants.js ├── BorisB │ └── index.js ├── JedW │ └── index.js ├── JessT │ └── index.js ├── KevinY │ └── index.js ├── LaurenA │ └── index.js ├── MalB │ └── index.js ├── AbbasA │ └── index.js ├── TomW │ └── index.js ├── SanjiyaD │ └── index.js ├── MikeG │ └── index.js ├── TiciA │ └── index.js ├── TuanH │ └── index.js ├── CharlesL │ └── index.js ├── TimL │ └── index.js ├── Madds │ └── index.js ├── JohnM │ └── index.js ├── README.md ├── NathS │ └── index.js ├── BenC │ └── index.js └── DomW │ └── index.js ├── README.md ├── .gitignore ├── .editorconfig └── 201809-binary-search-tree ├── package.json ├── .eslintrc.js ├── timl ├── golf.js ├── tree.js └── while.js ├── README.md ├── tree.example.js ├── noviny └── tree.js └── TiciA └── tree.js /201810-maze/maze.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thinkmill/code-challenge/HEAD/201810-maze/maze.gif -------------------------------------------------------------------------------- /201810-maze/timl/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class BOT { 4 | Move({ MAP }) { 5 | return MAP[2][3] ? 'right' : 'down'; 6 | } 7 | } 8 | 9 | module.exports = exports = BOT; 10 | -------------------------------------------------------------------------------- /201808-sorting/charles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sight4SortEyes", 3 | "version": "1.0.0", 4 | "description": "sorting thing", 5 | "main": "index.js", 6 | "author": "me", 7 | "license": "MIT", 8 | "bin": { 9 | "sight4SortEyes": "./index.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /201904-transpiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@noviny/tm-code-challenge", 3 | "version": "1.0.0", 4 | "description": "Nope", 5 | "main": "index.js", 6 | "author": "Ben Conolly", 7 | "license": "MIT", 8 | "dependencies": { 9 | "jest": "^24.3.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /201808-sorting/charles/README.md: -------------------------------------------------------------------------------- 1 | ### Instructions 2 | To run, enter `sight4SortEyes` followed by the following arguments into your terminal: 3 | * `listKey`: expects value of either `villains`, `normies` or `heroes`. 4 | * `sortType`: currently the only supported sortTypes are `mergeSort` and `selectionSort`. 5 | -------------------------------------------------------------------------------- /201810-maze/example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class BOT { 4 | constructor({ size, start, end }) {} 5 | 6 | Move({ MAP }) { 7 | const actions = ['up', 'right', 'down', 'left']; 8 | return actions[ Math.floor( Math.random() * actions.length ) ]; 9 | } 10 | } 11 | 12 | module.exports = exports = BOT; 13 | -------------------------------------------------------------------------------- /201808-sorting/charles/sortLegend.txt: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c 4 | d 5 | e 6 | f 7 | g 8 | h 9 | i 10 | j 11 | k 12 | l 13 | m 14 | n 15 | o 16 | p 17 | q 18 | r 19 | s 20 | t 21 | u 22 | v 23 | w 24 | x 25 | y 26 | z 27 | 28 | 1 29 | 2 30 | 3 31 | 4 32 | 5 33 | 6 34 | 7 35 | 8 36 | 9 37 | 0 38 | : 39 | - 40 | . 41 | ' 42 | ’ 43 | # 44 | / 45 | -------------------------------------------------------------------------------- /201808-coup/JossM/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function unique(value, index, self) { 4 | return self.indexOf(value) === index; 5 | } 6 | function clone(arr) { 7 | return arr.slice(0); 8 | } 9 | function toArray(obj) { 10 | return Object.keys(obj).map((k) => obj[k]); 11 | } 12 | 13 | module.exports = exports = { clone, toArray, unique }; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Thinkmill Code Challenges 2 | ========================= 3 | 4 | Each month we have a code challenge at [Thinkmill](https://thinkmill.com.au). 5 | 6 | ## Archive 7 | 8 | - [August 2018](201808-coup/) 9 | - [August (part 2) 2018](201808-sorting/) 10 | - [September 2018](201809-binary-search-tree/) 11 | - [October 2018](201810-maze/) 12 | -------------------------------------------------------------------------------- /201808-coup/coup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { COUP, LOOP } = require('./index.js'); 4 | 5 | if (process.argv.includes('play') || !process.argv.includes('loop')) { 6 | new COUP().Play(); 7 | } 8 | 9 | if (process.argv.includes('loop')) { 10 | const loop = new LOOP(); 11 | const debug = process.argv.includes('-d'); 12 | 13 | loop.Run(debug); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | codekit-config.json 5 | *.codekit 6 | node_modules 7 | .sass-cache 8 | .idea 9 | validation-report.json 10 | validation-status.json 11 | npm-debug.log 12 | lerna-debug.log 13 | package-lock.json 14 | 15 | dist/* 16 | coverage/* 17 | __tests__/* 18 | *.log 19 | 20 | yarn.lock 21 | package-lock.json 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | # See http://stackoverflow.com/a/729795 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [/**/fixture/**] 16 | insert_final_newline = false 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /201809-binary-search-tree/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-challenge-201809-binary-search-tree", 3 | "version": "1.0.0", 4 | "description": "Binary Search Tree Code Challenge", 5 | "main": "tree.js", 6 | "scripts": { 7 | "test": "jest", 8 | "lint": "eslint *.js --ignore-pattern tree.example.js" 9 | }, 10 | "dependencies": { 11 | "eslint": "^5.4.0", 12 | "jest": "^23.5.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /201810-maze/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-challenge-201810-path-finder", 3 | "version": "1.0.0", 4 | "description": "October 2018 code challenge", 5 | "main": "index.js", 6 | "scripts": { 7 | "play": "node maze.js play" 8 | }, 9 | "bin": { 10 | "maze": "./maze.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "prettier": "^1.13.7" 17 | }, 18 | "dependencies": { 19 | "cli-size": "^1.0.9" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /201808-coup/JossM/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AVERAGE_TURNS = 14; 4 | const ME = 'JossM'; 5 | const CARDS = { 6 | duke: { action: 'taking-3', counter: 'foreign-aid' }, 7 | captain: { action: 'stealing', counter: 'stealing' }, 8 | assassin: { action: 'assassination', counter: null }, 9 | ambassador: { action: 'swapping', counter: 'stealing' }, 10 | contessa: { action: null, counter: 'assassination' }, 11 | }; 12 | 13 | module.exports = exports = { AVERAGE_TURNS, CARDS, ME }; 14 | -------------------------------------------------------------------------------- /201809-binary-search-tree/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "jest": true, 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": [ 14 | "error", 15 | "tab" 16 | ], 17 | "linebreak-style": [ 18 | "error", 19 | "unix" 20 | ], 21 | "quotes": [ 22 | "error", 23 | "single" 24 | ], 25 | "semi": [ 26 | "error", 27 | "always" 28 | ] 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /201808-sorting/tici/README.md: -------------------------------------------------------------------------------- 1 | ## Selection Sort 2 | 3 | normies.json 4 | real 0m0.111s 5 | user 0m0.086s 6 | sys 0m0.021s 7 | 8 | villains.json 9 | real 0m0.697s 10 | user 0m0.673s 11 | sys 0m0.021s 12 | 13 | heroes.json 14 | real 0m6.908s 15 | user 0m6.855s 16 | sys 0m0.032s 17 | 18 | 19 | ## Merge Sort 20 | 21 | normies.json 22 | real 0m0.101s 23 | user 0m0.082s 24 | sys 0m0.019s 25 | 26 | villains.json 27 | real 0m0.120s 28 | user 0m0.106s 29 | sys 0m0.020s 30 | 31 | heroes.json 32 | real 0m0.163s 33 | user 0m0.145s 34 | sys 0m0.028s -------------------------------------------------------------------------------- /201808-sorting/charles/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.isLarger = function valueIsLarger(firstValue, secondValue, legend) { 2 | if (!secondValue) return true; 3 | if (!firstValue) return false; 4 | const left = firstValue.toLowerCase(); 5 | const right = secondValue.toLowerCase(); 6 | for (let index = 0; index < firstValue.length; index++) { 7 | const charVal1 = legend.indexOf(left[index]); 8 | const charVal2 = legend.indexOf(right[index]); 9 | 10 | if (charVal1 > charVal2 ) { 11 | return true; 12 | } else if (charVal1 < charVal2) { 13 | return false; 14 | } 15 | } 16 | return false; 17 | } 18 | -------------------------------------------------------------------------------- /201808-sorting/charles/selectionSort.js: -------------------------------------------------------------------------------- 1 | const { isLarger } = require('./utils'); 2 | module.exports = function selectionSort (array, legend) { 3 | const unsortedList = array.slice(); 4 | const sortedList = [] 5 | while (unsortedList.length > 0) { 6 | let largestItem = { 7 | value: '', 8 | index: null, 9 | }; 10 | unsortedList.forEach((value, index) => { 11 | if (isLarger(value, largestItem.value, legend)) { 12 | largestItem = { 13 | value, 14 | index, 15 | } 16 | } 17 | }); 18 | unsortedList.splice(largestItem.index, 1); 19 | sortedList.push(largestItem.value); 20 | } 21 | return sortedList; 22 | }; 23 | -------------------------------------------------------------------------------- /201808-coup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-challenge-201808-coup", 3 | "version": "1.0.0", 4 | "description": "August 2018 code challenge", 5 | "main": "index.js", 6 | "scripts": { 7 | "test:format": "npm run format:config -- --list-different \"**/*.js\"", 8 | "test:code": "node test.js", 9 | "test": "npm run test:code && npm run test:format", 10 | "format:config": "prettier --single-quote --trailing-comma es5 --use-tabs --arrow-parens always", 11 | "format": "npm run format:config -- --write \"**/*.js\"", 12 | "play": "node coup.js play", 13 | "loop": "node coup.js loop" 14 | }, 15 | "bin": { 16 | "coup": "./coup.js" 17 | }, 18 | "keywords": [], 19 | "author": "", 20 | "license": "ISC", 21 | "devDependencies": { 22 | "prettier": "^1.13.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /201808-coup/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Style = { 4 | parse: (text, start, end = '39m') => { 5 | if (text !== undefined) { 6 | return `\u001B[${start}${text}\u001b[${end}`; 7 | } else { 8 | return ``; 9 | } 10 | }, 11 | black: (text) => Style.parse(text, '30m'), 12 | red: (text) => Style.parse(text, '31m'), 13 | green: (text) => Style.parse(text, '32m'), 14 | yellow: (text) => Style.parse(text, '33m'), 15 | blue: (text) => Style.parse(text, '34m'), 16 | magenta: (text) => Style.parse(text, '35m'), 17 | cyan: (text) => Style.parse(text, '36m'), 18 | white: (text) => Style.parse(text, '37m'), 19 | gray: (text) => Style.parse(text, '90m'), 20 | bold: (text) => Style.parse(text, '1m', '22m'), 21 | }; 22 | 23 | module.exports = exports = { 24 | Style, 25 | }; 26 | -------------------------------------------------------------------------------- /201808-sorting/charles/mergeSort.js: -------------------------------------------------------------------------------- 1 | const { isLarger } = require('./utils'); 2 | module.exports = function mergeSort (array, legend) { 3 | if (array.length <= 1) return array; 4 | 5 | let list1 = []; 6 | let list2 = []; 7 | 8 | array.forEach((value, index) => { 9 | index < (array.length / 2) ? list1.push(value) : list2.push(value); 10 | }); 11 | 12 | list1 = mergeSort(list1, legend); 13 | list2 = mergeSort(list2, legend); 14 | 15 | return merge(list1, list2, legend); 16 | } 17 | 18 | function merge (first, second, legend) { 19 | const left = first.slice(); 20 | const right = second.slice(); 21 | let sortedList = []; 22 | 23 | while (left.length || right.length) { 24 | const firstElement = left[0]; 25 | const secondElement = right[0]; 26 | if (isLarger(firstElement, secondElement, legend)) { 27 | sortedList.push(firstElement); 28 | left.shift(); 29 | } else { 30 | sortedList.push(secondElement); 31 | right.shift(); 32 | } 33 | } 34 | return sortedList; 35 | } 36 | -------------------------------------------------------------------------------- /201809-binary-search-tree/timl/golf.js: -------------------------------------------------------------------------------- 1 | M=Math 2 | f=(t,s)=>{t.v=s.v;t.l=s.l;t.r=s.r} 3 | y=t=>t.v=a('r')(t.l) 4 | s=t=>t.r.l?s(t.r):f(t,t.l) 5 | j=(t,v)=>t.l?j(v>t.v?t.r:t.l,v):f(t,{v,l:{},r:{}}) 6 | z=(t,v)=>t.l&&(t.v==v?t.l.l==t.r.l?f(t,{}):t.l.l&&t.r.l?(y(t),t.l.l.l==t.l.r.l?t.l={}:t.l.r.l?s(t.l.r):f(t.l,t.l.l)):f(t,t.l.l?t.l:t.r):z(v>t.v?t.r:t.l,v)) 7 | k=(t,v)=>!!t&&(t.v==v||k(v>t.v?t.r:t.l,v)) 8 | d=(t,v)=>t.v==v?0:d(v>t.v?t.r:t.l,v)+1 9 | h=t=>t?M.max(h(t.l),h(t.r))+1:-1 10 | c=t=>t?c(t.l)+c(t.r)+1:-.5 11 | b=t=>!t||b(t.l)&&b(t.r)&&M.abs(h(t.l)-h(t.r))<=1 12 | a=x=>t=>t[x].l?a(x)(t[x]):t.v 13 | i=t=>t.l?[...i(t.l),t.v,...i(t.r)]:[] 14 | p=t=>t.l?[t.v,...p(t.l),...p(t.r)]:[] 15 | q=t=>t.l?[...q(t.l),...q(t.r),t.v]:[] 16 | m=g=>+g<1?[]:[g[0].v,...m([...g.splice(1),g[0].l,g[0].r].filter(i=>i.l))] 17 | module.exports={newTree:_=>({}),insert:j,remove:z,find:k,depth:(t,v)=>k(t,v)?d(t,v):-1,height:h,count:c,balanced:b,biggest:a('r'),smallest:a('l'),inOrder:i,preOrder:p,postOrder:q,breadthFirst:t=>t.l?m([t]):[]} 18 | -------------------------------------------------------------------------------- /201808-coup/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Path = require('path'); 4 | const Fs = require('fs'); 5 | 6 | const GetPlayer = (path = '.') => { 7 | const allPlayer = Fs.readdirSync(path) 8 | .map((name) => Path.join(path, name)) 9 | .filter((item) => Fs.lstatSync(item).isDirectory()) 10 | .filter((folder) => !folder.startsWith('.') && folder !== 'node_modules'); 11 | 12 | if (allPlayer.length < 2) { 13 | console.error(`\n🛑 We need at least two player to play this game!\n`); 14 | process.exit(1); 15 | } else { 16 | return allPlayer; 17 | } 18 | }; 19 | 20 | const ALLBOTS = GetPlayer; 21 | 22 | const CARDS = () => ['duke', 'assassin', 'captain', 'ambassador', 'contessa']; 23 | 24 | const GetStack = (cards = CARDS()) => { 25 | let STACK = []; 26 | cards.forEach((card) => (STACK = [...STACK, ...new Array(3).fill(card)])); 27 | return STACK; 28 | }; 29 | 30 | const DECK = GetStack; 31 | 32 | const ACTIONS = () => [ 33 | 'taking-1', 34 | 'foreign-aid', 35 | 'couping', 36 | 'taking-3', 37 | 'assassination', 38 | 'stealing', 39 | 'swapping', 40 | ]; 41 | 42 | module.exports = exports = { 43 | ALLBOTS, 44 | CARDS, 45 | DECK, 46 | ACTIONS, 47 | }; 48 | -------------------------------------------------------------------------------- /201810-maze/maze.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { Style } = require('./../201808-coup/helper.js'); 4 | const { MAZE } = require('./index.js'); 5 | const Path = require('path'); 6 | const Fs = require('fs'); 7 | 8 | const userPath = Path.normalize( process.argv[ 3 ] ); 9 | 10 | if( !Fs.existsSync( userPath ) ) { 11 | console.error(`\n\n${ Style.red('The user ') }${ Style.yellow( userPath ) }${Style.red(' was not found.')}\n\n`); 12 | process.exit( 1 ); 13 | } 14 | 15 | 16 | if( process.argv.includes('play') ) { 17 | let level = 1; 18 | let levelIndex = process.argv.indexOf('-l'); 19 | if( levelIndex === -1 ) { 20 | levelIndex = process.argv.indexOf('--level'); 21 | } 22 | 23 | if( levelIndex !== -1 && process.argv[( levelIndex + 1 )] ) { 24 | level = process.argv[( levelIndex + 1 )]; 25 | } 26 | 27 | let speed; 28 | let speedIndex = process.argv.indexOf('-s'); 29 | if( speedIndex === -1 ) { 30 | speedIndex = process.argv.indexOf('--speed'); 31 | } 32 | 33 | if( speedIndex !== -1 && process.argv[( speedIndex + 1 )] ) { 34 | speed = process.argv[( speedIndex + 1 )]; 35 | } 36 | 37 | new MAZE({ 38 | level, 39 | userPath, 40 | stepTime: speed, 41 | }).Start(); 42 | } 43 | 44 | // if( process.argv.includes('loop') ) { 45 | // const loop = new LOOP(); 46 | // const debug = process.argv.includes('-d'); 47 | 48 | // loop.Run( debug ); 49 | // } 50 | -------------------------------------------------------------------------------- /201810-maze/mitchell/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let inverse = { 4 | up: "down", 5 | down: "up", 6 | right: "left", 7 | left: "right" 8 | }; 9 | 10 | class Reverse {} 11 | 12 | class BOT { 13 | constructor({ size, start, end }) { 14 | this.history = []; 15 | this.xDirection = start[0] < end[0] ? "right" : "left"; 16 | this.yDirection = start[1] < end[1] ? "down" : "up"; 17 | this.shouldReverse = 0; 18 | } 19 | 20 | Move({ MAP }) { 21 | try { 22 | let ret = this._move(MAP); 23 | this.history.push(ret); 24 | return ret; 25 | } catch (e) { 26 | if (e instanceof Reverse) { 27 | return inverse[this.history.pop()]; 28 | } 29 | throw e; 30 | } 31 | } 32 | 33 | _move(map) { 34 | if (this.shouldReverse) { 35 | this.shouldReverse--; 36 | this.previousWasReverse = true; 37 | throw new Reverse(); 38 | } 39 | let index = 2; 40 | 41 | let y = { 42 | up: map[index - 1][index], 43 | down: map[index + 1][index] 44 | }; 45 | let x = { 46 | right: map[index][index + 1], 47 | left: map[index][index - 1] 48 | }; 49 | 50 | if (Math.random() > 0.5 && y[this.yDirection]) { 51 | return this.yDirection; 52 | } else if (x[this.xDirection]) { 53 | return this.xDirection; 54 | } else { 55 | if (this.previousWasReverse) { 56 | this.shouldReverse += 2; 57 | } 58 | this.shouldReverse++; 59 | throw new Reverse(); 60 | } 61 | } 62 | } 63 | 64 | module.exports = exports = BOT; 65 | -------------------------------------------------------------------------------- /201808-sorting/charles/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const selectionSort = require('./selectionSort'); 5 | const mergeSort = require('./mergeSort'); 6 | 7 | const sorters = { 8 | mergeSort, 9 | selectionSort, 10 | } 11 | const { isLarger } = require('./utils'); 12 | const codeChallenge = function () { 13 | try { 14 | const [ _, __, listKey = 'normies', sortType = 'mergeSort' ] = process.argv; 15 | 16 | console.log(`== USING ${sortType} to sort ${listKey} list`); 17 | 18 | const filePath = path.resolve(__dirname, '../unsorted.json'); 19 | const sortLegendPath = path.resolve(__dirname, './sortLegend.txt'); 20 | const lists = JSON.parse(fs.readFileSync(filePath)); 21 | const sortLegend = fs.readFileSync(sortLegendPath).toString().split('\n'); 22 | if (typeof sorters[sortType] !== 'function') { 23 | throw new Error('Expected second argument to have value of either mergeSort or selectionSort'); 24 | } 25 | const sortedList = dedupe(sorters[sortType](lists[listKey], sortLegend)).reverse(); 26 | fs.writeFileSync(path.resolve(__dirname,`${listKey}.json`), JSON.stringify(sortedList)); 27 | console.log(`== FILE ${listKey}.json WRITTEN with sorted list`); 28 | } catch (e) { 29 | console.error(`=== ERROR: ${e.message}`); 30 | } 31 | } 32 | 33 | function dedupe (arr) { 34 | return arr.filter((el, i , arr) => arr.indexOf(el) === i); 35 | } 36 | 37 | codeChallenge(); 38 | 39 | // take first two items and compare them to each other 40 | // the larger one gets moved 41 | -------------------------------------------------------------------------------- /201808-coup/JossM/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | getBlockersFor, 5 | getCardFor, 6 | getCount, 7 | loseCard, 8 | swapCards, 9 | } = require('./helpers'); 10 | const { makeAction } = require('./logic'); 11 | const { ME } = require('./constants'); 12 | 13 | class CoupCouper { 14 | constructor() { 15 | this.turnCount = 0; 16 | } 17 | OnTurn(args) { 18 | return makeAction({ ...args, turnCount: this.turnCount++ }); 19 | } 20 | OnChallengeActionRound({ 21 | history, 22 | otherPlayers, 23 | action, 24 | discardedCards, 25 | myCards, 26 | toWhom, 27 | }) { 28 | if (toWhom !== ME) return false; // limit challenges, do not draw attention 29 | return getCount(getCardFor(action), [...myCards, ...discardedCards]) === 3; 30 | } 31 | OnCounterAction({ history, otherPlayers, action, myCards }) { 32 | if (action === 'assassination' && myCards.length === 1) return 'contessa'; 33 | const canBlock = getBlockersFor(action).find((c) => myCards.includes(c)); 34 | return canBlock || false; 35 | } 36 | OnCounterActionRound({ 37 | history, 38 | otherPlayers, 39 | action, 40 | card, 41 | discardedCards, 42 | myCards, 43 | }) { 44 | return getCount(card, [...myCards, ...discardedCards]) === 3; 45 | } 46 | OnSwappingCards({ history, otherPlayers, myCards, newCards }) { 47 | return swapCards([...myCards, ...newCards]); 48 | } 49 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 50 | if (myCards.length === 1) return myCards[0]; 51 | return loseCard(myCards); 52 | } 53 | } 54 | 55 | module.exports = exports = CoupCouper; 56 | -------------------------------------------------------------------------------- /201808-sorting/tici/MergeSort.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | const unsorted = '../unsorted.json'; 3 | 4 | // compare the arrays item by item and return the concatenated result 5 | const merge = (left, right) => { 6 | let result = [] 7 | let indexLeft = 0 8 | let indexRight = 0 9 | 10 | while (indexLeft < left.length && indexRight < right.length) { 11 | if (left[indexLeft] < right[indexRight]) { 12 | result.push(left[indexLeft]) 13 | indexLeft++ 14 | } else { 15 | result.push(right[indexRight]) 16 | indexRight++ 17 | } 18 | } 19 | 20 | return result.concat(left.slice(indexLeft)).concat(right.slice(indexRight)) 21 | } 22 | 23 | // Split the array into halves and merge them recursively 24 | const mergeSort = (arr) => { 25 | if (arr.length === 1) return arr; // return once we hit an array with a single item 26 | 27 | const middle = Math.floor(arr.length / 2) // get the middle item of the array rounded down 28 | const left = arr.slice(0, middle) // items on the left side 29 | const right = arr.slice(middle) // items on the right side 30 | 31 | return merge( 32 | mergeSort(left), 33 | mergeSort(right) 34 | ) 35 | } 36 | 37 | fs.readFile(unsorted, 'utf8', (error, data) => { 38 | 39 | if (error) new Error(error); 40 | 41 | const key = 'villains'; //normies, heroes or villains 42 | const parsedData = JSON.parse(data); 43 | const result = mergeSort(parsedData[key]); // Merge Sort each value 44 | 45 | fs.writeFile(`${key}.json`, JSON.stringify(result), 'utf8', (error) => { 46 | if (error) new Error(error); 47 | console.log(`${key}.json`); 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /201810-maze/jean-baudrillard/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class BOT { 4 | constructor({ size, start, end }) { 5 | console.log(size, start, end); 6 | this.counter = 0; 7 | this.size = size; 8 | this.start = start; 9 | this.current = start; 10 | this.end = end; 11 | } 12 | 13 | updateCurrentPosition (actionTaken) { 14 | switch (actionTaken) { 15 | case 'up': { 16 | const limit = 0; 17 | const newValue = this.current[0] - 1; 18 | if (newValue > limit) { 19 | this.current[0] = newValue; 20 | } 21 | break; 22 | } 23 | case 'down': { 24 | const limit = this.size.height; 25 | const newValue = this.current[0] + 1; 26 | console.log(newValue, limit); 27 | if (newValue < limit) { 28 | this.current[0] = newValue; 29 | } 30 | break; 31 | } 32 | case 'left': { 33 | const limit = 0; 34 | const newValue = this.current[1] - 1; 35 | if (newValue > limit) { 36 | this.current[1] = newValue; 37 | } 38 | break; 39 | } 40 | case 'right': { 41 | const limit = this.size.width; 42 | const newValue = this.current[1] + 1; 43 | if (newValue < limit) { 44 | this.current[1] = newValue; 45 | } 46 | break; 47 | } 48 | default: 49 | break; 50 | }; 51 | this.counter++; 52 | } 53 | 54 | getBestMove (MAP) { 55 | const [ y, x ] = this.current; 56 | const [ ey, ex ] = this.end; 57 | if (x < ex) { 58 | return 'right'; 59 | } 60 | if (y < ey) { 61 | return 'down'; 62 | } 63 | } 64 | 65 | Move({ MAP }) { 66 | const actionTaken = this.getBestMove(MAP); 67 | this.updateCurrentPosition(actionTaken); 68 | return actionTaken; 69 | } 70 | } 71 | 72 | module.exports = exports = BOT; 73 | -------------------------------------------------------------------------------- /201808-coup/BorisB/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-coup/JedW/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-coup/JessT/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-coup/KevinY/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-coup/LaurenA/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-coup/MalB/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 8 | const against = 9 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 10 | 11 | if (myCoins > 10) { 12 | action = 'couping'; 13 | } 14 | 15 | return { 16 | action, 17 | against, 18 | }; 19 | } 20 | 21 | OnChallengeActionRound({ 22 | history, 23 | myCards, 24 | myCoins, 25 | otherPlayers, 26 | discardedCards, 27 | action, 28 | byWhom, 29 | toWhom, 30 | }) { 31 | return [true, false][Math.floor(Math.random() * 2)]; 32 | } 33 | 34 | OnCounterAction({ 35 | history, 36 | myCards, 37 | myCoins, 38 | otherPlayers, 39 | discardedCards, 40 | action, 41 | byWhom, 42 | }) { 43 | if (action === 'assassination') { 44 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 45 | } else if (action === 'stealing') { 46 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 47 | } 48 | } 49 | 50 | OnCounterActionRound({ 51 | history, 52 | myCards, 53 | myCoins, 54 | otherPlayers, 55 | discardedCards, 56 | action, 57 | byWhom, 58 | toWhom, 59 | card, 60 | }) { 61 | return [true, false][Math.floor(Math.random() * 2)]; 62 | } 63 | 64 | OnSwappingCards({ 65 | history, 66 | myCards, 67 | myCoins, 68 | otherPlayers, 69 | discardedCards, 70 | newCards, 71 | }) { 72 | return newCards; 73 | } 74 | 75 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 76 | return myCards[0]; 77 | } 78 | } 79 | 80 | module.exports = exports = BOT; 81 | -------------------------------------------------------------------------------- /201808-sorting/README.md: -------------------------------------------------------------------------------- 1 | August 2018 challenge (part two) 2 | ===================== 3 | 4 | **Due date: 22nd August 2018** 5 | 6 | This months _second_ challenge consists of you writing two [sorting algorithms](https://en.wikipedia.org/wiki/Sorting_algorithm) from scratch. 7 | This folder contains a json file `unsorted.json`. 8 | 9 | In that file we can find an object with three arrays: `normies`, `heroes` and `villains`. 10 | Each of those need to be sorted alphabetically by both algorithms [selection sort](https://en.wikipedia.org/wiki/Selection_sort) and 11 | [merge sort](https://en.wikipedia.org/wiki/Merge_sort). 12 | 13 | You need to output two things: 14 | - Each list in three different json files: `normies.json`, `heroes.json` and `villains.json` 15 | - You record the time it took for each algorithm to sort each list via the `time` bash helper `$ time node yourscript.js` 16 | 17 | The output will then be: 18 | 19 | ``` 20 | # selection sort 21 | 22 | normies.json 23 | real 0m4.011s 24 | user 0m2.631s 25 | sys 0m0.787s 26 | 27 | heroes.json 28 | real 0m4.011s 29 | user 0m2.631s 30 | sys 0m0.787s 31 | 32 | villains.json 33 | real 0m4.011s 34 | user 0m2.631s 35 | sys 0m0.787s 36 | 37 | # merge sort 38 | 39 | normies.json 40 | real 0m4.011s 41 | user 0m2.631s 42 | sys 0m0.787s 43 | 44 | heroes.json 45 | real 0m4.011s 46 | user 0m2.631s 47 | sys 0m0.787s 48 | 49 | villains.json 50 | real 0m4.011s 51 | user 0m2.631s 52 | sys 0m0.787s 53 | ``` 54 | 55 | The sorting order is: 56 | 57 | ```sh 58 | a 59 | b 60 | c 61 | d 62 | e 63 | f 64 | g 65 | h 66 | i 67 | j 68 | k 69 | l 70 | m 71 | n 72 | o 73 | p 74 | q 75 | r 76 | s 77 | t 78 | u 79 | v 80 | w 81 | x 82 | y 83 | z 84 | [space] 85 | 1 86 | 2 87 | 3 88 | 4 89 | 5 90 | 6 91 | 7 92 | 8 93 | 9 94 | 0 95 | : 96 | - 97 | . 98 | ' 99 | ’ 100 | # 101 | / 102 | ``` 103 | 104 | Also shorter is sorted higher: 105 | 106 | ``` 107 | foo 108 | food 109 | ``` 110 | 111 | ## RULEZ 112 | 113 | 1. Node only 114 | 1. No dependencies 115 | 1. No use of `sort` 116 | -------------------------------------------------------------------------------- /201810-maze/noviny/scratchings.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | const fullGrid = [ 3 | [true, true, true, true, true], 4 | [false, true, true, true, true], 5 | [true, true, true, true, true], 6 | [true, true, true, true, true], 7 | [true, true, true, true, true], 8 | ] 9 | 10 | const getDistance = (position, goal, grid) => { 11 | let [posHeight, posWidth] = position; 12 | if (!grid[posHeight] || !grid[posHeight][posWidth]) return 100000000; 13 | let [goalHeight, goalWidth] = goal; 14 | 15 | let distance = 16 | Math.abs(posHeight - goalHeight) + Math.abs(posWidth - goalWidth); 17 | return distance; 18 | }; 19 | 20 | const getSurrounds = ([height, width]) => { 21 | let down = [height + 1, width]; 22 | let up = [height - 1, width]; 23 | let left = [height, width, -1]; 24 | let right = [height, width + 1]; 25 | return { up, down, left, right }; 26 | }; 27 | 28 | const distancesAround = (position, goal, grid) => { 29 | let [height, width] = position; 30 | let { up, down, left, right } = getSurrounds(position); 31 | 32 | return [ 33 | { direction: "up", distance: getDistance(up, goal, grid) }, 34 | { direction: "down", distance: getDistance(down, goal, grid) }, 35 | { direction: "left", distance: getDistance(left, goal, grid) }, 36 | { direction: "right", distance: getDistance(right, goal, grid) } 37 | ]; 38 | }; 39 | 40 | const getBestDistance = (position, goal, grid) => { 41 | let distances = distancesAround(position, goal, grid); 42 | distances = distances.sort((a, b) => a.distance - b.distance); 43 | return distances[0].direction; 44 | }; 45 | 46 | let current = [0, 0]; 47 | let end = [3, 3]; 48 | let stack = [current]; 49 | let count = 0; 50 | 51 | while (current[0] !== end[0] && current[1] !== end[1] && count < 1000) { 52 | count++; 53 | let direction = getBestDistance(current, end, fullGrid); 54 | let newLocation = getSurrounds(current)[direction]; 55 | stack.push(current); 56 | current = newLocation; 57 | } 58 | 59 | /* 60 | A place for comments so we don't have to all the slashes 61 | from the current location 62 | a) see best one, otherwise 63 | b) backtrack to the last place on your stack.ullGrid)); 64 | 65 | */ 66 | -------------------------------------------------------------------------------- /201809-binary-search-tree/README.md: -------------------------------------------------------------------------------- 1 | # Code Challenge - Binary Search Tree 2 | 3 | **The challenge finishes at team talks on Wednesday the 26th 4PM** 4 | 5 | A Binary Search Tree (BST) is a data structure which allows you to store values in a structure which supports fast insertion, removal, searching and sorting. 6 | 7 | For this challenge, you will need to implement a BST which can store numerical values. 8 | This will involve implementing a collection of functions which make up an API. 9 | The API is provided for you, all you need to do is fill in the blanks. 10 | 11 | To get started, copy the example file, and install the required packages. 12 | 13 | ``` 14 | cp tree.example.js tree.js 15 | yarn 16 | ``` 17 | 18 | ## Test driven development 19 | 20 | For this challenge, we will be following a test driven development approach. 21 | A collection of unit tests have been provided which all need to pass in order for the challenge to be complete. 22 | 23 | To run the tests, run 24 | 25 | ``` 26 | yarn test 27 | ``` 28 | 29 | When you first run this command, you can expect all the tests to fail. 30 | Your job is to implement the code in `tree.js` so that all of these tests pass. 31 | 32 | There are a number of different groups of tests in `tree.test.js`. 33 | During development, you may want to skip certain tests which you know will fail, or select particular tests which you are interested in. 34 | You can change the tests being run by adding `.skip` or `.only` to any `describe` or `test` function, e.g. 35 | 36 | ``` 37 | describe.skip('...', () => {...}); 38 | ... 39 | test.only('...', () => {...}); 40 | ``` 41 | 42 | Don't forget to remove these again to get the full test suite! 43 | 44 | ## Tips 45 | 46 | - Implement `newTree()` and `insert()` first. 47 | Without these, none of the other functions make any sense. 48 | - Draw the trees in the test cases by hand to make sure you understand what they should look like. 49 | - It might help to implement a `printTree()` function to help you visual the trees as you build them. 50 | Comparing this output to your hand drawn trees will help you track down any bugs. 51 | - Implement `remove()` last. It is probably the trickiest function of the API, and you might want to use some of the query functions as helpers. 52 | -------------------------------------------------------------------------------- /201808-sorting/tici/SelectionSort.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const unsorted = '../unsorted.json'; 3 | 4 | const sortOrder = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", " ", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", ":", "-", ".", "'", "’", "#", "/", ] 5 | 6 | // Function to swap two elements by assigning 7 | // the first to a temporary variable then 8 | // reassigning the actual array elements 9 | // This runs directly in the function and 10 | // acts on the array in memory rather than returning 11 | // a swapped array 12 | const swap = (array, firstIndex, secondIndex) => { 13 | let temp = array[firstIndex]; 14 | array[firstIndex] = array[secondIndex]; 15 | array[secondIndex] = temp; 16 | }; 17 | 18 | const indexOfMinimum = (array, startIndex) => { 19 | let minValue = array[startIndex]; 20 | let minIndex = startIndex; 21 | /* Loop through the "sub array" or array not including the minIndex, 22 | because we know that one has already been sorted If the index in the 23 | loop is less than the minIndex, make it the minIndex instead */ 24 | for(let i = minIndex + 1; i < array.length; i++) { 25 | if(array[i] < minValue) { 26 | minIndex = i; 27 | minValue = array[i]; 28 | } 29 | } 30 | return minIndex; 31 | }; 32 | 33 | const selectionSort = (array) => { 34 | let newIndex; 35 | // Loop through the entire array, reassigning the minIndex as we go 36 | // Swap the minIndex with i because minIndex will be smaller 37 | for(let i = 0; i < array.length; i++) { 38 | newIndex = indexOfMinimum(array, i); 39 | swap(array, i, newIndex); 40 | } 41 | return array; 42 | }; 43 | 44 | fs.readFile(unsorted, 'utf8', (error, data) => { 45 | 46 | if (error) new Error(error); 47 | 48 | const key = 'heroes'; //normies, heroes or villains 49 | const parsedData = JSON.parse(data); 50 | let result = selectionSort(parsedData[key]); // Selection Sort each value 51 | 52 | fs.writeFile(`${key}.json`, JSON.stringify(result), 'utf8', (error) => { 53 | if (error) new Error(error); 54 | console.log(`${key}.json`); 55 | }); 56 | 57 | // Object.entries(parsedData).forEach( ([key, value]) => { 58 | // let result = selectionSort(value); // Selection Sort each value 59 | // fs.writeFile(`${key}.json`, JSON.stringify(result), 'utf8', (error) => { 60 | // if (error) new Error(error); 61 | // console.log(`${key}.json`); 62 | // }); 63 | // }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /201808-coup/AbbasA/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const count = (card, cards) => cards.filter((c) => c === card).length; 4 | 5 | const sortCards = (cards) => { 6 | const order = { 7 | duke: 0, 8 | captain: 1, 9 | contessa: 2, 10 | ambassador: 3, 11 | assassin: 4, 12 | }; 13 | const inverseOrder = Object.entries(order).reduce( 14 | (o, [k, v]) => ({ ...o, [v]: k }), 15 | {} 16 | ); 17 | return cards 18 | .map((c) => order[c]) 19 | .sort() 20 | .map((x) => inverseOrder[x]); 21 | }; 22 | 23 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 24 | 25 | class BOT { 26 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 27 | let action; 28 | const against = 29 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 30 | 31 | if (myCoins >= 7) { 32 | action = 'couping'; 33 | } else if (myCards.includes('duke')) { 34 | action = 'taking-3'; 35 | } else if (myCards.includes('assassin') && myCoins >= 3) { 36 | action = 'assassination'; 37 | } else if (myCards.includes('captain')) { 38 | action = 'stealing'; 39 | } else if (myCards.includes('ambassador')) { 40 | action = 'swapping'; 41 | } else { 42 | action = 'taking-1'; 43 | } 44 | 45 | return { 46 | action, 47 | against, 48 | }; 49 | } 50 | 51 | OnChallengeActionRound({}) { 52 | return false; 53 | } 54 | 55 | OnCounterAction({ myCards, action }) { 56 | if (myCards.includes('captain') && action === 'stealing') { 57 | return 'captain'; 58 | } else if (myCards.includes('ambassador') && action === 'stealing') { 59 | return 'ambassador'; 60 | } else if (myCards.includes('contessa') && action === 'assassination') { 61 | return 'contessa'; 62 | } else if (myCards.includes('duke') && action === 'foreign-aid') { 63 | return 'duke'; 64 | } else if (myCards.length === 1 && action === 'assassination') { 65 | return 'contessa'; 66 | } else { 67 | return false; 68 | } 69 | } 70 | 71 | OnCounterActionRound({}) { 72 | return false; 73 | } 74 | 75 | OnSwappingCards({ myCards, newCards }) { 76 | const sorted = sortCards([...myCards, ...newCards]); 77 | const first = sorted[0]; 78 | return myCards.length === 1 79 | ? [first] 80 | : [first, sorted.find((c) => c !== first) || first]; 81 | } 82 | 83 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 84 | return sortCards([...myCards]).slice(-1)[0]; 85 | } 86 | } 87 | 88 | module.exports = exports = BOT; 89 | -------------------------------------------------------------------------------- /201808-coup/TomW/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 7 | let action = ['taking-1', 'foreign-aid', 'taking-3'][ 8 | Math.floor(Math.random() * 3) 9 | ]; 10 | const against = 11 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 12 | 13 | if (myCoins >= 7) { 14 | action = 'couping'; 15 | return { action, against }; 16 | } 17 | 18 | if (myCoins >= 3 && myCards.includes('assassin')) { 19 | action = 'assassination'; 20 | return { action, against }; 21 | } 22 | 23 | if (myCards.includes('duke')) { 24 | action = 'taking-3'; 25 | return { action, against }; 26 | } 27 | 28 | if (myCards.includes('captain')) { 29 | action = 'stealing'; 30 | return { action, against }; 31 | } 32 | 33 | if (myCoins === 0 && myCards.includes('ambassador')) { 34 | action = 'swapping'; 35 | return { action, against }; 36 | } 37 | 38 | if (myCoins === 0) { 39 | action = 'foreign-aid'; 40 | return { action, against }; 41 | } 42 | 43 | return { action, against }; 44 | } 45 | 46 | OnChallengeActionRound({ 47 | history, 48 | myCards, 49 | myCoins, 50 | otherPlayers, 51 | discardedCards, 52 | action, 53 | byWhom, 54 | toWhom, 55 | }) { 56 | return false; 57 | // return [ true, false ][ Math.floor( Math.random() * 2 ) ]; 58 | } 59 | 60 | OnCounterAction({ 61 | history, 62 | myCards, 63 | myCoins, 64 | otherPlayers, 65 | discardedCards, 66 | action, 67 | byWhom, 68 | }) { 69 | if (action === 'assassination' && myCards.includes('contessa')) { 70 | return 'contessa'; 71 | } 72 | if (action === 'stealing' && myCards.includes('ambassador')) { 73 | return 'ambassador'; 74 | } 75 | if (action === 'stealing' && myCards.includes('captain')) { 76 | return 'captain'; 77 | } 78 | return false; 79 | } 80 | 81 | OnCounterActionRound({ 82 | history, 83 | myCards, 84 | myCoins, 85 | otherPlayers, 86 | discardedCards, 87 | action, 88 | byWhom, 89 | toWhom, 90 | card, 91 | }) { 92 | return false; 93 | // return [ true, false ][ Math.floor( Math.random() * 2 ) ]; 94 | } 95 | 96 | OnSwappingCards({ 97 | history, 98 | myCards, 99 | myCoins, 100 | otherPlayers, 101 | discardedCards, 102 | newCards, 103 | }) { 104 | return newCards; 105 | } 106 | 107 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 108 | return myCards[0]; 109 | } 110 | } 111 | 112 | module.exports = exports = BOT; 113 | -------------------------------------------------------------------------------- /201808-coup/JossM/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { CARDS } = require('./constants'); 4 | const { clone, toArray, unique } = require('./utils'); 5 | 6 | function cardPreference(shouldReverse) { 7 | const order = Object.keys(CARDS); 8 | return shouldReverse ? order.reverse() : order; 9 | } 10 | function loseCard(cards) { 11 | return cardPreference(true).filter((c) => cards.includes(c))[0]; 12 | } 13 | function swapCards(cards) { 14 | const uniqueCards = cards.filter(unique); 15 | return cardPreference() 16 | .filter((c) => uniqueCards.includes(c)) 17 | .slice(0, 2); 18 | } 19 | function getCount(card, cards) { 20 | return cards.filter((c) => c === card).length; 21 | } 22 | function getCardFor(action) { 23 | const actions = { 24 | 'taking-3': 'duke', 25 | assassination: 'assassin', 26 | stealing: 'captain', 27 | swapping: 'ambassador', 28 | }; 29 | 30 | return actions[action]; 31 | } 32 | function getCounterAction(action) { 33 | const actions = { 34 | swapping: 'stealing', 35 | stealing: 'stealing', 36 | 'taking-3': 'foreign-aid', 37 | }; 38 | 39 | return actions[action]; 40 | } 41 | function getBlockersFor(action) { 42 | const actions = { 43 | assassination: ['contessa'], 44 | stealing: ['captain', 'ambassador'], 45 | 'foreign-aid': ['duke'], 46 | }; 47 | 48 | return actions[action] || []; 49 | } 50 | function getActionFrom(card) { 51 | return card && CARDS[card].action; 52 | } 53 | function getCounterFrom(card) { 54 | return card && CARDS[card].counter; 55 | } 56 | function getMostInfluential(players) { 57 | const byCoin = (a, b) => b.coins - a.coins; 58 | const withTwo = players.filter((p) => p.cards === 2); 59 | const withOne = players.filter((p) => p.cards === 1); 60 | const arr = withTwo.length ? withTwo.sort(byCoin) : withOne.sort(byCoin); 61 | 62 | return arr[0] || players[0]; 63 | } 64 | function getPassiveAction(playerData) { 65 | const aidWillBeCountered = Boolean( 66 | playerData.filter((p) => { 67 | return p.counters && p.counters.includes('foreign-aid'); 68 | }).length 69 | ); 70 | // console.log('aidWillBeCountered', aidWillBeCountered); 71 | const action = aidWillBeCountered ? 'taking-1' : 'foreign-aid'; 72 | return { action, against: null }; 73 | } 74 | function getCanditate(action, playerData) { 75 | return playerData.filter((p) => { 76 | // console.log('getCanditate for', action, p.name, p.counters); 77 | if (!p.counters || !p.counters.length) return true; 78 | return !p.counters.includes(action); 79 | })[0]; 80 | } 81 | 82 | module.exports = exports = { 83 | cardPreference, 84 | getActionFrom, 85 | getBlockersFor, 86 | getCanditate, 87 | getCardFor, 88 | getCount, 89 | getCounterAction, 90 | getCounterFrom, 91 | getMostInfluential, 92 | getPassiveAction, 93 | loseCard, 94 | swapCards, 95 | }; 96 | -------------------------------------------------------------------------------- /201808-coup/SanjiyaD/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ACTIONS } = require('../constants.js'); 4 | const actions = ACTIONS(); 5 | 6 | class BOT { 7 | shuffleActions(actions) { 8 | return actions[Math.floor(Math.random() * actions.length)]; 9 | } 10 | 11 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 12 | let action; 13 | let actionsAvailable = actions; 14 | const against = 15 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 16 | 17 | // console.log("===========================================") 18 | // console.log("myCards= " + myCards) 19 | 20 | // Dequalify actions 21 | //don't coup if I have less than 7 coins 22 | if (myCoins < 7) { 23 | actionsAvailable.splice(actionsAvailable.indexOf('couping'), 1); 24 | } 25 | 26 | if (myCoins < 3) { 27 | actionsAvailable.splice(actionsAvailable.indexOf('assassination'), 1); 28 | } 29 | 30 | // randomly choose action 31 | action = this.shuffleActions(actionsAvailable); 32 | 33 | if (action == 'couping' && myCoins < 7) { 34 | actions[Math.floor(Math.random() * actions.length)]; 35 | } 36 | if (myCards.includes('assassin') && myCoins > 2) { 37 | action = 'assassination'; 38 | } 39 | 40 | if (myCards.includes('captain')) { 41 | action = 'stealing'; 42 | } 43 | 44 | if (myCards.includes('duke')) { 45 | action = 'taking-3'; 46 | } 47 | 48 | if (myCoins > 10) { 49 | action = 'couping'; 50 | } 51 | 52 | return { 53 | action, 54 | against, 55 | }; 56 | } 57 | 58 | OnChallengeActionRound({ 59 | history, 60 | myCards, 61 | myCoins, 62 | otherPlayers, 63 | discardedCards, 64 | action, 65 | byWhom, 66 | toWhom, 67 | }) { 68 | if (toWhom == 'SanjiyaD') { 69 | return [true, false][Math.floor(Math.random() * 2)]; 70 | } else { 71 | return false; 72 | } 73 | } 74 | 75 | OnCounterAction({ 76 | history, 77 | myCards, 78 | myCoins, 79 | otherPlayers, 80 | discardedCards, 81 | action, 82 | byWhom, 83 | }) { 84 | if (action === 'assassination') { 85 | return [false, 'contessa'][Math.floor(Math.random() * 2)]; 86 | } else if (action === 'stealing') { 87 | return [false, 'ambassador', 'captain'][Math.floor(Math.random() * 3)]; 88 | } else if (action === 'foreign-aid') { 89 | return [false, 'duke'][Math.floor(Math.random() * 3)]; 90 | } 91 | } 92 | 93 | OnCounterActionRound({ 94 | history, 95 | myCards, 96 | myCoins, 97 | otherPlayers, 98 | discardedCards, 99 | action, 100 | byWhom, 101 | toWhom, 102 | card, 103 | }) { 104 | return [true, false][Math.floor(Math.random() * 2)]; 105 | } 106 | 107 | OnSwappingCards({ 108 | history, 109 | myCards, 110 | myCoins, 111 | otherPlayers, 112 | discardedCards, 113 | newCards, 114 | }) { 115 | return newCards; 116 | } 117 | 118 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 119 | return myCards[0]; 120 | } 121 | } 122 | 123 | module.exports = exports = BOT; 124 | -------------------------------------------------------------------------------- /201809-binary-search-tree/tree.example.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Creation/modification API 3 | */ 4 | 5 | /* 6 | * Create and return a new tree object. This can have whatever shape you like. 7 | */ 8 | const newTree = () => undefined; 9 | 10 | /* 11 | * Insert `value` into `tree`. 12 | */ 13 | const insert = (tree, value) => {}; 14 | 15 | /* 16 | * Remove `value` from `tree`. Only remove the first instance of the value if it appears multiple times. 17 | * Use the 'in-order predecessor` techinque for replacing the node when there are two child nodes. 18 | * (i.e. replace with the largest value from the nodes left-hand tree) 19 | */ 20 | const remove = (tree, value) => {}; 21 | 22 | /* 23 | * Query API 24 | */ 25 | 26 | /* 27 | * Determine whether `value` exists in the `tree`. Return boolean. 28 | */ 29 | const find = (tree, value) => false; 30 | 31 | /* 32 | * Calculate the depth of the given value within the tree. Return -1 if the value does not exist. 33 | * The value at the root has a depth of zero. 34 | */ 35 | const depth = (tree, value) => 0; 36 | 37 | /* 38 | * Calculate the height of the tree. An empty tree has a height of zero. 39 | */ 40 | const height = (tree) => 0; 41 | 42 | /* 43 | * Calculate the number of nodes in the tree. 44 | */ 45 | const count = (tree) => 0; 46 | 47 | /* 48 | * Determine whether the tree is balanced or not. A tree is balanced if: 49 | * - The left sub-tree is balanced, and 50 | * - The right sub-tree is balanced, and 51 | * - The height of the left sub-tree and right sub-tree differ by no more than one. 52 | * 53 | * An empty tree is always balanced. 54 | */ 55 | const balanced = (tree) => true; 56 | 57 | /* 58 | * Calculate the biggest value in the tree. Behaviour is undefined for an empty tree. 59 | */ 60 | const biggest = (tree) => 0; 61 | 62 | /* 63 | * Calculate the smallest value in the tree. Behaviour is undefined for an empty tree. 64 | */ 65 | const smallest = (tree) => 0; 66 | 67 | /* 68 | * Traversal API 69 | * 70 | * The traversal API allows the user to visit each node in the tree in a particular order. 71 | * 72 | * See https://en.wikipedia.org/wiki/Tree_traversal for definitions of the traversal types. 73 | */ 74 | 75 | /* 76 | * Traverse the tree using in-order traversal, returning an array. 77 | */ 78 | const inOrder = (tree) => []; 79 | 80 | /* 81 | * Traverse the tree using pre-order traversal, returning an array. 82 | */ 83 | const preOrder = (tree) => []; 84 | 85 | /* 86 | * Traverse the tree using post-order traversal, returning an array. 87 | */ 88 | const postOrder = (tree) => []; 89 | 90 | /* 91 | * Traverse the tree using breadth first (level-order) traversal, returning an array. 92 | */ 93 | const breadthFirst = (tree) => []; 94 | 95 | module.exports = { 96 | newTree, 97 | insert, 98 | remove, 99 | 100 | find, 101 | depth, 102 | height, 103 | count, 104 | balanced, 105 | biggest, 106 | smallest, 107 | 108 | inOrder, 109 | preOrder, 110 | postOrder, 111 | breadthFirst, 112 | }; 113 | -------------------------------------------------------------------------------- /201808-sorting/timl/index.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'); 3 | 4 | const order = [undefined, 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',' ','1','2','3','4','5','6','7','8','9','0',':','-','.',"'",'’','#','/']; 5 | 6 | const compare = (s1, s2) => { 7 | // Is s1 bigger than (or equal to) s2? ull/undefined are always big 8 | if (s1 === undefined) return true; 9 | if (s2 === undefined) return false; 10 | s1 = s1.toLowerCase(); 11 | s2 = s2.toLowerCase(); 12 | const N = Math.max(s1.length, s2.length); 13 | for (let i = 0; i < N; i++) { 14 | if (s1[i] !== s2[i]) return order.indexOf(s1[i]) > order.indexOf(s2[i]) 15 | } 16 | return true; // Must be identical 17 | } 18 | 19 | const selectionSort = (data) => { 20 | const N = data.length; 21 | const sorted = []; 22 | for (let i = 0; i < N; i++) { 23 | let marker = 0; 24 | for (let j = 1; j < N; j++) { 25 | if (data[j] && compare(data[marker], data[j])) { 26 | marker = j; 27 | } 28 | } 29 | sorted.push(data[marker]); 30 | data[marker] = undefined; 31 | } 32 | return sorted; 33 | } 34 | 35 | 36 | const merge = (data1, data2) => { 37 | let i1 = 0; 38 | let i2 = 0; 39 | const result = []; 40 | while (data1[i1] !== undefined || data2[i2] !== undefined) { 41 | result.push(compare(data1[i1], data2[i2]) ? data2[i2++] : data1[i1++]); 42 | } 43 | return result; 44 | } 45 | 46 | const mergeSort = (data, low, high) => { 47 | // Take index parameters so that we don't have to manipulate the underlying array 48 | low = low !== undefined ? low : 0; 49 | high = high !== undefined ? high : data.length; 50 | 51 | if (high === low) return [] // Base case 52 | if (high - low === 1) return [data[low]]; // Base case 53 | 54 | const middle = Math.floor((low + high) / 2); 55 | 56 | return merge(mergeSort(data, low, middle), mergeSort(data, middle, high)); 57 | } 58 | 59 | 60 | const timedSort = (data, sortFn) => { 61 | const N = data.length; 62 | const t0 = process.hrtime(); 63 | const output = sortFn([...data]); 64 | const t1 = process.hrtime(); 65 | const dt = 1000000000 * (t1[0] - t0[0]) + (t1[1] - t0[1]); 66 | console.log(`N: ${N} - time: ${dt/1000000000} s - per-elem: ${dt/N/1000} us`) 67 | if (N !== output.length) console.error(`Incorrect length: ${output.length} !== ${N}`) 68 | }; 69 | 70 | const data = JSON.parse(fs.readFileSync('../unsorted.json')); 71 | 72 | console.log('Merge Sort'); 73 | timedSort(data.normies, mergeSort); 74 | timedSort(data.villains, mergeSort); 75 | timedSort(data.heroes, mergeSort); 76 | console.log('Selection Sort'); 77 | timedSort(data.normies, selectionSort); 78 | timedSort(data.villains, selectionSort); 79 | timedSort(data.heroes, selectionSort); 80 | 81 | 82 | // Selection Sort 83 | // N: 1000 - time: 0.126074397 s - per-elem: 126.07439699999999 us 84 | // N: 20000 - time: 48.222657082 s - per-elem: 2411.1328541000003 us 85 | // N: 40000 - time: 188.528793157 s - per-elem: 4713.219828925 us 86 | // node index.js 236.94s user 1.21s system 100% cpu 3:57.86 total 87 | -------------------------------------------------------------------------------- /201810-maze/noviny/maze.js: -------------------------------------------------------------------------------- 1 | const NUMBER = 100000000; 2 | 3 | const getDistance = (position, goal, grid) => { 4 | let [posHeight, posWidth] = position; 5 | if (!grid[posHeight] || !grid[posHeight][posWidth]) return NUMBER; 6 | let [goalHeight, goalWidth] = goal; 7 | 8 | let distance = 9 | Math.abs(posHeight - goalHeight) + Math.abs(posWidth - goalWidth); 10 | return distance; 11 | }; 12 | const getSurrounds = ([height, width]) => { 13 | let down = [height + 1, width]; 14 | let up = [height - 1, width]; 15 | let left = [height, width - 1]; 16 | let right = [height, width + 1]; 17 | return { up, down, left, right }; 18 | }; 19 | const distancesAround = (position, goal, grid) => { 20 | let { up, down, left, right } = getSurrounds(position); 21 | return [ 22 | { direction: "up", distance: getDistance(up, goal, grid), position: up }, 23 | { 24 | direction: "down", 25 | distance: getDistance(down, goal, grid), 26 | position: down 27 | }, 28 | { 29 | direction: "left", 30 | distance: getDistance(left, goal, grid), 31 | position: left 32 | }, 33 | { 34 | direction: "right", 35 | distance: getDistance(right, goal, grid), 36 | position: right 37 | } 38 | ]; 39 | }; 40 | const getBestDistance = (position, goal, grid) => { 41 | let distances = distancesAround(position, goal, grid); 42 | return distances 43 | .filter(a => a.distance < NUMBER) 44 | .sort((a, b) => a.distance - b.distance)[0]; 45 | }; 46 | const updateMap = (topLeft, map, newInfo) => { 47 | let [colOffset, rowOffset] = topLeft; 48 | 49 | newInfo.forEach((row, colIndex) => { 50 | let modifiedColIndex = colIndex + colOffset; 51 | row.forEach((square, rowIndex) => { 52 | let modifiedRowIndex = rowIndex + rowOffset; 53 | if ( 54 | map[modifiedColIndex] && 55 | map[modifiedColIndex][modifiedRowIndex] === null 56 | ) { 57 | map[modifiedColIndex][modifiedRowIndex] = square; 58 | } 59 | }); 60 | }); 61 | return map; 62 | }; 63 | 64 | class BOT { 65 | constructor({ size, start, end }) { 66 | this.size = size; 67 | this.stack = [start]; 68 | this.currentPosition = start; 69 | this.fakeGrid = [...Array(size.height)].map(() => 70 | [...Array(size.width)].map(() => null) 71 | ); 72 | this.end = end; 73 | } 74 | 75 | Move({ MAP }) { 76 | let [column, row] = this.currentPosition; 77 | this.fakeGrid = updateMap([column - 2, row - 2], this.fakeGrid, MAP); 78 | let newPosition = getBestDistance( 79 | this.currentPosition, 80 | this.end, 81 | this.fakeGrid 82 | ); 83 | 84 | if (!newPosition) { 85 | // backtrack function 86 | let prior = this.stack.pop(); 87 | let [backCol, backRow] = prior; 88 | this.currentPosition = [backCol, backRow]; 89 | if (column < backCol) return "down"; 90 | if (column > backCol) return "up"; 91 | if (row < backRow) return "right"; 92 | if (row > backRow) return "left"; 93 | } 94 | 95 | this.fakeGrid[newPosition.position[0]][newPosition.position[1]] = false; 96 | 97 | this.currentPosition = newPosition.position; 98 | 99 | this.stack.push([column, row]); 100 | return newPosition.direction; 101 | } 102 | } 103 | 104 | module.exports = exports = BOT; 105 | -------------------------------------------------------------------------------- /201810-maze/nathan/index.js: -------------------------------------------------------------------------------- 1 | class BOT { 2 | constructor({ size, start, end }) { 3 | this.position = start; 4 | this.size = size; 5 | this.end = end; 6 | this.direction = "down"; 7 | this.actionSequence = []; 8 | this.history = []; 9 | } 10 | 11 | avoidBumping(direction, { MAP }) { 12 | const amOnTopEdge = this.position[0] == 0; 13 | const amOnBottomEdge = this.position[0] == this.size[0] - 1; 14 | const amOnLeftEdge = this.position[1] == 0; 15 | const amOnRightEdge = this.position[1] == this.size[1] - 1; 16 | 17 | const cantGo = { 18 | up: !MAP[1][2] || amOnTopEdge, 19 | down: !MAP[3][2] || amOnBottomEdge, 20 | left: !MAP[2][1] || amOnLeftEdge, 21 | right: !MAP[2][3] || amOnRightEdge 22 | }; 23 | 24 | if(!cantGo[direction]){ 25 | return direction; 26 | }else{ 27 | let actions = ["up", "right", "down", "left"]; 28 | if (cantGo.up) {actions = actions.filter(action => action != "up")} 29 | if (cantGo.down) {actions = actions.filter(action => action != "down")} 30 | if (cantGo.left) {actions = actions.filter(action => action != "left")} 31 | if (cantGo.right) {actions = actions.filter(action => action != "right")} 32 | 33 | return actions[Math.floor(Math.random() * actions.length)]; 34 | } 35 | } 36 | 37 | updateGPS(action) { 38 | if (action == "down") { this.position[0]++ } 39 | if (action == "up") { this.position[0]-- } 40 | if (action == "left") { this.position[1]-- } 41 | if (action == "right") { this.position[1]++ } 42 | 43 | this.history.unshift(action); 44 | this.history = this.history.slice(0, 9); 45 | } 46 | 47 | changeDirection() { 48 | if (this.direction == "down") { 49 | this.direction = "right"; 50 | } else { 51 | this.direction = "down"; 52 | } 53 | } 54 | 55 | Move({ MAP }) { 56 | const amOnTopEdge = this.position[0] == 0; 57 | const amOnBottomEdge = this.position[0] == this.size[0] - 1; 58 | const amOnLeftEdge = this.position[1] == 0; 59 | const amOnRightEdge = this.position[1] == this.size[1] - 1; 60 | 61 | const somethingIs = { 62 | toTheLeft: { 63 | up: !MAP[2][1] || amOnLeftEdge, 64 | down: !MAP[2][3] || amOnRightEdge, 65 | left: !MAP[3][2] || amOnBottomEdge, 66 | right: !MAP[1][2] || amOnTopEdge 67 | }, 68 | toTheRight: { 69 | up: !MAP[2][3] || amOnRightEdge, 70 | down: !MAP[2][1] || amOnLeftEdge, 71 | left: !MAP[1][2] || amOnTopEdge, 72 | right: !MAP[3][2] || amOnBottomEdge 73 | }, 74 | inFront: { 75 | up: !MAP[1][2] || amOnTopEdge, 76 | down: !MAP[3][2] || amOnBottomEdge, 77 | left: !MAP[2][1] || amOnLeftEdge, 78 | right: !MAP[2][3] || amOnRightEdge 79 | }, 80 | behind: { 81 | up: !MAP[3][2] || amOnBottomEdge, 82 | down: !MAP[1][2] || amOnTopEdge, 83 | left: !MAP[2][3] || amOnRightEdge, 84 | right: !MAP[2][1] || amOnLeftEdge 85 | } 86 | }; 87 | 88 | if (somethingIs.inFront[this.direction]) { 89 | this.changeDirection(); 90 | } 91 | 92 | let action = this.avoidBumping(this.direction,{ MAP }); 93 | 94 | if (this.actionSequence.length > 0) { 95 | action = this.actionSequence.shift(); 96 | } 97 | 98 | this.updateGPS(action); 99 | 100 | return action; 101 | } 102 | } 103 | 104 | module.exports = exports = BOT; 105 | -------------------------------------------------------------------------------- /201809-binary-search-tree/timl/tree.js: -------------------------------------------------------------------------------- 1 | /* Creation/modification */ 2 | const newTree = () => ({ value: null, left: null, right: null }); 3 | 4 | const isEmpty = tree => tree.value === null; 5 | 6 | const insert = (tree, value) => { 7 | if (isEmpty(tree)) { 8 | tree.value = value; 9 | tree.left = newTree(); 10 | tree.right = newTree(); 11 | } else { 12 | insert(value <= tree.value ? tree.left : tree.right, value); 13 | } 14 | }; 15 | 16 | const _assign = (t1, t2) => { t1.value = t2.value; t1.left = t2.left, t1.right = t2.right; }; 17 | 18 | const _removeRightmost = (node) => { 19 | if (isEmpty(node.right)) { 20 | _assign(node, node.left); // Hoist left to here. 21 | } else { 22 | _removeRightmost(node.right); 23 | } 24 | }; 25 | 26 | const remove = (tree, value) => { 27 | if (!isEmpty(tree)) { 28 | if (tree.value === value) { 29 | if (tree.left.value === null && tree.right.value === null) { // No children 30 | _assign(tree, newTree()); // Reset to null, nothing else to do. 31 | } else if (tree.left.value !== null && tree.right.value !== null) { // Two children 32 | tree.value = biggest(tree.left); // Replace with biggest predecessor 33 | if (tree.left.right.value === null) { // If it didn't have a right child 34 | _assign(tree.left, tree.left.left); // then simply hoist. 35 | } else { 36 | _removeRightmost(tree.left.right); 37 | } 38 | } else { // One child - hoist child into place here. 39 | _assign(tree, tree.left.value === null ? tree.right : tree.left); 40 | } 41 | } else { 42 | remove(value <= tree.value ? tree.left : tree.right, value); 43 | } 44 | } 45 | }; 46 | 47 | /* Query */ 48 | const find = (tree, value) => !isEmpty(tree) && (tree.value === value || find(value <= tree.value ? tree.left : tree.right, value)); 49 | 50 | const _depth = (tree, value) => tree.value === value ? 0 : _depth(value <= tree.value ? tree.left : tree.right, value) + 1; 51 | 52 | const depth = (tree, value) => find(tree, value) ? _depth(tree, value) : -1; 53 | 54 | const height = (tree) => isEmpty(tree) ? 0 : Math.max(height(tree.left), height(tree.right)) + 1; 55 | 56 | const count = (tree) => isEmpty(tree) ? 0 : count(tree.left) + count(tree.right) + 1; 57 | 58 | const balanced = (tree) => isEmpty(tree) || (balanced(tree.left) && balanced(tree.right) && Math.abs(height(tree.left) - height(tree.right)) <= 1); 59 | 60 | const biggest = (tree) => isEmpty(tree.right) ? tree.value : biggest(tree.right); 61 | 62 | const smallest = (tree) => isEmpty(tree.left) ? tree.value : smallest(tree.left); 63 | 64 | /* Traversal */ 65 | const inOrder = (tree) => isEmpty(tree) ? [] : [...inOrder(tree.left), tree.value, ...inOrder(tree.right)]; 66 | const preOrder = (tree) => isEmpty(tree) ? [] : [tree.value, ...preOrder(tree.left), ...preOrder(tree.right)]; 67 | const postOrder = (tree) => isEmpty(tree) ? [] : [...postOrder(tree.left), ...postOrder(tree.right), tree.value]; 68 | 69 | const _bf = (q) => q.length ? [q[0].value, ..._bf(q.slice(1).concat([q[0].left, q[0].right].filter(t => t.value !== null)))] : []; 70 | 71 | const breadthFirst = (tree) => isEmpty(tree) ? [] : _bf([tree]); 72 | 73 | module.exports = { 74 | newTree, 75 | insert, 76 | remove, 77 | 78 | find, 79 | depth, 80 | height, 81 | count, 82 | balanced, 83 | biggest, 84 | smallest, 85 | 86 | inOrder, 87 | preOrder, 88 | postOrder, 89 | breadthFirst, 90 | }; 91 | -------------------------------------------------------------------------------- /201808-coup/MikeG/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | const GAME_STAGE_EARLY = 0; 6 | const GAME_STAGE_MID = 1; 7 | const GAME_STAGE_LATE = 2; 8 | 9 | let gameTurn = 0; 10 | 11 | class BOT { 12 | // Really basic strat is based on early / late game assumptions (Fyi I haven't played COUP yet...) 13 | getGameStageStrategy() { 14 | let gameStrategy; 15 | // Early play the Duke to get the coins 16 | if (gameTurn < 2) { 17 | gameStrategy = { 18 | bluff: 0.5, 19 | challenge: 0.5, 20 | gameStage: GAME_STAGE_EARLY, 21 | action: 'taking-3', 22 | }; 23 | // Start tighting up mid game 24 | } else if (gameTurn < 4) { 25 | gameStrategy = { 26 | bluff: 0.3, 27 | challenge: 0.7, 28 | gameStage: GAME_STAGE_MID, 29 | action: 'taking-1', 30 | }; 31 | // Late game TODO: What is the COUP late game? 32 | } else { 33 | gameStrategy = { 34 | bluff: 0.2, 35 | challenge: 0.8, 36 | gameStage: GAME_STAGE_EARLY, 37 | action: 'taking-1', 38 | }; 39 | } 40 | return this.getPlayerStrategy(gameStrategy); 41 | } 42 | 43 | // Apply any player strategy mod based on history / for the trolling! 44 | getPlayerStrategy(gameStrategy) { 45 | // Random challenge / bluff. TODO implement actual player / history strats 46 | gameStrategy.bluff = Math.random(); 47 | gameStrategy.challenge = Math.random(); 48 | return gameStrategy; 49 | } 50 | 51 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 52 | let action = ACTIONS()[Math.floor(Math.random() * ACTIONS().length)]; 53 | const against = 54 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 55 | ++gameTurn; 56 | // TODO: Understand the end game 57 | if (myCoins > 10) { 58 | action = 'couping'; 59 | } else { 60 | action = this.getGameStageStrategy().action; 61 | } 62 | 63 | return { 64 | action, 65 | against, 66 | }; 67 | } 68 | 69 | OnChallengeActionRound({ 70 | history, 71 | myCards, 72 | myCoins, 73 | otherPlayers, 74 | discardedCards, 75 | action, 76 | byWhom, 77 | toWhom, 78 | }) { 79 | return [true, false][Math.floor(this.getGameStageStrategy().challenge * 2)]; 80 | } 81 | 82 | OnCounterAction({ 83 | history, 84 | myCards, 85 | myCoins, 86 | otherPlayers, 87 | discardedCards, 88 | action, 89 | byWhom, 90 | }) { 91 | if (action === 'assassination') { 92 | return [false, 'contessa'][ 93 | Math.floor(this.getGameStageStrategy().bluff * 2) 94 | ]; 95 | } else if (action === 'stealing') { 96 | return [false, 'ambassador', 'captain'][ 97 | Math.floor(this.getGameStageStrategy().bluff * 3) 98 | ]; 99 | } 100 | } 101 | 102 | OnCounterActionRound({ 103 | history, 104 | myCards, 105 | myCoins, 106 | otherPlayers, 107 | discardedCards, 108 | action, 109 | byWhom, 110 | toWhom, 111 | card, 112 | }) { 113 | return [true, false][Math.floor(this.getGameStageStrategy().challenge * 2)]; 114 | } 115 | 116 | OnSwappingCards({ 117 | history, 118 | myCards, 119 | myCoins, 120 | otherPlayers, 121 | discardedCards, 122 | newCards, 123 | }) { 124 | return newCards; 125 | } 126 | 127 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 128 | return myCards[0]; 129 | } 130 | } 131 | 132 | module.exports = exports = BOT; 133 | -------------------------------------------------------------------------------- /201810-maze/SimonBot/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class BOT { 4 | constructor({ size, start, end }) { 5 | this.bannedAction = '' // bannedAction contains a nextAction we know we shouldn't take 6 | } 7 | 8 | Move({ MAP }) { 9 | 10 | // TODO - Improvements: 11 | // 1. Determine preferredActions automatically - it's currently hard coded 12 | // 2. The edge case fix will only work for dead ends that are no more than 2 levels deep 13 | 14 | // We simply move right and down towards the goal 15 | // TODO - we can use start, end in constructor to determine our prefferedActions 16 | const prefferedActions = ['right', 'down'].filter(action => action !== this.bannedAction); 17 | let nextAction = prefferedActions[Math.floor(Math.random() * prefferedActions.length)] 18 | 19 | /** 20 | * We don't want to waste moves by trying to move into blockages 21 | * Calculate whether any of our potenital next actions are blocked 22 | * 23 | * @param {Array} map - Array representation of the map surroundings 24 | * 25 | * @return {Object} - false indicates that nextAction is blocked { up: true, right: true, down: false, left: true } 26 | * 27 | */ 28 | const determineSurroundings = map => { 29 | // Our current location is the center of the map 30 | const currentLocation = Math.floor(MAP.length / 2); 31 | 32 | return { 33 | up: map[currentLocation - 1][currentLocation], 34 | right: map[currentLocation][currentLocation + 1], 35 | down: map[currentLocation + 1][currentLocation], 36 | left: map[currentLocation][currentLocation - 1] 37 | } 38 | } 39 | 40 | const canMove = determineSurroundings(MAP); 41 | 42 | if(!canMove.right && canMove.down) nextAction = this.bannedAction === 'down' ? 'up' : 'down' 43 | if(!canMove.down && canMove.right) nextAction = this.bannedAction === 'right' ? 'left' : 'right' 44 | 45 | // EDGE CASE 46 | // A nasty edge case of the simple 'just move down and right' approach is that we can get stuck in an inifinte loop for some geometrys: 47 | // ░ ▓ ▓ ░ ░ 48 | // ░ Φ ░ ▓ ░ 49 | // ░ ▓ ▓ ░ ░ 50 | // ░ ░ ░ ▓ ░ 51 | // ░ ░ ░ ▓ ░ 52 | // Step 1. In the above example, the player will choose to move right because it cannot move down. 53 | 54 | // ░ ▓ ▓ ░ ░ 55 | // ░ ░ Φ ▓ ░ 56 | // ░ ▓ ▓ ░ ░ 57 | // ░ ░ ░ ▓ ░ 58 | // ░ ░ ░ ▓ ░ 59 | // Step 2. Now the player will choose to move back because it cannot move any other way. Thus returning to previous position 60 | 61 | // ░ ▓ ▓ ░ ░ 62 | // ░ Φ ░ ▓ ░ 63 | // ░ ▓ ▓ ░ ░ 64 | // ░ ░ ░ ▓ ░ 65 | // ░ ░ ░ ▓ ░ 66 | // Step 3. The player will choose to move right because... Uh oh - we are stuck in an infinite loop 67 | 68 | // To remedy this, when Step 2 occurs, we set a reminder to ban repeating the action on our next step 69 | // This will break down for deeper dead end geometry - but it's a simple solution for now 70 | if((!canMove.right || this.bannedAction === 'right') && !canMove.down && canMove.up) { 71 | // we got stuck, lets remember not to return to this position 72 | this.bannedAction = 'down'; 73 | nextAction = 'up' 74 | } 75 | else if(!canMove.right && !canMove.down && !canMove.up) { 76 | // we got stuck, lets remember not to return to this position 77 | this.bannedAction = 'right'; 78 | nextAction = 'left' 79 | } else { 80 | this.bannedAction = '' 81 | } 82 | 83 | return nextAction; 84 | } 85 | } 86 | 87 | module.exports = exports = BOT; 88 | -------------------------------------------------------------------------------- /201808-coup/TiciA/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | const target = (players) => 6 | players 7 | .map((p) => [p.coins * p.cards, p]) 8 | .sort((a, b) => b[0] - a[0]) 9 | .map((x) => x[1]); 10 | 11 | const sortCards = (cards) => { 12 | const order = { 13 | duke: 0, 14 | captain: 1, 15 | contessa: 2, 16 | ambassador: 3, 17 | assassin: 4, 18 | }; 19 | const inverseOrder = Object.entries(order).reduce( 20 | (o, [k, v]) => ({ ...o, [v]: k }), 21 | {} 22 | ); 23 | return cards 24 | .map((c) => order[c]) 25 | .sort() 26 | .map((x) => inverseOrder[x]); 27 | }; 28 | class BOT { 29 | constructor() { 30 | // this.ROUND = 0; 31 | } 32 | 33 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 34 | let action = ['taking-1', 'foreign-aid'][Math.floor(Math.random() * 2)]; 35 | let against = 36 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 37 | let enemy = target(otherPlayers); 38 | 39 | // this.ROUND++; 40 | 41 | if (myCoins > 6) { 42 | action = 'couping'; 43 | } else if (myCards.includes('duke')) { 44 | action = 'taking-3'; 45 | } else if (myCards.includes('assassin') && myCoins >= 3) { 46 | action = 'assassination'; 47 | against = enemy[0].name; 48 | } else if (myCards.includes('captain')) { 49 | action = 'stealing'; 50 | against = enemy[0].name; 51 | } else if (myCards.includes('ambassador')) { 52 | action = 'swapping'; 53 | } else { 54 | action = 'taking-1'; 55 | } 56 | 57 | return { 58 | action, 59 | against, 60 | }; 61 | } 62 | 63 | OnChallengeActionRound({ 64 | history, 65 | myCards, 66 | myCoins, 67 | otherPlayers, 68 | discardedCards, 69 | action, 70 | byWhom, 71 | toWhom, 72 | }) { 73 | if (this.ROUND <= 2) { 74 | if (action === 'taking-3') { 75 | return false; 76 | } 77 | } 78 | return [true, false][Math.floor(Math.random() * 2)]; 79 | } 80 | 81 | OnCounterAction({ 82 | history, 83 | myCards, 84 | myCoins, 85 | otherPlayers, 86 | discardedCards, 87 | action, 88 | byWhom, 89 | }) { 90 | if (myCards.includes('ambassador') && action === 'stealing') { 91 | return 'ambassador'; 92 | } else if (myCards.includes('captain') && action === 'stealing') { 93 | return 'captain'; 94 | } else if (myCards.includes('contessa') && action === 'assassination') { 95 | return 'contessa'; 96 | } else if (myCards.includes('duke') && action === 'foreign-aid') { 97 | return 'duke'; 98 | } else if (myCards.length === 1 && action === 'assassination') { 99 | return 'contessa'; 100 | } else { 101 | return false; 102 | } 103 | } 104 | 105 | OnCounterActionRound({ 106 | history, 107 | myCards, 108 | myCoins, 109 | otherPlayers, 110 | discardedCards, 111 | action, 112 | byWhom, 113 | toWhom, 114 | card, 115 | }) { 116 | return [true, false, false][Math.floor(Math.random() * 3)]; 117 | } 118 | 119 | OnSwappingCards({ 120 | history, 121 | myCards, 122 | myCoins, 123 | otherPlayers, 124 | discardedCards, 125 | newCards, 126 | }) { 127 | // Pick the best two non-identical cards 128 | const sorted = sortCards([...myCards, ...newCards]); 129 | const first = sorted[0]; 130 | return myCards.length === 1 131 | ? [first] 132 | : [first, sorted.find((c) => c !== first) || first]; 133 | } 134 | 135 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 136 | return sortCards([...myCards]).slice(-1)[0]; 137 | } 138 | } 139 | 140 | module.exports = exports = BOT; 141 | -------------------------------------------------------------------------------- /201810-maze/noviny/stolen-a-star.js: -------------------------------------------------------------------------------- 1 | var astar = { 2 | init: function(grid) { 3 | for(var x = ; x < grid.length; x++) { 4 | for(var y = ; y < grid[x].length; y++) { 5 | grid[x][y].f = ; 6 | grid[x][y].g = ; 7 | grid[x][y].h = ; 8 | grid[x][y].debug = ""; 9 | grid[x][y].parent = null; 10 | } 11 | } 12 | }, 13 | search: function(grid, start, end) { 14 | astar.init(grid); 15 | 16 | var openList = []; 17 | var closedList = []; 18 | openList.push(start); 19 | 20 | while(openList.length > ) { 21 | 22 | // Grab the lowest f(x) to process next 23 | var lowInd = ; 24 | for(var i=; iG: " + neighbor.g + "
H: " + neighbor.h; 78 | } 79 | } 80 | } 81 | 82 | // No result was found -- empty array signifies failure to find path 83 | return []; 84 | }, 85 | heuristic: function(pos0, pos1) { 86 | // This is the Manhattan distance 87 | var d1 = Math.abs (pos1.x - pos0.x); 88 | var d2 = Math.abs (pos1.y - pos0.y); 89 | return d1 + d2; 90 | }, 91 | neighbors: function(grid, node) { 92 | var ret = []; 93 | var x = node.pos.x; 94 | var y = node.pos.y; 95 | 96 | if(grid[x-1] && grid[x-1][y]) { 97 | ret.push(grid[x-1][y]); 98 | } 99 | if(grid[x+1] && grid[x+1][y]) { 100 | ret.push(grid[x+1][y]); 101 | } 102 | if(grid[x][y-1] && grid[x][y-1]) { 103 | ret.push(grid[x][y-1]); 104 | } 105 | if(grid[x][y+1] && grid[x][y+1]) { 106 | ret.push(grid[x][y+1]); 107 | } 108 | return ret; 109 | } 110 | }; -------------------------------------------------------------------------------- /201808-coup/TuanH/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | const ME = 'TuanH'; 6 | 7 | const CARD_ORDER = ['duke', 'captain', 'ambassador', 'contessa', 'assassin']; 8 | 9 | const PLAYER_ORDER = [ 10 | 'TimL', 11 | 'JohnM', 12 | 'DomW', 13 | 'MikeH', 14 | 'AbbasA', 15 | 'TomW', 16 | 'JossM', 17 | 'BenC', 18 | 'NathS', 19 | 'TiciA', 20 | 'SanjiyaD', 21 | 'BorisB', 22 | 'CharlesL', 23 | 'JedW', 24 | 'JessT', 25 | 'KevinY', 26 | 'LaurenA', 27 | 'MalB', 28 | 'MikeG', 29 | ]; 30 | 31 | const count = (card, cards) => cards.filter((c) => c === card).length; 32 | 33 | const cardFor = (action) => 34 | ({ 35 | 'taking-3': 'duke', 36 | assassination: 'assassin', 37 | stealing: 'captain', 38 | swapping: 'ambassador', 39 | }[action]); 40 | 41 | const blockersFor = (action) => 42 | ({ 43 | assassination: ['contessa'], 44 | stealing: ['captain', 'ambassador'], 45 | 'foreign-aid': ['duke'], 46 | }[action] || []); 47 | 48 | const findAgainstPlayers = (players) => 49 | players 50 | .map((player) => [player.coins * player.cards, player]) 51 | .sort((a, b) => b[0] - a[0]) 52 | .map((item) => item[1]); 53 | 54 | const getPlayer = (name, players) => 55 | players.find((player) => player.name === name); 56 | 57 | class BOT { 58 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 59 | const knownCards = myCards.concat(discardedCards); 60 | const againstPlayer = findAgainstPlayers(otherPlayers)[0]; 61 | 62 | let action = 'taking-1'; 63 | 64 | if (myCoins > 6) { 65 | action = 'couping'; 66 | } else if (myCards.includes(cardFor('assassination')) && myCoins >= 3) { 67 | action = 'assassination'; 68 | } else if (myCards.includes(cardFor('taking-3'))) { 69 | action = 'taking-3'; 70 | } else if (myCards[0] === myCards[1]) { 71 | action = 'swapping'; 72 | } else if (myCards.includes(cardFor('swapping'))) { 73 | action = 'swapping'; 74 | } else if ( 75 | myCards.includes(cardFor('stealing')) && 76 | againstPlayer.coins >= 2 77 | ) { 78 | action = 'stealing'; 79 | } else if ( 80 | blockersFor('foreign-aid').every((card) => count(card, knownCards) === 3) 81 | ) { 82 | action = 'foreign-aid'; 83 | } else { 84 | action = 'taking-1'; 85 | } 86 | 87 | return { 88 | action, 89 | against: againstPlayer.name, 90 | }; 91 | } 92 | 93 | OnChallengeActionRound({ 94 | history, 95 | myCards, 96 | myCoins, 97 | otherPlayers, 98 | discardedCards, 99 | action, 100 | byWhom, 101 | toWhom, 102 | }) { 103 | const knownCards = myCards.concat(discardedCards); 104 | const shouldCallOut = 105 | knownCards.filter((card) => card === cardFor(action)).length === 3; 106 | return shouldCallOut; 107 | 108 | if (action === 'assassination' && toWhom === ME && myCards.length === 1) { 109 | return true; 110 | } 111 | 112 | if ( 113 | action === 'stealing' && 114 | toWhom === ME && 115 | blockersFor('stealing').find((card) => myCards.includes(card)) 116 | ) { 117 | return true; 118 | } 119 | } 120 | 121 | OnCounterAction({ 122 | history, 123 | myCards, 124 | myCoins, 125 | otherPlayers, 126 | discardedCards, 127 | action, 128 | byWhom, 129 | }) { 130 | const haveBlockers = blockersFor(action).find((card) => 131 | myCards.includes(card) 132 | ); 133 | if (haveBlockers) { 134 | return haveBlockers; 135 | } 136 | } 137 | 138 | OnCounterActionRound({ 139 | history, 140 | myCards, 141 | myCoins, 142 | otherPlayers, 143 | discardedCards, 144 | action, 145 | byWhom, 146 | toWhom, 147 | card, 148 | }) { 149 | const knownCards = myCards.concat(discardedCards); 150 | const shouldCallOut = knownCards.filter((c) => c === card).length === 3; 151 | return shouldCallOut; 152 | 153 | if (action === 'assassination' && myCards.length === 1) { 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | 160 | OnSwappingCards({ 161 | history, 162 | myCards, 163 | myCoins, 164 | otherPlayers, 165 | discardedCards, 166 | newCards, 167 | }) { 168 | const allCards = [...myCards, ...newCards]; 169 | const considerationCards = allCards.filter((card) => card !== 'ambassador'); 170 | const reorderCard = CARD_ORDER.filter((card) => 171 | considerationCards.includes(card) 172 | ); 173 | return reorderCard.slice(0, myCards.length); 174 | } 175 | 176 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 177 | if (myCards.length === 1) { 178 | return myCards[0]; 179 | } 180 | 181 | if (CARD_ORDER.indexOf(myCards[0]) < CARD_ORDER.indexOf(myCards[1])) { 182 | return myCards[0]; 183 | } else { 184 | return myCards[1]; 185 | } 186 | } 187 | } 188 | 189 | module.exports = exports = BOT; 190 | -------------------------------------------------------------------------------- /201810-maze/README.md: -------------------------------------------------------------------------------- 1 | October 2018 challenge 2 | ====================== 3 | 4 | This months challenge consists of you writing a bot to solve a maze. 5 | We will have three levels, level > 1 will be announced soon. 6 | 7 | ![Maze](https://raw.githubusercontent.com/Thinkmill/code-challenge/master/201810-maze/maze.gif) 8 | 9 | Level 1: **DUE 31st Oct** 10 | Level 2: **DUE 14th Nov** 11 | Level 3: **DUE 23rd Jan** 12 | 13 | ## RULEZ 14 | 15 | 1. Node only 16 | 1. No dependencies 17 | 1. No changes to engine 18 | 1. Put your bot into a folder and name folder appropriately 19 | 1. No data sharing between games 20 | 1. No Internet 21 | 1. No js prototype changing 22 | 1. Your code has to stay inside your bots folder 23 | 1. Do not output to `stdout` 24 | 1. At the beginning of each round, add your bot to a new folder then open a PR to this repo (we only merge on the day the round begins) 25 | 26 | ## Levels 27 | 28 | - **LEVEL 1** 29 | Solve the maze by going to the green cross. 30 | You have 3000 steps to solve the first level. 31 | Due 31st Oct 32 | - **LEVEL 2** 33 | Solve both levels with the same bot. 34 | You have 3000 steps to solve the first level. 35 | You have 6000 steps to solve the first level. 36 | Due 14th Nov 37 | - **LEVEL 3** 38 | Solve all three levels with the same bot. 39 | You have 3000 steps to solve the first level. 40 | You have 6000 steps to solve the first level. 41 | You have 9120 steps to solve the last level. 42 | Due 23rd Jan 43 | 44 | ## How to run the game? 45 | 46 | The game comes with a small "example" bot that just randomizes it's movements. 47 | 48 | To run the game for a bot `cd` into the challenge `201810-maze` folder. 49 | To play the game run: 50 | 51 | ```sh 52 | yarn play example/index.js --level 1 53 | ``` 54 | 55 | _(💡 Tip: `--level` & `--speed` are optional. See [CLI Options](#cli-options) below.)_ 56 | 57 | ```sh 58 | . 59 | ├── bot1 60 | │   └── index.js 61 | ├── bot2 62 | │   └── index.js 63 | ├── bot3 64 | │   └── index.js 65 | │ 66 | ├── README.md 67 | ├── constants.js 68 | ├── helper.js 69 | ├── index.js 70 | └── test.js 71 | ``` 72 | 73 | So in the example above to run the game for bot2 you must run: 74 | 75 | ```sh 76 | yarn play bot2/index.js 77 | ``` 78 | 79 | Once the game runs you can use the key `q` to quit the game any time. 80 | You can also use the arrow functions `←` and `→` to step through each step your bot has taken. 81 | Go back in history and analyses where your bot went wrong etc. 82 | 83 | ### CLI Options 84 | 85 | ``` 86 | --level|-l Set the level to run (Default: 1) 87 | --speed|-s Set the time in milliseconds between each step (Default: 500) 88 | ``` 89 | 90 | ## How do I build a bot? 91 | 92 | - Create a folder in the root (next to the example bot) 93 | - Include a javascript file that exports below class 94 | 95 | ### Class to export 96 | 97 | The example bot is structured like this: 98 | 99 | ```js 100 | class BOT { 101 | constructor({ size, start, end }) {} 102 | 103 | Move({ MAP }) { 104 | const actions = ['up', 'right', 'down', 'left']; 105 | return actions[ Math.floor( Math.random() * actions.length ) ]; 106 | } 107 | } 108 | 109 | module.exports = exports = BOT; 110 | ``` 111 | 112 | The class you have to export from your bot needs to include the below method: 113 | 114 | - `Move` 115 | - Called when it is your turn to decide where to go 116 | - parameters: `{ MAP }` 117 | - return: `'up' | 'right' | 'down' | 'left'` 118 | - `constructor` of your class 119 | - parameters: `size`, `start`, `end` 120 | 121 | ### The parameters 122 | 123 | `MAP` is an array of arrays and tells you in a grid of 5x5 around you where blocks are. 124 | 125 | `false` = blocks 126 | `true` = no blocks 127 | 128 | An example would be: 129 | 130 | ```js 131 | MAP = [ 132 | [ true, false, true, true, true ], 133 | [ true, true, true, true, false ], 134 | [ true, true, false, true, true ], 135 | [ false, true, true, false, true ], 136 | [ true, true, true, false, true ], 137 | ]; 138 | ``` 139 | This would visualize as: 140 | 141 | ```sh 142 | ░ ▓ ░ ░ ░ 143 | ░ ░ ░ ░ ▓ 144 | ░ ░ ▓ ░ ░ 145 | ▓ ░ ░ ▓ ░ 146 | ░ ░ ░ ▓ ░ 147 | ``` 148 | 149 | Your constructor of your BOT will also get three parameters: 150 | 151 | - `size` = `{ width: , height: }` - The size of your board 152 | - `start` = `[ , ]` - The position you're starting at _(The first number is the row, the second the column)_ 153 | - `end` = `[ , ]` - The position you want to go to _(The first number is the row, the second the column)_ 154 | 155 | ## How does the engine work? 156 | 157 | The engine will run the entire game and populate a history array with all the steps your bot has taken. 158 | Only then will it start playing it back to you. 159 | This gives you the power to go through the history at your own pace and even go back in time. 160 | That's the reason why the outcome of the game is displayed on the top right away. 161 | -------------------------------------------------------------------------------- /201809-binary-search-tree/timl/while.js: -------------------------------------------------------------------------------- 1 | /* Creation/modification */ 2 | const newTree = () => ({ value: null, left: null, right: null }); 3 | 4 | const isEmpty = tree => tree.value === null; 5 | 6 | const _assign = (t1, t2) => { t1.value = t2.value; t1.left = t2.left, t1.right = t2.right; }; 7 | 8 | const insert = (tree, value) => { 9 | while (!isEmpty(tree)) { 10 | tree = value <= tree.value ? tree.left : tree.right; 11 | } 12 | _assign(tree, { value, left: newTree(), right: newTree() }); 13 | }; 14 | 15 | const _removeRightmost = (node) => { 16 | while (!isEmpty(node.right)) { 17 | node = node.right; 18 | } 19 | _assign(node, node.left); // Hoist left to here. 20 | }; 21 | 22 | const remove = (tree, value) => { 23 | while (!isEmpty(tree) && tree.value !== value) { 24 | tree = value <= tree.value ? tree.left : tree.right; 25 | } 26 | if (!isEmpty(tree)) { 27 | if (tree.left.value === null && tree.right.value === null) { // No children 28 | _assign(tree, newTree()); // Reset to null, nothing else to do. 29 | } else if (tree.left.value !== null && tree.right.value !== null) { // Two children 30 | tree.value = biggest(tree.left); // Replace with biggest predecessor 31 | if (tree.left.right.value === null) { // If it didn't have a right child 32 | _assign(tree.left, tree.left.left); // then simply hoist. 33 | } else { 34 | _removeRightmost(tree.left.right); 35 | } 36 | } else { // One child - hoist child into place here. 37 | _assign(tree, tree.left.value === null ? tree.right : tree.left); 38 | } 39 | } 40 | }; 41 | 42 | /* Query */ 43 | const find = (tree, value) => { 44 | while (!isEmpty(tree) && tree.value !== value) { 45 | tree = value <= tree.value ? tree.left : tree.right; 46 | } 47 | return tree.value === value; 48 | }; 49 | 50 | const depth = (tree, value) => { 51 | let d = 0; 52 | while (!isEmpty(tree) && tree.value !== value) { 53 | d += 1; 54 | tree = value <= tree.value ? tree.left : tree.right; 55 | } 56 | return isEmpty(tree) ? -1 : d; 57 | }; 58 | 59 | const height = (tree) => { 60 | let h = 0; 61 | let nextLevel = [tree]; 62 | while (nextLevel.length > 0) { 63 | const thisLevel = nextLevel.filter(t => !isEmpty(t)); 64 | nextLevel = []; 65 | if (thisLevel.length) h += 1; 66 | while (thisLevel.length) { 67 | const subtree = thisLevel.pop(); 68 | nextLevel.push(subtree.left, subtree.right); 69 | } 70 | } 71 | return h; 72 | }; 73 | 74 | const count = (tree) => { 75 | let c = 0; 76 | let q = [tree]; 77 | while (q.length > 0) { 78 | const subtree = q.shift(); 79 | if (!isEmpty(subtree)) { 80 | c += 1; 81 | q.push(subtree.left, subtree.right); 82 | } 83 | } 84 | return c; 85 | }; 86 | 87 | const balanced = (tree) => isEmpty(tree) || (balanced(tree.left) && balanced(tree.right) && Math.abs(height(tree.left) - height(tree.right)) <= 1); 88 | 89 | const biggest = (tree) => { 90 | while (!isEmpty(tree.right)) { 91 | tree = tree.right; 92 | } 93 | return tree.value; 94 | }; 95 | 96 | const smallest = (tree) => { 97 | while (!isEmpty(tree.left)) { 98 | tree = tree.left; 99 | } 100 | return tree.value; 101 | }; 102 | 103 | /* Traversal */ 104 | const inOrder = (tree) => { 105 | const result = []; 106 | let backTrack = false; 107 | const stack = [tree]; 108 | let subtree = tree; 109 | while (stack.length) { 110 | if (backTrack) { 111 | subtree = stack.pop(); 112 | if (!isEmpty(subtree)) { 113 | result.push(subtree.value); 114 | 115 | stack.push(subtree.right); 116 | subtree = subtree.right; 117 | backTrack = false; 118 | } 119 | } else { 120 | if (isEmpty(subtree)) { 121 | backTrack = true; 122 | } else { 123 | stack.push(subtree.left); 124 | subtree = subtree.left; 125 | } 126 | } 127 | } 128 | return result; 129 | }; 130 | 131 | const preOrder = (tree) => { 132 | const result = []; 133 | let backTrack = false; 134 | const stack = [tree]; 135 | let subtree = tree; 136 | while (stack.length) { 137 | if (backTrack) { 138 | subtree = stack.pop(); 139 | if (!isEmpty(subtree)) { 140 | stack.push(subtree.right); 141 | subtree = subtree.right; 142 | backTrack = false; 143 | } 144 | } else { 145 | if (isEmpty(subtree)) { 146 | backTrack = true; 147 | } else { 148 | result.push(subtree.value); 149 | 150 | stack.push(subtree.left); 151 | subtree = subtree.left; 152 | } 153 | } 154 | } 155 | return result; 156 | }; 157 | 158 | const postOrder = (tree) => isEmpty(tree) ? [] : [...postOrder(tree.left), ...postOrder(tree.right), tree.value]; 159 | 160 | 161 | const breadthFirst = (tree) => { 162 | const result = []; 163 | const q = [tree]; 164 | while (q.length) { 165 | const subtree = q.shift(); 166 | if (!isEmpty(subtree)) { 167 | result.push(subtree.value); 168 | q.push(subtree.left, subtree.right); 169 | } 170 | } 171 | return result; 172 | }; 173 | 174 | module.exports = { 175 | newTree, 176 | insert, 177 | remove, 178 | 179 | find, 180 | depth, 181 | height, 182 | count, 183 | balanced, 184 | biggest, 185 | smallest, 186 | 187 | inOrder, 188 | preOrder, 189 | postOrder, 190 | breadthFirst, 191 | }; 192 | -------------------------------------------------------------------------------- /201808-coup/JossM/logic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { unique, toArray } = require('./utils'); 4 | const { 5 | getActionFrom, 6 | getBlockersFor, 7 | getCanditate, 8 | getCardFor, 9 | getCount, 10 | getCounterFrom, 11 | getMostInfluential, 12 | getPassiveAction, 13 | } = require('./helpers'); 14 | 15 | function aggregatePlayerData({ 16 | discardedCards, 17 | history, 18 | myCards, 19 | otherPlayers, 20 | }) { 21 | let knownCards = [...myCards, ...discardedCards]; 22 | const init = () => ({ actions: [], counters: [] }); 23 | 24 | let historicData = history.reduce((obj, event) => { 25 | let presumptionMade = false; 26 | 27 | // log actions for a given a player 28 | if (event.type === 'action' && event.from) { 29 | if (!obj[event.from]) obj[event.from] = init(); 30 | obj[event.from].actions.push(event.action); 31 | 32 | // could assume foreign aid block, but not until their bot actually does it 33 | // if (event.action === 'taking-3') { 34 | // presumptionMade = true; 35 | // obj[event.from].counters.push('foreign-aid'); 36 | // } 37 | if (event.action === 'swapping' || event.action === 'stealing') { 38 | presumptionMade = true; 39 | obj[event.from].counters.push('stealing'); 40 | } 41 | } 42 | // log counter actions for a given a player 43 | if (event.type === 'counter-action' && event.counterer) { 44 | if (!obj[event.counterer]) obj[event.counterer] = init(); 45 | obj[event.counterer].counters.push(event.action); 46 | } 47 | 48 | // remove erroneous actions/counters when player bluffing 49 | if (event.type === 'challenge-round' && event.challengee) { 50 | if (!obj[event.challengee]) obj[event.challengee] = init(); 51 | if (event.lying) { 52 | obj[event.challengee].actions.pop(); 53 | if (presumptionMade) obj[event.challengee].counters.pop(); 54 | } 55 | } 56 | if (event.type === 'counter-round' && event.challengee) { 57 | if (!obj[event.challengee]) obj[event.challengee] = init(); 58 | if (event.lying) obj[event.challengee].counters.pop(); 59 | } 60 | 61 | // filter out actions/counters when a player loses a card 62 | if (event.type === 'lost-card' && event.player) { 63 | if (!obj[event.player]) obj[event.player] = init(); 64 | obj[event.player].actions = obj[event.player].actions.filter( 65 | (x) => x !== getActionFrom(event.lost) 66 | ); 67 | obj[event.player].counters = obj[event.player].counters.filter( 68 | (x) => x !== getCounterFrom(event.lost) 69 | ); 70 | } 71 | 72 | return obj; 73 | }, {}); 74 | 75 | // remove own and deceased bots | reintroduce coins & cards 76 | let cleanData = {}; 77 | otherPlayers.forEach((player) => { 78 | cleanData[player.name] = { ...historicData[player.name], ...player }; 79 | }); 80 | 81 | // wash away actions/counters that appear 3x in known cards 82 | return toArray(cleanData); 83 | } 84 | 85 | function makeAction(args) { 86 | const { history, myCards, myCoins, otherPlayers, turnCount } = args; 87 | const playerData = aggregatePlayerData(args); 88 | // console.log('playerData', playerData); 89 | const exchangeCards = { action: 'swapping', against: null }; 90 | const canAssassinate = myCoins >= 3; 91 | const isHeadToHead = otherPlayers.length === 1; 92 | 93 | // you're gonna die -- attempt hail mary 94 | if (isHeadToHead && otherPlayers[0].coins >= 7) { 95 | // you can steal and they won't block 96 | if ( 97 | myCards.includes(getCardFor('stealing')) && 98 | getCanditate('stealing', playerData) 99 | ) { 100 | return { 101 | action: 'stealing', 102 | against: otherPlayers[0].name, 103 | }; 104 | } 105 | 106 | // you can afford to assassinate 107 | if (canAssassinate) { 108 | return { 109 | action: 'assassination', 110 | against: otherPlayers[0].name, 111 | }; 112 | } 113 | } 114 | 115 | // sneaky tax: grab 3 early 116 | // NOTE: too dangerous with random challenge bots... 117 | // if (turnCount === 0) { 118 | // return { action: 'taking-3', against: null }; 119 | // } 120 | 121 | // lay low early in the game when good hand 122 | // NOTE: doesn't seem to change the outcome 123 | // if ( 124 | // turnCount <= 2 && 125 | // (myCards.includes(getCardFor('taking-3')) || 126 | // myCards.includes(getCardFor('stealing'))) 127 | // ) { 128 | // return getPassiveAction(playerData); 129 | // } 130 | 131 | if (myCoins >= 7) { 132 | return { 133 | action: 'couping', 134 | against: getMostInfluential(otherPlayers).name, 135 | }; 136 | } 137 | if (myCards.includes(getCardFor('assassination')) && canAssassinate) { 138 | let candidate = getCanditate('assassination', playerData); 139 | if (candidate) return { action: 'assassination', against: candidate.name }; 140 | } 141 | if (myCards.includes(getCardFor('taking-3'))) { 142 | return { 143 | action: 'taking-3', 144 | against: null, 145 | }; 146 | } 147 | if (myCards.includes(getCardFor('stealing'))) { 148 | let candidate = getCanditate('stealing', playerData); 149 | if (candidate) return { action: 'stealing', against: candidate.name }; 150 | } 151 | if (myCards.includes(getCardFor('swapping'))) { 152 | return exchangeCards; 153 | } 154 | 155 | return getPassiveAction(playerData); 156 | } 157 | 158 | module.exports = exports = { aggregatePlayerData, makeAction }; 159 | -------------------------------------------------------------------------------- /201809-binary-search-tree/noviny/tree.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /*:: 4 | type Leaf = { 5 | value: number, 6 | left: Leaf | null, 7 | right: Leaf | null 8 | }; 9 | 10 | type Tree = { 11 | value: number | null, 12 | left: Leaf | null, 13 | right: Leaf | null 14 | }; 15 | */ 16 | 17 | const newTree = () /*: Tree */ => { 18 | return { 19 | value: null, 20 | left: null, 21 | right: null 22 | }; 23 | }; 24 | 25 | const getPath = (value /*: number */, tree /*: Leaf */) => 26 | value <= tree.value ? tree.left : tree.right; 27 | 28 | const insert = (tree /*: Tree | Leaf | null */, value /*: number */) /*: Leaf */ => { 29 | if (tree === null) return { left: null, right: null, value }; 30 | if (tree.value === null) { 31 | tree.value = value; 32 | } else if (value <= tree.value) { 33 | tree.left = insert(tree.left, value); 34 | } else { 35 | tree.right = insert(tree.right, value); 36 | } 37 | return tree; 38 | }; 39 | 40 | const getRightValueNode = tree => { 41 | if (tree.right) return getRightValueNode(tree.right); 42 | return tree.value; 43 | }; 44 | 45 | const reassign = (tree /*: Tree */, node /*: Leaf */) => { 46 | tree.value = node.value; 47 | tree.left = node.left; 48 | tree.right = node.right; 49 | }; 50 | 51 | const remove = ( 52 | tree /*: Tree | Leaf | null */, 53 | value /*: number */, 54 | parent /*: ?Leaf | Tree */ 55 | ) /*: void */ => { 56 | if (!find(tree, value) || tree === null) return; 57 | if (tree.value !== value) { 58 | let path = getPath(value, tree); 59 | remove(path, value, tree); 60 | } else { 61 | if (parent && tree.left === tree.right) { 62 | let direction = value <= parent.value ? 'left' : 'right'; 63 | parent[direction] = null; 64 | return; 65 | } else { 66 | if (tree.left === tree.right) { 67 | tree.value = null; 68 | return; 69 | } else if (tree.left === null) { 70 | return reassign(tree, tree.right); 71 | } else if (tree.right === null) { 72 | return reassign(tree, tree.left); 73 | } else { 74 | let newVal = getRightValueNode(tree.left); 75 | tree.value = newVal; 76 | return remove(tree.left, newVal, tree); 77 | } 78 | } 79 | } 80 | }; 81 | 82 | const find = (tree /*: Tree | Leaf | null */, value /*: number */) /*: boolean */ => { 83 | if (tree === null) return false; 84 | if (tree.value === null) return false; 85 | if (tree.value === value) return true; 86 | let path = getPath(value, tree); 87 | return find(path, value); 88 | }; 89 | 90 | const depth = (tree /*: Tree | Leaf | null */, value /*: number */) /*: number */ => { 91 | if (tree === null) return -1; 92 | if (tree.value === null) return -1; 93 | if (tree.value === value) return 0; 94 | let path = getPath(value, tree); 95 | let calcDepth = depth(path, value); 96 | return calcDepth === -1 ? calcDepth : calcDepth + 1; 97 | }; 98 | 99 | const height = (tree /*: Tree | Leaf | null */) /*: number */ => { 100 | if (tree === null) return 0; 101 | if (tree.value === null) return 0; 102 | let leftHeight = 1 + height(tree.left); 103 | let rightHeight = 1 + height(tree.right); 104 | return leftHeight > rightHeight ? leftHeight : rightHeight; 105 | }; 106 | 107 | const count = (tree /*: Tree | Leaf | null */) /*: number */ => { 108 | if (tree === null) return 0; 109 | if (tree.value === null) return 0; 110 | return 1 + count(tree.left) + count(tree.right); 111 | }; 112 | 113 | const cheekyIsWithinOne = (v1, v2) => [v1, v1 + 1, v1 - 1].includes(v2); 114 | 115 | const balanced = (tree /*: Tree | Leaf | null */) /*: boolean */ => { 116 | if (tree === null) return true; 117 | if (tree.value === null) return true; 118 | if (tree.left === tree.right) return true; 119 | return ( 120 | balanced(tree.left) && 121 | balanced(tree.right) && 122 | cheekyIsWithinOne(height(tree.left), height(tree.right)) 123 | ); 124 | }; 125 | const biggest = (tree /*: Tree | Leaf */) /*: number | void */ => { 126 | if (tree.value === null) return undefined; 127 | if (tree.right === null) return tree.value; 128 | return biggest(tree.right); 129 | }; 130 | const smallest = (tree /*: Tree | Leaf */) /*: number | void */ => { 131 | if (tree.value === null) return undefined; 132 | if (tree.left === null) return tree.value; 133 | return smallest(tree.left); 134 | }; 135 | const inOrder = (tree /*: Tree | Leaf | null */) /*: number[] */ => { 136 | if (tree === null) return []; 137 | if (tree.value === null) return []; 138 | else return [...inOrder(tree.left), tree.value, ...inOrder(tree.right)]; 139 | }; 140 | const preOrder = (tree /*: Tree | Leaf | null */) /*: number[] */ => { 141 | if (tree === null) return []; 142 | if (tree.value === null) return []; 143 | else return [tree.value, ...preOrder(tree.left), ...preOrder(tree.right)]; 144 | }; 145 | const postOrder = (tree /*: Tree | Leaf | null */) /*: number[] */ => { 146 | if (tree === null) return []; 147 | if (tree.value === null) return []; 148 | else return [...postOrder(tree.left), ...postOrder(tree.right), tree.value]; 149 | }; 150 | 151 | const breadthFirst = (tree /*: Tree | Leaf */) /*: number[] */ => { 152 | if (tree === null) return []; 153 | if (tree.value === null) return []; 154 | let results = []; 155 | let queue = []; 156 | 157 | results.push(tree.value); 158 | queue.push(tree.left, tree.right); 159 | while (queue.length > 0) { 160 | let leaf = queue.shift(); 161 | if (leaf === null) continue; 162 | results.push(leaf.value); 163 | queue.push(leaf.left, leaf.right); 164 | } 165 | 166 | return results; 167 | }; 168 | 169 | module.exports = { 170 | newTree, 171 | insert, 172 | remove, 173 | 174 | find, 175 | depth, 176 | height, 177 | count, 178 | balanced, 179 | biggest, 180 | smallest, 181 | 182 | inOrder, 183 | preOrder, 184 | postOrder, 185 | breadthFirst 186 | }; 187 | -------------------------------------------------------------------------------- /201808-coup/CharlesL/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | const CARDPREFERENCE = [ 5 | 'duke', 6 | 'captain', 7 | 'assassin', 8 | 'contessa', 9 | 'ambassador', 10 | ]; 11 | const PLAYERSWITHBOTS = [ 12 | 'TimL', 13 | 'DomW', 14 | 'JohnM', 15 | 'JossM', 16 | 'AbassA', 17 | 'TuanH', 18 | 'MikeH', 19 | 'TomW', 20 | 'NathS', 21 | 'BenC', 22 | ]; 23 | 24 | let counterHistory = []; 25 | const me = 'CharlesL'; 26 | 27 | const TOPCONTENDERS = ['TimL', 'DomW', 'JossM', 'JohnM']; 28 | class BOT { 29 | constructor() { 30 | this.counterHistory = []; 31 | console.log(DECK()); 32 | this.playerHistory = ALLBOTS().reduce((acc, curr) => { 33 | acc[curr] = []; 34 | return acc; 35 | }, {}); 36 | } 37 | shouldForeignAid(discardedCards) { 38 | const foreignAidBlocks = this.counterHistory.filter( 39 | (event) => event.action === 'foreign-aid' 40 | ); 41 | if (!this.cardsStillInPlay(discardedCards, 'duke')) return true; 42 | return [true, false][Math.floor(Math.random() * 2)]; 43 | } 44 | findAppropriateTarget(players) { 45 | const playerNames = players.map((player) => player.name); 46 | const richestPlayer = players.sort((a, b) => a.coins - b.coins)[0]; 47 | let favoredPlayer; 48 | for (let index = 0; index < TOPCONTENDERS.length; index++) { 49 | if (playerNames.includes(TOPCONTENDERS[index])) { 50 | favoredPlayer = TOPCONTENDERS[index]; 51 | break; 52 | } 53 | } 54 | return favoredPlayer || richestPlayer.name; 55 | } 56 | defineAction(myCards, myCoins, discardedCards) { 57 | let action = 'taking-1'; 58 | if (myCards.includes('ambassador')) { 59 | action = 'swapping'; 60 | } else if (myCards.includes('duke')) { 61 | action = 'taking-3'; 62 | } else { 63 | if (this.shouldForeignAid(discardedCards)) { 64 | action = 'foreign-aid'; 65 | } 66 | } 67 | 68 | if (myCoins >= 3 && myCards.includes('assassin')) { 69 | action = 'assassination'; 70 | } 71 | 72 | if (myCoins >= 7) { 73 | action = 'couping'; 74 | } 75 | return action; 76 | } 77 | 78 | defineAgainst(action, otherPlayers) { 79 | return this.findAppropriateTarget(otherPlayers); 80 | } 81 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 82 | const action = this.defineAction(myCards, myCoins, discardedCards); 83 | const against = this.defineAgainst(action, otherPlayers); 84 | return { 85 | action, 86 | against, 87 | }; 88 | } 89 | 90 | cardsStillInPlay(discardedCards, card) { 91 | if ( 92 | discardedCards.filter((discardedCard) => discardedCard === card) 93 | .length === 4 94 | ) 95 | return false; 96 | return true; 97 | } 98 | 99 | OnChallengeActionRound({ 100 | history, 101 | myCards, 102 | myCoins, 103 | otherPlayers, 104 | discardedCards, 105 | action, 106 | byWhom, 107 | toWhom, 108 | }) { 109 | this.playerHistory[byWhom].push({ action, toWhom }); 110 | if (toWhom === me) { 111 | switch (action) { 112 | case 'stealing': 113 | if (!this.cardsStillInPlay(discardedCards, 'captain')) { 114 | return true; 115 | } 116 | case 'assassination': 117 | if (!this.cardsStillInPlay(discardedCards, 'assassin')) { 118 | return true; 119 | } 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | OnCounterAction({ 126 | history, 127 | myCards, 128 | myCoins, 129 | otherPlayers, 130 | discardedCards, 131 | action, 132 | byWhom, 133 | }) { 134 | if (action === 'assassination') return 'contessa'; 135 | if (action === 'stealing') { 136 | if (myCards.includes('ambassador')) return 'ambassador'; 137 | if (myCards.includes('captain')) return 'captain'; 138 | return ['captain', 'ambassador', false][Math.floor(Math.random() * 3)]; 139 | } 140 | if (action === 'foreign-aid') { 141 | if (myCards.includes('duke')) return 'duke'; 142 | return false; 143 | } 144 | } 145 | 146 | OnCounterActionRound({ 147 | history, 148 | myCards, 149 | myCoins, 150 | otherPlayers, 151 | discardedCards, 152 | action, 153 | byWhom, 154 | toWhom, 155 | card, 156 | }) { 157 | if (toWhom === me) { 158 | switch (action) { 159 | case 'assassination': { 160 | if (!cardsStillInPlay(discardedCards, 'assassin')) { 161 | return true; 162 | } 163 | return false; 164 | } 165 | case 'stealing': { 166 | if ((!cardsStillInPlay(discardedCards), 'captain')) { 167 | return true; 168 | } 169 | return [true, false][Math.floor(Math.random() * 2)]; 170 | } 171 | case 'taking-3': { 172 | if (!cardsStillInPlay(discardedCards, 'duke')) { 173 | return true; 174 | } 175 | return [true, false][Math.floor(Math.random() * 2)]; 176 | } 177 | default: 178 | return [true, false][Math.floor(Math.random() * 2)]; 179 | } 180 | } 181 | return false; 182 | } 183 | 184 | OnSwappingCards({ 185 | history, 186 | myCards, 187 | myCoins, 188 | otherPlayers, 189 | discardedCards, 190 | newCards, 191 | }) { 192 | const cardPool = myCards.slice().concat(newCards); 193 | let newCardCandidate = []; 194 | let index = 0; 195 | while ( 196 | newCardCandidate.length <= myCards.length && 197 | index < CARDPREFERENCE.length 198 | ) { 199 | if (cardPool.includes(CARDPREFERENCE[index])) { 200 | newCardCandidate.push(CARDPREFERENCE[index]); 201 | } 202 | index = index + 1; 203 | } 204 | return newCardCandidate; 205 | } 206 | 207 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 208 | const DISCARDPREFERENCE = CARDPREFERENCE.slice().reverse(); 209 | for (let i = 0; i <= DISCARDPREFERENCE.length; i++) { 210 | if (myCards.includes(DISCARDPREFERENCE[i])) return DISCARDPREFERENCE[i]; 211 | } 212 | } 213 | } 214 | 215 | module.exports = exports = BOT; 216 | -------------------------------------------------------------------------------- /201808-coup/TimL/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let { CARDS } = require('../constants.js'); 4 | 5 | const ME = 'TimL'; 6 | 7 | const count = (card, cards) => cards.filter((c) => c === card).length; 8 | 9 | const cardFor = (action) => 10 | ({ 11 | 'taking-3': 'duke', 12 | assassination: 'assassin', 13 | stealing: 'captain', 14 | swapping: 'ambassador', 15 | }[action]); 16 | 17 | const blockersFor = (action) => 18 | ({ 19 | assassination: ['contessa'], 20 | stealing: ['captain', 'ambassador'], 21 | 'foreign-aid': ['duke'], 22 | }[action] || []); 23 | 24 | const reformatHistory = (history) => { 25 | const newHistory = []; 26 | let currentTurn = []; 27 | history.forEach((record) => { 28 | if (record.type === 'action' && currentTurn.length > 0) { 29 | newHistory.push(currentTurn); 30 | currentTurn = []; 31 | } 32 | currentTurn.push(record); 33 | }); 34 | return newHistory; 35 | }; 36 | 37 | const historyAfterLossOrSwap = (history, player, card) => { 38 | history = reformatHistory(history); 39 | const i = 40 | history.indexOf( 41 | [...history] 42 | .reverse() 43 | .find( 44 | (turn) => 45 | turn.find( 46 | (record) => 47 | record.type === 'lost-card' && 48 | record.player === player && 49 | record.lost === card 50 | ) || 51 | turn.find( 52 | (record) => 53 | record.action === 'swap-1' && 54 | record.from === player && 55 | record.card === card 56 | ) || 57 | turn.find( 58 | (record) => record.action === 'swapping' && record.from === player 59 | ) 60 | ) 61 | ) + 1; 62 | return i ? (history = history.slice(i)) : history; 63 | }; 64 | 65 | const doesPlayerHave = (history, player, card, otherPlayers) => 66 | player.constructor === Array 67 | ? player.some((p) => doesPlayerHave(history, p.name, card, otherPlayers)) 68 | : historyAfterLossOrSwap(history, player, card).some( 69 | (turn) => 70 | (turn[0].from === player && cardFor(turn[0].action) === card) || 71 | turn.find( 72 | (record) => 73 | record.type === 'counter-action' && 74 | record.counterer === player && 75 | record.counter === card 76 | ) 77 | ); 78 | 79 | const safeish = (history, visibleCards, action, against, otherPlayers) => 80 | blockersFor(action).every((c) => count(c, visibleCards) === 3) || 81 | !blockersFor(action).some((c) => 82 | doesPlayerHave(history, against, c, otherPlayers) 83 | ); 84 | 85 | const sortCards = (cards) => { 86 | const order = { 87 | duke: 0, 88 | captain: 1, 89 | contessa: 2, 90 | ambassador: 3, 91 | assassin: 4, 92 | }; 93 | const inverseOrder = Object.entries(order).reduce( 94 | (o, [k, v]) => ({ ...o, [v]: k }), 95 | {} 96 | ); 97 | return cards 98 | .map((c) => order[c]) 99 | .sort() 100 | .map((x) => inverseOrder[x]); 101 | }; 102 | 103 | const findTargets = (players) => 104 | players 105 | .map((p) => [p.coins * p.cards, p]) 106 | .sort((a, b) => b[0] - a[0]) 107 | .map((x) => x[1]); 108 | 109 | class BOT { 110 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 111 | const target = findTargets(otherPlayers)[0]; 112 | const visibleCards = [...myCards, ...discardedCards]; 113 | 114 | let action; 115 | if ( 116 | myCards.includes(cardFor('assassination')) && 117 | myCoins >= 3 && 118 | safeish(history, visibleCards, 'assassination', target.name, otherPlayers) 119 | ) { 120 | action = 'assassination'; 121 | } else if (myCoins >= 7) { 122 | action = 'couping'; 123 | } else if (myCards.includes(cardFor('taking-3'))) { 124 | action = 'taking-3'; 125 | } else if (myCards.includes(cardFor('swapping'))) { 126 | action = 'swapping'; 127 | } else if ( 128 | myCards.includes(cardFor('stealing')) && 129 | safeish(history, visibleCards, 'stealing', target.name, otherPlayers) && 130 | target.coins >= 2 131 | ) { 132 | action = 'stealing'; 133 | } else if (safeish(history, visibleCards, 'foreign-aid', otherPlayers)) { 134 | action = 'foreign-aid'; 135 | } else { 136 | action = 'taking-1'; 137 | } 138 | 139 | return { 140 | action, 141 | against: target.name, 142 | }; 143 | } 144 | 145 | OnChallengeActionRound({ myCards, discardedCards, action }) { 146 | // If they're obviously bullshitting, call them 147 | return count(cardFor(action), [...myCards, ...discardedCards]) === 3; 148 | } 149 | 150 | OnCounterAction({ myCards, action }) { 151 | // If we can counter this, then do counter this. 152 | const match = blockersFor(action).find((c) => myCards.includes(c)); 153 | if (match) { 154 | return match; 155 | } 156 | 157 | // If we're gonna be dead, fight it! 158 | if (action === 'assassination' && myCards.length === 1) { 159 | return 'contessa'; 160 | } 161 | return false; 162 | } 163 | 164 | OnCounterActionRound({ 165 | history, 166 | myCards, 167 | otherPlayers, 168 | discardedCards, 169 | card, 170 | counterer, 171 | }) { 172 | // If they're obviously bullshitting, call them 173 | if (count(card, [...myCards, ...discardedCards]) === 3) return true; 174 | 175 | // If it looks like they have it, let it slide. 176 | if (doesPlayerHave(history, counterer, card, otherPlayers)) return false; 177 | 178 | // If it looks like they're holding other cards, call them. 179 | const cardsHeld = CARDS().filter( 180 | (c) => 181 | doesPlayerHave(history, counterer, c, otherPlayers) && 182 | count(c, [...myCards, ...discardedCards]) < 3 183 | ).length; 184 | const other = otherPlayers.find((p) => p.name === counterer); 185 | return other && cardsHeld >= other.cards; 186 | } 187 | 188 | OnSwappingCards({ myCards, newCards }) { 189 | // Pick the best two non-identical cards 190 | const sorted = sortCards([...myCards, ...newCards]); 191 | const first = sorted[0]; 192 | return myCards.length === 1 193 | ? [first] 194 | : [first, sorted.find((c) => c !== first) || first]; 195 | } 196 | 197 | OnCardLoss({ myCards }) { 198 | return sortCards([...myCards]).slice(-1)[0]; 199 | } 200 | } 201 | 202 | module.exports = exports = BOT; 203 | -------------------------------------------------------------------------------- /201809-binary-search-tree/TiciA/tree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Creation/modification API 3 | */ 4 | 5 | /* 6 | * Create and return a new tree object. This can have whatever shape you like. 7 | */ 8 | const newTree = (value = null) => ({ 9 | value: value, 10 | left: null, 11 | right: null 12 | }); 13 | 14 | const direction = (tree, value) => value <= tree.value ? 'left': 'right'; 15 | 16 | /* 17 | * Insert `value` into `tree`. 18 | */ 19 | const insert = (tree, value) => { 20 | if(tree === null) return newTree(value); 21 | if(tree.value === null) return tree.value = value; 22 | if(value <= tree.value) { 23 | tree.left === null ? tree.left = newTree(value) : insert(tree.left, value); 24 | } else { 25 | tree.right === null ? tree.right = newTree(value) : insert(tree.right, value); 26 | } 27 | }; 28 | 29 | /* 30 | * Remove `value` from `tree`. Only remove the first instance of the value if it appears multiple times. 31 | * Use the 'in-order predecessor` techinque for replacing the node when there are two child nodes. 32 | * (i.e. replace with the largest value from the nodes left-hand tree) 33 | */ 34 | const remove = (tree, value) => tree + value; 35 | 36 | /* 37 | * Query API 38 | */ 39 | 40 | /* 41 | * Determine whether `value` exists in the `tree`. Return boolean. 42 | */ 43 | const find = (tree, value) => { 44 | if(tree === null || tree.value === null) return false; 45 | if(tree.value === value) return true; 46 | const path = direction(tree, value); 47 | return find(tree[path], value); 48 | }; 49 | 50 | /* 51 | * Calculate the depth of the given value within the tree. Return -1 if the value does not exist. 52 | * The value at the root has a depth of zero. 53 | */ 54 | const depth = (tree, value) => { 55 | let noValue = find(tree, value) === false; 56 | if(tree === null || tree.value === null || noValue) return -1; 57 | if (tree.value === value) return 0; 58 | 59 | const path = direction(tree, value); 60 | return depth(tree[path], value) + 1; 61 | }; 62 | 63 | /* 64 | * Calculate the height of the tree. An empty tree has a height of zero. 65 | */ 66 | const height = (tree) => { 67 | if(tree === null || tree.value === null) return 0; 68 | 69 | const heightLeft = height(tree.left); 70 | const heightRight = height(tree.right); 71 | return heightLeft > heightRight ? heightLeft + 1 : heightRight + 1; 72 | }; 73 | 74 | /* 75 | * Calculate the number of nodes in the tree. 76 | */ 77 | const count = (tree) => { 78 | if(tree === null || tree.value === null) return 0; 79 | return count(tree.left) + count(tree.right) + 1; 80 | }; 81 | 82 | /* 83 | * Determine whether the tree is balanced or not. A tree is balanced if: 84 | * - The left sub-tree is balanced, and 85 | * - The right sub-tree is balanced, and 86 | * - The height of the left sub-tree and right sub-tree differ by no more than one. 87 | * 88 | * An empty tree is always balanced. 89 | */ 90 | const balanced = (tree) => { 91 | if (tree === null 92 | || tree.value === null 93 | || tree.left === tree.right 94 | ) return true; 95 | 96 | const heightLeft = height(tree.left); 97 | const heightRight = height(tree.right); 98 | const difference = Math.abs(heightRight - heightLeft); 99 | if(difference <= 1 && balanced(tree.left) && balanced(tree.right)) return true; 100 | return false; 101 | }; 102 | 103 | /* 104 | * Calculate the biggest value in the tree. Behaviour is undefined for an empty tree. 105 | */ 106 | const biggest = (tree) => { 107 | if(tree.value === null) return undefined; 108 | return (tree.right === null) ? tree.value : biggest(tree.right); 109 | }; 110 | /* 111 | * Calculate the smallest value in the tree. Behaviour is undefined for an empty tree. 112 | */ 113 | const smallest = (tree) => { 114 | if(tree.value === null) return undefined; 115 | return (tree.left === null) ? tree.value : smallest(tree.left); 116 | }; 117 | 118 | /* 119 | * Traversal API 120 | * 121 | * The traversal API allows the user to visit each node in the tree in a particular order. 122 | * 123 | * See https://en.wikipedia.org/wiki/Tree_traversal for definitions of the traversal types. 124 | */ 125 | 126 | /* 127 | * Traverse the tree using in-order traversal, returning an array. 128 | */ 129 | const inOrder = (tree) => { 130 | if(tree === null || tree.value === null) return ([]); 131 | 132 | const result = []; 133 | const transverseInOrder = (tree) => { 134 | tree.left && transverseInOrder(tree.left); 135 | result.push(tree.value); 136 | tree.right && transverseInOrder(tree.right); 137 | }; 138 | 139 | transverseInOrder(tree); 140 | return result; 141 | }; 142 | 143 | /* 144 | * Traverse the tree using pre-order traversal, returning an array. 145 | */ 146 | const preOrder = (tree) => { 147 | if(tree.value === null) return ([]); 148 | const result = []; 149 | const transversePreOrder = (tree) => { 150 | result.push(tree.value); 151 | tree.left && transversePreOrder(tree.left); 152 | tree.right && transversePreOrder(tree.right); 153 | }; 154 | 155 | transversePreOrder(tree); 156 | return result; 157 | }; 158 | // [currentNode, ...left, ...right] depth first traversal 159 | /* 160 | * Traverse the tree using post-order traversal, returning an array. 161 | */ 162 | const postOrder = (tree) => { 163 | if(tree.value === null) return ([]); 164 | const result = []; 165 | const transversePostOrder = (tree) => { 166 | tree.left && transversePostOrder(tree.left); 167 | tree.right && transversePostOrder(tree.right); 168 | result.push(tree.value); 169 | }; 170 | 171 | transversePostOrder(tree); 172 | return result; 173 | }; 174 | // [...left, ...right, currentNode] depth first traversal 175 | /* 176 | * Traverse the tree using breadth first (level-order) traversal, returning an array. 177 | */ 178 | // Hint, use a queue 179 | // queue = [5] 180 | // result = [] 181 | // 1 pop the queue 182 | // 2 put the value in the result 183 | // 3 push left and right onto queue 184 | const breadthFirst = (tree) => { 185 | let queue = [tree]; 186 | let result = []; 187 | while(queue.length) { 188 | let subtree = queue.shift(); //removes the first element from an array and returns that removed element 189 | if(subtree !== null && subtree.value !== null) { 190 | result.push(subtree.value); 191 | queue.push(subtree.left, subtree.right); 192 | } 193 | } 194 | return result; 195 | }; 196 | 197 | 198 | module.exports = { 199 | newTree, 200 | insert, 201 | remove, 202 | 203 | find, 204 | depth, 205 | height, 206 | count, 207 | balanced, 208 | biggest, 209 | smallest, 210 | 211 | inOrder, 212 | preOrder, 213 | postOrder, 214 | breadthFirst, 215 | }; 216 | -------------------------------------------------------------------------------- /201808-coup/Madds/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | const me = 'Madds'; 6 | 7 | const getCardFor = (action) => 8 | ({ 9 | assassination: 'assassin', 10 | stealing: 'captain', 11 | swapping: 'ambassador', 12 | 'taking-3': 'duke', 13 | }[action]); 14 | 15 | const turns = (history) => { 16 | return history.filter((h) => h.type === 'action'); 17 | }; 18 | 19 | const isFirstTurn = (history, otherPlayers) => { 20 | const numTurns = turns(history).length; 21 | // General case of no-one dying first round 22 | return numTurns <= otherPlayers.length + 1; 23 | }; 24 | 25 | const otherPlayerSwapped = (turn) => { 26 | if (turn.type === 'action' && turn.action === 'swapping' && turn.from !== me) 27 | return true; 28 | if ( 29 | turn.action === 'swap-1' && 30 | (turn.from !== me && turn.card !== 'ambassador') 31 | ) 32 | return true; 33 | }; 34 | 35 | const swapsSinceAmbass = (history) => { 36 | const historyRev = [...history].reverse(); 37 | const myAmbassIndex = historyRev.findIndex( 38 | (t) => t.action === 'swapping' && t.from === me 39 | ); 40 | const recent = historyRev.slice(0, myAmbassIndex); 41 | const swaps = recent.filter(otherPlayerSwapped); 42 | if (swaps.length > 0) return true; 43 | }; 44 | 45 | ////////////////////////////////////////////////////////////////////////////////////////////// 46 | 47 | class RoughBot { 48 | constructor() { 49 | this.deckCards = []; 50 | } 51 | 52 | richest(otherPlayers) { 53 | return [...otherPlayers].sort((a, b) => b.coins - a.coins)[0]; 54 | } 55 | 56 | takeOut(otherPlayers) { 57 | const richMofos = otherPlayers.filter((p) => p.coins >= 3); 58 | if (richMofos.length > 0) { 59 | const baller = this.richest(richMofos); 60 | return baller.name; 61 | } 62 | 63 | const twoCarders = otherPlayers.filter((p) => p.cards === 2); 64 | if (twoCarders.length > 0) { 65 | return twoCarders[0].name; 66 | } 67 | 68 | return otherPlayers[0].name; 69 | } 70 | 71 | hasThreeCardsKnown(myCards, discardedCards, card) { 72 | const knownCards = [...myCards, ...discardedCards, ...this.deckCards]; 73 | const numKnown = knownCards.filter((c) => c === card).length; 74 | if (numKnown === 3) return true; 75 | } 76 | 77 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 78 | // console.log('MY CARDS:', myCards); 79 | 80 | const coup = { action: 'couping', against: this.takeOut(otherPlayers) }; 81 | const swap = { action: 'swapping', against: null }; 82 | const takeThree = { action: 'taking-3', against: null }; 83 | const steal = { 84 | action: 'stealing', 85 | against: this.richest(otherPlayers).name, 86 | }; 87 | 88 | if (myCoins >= 7) return coup; 89 | 90 | if (history.length > 0 && isFirstTurn(history, otherPlayers)) { 91 | if (myCards.includes('ambassador')) return swap; 92 | if (!myCards.includes('duke')) return swap; 93 | } 94 | 95 | if (myCards.includes('assassin') && myCoins >= 3) { 96 | // Has target blocked assassination before (roughly) 97 | const hasBlockedAssass = (player) => 98 | history.some((t) => t.counter === 'contessa' && t.counterer === player); 99 | const targets = otherPlayers.filter((p) => !hasBlockedAssass(p.name)); 100 | if (targets.length > 0) { 101 | return { action: 'assassination', against: this.takeOut(targets) }; 102 | } 103 | } 104 | 105 | if (this.deckCards.length === 0 && myCards.includes('ambassador')) { 106 | return swap; 107 | } 108 | 109 | if (myCards.includes('captain')) { 110 | return steal; 111 | } 112 | 113 | return takeThree; 114 | } 115 | 116 | OnChallengeActionRound({ 117 | history, 118 | myCards, 119 | myCoins, 120 | otherPlayers, 121 | discardedCards, 122 | action, 123 | byWhom, 124 | toWhom, 125 | }) { 126 | if (swapsSinceAmbass(history)) { 127 | this.deckCards = []; 128 | } 129 | 130 | const card = getCardFor(action); 131 | if (this.hasThreeCardsKnown(myCards, discardedCards, card)) { 132 | return true; 133 | } 134 | 135 | // Last ditch effort 136 | if ( 137 | action === 'assassination' && 138 | toWhom === me && 139 | myCards.length === 1 && 140 | !myCards.includes('contessa') 141 | ) { 142 | return true; 143 | } 144 | } 145 | 146 | OnCounterAction({ 147 | history, 148 | myCards, 149 | myCoins, 150 | otherPlayers, 151 | discardedCards, 152 | action, 153 | byWhom, 154 | }) { 155 | if (action === 'assassination' && myCards.includes('contessa')) { 156 | return 'contessa'; 157 | } 158 | if (action === 'stealing' && myCards.includes('captain')) { 159 | return 'captain'; 160 | } 161 | if (action === 'stealing' && myCards.includes('ambassador')) { 162 | return 'ambassador'; 163 | } 164 | if (action === 'foreign-aid' && myCards.includes('duke')) { 165 | return 'duke'; 166 | } 167 | } 168 | 169 | OnCounterActionRound({ 170 | history, 171 | myCards, 172 | myCoins, 173 | otherPlayers, 174 | discardedCards, 175 | action, 176 | byWhom, 177 | toWhom, 178 | card, 179 | counterer, 180 | }) { 181 | if (this.hasThreeCardsKnown(myCards, discardedCards, card)) { 182 | return true; 183 | } 184 | } 185 | 186 | OnSwappingCards({ 187 | history, 188 | myCards, 189 | myCoins, 190 | otherPlayers, 191 | discardedCards, 192 | newCards, 193 | }) { 194 | const available = [...myCards, ...newCards]; 195 | const keeping = []; 196 | 197 | const index = (card) => available.indexOf(card); 198 | 199 | if (otherPlayers.length < 3 && index('captain') > -1) { 200 | keeping.push(...available.splice(index('captain'), 1)); 201 | } 202 | 203 | if (index('duke') > -1) { 204 | keeping.push(...available.splice(index('duke'), 1)); 205 | } 206 | 207 | if (keeping.length < 2 && index('assassin') > -1) { 208 | keeping.push(...available.splice(index('assassin'), 1)); 209 | } 210 | 211 | if (keeping.length === 2) { 212 | this.deckCards = [...available]; 213 | return keeping; 214 | } 215 | 216 | if (keeping.length === 1) { 217 | keeping.push(...available.splice(0, 1)); 218 | this.deckCards = [...available]; 219 | return keeping; 220 | } 221 | 222 | this.deckCards = [...newCards]; 223 | return myCards; 224 | } 225 | 226 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 227 | if (myCards.includes('contessa')) return 'contessa'; 228 | if (myCards.includes('ambassador')) return 'ambassador'; 229 | if (myCards.includes('captain')) return 'captain'; 230 | if (myCards.includes('assassin')) return 'assassin'; 231 | return myCards[0]; 232 | } 233 | } 234 | 235 | module.exports = exports = RoughBot; 236 | -------------------------------------------------------------------------------- /201808-coup/JohnM/index.js: -------------------------------------------------------------------------------- 1 | // Approx precedence of hands 2 | const handPreferrence = [ 3 | 'assassin & duke', // 8477 4 | 'assassin & captain', // 8449 5 | 'captain & duke', // 8392 6 | 'ambassador & duke', // 8302 7 | 'captain & contessa', // 8176 8 | 'contessa & duke', // 8170 9 | 'assassin & contessa', // 8120 10 | 'ambassador & captain', // 8055 11 | 'ambassador & assassin', // 7756 12 | 'ambassador & contessa', // 6350 13 | 'duke & duke', // 2819 14 | 'captain & captain', // 2781 15 | 'ambassador & ambassador', // 2482 16 | 'assassin & assassin', // 2373 17 | 'contessa & contessa', // 1481 18 | ]; 19 | 20 | const cardPreferrence = [ 21 | 'duke', // 38979 ... 2819 + 2819 + 8170 + 8302 + 8392 + 8477 22 | 'captain', // 38634 ... 2781 + 2781 + 8055 + 8176 + 8392 + 8449 23 | 'assassin', // 37548 ... 2373 + 2373 + 7756 + 8120 + 8449 + 8477 24 | 'ambassador', // 35427 ... 2482 + 2482 + 6350 + 7756 + 8055 + 8302 25 | 'contessa', // 33778 ... 1481 + 1481 + 6350 + 8120 + 8170 + 8176 26 | ]; 27 | 28 | // Action requiirements 29 | const actionCardRequirements = { 30 | 'taking-1': null, 31 | 'foreign-aid': null, 32 | couping: null, 33 | 'taking-3': 'duke', 34 | assassination: 'assassin', 35 | stealing: 'captain', 36 | swapping: 'ambassador', 37 | }; 38 | 39 | // TODO: Preceedence of operations? 40 | let operations = [ 41 | (args) => { 42 | if (args.myCoins >= 3 && args.myCards.includes('assassin')) { 43 | return { action: 'assassination', against: args.otherPlayers[0].name }; 44 | } 45 | }, 46 | (args) => { 47 | if (args.myCoins > 6) { 48 | return { action: 'couping', against: args.otherPlayers[0].name }; 49 | } 50 | }, 51 | // Late stage captain'ing? 52 | (args) => { 53 | if ( 54 | args.otherPlayers.length === 1 && 55 | args.myCards.includes('captain') && 56 | args.otherPlayers[0].coins > 1 57 | ) { 58 | return { action: 'stealing', against: args.otherPlayers[0].name }; 59 | } 60 | }, 61 | // Swap if we have two cards of the same type; so long as it's not too suspicious 62 | (args) => { 63 | if (args.myCards[0] === args.myCards[1]) return { action: 'swapping' }; 64 | }, 65 | (args) => { 66 | if (args.myCards.includes('duke')) { 67 | return { action: 'taking-3' }; 68 | } 69 | }, 70 | (args) => { 71 | if (args.myCards.includes('captain')) { 72 | const stealFrom = args.otherPlayers 73 | .filter((p) => p.coins > 2) 74 | .reduce((a, p) => (a ? a : p.name), ''); 75 | if (stealFrom) return { action: 'stealing', against: stealFrom }; 76 | } 77 | }, 78 | (args) => { 79 | const nonEnemyDukes = [] 80 | .concat(args.myCards, args.discardedCards) 81 | .filter((c) => c === 'duke'); 82 | if (nonEnemyDukes === 3) return { action: 'foreign-aid' }; 83 | }, 84 | (args) => { 85 | if (args.myCards.includes('ambassador')) return { action: 'swapping' }; 86 | }, 87 | (args) => { 88 | return { action: 'taking-1' }; 89 | }, 90 | ]; 91 | 92 | class Sensible { 93 | constructor({ name }) { 94 | this.name = name; 95 | this.turnNumber = 0; 96 | } 97 | 98 | // { history, myCards, myCoins, otherPlayers, discardedCards } 99 | OnTurn(args) { 100 | this.turnNumber++; 101 | if (this.turnNumber === 1) { 102 | // console.log(`${this.name} (Sensible): I've been dealt: ${args.myCards.join(' & ')}`); 103 | } 104 | 105 | for (let op of operations) { 106 | const result = op(args); 107 | if (result) return result; 108 | } 109 | } 110 | 111 | // Counter the obvious stuff 112 | // { history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom } 113 | OnCounterAction(args) { 114 | if (args.action === 'foreign-aid' && args.myCards.includes('duke')) 115 | return 'duke'; 116 | if (args.action === 'stealing' && args.myCards.includes('captain')) 117 | return 'captain'; 118 | if (args.action === 'stealing' && args.myCards.includes('ambassador')) 119 | return 'ambassador'; 120 | if ( 121 | args.action === 'assassination' && 122 | (args.myCards.includes('contessa') || args.myCards.length === 1) 123 | ) 124 | return 'contessa'; 125 | 126 | // If I'm one of 2 players left and being stolen from, fake a block 127 | if (args.action === 'stealing' && args.otherPlayers.length === 1) { 128 | return 'ambassador'; 129 | } 130 | } 131 | 132 | // { history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom, toWhom } 133 | OnChallengeActionRound(args) { 134 | // Is the action possible based on known cards? 135 | const reqCard = actionCardRequirements[args.action]; 136 | const nonOpCards = [].concat(args.myCards, args.discardedCards); 137 | const couldHaveReqCard = 138 | !reqCard || nonOpCards.filter((c) => c === reqCard).length < 3; 139 | if (!couldHaveReqCard) return true; 140 | 141 | // If its not me being attacked or I have a counter action 142 | if (args.toWhom !== this.name) return false; 143 | if (this.OnCounterAction(args)) return false; 144 | 145 | // Possible chalenge for other reasons? 146 | return false; 147 | } 148 | 149 | // { history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom, toWhom } 150 | // args.action is currently the original action, not the "block" action (eg. 'stealing' not block by 'captain') 151 | OnCounterActionRound(args) { 152 | const challengerCanNotHave = (reqCard) => 153 | [].concat(args.myCards, args.discardedCards).filter((c) => c === reqCard) 154 | .length > 2; 155 | if (args.action === 'foreign-aid') return challengerCanNotHave('duke'); 156 | if (args.action === 'stealing') 157 | return ( 158 | challengerCanNotHave('captain') && challengerCanNotHave('ambassador') 159 | ); 160 | if (args.action === 'assassination') 161 | return challengerCanNotHave('contessa'); 162 | return false; 163 | } 164 | 165 | // { history, myCards, myCoins, otherPlayers, discardedCards, newCards } 166 | OnSwappingCards({ myCards, newCards }) { 167 | const allCombos = [ 168 | myCards, 169 | newCards, 170 | [myCards[0], newCards[0]], 171 | [myCards[0], newCards[1]], 172 | [myCards[1], newCards[0]], 173 | [myCards[1], newCards[1]], 174 | ]; 175 | const validCombos = allCombos.filter( 176 | (p) => p.filter((c) => typeof c !== 'undefined').length === 2 177 | ); 178 | const strCombos = validCombos.map((p) => p.sort().join(' & ')); 179 | for (let pref of handPreferrence) { 180 | if (strCombos.includes(pref)) return pref.split(' & '); 181 | } 182 | } 183 | 184 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 185 | if (myCards.length === 1) return myCards[0]; 186 | return cardPreferrence.indexOf(myCards[0]) > 187 | cardPreferrence.indexOf(myCards[1]) 188 | ? myCards[0] 189 | : myCards[1]; 190 | } 191 | } 192 | 193 | module.exports = exports = Sensible; 194 | -------------------------------------------------------------------------------- /201904-transpiler/README.md: -------------------------------------------------------------------------------- 1 | # March 2019 Challenge 2 | 3 | This challenge is going to see us build our own compiler (transpiler), and learn the parts that make up this kind of software. 4 | 5 | To do this, you will need to understand the steps that a transpiler goes through as it does its work. 6 | 7 | ## Your Mission, if you choose to accept it: 8 | 9 | Expand out the `transpiler.example.js`'s functions to pass the test suite found in `transpiler.tests.js`. 10 | 11 | You can write your functions in any order, however it is likely helpful to understand tokens before you write your parser. 12 | 13 | You are not allowed to use any node modules (other than jest). 14 | 15 | ## To get started 16 | 17 | Read the Readme, then open up the `transpiler.example.js` file, and fill in the missing functions. 18 | 19 | You can run `yarn jest --watch` in this folder to test your code. 20 | 21 | ## What is a Transpiler? 22 | 23 | A transpiler is software that takes in code from one language, converts it to an Abstract Syntax Tree (AST), and then converts 24 | it back to the same language. Tranpilers are useful to perform transformations on code without doing a full language switch. 25 | For us JS developers, babel and everything we get it to do is why transpilers matter to us. 26 | 27 | ## How does a transpiler work? 28 | 29 | A transpiler performs a series of transforms, to go from raw code to raw code. The conversion steps look like: 30 | 31 | ``` 32 | tokenizer(raw_code) => tokens 33 | parser(tokens) => AST 34 | transformer(AST) => AST 35 | generator(AST) => raw_code 36 | ``` 37 | 38 | ### Tokenizing 39 | 40 | Tokenizing is the process of converting raw code into tokens. A token is an object that represents a discrete unit 41 | in your code, such as: 42 | 43 | ```js 44 | { type 'Number', value: '7' } 45 | ``` 46 | 47 | Every token has a type. This is used by later processes to decide what to do with the token. In addition, some 48 | tokens have values, which are a string, and hold some information from the source code. An example of tokenizing 49 | would be taking the code snippet: 50 | 51 | ```js 52 | a = 7 53 | ``` 54 | 55 | And transforming it into the list of tokens: 56 | 57 | ```js 58 | [ 59 | { type 'Identifier', value: 'a' }, 60 | { type: 'VariableAssignmentOperator' }, 61 | { type: "Number", value: "7" } 62 | ] 63 | ``` 64 | 65 | ### Parsing 66 | 67 | An Abstract Syntax Tree (AST) is a tree representation of your code, that goes further than tokens as it contains 68 | informations about how tokens relate to one another. 69 | 70 | This can be seen from something such as this variable declaration in an AST: 71 | 72 | if you start with the code 73 | 74 | ```js 75 | let a = 7 76 | ``` 77 | 78 | You would get the tokens 79 | 80 | ```js 81 | [ 82 | { type: 'VariableDeclarator' }, 83 | { type 'Identifier', value: 'a' }, 84 | { type: 'VariableAssignmentOperator' }, 85 | { type: "Number", value: "7" } 86 | ] 87 | ``` 88 | 89 | This would then be parsed into the the following node in an AST 90 | 91 | ```js 92 | { 93 | type: "VariableDeclaration", 94 | id: { type: "Identifier", value: "a" }, 95 | initialValue: { 96 | type: "Number", 97 | value: "7" 98 | } 99 | } 100 | ``` 101 | 102 | An AST is built from tokens, not code directly, however can be used to convert back into code. 103 | 104 | ### Transforming 105 | 106 | In transformation, the goal is to use information from the AST to return a modified AST. 107 | 108 | For example, if you wanted a transformation to capitalise all variables, you would be 109 | passed the node: 110 | 111 | ```js 112 | { 113 | type: "VariableDeclaration", 114 | id: { type: "Identifier", value: "a" }, 115 | initialValue: { 116 | type: "Number", 117 | value: "7" 118 | } 119 | } 120 | ``` 121 | 122 | and return the node: 123 | 124 | ```js 125 | { 126 | type: "VariableDeclaration", 127 | id: { type: "Identifier", value: "A" }, 128 | initialValue: { 129 | type: "Number", 130 | value: "7" 131 | } 132 | } 133 | ``` 134 | 135 | ### Generating 136 | 137 | In the generation step, you need to convert an AST into code. For example, you could convert 138 | 139 | ```js 140 | { 141 | type: "VariableDeclaration", 142 | id: { type: "Identifier", value: "A" }, 143 | initialValue: { 144 | type: "Number", 145 | value: "7" 146 | } 147 | } 148 | ``` 149 | 150 | into the code: 151 | 152 | ```js 153 | let A = 7 154 | ``` 155 | 156 | ## Challenge 1 - initial set of tokens 157 | 158 | For the first challenge, our goal is to remove unused variables. For example, in the statement: 159 | 160 | ```js 161 | let a = 5 162 | let b = a 163 | export default a; 164 | ``` 165 | 166 | The variable b is never used and could be removed, leaving us with the code: 167 | 168 | ```js 169 | let a = 5 170 | export default a 171 | ``` 172 | 173 | An unused variable is defined as a variable not used in the code after its declaration. 174 | 175 | For this first part we are using a very reduced set of javascript that includes: 176 | 177 | - `let` as the only variable type 178 | - `numbers` and `identifiers` (variable names) as the only values/data types 179 | - Basic math operators (`+`, `-`, `*`) as the only kinds of operation (no functions yet) 180 | 181 | 182 | ## Formal grammar definition 183 | 184 | This is a formal grammar that defines what a valid syntax is in the subset of javascript which we are using. 185 | 186 | ``` 187 | Program:: StatementWithLineBreak* [Statement] 188 | StatementWithLineBreak:: [Statement] : LineBreak 189 | Statement:: AssignmentExpression | OperationalExpression 190 | AssignmentExpression:: DefaultExportExpression | VariableDeclaration | VariableAssignment 191 | OperationalExpression:: Value | BinaryExpression 192 | DefaultExportExpression:: DefaultExport : OperationalExpression 193 | VariableDeclaration:: VariableDeclarator : Identifier : VariableAssignmentOperator : OperationalExpression 194 | VariableAssignment:: Identifer : VariableAssignmentOperator : OperationalExpression 195 | Value:: Number | Identifier 196 | BinaryExpression:: Value : BinaryOperator : OperationalExpression 197 | 198 | # tokens 199 | 200 | DefaultExport:: "export default" 201 | VariableDeclarator:: "let" 202 | Identifier:: /[a-zA-Z]+/ 203 | Number:: /[0-9]+/ 204 | VariableAssignmentOperator:: "=" 205 | BinaryOperator:: "+" | "-" | "*" 206 | LineBreak:: "\n" 207 | 208 | 209 | # Reading this grammar 210 | # A definition is: 211 | # DefinitionName:: Definition 212 | # A definition may combine multiple other definitions 213 | 214 | # How to indicate optional part of definition 215 | # [ThisInBracketsIsOptional] 216 | 217 | # Indicate a definition is repeated 218 | # RepeatingBit* 219 | 220 | # How to indicate one definition followed by another 221 | # PartA : PartB 222 | 223 | # How to indicate it is one or the other 224 | # ThisOne | OrThisOne 225 | ``` 226 | 227 | ## Other Resources 228 | 229 | - [How to be a compiler](https://medium.com/@kosamari/how-to-be-a-compiler-make-a-compiler-with-javascript-4a8a13d473b4) is a good article on the concepts at play 230 | - [The AST explorer allows you to examine generated ASTs of code using several different parsers](https://astexplorer.net/) 231 | - [The Super Tiny Compiler](https://github.com/jamiebuilds/the-super-tiny-compiler) is a similar exercise -------------------------------------------------------------------------------- /201904-transpiler/mitchellhamilton/transpiler.js: -------------------------------------------------------------------------------- 1 | // DefaultExport:: "export default" 2 | // VariableDeclarator:: "let" 3 | // Identifier:: /[a-zA-Z]+/ 4 | // Number:: /[0-9]+/ 5 | // VariableAssignmentOperator:: "=" 6 | // BinaryOperator:: "+" | "-" | "*" 7 | // LineBreak:: "\n" 8 | 9 | let possibleTokens = { 10 | DefaultExport: { 11 | pattern: /^export default$/ 12 | }, 13 | VariableDeclarator: { 14 | pattern: /^let$/ 15 | }, 16 | Identifier: { 17 | pattern: /^[a-zA-Z]+$/, 18 | includeValue: true 19 | }, 20 | Number: { 21 | pattern: /^[0-9]+$/, 22 | includeValue: true 23 | }, 24 | VariableAssignmentOperator: { 25 | pattern: /^=$/ 26 | }, 27 | BinaryOperator: { 28 | pattern: /^(\+|-|\*)$/, 29 | includeValue: true 30 | }, 31 | LineBreak: { 32 | pattern: /^\n$/ 33 | } 34 | }; 35 | 36 | function tryThing(buffer) { 37 | for (let type of Object.keys(possibleTokens)) { 38 | let thing = possibleTokens[type]; 39 | let value = buffer.replace(/(\t| )+$/g, "").replace(/^(\t| )+/g, ""); 40 | if (thing.pattern.test(value)) { 41 | return thing.includeValue 42 | ? { 43 | type, 44 | value 45 | } 46 | : { type }; 47 | } 48 | } 49 | return null; 50 | } 51 | 52 | let keywordPattern = /export|default|let/; 53 | 54 | const tokenizer = code => { 55 | let tokens = []; 56 | 57 | let buffer = ""; 58 | 59 | let chars = code.split(""); 60 | Char: for (let i = 0; i < chars.length; ) { 61 | buffer += chars[i]; 62 | let thing = tryThing(buffer); 63 | if (thing !== null) { 64 | let val = thing; 65 | let lastVal = val; 66 | let innerStr = buffer; 67 | let innerI = i; 68 | 69 | while (val !== null && innerI < chars.length) { 70 | lastVal = val; 71 | innerI++; 72 | innerStr += chars[innerI]; 73 | val = tryThing(innerStr); 74 | } 75 | 76 | i += innerStr.length - buffer.length; 77 | if (lastVal.type === "Identifier" && keywordPattern.test(lastVal.value)) { 78 | i++; 79 | buffer = innerStr; 80 | continue Char; 81 | } 82 | buffer = ""; 83 | tokens.push(lastVal); 84 | continue Char; 85 | } 86 | i++; 87 | } 88 | return tokens; 89 | }; 90 | 91 | // Program:: StatementWithLineBreak* [Statement] 92 | // StatementWithLineBreak:: [Statement] : LineBreak 93 | // Statement:: AssignmentExpression | OperationalExpression 94 | // AssignmentExpression:: DefaultExportExpression | VariableDeclaration | VariableAssignment 95 | // OperationalExpression:: Value | BinaryExpression 96 | // DefaultExportExpression:: DefaultExport : OperationalExpression 97 | // VariableDeclaration:: VariableDeclarator : Identifier : VariableAssignmentOperator : OperationalExpression 98 | // VariableAssignment:: Identifer : VariableAssignmentOperator : OperationalExpression 99 | // Value:: Number | Identifier 100 | // BinaryExpression:: Value : BinaryOperator : OperationalExpression 101 | 102 | const parser = tokens => { 103 | let statements = []; 104 | let i = 0; 105 | function walk() { 106 | let token = tokens[i]; 107 | if ( 108 | token.type === "Identifier" && 109 | tokens[i + 1] && 110 | tokens[i + 1].type === "VariableAssignmentOperator" 111 | ) { 112 | i += 2; 113 | return { 114 | type: "VariableAssignment", 115 | id: token, 116 | value: walk() 117 | }; 118 | } 119 | if (token.type === "Number" || token.type === "Identifier") { 120 | i++; 121 | if (tokens[i] && tokens[i].type === "BinaryOperator") { 122 | i++; 123 | return { 124 | type: "BinaryExpression", 125 | operator: tokens[i - 1].value, 126 | left: token, 127 | right: walk() 128 | }; 129 | } 130 | 131 | return token; 132 | } 133 | if (token.type === "DefaultExport") { 134 | i++; 135 | return { 136 | type: "DefaultExportExpression", 137 | value: walk() 138 | }; 139 | } 140 | 141 | if (token.type === "VariableDeclarator") { 142 | i += 3; 143 | return { 144 | type: "VariableDeclaration", 145 | id: tokens[i - 2], 146 | value: walk() 147 | }; 148 | } 149 | if (token.type === "LineBreak") { 150 | i++; 151 | if (tokens.length === i) { 152 | return null; 153 | } 154 | return walk(); 155 | } 156 | throw new Error("Unknown Thing: " + token.type); 157 | } 158 | while (i < tokens.length) { 159 | statements.push(walk()); 160 | } 161 | /* ... */ 162 | return { type: "Program", statements: statements.filter(x => x) }; // an object which is our AST - we can actually refer back to the tree traversal stuff we did 163 | }; 164 | 165 | function traverser(ast, visitor) { 166 | function traverseNode(node) { 167 | let enter = visitor[node.type]; 168 | if (enter) { 169 | enter(node); 170 | } 171 | 172 | switch (node.type) { 173 | case "Program": { 174 | node.statements.forEach( 175 | statementButItsNotReallyTechnicallyAStatementButWhatever => { 176 | traverseNode( 177 | statementButItsNotReallyTechnicallyAStatementButWhatever 178 | ); 179 | } 180 | ); 181 | break; 182 | } 183 | case "VariableAssignment": 184 | case "VariableDeclaration": { 185 | traverseNode(node.id); 186 | traverseNode(node.value); 187 | break; 188 | } 189 | 190 | case "BinaryExpression": { 191 | traverseNode(node.left); 192 | traverseNode(node.right); 193 | break; 194 | } 195 | case "DefaultExportExpression": { 196 | traverseNode(node.value); 197 | break; 198 | } 199 | 200 | case "Identifier": 201 | case "Number": 202 | break; 203 | 204 | default: 205 | throw new Error("Unknown Thing: " + node.type); 206 | } 207 | } 208 | 209 | traverseNode(ast); 210 | } 211 | 212 | const transformer = ast => { 213 | let declarations = ast.statements.filter( 214 | x => x.type === "VariableDeclaration" 215 | ); 216 | 217 | let usedDeclarations = new Set(); 218 | 219 | traverser(ast, { 220 | Identifier(node) { 221 | let decl = declarations.find(x => x.id.value === node.value); 222 | if (decl && node !== decl.id) { 223 | usedDeclarations.add(decl); 224 | } 225 | } 226 | }); 227 | 228 | let newAst = { 229 | type: "Program", 230 | statements: ast.statements.filter(statement => { 231 | return !( 232 | statement.type === "VariableDeclaration" && 233 | !usedDeclarations.has(statement) 234 | ); 235 | }) 236 | }; 237 | /* ... */ 238 | return newAst; 239 | }; 240 | function stringifyNode(node) { 241 | switch (node.type) { 242 | case "Number": 243 | case "Identifier": { 244 | return node.value; 245 | } 246 | case "VariableDeclaration": { 247 | return `let ${stringifyNode(node.id)} = ${stringifyNode(node.value)}`; 248 | } 249 | case "VariableAssignment": { 250 | return `${stringifyNode(node.id)} = ${stringifyNode(node.value)}`; 251 | } 252 | case "DefaultExportExpression": { 253 | return `export default ${stringifyNode(node.value)}`; 254 | } 255 | case "BinaryExpression": { 256 | return `${stringifyNode(node.left)} ${node.operator} ${stringifyNode( 257 | node.right 258 | )}`; 259 | } 260 | case "Program": { 261 | return node.statements.map(stringifyNode).join("\n"); 262 | } 263 | } 264 | } 265 | 266 | const generator = ast => { 267 | return stringifyNode(ast); 268 | }; 269 | 270 | const generate = code => { 271 | const tokens = tokenizer(code); 272 | const AST = parser(tokens); 273 | const transformedAST = transformer(AST); 274 | const newCode = generator(transformedAST); 275 | return newCode; 276 | }; 277 | 278 | module.exports = generate; 279 | module.exports.tokenizer = tokenizer; 280 | module.exports.parser = parser; 281 | module.exports.transformer = transformer; 282 | module.exports.generator = generator; 283 | -------------------------------------------------------------------------------- /201904-transpiler/charles/transpiler.example.js: -------------------------------------------------------------------------------- 1 | /* 2 | ### TOKENIZING ### 3 | */ 4 | const tokenizer = code => { 5 | /* ... */ 6 | if (!code) return []; 7 | const truncatedCode = truncate(code); 8 | const splitCode = truncatedCode.split(' ').filter(v => v !== ''); 9 | const cleanedCode = cleanCode(splitCode); 10 | const tokens = cleanedCode.filter(v => v).map(convertToToken); 11 | return tokens; // an array of tokens in processed order 12 | }; 13 | 14 | const truncate = code => code.replace(/\b/gi,' '); 15 | 16 | const cleanCode = code => { 17 | return code.map(v => v.replace(/\t+/, '')).reduce((acc, curr) => { 18 | if (curr.match(/\n/)) { 19 | const linebreakIndex = curr.indexOf('\n'); 20 | const a = curr.substring(0, linebreakIndex); 21 | const b = curr.substring(linebreakIndex, linebreakIndex + 1); 22 | const c = curr.substring(linebreakIndex + 1, code.length); 23 | acc = acc.concat([a,b,c]); 24 | } else if (curr.match('default')) { 25 | acc[acc.length - 1] = `${acc[acc.length - 1]} default`; 26 | } else { 27 | acc.push(curr); 28 | } 29 | return acc; 30 | }, []) 31 | } 32 | 33 | const convertToToken = (code) => { 34 | if (code.match(/\n/gi)) { 35 | return { 36 | type: 'LineBreak', 37 | } 38 | } 39 | if (code.match('export default')) { 40 | return { 41 | type: 'DefaultExport', 42 | } 43 | } 44 | if (!isNaN(Number(code))) { 45 | return { 46 | type: 'Number', 47 | value: code, 48 | } 49 | } 50 | if (code.match('let')) { 51 | return { 52 | type: 'VariableDeclarator', 53 | } 54 | } 55 | if (code.match(/[+-\/*]/gi)) { 56 | return { 57 | type: "BinaryOperator", 58 | value: code, 59 | } 60 | } 61 | if (code.match(/=/gi)) { 62 | return { 63 | type: 'VariableAssignmentOperator', 64 | } 65 | } 66 | if (!code.includes('"')) { 67 | return { 68 | type: 'Identifier', 69 | value: code, 70 | } 71 | } 72 | }; 73 | 74 | /* 75 | ### PARSING ### 76 | */ 77 | 78 | const parser = tokens => { 79 | /* ... */ 80 | return { 81 | type: "Program", 82 | statements: parseStatements(tokens), 83 | }; // an object which is our AST - we can actually refer back to the tree traversal stuff we did 84 | }; 85 | 86 | const performSyntacticAnalysis = tokens => { 87 | // LL(1) here, because its easy. 88 | // with added time, would interrogate LR or LL(k) 89 | const token = tokens[0]; 90 | const lookahead = tokens[1]; 91 | switch (tokens[0].type) { 92 | case "DefaultExport": { 93 | return parseDefaultExport(tokens); 94 | } 95 | case "VariableDeclarator": { 96 | return parseVariableDeclaration(tokens); 97 | } 98 | case "Identifier": { 99 | if (lookahead && lookahead.type === "VariableAssignmentOperator") { 100 | return parseVariableAssigment(tokens); 101 | } 102 | } 103 | case "String": 104 | case "Number": { 105 | if (lookahead && lookahead.type === 'BinaryOperator') { 106 | return parseBinaryExpression(tokens); 107 | } 108 | return tokens.shift(); 109 | } 110 | default: { 111 | tokens.shift(); 112 | } 113 | } 114 | } 115 | 116 | const parseDefaultExport = tokens => { 117 | tokens.shift(); 118 | return { 119 | type: 'DefaultExportExpression', 120 | value: performSyntacticAnalysis(tokens), 121 | } 122 | } 123 | 124 | const parseVariableAssigment = tokens => { 125 | let id = tokens.shift(); 126 | tokens.shift() //remove operator; 127 | let value = performSyntacticAnalysis(tokens); 128 | 129 | const expression = { 130 | type: 'VariableAssignment', 131 | id, 132 | value, 133 | } 134 | console.log(expression); 135 | return expression; 136 | } 137 | 138 | const parseStatements = tokens => { 139 | const statements = []; 140 | while (tokens.length > 0) { 141 | statements.push(performSyntacticAnalysis(tokens)); 142 | } 143 | return statements.filter(i=>i); 144 | } 145 | 146 | const parseBinaryExpression = tokens => { 147 | let left = tokens.shift(); 148 | let operator = tokens.shift().value; 149 | let right = performSyntacticAnalysis(tokens); 150 | 151 | const expression = { 152 | type: 'BinaryExpression', 153 | left, 154 | operator, 155 | right, 156 | } 157 | return expression; 158 | } 159 | 160 | 161 | const parseValue = tokens => { 162 | tokens.shift(); 163 | const value = tokens[0]; 164 | const lookahead = tokens[1] 165 | if (tokens[0].value && !lookahead || lookahead.type === 'LineBreak' ) { 166 | return tokens.shift(); 167 | } else if (lookahead.type === 'BinaryOperator') { 168 | return parseBinaryExpression(tokens); 169 | } 170 | } 171 | 172 | const parseVariableDeclaration = tokens => { 173 | tokens.shift(); 174 | let id = tokens.shift(); 175 | let value = parseValue(tokens); 176 | return { 177 | type: "VariableDeclaration", 178 | id, 179 | value, 180 | } 181 | } 182 | 183 | /* 184 | ## TRANSFORMATION ## 185 | */ 186 | const constructWithReferences = AST => AST; 187 | const transformer = AST => { 188 | /* ... */ 189 | const unusedVars = AST.statements.reduce((acc, curr) => { 190 | if (curr.type === 'VariableDeclaration') { 191 | acc.push({ name: curr.id.value, used: false }); 192 | visit(curr.value, acc); 193 | return acc; 194 | } 195 | visit(curr, acc); 196 | return acc; 197 | }, []).map(u => !u.used ? u.name : undefined); 198 | return traverse(AST, unusedVars); // a modified AST 199 | }; 200 | 201 | const visit = (AST, declaredVars) => { 202 | if (!AST || typeof AST !== "object") return; 203 | if (AST.type === 'Identifier') { 204 | const foundVariable = declaredVars.find(v => v.name === AST.value); 205 | if (foundVariable) { 206 | foundVariable.used = true; 207 | } 208 | } 209 | const keys = Object.keys(AST); 210 | keys.forEach(k => visit(AST[k], declaredVars)); 211 | } 212 | 213 | const traverse = (AST, unusedVars) => { 214 | let traverser = traversers[AST.type]; 215 | if (!traverser) { 216 | console.error(`Traverser does not exist for this type: ${AST.type}`); 217 | return; 218 | } 219 | return traverser(AST, unusedVars); 220 | } 221 | 222 | const traversers = { 223 | Program: (AST, unusedVars) => { 224 | return { 225 | type: AST.type, 226 | statements: AST.statements.map(s => traverse(s, unusedVars)).filter(i=>i), 227 | } 228 | }, 229 | VariableAssignment: (AST, unusedVars) => { 230 | return { 231 | type: AST.type, 232 | id: traverse(AST.id, unusedVars), 233 | value: traverse(AST.value, unusedVars), 234 | } 235 | }, 236 | VariableDeclaration: (AST, unusedVars) => { 237 | const { value } = traverse(AST.id); 238 | if (unusedVars && unusedVars.includes(value)) return; 239 | return { 240 | type: AST.type, 241 | id: traverse(AST.id, unusedVars), 242 | value: traverse(AST.value, unusedVars) 243 | } 244 | }, 245 | Identifier: (AST, unusedVars) => ({ 246 | type: AST.type, 247 | value: AST.value, 248 | }), 249 | Number: (AST, unusedVars) => ({ 250 | type: AST.type, 251 | value: AST.value, 252 | }), 253 | BinaryExpression: (AST, unusedVars) => ({ 254 | type: AST.type, 255 | left: traverse(AST.left, unusedVars), 256 | operator: AST.operator, 257 | right: traverse(AST.right, unusedVars) 258 | }), 259 | DefaultExportExpression: (AST, unusedVars) => { 260 | return { 261 | type: AST.type, 262 | value: traverse(AST.value, unusedVars), 263 | } 264 | } 265 | } 266 | 267 | const generator = AST => { 268 | /* ... */ 269 | return convertToString(AST) 270 | }; 271 | 272 | const convertToString = AST => { 273 | const generator = generators[AST.type]; 274 | if (!generator) { 275 | console.error(`generator does not exist for this type: ${AST.type}`); 276 | return; 277 | } 278 | return generator(AST); 279 | } 280 | 281 | const generators = { 282 | DefaultExportExpression: AST => `export default ${convertToString(AST.value)}`, 283 | Program: AST => { 284 | if (!AST.statements) return ''; 285 | const code = AST.statements.map(convertToString).join('\n'); 286 | return code; 287 | }, 288 | BinaryExpression: AST => { 289 | return `${convertToString(AST.left)} ${AST.operator} ${convertToString(AST.right)}` 290 | }, 291 | VariableAssignment: AST => { 292 | return `${convertToString(AST.id)} = ${convertToString(AST.value)}`; 293 | }, 294 | VariableDeclaration: (AST) => { 295 | return `let ${convertToString(AST.id)} = ${convertToString(AST.value)}`; 296 | }, 297 | Identifier: (AST) => { 298 | return AST.value; 299 | }, 300 | Number: (AST) => { 301 | return AST.value; 302 | } 303 | } 304 | 305 | const generate = code => { 306 | const tokens = tokenizer(code); 307 | const AST = parser(tokens); 308 | const transformedAST = transformer(AST); 309 | const newCode = generator(transformedAST); 310 | return newCode; 311 | }; 312 | 313 | module.exports = generate; 314 | module.exports.tokenizer = tokenizer; 315 | module.exports.parser = parser; 316 | module.exports.transformer = transformer; 317 | module.exports.generator = generator; 318 | -------------------------------------------------------------------------------- /201808-coup/README.md: -------------------------------------------------------------------------------- 1 | August 2018 challenge 2 | ===================== 3 | 4 | This months challenge consists of you writing a bot to compete against other bots in the game of [COUP](http://gamegrumps.wikia.com/wiki/Coup). 5 | We will have the three rounds of (1,000,000) games to find the winner (sum all scores). Between each round you have time to make adjustments to your bot. 6 | Dates: 7 | - First round: **on Wednesday the 25th July** 8 | - Second round: **on Wednesday the 1st August**. 9 | - Final round: **on Wednesday the 8th of August**. 10 | 11 | ## RULEZ 12 | 13 | 1. Node only 14 | 1. No dependencies 15 | 1. No changes to engine 16 | 1. Name folder appropriately (so you can target specific bots) 17 | 1. No data sharing between games 18 | 1. No access to other bots 19 | 1. No changing other bots 20 | 1. No Internet 21 | 1. No js prototype changing 22 | 1. Your code has to stay inside your bots folder 23 | 1. Do not output to `stdout` 24 | 1. At the beginning of each round you add PRs to the repo (we only merge on the day the round begins) 25 | 26 | ## Scoring 27 | 28 | Each game is a zero-sum-game in terms of score. The score is determined by the number of players (can't be more than 6 per game) and winners 29 | (there are instances where the game can stall in a stale-mate with multiple winners). 30 | Each game will take a max of 6 bots that are randomly elected. Those who win get positive score, those who lose will get negative score. 31 | 32 | - Score for losers: -1/(players-1) 33 | - Score for winners: ∑losers/winners 34 | 35 | ## How to run the game? 36 | 37 | The game comes with a simple "dumb" bot that just randomizes it's answers without checking much whether the actions are appropriate. 38 | Each bot lives inside a folder and is named after that folder name. 39 | 40 | ```sh 41 | . 42 | ├── bot1 43 | │   └── index.js 44 | ├── bot2 45 | │   └── index.js 46 | ├── bot3 47 | │   └── index.js 48 | │ 49 | ├── README.md 50 | ├── constants.js 51 | ├── helper.js 52 | ├── index.js 53 | └── test.js 54 | ``` 55 | 56 | To run the game `cd` into the challenge `201808` folder. 57 | Install dependencies (`prettier` and `pre-commit`). 58 | 59 | ```sh 60 | yarn 61 | ``` 62 | 63 | **Do make sure you run the formatter before each commit** 64 | On the pre commit hook perrier runs and checks if there are any changes. 65 | If it finds changes it will stop the commit. 66 | Run the formatter via: 67 | 68 | ```sh 69 | yarn format 70 | ``` 71 | 72 | To play the game run: 73 | 74 | ```sh 75 | yarn play 76 | ``` 77 | 78 | To run 1000 games: 79 | 80 | ```sh 81 | yarn loop 82 | ``` 83 | 84 | To run `n` number of games: 85 | 86 | ```sh 87 | yarn loop -- -r [n] 88 | ``` 89 | 90 | In the loop rounds all output is suppressed so that the games run smoothly on the day. 91 | For development please use the `-d` flag to enable debug mode. It will stop the game loop when it 92 | encounters an error and display the last game with error. 93 | 94 | ```sh 95 | yarn loop -r [number] -d 96 | ``` 97 | 98 | To run the test suit: 99 | 100 | ```sh 101 | yarn test 102 | ``` 103 | 104 | 105 | ## 106 | 107 | 108 | ## How do I build a bot? 109 | 110 | - Create a folder in the root (next to the fake bot) 111 | - Pick the name of the folder from the player list below 112 | - Include an `index.js` file that exports below class 113 | - Run as many test rounds as you want to 114 | - Create PR on the day of each round 115 | 116 | You get to require 4 functions from the engine at `constants.js` inside your bot: 117 | 118 | - `ALLBOTS()` Returns an array of all players in the game `` 119 | - `CARDS()` Returns an array of all 5 card types `` 120 | - `DECK()` Returns an array of all cards in the deck (3 of each) 121 | - `ACTIONS()` Returns an array of all actions `` 122 | 123 | > TIP: If you console log out the string `STOP` the loop will stop as soon as a game prints this and print everything out from that game. Great for debugging. 124 | > Just make sure you remove the console log before submitting. 125 | 126 | ### `` 127 | 128 | - `AbbasA` 129 | - `BenC` 130 | - `BorisB` 131 | - `CharlesL` 132 | - `JedW` 133 | - `DomW` 134 | - `JessT` 135 | - `JohnM` 136 | - `JossM` 137 | - `KevinY` 138 | - `LaurenA` 139 | - `MalB` 140 | - `MikeG` 141 | - `MikeH` 142 | - `NathS` 143 | - `SanjiyaD` 144 | - `TiciA` 145 | - `TimL` 146 | - `TomW` 147 | - `TuanH` 148 | 149 | ### `` 150 | 151 | - `duke` 152 | - `assassin` 153 | - `captain` 154 | - `ambassador` 155 | - `contessa` 156 | 157 | ### `` 158 | 159 | - `taking-1` 160 | - `foreign-aid` 161 | - `couping` 162 | - `taking-3` 163 | - `assassination` 164 | - `stealing` 165 | - `swapping` 166 | 167 | ### `` 168 | 169 | - `foreign-aid` -> [`duke`, `false`], 170 | - `assassination` -> [`contessa`, `false`], 171 | - `stealing` -> [`captain`, `ambassador`, `false`], 172 | - `taking-3` -> [`duke`, `false`], 173 | 174 | ### Class to export 175 | 176 | The class you have to export from your bot needs to include the below methods: 177 | 178 | - `OnTurn` 179 | - Called when it is your turn to decide what you may want to do 180 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards }` 181 | - returns: `{ action: , against: }` 182 | - `OnChallengeActionRound` 183 | - Called when another bot made an action and everyone get's to decide whether they want to challenge that action 184 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom, toWhom }` 185 | - returns: `` 186 | - `OnCounterAction` 187 | - Called when someone does something that can be countered with a card: `foreign-aid`, `stealing` and `assassination` 188 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom }` 189 | - returns: `` 190 | - `OnCounterActionRound` 191 | - Called when a bot did a counter action and everyone get's to decided whether they want to challenge that counter action 192 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards, action, byWhom, toWhom, card, counterer }` 193 | - returns: `` 194 | - `OnSwappingCards` 195 | - Called when you played your ambassador and now need to decide which cards you want to keep 196 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards, newCards }` 197 | - returns: `Array()` 198 | - `OnCardLoss` 199 | - Called when you lose a card to decide which one you want to lose 200 | - parameters: `{ history, myCards, myCoins, otherPlayers, discardedCards }` 201 | - returns: `` 202 | 203 | ### The parameters 204 | 205 | Each function is passed one parameter object that can be deconstructed into the below items. 206 | 207 | | parameter | description | 208 | |------------------|-------------------------------| 209 | | `history` | The history array. More below `Array()` | 210 | | `myCards` | An array of your cards `Array()` | 211 | | `myCoins` | The number of coins you have | 212 | | `otherPlayers` | An array of objects of each player, format: `[{ name: , coins: , cards: }, { name: , coins: , cards: }]` | 213 | | `discardedCards` | An array of all cards that have been discarded so far (from penalties, coups or assassinations) | 214 | | `action` | The action that was taken `` | 215 | | `byWhom` | Who did the action `` | 216 | | `toWhom` | To whom is the action directed `` | 217 | | `card` | A string of the counter action taken by the previous bot 218 | | `newCards` | An array of cards for the ambassador swap `Array()` | 219 | | `counterer` | The player who countered an action | 220 | 221 | ### The history array 222 | 223 | Each event is recorded in the history array. See below a list of all events and it's entires: 224 | 225 | An action: 226 | ``` 227 | { 228 | type: 'action', 229 | action: , 230 | from: , 231 | to: , 232 | } 233 | ``` 234 | 235 | Lose a card: 236 | ``` 237 | { 238 | type: 'lost-card', 239 | player: , 240 | lost: , 241 | } 242 | ``` 243 | 244 | Challenge outcome: 245 | ``` 246 | { 247 | type: 'challenge-round' || 'counter-round', 248 | challenger: , 249 | challengee: , 250 | player: , 251 | action: , 252 | lying: , 253 | } 254 | ``` 255 | 256 | A Penalty: 257 | ``` 258 | { 259 | type: 'penalty', 260 | from: , 261 | } 262 | ``` 263 | 264 | An unsuccessful challenge: 265 | ``` 266 | { 267 | type: 'unsuccessful-challenge', 268 | action: 'swap-1', 269 | from: , 270 | } 271 | ``` 272 | 273 | A counter action: 274 | ``` 275 | { 276 | type: 'counter-action', 277 | action: , 278 | from: , 279 | to: , 280 | counter: , 281 | counterer: , 282 | } 283 | ``` 284 | 285 | ## How does the engine work? 286 | 287 | The challenge algorithm: 288 | 289 | ``` 290 | if( assassination, stealing, swapping ) 291 | ChallengeRound via all bot.OnChallengeActionRound 292 | ? false = continue 293 | : true = stop 294 | 295 | if( foreign-aid, assassination, stealing ) 296 | CounterAction via bot.OnCounterAction 297 | ? false = continue 298 | : true = CounterChallengeRound via bot.OnCounterActionRound 299 | ? false = continue 300 | : true = stop 301 | 302 | else 303 | do-the-thing 304 | ``` 305 | -------------------------------------------------------------------------------- /201808-coup/NathS/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | constructor() { 7 | this.turn = 0; 8 | } 9 | 10 | shuffleActions(actions) { 11 | return actions[Math.floor(Math.random() * actions.length)]; 12 | } 13 | 14 | count(card, pile) { 15 | let counter = 0; 16 | pile.forEach((visibleCard) => { 17 | if (visibleCard == card) { 18 | counter++; 19 | } 20 | }); 21 | return counter; 22 | } 23 | 24 | removeAction(action, actionsAvailable) { 25 | if (actionsAvailable.includes(action)) 26 | actionsAvailable.splice(actionsAvailable.indexOf(action), 1); 27 | //return actionsAvailable; 28 | } 29 | 30 | ifCounteredPreviously(history, player, action) { 31 | history.forEach((event) => { 32 | if ( 33 | event.type == 'counter-action' && 34 | event.from == player.name && 35 | event.action == action 36 | ) { 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | }); 42 | } 43 | 44 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 45 | this.turn++; 46 | 47 | // console.log("======================================="); 48 | // console.log("Nathan: " + myCards); 49 | //console.log("Discarded: " + discardedCards); 50 | 51 | let action; 52 | let actionsAvailable = ACTIONS(); 53 | let against = 54 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 55 | 56 | // Dequalify actions 57 | //don't coup if I have less than 7 coins 58 | if (myCoins < 7) { 59 | this.removeAction('couping', actionsAvailable); 60 | } 61 | 62 | //dont assassinate if I have less than 3 coins 63 | if (myCoins < 3) { 64 | this.removeAction('assassination', actionsAvailable); 65 | } 66 | 67 | //dont assassinate if I have less than 3 coins 68 | if (myCards.length == 1 && !myCards.includes('duke')) { 69 | this.removeAction('taking-3', actionsAvailable); 70 | } 71 | 72 | //don't swap if I have cards that I like 73 | if (myCards.includes('duke') || myCards.includes('assassin')) { 74 | this.removeAction('swapping', actionsAvailable); 75 | } 76 | 77 | // don't swap if I have 1 card that is not an ambassador 78 | if (myCards.length == 1 && !myCards.includes('ambassador')) { 79 | this.removeAction('swapping', actionsAvailable); 80 | } 81 | 82 | if ( 83 | this.count('captain', discardedCards) >= 2 && 84 | !myCards.includes('captain') 85 | ) { 86 | this.removeAction('stealing', actionsAvailable); 87 | } 88 | 89 | if (this.count('assassin', discardedCards) == 3) { 90 | this.removeAction('assassination', actionsAvailable); 91 | } 92 | 93 | if (this.count('ambassador', discardedCards) == 3) { 94 | this.removeAction('swapping', actionsAvailable); 95 | } 96 | 97 | if (this.count('duke', discardedCards) == 3) { 98 | this.removeAction('taking-3', actionsAvailable); 99 | } 100 | 101 | // don't steal if no one has 2 or more coins... 102 | let letsSteal = false; 103 | otherPlayers.some((player) => { 104 | if (player.coins > 2) { 105 | letsSteal = true; 106 | return true; 107 | } 108 | }); 109 | // ...or if I have 3 or more coins, and no captain 110 | if ( 111 | !letsSteal || 112 | (myCards.length == 1 && !myCards.includes('captain')) || 113 | (myCoins >= 3 && !myCards.includes('captain')) 114 | ) { 115 | this.removeAction('stealing', actionsAvailable); 116 | } 117 | 118 | //don't swap if I have lots of coins and I don't have an ambassador 119 | if (myCoins > 4 && !myCards.includes('ambassador')) { 120 | this.removeAction('swapping', actionsAvailable); 121 | } 122 | 123 | //don't assassinate if I have one card left, which is not an assassin 124 | if (myCards.length == 1 && !myCards.includes('assassin')) { 125 | this.removeAction('assassination', actionsAvailable); 126 | } 127 | 128 | // randomly choose action 129 | action = this.shuffleActions(actionsAvailable); 130 | 131 | // overwrite actions 132 | 133 | //take 3 coins until I get called out on it 134 | if (myCards.includes('duke')) { 135 | action = 'taking-3'; 136 | } 137 | 138 | //decide if I should swap 139 | if ( 140 | (myCards.includes('ambassador') || myCards[0] == myCards[1]) && 141 | actionsAvailable.includes('swapping') 142 | ) { 143 | action = this.shuffleActions([action, 'swapping']); 144 | } 145 | 146 | if (myCards.includes('assassin') && myCoins >= 3) { 147 | //if there is one player left and I have two cards, targeted attack! 148 | if (otherPlayers.length === 1 && myCards.length === 2) { 149 | action = 'assassination'; 150 | } 151 | //find someone to pick on 152 | else { 153 | const target = otherPlayers.find((player) => player.cards.length === 1); 154 | if (target) { 155 | action = 'assassination'; 156 | against = target; 157 | } 158 | 159 | // no target found 160 | if (!target) { 161 | action = this.shuffleActions([action, 'assassination']); 162 | } 163 | } 164 | } 165 | 166 | //if I can steal, I am trying to pick up, steal instead 167 | if ( 168 | (action == 'taking-1' || 169 | action == 'taking-3' || 170 | action == 'foreign-aid') && 171 | myCards.includes('captain') && 172 | actionsAvailable.includes('stealing') 173 | ) { 174 | action = 'stealing'; 175 | } 176 | 177 | //try to find someone to steal from 178 | if (action == 'stealing') { 179 | otherPlayers.some((player) => { 180 | if ( 181 | (player.coins >= 2) & 182 | !this.ifCounteredPreviously(history, player, 'stealing') 183 | ) { 184 | against = player.name; 185 | return true; 186 | } 187 | }); 188 | } 189 | 190 | if (this.turn == 1) { 191 | if (myCards.includes('duke')) { 192 | action = 'taking-3'; 193 | } else { 194 | action = 'foreign-aid'; 195 | } 196 | } 197 | 198 | if (myCoins >= 7) { 199 | if (myCards.includes('assassin')) { 200 | action = 'assassination'; 201 | } else { 202 | action = 'couping'; 203 | } 204 | } 205 | 206 | if (myCoins >= 10) { 207 | action = 'couping'; 208 | } 209 | 210 | return { action, against }; 211 | } 212 | 213 | OnChallengeActionRound({ 214 | history, 215 | myCards, 216 | myCoins, 217 | otherPlayers, 218 | discardedCards, 219 | action, 220 | byWhom, 221 | toWhom, 222 | }) { 223 | const visibleCards = [...myCards, ...discardedCards]; 224 | 225 | if (action == 'stealing') { 226 | if (toWhom == 'NathS' && action == 'stealing' && myCoins < 2) { 227 | return false; 228 | } else if (this.count('captain', visibleCards) == 3) { 229 | return true; 230 | } else { 231 | return false; 232 | } 233 | } else if (action == 'assassination') { 234 | if (toWhom == 'NathS' && myCards.length == 1) { 235 | return true; 236 | } else if (this.count('assassin', visibleCards) == 3) { 237 | return true; 238 | } else { 239 | return false; 240 | } 241 | } else if (action == 'exchange') { 242 | if (this.count('ambassador', visibleCards) == 3) { 243 | return true; 244 | } else { 245 | return false; 246 | } 247 | } else if (action == 'taking-3') { 248 | if (this.count('duke', visibleCards) == 3) { 249 | return true; 250 | } else { 251 | return false; 252 | } 253 | } else { 254 | return false; 255 | } 256 | } 257 | 258 | OnCounterAction({ 259 | history, 260 | myCards, 261 | myCoins, 262 | otherPlayers, 263 | discardedCards, 264 | action, 265 | byWhom, 266 | }) { 267 | const visibleCards = [...myCards, ...discardedCards]; 268 | 269 | if (action === 'assassination') { 270 | if ( 271 | myCards.includes('contessa') || 272 | (myCards.length === 1 && this.count('contessa', discardedCards) < 1) 273 | ) { 274 | return 'contessa'; 275 | } 276 | return false; 277 | } else if (action === 'stealing') { 278 | if (myCards.includes('ambassador')) { 279 | return 'ambassador'; 280 | } else if (myCards.includes('captain')) { 281 | return 'captain'; 282 | } else if (myCoins == 0) { 283 | return false; 284 | } else { 285 | return false; 286 | } 287 | } 288 | } 289 | 290 | OnCounterActionRound({ 291 | history, 292 | myCards, 293 | myCoins, 294 | otherPlayers, 295 | discardedCards, 296 | action, 297 | byWhom, 298 | toWhom, 299 | card, 300 | }) { 301 | const visibleCards = [...myCards, ...discardedCards]; 302 | 303 | if (action == 'blocking' && this.count('duke', visibleCards) == 3) { 304 | return true; 305 | } 306 | 307 | if (action == 'assassination') { 308 | //there is one player left with one card, and I have two cards left 309 | if ( 310 | otherPlayers.length == 1 && 311 | otherPlayers[0].cards.length == 1 && 312 | myCards == 2 313 | ) { 314 | return true; 315 | } else if (this.count('contessa', visibleCards) < 1) { 316 | return true; 317 | } else if (toWhom == 'NathS' && myCards.length == 1) { 318 | return true; 319 | } else { 320 | return false; 321 | } 322 | } 323 | } 324 | 325 | OnSwappingCards({ 326 | history, 327 | myCards, 328 | myCoins, 329 | otherPlayers, 330 | discardedCards, 331 | newCards, 332 | }) { 333 | return newCards; 334 | } 335 | 336 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 337 | return myCards[0]; 338 | } 339 | } 340 | 341 | module.exports = exports = BOT; 342 | -------------------------------------------------------------------------------- /201810-maze/jesstelford/index.js: -------------------------------------------------------------------------------- 1 | const X = 0; 2 | const Y = 1; 3 | const VISIBLE_DISTANCE = 2; 4 | const VISIBLE_WIDTH = (VISIBLE_DISTANCE * 2) + 1; 5 | const VISIBLE_HEIGHT = (VISIBLE_DISTANCE * 2) + 1; 6 | const IS_TRAVERSIBLE = true; 7 | 8 | const spliceImmutably = (collection, index, deleteNum = collection.length, newStuff = []) => { 9 | if (index < 0) { throw new Error('Index must be a positive integer'); } 10 | if (deleteNum < 0) { throw new Error('deleteNum must be a positive integer'); } 11 | if (!Array.isArray(newStuff)) { throw new Error('Must provide an array to insert'); } 12 | return collection.slice(0, index).concat(...newStuff, collection.slice(index + deleteNum)); 13 | }; 14 | 15 | // TODO implement a non-naive algorithm 16 | function createPriorityQueue(comparator) { 17 | const queue = []; 18 | 19 | function indexOfLowestPriority() { 20 | let lowestPriority = Infinity; 21 | let lowestIndex = -1; 22 | 23 | for (let index = 0; index < queue.length; index += 1) { 24 | if (comparator(queue[index].priority, lowestPriority) < 0) { 25 | lowestPriority = queue[index].priority; 26 | lowestIndex = index; 27 | } 28 | } 29 | 30 | return lowestIndex; 31 | } 32 | 33 | function indexOfItem(item) { 34 | return queue.find(item => item.item === item); 35 | } 36 | 37 | return { 38 | // returns the (numerically) lowest priority of any item in the queue (or infinity if the queue is empty) 39 | topKey() { 40 | if (!queue.length) { 41 | return Infinity; 42 | } 43 | return queue[indexOfLowestPriority()].priority; 44 | }, 45 | 46 | // removes the item with the lowest priority from the queue and returns it 47 | pop() { 48 | if (!queue.length) { 49 | return null; 50 | } 51 | 52 | const index = indexOfLowestPriority(); 53 | const result = queue[index]; 54 | 55 | queue.splice(index, 1); 56 | 57 | return result.item; 58 | }, 59 | 60 | // inserts a item with a given priority into the queue 61 | insert(item, priority) { 62 | queue.push({ item, priority }); 63 | }, 64 | 65 | // removes a item from the queue 66 | remove(item) { 67 | const index = indexOfItem(item); 68 | if (index !== -1) { 69 | queue.splice(index, 1); 70 | } 71 | }, 72 | 73 | // returns true if the queue contains the specified item, false if not 74 | contains(item) { 75 | const index = indexOfItem(item); 76 | return index !== -1; 77 | }, 78 | }; 79 | } 80 | 81 | function createAstar(map, { width, height }) { 82 | return { 83 | computeShortestPath([startX, startY], [endX, endY]) { 84 | // The set of nodes already evaluated 85 | const closedSet = []; 86 | 87 | // The set of currently discovered nodes that are not evaluated yet. 88 | // Initially, only the start node is known. 89 | const openSet = [[startX, startY]]; 90 | 91 | const nodeMap = map.map(row => row.map(() => ({ 92 | // For each node, which node it can most efficiently be reached from. 93 | // If a node can be reached from many nodes, cameFrom will eventually contain the 94 | // most efficient previous step. 95 | cameFrom: null, 96 | 97 | // For each node, the cost of getting from the start node to that node. 98 | gScore: Infinity, 99 | 100 | // For each node, the total cost of getting from the start node to the goal 101 | // by passing by that node. That value is partly known, partly heuristic. 102 | fScore: Infinity, 103 | }))); 104 | 105 | // The cost of going from start to start is zero. 106 | nodeMap[startY][startX].gScore = 0; 107 | 108 | // For the first node, that value is completely heuristic. 109 | nodeMap[startY][startX].fScore = heuristic([startX, startY], [endX, endY]); 110 | 111 | while (openSet.length) { 112 | const [currentX, currentY] = popLowestFScoreFromOpen(openSet, nodeMap); 113 | if (currentX === endX && currentY === endY) { 114 | return reconstructPath([startX, startY], [endX, endY], nodeMap); 115 | } 116 | 117 | closedSet.push([currentX, currentY]); 118 | 119 | getNeighbours(currentX, currentY).forEach(([neighbourX, neighbourY]) => { 120 | if (closedSet.find(([closedX, closedY]) => neighbourX === closedX && neighbourY === closedY)) { 121 | return; 122 | } 123 | 124 | const currentGScore = nodeMap[currentY][currentX].gScore + getCostBetween([currentX, currentY], [neighbourX, neighbourY]) 125 | 126 | if (!openSet.find(([openX, openY]) => neighbourX === openX && neighbourY === openY)) { 127 | // New node discovered 128 | openSet.push([neighbourX, neighbourY]); 129 | } else if (currentGScore >= nodeMap[neighbourY][neighbourX].gScore) { 130 | // This is not a better path 131 | return; 132 | } 133 | 134 | // This is the best path so far, so we record it 135 | nodeMap[neighbourY][neighbourX].cameFrom = [currentX, currentY]; 136 | nodeMap[neighbourY][neighbourX].gScore = currentGScore; 137 | nodeMap[neighbourY][neighbourX].fScore = nodeMap[neighbourY][neighbourX].gScore + heuristic([neighbourX, neighbourY], [endX, endY]); 138 | }); 139 | 140 | 141 | } 142 | }, 143 | } 144 | 145 | function getCostBetween() { 146 | // NOTE: No diagonal movements 147 | return 1; 148 | } 149 | 150 | function getNeighbours(x, y) { 151 | const result = []; 152 | 153 | if (x > 0 && map[y][x - 1] === IS_TRAVERSIBLE) { 154 | result.push([x - 1, y]); 155 | } 156 | 157 | if (y > 0 && map[y - 1][x] === IS_TRAVERSIBLE) { 158 | result.push([x, y - 1]); 159 | } 160 | 161 | if (x < width - 1 && map[y][x + 1] === IS_TRAVERSIBLE) { 162 | result.push([x + 1, y]); 163 | } 164 | 165 | if (y < height - 1 && map[y + 1][x] === IS_TRAVERSIBLE) { 166 | result.push([x, y + 1]); 167 | } 168 | 169 | return result; 170 | } 171 | 172 | // Return the best path (inclusive of the starting position) 173 | function reconstructPath([startX, startY], [endX, endY], nodeMap) { 174 | const path = [[endX, endY]]; 175 | 176 | let currentX = endX; 177 | let currentY = endY; 178 | 179 | while (currentX !== startX || currentY !== startY) { 180 | [currentX, currentY] = nodeMap[currentY][currentX].cameFrom; 181 | path.unshift([currentX, currentY]); 182 | } 183 | 184 | return path; 185 | } 186 | 187 | function popLowestFScoreFromOpen(openSet, nodeMap) { 188 | let lowestScore = Infinity; 189 | let lowestIndex = -1; 190 | openSet.forEach(([x, y], index) => { 191 | if (nodeMap[y][x].fScore < lowestScore) { 192 | lowestScore = nodeMap[y][x].fScore; 193 | lowestIndex = index; 194 | } 195 | }); 196 | 197 | if (lowestIndex !== -1) { 198 | // capture result 199 | const result = openSet[lowestIndex]; 200 | // remove it from the array 201 | openSet.splice(lowestIndex, 1); 202 | // return it 203 | return result; 204 | } 205 | 206 | return null; 207 | } 208 | 209 | function heuristic([fromX, fromY], [endX, endY]) { 210 | // NOTE: No diagonal movements, so the diagonal is not an accurate heuristic 211 | // Instead we use the straight edge distances 212 | return Math.abs(endX - fromX) + Math.abs(endY - fromY); 213 | } 214 | } 215 | 216 | class BOT { 217 | // size = {width: 50, height: 15} 218 | // start = [0, 0] 219 | // end = [14, 49] 220 | constructor({ size, start, end }) { 221 | // The coordinates come in as [y, x], so we swap them for usage internally 222 | this.start = [start[1], start[0]]; 223 | this.end = [end[1], end[0]]; 224 | this.position = [...this.start]; 225 | this.size = {...size}; 226 | 227 | this.map = Array(size.height).fill(null).map(() => 228 | // Default to the entire map being traversable 229 | Array(size.width).fill(IS_TRAVERSIBLE) 230 | ); 231 | 232 | this.astar = createAstar(this.map, this.size); 233 | 234 | this.Move = this.Move.bind(this); 235 | this.mergeVisibleRangeIntoMap = this.mergeVisibleRangeIntoMap.bind(this); 236 | } 237 | 238 | mergeVisibleRangeIntoMap(visible) { 239 | const startRow = Math.max(0, this.position[Y] - VISIBLE_DISTANCE); 240 | const endRow = Math.min(this.size.height - 1, this.position[Y] + VISIBLE_DISTANCE); 241 | const startCol = Math.max(0, this.position[X] - VISIBLE_DISTANCE); 242 | const endCol = Math.min(this.size.width - 1, this.position[X] + VISIBLE_DISTANCE); 243 | const visibleRowOffset = -Math.min(0, this.position[Y] - VISIBLE_DISTANCE) 244 | const visibleColOffset = -Math.min(0, this.position[X] - VISIBLE_DISTANCE) 245 | 246 | const aStarNodes = []; 247 | 248 | // TODO: Could we make this an immutable operation? 249 | for (let mapRow = startRow; mapRow <= endRow; mapRow += 1) { 250 | const visibleRow = visible[visibleRowOffset + (mapRow - startRow)].slice(visibleColOffset); 251 | this.map[mapRow] = spliceImmutably(this.map[mapRow], startCol, (endCol - startCol) + 1, visibleRow); 252 | 253 | // Queue up node changes for Astar 254 | aStarNodes.push(...visibleRow.map((traversible, visibleIndex) => ({ 255 | x: visibleColOffset + visibleIndex, 256 | y: mapRow, 257 | traversible, 258 | }))); 259 | } 260 | }; 261 | 262 | Move({ MAP }) { 263 | this.mergeVisibleRangeIntoMap(MAP); 264 | const path = this.astar.computeShortestPath(this.position, this.end); 265 | 266 | // Drop the 'current position' from the start 267 | path.shift(); 268 | 269 | if (!path.length) { 270 | throw new Error('No path found'); 271 | } 272 | 273 | const [nextX, nextY] = path[0]; 274 | 275 | let action; 276 | 277 | if (nextX < this.position[X]) { 278 | action = 'left'; 279 | this.position[X] = this.position[X] - 1; 280 | } else if (nextX > this.position[X]) { 281 | action = 'right'; 282 | this.position[X] = this.position[X] + 1; 283 | } else if (nextY < this.position[Y]) { 284 | action = 'up'; 285 | this.position[Y] = this.position[Y] - 1; 286 | } else if (nextY > this.position[Y]) { 287 | action = 'down'; 288 | this.position[Y] = this.position[Y] + 1; 289 | } 290 | 291 | return action; 292 | } 293 | } 294 | 295 | module.exports = exports = BOT; 296 | -------------------------------------------------------------------------------- /201808-coup/BenC/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 'use strict'; 3 | 4 | const DO_NOT_CALL_THIS_FUNCTION = () => { 5 | DO_NOT_CALL_THIS_FUNCTION(); 6 | }; 7 | 8 | const { 9 | ALLBOTS: LuomaFaupelVandaeleDubreeVienneauLapdImpress, 10 | DECK: AmdPeepeeAtvDey, 11 | ACTIONS: ZillahReehMerciDenyMealoLuciennePos /*: { 12 | DECK: any, ALLBOTS: any, ACTIONS: () => [ 13 | 'taking-1', 14 | 'foreign-aid', 15 | 'couping', 16 | 'taking-3', 17 | 'assassination', 18 | 'stealing', 19 | 'swapping', 20 | ] } */, 21 | } = 22 | // $FlowFixMe 23 | require('../constants.js'); 24 | 25 | /*:: 26 | type Card = 'duke' | 'assassin' | 'captain' | 'ambassador' | 'contessa' 27 | type SoloActions = 'taking-1' | 'foreign-aid' | 'swapping'| 'taking-3' 28 | type Interactions = 'couping' | 'assassination' | 'stealing' 29 | type AllActions = SoloActions | Interactions 30 | type CounterAction = 'foreign-aid' |'assassination' |'stealing' 31 | 32 | type UntargetedAction = { 33 | type: 'action', 34 | action: SoloActions, 35 | from: string, 36 | } 37 | type TargetedAction = { 38 | type: 'action', 39 | action: Interactions, 40 | from: string, 41 | to: string, 42 | } 43 | 44 | type AllActionTypes = UntargetedAction | TargetedAction 45 | 46 | type LoseCard = { type: 'lost-card', player: string, lost: Card } 47 | 48 | // TODO: complete this type 49 | type HistoryTypes = AllActionTypes | LoseCard 50 | 51 | type Player = { name: string, coins: number, cards: number } 52 | type MyCards = [Card, Card] 53 | // type MyCards = [Card, Card] | [Card] 54 | type History = Array; 55 | 56 | type BaseInfo = { 57 | history: History, 58 | myCards: MyCards, 59 | myCoins: number, 60 | otherPlayers: Player[], 61 | discardedCards: Card[] 62 | } 63 | 64 | type Turn = BaseInfo 65 | type Lose = BaseInfo 66 | type Challenge = BaseInfo & { 67 | action: AllActions, 68 | byWhom: string, 69 | toWhom: string 70 | } 71 | type Counter = BaseInfo & { 72 | action: AllActions, 73 | byWhom: string, 74 | } 75 | type CounterRound = BaseInfo & {} 76 | type Swap = BaseInfo & { newCards: [Card, Card] } 77 | */ 78 | 79 | /* 80 | Base logic assumptions: 81 | 6 players 82 | 15 cards all up 83 | 12 cards dealt 84 | 3 cards in deck 85 | 86 | Lie consistently if lying 87 | Set up several play styles and randomly select one at the beginning of the game 88 | Track the claims of others, do maths around what cards are where (esp for shuffle to deck) 89 | Learn better maths to map this 90 | 91 | Obv rules: 92 | If stealing from someone fails, don't try it again 93 | If assassinating someone fails, don't try it again 94 | (fix challenging counter-actions to be less hard random) 95 | */ 96 | 97 | const maddAdoption = (kazooOffhand, options, borchard) => { 98 | if (kazooOffhand.length < 2 && options.includes(borchard)) { 99 | return [...kazooOffhand, borchard]; 100 | } else return kazooOffhand; 101 | }; 102 | 103 | const sandomIndex = (num /*: number */) => 104 | /*: number */ Math.floor(Math.random() * num); 105 | 106 | const bightMance = (waltzAnse, distractions) => { 107 | if (sandomIndex(waltzAnse) + 1 !== waltzAnse) return false; 108 | else return distractions[sandomIndex(distractions.length + 1)]; 109 | }; 110 | 111 | const balabanGuyOhearnDenyLancon = ( 112 | nankaiRetards /*: MyCards*/, 113 | chiGoins /*: number*/ 114 | ) => /*: Array */ { 115 | let fractions = ['taking-1']; 116 | if (nankaiRetards.includes('assassin') && chiGoins > 3) 117 | fractions.push('assassination'); 118 | if (chiGoins > 7) fractions.push('couping'); 119 | nankaiRetards.forEach((slee) => 120 | fractions.concat(papBurchardTenutaInfractions(slee).actions) 121 | ); 122 | return fractions; 123 | }; 124 | 125 | // /*: { actions: Array, counters: } */ 126 | const papBurchardTenutaInfractions = ( 127 | yarde /*: Card */ 128 | ) => /*: { actions: Array, counters: Array } */ { 129 | if ('duke') return { actions: ['taking-3'], counters: ['foreign-aid'] }; 130 | if ('assassin') return { actions: [], counters: [] }; 131 | if ('captain') return { actions: ['stealing'], counters: ['stealing'] }; 132 | if ('ambassador') return { actions: ['swapping'], counters: ['stealing'] }; 133 | if ('contessa') return { actions: [], counters: ['assassination'] }; 134 | else return { actions: [], counters: [] }; 135 | }; 136 | 137 | const tieDisqueRegester = (smotherZayres /*: Array */) => { 138 | return smotherZayres.sort((herst, reckoned) => { 139 | if (herst.coins === reckoned.coins) { 140 | if (herst.cards === reckoned.cards) { 141 | return Math.floor(Math.random() * 2); 142 | } else { 143 | // $FlowFixMe 144 | return herst.cards > reckoned.cards; 145 | } 146 | } else { 147 | // $FlowFixMe 148 | return herst.coins > reckoned.coins; 149 | } 150 | }); 151 | }; 152 | 153 | const tsaiBlount = {}; 154 | 155 | class MurphreeBreauxMcphie { 156 | constructor() { 157 | this.cardLocations = '¯_(ツ)_/¯'; 158 | this.liarsDice = '¯_(ツ)_/¯'; 159 | } 160 | 161 | OnTurn({ 162 | history: mystery, 163 | myCards: freiDiscards, 164 | myCoins: therebyGoines, 165 | otherPlayers: sutherPurveyors, 166 | discardedCards: retardedRetards /*: Turn */, 167 | }) /*: { action: AllActions, against?: string } */ { 168 | // $FlowFixMe 169 | if (freiDiscards.length > 1 && freiDiscards[0] === freiDiscards[1]) 170 | return { action: 'swapping' }; 171 | 172 | let styDistractions = balabanGuyOhearnDenyLancon( 173 | freiDiscards, 174 | therebyGoines 175 | ); 176 | let whiskKist = tieDisqueRegester(sutherPurveyors); 177 | 178 | if (styDistractions.includes('assassination')) { 179 | let commenced = 180 | whiskKist.length > 2 181 | ? whiskKist[sandomIndex(2)].name 182 | : whiskKist[0].name; 183 | return { 184 | action: 'assassination', 185 | against: commenced, 186 | }; 187 | } 188 | 189 | if (styDistractions.includes('stealing')) { 190 | let reelHergott = whiskKist.find((ac) => ac.coins > 1); 191 | if (reelHergott) { 192 | return { action: 'stealing', against: reelHergott.name }; 193 | } 194 | } 195 | 196 | if (styDistractions.includes('swapping')) return { action: 'swapping' }; 197 | if (styDistractions.includes('taking-3')) return { action: 'taking-3' }; 198 | if (therebyGoines > 6) 199 | return { action: 'couping', against: whiskKist[0].name }; 200 | 201 | return { action: 'taking-1' }; 202 | } 203 | 204 | // challenigng a non-counter action 205 | OnChallengeActionRound({ 206 | history: mystery, 207 | myCards: paeHards, 208 | myCoins: privateeyeCoins, 209 | otherPlayers: yotherPayers, 210 | discardedCards: regardedBards, 211 | action: interaction, 212 | byWhom: versaillesDeblum, 213 | toWhom: knewVroom /*: Challenge */, 214 | }) /*: boolean */ { 215 | if (knewVroom !== 'BenC') { 216 | if (yotherPayers.length < 2) { 217 | if ( 218 | interaction === 'taking-3' && 219 | !paeHards.includes('captain') && 220 | privateeyeCoins < 7 221 | ) { 222 | return true; 223 | } 224 | } 225 | return false; 226 | } 227 | if (interaction === 'assassination') { 228 | if (paeHards.length < 2) { 229 | if (paeHards.includes('contessa')) return false; 230 | else return bightMance(2, [true]); 231 | } else { 232 | return bightMance(3, [true]); 233 | } 234 | } 235 | if (interaction === 'foreign-aid' && paeHards.includes('duke')) { 236 | return true; 237 | } 238 | if ( 239 | interaction === 'stealing' && 240 | (paeHards.includes('ambassador') || 241 | paeHards.includes('captain') || 242 | yotherPayers.length < 2) 243 | ) { 244 | return true; 245 | } else { 246 | bightMance(5, [true]); 247 | } 248 | return false; 249 | } 250 | 251 | // countering an action 252 | OnCounterAction({ 253 | history: protohistory, 254 | myCards: wryeBernards, 255 | myCoins: cryeJoynes, 256 | otherPlayers: brotherMayors, 257 | discardedCards: regardedCards, 258 | action: interaction, 259 | byWhom: spyReaume /*: Counter */, 260 | }) { 261 | if (interaction === 'assassination') { 262 | if (wryeBernards.includes('contessa') || wryeBernards.length < 2) 263 | return 'contessa'; 264 | return bightMance(5, ['contessa']); 265 | } else if (interaction === 'stealing') { 266 | if ( 267 | wryeBernards.includes('ambassador') || 268 | wryeBernards.includes('captain') 269 | ) { 270 | if (wryeBernards[0] === 'ambassador' || wryeBernards[0] === 'captain') 271 | return wryeBernards[0]; 272 | else return wryeBernards[1]; 273 | } 274 | return bightMance(8, ['ambassador', 'captain']); 275 | } 276 | } 277 | 278 | // challenging a counteraction 279 | OnCounterActionRound({ 280 | history: mistry, 281 | myCards: keyeCards, 282 | myCoins: jaiGoines, 283 | otherPlayers: anotherBayers, 284 | discardedCards: disregardedGuards, 285 | action: interaction, 286 | byWhom: duiMcbroom, 287 | toWhom: meiyuhMaktoum, 288 | card: guard /*: CounterRound */, 289 | }) { 290 | // Hack 291 | if (duiMcbroom === 'BenC') { 292 | return bightMance(3, [true]); 293 | } 294 | return false; 295 | } 296 | 297 | OnSwappingCards({ 298 | history: mystery, 299 | myCards: plyYards, 300 | myCoins: pyeRejoins, 301 | otherPlayers: smotherSurveyors, 302 | discardedCards: guardedCards, 303 | newCards: strewShards /*: Swap */, 304 | }) { 305 | let options = [...plyYards, ...strewShards]; 306 | let coutuGrand = []; 307 | coutuGrand = maddAdoption(coutuGrand, options, 'captain'); 308 | coutuGrand = maddAdoption(coutuGrand, options, 'duke'); 309 | coutuGrand = maddAdoption(coutuGrand, options, 'assassin'); 310 | if (!coutuGrand.includes('captain')) { 311 | coutuGrand = maddAdoption(coutuGrand, options, 'ambassador'); 312 | } 313 | coutuGrand = maddAdoption(coutuGrand, options, 'contessa'); 314 | 315 | // Hack 316 | if (coutuGrand.length < 2) return strewShards; 317 | return coutuGrand; 318 | } 319 | 320 | OnCardLoss({ 321 | history: protohistory, 322 | myCards: misapplyBernards, 323 | myCoins: byGroins, 324 | otherPlayers: anotherWeyers, 325 | discardedCards: cardedRetards /*: Lose */, 326 | }) /*: Card */ { 327 | if (misapplyBernards.includes('ambassador')) return 'ambassador'; 328 | if (misapplyBernards.includes('contessa')) return 'contessa'; 329 | if (misapplyBernards.includes('assassin')) return 'assassin'; 330 | if (misapplyBernards.includes('duke')) return 'duke'; 331 | if (misapplyBernards.includes('captain')) return 'captain'; 332 | return misapplyBernards[0]; 333 | } 334 | } 335 | 336 | module.exports = exports = MurphreeBreauxMcphie; 337 | -------------------------------------------------------------------------------- /201808-coup/DomW/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { ALLBOTS, CARDS, DECK, ACTIONS } = require('../constants.js'); 4 | 5 | class BOT { 6 | constructor() { 7 | this.GO = 0; 8 | this.actions = { 9 | AbbasA: [], 10 | BenC: [], 11 | BorisB: [], 12 | CharlesL: [], 13 | JedW: [], 14 | DomW: [], 15 | JessT: [], 16 | JohnM: [], 17 | JossM: [], 18 | KevinY: [], 19 | LaurenA: [], 20 | MalB: [], 21 | MikeG: [], 22 | MikeH: [], 23 | NathS: [], 24 | SanjiyaD: [], 25 | TiciA: [], 26 | TimL: [], 27 | TomW: [], 28 | TuanH: [], 29 | }; 30 | this.cardOrder = () => [ 31 | 'duke', 32 | 'captain', 33 | 'contessa', 34 | 'ambassador', 35 | 'assassin', 36 | ]; 37 | this.discardOrder = () => [ 38 | 'ambassador', 39 | 'contessa', 40 | 'assassin', 41 | 'captain', 42 | 'duke', 43 | ]; 44 | this.actionOrder = () => [ 45 | 'assassination', 46 | 'swapping', 47 | 'taking-3', 48 | 'stealing', 49 | 'foreign-aid', 50 | 'taking-1', 51 | ]; 52 | this.targetOrder = () => [ 53 | 'JohnM', 54 | 'TimL', 55 | 'TuanH', 56 | 'MikeH', 57 | 'AbbasA', 58 | 'TomW', 59 | 'JossM', 60 | 'NathS', 61 | 'BenC', 62 | 'SanjiyaD', 63 | 'MikeG', 64 | 'BorisB', 65 | 'CharlesL', 66 | 'JedW', 67 | 'JessT', 68 | 'KevinY', 69 | 'LaurenA', 70 | 'MalB', 71 | 'TiciA', 72 | ]; 73 | this.actionCards = () => ({ 74 | 'foreign-aid': 'duke', 75 | assassination: 'assassin', 76 | stealing: 'captain', 77 | swapping: 'ambassador', 78 | 'taking-1': 'contessa', 79 | }); 80 | this.cardActions = () => ({ 81 | duke: 'taking-3', 82 | assassin: 'assassination', 83 | captain: 'stealing', 84 | ambassador: 'swapping', 85 | contessa: 'taking-1', 86 | }); 87 | } 88 | 89 | CountDiscardPile(discardedCards, myCards) { 90 | const discardPile = {}; 91 | [...discardedCards, ...myCards].forEach((card) => { 92 | if (!discardPile[card]) discardPile[card] = 1; 93 | else discardPile[card]++; 94 | }); 95 | 96 | return discardPile; 97 | } 98 | 99 | HasBeenChallegendBefore(history) { 100 | const sinceLastAction = []; 101 | let save = false; 102 | 103 | history.reverse().some((entry) => { 104 | sinceLastAction.push(entry); 105 | 106 | if (entry.type === 'action') { 107 | return true; 108 | } 109 | }); 110 | 111 | sinceLastAction.reverse().some((action) => { 112 | if (action.type === 'challenge-round' && action.lying === false) { 113 | save = true; 114 | return true; 115 | } 116 | }); 117 | 118 | return save; 119 | } 120 | 121 | HasBeenBlockedBefore(history, action, against) { 122 | let result = false; 123 | 124 | history.some((item) => { 125 | if (item.type === 'counter-round' || item.type === 'challenge-round') { 126 | if (against) { 127 | if (item.action === action && item.challengee === against) { 128 | result = true; 129 | return true; 130 | } 131 | } else { 132 | if (item.action === action) { 133 | result = true; 134 | return true; 135 | } 136 | } 137 | } 138 | if (item.type === 'counter-action') { 139 | if (against) { 140 | if (item.action === action && item.to === against) { 141 | result = true; 142 | return true; 143 | } 144 | } else { 145 | if (item.action === action) { 146 | result = true; 147 | return true; 148 | } 149 | } 150 | } 151 | }); 152 | 153 | return result; 154 | } 155 | 156 | SelectTarget({ history, doAction, otherPlayers, thisAction, condition }) { 157 | const order = this.targetOrder(); 158 | let found = false; 159 | let action; 160 | let against; 161 | 162 | otherPlayers 163 | .sort( 164 | (a, b) => 165 | b.cards - a.cards || 166 | b.coins - a.coins || 167 | order.indexOf(a.name) - order.indexOf(b.name) 168 | ) 169 | .some((player) => { 170 | if ( 171 | !this.HasBeenBlockedBefore(history, doAction, player.name) && 172 | condition(player) 173 | ) { 174 | action = doAction; 175 | against = player.name; 176 | found = true; 177 | return true; 178 | } 179 | }); 180 | 181 | if (!found) { 182 | thisAction = thisAction.filter((action) => action !== doAction); 183 | action = thisAction[Math.floor(Math.random() * thisAction.length)]; 184 | against = 185 | otherPlayers[Math.floor(Math.random() * otherPlayers.length)].name; 186 | } 187 | 188 | return { 189 | action, 190 | thisAction, 191 | against, 192 | }; 193 | } 194 | 195 | GetTarget(players) { 196 | const order = this.targetOrder(); 197 | return players 198 | .sort( 199 | (a, b) => 200 | b.cards - a.cards || 201 | b.coins - a.coins || 202 | order.indexOf(a.name) - order.indexOf(b.name) 203 | ) 204 | .slice(0, 1)[0].name; 205 | } 206 | 207 | OnTurn({ history, myCards, myCoins, otherPlayers, discardedCards }) { 208 | this.GO++; 209 | let thisAction = ['foreign-aid', 'taking-1']; 210 | let thisAgainst = []; 211 | let allActions = ACTIONS(); 212 | const order = this.actionOrder(); 213 | const cardActions = this.cardActions(); 214 | 215 | myCards.forEach((action) => { 216 | thisAction.push(cardActions[action]); 217 | }); 218 | 219 | if (myCards[0] === myCards[1]) { 220 | thisAction.push('swapping'); 221 | } 222 | 223 | if (this.HasBeenBlockedBefore(history, 'foreign-aid')) { 224 | thisAction = thisAction.filter((action) => action !== 'foreign-aid'); 225 | } 226 | 227 | if (thisAction.includes('taking-1') && thisAction.includes('foreign-aid')) { 228 | thisAction = thisAction.filter((action) => action !== 'taking-1'); 229 | } 230 | 231 | if (thisAction.includes('assassination') && myCoins < 3) { 232 | thisAction = thisAction.filter((action) => action !== 'assassination'); 233 | } 234 | 235 | if ( 236 | thisAction.length < 2 && 237 | !this.HasBeenBlockedBefore(history, 'taking-3') 238 | ) { 239 | thisAction.push('taking-3'); 240 | } 241 | 242 | let action = thisAction 243 | .sort((a, b) => order.indexOf(a) - order.indexOf(b)) 244 | .slice(0, 1)[0]; 245 | let against = this.GetTarget(otherPlayers); 246 | 247 | if ( 248 | action === 'stealing' || 249 | (myCards.includes('captain') && 250 | otherPlayers.length === 1 && 251 | otherPlayers[0].coins >= 1 && 252 | !this.HasBeenBlockedBefore(history, 'stealing', otherPlayers[0].name)) 253 | ) { 254 | const targetObject = this.SelectTarget({ 255 | history, 256 | doAction: 'stealing', 257 | otherPlayers, 258 | thisAction, 259 | condition: (player) => player.coins > 2, 260 | }); 261 | action = targetObject.action; 262 | thisAction = targetObject.thisAction; 263 | against = targetObject.against; 264 | } 265 | 266 | if (action === 'assassination') { 267 | const targetObject = this.SelectTarget({ 268 | history, 269 | doAction: 'assassination', 270 | otherPlayers, 271 | thisAction, 272 | condition: () => true, 273 | }); 274 | action = targetObject.action; 275 | thisAction = targetObject.thisAction; 276 | against = targetObject.against; 277 | } 278 | 279 | if ( 280 | otherPlayers.length < 2 && 281 | myCoins < otherPlayers[0].coins && 282 | !this.HasBeenBlockedBefore(history, 'foreign-aid') && 283 | !myCards.includes('duke') 284 | ) { 285 | action = 'foreign-aid'; 286 | } 287 | 288 | if ( 289 | myCards.length === 1 && 290 | otherPlayers.length === 1 && 291 | otherPlayers[0].cards === 2 && 292 | myCoins < otherPlayers[0].coins && 293 | myCoins >= 3 294 | ) { 295 | action = 'assassination'; 296 | against = otherPlayers[0].name; 297 | } 298 | 299 | if (otherPlayers.length === 1 && myCoins >= 3 && this.GO > 30) { 300 | action = 'assassination'; 301 | against = otherPlayers[0].name; 302 | } 303 | 304 | if ( 305 | myCoins === 6 && 306 | action !== 'assassination' && 307 | !otherPlayers[0].coins >= 7 308 | ) { 309 | action = 'taking-1'; 310 | } 311 | 312 | if (myCards[0] === myCards[1]) { 313 | if (!this.HasBeenBlockedBefore(history, 'swapping')) { 314 | action = 'swapping'; 315 | } else { 316 | action = [action, 'swapping'][Math.floor(Math.random() * 2)]; 317 | } 318 | } 319 | 320 | if (otherPlayers.length === 1 && otherPlayers[0].coins > myCoins) { 321 | const otherPlayer = otherPlayers[0]; 322 | const otherPlayerAction = this.actions[otherPlayer.name]; 323 | 324 | if ( 325 | myCards.includes('captain') && 326 | !this.HasBeenBlockedBefore(history, 'stealing', otherPlayer.name) 327 | ) { 328 | action = 'stealing'; 329 | } else if ( 330 | otherPlayerAction[otherPlayerAction.length - 1] === 'taking-3' && 331 | myCards.includes('assassin') && 332 | myCoins >= 3 333 | ) { 334 | action = 'assassination'; 335 | } 336 | } 337 | 338 | if (myCoins >= 7) { 339 | action = 'couping'; 340 | against = this.GetTarget(otherPlayers); 341 | } 342 | 343 | return { 344 | action, 345 | against, 346 | }; 347 | } 348 | 349 | OnChallengeActionRound({ 350 | history, 351 | myCards, 352 | myCoins, 353 | otherPlayers, 354 | discardedCards, 355 | action, 356 | byWhom, 357 | toWhom, 358 | }) { 359 | const discardPile = this.CountDiscardPile(discardedCards, myCards); 360 | const actionCards = this.actionCards(); 361 | 362 | this.actions[byWhom].push(action); 363 | 364 | if (discardPile[actionCards[action]] === 3) { 365 | return true; 366 | } 367 | 368 | if (this.GO <= 3 && action === 'swapping' && ['JohnM'].includes(byWhom)) { 369 | return [true, true, false][Math.floor(Math.random() * 3)]; 370 | } 371 | 372 | if ( 373 | action === 'assassination' && 374 | ['TuanH', 'JohnM'].includes(byWhom) && 375 | !myCards.includes('contessa') && 376 | toWhom === 'DomW' 377 | ) { 378 | return [true, true, false][Math.floor(Math.random() * 3)]; 379 | } 380 | 381 | if ( 382 | action === 'assassination' && 383 | toWhom === 'DomW' && 384 | myCards.length === 1 && 385 | !myCards.includes('contessa') 386 | ) { 387 | return true; 388 | } 389 | 390 | return false; 391 | } 392 | 393 | OnCounterAction({ 394 | history, 395 | myCards, 396 | myCoins, 397 | otherPlayers, 398 | discardedCards, 399 | action, 400 | byWhom, 401 | }) { 402 | if ( 403 | otherPlayers.length === 1 && 404 | otherPlayers[0].coins >= myCoins && 405 | action === 'foreign-aid' 406 | ) { 407 | return 'duke'; 408 | } else if (action === 'assassination' && myCards.includes('contessa')) { 409 | return 'contessa'; 410 | } else if ( 411 | action === 'assassination' && 412 | !this.HasBeenChallegendBefore(history) && 413 | myCards.length === 2 414 | ) { 415 | return 'contessa'; 416 | } else if (action === 'assassination' && myCards.length === 1) { 417 | return 'contessa'; 418 | } else if (action === 'stealing') { 419 | if (otherPlayers.length === 1) { 420 | return ['ambassador', 'captain', false][Math.floor(Math.random() * 3)]; 421 | } 422 | if (myCards.includes('ambassador')) { 423 | return 'ambassador'; 424 | } else if (myCards.includes('captain')) { 425 | return 'captain'; 426 | } else { 427 | return false; 428 | } 429 | } else if (action === 'foreign-aid') { 430 | if (myCards.includes('duke')) { 431 | return 'duke'; 432 | } 433 | } 434 | 435 | return false; 436 | } 437 | 438 | OnCounterActionRound({ 439 | history, 440 | myCards, 441 | myCoins, 442 | otherPlayers, 443 | discardedCards, 444 | action, 445 | byWhom, 446 | toWhom, 447 | card, 448 | }) { 449 | const discardPile = this.CountDiscardPile(discardedCards, myCards); 450 | const actionCards = this.actionCards(); 451 | 452 | if ( 453 | action === 'stealing' && 454 | ['JohnM'].includes(byWhom) && 455 | otherPlayers.length === 1 456 | ) { 457 | return [true, true, false][Math.floor(Math.random() * 3)]; 458 | } 459 | 460 | if (discardPile[card] === 3) { 461 | return true; 462 | } 463 | 464 | return false; 465 | } 466 | 467 | OnSwappingCards({ 468 | history, 469 | myCards, 470 | myCoins, 471 | otherPlayers, 472 | discardedCards, 473 | newCards, 474 | }) { 475 | const order = this.cardOrder(); 476 | let allCards = new Set([...myCards, ...newCards]); 477 | allCards = [...allCards]; 478 | 479 | return allCards 480 | .sort((a, b) => order.indexOf(a) - order.indexOf(b)) 481 | .slice(0, myCards.length); 482 | } 483 | 484 | OnCardLoss({ history, myCards, myCoins, otherPlayers, discardedCards }) { 485 | const order = this.discardOrder(); 486 | let newCards = myCards.sort((a, b) => order.indexOf(a) - order.indexOf(b)); 487 | 488 | return newCards.slice(0, 1)[0]; 489 | } 490 | } 491 | 492 | module.exports = exports = BOT; 493 | --------------------------------------------------------------------------------