Hello! I have a lot of difficulties to write a good description of this kata. I published it some time ago but I met a lot of critics with that description and received good evaluations of the task. I tried to rewrite it several times but it would not get better. If you like thaŠµ kata and you are interested in helping me it would be great!
What we have:
-
Input: a
table
pool (two-dimensional array); -
"X"
is a part of the table or cell; -
One white cue ball is represented by number >=1. This number means the force of shot: one point of the force is equal to one movement of a ball, so the ball with number 3 can move 3 cells (to move to the next cell you should subtract one point from its force). After collision with a coloured ball it gives the rest points of force to a coloured ball and disappears (the white ball only determines the direction).
-
One coloured ball is represented by
"O"
. -
Pockets are represented by
"U"
. They can be in any place on the borders of the table; -
The output is: a boolean (whether the coloured ball rolled into a pocket) and a pair of integers (the final coordinates of the coloured ball).
Rules
The balls can move only to their adjacent cells in the horizontal, vertical ou diagonal direction. The line
You should shot the white ball in the direction of the coloured ball using that way that results in the imaginable straight line between them:
Possible collisions:
1) ["O", "X", "X"] 2) ["X", "X", "X"] 3) ["X", "4", "X"]
["X", "X", "X"] ["O", "X", "4"] ["X", "X", "X"]
["X", "X", "4"] ["X", "X", "X"] ["X", "O", "X"]
No collisions:
1) ["X", "4", "X"] 2) ["X", "X", "X", "X"]
["X", "X", "X"] ["X", "X", "X", "O"]
["O", "X", "X"] ["4", "X", "X", "X"]
If the white ball can't reach the coloured ball by that way, a collision won't happen.
In the case of collision, the coloured ball "O"
starts to move(in the same direction that the white ball moved) until it stops because its force is over or until it enters a pocket. If it hits the "table wall", the ball changes its angle of movement according to physics.
For example, in this case, after collision the ball "O"
goes to the opposite direction:
["X", "O", "X"] ["X", "O", "X"] ["X", "X", "X"] ["X", "X", "X"] ["X", "X", "X"]
["X", "4", "X"]=>["X", "X", "X"]=>["X", "O", "X"]=>["X", "X", "X"]=>["X", "O", "X"]
["X", "X", "X"] ["X", "X", "X"] ["X", "X", "X"] ["X", "O", "X"] ["X", "X", "X"]
And in the next case, it "reflects":
["X", "O", "X"] ["X", "O", "X"] ["X", "X", "X"] ["X", "X", "X"] ["X", "X", "X"]
["4", "X", "X"]=>["X", "X", "X"]=>["X", "X", "O"]=>["X", "X", "X"]=>["O", "X", "X"]
["X", "X", "X"] ["X", "X", "X"] ["X", "X", "X"] ["X", "O", "X"] ["X", "X", "X"]
Return true
or false
if the coloured ball did enter a pocket or not and the coordinates of coloured ball.
Notes
The size of a table can be different but always rectangular.
There are only two balls.
Examples
let table = [["U", "X", "O", "X"], // the collision is possible
["X", "X", "X", "X"],
["9", "X", "U", "X"]]
table = [["U", "X", "O", "X"],
["X", "8", "X", "X"], // <-- one movement spends one point of force
["X", "X", "U", "X"]]
table = [["U", "X", "O", "X"], // <-- 7 points of force (the white ball was removed)
["X", "X", "X", "X"],
["X", "X", "U", "X"]]
table = [["U", "X", "X", "X"],
["X", "X", "X", "O"],// <--6 points
["X", "X", "U", "X"]]
table = [["U", "X", "X", "X"],
["X", "X", "X", "X"],
["X", "X", "U", "X"]] // <--the coloured ball entered a pocket
return [true, [2, 2] ]
let table = [["U", "X", "X"], // the collision is possible,
["1", "O", "U"], // but there is not enough force to reach a pocket
["X", "X", "X"]]
return [false, [1, 1] ]
let table = [["U", "X", "X", "X"], // the collision is not possible
["X", "X", "X", "O"],
["9", "X", "U", "X"]]
return [false, [1, 3] ]
!The last critic of a Moderator:
-
would be more readable to have the input as an array of strings, rather than an array of arays of strings, no? (and you could use whitespaces instead of X for "empty" places)
-
the description really needs a rewrite... ;) General advises:
a) don't give the examples before the specifications
b) especially, don't mix explanations and examples
c) give the general idea of the task right at the beginning before going into details. Like, that's the very first thing that should be in the description, before the picture. -
I believe you didn't specify that no pockets will ever be under the starting positions of the balls
-
the notes are parts of the specs
-
did you say that the force/speed may be greater than 9 (and is never negative)?
*3 - I'm going t fix it
function poolgame(table) {
let force;
let coordBall1 = {};
let coordBall2 = {};
for (let i = 0; i < table.length; i++) {
for (let k = 0; k < table[i].length; k++) {
if (Object.keys(coordBall1).length && Object.keys(coordBall2).length) break;
if (!isNaN(table[i][k])) {
coordBall1.X = k;
coordBall1.Y = i;
force = table[i][k];
} else if (table[i][k] == "O") {
coordBall2.X = k;
coordBall2.Y = i;
}
}
}
let dir = { X: 0, Y: 0 };
let y = Math.abs(coordBall1.Y - coordBall2.Y);
let x = Math.abs(coordBall1.X - coordBall2.X);
if (coordBall1.Y === coordBall2.Y) {
force -= x;
} else if (coordBall1.X === coordBall2.X) {
force -= y;
} else {
if (y !== x) return [false, [coordBall2.Y, coordBall2.X]]; //check if there is no collision
force -= x;
}
if (force <= 0) return [false, [coordBall2.Y, coordBall2.X]]; //check if there is no collision
dir.X = (coordBall1.X > coordBall2.X) ? -1 : (coordBall1.X === coordBall2.X) ? 0 : 1;
dir.Y = (coordBall1.Y > coordBall2.Y) ? -1 : (coordBall1.Y === coordBall2.Y) ? 0 : 1;
let tableLimit = {
X: table[0].length - 1,
Y: table.length - 1
}
while (force--) {
let possibleX = coordBall2.X + dir.X;
let possibleY = coordBall2.Y + dir.Y;
if (possibleX > tableLimit.X || possibleX < 0) dir.X = -dir.X;
if (possibleY > tableLimit.Y || possibleY < 0) dir.Y = -dir.Y;
coordBall2.X += dir.X;
coordBall2.Y += dir.Y;
if (table[coordBall2.Y][coordBall2.X] === "U") return [true, [coordBall2.Y, coordBall2.X]];
}
return [false, [coordBall2.Y, coordBall2.X]];
}
const chai = require("chai");
const assert = chai.assert;
chai.config.truncateThreshold = 0;
describe("Sample tests", function() {
it(`[
["X", "X", "U"],
["O", "X", "5"],
["U", "X", "X"]
]`, function() {
assert.deepEqual(poolgame(
[
["X", "X", "U"],
["O", "X", "5"],
["U", "X", "X"]
]), [ false, [ 1, 1 ] ]);
});
it(`[
["U", "X", "3"],
["X", "X", "X"],
["O", "X", "U"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "3"],
["X", "X", "X"],
["O", "X", "U"]
]), [ false, [ 1, 1 ] ]);
});
it(`[
["U", "X", "O","X"],
["X", "X", "X","X"],
["8", "X", "X","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "O","X"],
["X", "X", "X","X"],
["8", "X", "X","X"]
]), [ true, [ 0, 0 ] ]);
});
it(`[
["U", "X", "X","X"],
["X", "X", "X","O"],
["8", "X", "X","X"],
["X", "U", "U","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "X","X"],
["X", "X", "X","O"],
["8", "X", "X","X"],
["X", "U", "U","X"]
]), [ false, [ 1, 3 ] ]);
});
it(`[
["U", "X", "X","O"],
["X", "X", "X","X"],
["X", "X", "X","X"],
["8", "U", "U","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "X","O"],
["X", "X", "X","X"],
["X", "X", "X","X"],
["8", "U", "U","X"]
]), [ false, [ 1, 2 ] ]);
});
it(`[
["U", "X", "X","X"],
["X", "X", "O","X"],
["9", "X", "X","X"],
["X", "U", "U","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "X","X"],
["X", "X", "O","X"],
["9", "X", "X","X"],
["X", "U", "U","X"]
]), [ false, [ 1, 2 ] ]);
});
it(`[
["U", "X", "X","O"],
["X", "X", "X","X"],
["X", "9", "X","X"],
["U", "X", "U","X"]
]`, function () {
assert.deepEqual(poolgame(
[
["U", "X", "X", "O"],
["X", "X", "X", "X"],
["X", "9", "X", "X"],
["U", "X", "U", "X"]
]), [true, [3, 0]]);
});
it(`[
["U", "X", "X","X"],
["X", "X", "X","X"],
["2", "X", "X","O"],
["X", "X", "U","X"],
["X", "U", "U","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "X","X"],
["X", "X", "X","X"],
["2", "X", "X","O"],
["X", "X", "U","X"],
["X", "U", "U","X"]
]),[ false, [ 2, 3 ] ]);
});
it(`[
["U", "X", "X","X"],
["X", "X", "X","X"],
["1", "O", "X","X"]
]`, function() {
assert.deepEqual(poolgame(
[
["U", "X", "X","X"],
["X", "X", "X","X"],
["1", "O", "X","X"]
]), [ false, [ 2, 1 ] ]);
});
});
describe("New Edge test", function() {
it(`[
["X", "X", "U", "X", "X", "X", "U", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "O", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "U"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "U"],
["X", "50", "X", "X", "X", "X", "X", "X", "X", "X"],
["U", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["U", "X", "U", "X", "U", "X", "X", "X", "X", "X"]
]`, function() {
assert.deepEqual(poolgame(
[
["X", "X", "U", "X", "X", "X", "U", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "O", "X", "X", "X", "X", "X"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "U"],
["X", "X", "X", "X", "X", "X", "X", "X", "X", "U"],
["X", "50", "X", "X", "X", "X", "X", "X", "X", "X"],
["U", "X", "X", "X", "X", "X", "X", "X", "X", "X"],
["U", "X", "U", "X", "U", "X", "X", "X", "X", "X"]
]), [ true, [ 8, 0 ] ]);
});
});
describe("Random tests", function() {
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive
}
function createTable(a, b) {
let arr = [];
while (arr.length < a) {
let sub = [];
while (sub.length < b) {
sub.push("X");
}
arr.push(sub);
}
return arr;
}
function createTest() {
let outerSize = getRandomInt(3, 11);
let innerSize = getRandomInt(3, 11);
let arr = createTable(outerSize, innerSize)
let coordBall1 = {
Y: getRandomInt(0, outerSize),
X: getRandomInt(0, innerSize)
}
let coordBall2 = {};
let typeOfPos = Math.random();
if (typeOfPos < 0.3) {
coordBall2.X = coordBall1.X;
coordBall2.Y = getRandomInt(0, outerSize)
if (coordBall2.Y === coordBall1.Y) {
coordBall2.Y += (coordBall2.Y > 0) ? -1 : 1;
}
} else if (typeOfPos < 0.6) {
coordBall2.X = getRandomInt(0, innerSize)
coordBall2.Y = coordBall1.Y;
if (coordBall2.X === coordBall1.X) {
coordBall2.X += (coordBall2.X > 0) ? -1 : 1;
}
} else if (typeOfPos < 0.9) { //simple diagonal tests
if (Math.random() < 0.5) {
coordBall2.X = (coordBall1.X > 0) ? coordBall1.X - 1 : coordBall1.X + 1;
} else {
coordBall2.X = (coordBall1.X < innerSize - 1) ? coordBall1.X + 1 : coordBall1.X - 1;
}
if (Math.random() < 0.5) {
coordBall2.Y = (coordBall1.Y > 0) ? coordBall1.Y - 1 : coordBall1.Y + 1;
} else {
coordBall2.Y = (coordBall1.Y < outerSize - 1) ? coordBall1.Y + 1 : coordBall1.Y - 1;
}
} else {
coordBall2.Y = getRandomInt(0, outerSize)
if (coordBall2.Y === coordBall1.Y) {
coordBall2.Y += (coordBall2.Y > 0) ? -1 : 1;
}
coordBall2.X = getRandomInt(0, innerSize)
if (coordBall2.X === coordBall1.X) {
coordBall2.X += (coordBall2.X > 0) ? -1 : 1;
}
}
let pockets = [];
function createPockets(pos) {
let pocket;
if (pos === "top") {
pocket = [0, getRandomInt(0, innerSize)];
} else if (pos === "bottom") {
pocket = [outerSize - 1, getRandomInt(0, innerSize)];
} else if (pos === "left") {
pocket = [getRandomInt(0, outerSize), 0];
} else if (pos === "right") {
pocket = [getRandomInt(0, outerSize), innerSize - 1];
}
pockets.push(pocket)
}
let totalS = outerSize + innerSize;
if (totalS === 6) {
createPockets("top");
createPockets("bottom");
} else if (totalS < 10) {
createPockets("top");
createPockets("bottom");
createPockets("left");
createPockets("right");
} else if (totalS < 14) {
createPockets("top");
createPockets("bottom");
createPockets("left");
createPockets("right");
createPockets("left");
createPockets("right");
} else {
createPockets("top");
createPockets("bottom");
createPockets("top");
createPockets("bottom");
createPockets("left");
createPockets("right");
createPockets("left");
createPockets("right");
}
for (let i = 0; i < pockets.length; i++) {
let pocket = pockets[i];
arr[pocket[0]][pocket[1]] = "U";
}
arr[coordBall1.Y][coordBall1.X] = (getRandomInt(1, 50)).toString();
arr[coordBall2.Y][coordBall2.X] = "O";
return arr;
}
function mySol(table) {
let force;
let coordBall1 = {};
let coordBall2 = {};
for (let i = 0; i < table.length; i++) {
for (let k = 0; k < table[i].length; k++) {
if (Object.keys(coordBall1).length && Object.keys(coordBall2).length) break;
if (!isNaN(table[i][k])) {
coordBall1.X = k;
coordBall1.Y = i;
force = table[i][k];
} else if (table[i][k] == "O") {
coordBall2.X = k;
coordBall2.Y = i;
}
}
}
let dir = { X: 0, Y: 0 };
let y = Math.abs(coordBall1.Y - coordBall2.Y);
let x = Math.abs(coordBall1.X - coordBall2.X);
if (coordBall1.Y === coordBall2.Y) {
force -= x;
} else if (coordBall1.X === coordBall2.X) {
force -= y;
} else {
if (y !== x) return [false, [coordBall2.Y, coordBall2.X]]; //check if there is no collision
force -= x;
}
if (force <= 0) return [false, [coordBall2.Y, coordBall2.X]]; //check if there is no collision
dir.X = (coordBall1.X > coordBall2.X) ? -1 : (coordBall1.X === coordBall2.X) ? 0 : 1;
dir.Y = (coordBall1.Y > coordBall2.Y) ? -1 : (coordBall1.Y === coordBall2.Y) ? 0 : 1;
let tableLimit = {
X: table[0].length - 1,
Y: table.length - 1
}
while (force--) {
let possibleX = coordBall2.X + dir.X;
let possibleY = coordBall2.Y + dir.Y;
if (possibleX > tableLimit.X || possibleX < 0) dir.X = -dir.X;
if (possibleY > tableLimit.Y || possibleY < 0) dir.Y = -dir.Y;
coordBall2.X += dir.X;
coordBall2.Y += dir.Y;
if (table[coordBall2.Y][coordBall2.X] === "U") return [true, [coordBall2.Y, coordBall2.X]];
}
return [false, [coordBall2.Y, coordBall2.X]];
}
function verify() {
for (let i = 0; i < 90; i++) {
let arr = createTest();
let solution = mySol(arr)
it(`arr`, function() {
assert.deepEqual(poolgame(arr), solution);
});
}
}
verify()
});