如何实现 2048

如何解决如何实现 2048

我正在尝试使用 JavaScript 实现 the game 2048。我使用二维数组来表示板。对于每一行,它使用一个整数数组表示。

在这里,我专注于实现左合并功能,即在用户按下键盘上的左键后发生的合并。

这是我想出的一组测试用例

const array1 = [2,2,0] //  [4,0]
const array2 = [2,2] // [4,4,0]
const array3 = [2,0]
const array4 = [2,16] // [4,16,0]

注释部分是merge left发生后的预期结果。

这是我的尝试

const arrays = [
  [2,0],//  [4,0]
  [2,2],// [4,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)));

所以这里的想法是我合并数组,然后将非零项向左移动。

对于这种特殊情况,我当前的解决方案有问题 - 当数组为 [2,2] 时,当预期输出为 [4,0] 时,输出为 [4,0]

我知道我的实现也不优雅。所以我很想看看如何以(更好)的方式实现这一点。

顺便说一下,我在代码审查堆栈交换中发现有一个似乎正在运行的 Python 实现。但是,我并不真正了解 Python 或函数式编程范式。如果有人可以take a look at it看看是否可以将其翻译成 JavaScript,我将不胜感激

解决方法

你可以试试这个。

const arrays = [
  [2,2,0],//  [4,0]
  [2,2],// [4,4,16] // [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)));

,

我认为递归版本在这里最简单:

const zeroFill = xs => 
  xs .concat ([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,16],[0,8,0]];

arrays .forEach (
  a => console.log(`${JSON .stringify (a)}: ${JSON .stringify (shiftLeft (a))}`)
)

我们的基本 shiftzeroFill 包裹,它向数组添加尾随零,使其长度为四。

主要函数是 shift,它执行一行的左移,但如果我要构建一个完整的 2048,我会将它用于所有移位,只需将方向转换为所需的索引即可。它是这样工作的:

  • 如果我们的数组为空,我们返回一个空数组
  • 如果第一个值为零,我们忽略它并继续数组的其余部分
  • 如果第二个值为零,我们将其删除并使用余数(包括第一个值)重复执行
  • 如果前两个值相等,我们将它们合并为第一个点并在余数上重复
  • 否则,我们保留第一个值,然后在其他所有值(包括第二个值)上重复执行

尽管我们可以移除包装器,将零填充合并到主函数中,这样,例如在第二种情况下,我们将返回 shift([n1,...ns]) 而不是返回 zeroFill(shift([n1,...ns]))。但这意味着无缘无故地多次调用零填充。

更新

评论要求澄清我将如何使用它来向各个方向移动板。这是我的第一个想法:

// 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,... shift ([n1,... ns])]

const shiftRow = (ns) => 
  shift (ns) .concat ([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,[8,8],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}

我们从反转数组副本的函数开始,并在主对角线上(西北到东南)转置网格。我们将这两者结合起来以创建顺时针和逆时针旋转网格的函数。然后我们包含上面讨论的函数,稍微重命名,并内联零填充助手。

使用这些,我们现在可以很容易地编写我们的方向移位函数。 shiftLeft 只是在行上映射 shiftRowshiftRight 首先反转行,调用 shiftLeft 然后再次反转它们。 shiftUpshiftDown 逆时针旋转棋盘,分别调用shiftLeftshiftRight,然后顺时针旋转棋盘。

请注意,这些主要函数都不会改变您的数据。每个返回一个新板。这是函数式编程最重要的原则之一:将数据视为不可变的。

这不是一个完整的 2048 系统。它不会向板随机添加新的 24,也没有任何用户界面的概念。但我认为对于游戏的功能版本来说,它可能是一个相当可靠的核心..

,

这是一个在一个循环中执行合并和移位的函数:

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,0]
];

for (let array of arrays) console.log(...mergeLeft(array));

说明

for 循环将 endIndex 从 1 增加到 3。此索引代表需要转移和/或合并的潜在价值。

如果该索引指向一个空槽(值为 0),那么它不需要发生任何事情,因此我们continue 进行循环的下一次迭代。

所以现在我们遇到了 endIndex 指的是非零值的情况。有两种情况需要使用该值发生某些事情:

  • startIndex 处的值为零:在这种情况下,endIndex 处的值必须移动到 startIndex

  • startIndex 处的值等于 endIndex 处的值:在这种情况下,endIndex 处的值也必须移动到 startIndex,但要加上什么已经在那里了。

这些案例非常相似。在第一种情况下,我们甚至可以说 endIndex 处的值被添加startIndex 处的值,因为后者为零。所以这两种情况在一个 if 块中处理。

如果我们不是这两种情况中的任何一种,那么我们知道 startIndex 处的值是非零的,并且与 endIndex 处的值不同。在这种情况下,我们应该保持 startIndex 的值不变,然后继续。但是,我们应该在下一次迭代中再次重新考虑这个 endIndex 的值,因为它可能需要静止不动。所以这就是为什么我们这样做 endIndex-- 是为了消除循环的 endIndex++ 将在一瞬间发生。

有一种情况我们确实想要去下一个endIndex:那就是当startIndex变得等于endIndex时:那不应该在这个算法中是允许的。

最后,当 startIndex 最初具有非零值时,它会增加。但是,如果在此迭代开始时为零,则应在循环的下一次迭代中重新考虑。所以我们不加1。 startIndex += !!target 只是另一种方式:

if (target > 0) startIndex++;

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res