hackergame
hackergame copied to clipboard
可以改进给题目类别随机分配颜色的算法
目前hackergame系统给类别分配颜色的途径是 随机生成一个颜色。
这样会有两个问题,其一是相邻的类别有几率变成很相似的颜色,难以区分;其二是生成的颜色亮度不均匀,对易读性不友好。
https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ 提出可以在 HSV 色彩空间上生成颜色,固定明度和饱和度,每生成一种颜色把相位向前移动 $0.618 * 2\pi$。这样的话,每种颜色之间都有比较大的区分度,亮度也比较均匀。
CSS 没有原生支持 HSV,但我们可以用类似的 HSL。比如,如果令第 i 种类别的颜色是
`hsl(${360 * ((.2 + 0.618*i)%1)}deg, 50%, 35%)`
可以得到这样的效果:

这样不仅看起来比较好看,而且省去了现在的 color.js 和 seedrandom.js 两个依赖。
当前的生成算法是有意设计的,采用了 CIELUV 色彩空间。HSV 或 HSL 色彩空间没有考虑人眼对色彩的感知,固定 L 分量生成的颜色的亮度并不是相同的。color.js 依赖就是用于计算 CIELUV 色彩空间与 sRGB 色彩空间转换的。
之所以没采用把亮度固定为常数的做法,而是在 0% ~ 50% 随机生成,是因为同一个视觉感受亮度下存在的颜色实在太少了,无论如何生成都不会显得丰富。下图展示了将视觉感受亮度(L* 分量)固定在 35% 时,可以使用的所有颜色(横纵轴分别是 u* 和 v* 分量):
如果像示例中那样,不仅固定亮度,还固定饱和度,可用的颜色就更有限了(相当于图上一个以中心为圆心的圆)。
另外,红色盲用户感知不到 u* 分量,如果 L* 分量被固定,将只剩下 v* 这一个维度,颜色更单调。将 L* 分量也随机生成,可以让生成的颜色较为丰富。
用类别名称作为种子,用确定性伪随机数来生成颜色,是因为不然的话类别序号 i 没有好的定义。确定性生成算法有诸多好处,比如不同部署实例上相同名字的类型颜色相同,测试环境和生产环境效果相同,即使删除后重新添加了类别,或者进行了其他调整,也不会导致选手们发现“类别的颜色怎么突然变了”,也不会泄露后台信息(选手看到类别的颜色只有 i = 2 3 4 的,就可以猜测还有一个隐藏的类别 i=1,不知为何没有出现)。
HSV 或 HSL 色彩空间没有考虑人眼对色彩的感知
这个确实。我使用用 HSL 的原因是它观感可以接受(L 分量不在 50% 附近的时候感知到的亮度差一点问题不大,比如上图的例子,并不存在易读性问题),且编码实现容易(CSS 原生支持)。
同一个视觉感受亮度下存在的颜色实在太少了,无论如何生成都不会显得丰富
其实颜色还挺丰富的,例如网页上这个给出的例子(下图),至少生成十个左右都没什么问题,用来做题目类型绝对够了。而且每次前进 0.618 圈确保了相邻的几个颜色一定相差较大,相隔很远的颜色比较相近也不太容易发现。

另外在 0 到 50 之间随机亮度还可能导致一些对比度更高的类别比其他类别更 “显眼”。这在视觉设计上比较奇怪,相当于把语义相同的badge赋予了不同的优先级。
红色盲用户感知不到 u* 分量,如果 L* 分量被固定,将只剩下 v* 这一个维度,颜色更单调
拿红色盲滤镜套了一下,可以看到因为它每次移动了 0.618 圈,虽然一个分量被固定,这个算法在这种计算的情况下还是能做到相邻的颜色区别较大的(间隔2个颜色的Misc和Algorithm颜色相近了,比较遗憾,但原来的算法也会出现个别颜色相近的情况,这个无法完全避免)。

另外关于视觉障碍用户的使用体验问题:根据上图chrome那个色彩滤镜的效果,我没有感觉到体验有太大的问题(况且颜色显示并非平台核心功能)。不知道真实用户的感受如何。
确定性伪随机数来生成颜色,是因为不然的话类别序号 i 没有好的定义
目前我的做法是前端按在题目列表中的展示顺序排列,第一个出现的类别 i 为 0,以此类推,这样才能带来“相邻两个颜色一定差别较大”的效果。 确实,如果调整题目顺序,颜色就会变化。但考虑到目前hackergame并没有为类型的颜色赋予语义,而且调整题目顺序并不经常发生,我没有觉得这是一个很大的问题。
总之我提出的这个方法,主要是想解决两个比较影响体验的地方
- 原来badge的对比度忽低忽高,导致一些类别看起来比其他类别更显眼
- 相邻的颜色有时很像、难以区分
此方法确实没有 “同类别的颜色一定固定”、“对视觉障碍用户友好” 这些特性。个人感觉这些问题并不严重,不知道能不能再改进一下
(其实只要把 L* 的范围限制在一个比较小的区间内,然后在生成颜色时考虑一下相邻颜色不要太接近,也可以在不抛弃 CIELUV 的前提下解决上面两条)
另外在 0 到 50 之间随机亮度还可能导致一些对比度更高的类别比其他类别更 “显眼”。这在视觉设计上比较奇怪,相当于把语义相同的badge赋予了不同的优先级。
我倒是认为这种标签完全可以亮度和对比度不同。其实 HSL 生成的结果(原文的那个最终结果图)看起来亮度和对比度就是不同的,有一些标签明显比别的更“突出”,但五颜六色的感觉还不错啊。另一个参考:GitHub 上 issue labels 的颜色,默认用户头像的颜色,以及其他一些这样随机生成颜色的系统,其实也不固定亮度和对比度的。
而且调整题目顺序并不经常发生
啊这,科大的 hackergame 虽然尽了很大努力不在题目公开后调整顺序或增减题目,但这个项目希望尽量写得通用一些,不要假定这种情况不会发生。目前这个项目的一个 fork 就被用来提供一个长期存在的网站,会不断调整题目和分类的。
所以,如果类别名能被事先确定,保证不增减,并且可以枚举,整个问题都很好办。现在的难点主要还是来自于上游这个代码要写成通用的,应当可以妥善应对各种情况。在这个前提下我感觉选择很少。。。建议下游如果符合“类别名可以确定 + 可以枚举”这个使用场景的话,自行把 JS 中“类别名 -> 颜色”的算法改成一个 switch 语句(或者查表)即可😂
我觉得这个问题不需要大家讨论出一致的观点,题主可以提一个 PR,把色彩改成可以配置的,一个配置选项是使用哪种色彩空间,另一个配置选项是按顺序分配颜色还是按题目名称的哈希分配颜色。