From 496feaa79c3d9d077788c52eac13175eafc84d7b Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Sun, 8 Mar 2020 01:38:39 -0800 Subject: [PATCH 1/5] WIP proof of concept for a collaboration wrapper on behavior --- .eslintrc.js | 1 + src/Collaborate.ts | 33 +++++++++++++++++++++++++++++++++ src/SnakeBrain.ts | 21 ++++++++++++++++++--- src/Types.ts | 3 +++ src/helpers.ts | 10 ++++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/Collaborate.ts diff --git a/.eslintrc.js b/.eslintrc.js index 4abdb21..e60c2fb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,7 @@ module.exports = { // note you must disable the base rule as it can report incorrect errors 'no-unused-vars': 'off', + 'array-element-newline': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { diff --git a/src/Collaborate.ts b/src/Collaborate.ts new file mode 100644 index 0000000..0034ae5 --- /dev/null +++ b/src/Collaborate.ts @@ -0,0 +1,33 @@ +import { Behavior, Directions, ISnake } from './Types'; +import Pathfinder from './Pathfinder'; + +export function collaborate( + action: Behavior, // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionArguments: Array, + pathfinder: Pathfinder, + us: ISnake, + partners: ISnake[] +): Directions { + const collaborateArguments = actionArguments.map(arg => + arg instanceof Pathfinder ? pathfinder : arg + ); + partners.forEach(snake => { + const snakeArgs = collaborateArguments.map(arg => + arg === us ? snake : arg + ); + const direction = action(...snakeArgs); + const nonWalkableCoordinate = snake.body[0]; + switch (direction) { + case Directions.UP: + nonWalkableCoordinate.y -= 1; + case Directions.DOWN: + nonWalkableCoordinate.y += 1; + case Directions.LEFT: + nonWalkableCoordinate.x -= 1; + case Directions.RIGHT: + nonWalkableCoordinate.x += 1; + } + pathfinder.grid[nonWalkableCoordinate.x][nonWalkableCoordinate.y] = 1; + }); + return action(...collaborateArguments); +} diff --git a/src/SnakeBrain.ts b/src/SnakeBrain.ts index 25176ef..c2ad405 100644 --- a/src/SnakeBrain.ts +++ b/src/SnakeBrain.ts @@ -2,6 +2,7 @@ import { IGameState, ISnake, IBoard, IGame, Directions } from './Types'; import { turtle } from './behaviours/turtle'; import { canKillNemesis, + getPartners, getNemesis, shouldChaseOurTail, firstToFood, @@ -12,6 +13,7 @@ import { chaseEnemyTail } from './behaviours/chaseEnemyTail'; import Pathfinder from './Pathfinder'; import { floodFill } from './behaviours/floodFill'; import seekSafestFood from './behaviours/seekSafestFood'; +import { collaborate } from './collaborate'; // I hate writing "this." all the time. let game: IGame; @@ -19,6 +21,7 @@ let turn: number; let board: IBoard; let selfDestruct: boolean; let us: ISnake; +let partners: ISnake[]; let nemesis: ISnake; let everybody: ISnake[]; @@ -34,6 +37,7 @@ export default class SnakeBrain { selfDestruct = exploited; us = gameStateResponse.you; everybody = board.snakes; + partners = getPartners(us, everybody); nemesis = getNemesis(us, everybody); } @@ -44,7 +48,6 @@ export default class SnakeBrain { */ public decide(): SnakeBrain { // Logic for start of game. - // eslint-disable-next-line array-element-newline console.log({ turn, game, board, us }); // Instantiate Pathfinder with board and snakes @@ -52,9 +55,21 @@ export default class SnakeBrain { // Try some moves out, see what feels good const cower = turtle(PF, us); - const headbutt = attackHead(PF, us, nemesis); + const headbutt = collaborate( + attackHead, + [PF, us, nemesis], + new Pathfinder(board, everybody), + us, + partners + ); const goingInCircles = chaseTail(PF, us); - const hangry = seekSafestFood(PF, board, us); + const hangry = collaborate( + seekSafestFood, + [PF, board, us], + new Pathfinder(board, everybody), + us, + partners + ); const ridingCoattails = chaseEnemyTail(PF, us, everybody); if (selfDestruct) { diff --git a/src/Types.ts b/src/Types.ts index bf85f9e..fbec53c 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -37,3 +37,6 @@ export enum Directions { UP = 'up', DOWN = 'down', } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type Behavior = (...args: any[]) => Directions; diff --git a/src/helpers.ts b/src/helpers.ts index 25e7110..0b69ebf 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -11,6 +11,16 @@ export function getNemesis(us: ISnake, snakes: ISnake[]): ISnake { return snakes.find(snake => snake.name !== us.name); } +/** + * Get our partners in crime from the array of snakes + * @param {ISnake} us - this instance of Echosnek + * @param {ISnake[]} snakes - all the snakes + */ +export function getPartners(us: ISnake, snakes: ISnake[]): ISnake[] { + // There should only ever be 1 snake that has a different name + return snakes.filter(snake => snake.name === us.name && snake.id !== us.id); +} + /** * Are we currently equal in length or longer than the enemy? * @param {ISnake} us - this instance of Echosnek From 308ad87a25677c8468c3e5ba1a4996037c89ed2d Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Sun, 8 Mar 2020 01:41:18 -0800 Subject: [PATCH 2/5] WIP spelling --- src/SnakeBrain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SnakeBrain.ts b/src/SnakeBrain.ts index c2ad405..c08bd57 100644 --- a/src/SnakeBrain.ts +++ b/src/SnakeBrain.ts @@ -13,7 +13,7 @@ import { chaseEnemyTail } from './behaviours/chaseEnemyTail'; import Pathfinder from './Pathfinder'; import { floodFill } from './behaviours/floodFill'; import seekSafestFood from './behaviours/seekSafestFood'; -import { collaborate } from './collaborate'; +import { collaborate } from './Collaborate'; // I hate writing "this." all the time. let game: IGame; From 54b1fd70891f8222a156fbee84eb12e6429f0c75 Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Sun, 8 Mar 2020 13:06:34 -0700 Subject: [PATCH 3/5] modified to give snakes decision ordering --- src/Collaborate.ts | 14 ++++++-------- src/SnakeBrain.ts | 6 ++---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Collaborate.ts b/src/Collaborate.ts index 0034ae5..99c804c 100644 --- a/src/Collaborate.ts +++ b/src/Collaborate.ts @@ -4,17 +4,15 @@ import Pathfinder from './Pathfinder'; export function collaborate( action: Behavior, // eslint-disable-next-line @typescript-eslint/no-explicit-any actionArguments: Array, - pathfinder: Pathfinder, us: ISnake, partners: ISnake[] ): Directions { - const collaborateArguments = actionArguments.map(arg => - arg instanceof Pathfinder ? pathfinder : arg - ); + const pathfinder = actionArguments.find(arg => arg instanceof Pathfinder); + partners = partners + .sort((s1, s2) => (s1.id > s2.id ? 1 : -1)) + .filter(s1 => s1.id < us.id); partners.forEach(snake => { - const snakeArgs = collaborateArguments.map(arg => - arg === us ? snake : arg - ); + const snakeArgs = actionArguments.map(arg => (arg === us ? snake : arg)); const direction = action(...snakeArgs); const nonWalkableCoordinate = snake.body[0]; switch (direction) { @@ -29,5 +27,5 @@ export function collaborate( } pathfinder.grid[nonWalkableCoordinate.x][nonWalkableCoordinate.y] = 1; }); - return action(...collaborateArguments); + return action(...actionArguments); } diff --git a/src/SnakeBrain.ts b/src/SnakeBrain.ts index c08bd57..9683936 100644 --- a/src/SnakeBrain.ts +++ b/src/SnakeBrain.ts @@ -57,16 +57,14 @@ export default class SnakeBrain { const cower = turtle(PF, us); const headbutt = collaborate( attackHead, - [PF, us, nemesis], - new Pathfinder(board, everybody), + [new Pathfinder(board, everybody), us, nemesis], us, partners ); const goingInCircles = chaseTail(PF, us); const hangry = collaborate( seekSafestFood, - [PF, board, us], - new Pathfinder(board, everybody), + [new Pathfinder(board, everybody), board, us], us, partners ); From dd84fded40ccd0817cc55e930b3783bd948a3ef7 Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Sun, 8 Mar 2020 13:34:17 -0700 Subject: [PATCH 4/5] changed behavior to deferred execution for some speed --- src/Collaborate.ts | 46 ++++++++++++++++++++++++---------------------- src/SnakeBrain.ts | 41 +++++++++++++++++++++++------------------ src/index.ts | 8 ++++---- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/Collaborate.ts b/src/Collaborate.ts index 99c804c..f03979b 100644 --- a/src/Collaborate.ts +++ b/src/Collaborate.ts @@ -6,26 +6,28 @@ export function collaborate( actionArguments: Array, us: ISnake, partners: ISnake[] -): Directions { - const pathfinder = actionArguments.find(arg => arg instanceof Pathfinder); - partners = partners - .sort((s1, s2) => (s1.id > s2.id ? 1 : -1)) - .filter(s1 => s1.id < us.id); - partners.forEach(snake => { - const snakeArgs = actionArguments.map(arg => (arg === us ? snake : arg)); - const direction = action(...snakeArgs); - const nonWalkableCoordinate = snake.body[0]; - switch (direction) { - case Directions.UP: - nonWalkableCoordinate.y -= 1; - case Directions.DOWN: - nonWalkableCoordinate.y += 1; - case Directions.LEFT: - nonWalkableCoordinate.x -= 1; - case Directions.RIGHT: - nonWalkableCoordinate.x += 1; - } - pathfinder.grid[nonWalkableCoordinate.x][nonWalkableCoordinate.y] = 1; - }); - return action(...actionArguments); +): () => Directions { + return (): Directions => { + const pathfinder = actionArguments.find(arg => arg instanceof Pathfinder); + partners = partners + .sort((s1, s2) => (s1.id > s2.id ? 1 : -1)) + .filter(s1 => s1.id < us.id); + partners.forEach(snake => { + const snakeArgs = actionArguments.map(arg => (arg === us ? snake : arg)); + const direction = action(...snakeArgs); + const nonWalkableCoordinate = snake.body[0]; + switch (direction) { + case Directions.UP: + nonWalkableCoordinate.y -= 1; + case Directions.DOWN: + nonWalkableCoordinate.y += 1; + case Directions.LEFT: + nonWalkableCoordinate.x -= 1; + case Directions.RIGHT: + nonWalkableCoordinate.x += 1; + } + pathfinder.grid[nonWalkableCoordinate.x][nonWalkableCoordinate.y] = 1; + }); + return action(...actionArguments); + }; } diff --git a/src/SnakeBrain.ts b/src/SnakeBrain.ts index 9683936..333662c 100644 --- a/src/SnakeBrain.ts +++ b/src/SnakeBrain.ts @@ -48,7 +48,7 @@ export default class SnakeBrain { */ public decide(): SnakeBrain { // Logic for start of game. - console.log({ turn, game, board, us }); + // console.log({ turn, game, board, us }); // Instantiate Pathfinder with board and snakes const PF = new Pathfinder(board, everybody); @@ -61,33 +61,38 @@ export default class SnakeBrain { us, partners ); - const goingInCircles = chaseTail(PF, us); + const goingInCircles = collaborate( + chaseTail, + [new Pathfinder(board, everybody), us], + us, + partners + ); const hangry = collaborate( seekSafestFood, [new Pathfinder(board, everybody), board, us], us, partners ); - const ridingCoattails = chaseEnemyTail(PF, us, everybody); - + const ridingCoattails = (): Directions => chaseEnemyTail(PF, us, everybody); + let action: Directions; if (selfDestruct) { // OH NO! We've been hacked! - console.log('AHHHHHH'); + console.log(`${turn} | ${us.id}: AHHHHHH`); this.action = cower; - } else if (canKillNemesis(us, everybody) && headbutt) { - console.log('*THUNK*'); - this.action = headbutt; - } else if (firstToFood && hangry) { - console.log('CHONK'); - this.action = hangry; - } else if (goingInCircles && shouldChaseOurTail(us, turn)) { - console.log('Follow our butt'); - this.action = goingInCircles; - } else if (ridingCoattails) { - console.log('Follow your butt'); - this.action = ridingCoattails; + } else if (canKillNemesis(us, everybody) && (action = headbutt())) { + console.log(`${turn} | ${us.id}: *THUNK*`); + this.action = action; + } else if (firstToFood && (action = hangry())) { + console.log(`${turn} | ${us.id}: CHONK`); + this.action = action; + } else if (shouldChaseOurTail(us, turn) && (action = goingInCircles())) { + console.log(`${turn} | ${us.id}: Follow our butt`); + this.action = action; + } else if ((action = ridingCoattails())) { + console.log(`${turn} | ${us.id}: Follow your butt`); + this.action = action; } else { - console.log('Feeling aimless'); + console.log(`${turn} | ${us.id}: Feeling aimless`); // floodFill is costly, so only calculating when we need it this.action = PF.getDirection(us.body[0], floodFill(PF.grid, us)); } diff --git a/src/index.ts b/src/index.ts index 643d27d..15c65fb 100755 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ app.use(poweredByHandler); // Handle POST request to '/start' app.post('/start', (request, response) => { // NOTE: Do something here to start the game - console.log(request.body); + // console.log(request.body); // Response data const data = { @@ -53,14 +53,14 @@ app.post('/move', (request, response) => { }); app.post('/end', (request, response) => { - console.log(request.body); - + // console.log(request.body); + console.log('/end'); // NOTE: Any cleanup when a game is complete. return response.json({}); }); app.post('/ping', (request, response) => { - console.log(request.body); + // console.log(request.body); // Used for checking if this snake is still alive. return response.json({}); From 580954919f7f64fff07bf0db16cbbbfe43cdba4a Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Sun, 8 Mar 2020 13:43:44 -0700 Subject: [PATCH 5/5] remove unused game var --- src/SnakeBrain.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SnakeBrain.ts b/src/SnakeBrain.ts index 333662c..f8c6ebd 100644 --- a/src/SnakeBrain.ts +++ b/src/SnakeBrain.ts @@ -1,4 +1,4 @@ -import { IGameState, ISnake, IBoard, IGame, Directions } from './Types'; +import { IGameState, ISnake, IBoard, Directions } from './Types'; import { turtle } from './behaviours/turtle'; import { canKillNemesis, @@ -16,7 +16,6 @@ import seekSafestFood from './behaviours/seekSafestFood'; import { collaborate } from './Collaborate'; // I hate writing "this." all the time. -let game: IGame; let turn: number; let board: IBoard; let selfDestruct: boolean; @@ -29,7 +28,6 @@ export default class SnakeBrain { private action: Directions; constructor(gameStateResponse: IGameState, exploited: boolean) { - game = gameStateResponse.game; turn = gameStateResponse.turn; board = gameStateResponse.board; // Not sure why we left this in here?