├── 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 | 
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 |
--------------------------------------------------------------------------------