扩散性百万甜面包
2018-06-21 16:55:49
Node.js
本身使用事件驱动、非阻塞和异步输入输出模型等技术,非常方便, 我们也可以将其利用成为冬日绘版中的佼佼者
本篇将简单介绍Node.js的使用,完成冬日绘版的自动提交
您可以需要了解:
Javascript
Chrome开发者工具(元素审查大师)
Node.js
白话版:
Node.js 可以让JavaScript代码跑在服务器上
专业版:
Node.js 通过
v8
(Chrome内核), 实现高效率的Web服务器, 最后实现出了单线程/单进程系统
利用 Node.js
事件驱动的优势, 根据每次事件间隔进行 post
主体代码如下:
const EventEmitter = require('events')
const poster = new EventEmitter()
poster.on('start', () => {
post('luogu.org/', {})
})
setInterval(() => poster.emit('start'), 30 * 1000)
我们设定30的间隔秒然后进行post
然后我们思考几个细节
忽略即可, 失败的原因可能是你post太频繁了, 但是这并不影响冷却. 或者是因为洛谷更新导致需要refer字段等各种玄学问题
我们先预处理出图片内容
我们事件中加入一个检查画板的事件, 他定时帮助我们检查哪些是不需要的任务
我们使用模块化, 将多个部分拆分开来, 最后组成完整的 Poster
我们使用 Python3
, 使用第三方库 Pillow
, 读取图片每个像素的颜色, 和洛谷提供的数据进行一一比对, 然后选择最适合的值
伪代码如下:
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
Colour metric
我们直接使用Python自带库 colorsys
部分代码如下, 具体可以到我的项目中查看
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
首先我们得知道的是哪个方向才是X/Y轴
多点几个点就能看出来了
然后我们发现里面有 a
, b
, c
... 这样的内容, 洛谷为了方便起见10以上的分别用abc表示
我们将返回的字符串序列进行简单处理
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
实现其他功能
伪代码如下:
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
进行调试, 具体使用起来就是
// 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 中
其中 Poster 在 poster.js 内
yyfcpp 给了我很多Cookie, 以便我不到半小时就画完了我的头像
abc1763613206 帮助我维护服务器上的 luogu-drawer