整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

手把手教你JS实现魔方模拟器

手把手教你JS实现魔方模拟器



方是个结构简单而变化无穷的神奇玩具。那么如何在万能的浏览器里模拟出魔方的无尽变换,又如何将其还原呢?下面让我们一步步地来一探究竟吧。

魔方的抽象

拆解过魔方的同学可能知道,现实中魔方的内部结构包含了中轴、弹簧、螺丝等机械装置。但当我们只是想要「模拟」它的时候,我们只需抓住它最显著的性质即可——3x3x3 的一组立方体:



基本概念

上图演示了魔方最基本的思维模型。但光有这样的感性认识还不够:组成魔方的每个块并非随意安置,它们之间有着细微的区别:

  • 位于魔方各角的块称为角块,每个角块均具有 3 个颜色。一个立方体有 8 个角,故而一个魔方也具有 8 个角块。
  • 位于魔方各棱上的块称为棱块,每个棱块均具有 2 个颜色。一个立方体有 12 条棱,故而一个魔方也具有 12 个棱块。
  • 位于魔方各面中心的块称为中心块,每个中心块仅有 1 个颜色。一个立方体有 6 个面,故而一个魔方也具有 6 个中心块。
  • 位于整个魔方中心的块没有颜色,在渲染和还原的过程中也不起到什么实际的用处,我们可以忽略这个块。

将以上四种块的数量相加,正好是 3^3=27 块。对这些块,你所能使用的唯一操作(或者说变换)方式,就是在不同面上的旋转。那么,我们该如何标识出一次旋转操作呢?

设想你的手里「端正地」拿着一个魔方,我们将此时面对你的那一面定义为 Front,背对的一面定义为 Back。类似地,我们有了 Left / Right / Upper / Down 来标识其余各面。当你旋转某一面时,我们用这一面的简写(F / B / L / R / U / D)来标识在这一面上的一次顺时针 90 度旋转。对于一次逆时针的旋转,我们则用 F' / U' 这样带 ' 的记号来表达。如果你旋转了 180 度,那么可以用形如 R2 / U2 的方式表示。如下图的 5 次操作,如果我们约定蓝色一面为 Front,其旋转序列就是 F' R' L' B' F':



关于魔方的基础结构和变换方式,知道这些就足够了。下面我们需要考虑这个问题:如何设计一个数据结构来保存的魔方状态,并使用编程语言来实现某个旋转变换呢?

数据结构

喜欢基于「面向对象」抽象的同学可能很快就能想到,我们可以为每个块设计一个 Block 基类,然后用形如 CornerBlock 和 EdgeBlock 的类来抽象棱块和角块,在每个角块实例中还可以保存这个角块到它相邻三个棱块的引用……这样一个魔方的 Cube 对象只需持有对中心块的引用,就可以基于各块实例的邻接属性保存整个魔方了。

上面这种实现很类似于链表,它可以 O(1) 地实现「给定某个块,查找其邻接块」的操作,但不难发现,它需要 O(N) 的复杂度来实现形如「某个位置的块在哪里」这样的查找操作,基于它的旋转操作也并不十分符合直觉。相对地,另一种显得「过于暴力」的方式反而相当实用:直接开辟一个长度为 27 的数组,在其中存储每一块的颜色信息即可。

为什么可以这样呢?我们知道,数组在基于下标访问时,具有 O(1) 的时间复杂度。而如果我们在一个三维坐标系中定位魔方的每一个块,那么每个块的空间坐标都可以唯一地映射到数组的下标上。更进一步地,我们可以令 x, y, z 分别取 -1, 0, 1 这三个值来表达一个块在其方向上可能的位置,这时,例如前面所定义的一次 U 旋转,刚好就是对所有 y 轴坐标值为 1 的块的旋转。这个良好的性质很有利于实现对魔方的变换操作。

旋转变换

在约定好数据结构之后,我们如何实现对魔方的一次旋转变换呢?可能有些同学会直接将这个操作与三维空间中的四阶变换矩阵联系起来。但只要注意到一次旋转的角度都是 90 度的整数倍,我们可以利用数学性质极大地简化这一操作:

在旋转 90 度时,旋转面上每个角块都旋转到了该面上的「下一个」角块的位置上,棱块也是这样。故而,我们只需要循环交替地在每个块的「下一个」位置赋值,就能轻松地将块「移动」到其新位置上。但这还不够:每个新位置上的块,还需要对其自身六个面的颜色做一次「自旋」,才能将它的朝向指向正确的位置。这也是一次交替的赋值操作。从而,一次三维空间绕某个面中心的旋转操作,就被我们分解为了一次平移操作和一次绕各块中心的旋转操作。只需要 30 余行代码,我们就能实现这一魔方最核心的变换机制:

rotate (center, clockwise=true) {
  const axis=center.indexOf(1) + center.indexOf(-1) + 1
  // Fix y direction in right-handed coordinate system.
  clockwise=center[1] !==0 ? !clockwise : clockwise
  // Fix directions whose faces are opposite to axis.
  clockwise=center[axis]===1 ? clockwise : !clockwise

  let cs=[[1, 1], [1, -1], [-1, -1], [-1, 1]] // corner coords
  let es=[[0, 1], [1, 0], [0, -1], [-1, 0]] // edge coords
  const prepareCoord=coord=> coord.splice(axis, 0, center[axis])
  cs.forEach(prepareCoord); es.forEach(prepareCoord)
  if (!clockwise) { cs=cs.reverse(); es=es.reverse() }

  // 移动每个块到其新位置
  const rotateBlocks=([a, b, c, d])=> {
    const set=(a, b)=> { for (let i=0; i < 6; i++) a[i]=b[i] }
    const tmp=[]; set(tmp, a); set(a, d); set(d, c); set(c, b); set(b, tmp)
  }
  const colorsAt=coord=> this.getBlock(coord).colors
  rotateBlocks(cs.map(colorsAt)); rotateBlocks(es.map(colorsAt))

  // 调整每个块的自旋朝向
  const swap=[
    [[F, U, B, D], [L, F, R, B], [L, U, R, D]],
    [[F, D, B, U], [F, L, B, R], [D, R, U, L]]
  ][clockwise ? 0 : 1][axis]
  const rotateFaces=coord=> {
    const block=colorsAt(coord)
    ;[block[swap[1]], block[swap[2]], block[swap[3]], block[swap[0]]]=[block[swap[0]], block[swap[1]], block[swap[2]], block[swap[3]]]
  }
  cs.forEach(rotateFaces); es.forEach(rotateFaces)
  return this
}
复制代码

这个实现的效率应该不差:在笔者的浏览器里,上面的代码可以支持每秒 30 万次的旋转变换。为什么在这里我们需要在意性能呢?在魔方的场景下,有一个非常不同的地方,即状态的有效性与校验

熟悉魔方的同学应该知道,并不是随便给每块涂上不同颜色的魔方都是可以还原的。在普通的业务开发领域,数据的有效性和校验常常可以通过类型系统来保证。但对于一个打乱的魔方,保证它的可解性则是一个困难的数学问题。故而我们在保存魔方状态时,只有保存从六面同色的初始状态到当前状态下的所有变换步骤,才能保证这个状态一定是可解的。这样一来,反序列化一个魔方状态的开销就与操作步骤数量之间有了 O(N) 的关联。好在一个实际把玩中的魔方状态一般只会在 100 步之内,故而上面以牺牲时间复杂度换取数据有效性的代价应当是值得的。另外,这个方式可以非常简单地实现魔方任意状态之间的时间旅行:从初始状态走到任意一步的历史状态,都只需要叠加上它们之间一系列的旋转 diff 操作即可。这是一个很可靠的思维模型。

上面的实现中有一个特别之处:当坐标轴是 y 轴时,我们为旋转方向进行了一次取反操作。这初看起来并不符合直觉,但其背后却是坐标系定义的问题:如果你推导过每个块在顺时针变换时所处的下一个位置,那么在高中教科书和 WebGL 所用的右手坐标系中,绕 y 轴旋转时各个块的下一个位置,其交换顺序与 x 轴和 z 轴是相反的。反而在 DirectX 的左手坐标系中,旋转操作的正负能完全和坐标系的朝向一致。笔者作为区区码农,并不了解这背后的对称性是否蕴含了什么深刻的数学原理,希望数学大佬们解惑。

到此为止,我们已经基本完成了对魔方状态的抽象和变换算法的设计了。但相信很多同学可能更好奇的是这个问题:在浏览器环境下,我们该如何渲染出魔方呢?让我们来看看吧。

魔方的渲染

在浏览器这个以无数的二维矩形作为排版原语的世界里,要想渲染魔方这样的三维物体并不是件查个文档写几行胶水代码就可以搞定的事情。好在我们有 WebGL 这样的三维图形库可供差遣(当然了,相信熟悉样式的同学应该是可以使用 CSS 来渲染魔方的,可惜笔者的 CSS 水平很菜)。

WebGL 渲染基础

由于魔方思维模型的简单性,要渲染它并不需要使用图形学中纹理、光照和阴影等高级特性,只需要最基本的几何图形绘制特性就足够了。正因为如此,笔者在这里只使用了完全原生的 WebGL API 来绘制魔方。笼统地说,渲染魔方这样的一组立方体,所需要的步骤大致如下:

  1. 初始化着色器(编译供 GPU 执行的程序)
  2. 向缓冲区中传递顶点和颜色数据(操作显存)
  3. 设置用于观察的透视矩阵和模-视变换矩阵(传递变量给 GPU)
  4. 调用 drawElements 或 drawArray 渲染一帧

在前文中,我们设计的数据结构使用了长度为 27 的数组来存储 [-1, -1, -1] 到 [1, 1, 1] 的一系列块。在一个三重的 for 循环里,逐个将这些块绘制到屏幕上的逻辑大概就像前面看到的这张图:



需要注意的是,并不是越接近底层的代码就一定越快。例如在最早的实现中,笔者直接通过循环调用来自(或者说抄自)MDN 的 3D 立方体例程来完成 27 个小块的渲染。这时对于 27 个立方体区区不足千个顶点,60 帧绘制动画时的 CPU 占用率都可能跑满。经过定位,发现重复的 CPU 与 GPU 交互是一个大忌:从 CPU 向 GPU 传递数据,以及最终对 GPU 绘图 API 的调用,都具有较大的固定开销。一般我们需要将一帧中 Draw Call 的数量控制在 20 个以内,对于 27 个立方体就使用 27 次 Draw Call 的做法显然是个反模式。在将代码改造为一次批量传入全部顶点并调用一次 drawElements 后,即可实现流畅的 60 帧动画了 :)

旋转动画实现

在实现基本的渲染机制后,魔方整体的旋转效果可以通过对模-视矩阵做矩阵乘法来实现。模-视矩阵会在顶点着色器由 GPU 中对每一个顶点并行地计算,得到顶点变换后的 gl_Position 位置。但对于单个面的旋转,我们选择了先在 CPU 中计算好顶点位置,再将其传入顶点缓冲区。这和魔方旋转动画的实现原理直接相关:

  • 在一次某个面的旋转过程中,魔方的数据模型不发生改变,仅改变受影响的顶点所在位置。
  • 在旋转结束时,我们调用上文中实现的 rotate API 来「瞬间旋转好」魔方的数据模型,而后再多绘制一帧。

我们首先需要设计用于渲染一帧的 render API。考虑到魔方在绘制时可能存在对某个面一定程度的旋转,这个无状态的渲染 API 接口形如:

render (rX=0, rY=0, moveFace=null, moveAngle=0) {
  if (!this.gl) throw new Error('Missing WebGL context!')
  this.buffer=getBuffer(this.gl, this.blocks, moveFace, moveAngle)
  renderFrame(this.gl, this.programInfo, this.buffer, rX, rY)
}
复制代码

而对单个面的旋转过程中,我们可以使用浏览器的 requestAnimationFrame API 来实现基本的时序控制。一次调用 animate 的旋转返回一个在单次旋转结束时 resolve 的 Promise,其实现如下:

animate (move=null, duration=500) {
  if (move && move.length===0) return Promise.resolve()
  if (!move || this.__ANIMATING) throw new Error('Unable to animate!')

  this.__ANIMATING=true
  let k=move.includes("'") ? 1 : -1
  if (/B|D|L/.test(move)) k=k * -1
  const beginTime=+new Date()
  return new Promise((resolve, reject)=> {
    const tick=()=> {
      const diff=+new Date() - beginTime
      const percentage=diff / duration
      const face=move.replace("'", '')
      if (percentage < 1) {
        this.render(this.rX, this.rY, face, 90 * percentage * k)
        window.requestAnimationFrame(tick)
      } else {
        this.move(move)
        this.render(this.rX, this.rY, null, 0)
        this.__ANIMATING=false
        resolve()
      }
    }
    window.requestAnimationFrame(tick)
  })
}
复制代码

连续旋转实现

在实现了单次旋转后,如何支持连续的多次旋转呢?本着能偷懒就偷懒的想法,笔者对上面的函数进行了不改动已有逻辑的递归化改造,只需要在原函数入口处加入如下几行,就可以使支持传入数组为参数来递归调用自身,并在传入的连续动画数组长度为 1 时作为递归的出口,从而轻松地实现连续的动画效果:

if (Array.isArray(move) && move.length > 1) {
  const lastMove=move.pop()
  // 返回递归得到的 Promise
  return this.animate(move).then(()=> this.animate(lastMove))
} else if (move.length===1) move=move[0] // 继续已有逻辑
复制代码

到这里,一个可以供人体验的魔方基本就可以在浏览器里跑起来了。但这还不是我们最终的目标:我们该怎么自动还原一个魔方呢?

魔方的还原

魔方的还原算法在学术界已有很深入的研究,计算机在 20 步之内可以解出任意状态的魔方,也有成熟的轮子可以直接调用。但作为一个(高中时)曾经的魔方业余爱好者,笔者这里更关注的是「如何模拟出我自己还原魔方的操作」,故而在这里我们要介绍的是简单易懂的 CFOP 层先算法。

在开始前,有必要强调一个前文中一笔带过的概念:在旋转时,魔方中心块之间的相对位置始终不会发生变化。如下图:



因此,在魔方旋转时,我们只需关注角块和棱块是否归位即可。在 CFOP 层先法中,归位全部角块和棱块的步骤,被分为了逐次递进的四步:

  1. 还原底部四个棱块,构建出「十字」。
  2. 分组还原底层和第二层的所有角块和棱块。
  3. 调整顶层块朝向,保证顶面同色。
  4. 调整顶层块顺序,完成整个求解。

让我们依次来看看每一步都发生了什么吧。

底层十字

这一步可以说是最简单也最难的,在此我们的目标是还原四个底部棱块,像这样:



对一个完全打乱的魔方,每个目标棱块都可能以两种不同的朝向出现在任意一个棱块的位置上。为什么有两种朝向呢?请看下图:



这是最简单的一种情形,此时直接做一次 R2 旋转即可使红白棱块归位。但下面这种情况也是完全合法的:



这时由于棱块的朝向不同,所需的步骤就完全不同了。但总的来说,构成十字所需的棱块可能出现的位置总是有限的。拆解分类出所有可能的情形后,我们不难使用贪心策略来匹配:

  1. 每次找到一个构成十字所需的棱块,求出它到目标位置的一串移动步骤。
  2. 在不影响其他十字棱块的前提下将其归位,而后寻找下一个棱块。

这个最简单的策略很接近语法分析中向前看符号数量为 1 时的算法,不过这里不需要回溯。实现机制可以抽象如下:

solveCross () {
  const clonedCube=new Cube(null, this.cube.moves)
  const moveSteps=[]
  while (true) {
    const lostEdgeCoords=findCrossCoords(clonedCube)
    if (!lostEdgeCoords.length) break
    moveSteps.push(solveCrossEdge(clonedCube, lostEdgeCoords[0]))
  }
  return moveSteps
}
复制代码

这个实现原理并不复杂,其代价就是过小的局部最优造成了较多的冗余步骤。如果同时观察 2 个甚至更多的棱块状态并将其一并归位,其效率显然能得到提升(这时的实现难度也是水涨船高)。作为对比,一流的魔方玩家可以在 7 步内完成十字,但这个算法实现却需要 20 步左右——不过这里意思已经到了,各位看官就先不要太苛刻啦。

底部两层

这里的目标是在底部十字完成的基础上,完成底部两层所有块的归位。我们的目标是实现这样的状态:



这个步骤中,我们以 Slot 和 Pair 的概念作为还原的基本元素。相邻的十字之间所间隔的一个棱和一个角,构成了一个 Slot,而它们所对应的两个目标块则称为一个 Pair。故而这个步骤中,我们只需要重复四次将 Pair 放入 Slot 中的操作即可。一次最简单的操作大概是这样的:



上图将顶层的一对 Pair 放入了蓝红相间的 Slot 中。类似于之前解十字时的情形,这一步中的每个棱块和角块也有不同的位置和朝向。如果它们都在顶层,那么我们可以通过已有的匹配规则来实现匹配;如果它们在其它的 Slot 中,那么我们就递归地执行「将 Pair 从其它 Slot 中旋出」的算法,直到这组 Pair 都位于顶层为止。

这一步的还原算法与下面的步骤相当接近,稍后一并介绍。

顶层同色与顶层顺序

完成了前两层的还原后,我们最后所需要处理的就是顶层的 8 个棱块与角块了。首先是顶面同色的步骤,将各块调整到正确的朝向,实现顶面同色(一般采用白色作为底面,此时按照约定,黄色为顶面):



而后是顶层顺序的调整。这一步在不改变棱与角朝向的前提下,改变它们的排列顺序,最终完成整个魔方的还原:



从前两层的还原到顶层的还原步骤中,都有大量的魔方公式规则可供匹配使用。如何将这些现成的规则应用到还原算法中呢?我们可以使用规则驱动的方式来使用它们。

规则驱动设计

了解编译过程的同学应该知道,语法分析的过程可以通过编写一系列的语法规则来实现。而在魔方还原时,我们也有大量的规则可供使用。一条规则的匹配部分大概是这样的:



在顶面同色过程中,满足上述 "pattern" 的顶面,可以通过 U L U' R' U L' U' R 的步骤来还原。类似地,在还原顶层顺序时,规则的匹配方式形如这样:



满足这条规则的顶层状态可以通过该规则所定义的步骤求解:R2 U' R' U' R U R U R U' R。这样一来,只需要实现对规则的匹配和执行操作,规则的逻辑就可以完全与代码逻辑解耦,变为可配置的 JSON 格式数据。用于还原前两层的一条规则格式形如:

{
  match: { [E]: topEdge(COLOR_F, E), [SE]: SE_D_AS_F },
  moves: "U (R U' R')"
}
复制代码

顶层同色的规则格式形如:

{
  match: { [NW]: L, [NE]: R, [SE]: R, [SW]: L },
  moves: "R U R' U R U' R' U R U U R'"
}
复制代码

顶面顺序的规则格式形如:

{
  match: { [N]: W, [W]: [E], [E]: N },
  moves: "R R U' R' U' R U R U R U' R"
}
复制代码

这里的 NW / E / SE 是笔者的实现中基于九宫格东西南北方向定位的简写。在实现了对规则的自动匹配和应用之后,CFOP 中后面三步的实现方式可以说大同小异,主要的工作集中在一些与旋转相关的 mapping 处理。

规则的自测试

在整个还原过程中,一共有上百条规则需要匹配。对于这么多的规则,该如何保证它们的正确性呢?在 TDD 测试驱动开发的理念中,开发者需要通过编写各种繁冗的测试用例来实现对代码逻辑的覆盖。但在魔方领域,笔者发现了一种优雅得多的性质:任何一条规则本身,就是自己的测试用例!如这条规则:

{
  match: { [N]: W, [W]: [E], [E]: N },
  moves: "R R U' R' U' R U R U R U' R"
}
复制代码

我们只需要将 moves 中的每一步顺序颠倒地输入初始状态的魔方,就可以用这个状态来验证规则是否能够匹配,以及魔方是否能基于该规则还原了。这个性质使得我很容易地编写了下面这样简单的代码,自动验证每条输入规则的正确性:

const flip=moves=> moves.map(x=> x.length > 1 ? x[0] : x + "'").reverse()

OLL.forEach(rule=> {
  const rMoves=flip(rule.moves)
  const cube=new Cube(null, rMoves)
  if (
    matchOrientationRule(cube, rule) &&
    isOrientationSolved(cube.move(rule.moves))
  ) {
    console.log('OLL test pass', rule.id)
  } else console.error('Error OLL rule match', rule.id)
})
复制代码

在这个支持自测试的规则匹配算法基础上,求解魔方的全部步骤就这样计算出来了 :)

成果与后记

经过半个多月业余时间的折腾,笔者实现了一个非常小巧的魔方求解模拟器 Freecube。它支持三阶魔方状态的渲染和逐步求解,还提供了旋转与求解的 API 可供复用。由于它没有使用任何第三方依赖并使用了各种力求精简的「技巧」,因而它的体积被控制在了压缩后 10KB 内。欢迎移步 GitHub 观光 XD

Freecube 案例地址:https://ewind.us/h5/freecube/

Freecube 是笔者在很多地方忙里偷闲地实现的:咖啡厅、动车、公交车甚至饭桌上……即便写不了代码的场合,也可以拿平板写写画画来设计它。它的灵感来自于 @youngdro 神奇的吉他和弦算法博文,

https://juejin.im/post/5b2627d051882574ac7848a4

另外感谢大佬的指点和某人对 README 文档的审校 XD


Github地址:https://github.com/doodlewind/freecube

原地址:https://juejin.im/post/5b837c0b51882542d950efb4

久没参与魔方的发布工作了,今天提笔,有种莫名的生疏和惶恐,从下午四点半一直坐到现在,3个小时,删删写写。千言无语,千头万绪,千百般滋味涌上心头,真的很怀念那些年每个周五定期一更的日子,简单而快乐。

2006年12月Vista优化大师发布第一个测试版,再到2009年9月发布魔方0.1,时光荏苒,便过去了9年。很多当初最早的那批朋友,现在都成了老友,有时候一觉醒来,发现软媒的那个最老用户群里的消息闪烁,由衷的开心,没有什么比熟悉的感觉更有韵味。

产品部的魔方一哥过来催文了,尽管他说理解我的心情啊什么的,但是为了保持一贯的不加班作风,我得直入主题了——

魔方6.16正式版现在发布,“忍不住”还是让产品组加了新功能,本来说好的要克制加新功能的冲动,重点大幅改进清理等原有常用功能的。

这次忍不住要加的新功能,大家在标题里面已经看到了,就是一键提取微软官方的精美聚焦壁纸(美化大师顶部加入了“聚焦壁纸”)。熟悉微软的朋友都知道,Win10TH2开始系统增加了“Windows 聚焦”壁纸,大家在系统设置的“个性化”-“锁屏设置”里面可以设置锁屏的背景壁纸为“Windows 聚焦”,如下图所示:

这些微软官方提供的锁屏壁纸还是非常精美的,会自动的下载并切换,于是魔方便加入了提取功能,需要注意的是,这儿的提取,是提取的本机已经下载的,如果您之前没有开启过Windows聚焦功能,是抓不到的。这个抓取功能更方便的是让大家随时保存最新的。那过去的好看聚焦壁纸怎么办?别着急,我们在软件界面提供了所有Win10历史聚焦壁纸下载大全的链接,很贴心的说。

当然,这次魔方还有其他的有爱更新,例如清理大师的重复文件查找支持了批量选择和删除操作,例如设置大师中加入了让资源管理器关闭mkv文件的预览以防止卡顿,软媒雷达、软媒时间、WiFi助手、软媒压缩等都有界面和功能修复改进。

具体更新内容的细节,请看下面的更新历史吧!

PS:按照惯例,软媒魔方将在发布数十分钟后才放开自动升级。

一、软媒魔方更新历史

软媒魔方 6.1.6.0 正式版 - 2015年12月17日

魔方主程序 6.1.6.0:

新增:应用大全 - 增加旗鱼浏览器PC版入口

软媒美化大师 3.6.9.0:

新增:针对Win10 TH2增加Windows聚焦壁纸一键提取功能

软媒时间 3.1.3.0:

修正:界面 - 多云和阴天的小图标弄反的问题修正:界面 - 系统开启高DPI的时候,窗口太小的问题

软媒清理大师 3.7.3.0:

新增:隐私清理 - Office 2016打开历史记录清理

改进:重复文件查找 - 支持批量操作改进:注册表清理 - 屏蔽注册表清理功能

修正:隐私清理 - Office 2013打开历史记录清理不掉的问题

软媒设置大师 3.6.8.0:

新增:资源管理器 - 关闭MKV视频预览

软媒雷达 6.0.7.0:

修正:本机信息 - 读取IE的Flash Player版本号错误的问题修正:界面 - 系统开启高DPI的时候,窗口太小的问题

软媒IE管理大师 1.9.8.0:

修正:界面 - 系统开启高DPI的时候,窗口太小的问题

软媒WiFi助手 1.1.8.0:

修正:稳定性 - 启动WiFi助手时可能发生的崩溃问题

软媒压缩 1.1.5.0:

修正:界面 - 系统开启高DPI的时候,窗口太小的问题

二、为什么大家都在用魔方?

软媒魔方好不好?软媒魔方有什么用?为什么要用软媒魔方?

先列出一些基本组件功能:

1、清理大师:一键清理、深度清理、注册表清理、字体清理,还有隐私清理。。。难道你不需要?

2、美化大师:改系统字体、DIY win7开始按钮、设置开机动画、破解系统主题、修改系统声音。。。怎么个性怎么玩,美化大师全搞定!

3、优化大师:一键加速、添加、删除系统启动项,让你轻松掌控系统进程的开启!

4、软媒时间,软媒全球首创独创的创意,任务栏时间区加入农历、天气等显示,不占任何额外空间,超NB!

5、软媒桌面:哈,Windows系统里多了类似苹果Mac OS X的快捷栏,方便大发了。

6、软件管家:精选装机必备软件大全,下载杠杠的……

7、系统雷达:任务栏窗口+桌面悬浮窗自由随意选择,实时监控网络流量、CPU、内存占用、磁盘读写,简单、清晰、方便!

8、U盘启动:一键制作U盘系统安装盘,装机还是PE维护简直轻松到极点!

9、硬盘装机:仅需两步,轻松帮你重装系统,win7、win8、win10,想装什么装什么,你肯定需要!

10、WiFi共享助手:超级简单,打开软件,一键开启热点,立马摆脱手机流量不够用的困扰!!亲,你的流量还够用吗?

还有啥?上面列出的连一半功能都不到,还有网速测试、磁盘大师、文件校验、文件解锁、文件分割合并、文件粉碎……等等,还有好多功能,你能想到的基本都有!

软媒魔方现在已经有4000万用户下载使用,好用没得说!

三、软媒魔方软件截图

只需一键,智能模式让你可以放心的把软媒魔方介绍给你的老婆、小姨子和表妹……

右上角轻松切换到专业模式,熟悉中包含着科学改进后的经典布局

软媒时间,风云变幻-不占用任何额外空间,万年历天气闹钟记事本在任务栏时间区完美呈现;

软媒桌面,好玩好看-喜欢的鼠标拖进来,不爱的鼠标拖出去,桌面干净整洁灵动惊艳;

软媒雷达,实时侦探-把握您爱机的每一跳脉动,精确掌控系统软硬件的占用资源;

软件管家,纯净精干-编辑精选装机必备的500款最常用软件,一键安装绝无插件;

四、软媒魔方下载信息

初级用户解惑:安装包和绿色版有什么不同?安装版下载后直接双击运行即可进入向导安装,方便快捷。绿色版是ZIP压缩格式,直接解压到你指定的文件夹路径下。

软媒 - 存在,创造价值。

微信搜索“IT之家”关注抢6s大礼!下载IT之家客户端(戳这里)也可参与评论抽楼层大奖!

oogle 的第一个 Doodle 诞生于 1998 年 8 月 30 日。当时,公司的两位联合创始人去内华达参加“火人节”。他们在主页的 Google 后面放了一个燃火的小人,以表示自己外出旅行了。这个 Doodle 非常简单,但是,把公司 Logo 与重要纪念日结合的想法就此诞生。多年以来,Doodle 已经成为 Google 文化的一部分,传达着科技背后的人文化精神。

如今,Google 内部有专业的 Doodle 团队,拥有 10 名插画师,4 个全职开发者和 2 个项目经理。他们会收集创意,对其进行定期整理,并且制定 Doodle 呈现计划。Doodle 的形式也已经丰富多彩了。除了静态图片之外,它也可以是动态图片、动画短片,甚至是交互性的小游戏。2010 年的吃豆人就是让人印象深刻的一个互动 Doodle。今年的 5 月 19 日,为了纪念魔方发明 40 周年,Google 特意制作了一个交互性的小游戏:魔方 Doodle。

魔方 Doodle 看起来并不复杂,但是对于 Doodle 团队来说,它是极具挑战性的一次尝试。如果你玩过网上的魔方游戏,或许会注意到,它们基本都是使用了 Flash 技术,而且操作上很是糟糕。Google 的魔方 Doodle 使用的完全是网页技术。

“魔方 Doodle 是人们多次建议过的东西,但是我们一直觉得,制作它的网页技术还没有成熟,” Doodle 的团队主管 Ryan Gemick 对 Wired 网站说。 Google 创意实验室把它称作是,“我们最具技术野心的 Doodle 之一。”

魔方 Doodle 能够实现,是因为近来大多数浏览器都开始支持 CSS 3-D Transforms。“CSS 3-D Transforms 可以让我们把魔方展现在 3D 空间,而不是一种栅格化的 2D 体验,” 主工程师 Kristopher Hom 说,“这使它鲜活起来,当你移动鼠标的时候,你能看到魔方在 3D 空间里转动。”

Ryan Germick 说,他们面临的最大挑战是,如何让游戏体现出魔方设计上的简朴功能。“从设计角度来说,我们使它尽可能的简单、朴素,” Germick 解释说,“ ‘Google’ 的拼写是极简、抽象和几何形式的。界面极简化,只告诉你移动了多少步。”

对魔方Doodle 背后的技术感兴趣?Google 已经把源代码全部开源了。任何人都可以去改进它,去制作更好的魔方游戏。同时,Google 还与自由科学中心合作,在其举办的魔方 40 周年纪念展上放置了一个互动的魔方。(魔方诞生 40 周年之际,魔方创始人、69 岁的匈牙利发明家 Enno Rubik 与美国新泽西州的自由科学中心合作举办了魔方展。在展示的魔方中,有第一个原型,制造材料是木头和橡皮筋,还有世界上最贵的魔方,18 K 金打造,1360 块宝石,价值估计为 150 万英镑。)

在 Ryan Germick 看来,Google 制作 Doodle 的目的,是为了赞赏魔方中体现的创造力。“如果你小时候得到了一个魔方,你对它感兴趣的原因是因为你是一个问题解决者,或者你对那些促使你以后走向编程、工程和技术的东西感兴趣。” 他说,“我认为,我们对它感兴趣的原因是,它是一个符号,一个解决问题的符号,一个简单与复杂并存的符号。这是我们真正想要庆祝的东西。”

图片来自 florianmecl