Skip to content
This repository was archived by the owner on Jul 27, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down
33 changes: 33 additions & 0 deletions src/Collaborate.ts
Original file line number Diff line number Diff line change
@@ -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<any>,
us: ISnake,
partners: ISnake[]
): () => 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);
};
}
64 changes: 40 additions & 24 deletions src/SnakeBrain.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IGameState, ISnake, IBoard, IGame, Directions } from './Types';
import { IGameState, ISnake, IBoard, Directions } from './Types';
import { turtle } from './behaviours/turtle';
import {
canKillNemesis,
getPartners,
getNemesis,
shouldChaseOurTail,
firstToFood,
Expand All @@ -12,28 +13,29 @@ 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;
let turn: number;
let board: IBoard;
let selfDestruct: boolean;
let us: ISnake;
let partners: ISnake[];
let nemesis: ISnake;
let everybody: ISnake[];

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?
// TODO: remove self-destruct
selfDestruct = exploited;
us = gameStateResponse.you;
everybody = board.snakes;
partners = getPartners(us, everybody);
nemesis = getNemesis(us, everybody);
}

Expand All @@ -44,37 +46,51 @@ 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 });
// console.log({ turn, game, board, us });

// Instantiate Pathfinder with board and snakes
const PF = new Pathfinder(board, everybody);

// Try some moves out, see what feels good
const cower = turtle(PF, us);
const headbutt = attackHead(PF, us, nemesis);
const goingInCircles = chaseTail(PF, us);
const hangry = seekSafestFood(PF, board, us);
const ridingCoattails = chaseEnemyTail(PF, us, everybody);

const headbutt = collaborate(
attackHead,
[new Pathfinder(board, everybody), us, nemesis],
us,
partners
);
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 = (): 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));
}
Expand Down
3 changes: 3 additions & 0 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
10 changes: 10 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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({});
Expand Down