How I solved Bomberman challenge on HackerRank

The Bomberman Game

Rules

  • Bombs will explode in 3 seconds
  • When a bomb explodes it clears its tile and 1 tile up, down, left, right
  • Bombs that are about to go off explode at the same time
  • There are no chain reactions
  • If a bomb explodes next to another that is not about to go off then the nearby bomb is just cleared

Iteration

  1. Bomberman plants some bombs (you’re given an input with the map)
  2. A second passes
  3. A second passes. Bomberman plants bombs on all empty slots
  4. A second passes
  5. Repeat 3 and 4 until N seconds passes

Input

  • R = number of rows
  • C = number of cols
  • S = number of seconds
  • O = bomb
  • . = empty
R C S
O..
...

Task

Solving

export function extractParams(input: string) {
return {
cols: undefined,
matrix: undefined,
rows: undefined,
seconds: undefined,
};
}
describe('extractParams', () => {
it('returns parameters as object', () => {
const settings = extractParams(sampleInput);

expect(settings.cols).toBe(7);
expect(settings.matrix).toEqual(expectedMatrix);
expect(settings.rows).toBe(6);
expect(settings.seconds).toBe(3);
});
});
export function extractParams(input: string): Settings {
const pieces = input.split('\n');

const [rows, cols, seconds] = pieces[0]
.split(' ')
.map(setting => +setting);

const matrix = pieces.slice(1)
.map(row => row.split('').map(e => e === 'O' ? 3 : emptySlot));

return {
cols,
matrix,
rows,
seconds, // hint: change *something* here for performance
};
}
export function* tick({ cols, matrix, rows, seconds }: Settings) {
let result = [
...matrix.map(row => [...row]),
];
let currentSeconds = seconds + 1;

while (currentSeconds--) {
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if (result[row][col] === 0) {
explode(result, { col, cols, row, rows });
}
}
}

yield result;
}
}
export function explode(matrix: Array<number[]>, { col, cols, row, rows }) {
matrix[row][col] = emptySlot;
}
  • Boundaries: I shouldn’t change a position that doesn’t even exist in the matrix (ex: col -1)
  • Bombs explode at the same time: I can’t clear the position of a bomb that is also exploding, because it is exploding “at the same time”, so I need to keep it there until I get to it and make it explode
export function explode(matrix: Array<number[]>, { col, cols, row, rows }) {
if (col - 1 >= 0) {
matrix[row][col - 1] = nextStateAfterExplosion(matrix[row][col - 1]);
}

if (col + 1 < cols) {
matrix[row][col + 1] = nextStateAfterExplosion(matrix[row][col + 1]);
}

if (row - 1 >= 0) {
matrix[row - 1][col] = nextStateAfterExplosion(matrix[row - 1][col]);
}

if (row + 1 < rows) {
matrix[row + 1][col] = nextStateAfterExplosion(matrix[row + 1][col]);
}

matrix[row][col] = emptySlot;
}
  • Bomb could be on
  • top left (no neighbors to the left or top)
  • middle left
  • bottom left
  • center
  • and so on…
it('returns map with cleared neighbors after explosion', () => {
const matrix = [
[0, 2],
[3, 1],
];
const expected = [
[emptySlot, emptySlot],
[emptySlot, 1],
];

explode(matrix, { col: 0, cols: 2, row: 0, rows: 2 });

expect(matrix).toEqual(expected);
});
export function nextStateAfterExplosion(element: number) {
return element === 0 ? element : emptySlot;
}
describe('nextState', () => {
it('returns emptySlot value when seconds to explode is 3', () => {
expect(nextStateAfterExplosion(3)).toBe(emptySlot);
});

it('returns emptySlot value when seconds to explode is 2', () => {
expect(nextStateAfterExplosion(2)).toBe(emptySlot);
});

it('returns emptySlot value when seconds to explode is 1', () => {
expect(nextStateAfterExplosion(1)).toBe(emptySlot);
});

it('returns 0 when seconds to explode is 0', () => {
expect(nextStateAfterExplosion(0)).toBe(0);
});

it('returns emptySlot value when it is an empty slot', () => {
expect(nextStateAfterExplosion(emptySlot)).toBe(emptySlot);
});
});
export function* tick({ cols, matrix, rows, seconds }: Settings) {
let result = [
...matrix.map(row => [...row]),
];
let currentSeconds = seconds + 1;

while (currentSeconds--) {
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if (result[row][col] === 0) {
explode(result, { col, cols, row, rows });
}
else {
if (result[row][col] > 0) { // bomb not ready
result[row][col] = nextStateAfterTick(result[row][col]);
}
}
}
}

yield result;
}
}
export function nextStateAfterTick(element: number) {
if (element === 0) {
return element;
}

if (element === emptySlot) {
return emptySlot;
}

return element - 1;
}
describe('nextStateAfterTick', () => {
it('returns 2 when seconds to explode is 3', () => {
expect(nextStateAfterTick(3)).toBe(2);
});

it('returns 1 when seconds to explode is 2', () => {
expect(nextStateAfterTick(2)).toBe(1);
});

it('returns 0 when seconds to explode is 1', () => {
expect(nextStateAfterTick(1)).toBe(0);
});

it('returns 0 when seconds to explode is 0', () => {
expect(nextStateAfterTick(0)).toBe(0);
});

it('returns emptySlot value when it is an empty slot', () => {
expect(nextStateAfterTick(emptySlot)).toBe(emptySlot);
});
});
export function* tick({ cols, matrix, rows, seconds }: Settings) {
let turn = 0;
let result = [
...matrix.map(row => [...row]),
];
let currentSeconds = seconds + 1;

while (currentSeconds--) {
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
if (result[row][col] === 0) {
explode(result, { col, cols, row, rows });
}
else {
if (result[row][col] > 0) {
result[row][col] = nextStateAfterTick(result[row][col]);
}
}
}

yield result;
}

// plant bombs
if ((turn & 1) === 1) {
result = [
...result.map(row => row.map(col => col === -1 ? 3 : col)),
];
}

turn++;
}
}
export function processData(input: string) {
const settings = extractParams(input);

const tickGen = tick(settings);

let fieldState;

// step all the way to the end
// as I said, it doesn't make much sense here
// but it is fun
// we could make a replay of the game with it
for (const state of tickGen) {
fieldState = state;
}

return fieldState;
}

Good practices

Tests

Debug

if (result[row][col] === 0) {
explode(result, { col, cols, row, rows });
yield result; // here
}
else {
if (result[row][col] > 0) {
result[row][col] = nextStateAfterTick(result[row][col]);
}
yield result; // here
}

Drawing

Resources

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Material UI in Svelte using Smelte

How to Implement Push Notifications in PWA using React

AngularJS vs Angular2+

We choose React & TypeScript not because they’re easy, but because they’re hard.

What is Webpack? How Webpack Quickly Speeds Up Your Website and Generate Revenue for Your Business

Application Framework

Building a business from scratch — day 15

Learning the Hard Way: Microcontrollers for Newbs — the Martin files

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bruno Leonardo Michels

Bruno Leonardo Michels

More from Medium

[Learn Open Source] Learn install-pkg in 5 minutes

Conditional (ternary) operator in Javascript

How JavaScript works: the factory design pattern + 4 use cases

What is NodeJS?