24 点纸牌游戏:加减乘除凑出 24,一秒上头

举报
deli007 发表于 2026/09/14 16:18:16 2026/09/14
【摘要】 24 点纸牌游戏:加减乘除凑出 24,一秒上头

24 点的乐趣全在「这四张牌到底有没有解」的抓心挠肝里。这个工具不只是随机发四张牌,发牌前会先用完整搜索确认有解,避免拿到一手死局;点牌、点亮运算符就能搭表达式,实时校验括号和答案,卡住还能一键看解法。先看成品,再讲怎么用华为云 CodeArts 码道 WebUI 一段话把它做出来、部署上线。

这个工具做什么

它把「24 点」做成一个即开即玩的单页小游戏:

  • 发牌必可解:不是随便发四张,而是先跑 solve24 全搜索,筛出确实能凑到 24 的组合。
  • 点牌搭表达式:点击卡牌和 + − × ÷ ( ) 按钮组成式子,用过的牌自动灰掉,不许连放两张数字。
  • 实时校验:括号是否配对、表达式是否完整、数值是否等于 24,一输入就有反馈。
  • 三档难度:简单 / 中等 / 困难对应不同的数值范围和倍数加成。
  • 计时计分:越快解出分数越高,用提示会打折;连续得分记录在最近成绩里。
  • 提示解法:卡住时一键展示可解的完整表达式,学完再继续。
  • 重新发牌:再来一局,计时和牌面立即重置。
  • 胜利特效:彩带 + 清晰的成功文案,成就感拉满。

24 点纸牌游戏运行界面

码道开通(免费体验版)

开始之前,先免费开通码道体验版。不需要装任何软件,浏览器就能用:

https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwsf&sourcead=dmzntgwwbwz

开通后有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

进入 AI 开发者空间

  1. 浏览器打开 https://devstation.developer.huaweicloud.com/aidevspace?utm_source=opcsfsq&utm_medium=wbwz
  2. 弹窗「请选择要进入的版本」→ 点击 体验版
  3. 首次使用会跳到华为云登录页,扫码登录一次,后续浏览器会话复用
  4. 弹窗「临时凭据自动配置」→ 点击 同意,沙箱自动创建

进入 AI 开发者空间主界面后,先等左侧菜单加载出来,再找到 CodeArts,点击它右侧的浏览器图标,打开码道 WebUI 对话窗口:

AI 开发者空间主界面

打开后码道 WebUI 会先和你打招呼,下方就是需求输入框:

码道 WebUI 欢迎页

小贴士:体验版沙箱 3 小时后自动重置,生成完记得把 index.html 下载保存。

任务描述

在码道 WebUI 输入框粘贴以下需求,回车发送:

开发一个「24 点纸牌游戏」单文件网页应用:随机抽 4 张扑克牌(用 1~13 表示数字),玩家用加减乘除和括号把四个数凑成 24;支持点击卡牌和运算符构建表达式、实时校验、提示与显示解法、计时和计分、选择难度、重新发牌,界面美观耐玩,手机和电脑都能用,所有代码写在一个 index.html 里。完成后用部署功能部署成可访问的网页应用,生成一个公网可访问的分享链接,并告诉我访问地址。

输入需求后开始生成

这轮码道具体做了什么

不是复述通用 AI 能力,而是它在这次项目里的实际动作:

  • /workspace/game24 下创建单文件 index.html,把求解搜索、发牌、表达式构建、计分和胜利特效全部放进一个文件,无第三方依赖。
  • 写入代码后先做 JS 语法检查,再探测空闲端口 3002、启动 http.servercurl 自检 HTTP 200。
  • 用 DevBridge 创建公网隧道并验证内容,最后给出 8 小时有效的公网分享链接。

代码亮点

以下是 index.html 里值得关注的设计思路和实现细节。

1. 完整搜索求解,不是拍脑袋拼

solve24 把问题拆成递归:每次从当前数字列表里挑两个数做加减乘除,把结果和一个带括号的表达式塞回列表,直到只剩一个数,看它是否等于 24:

function solve24(nums){
  function helper(arr){
    if(arr.length===1){
      if(Math.abs(arr[0].v-24)<EPS) return arr[0].e;
      return null;
    }
    for(let i=0;i<arr.length;i++){
      for(let j=0;j<arr.length;j++){
        if(i===j) continue;
        const a=arr[i], b=arr[j];
        const rest=arr.filter((_,k)=>k!==i&&k!==j);
        const cands=[
          {v:a.v+b.v, e:`(${a.e}+${b.e})`},
          {v:a.v-b.v, e:`(${a.e}-${b.e})`},
          {v:a.v*b.v, e:`(${a.e}×${b.e})`}
        ];
        if(Math.abs(b.v)>EPS) cands.push({v:a.v/b.v, e:`(${a.e}÷${b.e})`});
        for(const c of cands){
          const r=helper(rest.concat([{v:c.v,e:c.e}]));
          if(r) return r;
        }
      }
    }
    return null;
  }
  return helper(nums.map(n=>({v:n,e:String(n)})));
}

1e-6 做浮点容差,避免 6/(1-5/8) 这类除不尽时被浮点误差误判。

2. 发牌先保证有解,玩起来才不窝火

deal 会先随机抽一组数,用 hasSolution 判断是否有解,没有就重抽,直到抽到可解组合:

function hasSolution(nums){ return solve24(nums)!==null; }

function deal(){
  const cfg=DIFF_CFG[state.diff];
  let tries=0, vals;
  while(true){
    vals=[randInt(cfg.min,cfg.max),randInt(cfg.min,cfg.max),randInt(cfg.min,cfg.max),randInt(cfg.min,cfg.max)];
    tries++;
    if(!cfg.ensureSolvable) break;
    if(hasSolution(vals) || tries>2000) break;
  }
  state.cards=vals.map((v,i)=>({id:i,value:v,suit:SUITS[randInt(0,3)],used:false}));
  state.tokens=[]; state.solved=false; state.hintUsed=false;
  state.startTs=Date.now(); startTimer();
  renderCards(); renderExpr(); setMsg('info','💡 新的一局开始!把四张牌凑成 24 吧。');
  submitBtn.classList.remove('ready');
}

多一道搜索,换来的是用户永远拿不到「无解死局」。

3. 表达式求值带校验,括号和数值一次判清

玩家点出来的 token 最终拼成字符串,用 Function 做一次安全范围里的求值;validate 同时检查括号配对、表达式完整度、是否用完四张牌、是否等于 24:

function evalExpr(raw){
  try{
    const v=Function('"use strict";return ('+raw+')')();
    if(typeof v==='number' && isFinite(v)) return v;
  }catch(e){}
  return null;
}

function validate(announce){
  const used=state.cards.filter(c=>c.used).length;
  if(balance()!==0){
    setResult('bad','括号未配对','未完成'); submitBtn.classList.remove('ready'); return;
  }
  const raw=buildRawExpr();
  const v=evalExpr(raw);
  if(v===null){
    setResult('wait','表达式不完整','未完成'); submitBtn.classList.remove('ready'); return;
  }
  const allUsed = used===4;
  const is24 = Math.abs(v-24)<EPS;
  if(is24 && allUsed){
    setResult('ok',fmt(v),'= 24 ✓');
    submitBtn.classList.add('ready');
    if(announce) doWin();
  }
}

4. 计分和连胜都写进 localStorage,关掉也不丢

doWin 用难度倍率减去耗时的公式算分,提示会打折,分数和连胜记录持久化保存:

function doWin(){
  if(state.solved) return;
  state.solved=true; stopTimer();
  const elapsed=Math.floor((Date.now()-state.startTs)/1000);
  const cfg=DIFF_CFG[state.diff];
  let gain=Math.round(cfg.mult*100 - elapsed);
  if(state.hintUsed) gain=Math.round(gain*0.4);
  if(gain<10) gain=10;
  state.score+=gain; state.streak+=1;
  scoreEl.textContent=state.score; streakEl.textContent=state.streak;
  const solStr=state.tokens.map(t=>{
    if(t.type==='num'){ const c=state.cards[t.cardId]; return (FACE[c.value]||c.value)+c.suit; }
    if(t.value==='('||t.value===')') return t.value;
    return OP_DISPLAY[t.value];
  }).join(' ');
  setMsg('success',`🎉 太棒了!<span class="solution">${solStr} = 24</span> 用时 ${sec2str(elapsed)},+${gain}${state.hintUsed?'(用了提示)':''}`);
  state.history.unshift(`+${gain}`);
  if(state.history.length>5) state.history.pop();
  historyEl.innerHTML='最近得分:'+state.history.map(h=>`<span>${h}</span>`).join(' ');
  submitBtn.classList.remove('ready');
  confetti();
}

直接下载试玩

本次生成的项目就是单文件 index.html,没有第三方依赖,把文件保存到本地后双击用浏览器打开即可运行。源码同步到公开 Demo Park 仓库:

总结

24 点小游戏核心就两件事:用递归搜索保证发牌有解,用带容差的表达式求值保证判定准确。solve24 每次合并两个数并保留带括号的表达式,最终返回一条完整解法;deal 在发牌前排掉死局,validate 在提交前检查括号、完整度和结果。想把工具再变强,可以接着做「多解提示」「限时挑战排行榜」或把运算符扩展到乘方和开方。

如果还没用过码道,可以免费开通体验版:https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwsf&sourcead=dmzntgwwbwz

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。