Created
January 13, 2021 15:18
-
-
Save incik/2c5b474211f24b20115785ad7ebcd8ce to your computer and use it in GitHub Desktop.
Logic of 2048
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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