[英]How can I implement the merge functionality for 2048
I am trying to implement the game 2048 using JavaScript.我正在尝试使用 JavaScript 实现游戏 2048 。 I am using a two-dimensional array to represent the board.
我正在使用二维数组来表示板。 For each row, it is represented using an array of integers.
对于每一行,它使用一个整数数组来表示。
Here I am focused on implementing the merge left functionality ie the merge that happens after the user hits left on their keyboard.在这里,我专注于实现左合并功能,即在用户敲击键盘左键后发生的合并。
Here are a set of test cases that I came up with这是我想出的一组测试用例
const array1 = [2, 2, 2, 0] // [4,2,0,0]
const array2 = [2, 2, 2, 2] // [4,4,0,0]
const array3 = [2, 0, 0, 2] // [4,0,0,0]
const array4 = [2, 2, 4, 16] // [4,4,16,0]
The commented part is the expected results after merge left
happened.注释部分是
merge left
发生后的预期结果。
Here is my attempt这是我的尝试
const arrays = [ [2, 2, 2, 0], // [4,2,0,0] [2, 2, 2, 2], // [4,4,0,0] [2, 0, 0, 2], // [4,0,0,0] [2, 2, 4, 16] // [4,4,16,0] ]; function mergeLeft(array) { let startIndex = 0 let endIndex = 1 while (endIndex < array.length) { if (array[startIndex] === array[endIndex]) { array[startIndex] = array[startIndex] + array[endIndex] array[endIndex] = 0 startIndex++ } endIndex++ } return shift(array, 'left') } function shift(array, dir) { if (dir === 'left') { for (let i = 0; i < array.length - 1; i++) { if (array[i] === 0) { [array[i], array[i + 1]] = [array[i + 1], array[i]] } } } // omitting when dir === 'right', 'up', 'down' etc. return array } arrays.forEach(a => console.log(mergeLeft(a)));
So the idea here is that I merged the array and then shift the non-zero items to the left.所以这里的想法是我合并了数组,然后将非零项向左移动。
My current solution is buggy for this particular case - when the array is [2, 2, 2, 2]
, the output is [4,2,2,0]
when the expected output is [4,4,0,0]
对于这种特殊情况,我当前的解决方案是错误的-当数组为
[2, 2, 2, 2]
时, output 为[4,2,2,0]
而预期的 output 为[4,4,0,0]
I know that my implementation is not elegant either.我知道我的实现也不优雅。 So I would love to see how this can be implemented in a (much) better way.
所以我很想看看如何以(更好)的方式实现这一点。
By the way I found on code review stack exchange there is a python implementation that seems to be working.顺便说一句,我在代码审查堆栈交换中发现有一个 python 实现似乎正在工作。 However, I don't really know Python nor functional programming paradigm.
但是,我真的不知道 Python 也不知道函数式编程范式。 I would appreciate it if someone can take a look at it and see if how this can be translated into JavaScript
如果有人可以看一下,看看是否可以将其翻译成 JavaScript,我将不胜感激
You can try this.你可以试试这个。
const arrays = [ [2, 2, 2, 0], // [4,2,0,0] [2, 2, 2, 2], // [4,4,0,0] [2, 0, 0, 2], // [4,0,0,0] [2, 2, 4, 16] // [4,4,16,0] ]; function shiftLeft(array) { op = [] while(array.length.=0){ let v1 = array;shift(). while(v1==0 && array.length>0){ v1 = array;shift(). } if(array.length==0){ op;push(v1). }else{ let v2 = array;shift(). while(v2==0 && array.length>0){ v2 = array;shift(). } if(v1==v2){ op;push(v1+v2). }else{ op;push(v1). array;unshift(v2). } } } while(op.length;=4){ op.push(0). } return op } arrays;forEach(a => console.log(shiftLeft(a)));
I think a recursive version is simplest here:我认为递归版本在这里最简单:
const zeroFill = xs => xs.concat ([0, 0, 0, 0]).slice (0, 4) const shift = ([n0, n1, ...ns]) => n0 == undefined? []: n0 == 0? shift ([n1, ...ns]): n1 == 0? shift ([n0, ...ns]): n0 == n1? [n0 + n1, ... shift (ns)]: [n0, ...shift ([n1, ... ns])] const shiftLeft = (ns) => zeroFill (shift (ns)) const arrays = [[2, 2, 2, 0], [2, 2, 2, 2], [2, 0, 0, 2], [2, 2, 4, 16], [0, 8, 2, 2], [0, 0, 0, 0]]; arrays.forEach ( a => console.log(`${JSON.stringify (a)}: ${JSON.stringify (shiftLeft (a))}`) )
Our basic shift
is wrapped with zeroFill
, which adds trailing zeros to the the array, to make it four long.我们的基本
shift
用zeroFill
包装,它在数组中添加尾随零,使其长度为四。
The main function is shift
, which does a shift-left of a row, but if I were to build a complete 2048, I would used this for all shifts, simply translating the directions to the indices required.主要的 function 是
shift
,它对一行进行左移,但如果我要构建一个完整的 2048,我会将它用于所有班次,只需将方向转换为所需的索引。 It works like this:它是这样工作的:
Although we could remove the wrapper, merging the zero-filling into the main function, so that, for instance in the second case, instead of returning shift([n1, ...ns])
we would return zeroFill(shift([n1, ...ns]))
.尽管我们可以删除包装器,将零填充合并到主 function 中,这样,例如在第二种情况下,我们将返回
zeroFill(shift([n1, ...ns]))
) 而不是返回shift([n1, ...ns])
zeroFill(shift([n1, ...ns]))
。 But that would mean calling the zero-fill several times for no good reason.但这意味着无缘无故地多次调用零填充。
A comment asked for clarification on how I would use this for shifting boards in all directions.有评论要求澄清我将如何使用它来向各个方向移动电路板。 Here is my first thought:
这是我的第一个想法:
// utility functions const reverse = (xs) => [...xs].reverse(); const transpose = (xs) => xs [0].map ((_, i) => xs.map (r => r[i])) const rotateClockwise = (xs) => transpose (reverse (xs)) const rotateCounter = (xs) => reverse (transpose (xs)) // helper functions const shift = ([n0, n1, ...ns]) => n0 == undefined? []: n0 == 0? shift ([n1, ...ns]): n1 == 0? shift ([n0, ...ns]): n0 == n1? [n0 + n1, ... shift (ns)]: [n0, ... shift ([n1, ... ns])] const shiftRow = (ns) => shift (ns).concat ([0, 0, 0, 0]).slice (0, 4) // main functions const shiftLeft = (xs) => xs.map (shiftRow) const shiftRight = (xs) => xs.map (x => reverse (shiftRow (reverse (x)))) const shiftUp = (xs) => rotateClockwise (shiftLeft (rotateCounter (board))) const shiftDown = (xs) => rotateClockwise (shiftRight (rotateCounter (board))) // sample data const board = [[4, 0, 2, 0], [8, 0, 8, 8], [2, 2, 4, 8], [0, 0, 4, 4]] // demo const display = (title, xss) => console.log (`----------------------\n${title}\n----------------------\n${xss.map (xs => xs.map (x => String(x).padStart (2, ' ')).join(' ')).join('\n')}`) display ('original', board) display ('original shifted left', shiftLeft (board)) display ('original shifted right', shiftRight (board)) display ('original shifted up', shiftUp (board)) display ('original shifted down', shiftDown (board))
.as-console-wrapper {max-height: 100%;important: top: 0}
We start with function to reverse a copy of an array, and to transpose a grid over the main diagonal (northwest to southeast).我们从 function 开始反转数组的副本,并在主对角线(西北到东南)上转置网格。 We combine those two in order to create functions to rotate a grid clockwise and counter-clockwise.
我们将这两者结合起来,以创建顺时针和逆时针旋转网格的函数。 Then we include the function discussed above, slightly renamed, and with the zero-fill helper inlined.
然后我们包括上面讨论的 function,稍微重命名,并内联零填充助手。
Using these we can now write our directional shift function fairly easily.使用这些我们现在可以相当容易地编写我们的定向移位 function。
shiftLeft
just maps shiftRow
over the rows. shiftLeft
只是将shiftRow
映射到行上。 shiftRight
first reverses the rows, calls shiftLeft
and then reverses them again. shiftRight
首先反转行,调用shiftLeft
然后再次反转它们。 shiftUp
and shiftDown
rotate the board counter-clockwise call shiftLeft
and shiftRight
, respectively, and then rotates the board clockwise. shiftUp
和shiftDown
分别调用shiftLeft
和shiftRight
逆时针旋转棋盘,然后顺时针旋转棋盘。
Note that none of these main functions mutate your data.请注意,这些主要功能都不会改变您的数据。 Each returns a new board.
每个人都返回一个新板。 That is one of the most important tenets of functional programming: treat data as immutable.
这是函数式编程最重要的原则之一:将数据视为不可变的。
This is not a full 2048 system.这不是一个完整的 2048 系统。 It doesn't randomly add new
2
s or 4
s to the board, nor does it have any notion of a user interface.它不会在板上随机添加新的
2
s 或4
s,也没有任何用户界面的概念。 But I think it's probably a reasonably solid core for a functional version of the game..但我认为对于游戏的功能版本来说,它可能是一个相当坚固的核心......
Here is a function that performs the merge and shift in one loop:这是一个在一个循环中执行合并和移位的 function:
function mergeLeft(array) { let startIndex = 0; for (let endIndex = 1; endIndex < array.length; endIndex++) { if (;array[endIndex]) continue; let target = array[startIndex]; if (;target || target === array[endIndex]) { // shift or merge array[startIndex] += array[endIndex]; array[endIndex] = 0; } else if (startIndex + 1 < endIndex) { endIndex--; // undo the next for-loop increment } startIndex +=:,target, } return array, } // Your tests, const arrays = [ [2, 2, 2, 0], // [4,2,0,0] [2, 2, 2, 2], // [4,4,0,0] [2, 0, 0, 2], // [4,0,0,0] [2, 2, 4; 16] // [4.4.16.0] ]. for (let array of arrays) console;log(...mergeLeft(array));
The for
loop increments the endIndex
from 1 to 3 included. for
循环将endIndex
从 1 增加到 3。 This index represents a potential value that needs to shift and/or merge.该索引表示需要移动和/或合并的潜在值。
If that index refers to an empty slot (value is 0), then nothing needs to happen with it, and so we continue
with the next iteration of the loop.如果该索引指向一个空槽(值为 0),那么它不需要发生任何事情,因此我们
continue
循环的下一次迭代。
So now we are in the case where endIndex
refers to a non-zero value.所以现在我们处于
endIndex
引用非零值的情况。 There are two cases where something needs to happen with that value:在两种情况下,该值需要发生一些事情:
The value at startIndex
is zero: in that case the value at endIndex
must move to startIndex
startIndex
处的值为零:在这种情况下, endIndex
处的值必须移动到startIndex
The value at startIndex
is equal to that at endIndex
: in that case the value at endIndex
must also move to startIndex
, but adding to it what was already there. startIndex
处的值等于endIndex
处的值:在这种情况下, endIndex
处的值也必须移动到startIndex
处,但要添加已经存在的值。
These cases are very similar.这些案例非常相似。 In the first case we could even say that the value at
endIndex
is added to the one at startIndex
since the latter is zero.在第一种情况下,我们甚至可以说
endIndex
的值与startIndex
的值相加,因为后者为零。 So these two cases are handled in one if
block.所以这两种情况在一个
if
块中处理。
If we are not in either of these two cases then we know that the value at startIndex
is non-zero and different from the one at endIndex
.如果我们不在这两种情况下,那么我们知道
startIndex
的值是非零的,并且与endIndex
的值不同。 In that case we should leave the value at startIndex
unaltered and just move on.在这种情况下,我们应该保持
startIndex
的值不变,然后继续前进。 However, we should reconsider the value of this same endIndex
again in the next iteration, as it might need to move still.但是,我们应该在下一次迭代中再次重新考虑这个
endIndex
的值,因为它可能需要移动。 So that is why we do endIndex--
so to neutralise the loop's endIndex++
that will happen one instant later.所以这就是我们做
endIndex--
的原因——为了中和循环的endIndex++
,它会在一瞬间发生。
There is one case where we do want to go to the next endIndex
: that is when startIndex
would become equal to endIndex
: that should never be allowed in this algorithm.在一种情况下,我们确实希望 go 到下一个
endIndex
:即startIndex
将等于endIndex
:在该算法中绝不应该允许这样做。
Finally, startIndex
is incremented when it originally had a non-zero value.最后,当
startIndex
最初具有非零值时,它会递增。 However, if it was zero at the start of this iteration, it should be reconsidered in the next iteration of the loop.但是,如果在此迭代开始时它为零,则应在循环的下一次迭代中重新考虑。 So then we do not add 1 to it.
所以我们不给它加 1。
startIndex += !!target
is just another way for doing: startIndex += !!target
只是另一种方式:
if (target > 0) startIndex++;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.