范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

用原生JS开发编程游戏机器人流水线

  作者:吴亮(月影)
  记得之前玩过一个 flash 编程小游戏,印象深刻,叫"机器人流水线(manufactoria)",不知道有没有同学也玩过。可惜的是,现在 falsh 已经停止运行了,这个原版的小游戏无法体验到。
  不过最近几天,我凭着之前的印象,复刻出了这个小游戏。
  这个小游戏的规则是,将左侧的元件放置到右侧的面板上,然后点击运行,机器人会沿着元件指定的路径运行,并影响地步序列的状态,最终按照任务的要求完成,即可过关。
  例如上面的截图是第五关,任务是"队列里不能出现不同颜色的球",也就是说如果队列中只有红球或只有蓝球,要把机器人移动到 处,否则将机器人移到任意其他空格。
  我们能将元件放置到在任意白色空格处,机器人走到元件上会根据元件的类型来产生相应的动作。
  manufactoria 的元件非常简单,只有两种类型:传送器和比较器,但根据不同的作用一共分为 7 种:
  其中传送器有五种,四种带颜色的,机器人通过的时候会将对应颜色的球添加到序列的末尾,还有最后一种黑色的,机器人通过,序列不变。
  比较器有两种,分别是红蓝比较器和黄绿比较器。比较器的作用是,当机器人通过它时,判断序列头部的球颜色,若颜色是比较器允许的颜色,则机器人朝对应的加号方向前进,并将该序列头部的这个球取出,否则,机器人沿着弧形箭头方向前进,且序列保持不变。
  神奇的是有了这些简单的元件,我们就可以让机器人完成复杂的任务了。而且这和编程思想是一致的,我们可以通过元件构建出顺序,选择和循环结构体!
  如下图,在第 22 关,可以用绿色小球构建出循环体解决问题:
  好了,前面说了规则,有兴趣的同学可以自行挑战,目前有 20 多个关卡,我会不定期更新新的关卡,等待大家的挑战。
  接下来,我们看一下游戏是怎么实现的。
  首先是面板的 HTML 结构,这个结构非常简单:                                                                                    说明:鼠标选择上方元件添加到右侧面板中,键盘上下左右旋转,空格翻转。                                                  序列 ← ❤️     结果 →
  在这里我就不多说了,元件是通过 CSS 样式绘制的,比如比较器: .comparator {   margin: 10px 20px;   border-bottom-right-radius: 50%;   border-bottom-left-radius: 50%; } .comparator::before {   content: "+";   margin-left: -10px; } .comparator::after {   content: "+";   margin-left: 10px; }  .comparator.red::before {   color: red; } .comparator.green::before {   color: green; }  .comparator.blue::after {   color: blue; } .comparator.yellow::after {   color: orange; }
  因为所有的元件结构都不复杂,所以用一个 HTML 标签,加上 before 和 after 伪元素,就完全可以绘制出来的。
  右侧的网格是一个 grid 布局的棋盘: #app {   width: 520px;   height: 520px;   border-bottom: solid 1px #0002;   border-right: solid 1px #0002;   background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.15) 2.5%, transparent 2.5%), linear-gradient( rgba(0, 0, 0, 0.15) 2.5%, transparent 2.5%);   background-size: 40px 40px;   background-repeat: repeat;   display: grid;   grid-template-columns: repeat(13, 40px);   grid-template-rows: repeat(13, 40px); }  #app > p {   text-align: center;   font-size: 1.8rem;   line-height: 48px;; }
  在网格中添加对应的元件,就只要找到对应的格子往里添加指定类型的元素就可以了。
  机器人是绝对定位的元素,它移动的时候的动画效果可以通过 transition 给出: #robot {   position: absolute;   transition: all linear .2s; }  #robot::after {   font-size: 1.8rem;   content: "";   margin: 5px; }
  这样,基本的 HTML 和 CSS 就实现完成了。实际上,大部分 UI 和交互效果都可以通过 HTML 和 CSS 指定,让 JS 只需要负责控制逻辑,这样就简单很多。
  接下来我们看具体的逻辑。
  首先我们实现一个点击左侧面板的元件,将元件用鼠标拾取的效果: unction enablePicker() {   const buttons = panel.querySelector(".buttons");   buttons.addEventListener("mousedown", ({target}) => {     if(main.className !== "running" && target !== buttons && target.className) {       const node = target.cloneNode(true);       mousePick.innerHTML = "";       mousePick.appendChild(node);     }   });   window.addEventListener("mousemove", ({x, y}) => {     mousePick.style.left = `${x - 25}px`;     mousePick.style.top = `${y - 25}px`;   });   window.addEventListener("contextmenu", (e) => {     e.preventDefault();     return false;   });   window.addEventListener("mouseup", ({target}) => {     if(target.parentNode !== buttons && target.className !== "normal") {       mousePick.innerHTML = "";     }   });   window.addEventListener("keydown", ({key}) => {     const el = mousePick.children[0];     if(!el || el.className === "trash") return;     if(key === "ArrowRight") {       el.dataset.turn = 0;     } else if(key === "ArrowDown") {       el.dataset.turn = 1;     } else if(key === "ArrowLeft") {       el.dataset.turn = 2;     } else if(key === "ArrowUp") {       el.dataset.turn = 3;     } else if(key === " ") {       let n = Number(el.dataset.flip) || 0;       el.dataset.flip = ++n % 2;     }     if(key.startsWith("Arrow") && el.classList.contains("comparator")) {       el.dataset.turn = (Number(el.dataset.turn) + 3) % 4;     }   }); }
  这里,我们直接用 cloneNode,将面板上的元素复制出来,做出一个透明效果,跟随鼠标移动。另外,我们还做了键盘控制,通过键盘控制元件的具体方向:
  注意,我们用 JS 控制元素方向的时候,通过设置 turn 和 flip 来表示元素翻转,至于元素具体的展现,则通过 CSS 来定义: *[data-turn="1"] {   transform: rotate(.25turn); } *[data-turn="2"] {   transform: rotate(.5turn); } *[data-turn="3"] {   transform: rotate(.75turn); }  *[data-flip="1"] {   transform: scale(-1, 1); } *[data-turn="1"][data-flip="1"] {   transform: rotate(.25turn) scale(-1, 1); } *[data-turn="2"][data-flip="1"] {   transform: rotate(.5turn) scale(-1, 1); } *[data-turn="3"][data-flip="1"] {   transform: rotate(.75turn) scale(-1, 1); }
  接着是设置和移动机器人的函数: function setRobot() {   const start = app.querySelector(".start");   const row = Number(start.dataset.x);   const col = Number(start.dataset.y);   let {x, y} = app.getBoundingClientRect();   x = x + col * 40;   y = y + row * 40;   const el = document.getElementById("robot") || document.createElement("p");   el.id = "robot";   el.style.left = `${x}px`;   el.style.top = `${y}px`;   el.dataset.x = x;   el.dataset.y = y;   el.dataset.row = row;   el.dataset.col = col;   el.dataset.fromDirection = "";   document.body.appendChild(el); }  function moveRobot(direction) {   let x = Number(robot.dataset.x);   let y = Number(robot.dataset.y);   let row = Number(robot.dataset.row);   let col = Number(robot.dataset.col);   let fromDirection = "";   if(direction === "left") {     x -= 40;     col--;     fromDirection = "right";   } else if(direction === "right") {     x += 40;     col++;     fromDirection = "left";   } else if(direction === "up") {     y -= 40;     row--;     fromDirection = "down";   } else if(direction === "down") {     y += 40;     row++;     fromDirection = "up";   }   robot.style.left = `${x}px`;   robot.style.top = `${y}px`;   robot.dataset.x = x;   robot.dataset.y = y;   robot.dataset.row = row;   robot.dataset.col = col;   robot.dataset.fromDirection = fromDirection;   // console.log(row, col, robot);    return new Promise(resolve => {     robot.addEventListener("transitionend", () => {       // console.log(row, col, robot.dataset.row, robot.dataset.col);       resolve(robot);     }, {once: true});     // 防止浏览器transitionend事件有时候不被触发     setTimeout(() => resolve(robot), 220);   }); }
  这里, setRobot  将机器人设置到起始位置,起始位置在网格中是一个 className 包含 start 的 p 元素,这个元素的位置在后续调用 loadLevel 读取当前关卡的时候初始化。
  moveRobot  实际上是一个异步方法,它返回一个 Promise,在机器人执行完动作之后 resolve。不过这里有个细节要注意,我一开始使用transitionend  来判断动画结束,但是浏览器不能保证transitionend  每次都被触发,所以有时候机器人会不明原因停下来,后来我就加了一个 setTimeout 来防止这种情况。
  接下来的一系列方法和底部序列有关,序列代表着输入输出,机器人就是通过移动来影响序列,从而达成指定任务。序列实际上是一个队列,操作比较简单。 function setDataList(list = []) {   io.innerHTML = "序列 ← ";   for(let i = 0; i < list.length; i++) {     const el = document.createElement("i");     el.innerHTML = list[i];     io.appendChild(el);   } }  function getTopData() {   const item = io.querySelector("i");   if(item) return item.innerHTML;   else return null; }  function popData() {   const item = io.querySelector("i");   item.style.width = 0;   return new Promise(resolve => {     item.addEventListener("transitionend", () => {       item.remove();       resolve(item);     }, {once: true});     // 防止浏览器transitionend事件有时候不被触发     setTimeout(() => {       item.remove();       resolve(item);     }, 220);   }); }  function appendData(data = "") {   const el = document.createElement("i");   el.innerHTML = data;   io.appendChild(el); }  function getIOData() {   const list = io.querySelectorAll("i");   let ret = "";   for(let i = 0; i < list.length; i++) {     ret += list[i].innerHTML;   }   return ret; }
  然后是一个辅助方法,用来获得机器人所在位置的棋盘元素。我们在初始化棋盘的时候,会给每个元素设置 x 和 y 坐标,在机器人走动的时候,也会更新对应的 row 和 col 坐标,所以我们通过选择器就可以快速找到机器人所在位置的棋盘格子,从而判断其中的元件。 function getRobotCell() {   let x = Number(robot.dataset.row);   let y = Number(robot.dataset.col);   const cell = document.querySelector(`#app > p[data-x="${x}"][data-y="${y}"]`);   return cell; }
  接下来就是代码最核心的部分了。 function checkCell(cell, fromDirection) {   const ret = {     direction: null,     effect: null,     type: null,     data: false,   };    const children = cell.children;   if(children.length) {     for(let i = 0; i < children.length; i++) {       const el = children[i];       const flip = el.dataset.flip;       const turn = el.dataset.turn;       if(el.classList.contains("pass")) {         ret.type = "pass";         // 通道         if(children.length > 1) {           // 交叉通道           if(fromDirection === "up" || fromDirection === "down") {             if(turn === "0" || turn === "2") continue;           }           if(fromDirection === "left" || fromDirection === "right") {             if(turn === "1" || turn === "3") continue;           }         }         if(turn === "0") ret.direction = "right";         if(turn === "1") ret.direction = "down";         if(turn === "2") ret.direction = "left";         if(turn === "3") ret.direction = "up";         if(el.classList.contains("red")) ret.effect = "";         if(el.classList.contains("green")) ret.effect = "";         if(el.classList.contains("yellow")) ret.effect = "";         if(el.classList.contains("blue")) ret.effect = "";       } else if(el.classList.contains("comparator")) {         // 比较器         ret.type = "comparator";         const data = getTopData();         if(data === "" && el.classList.contains("red")) {           if(turn === "0") ret.direction = "left";           if(turn === "1") ret.direction = "up";           if(turn === "2") ret.direction = "right";           if(turn === "3") ret.direction = "down";           ret.data = true;         } else if(data === "" && el.classList.contains("green")) {           if(turn === "0") ret.direction = "left";           if(turn === "1") ret.direction = "up";           if(turn === "2") ret.direction = "right";           if(turn === "3") ret.direction = "down";           ret.data = true;         } else if(data === "" && el.classList.contains("blue")) {           if(turn === "0") ret.direction = "right";           if(turn === "1") ret.direction = "down";           if(turn === "2") ret.direction = "left";           if(turn === "3") ret.direction = "up";           ret.data = true;         } else if(data === "" && el.classList.contains("yellow")) {           if(turn === "0") ret.direction = "right";           if(turn === "1") ret.direction = "down";           if(turn === "2") ret.direction = "left";           if(turn === "3") ret.direction = "up";           ret.data = true;         } else {           if(turn === "0") ret.direction = "down";           if(turn === "1") ret.direction = "left";           if(turn === "2") ret.direction = "up";           if(turn === "3") ret.direction = "right";         }       }       if(flip === "1") {         // 翻转交换         if(turn === "0" || turn === "2") {           if(ret.direction === "left") ret.direction = "right";           else if(ret.direction === "right") ret.direction = "left";         } else {           if(ret.direction === "up") ret.direction = "down";           else if(ret.direction === "down") ret.direction = "up";         }       }     }   }   // console.log(ret);   return ret; }  function checkState() {   const cell = getRobotCell();   const fromDirection = robot.dataset.fromDirection;   let state = {     direction: null,     effect: null,     accepted: false,     fromDirection,   };   if(cell.className === "flag") {     state.accepted = true;   } else if(cell.className !== "start") {     state = {       ...state,       ...checkCell(cell, fromDirection),     };   }   return state; }
  当机器人移动到一个格子的时候,我们通过 checkState 判断他的状态,状态包括四个信息,direction:机器人当前可以移动的方向,effect:机器人操作序列的动作,accepted:机器人是否移动到 ,fromDirection:机器人上一步从哪里移动过来的。
  checkCell 则是具体的判断逻辑,我们通过格子中的元件来具体判断机器人的这些状态,这部分逻辑虽然较繁琐,但其实也不太复杂,唯一需要注意的是,一个网格中可以放两个相互垂直的传送器,当机器人经过的时候,如果有两个方向,会默认选择直行的方向,这也是为什么我们需要 fromDirection 来判断机器人从哪个方向过来。
  接下来是展示结果,运行、停止按钮状态,sleep 等细节,就不一一赘述了。 function initResult() {   result.innerHTML = "结果 →"; }  function appendResult(success = false) {   const r = success ? "A" : "E";   const el = document.createElement("span");   el.innerHTML = r;   if(success) el.className = "accept";   result.appendChild(el); }  function sleep(ms = 10) {   return new Promise(resolve => {     setTimeout(resolve, ms);   }); }  runBtn.addEventListener("mousedown", async () => {   mousePick.innerHTML = "";   runBtn.className = "btn tap";   runBtn.disabled = true;   main.className = "running";   await run(); }); stopBtn.addEventListener("mousedown", () => {   mousePick.innerHTML = "";   stopBtn.className = "btn tap";   main.className = "";   // setRobot(); }); window.addEventListener("mouseup", () => {   if(stopBtn.className === "btn tap") {     stopBtn.className = "btn";     // runBtn.disabled = false;     // runBtn.className = "btn";   } });
  然后,我们根据关卡数据,读取和初始化对应的关卡: let currentLevel; function loadLevel(level) {   const data = levels[level];   currentLevel = {     ...data,     level,   };   taskInfo.innerHTML = `

任务:${data.task}

提示:${data.hint}`; const items = document.querySelectorAll(".buttons > p"); for(let i = 0; i < items.length; i++) { if(!data.units.includes(i)) { items[i].classList.add("hide"); } else { items[i].classList.remove("hide"); } } setDataList([...data.tests[0].data]); const board = new Array(169); board.fill(-1); const size = data.size || 13; const v = (13 - size) / 2; const range = [ v, v, v + size, v + size, ]; const [a, b, c, d] = range; for(let i = a; i < c; i++) { for(let j = b; j < d; j++) { const idx = i * 13 + j; board[idx] = 0; } } const s = v + Math.floor(size / 2); const start = v * 13 + s; const end = (v + size - 1) * 13 + s; board[start] = 1; board[end] = 2; init(board); const savedData = localStorage.getItem(`manufactoria-level-${level}`); if(savedData) { const data = JSON.parse(savedData); for(let i = 0; i < data.cells.length; i++) { const cell = data.cells[i]; const el = document.createElement("p"); el.className = cell.state; el.dataset.turn = cell.turn; el.dataset.flip = cell.flip; app.children[cell.idx].appendChild(el); } } setRobot(); return currentLevel; }   初始化之后,当放置好元件,点击运行时,让机器人运行起来: async function run() { levelPicker.disabled = true; const tests = currentLevel.tests; initResult(); for(let i = 0; i < tests.length; i++) { const {data, accept} = tests[i]; setDataList([...data]); setRobot(); await sleep(); await moveRobot("down"); while(true) { if(main.className !== "running") break; const state = checkState(); if(state.direction) { if(state.type === "comparator" && state.data) { await Promise.all([ moveRobot(state.direction), popData(), ]); } else { await moveRobot(state.direction); if(state.effect) { appendData(state.effect); } } } else { break; } } if(main.className !== "running") break; const cell = getRobotCell(); if(accept === true) { appendResult(cell.className === "flag"); } else if(typeof accept === "string") { if(cell.className !== "flag") { appendResult(false); } else { appendResult(accept === getIOData()); } } else { appendResult(cell.className !== "flag"); } await sleep(500); } runBtn.className = "btn"; runBtn.disabled = false; if(main.className === "running") { const success = !result.textContent.includes("E"); const el = document.createElement("span"); el.innerHTML = success? ":成功":":失败"; if(success) el.className = "accept"; result.appendChild(el); setDataList([]); } main.className = ""; levelPicker.disabled = false; setRobot(); }   因为有的关卡比较复杂,玩家也不希望好不容易通关的结果,下一次进游戏又没有了,所以我们做一个 localStorage 的本地保存机制: // 把数据保存到本地 function saveLevel() { const {level} = currentLevel; const data = {level, cells: []}; const cells = app.children; for(let i = 0; i < 169; i++) { const cell = cells[i]; if(cell.children.length) { for(let j = 0; j < cell.children.length; j++) { const item = cell.children[j]; const d = { state: item.className, turn: item.dataset.turn, flip: item.dataset.flip, idx: Number(cell.dataset.x) * 13 + Number(cell.dataset.y), }; data.cells.push(d); } } } localStorage.setItem(`manufactoria-level-${level}`, JSON.stringify(data)); }   最后的最后,我们做一个下拉列表来选择对应的关卡: function initLevelPicker() { const len = levels.length; levelPicker.innerHTML = ""; for(let i = 0; i < len; i++) { const option = new Option(i + 1, i); levelPicker.appendChild(option); } levelPicker.addEventListener("change", () => { loadLevel(levelPicker.value); }); loadLevel(levelPicker.value); } initLevelPicker();   这样,我们的游戏就开发完成了。实际上这个游戏本身开发的难度并不高,但是玩法却很丰富,关卡也很有挑战性。这就是编程游戏的乐趣。   有同学玩通关的话,欢迎戳下方链接,在代码评论区交流玩法心得~   manufactoria - 码上掘金 manufactoria - 码上掘金   关注「字节前端 ByteFE」公众号,追更不迷路!


无人驾驶农机翻地插秧施肥5G智慧农场减少药肥损耗,由人工向数控升级视频加载中5G智慧农场是如何喷药施肥的?在田埂上,记者看到了一辆正在工作的无人植保车,进行着喷洒作业演示。这条田埂宽约80厘米,车身宽约75厘米的无人植保车正稳稳行驶在上面。在土是公网对讲智能记录的融合解决方案,飞利浦智慧融合一体机VTR8300守护安全,提高效率执法记录仪与公网对讲机在解决各类突发事件通讯指挥调度上起着无法替代的作用,通讯规模持续增长之时,公网对讲智能记录的融合解决方案孕育而生。VTR8300创新型高端专中超4消息!高拉特破门,大连新援赛季报销,张稀哲加冕射手王第1个消息来自于高拉特。巴西乙级联赛第31轮比赛,巴伊亚在主场22战平铁路工人。本场比赛第42分钟,前中超球星旮旯特接到队友头球摆渡,在门前完成头槌破门,帮助球队将比分扳平。本赛季2022乌鲁木齐静默的第42天,我们出发了文章来源牧云还乡191UP(微信公众号原创)乌鲁木齐静默日记,停了好一阵了。说实话,我好像还没有学会说话,好些个时候,只有沉默,久了,也就没有继续写下去的心情了。这一篇,应该感谢虫在巴马7天,生活开销不到百元!其实,巴马旅居没你想的那么贵巴马是一个神奇的地方,但是你不要神化它,这里不是治病的地方,身体有羌的人还是先把疾治好后再考虑去巴马康养!来巴马长寿乡的人群中无非就是3种人,一种是随旅行团好奇来的,一种是有闲来此又偏又远的闽菜小馆,凭什么让本地老饕一去再去?熟悉我们的朋友都知道,每到一个城市,编辑部的小伙伴都要找一家本地小馆子尝尝,好似这样才能真正从味觉间感受到城市底蕴。在嬉游旅行指南这个号呢,我们也在一直持续这一个专题,专门推荐全国期待,就在27日!下次出现要107年以后好看的皮囊千篇一律数不尽的美景奇趣万千各位街坊不得了!不得了!不得了了!就在3天后!!!堪称太阳系大个子的木星将在9月27日上演冲日表演届时木星将达到最亮如果天色晴朗大气洁净可到近罕见!可可西里有新发现地处青藏高原的可可西里是我国最大的无人区。正在进行的中国地质大学(武汉)长江源科考活动中,科考队在可可西里发现一处当地罕见的大规模红山脉。这一特殊地质现象的发现,将有助于加深对青藏小身材有大能量,iPhoneSE4曝光,果粉这次有福了众所周知,苹果手机是一个实力强大的手机厂商,苹果手机不仅有优异的工业设计和强大的硬件性能,同时出色的系统体验更是赢得了消费者的簇拥,再加上优秀的售后服务,因此苹果手机的影响力和号召中国AI市场对美国企业充满吸引力9月份,世界人工智能(AI)大会在上海开幕,美国高通等欧美大型高科技企业的经营高管参加了大会。虽然中美高科技对立越来越尖锐,但美国企业无法忽视中国市场的巨大商机。中国想要加速开发人你真的了解地球吗?生活了几十年你有没有想过脚下踩的土地内部,也许不是实心的而是空的。可能我们从小受到的教育就是地球由地核地幔地壳组成的实心球体,但其实这个也只是假想猜测,地底下真的有什么没有人见过。
愤怒!为何ampampquot拜鬼ampampquot事件层出不穷?网上日本战犯纪念堂又被曝光,夏日祭这个细节让人脊背发凉,真的是巧合吗?真是不查不知道,一查吓一跳啊!最近从大连日本文化风情街,到遍及20多个城市的夏日祭,再到近期的玄奘寺供奉日军战犯,精日文化一次次刺痛着我们的神经。而且精日分子们搞得动静是一次比一次日本强势推进修宪,为世界增添危险岸田文雄岸田文雄内阁上周五举行会议,批准了2022年版的日本防卫白皮书,俄罗斯发动的对乌克兰的战争所导致的军事威胁升级,成为驱动日本加大防务支出的直接重大因素新版防卫白皮书专辟俄罗日本网友为什么中国至今都无法与日本和解,俄罗斯网友你们不配现在已经是和平年代了,中国为什么一直都没办法与日本和解,这是一位日本网友,在外国最大的问答网站上提出的问题,他很疑惑,为什么中国人一直对日本抱有敌意,明明距离那段侵略时期已经过去了日本姑娘嫁中国劳工生下一子,50年后再见丈夫时,他已儿孙满堂这是一段真实的爱情故事,故事的主人公叫李秀绅,原名叫李铁锤,他是抗日英雄,对日本人恨之入骨。然而当被强行抓到日本之后,他却爱上了一个日本姑娘,并与之结婚生子,成了日本女婿。他们爱得日本投降后,留在中国的11万日本女人,后来都去哪里了?东北作为日本侵略中国的开端,当时有着大量日本士兵部署,更是有很多的日本女性被派往到东北地区作为后勤,日本以东北为主要根据地,不断地从东北开始对外发起侵略战争,而日本选择东北,主要是2012年,上海小伙火烧日本大使馆,被抓后坦言靖国神社是我烧的2012年初的一天,一名叫刘强的中国上海男子,在韩国首尔朝日本大使馆扔了几个燃烧瓶。刘强被韩国警方逮捕后,详细供述了他不久前,在日本火烧靖国神社的事情。消息传出,韩国民众称刘强为火问记者丨中国海拔最高县是怎么搬迁的?今年7月,中国海拔最高县实施第二批生态搬迁,近万名牧民陆续从平均海拔5000米以上的地方搬迁到海拔3600米的安置点。有网友通过新华社客户端问记者平台提问这个县为什么要搬迁?安置点俄乌一仗让美军司令发现,俄军无人机不足为惧,中国才是最强对手俄乌冲突的爆发,改变了无人机不适用于高强度现代战争的观点,许多欧美国家开始担心俄罗斯在这场冲突结束后是否会吸取实战经验,转而大力发展无人机,威胁到欧洲各国。美军高官表示,俄罗斯无人猴面包树寿命达5000年,能吃能喝又能住,为何引进中国却大变样?在小王子当中描写了一种邪恶的植物,叫做猴面包树,如果你任其生长,那么它就会吞噬整个星球,这里的猴面包树其实是一种隐喻。但大家不知道的是,其实在现实当中真的存在猴面包树。小王子当中的屈辱抗争逆转!三十年,中国该赢微软一次了既要看得见的手支持,也要看不见的手合力。文丨华商韬略耿康祁2001年12月28日,北京市政府干了一件大事在办公系统采购中,采购国产软件红旗Linux,把微软踢出局。2002年1月1问天实验舱发射成功,霸占外媒头条,美版知乎网友一边倒支持中国据央视新闻消息,北京时间7月27日11时31分,为中国空间站航天员送去生活物资的天舟三号货运飞船受控再入大气层任务已顺利完成。7月24日14时22分,搭载问天实验舱的长征五号B遥三