Node.js下完成洛谷冬日绘版

扩散性百万甜面包

2018-06-21 16:55:49

Personal

[![F45cCD.md.jpg](https://s2.ax1x.com/2019/01/01/F45cCD.md.jpg)](https://imgchr.com/i/F45cCD) ## 前言 > `Node.js` 本身使用事件驱动、非阻塞和异步输入输出模型等技术,非常方便, 我们也可以将其利用成为冬日绘版中的佼佼者 本篇将简单介绍Node.js的使用,完成冬日绘版的自动提交 您可以需要了解: - Javascript - Chrome开发者工具(~~元素审查大师~~) ## 什么是`Node.js` 白话版: > Node.js 可以让JavaScript代码跑在服务器上 专业版: > Node.js 通过 `v8` (Chrome内核), 实现高效率的Web服务器, 最后实现出了单线程/单进程系统 ## 实现思路 利用 `Node.js` 事件驱动的优势, 根据每次事件间隔进行 `post` 主体代码如下: ```javascript const EventEmitter = require('events') const poster = new EventEmitter() poster.on('start', () => { post('luogu.org/', {}) }) setInterval(() => poster.emit('start'), 30 * 1000) ``` 我们设定30的间隔秒然后进行post 然后我们思考几个细节 1. 如何处理post失败情况 忽略即可, 失败的原因可能是你post太频繁了, 但是这并不影响冷却. 或者是因为洛谷更新导致需要refer字段等各种玄学问题 2. 如何把我们的图片转化成洛谷所提供的格式 我们先**预处理**出图片内容 3. 如何防止重复提交一个点多次 我们事件中加入一个**检查画板**的事件, 他定时帮助我们检查哪些是不需要的任务 4. 如何让我代码更具**扩展性**, 比如我立马需要添加其他小伙伴的Cookie/图片数据 我们使用模块化, 将多个部分拆分开来, 最后组成完整的 `Poster` ## 预处理 我们使用 `Python3`, 使用第三方库 `Pillow`, 读取图片每个像素的颜色, 和洛谷提供的数据进行一一比对, 然后选择最适合的值 伪代码如下: ```python def main(): img = ReadImg(imgPath) # 读取图片 height, width = img.size() data = [] for i in range(0, height): for j in range(0, width): color = getLuoguColor(img[i][j].color) data.append([i, j, color]) data.save() ``` 注意, 最小颜色差值也并不是我们直观上的欧几里得距离 有几篇文章阐述了原理, 有关详细原理超出编者的知识水平, 我这里不过多阐述, 供上链接 [COLOR SPACE CONVERSION](https://www.cambridgeincolour.com/tutorials/color-space-conversion.htm) [Colour metric](https://www.compuphase.com/cmetric.htm) 我们直接使用Python自带库 [`colorsys`](https://docs.python.org/3/library/colorsys.html) 部分代码如下, 具体可以到我的项目中查看 ```python map = {} # 这里是洛谷提供的颜色值 def get_color(pixel): return min_color_diff(pixel, colors)[1] def to_hsv(color): return rgb_to_hsv(*[x / 255.0 for x in color]) def color_dist(c1, c2): return sum((a - b) ** 2 for a, b in zip(to_hsv(c1), to_hsv(c2))) def min_color_diff(color_to_match, colors): return min( (color_dist(color_to_match, test), colors[test]) for test in colors) ``` ## 检查画板 洛谷给出了画板的链接 [board](https://www.luogu.org/paintBoard/board) 首先我们得知道的是哪个方向才是**X/Y轴** ~~多点几个点就能看出来了~~ 然后我们发现里面有 `a`, `b`, `c`... 这样的内容, 洛谷为了方便起见10以上的分别用abc表示 我们将返回的字符串序列进行简单处理 ```javascript function parseMap() { const _ = [] map.trim().split('\n').forEach((v, k) => { _[k] = new Proxy({ value: v }, { get (target, p) { // 这里使用一个代理, 你可以理解为重载运算符, 将 map[x][y] 的操作进行转换 // 避免 map[x][y] 返回字母, 而是要数字, 方便我们比对 const val = target.value[p] return parseInt(val, 36) // 36进制足够用了, 省去手写转换的麻烦 } }) }) return _ } ``` ## 扩展性 我们通过继承 `EventEmitter` 实现其他功能 伪代码如下: ```javascript const EventEmitter = require('events') class Poster extends EventEmitter { constructor (props) { super() this.tasks = loadTasks() this.users = loadUsers() this.map = getMap() this.registerEvent() } registerEvent () { this.on('checkMap', () => { this.tasks = reloadTask(this.tasks, this.map) }) this.on('start', () => { for (const k in this.users) { const user = this.users[k] const cookie = user.cookie const data = this.tasks[0] this.tasks.shift() // pop const [x, y, color] = data post('xxx', { x: x, y: y, color: color }, { cookie: cookie }).then(res => { if (res.data.status !== 200) { console.error('出错了!') this.tasks.push(data) // 失败时候重新加入数组 // other code } else { console.log('成功!') // other code } }) } }) } } const poster = new Poster() setInterval(() => poster.emit('start'), 30 * 1000) setInterval(() => poster.emit('checkMap'), 500 * 1000) // 检查地图无需时间间隔太短 ``` ## 其他部分 - 为了防止我们的服务器玄学崩溃, 我们需要再来一个进程来防止我们的进程崩溃 ~~为了防止那个进程也崩溃, 我们还需要一个进程来检测这个进程是否崩溃, 那么...~~ (并不 我们使用 `pm2` 进行检测 直接运行 > pm2 start index.js # 你的脚本 哪怕你的脚本一直 `return 0` 他也一直在重启 (逃 - 古人有云, 动态类型一时爽, 重构代码火葬场 我们使用 `typescript` 进行代码, typescript是一个编译时检查类型的语言, 然后转换成JavaScript代码 这里我就不过多阐述了 - 模块化可以继续扩展, 我们将一些重要的文件写进 `config.json` 中, 比如 `port`, `dataPath` 什么的 - 写好了一个脚本, 我们如何知道他跑的如何呢? 我们需要单元测试, 正所谓如果所有过程正确就可以证明结论一点正确 我们使用 `jest` 进行调试, 具体使用起来就是 ```javascript // getTask 是一个 将你的 tasks 和 map 进行比对, 然后返回需要post的的函数 it('should get task', function () { const res = getTask([ [0, 0, 1], [0, 1, 1], [0, 2, 1] ], [ '1122', '2222', '2222' ]) expect(res.length).toEqual(1) // 我断定他只剩下一个任务 }) ``` 然后我们跑一下测试 > npm run jest 运行正确 ## 结束 详细的代码在 Github [luogu-drawer](https://github.com/AimonaStudio/luogu-drawer) 中 其中 Poster 在 [poster.js](https://github.com/AimonaStudio/luogu-drawer/blob/master/src/poster/index.js) 内 ## 鸣谢 yyfcpp 给了我很多Cookie, 以便我不到半小时就画完了我的头像 abc1763613206 帮助我维护服务器上的 `luogu-drawer`