简体   繁体   English

如何实现 2048 的合并功能

[英]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.我们的基本shiftzeroFill包装,它在数组中添加尾随零,使其长度为四。

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:它是这样工作的:

  • If our array is empty, we return an empty array如果我们的数组为空,我们返回一个空数组
  • If the first value is zero, we ignore it and continue with the rest of the array如果第一个值为零,我们忽略它并继续数组的 rest
  • If the second value is zero, we remove it and recur with the remainder (including the first value)如果第二个值为零,我们将其删除并用余数(包括第一个值)递归
  • If the first two values are equal, we combine them for the first spot and recur on the remainder如果前两个值相等,我们将它们组合为第一个点并在其余点上重复
  • Otherwise, we keep the first value, and then recur on everything else (including the second value)否则,我们保留第一个值,然后在其他所有内容上重复(包括第二个值)

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.但这意味着无缘无故地多次调用零填充。

Update更新

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. shiftUpshiftDown分别调用shiftLeftshiftRight逆时针旋转棋盘,然后顺时针旋转棋盘。

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));

Explanations解释

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.

相关问题 如何在 Angular 中实现打印功能? - How can I implement a print functionality in Angular? 如何实现Javascript自动完成功能? - How can I implement Javascript autocomplete functionality? 如何获得IE的Javascript来实现“ this”关键字功能? - How can I get IE's Javascript to implement the 'this' keyword functionality? 如何在GMail中实现聊天窗口的弹出功能? - How can I implement the pop out functionality of chat windows in GMail? 如何在Three.js中从任何摄像机角度实现ZoomAll功能? - How can I implement ZoomAll functionality from any camera angle in Three.js? 在没有滚动条的情况下,如何在具有“文本溢出:省略号”的文本上实现水平滚动拖动功能? - How can I implement horizontal scroll on drag functionality on text that has “text-overflow: ellipsis” without the scrollbar? 如何在 React 应用程序中实现类似 Wordle 的共享功能? - How can I implement a Wordle-like sharing functionality in a React application? 我们如何对 mongoDB 数据库中的加密数据实现搜索功能? - How can we implement search functionality on the encrypted data in mongoDB database? 我们如何在钛合金网格视图项目中实现Onclick功能 - How we can implement the Onclick functionality in titanium grid view item 如何为数据库实现 sequelize 向下迁移功能? - How do I implement sequelize migration down functionality for databases?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM