Skip to content

Instantly share code, notes, and snippets.

@incik
Created January 13, 2021 15:18
Show Gist options
  • Save incik/2c5b474211f24b20115785ad7ebcd8ce to your computer and use it in GitHub Desktop.
Save incik/2c5b474211f24b20115785ad7ebcd8ce to your computer and use it in GitHub Desktop.
Logic of 2048
export enum Direction {
Up = 'Up',
Right = 'Right',
Down = 'Down',
Left = 'Left',
}
export interface XY {
x: number;
y: number;
}
export type Matrix = number[][];
type MovementResult = { matrix: Matrix; withScore: number };
// privates
const __getRandomNumber = (min: number, max: number) =>
Math.floor(Math.random() * max) + min;
const __getRandomValue = () => {
return [2, 2, 2, 2, 4][Math.floor(Math.random() * 5)];
};
const __getFreeCells = (matrix: Matrix): Array<XY> => {
let result = [];
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === 0) {
result.push({ x: i, y: j });
}
}
}
return result;
};
const __generateNewNumber = (matrix: Matrix, x?: number, y?: number) => {
let coords = { x, y };
if (!x && !y) {
const freeCells = __getFreeCells(matrix);
coords = freeCells[__getRandomNumber(0, freeCells.length)];
}
if (!!coords) {
const newMatrix = JSON.parse(JSON.stringify(matrix)); // hack for deep array copy
newMatrix[coords.x!][coords.y!] = __getRandomValue();
return newMatrix;
} else {
return matrix;
}
};
const invertMatrix = (matrix: Matrix) => {
let newMatrix = [];
for (let i = 3; i >= 0; i--) {
newMatrix.push(matrix[i]);
}
return newMatrix;
};
// moving numbers
const __squashRowLeft = (row: number[]) => {
let vals = row.filter((v) => v);
let zeros = Array(4 - vals.length).fill(0);
return vals.concat(zeros);
};
const __squashRowRight = (row: number[]) => {
let vals = row.filter((v) => v);
let zeros = Array(4 - vals.length).fill(0);
return zeros.concat(vals);
};
const __squashColumnUp = (matrix: Matrix, columnIndex: number) => {
let res = [];
for (let i = 0; i < 4; i++) {
if (matrix[i][columnIndex] !== 0) {
res.push(matrix[i][columnIndex]);
}
}
for (let i = 0; i < 4; i++) {
matrix[i][columnIndex] = res[i] === undefined ? 0 : res[i];
}
return matrix;
};
const __squashColumnDown = (matrix: Matrix, columnIndex: number) => {
return invertMatrix(__squashColumnUp(invertMatrix(matrix), columnIndex));
};
// combine
const __combineLeft = (row: number[]) => {
let pts = 0;
for (let i = 0; i < row.length - 1; i++) {
if (row[i] === row[i + 1]) {
row[i] *= 2;
row[i + 1] = 0;
pts += row[i];
}
}
return { row: __squashRowLeft(row), gainedPoints: pts };
};
const __combineRight = (row: number[]) => {
let pts = 0;
for (let i = 3; i >= 1; i--) {
if (row[i] === row[i - 1]) {
row[i] *= 2;
row[i - 1] = 0;
pts += row[i];
}
}
return { row: __squashRowRight(row), gainedPoints: pts };
};
const __combineUp = (matrix: Matrix, columnIndex: number) => {
let pts = 0;
for (let i: number = 0; i < 3; i++) {
if (matrix[i][columnIndex] === matrix[i + 1][columnIndex]) {
matrix[i][columnIndex] *= 2;
matrix[i + 1][columnIndex] = 0;
pts += matrix[i][columnIndex];
}
}
return { matrix: __squashColumnUp(matrix, columnIndex), gainedPoints: pts };
};
const __combineDown = (matrix: Matrix, columnIndex: number) => {
const combined = __combineUp(invertMatrix(matrix), columnIndex);
return {
matrix: invertMatrix(combined.matrix),
gainedPoints: combined.gainedPoints,
};
};
// compare matrixes and return true if they are equal
export const __eqMatrixes = (a: Matrix, b: Matrix, c?: Matrix, d?: Matrix) => {
return !!c && !!d
? JSON.stringify(a) === JSON.stringify(b) &&
JSON.stringify(b) === JSON.stringify(c) &&
JSON.stringify(c) === JSON.stringify(d)
: JSON.stringify(a) === JSON.stringify(b);
};
export const __makeAMove = (
matrix: Matrix,
inDirection: Direction,
withScore = 0,
): MovementResult => {
switch (inDirection) {
case Direction.Left: {
for (let i = 0; i < 4; i++) {
const combined = __combineLeft(__squashRowLeft(matrix[i]));
matrix[i] = combined.row;
withScore += combined.gainedPoints;
}
break;
}
case Direction.Right: {
for (let i = 0; i < 4; i++) {
const combined = __combineRight(__squashRowRight(matrix[i]));
matrix[i] = combined.row;
withScore += combined.gainedPoints;
}
break;
}
case Direction.Up: {
for (let i = 0; i < 4; i++) {
const combined = __combineUp(__squashColumnUp(matrix, i), i);
matrix = combined.matrix;
withScore += combined.gainedPoints;
}
break;
}
case Direction.Down: {
for (let i = 0; i < 4; i++) {
const combined = __combineDown(__squashColumnDown(matrix, i), i);
matrix = combined.matrix;
withScore += combined.gainedPoints;
}
break;
}
}
return { matrix, withScore };
};
export const __testMovement = (matrix: Matrix): boolean => {
const left = __makeAMove(matrix, Direction.Left).matrix;
const right = __makeAMove(matrix, Direction.Left).matrix;
const up = __makeAMove(matrix, Direction.Up).matrix;
const down = __makeAMove(matrix, Direction.Down).matrix;
return !__eqMatrixes(left, right, up, down);
};
// public
export const generate = () => {
const emptyMatrix = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
];
return __generateNewNumber(__generateNewNumber(emptyMatrix));
};
export const move = ({
from,
inDirection,
withScore,
}: {
from: Matrix;
inDirection: Direction;
withScore: number;
}): { state: Matrix; score: number; finished: boolean } => {
if (__testMovement(from)) {
const newMatrix = __makeAMove(from, inDirection, withScore);
return {
state: __eqMatrixes(from, newMatrix.matrix)
? newMatrix
: __generateNewNumber(newMatrix.matrix), // did something change? then add new number
finished: false,
score: newMatrix.withScore,
};
} else {
return {
state: from,
finished: true,
score: withScore,
};
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment