├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── jump.js ├── package-lock.json ├── package.json ├── public ├── images │ ├── demos │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── jump_screencap │ │ └── screencap.png ├── javascripts │ ├── demo.js │ ├── index.js │ ├── jump-test.js │ ├── jump.js │ ├── jump2.js │ ├── test.js │ └── zepto.min.js └── stylesheets │ └── style.css ├── routes ├── demo.js ├── index.js ├── jump.js └── test.js ├── screencapture.png └── views ├── demo.ejs ├── error.ejs ├── index.ejs └── test.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信小游戏跳一跳作弊器 2 | 3 | 最近这么火,没有必要不玩一下! 4 | 5 | ## 使用 6 | ```bash 7 | npm i 8 | npm start 9 | // open localhost:3000/demo 查看识别效果 10 | ``` 11 | 12 | 1. 安卓手机打开开发者选项usb调试,小米要允许模拟点击事件 13 | 2. 按照上面步骤启动程序 14 | 3. 数据线连上电脑,打开微信「跳一跳」点击开始 15 | 4. 打开 localhost:3000 自动跳转,打开devtools查看日志 16 | 17 | ## 进展 18 | 19 | 1. 目前完成图片canvas识别位置 20 | 21 | ![识别截图](screencapture.png) 22 | 23 | 2. 自动跳转完成 24 | 25 | ## 原理 26 | **只支持安卓** 27 | 28 | 1. 首先通过adb截图拉取到本地 29 | 2. 对本地图片通过Canvas获取当前和跳转位置 30 | 3. 通过计算两点距离,根据720等比例缩放,然后乘以系数2.04即为时间 31 | 4. 通过adb发送长按事件,事件为第三步计算的时间 -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var index = require('./routes/index'); 9 | var demo = require('./routes/demo'); 10 | var jump = require('./routes/jump'); 11 | var test = require('./routes/test'); 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({extended: false})); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | app.use(function(err, req, res, next) { 26 | res.json = function(data) { 27 | res.send(JSON.stringify(data)); 28 | }; 29 | next(); 30 | }); 31 | 32 | app.use('/', index); 33 | app.use('/demo', demo); 34 | app.use('/test', test); 35 | app.use('/jumponejump', jump); 36 | // catch 404 and forward to error handler 37 | app.use(function(req, res, next) { 38 | var err = new Error('Not Found'); 39 | err.status = 404; 40 | next(err); 41 | }); 42 | 43 | // error handler 44 | app.use(function(err, req, res, next) { 45 | // set locals, only providing error in development 46 | res.locals.message = err.message; 47 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 48 | 49 | // render the error page 50 | res.status(err.status || 500); 51 | res.render('error'); 52 | }); 53 | 54 | module.exports = app; 55 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('express:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /jump.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by beiwan on 2017/12/29. 3 | */ 4 | const util = require('util'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const exec = util.promisify(require('child_process').exec); 8 | 9 | const ADB_PATH = 'adb'; 10 | const SCREENCAP_REMOTE_PATH = '/sdcard/screencap.png'; 11 | const SCREENCAP_PATH = path.resolve('.', 'public/images/jump_screencap'); 12 | 13 | const BOOM = 2.04; 14 | 15 | jumpGo = async (timeout) => { 16 | let r = Math.random() * 20; 17 | if (timeout > 0 && !isNaN(timeout)) { 18 | const {stdout} = await exec(`${ADB_PATH} shell input swipe ${r + 10} ${r + 20} ${r - 10} ${r - 2} ${timeout}`); 19 | console.log(stdout, timeout); 20 | } 21 | }; 22 | 23 | fetchScreenCap = async () => { 24 | const {stdout, stderr} = await exec(`${ADB_PATH} shell screencap -p ${SCREENCAP_REMOTE_PATH}`); 25 | console.log('fetch...'); 26 | }; 27 | 28 | pullScreenCap = async () => { 29 | const {stdout, stderr} = await exec( 30 | `${ADB_PATH} pull ${SCREENCAP_REMOTE_PATH} ${SCREENCAP_PATH}/screencap.png`, 31 | [] 32 | ); 33 | console.log('pull...'); 34 | }; 35 | 36 | iJump = async (distance) => { 37 | await jumpGo(parseInt(distance * BOOM)); 38 | await setTimeout(async () => { 39 | await fetchScreenCap(); 40 | await pullScreenCap(); 41 | }, 2000); 42 | }; 43 | 44 | refreshScreencap = async () => { 45 | await fetchScreenCap(); 46 | await pullScreenCap(); 47 | }; 48 | 49 | module.exports = { 50 | iJump, 51 | refreshScreencap 52 | }; 53 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.4", 9 | "resolved": "http://pnpm.baidu.com/accepts/-/accepts-1.3.4.tgz", 10 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", 11 | "requires": { 12 | "mime-types": "2.1.17", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "http://pnpm.baidu.com/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "basic-auth": { 22 | "version": "2.0.0", 23 | "resolved": "http://pnpm.baidu.com/basic-auth/-/basic-auth-2.0.0.tgz", 24 | "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", 25 | "requires": { 26 | "safe-buffer": "5.1.1" 27 | } 28 | }, 29 | "body-parser": { 30 | "version": "1.18.2", 31 | "resolved": "http://pnpm.baidu.com/body-parser/-/body-parser-1.18.2.tgz", 32 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 33 | "requires": { 34 | "bytes": "3.0.0", 35 | "content-type": "1.0.4", 36 | "debug": "2.6.9", 37 | "depd": "1.1.1", 38 | "http-errors": "1.6.2", 39 | "iconv-lite": "0.4.19", 40 | "on-finished": "2.3.0", 41 | "qs": "6.5.1", 42 | "raw-body": "2.3.2", 43 | "type-is": "1.6.15" 44 | } 45 | }, 46 | "bytes": { 47 | "version": "3.0.0", 48 | "resolved": "http://pnpm.baidu.com/bytes/-/bytes-3.0.0.tgz", 49 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 50 | }, 51 | "content-disposition": { 52 | "version": "0.5.2", 53 | "resolved": "http://pnpm.baidu.com/content-disposition/-/content-disposition-0.5.2.tgz", 54 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 55 | }, 56 | "content-type": { 57 | "version": "1.0.4", 58 | "resolved": "http://pnpm.baidu.com/content-type/-/content-type-1.0.4.tgz", 59 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 60 | }, 61 | "cookie": { 62 | "version": "0.3.1", 63 | "resolved": "http://pnpm.baidu.com/cookie/-/cookie-0.3.1.tgz", 64 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 65 | }, 66 | "cookie-parser": { 67 | "version": "1.4.3", 68 | "resolved": "http://pnpm.baidu.com/cookie-parser/-/cookie-parser-1.4.3.tgz", 69 | "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", 70 | "requires": { 71 | "cookie": "0.3.1", 72 | "cookie-signature": "1.0.6" 73 | } 74 | }, 75 | "cookie-signature": { 76 | "version": "1.0.6", 77 | "resolved": "http://pnpm.baidu.com/cookie-signature/-/cookie-signature-1.0.6.tgz", 78 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 79 | }, 80 | "debug": { 81 | "version": "2.6.9", 82 | "resolved": "http://pnpm.baidu.com/debug/-/debug-2.6.9.tgz", 83 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 84 | "requires": { 85 | "ms": "2.0.0" 86 | } 87 | }, 88 | "depd": { 89 | "version": "1.1.1", 90 | "resolved": "http://pnpm.baidu.com/depd/-/depd-1.1.1.tgz", 91 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 92 | }, 93 | "destroy": { 94 | "version": "1.0.4", 95 | "resolved": "http://pnpm.baidu.com/destroy/-/destroy-1.0.4.tgz", 96 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 97 | }, 98 | "ee-first": { 99 | "version": "1.1.1", 100 | "resolved": "http://pnpm.baidu.com/ee-first/-/ee-first-1.1.1.tgz", 101 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 102 | }, 103 | "ejs": { 104 | "version": "2.5.7", 105 | "resolved": "http://pnpm.baidu.com/ejs/-/ejs-2.5.7.tgz", 106 | "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=" 107 | }, 108 | "encodeurl": { 109 | "version": "1.0.1", 110 | "resolved": "http://pnpm.baidu.com/encodeurl/-/encodeurl-1.0.1.tgz", 111 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 112 | }, 113 | "escape-html": { 114 | "version": "1.0.3", 115 | "resolved": "http://pnpm.baidu.com/escape-html/-/escape-html-1.0.3.tgz", 116 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 117 | }, 118 | "etag": { 119 | "version": "1.8.1", 120 | "resolved": "http://pnpm.baidu.com/etag/-/etag-1.8.1.tgz", 121 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 122 | }, 123 | "express": { 124 | "version": "4.15.5", 125 | "resolved": "http://pnpm.baidu.com/express/-/express-4.15.5.tgz", 126 | "integrity": "sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc=", 127 | "requires": { 128 | "accepts": "1.3.4", 129 | "array-flatten": "1.1.1", 130 | "content-disposition": "0.5.2", 131 | "content-type": "1.0.4", 132 | "cookie": "0.3.1", 133 | "cookie-signature": "1.0.6", 134 | "debug": "2.6.9", 135 | "depd": "1.1.1", 136 | "encodeurl": "1.0.1", 137 | "escape-html": "1.0.3", 138 | "etag": "1.8.1", 139 | "finalhandler": "1.0.6", 140 | "fresh": "0.5.2", 141 | "merge-descriptors": "1.0.1", 142 | "methods": "1.1.2", 143 | "on-finished": "2.3.0", 144 | "parseurl": "1.3.2", 145 | "path-to-regexp": "0.1.7", 146 | "proxy-addr": "1.1.5", 147 | "qs": "6.5.0", 148 | "range-parser": "1.2.0", 149 | "send": "0.15.6", 150 | "serve-static": "1.12.6", 151 | "setprototypeof": "1.0.3", 152 | "statuses": "1.3.1", 153 | "type-is": "1.6.15", 154 | "utils-merge": "1.0.0", 155 | "vary": "1.1.2" 156 | }, 157 | "dependencies": { 158 | "qs": { 159 | "version": "6.5.0", 160 | "resolved": "http://pnpm.baidu.com/qs/-/qs-6.5.0.tgz", 161 | "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" 162 | }, 163 | "statuses": { 164 | "version": "1.3.1", 165 | "resolved": "http://pnpm.baidu.com/statuses/-/statuses-1.3.1.tgz", 166 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 167 | } 168 | } 169 | }, 170 | "finalhandler": { 171 | "version": "1.0.6", 172 | "resolved": "http://pnpm.baidu.com/finalhandler/-/finalhandler-1.0.6.tgz", 173 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", 174 | "requires": { 175 | "debug": "2.6.9", 176 | "encodeurl": "1.0.1", 177 | "escape-html": "1.0.3", 178 | "on-finished": "2.3.0", 179 | "parseurl": "1.3.2", 180 | "statuses": "1.3.1", 181 | "unpipe": "1.0.0" 182 | }, 183 | "dependencies": { 184 | "statuses": { 185 | "version": "1.3.1", 186 | "resolved": "http://pnpm.baidu.com/statuses/-/statuses-1.3.1.tgz", 187 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 188 | } 189 | } 190 | }, 191 | "forwarded": { 192 | "version": "0.1.2", 193 | "resolved": "http://pnpm.baidu.com/forwarded/-/forwarded-0.1.2.tgz", 194 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 195 | }, 196 | "fresh": { 197 | "version": "0.5.2", 198 | "resolved": "http://pnpm.baidu.com/fresh/-/fresh-0.5.2.tgz", 199 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 200 | }, 201 | "http-errors": { 202 | "version": "1.6.2", 203 | "resolved": "http://pnpm.baidu.com/http-errors/-/http-errors-1.6.2.tgz", 204 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 205 | "requires": { 206 | "depd": "1.1.1", 207 | "inherits": "2.0.3", 208 | "setprototypeof": "1.0.3", 209 | "statuses": "1.4.0" 210 | } 211 | }, 212 | "iconv-lite": { 213 | "version": "0.4.19", 214 | "resolved": "http://pnpm.baidu.com/iconv-lite/-/iconv-lite-0.4.19.tgz", 215 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 216 | }, 217 | "inherits": { 218 | "version": "2.0.3", 219 | "resolved": "http://pnpm.baidu.com/inherits/-/inherits-2.0.3.tgz", 220 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 221 | }, 222 | "ipaddr.js": { 223 | "version": "1.4.0", 224 | "resolved": "http://pnpm.baidu.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz", 225 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 226 | }, 227 | "media-typer": { 228 | "version": "0.3.0", 229 | "resolved": "http://pnpm.baidu.com/media-typer/-/media-typer-0.3.0.tgz", 230 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 231 | }, 232 | "merge-descriptors": { 233 | "version": "1.0.1", 234 | "resolved": "http://pnpm.baidu.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 235 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 236 | }, 237 | "methods": { 238 | "version": "1.1.2", 239 | "resolved": "http://pnpm.baidu.com/methods/-/methods-1.1.2.tgz", 240 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 241 | }, 242 | "mime": { 243 | "version": "1.3.4", 244 | "resolved": "http://pnpm.baidu.com/mime/-/mime-1.3.4.tgz", 245 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 246 | }, 247 | "mime-db": { 248 | "version": "1.30.0", 249 | "resolved": "http://pnpm.baidu.com/mime-db/-/mime-db-1.30.0.tgz", 250 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 251 | }, 252 | "mime-types": { 253 | "version": "2.1.17", 254 | "resolved": "http://pnpm.baidu.com/mime-types/-/mime-types-2.1.17.tgz", 255 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 256 | "requires": { 257 | "mime-db": "1.30.0" 258 | } 259 | }, 260 | "morgan": { 261 | "version": "1.9.0", 262 | "resolved": "http://pnpm.baidu.com/morgan/-/morgan-1.9.0.tgz", 263 | "integrity": "sha1-0B+mxlhZt2/PMbPLU6OCGjEdgFE=", 264 | "requires": { 265 | "basic-auth": "2.0.0", 266 | "debug": "2.6.9", 267 | "depd": "1.1.1", 268 | "on-finished": "2.3.0", 269 | "on-headers": "1.0.1" 270 | } 271 | }, 272 | "ms": { 273 | "version": "2.0.0", 274 | "resolved": "http://pnpm.baidu.com/ms/-/ms-2.0.0.tgz", 275 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 276 | }, 277 | "negotiator": { 278 | "version": "0.6.1", 279 | "resolved": "http://pnpm.baidu.com/negotiator/-/negotiator-0.6.1.tgz", 280 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 281 | }, 282 | "on-finished": { 283 | "version": "2.3.0", 284 | "resolved": "http://pnpm.baidu.com/on-finished/-/on-finished-2.3.0.tgz", 285 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 286 | "requires": { 287 | "ee-first": "1.1.1" 288 | } 289 | }, 290 | "on-headers": { 291 | "version": "1.0.1", 292 | "resolved": "http://pnpm.baidu.com/on-headers/-/on-headers-1.0.1.tgz", 293 | "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" 294 | }, 295 | "parseurl": { 296 | "version": "1.3.2", 297 | "resolved": "http://pnpm.baidu.com/parseurl/-/parseurl-1.3.2.tgz", 298 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 299 | }, 300 | "path-to-regexp": { 301 | "version": "0.1.7", 302 | "resolved": "http://pnpm.baidu.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 303 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 304 | }, 305 | "proxy-addr": { 306 | "version": "1.1.5", 307 | "resolved": "http://pnpm.baidu.com/proxy-addr/-/proxy-addr-1.1.5.tgz", 308 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 309 | "requires": { 310 | "forwarded": "0.1.2", 311 | "ipaddr.js": "1.4.0" 312 | } 313 | }, 314 | "qs": { 315 | "version": "6.5.1", 316 | "resolved": "http://pnpm.baidu.com/qs/-/qs-6.5.1.tgz", 317 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 318 | }, 319 | "range-parser": { 320 | "version": "1.2.0", 321 | "resolved": "http://pnpm.baidu.com/range-parser/-/range-parser-1.2.0.tgz", 322 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 323 | }, 324 | "raw-body": { 325 | "version": "2.3.2", 326 | "resolved": "http://pnpm.baidu.com/raw-body/-/raw-body-2.3.2.tgz", 327 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 328 | "requires": { 329 | "bytes": "3.0.0", 330 | "http-errors": "1.6.2", 331 | "iconv-lite": "0.4.19", 332 | "unpipe": "1.0.0" 333 | } 334 | }, 335 | "safe-buffer": { 336 | "version": "5.1.1", 337 | "resolved": "http://pnpm.baidu.com/safe-buffer/-/safe-buffer-5.1.1.tgz", 338 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 339 | }, 340 | "send": { 341 | "version": "0.15.6", 342 | "resolved": "http://pnpm.baidu.com/send/-/send-0.15.6.tgz", 343 | "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=", 344 | "requires": { 345 | "debug": "2.6.9", 346 | "depd": "1.1.1", 347 | "destroy": "1.0.4", 348 | "encodeurl": "1.0.1", 349 | "escape-html": "1.0.3", 350 | "etag": "1.8.1", 351 | "fresh": "0.5.2", 352 | "http-errors": "1.6.2", 353 | "mime": "1.3.4", 354 | "ms": "2.0.0", 355 | "on-finished": "2.3.0", 356 | "range-parser": "1.2.0", 357 | "statuses": "1.3.1" 358 | }, 359 | "dependencies": { 360 | "statuses": { 361 | "version": "1.3.1", 362 | "resolved": "http://pnpm.baidu.com/statuses/-/statuses-1.3.1.tgz", 363 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 364 | } 365 | } 366 | }, 367 | "serve-favicon": { 368 | "version": "2.4.5", 369 | "resolved": "http://pnpm.baidu.com/serve-favicon/-/serve-favicon-2.4.5.tgz", 370 | "integrity": "sha512-s7F8h2NrslMkG50KxvlGdj+ApSwaLex0vexuJ9iFf3GLTIp1ph/l1qZvRe9T9TJEYZgmq72ZwJ2VYiAEtChknw==", 371 | "requires": { 372 | "etag": "1.8.1", 373 | "fresh": "0.5.2", 374 | "ms": "2.0.0", 375 | "parseurl": "1.3.2", 376 | "safe-buffer": "5.1.1" 377 | } 378 | }, 379 | "serve-static": { 380 | "version": "1.12.6", 381 | "resolved": "http://pnpm.baidu.com/serve-static/-/serve-static-1.12.6.tgz", 382 | "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=", 383 | "requires": { 384 | "encodeurl": "1.0.1", 385 | "escape-html": "1.0.3", 386 | "parseurl": "1.3.2", 387 | "send": "0.15.6" 388 | } 389 | }, 390 | "setprototypeof": { 391 | "version": "1.0.3", 392 | "resolved": "http://pnpm.baidu.com/setprototypeof/-/setprototypeof-1.0.3.tgz", 393 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 394 | }, 395 | "statuses": { 396 | "version": "1.4.0", 397 | "resolved": "http://pnpm.baidu.com/statuses/-/statuses-1.4.0.tgz", 398 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 399 | }, 400 | "type-is": { 401 | "version": "1.6.15", 402 | "resolved": "http://pnpm.baidu.com/type-is/-/type-is-1.6.15.tgz", 403 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", 404 | "requires": { 405 | "media-typer": "0.3.0", 406 | "mime-types": "2.1.17" 407 | } 408 | }, 409 | "unpipe": { 410 | "version": "1.0.0", 411 | "resolved": "http://pnpm.baidu.com/unpipe/-/unpipe-1.0.0.tgz", 412 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 413 | }, 414 | "utils-merge": { 415 | "version": "1.0.0", 416 | "resolved": "http://pnpm.baidu.com/utils-merge/-/utils-merge-1.0.0.tgz", 417 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 418 | }, 419 | "vary": { 420 | "version": "1.1.2", 421 | "resolved": "http://pnpm.baidu.com/vary/-/vary-1.1.2.tgz", 422 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 423 | } 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.18.2", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.9", 12 | "ejs": "~2.5.7", 13 | "express": "~4.15.5", 14 | "morgan": "~1.9.0", 15 | "serve-favicon": "~2.4.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/images/demos/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/1.png -------------------------------------------------------------------------------- /public/images/demos/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/10.png -------------------------------------------------------------------------------- /public/images/demos/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/11.png -------------------------------------------------------------------------------- /public/images/demos/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/12.png -------------------------------------------------------------------------------- /public/images/demos/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/13.png -------------------------------------------------------------------------------- /public/images/demos/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/2.png -------------------------------------------------------------------------------- /public/images/demos/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/3.png -------------------------------------------------------------------------------- /public/images/demos/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/4.png -------------------------------------------------------------------------------- /public/images/demos/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/5.png -------------------------------------------------------------------------------- /public/images/demos/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/6.png -------------------------------------------------------------------------------- /public/images/demos/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/7.png -------------------------------------------------------------------------------- /public/images/demos/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/8.png -------------------------------------------------------------------------------- /public/images/demos/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/demos/9.png -------------------------------------------------------------------------------- /public/images/jump_screencap/screencap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/public/images/jump_screencap/screencap.png -------------------------------------------------------------------------------- /public/javascripts/demo.js: -------------------------------------------------------------------------------- 1 | function getPointHtml(top, left) { 2 | return `
`; 3 | } 4 | let html = ''; 5 | for (let i = 1; i <= 13; i++) { 6 | let url = `/images/demos/${i}.png`; 7 | html += `
  • `; 8 | getPosition(url).then(({pos1, pos2, width, height}) => { 9 | let scale = 720 / width; 10 | pos1[0] = pos1[0] * scale; 11 | pos1[1] = pos1[1] * scale; 12 | pos2[0] = pos2[0] * scale; 13 | pos2[1] = pos2[1] * scale; 14 | let {top, left} = $(`#img${i}`).position(); 15 | let $curPoint = $(getPointHtml(top + pos1[1] - 5, left + pos1[0] - 5)); 16 | $curPoint.appendTo($('body')); 17 | 18 | let $nextPoint = $(getPointHtml(top + pos2[1] - 5, left + pos2[0] - 5)); 19 | $nextPoint.appendTo($('body')); 20 | }); 21 | } 22 | $('#list').html(html); 23 | -------------------------------------------------------------------------------- /public/javascripts/index.js: -------------------------------------------------------------------------------- 1 | function getScreenCap() { 2 | return new Promise((resolve, reject) => { 3 | $.ajax({ 4 | url: '/jumponejump/getscreencap', 5 | type: 'POST', 6 | dataType: 'json', 7 | data: {}, 8 | success: function(result) { 9 | if (result.error) { 10 | reject(result.error); 11 | } else { 12 | console.log('获取图片成功'); 13 | resolve(`/images/jump_screencap/screencap.png?_t=${Date.now()}`); 14 | } 15 | } 16 | }); 17 | }); 18 | } 19 | function jump({pos1, pos2, width}) { 20 | return new Promise((resolve, reject) => { 21 | if (pos1[0] > 10 && pos2[0] > 10 && pos1[1] > 10 && pos2[1] > 10 && pos1[0] < 3e4 && pos2[1] < 3e4) { 22 | } else { 23 | reject(pos1, pos2, width); 24 | } 25 | // 720修正 26 | let scale = 720 / width; 27 | pos1[0] = pos1[0] * scale; 28 | pos1[1] = pos1[1] * scale; 29 | pos2[0] = pos2[0] * scale; 30 | pos2[1] = pos2[1] * scale; 31 | 32 | const distance = Math.sqrt(Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2)); 33 | $.ajax({ 34 | url: '/jumponejump/', 35 | type: 'POST', 36 | dataType: 'json', 37 | data: { 38 | distance: distance 39 | }, 40 | success: function(result) { 41 | if (result.error) { 42 | reject(result.error); 43 | } else { 44 | console.log('跳转成功'); 45 | resolve(); 46 | } 47 | } 48 | }); 49 | }); 50 | } 51 | function start() { 52 | getScreenCap() 53 | .then(getPosition) 54 | .then(jump) 55 | .then(function() { 56 | setTimeout(start, 1e3); 57 | }) 58 | .catch((e) => { 59 | console.error(e); 60 | if (e.code === 1) { 61 | //发现存在图片获取失败的问题,增加reload页面 62 | location.reload(); 63 | } 64 | }); 65 | } 66 | 67 | start(); 68 | -------------------------------------------------------------------------------- /public/javascripts/jump-test.js: -------------------------------------------------------------------------------- 1 | function getImageData(src) { 2 | const img = new Image(); 3 | return new Promise((resolve, reject) => { 4 | img.onload = () => { 5 | img.onerror = img.onload = null; 6 | let canvas = document.createElement('canvas'); 7 | let width = (canvas.width = img.width); 8 | let height = (canvas.height = img.height); 9 | let ctx = canvas.getContext('2d'); 10 | ctx.drawImage(img, 0, 0); 11 | let imgData = ctx.getImageData(0, 0, width, height); 12 | resolve(imgData); 13 | }; 14 | img.onerror = (e) => { 15 | img.onerror = img.onload = null; 16 | e.message = '图片获取失败'; 17 | e.code = 1; 18 | reject(e); 19 | }; 20 | img.src = src; 21 | }); 22 | } 23 | function tolerenceHelper(r, g, b, rt, gt, bt, t) { 24 | return r > rt - t && r < rt + t && g > gt - t && g < gt + t && b > bt - t && b < bt + t; 25 | } 26 | 27 | function getCurCenter(data, width, height) { 28 | // 小人的颜色值 29 | const playerR = 40; 30 | const playerG = 43; 31 | const playerB = 86; 32 | 33 | let minX = Infinity; 34 | let minY = Infinity; 35 | let maxX = -1; 36 | let maxY = -1; 37 | // 找到小人当前的底部位置 38 | let pos = [0, 0]; 39 | 40 | let startY = Math.floor(height / 4); 41 | let endY = Math.floor(height * 3 / 4); 42 | for (let x = 0; x < width; x++) { 43 | for (let y = startY; y < endY; y++) { 44 | let i = y * (width * 4) + x * 4; 45 | let r = data[i]; 46 | let g = data[i + 1]; 47 | let b = data[i + 2]; 48 | if (y > pos[1] && tolerenceHelper(r, g, b, playerR, playerG, playerB, 16)) { 49 | // 将匹配到的像素点设置为红色 50 | data[i] = 255; 51 | data[i + 1] = 0; 52 | data[i + 2] = 0; 53 | minX = Math.min(minX, x); 54 | maxX = Math.max(maxX, x); 55 | maxY = Math.max(maxY, y); 56 | minY = Math.min(minY, y); 57 | } 58 | } 59 | } 60 | pos[0] = Math.floor((maxX + minX) / 2); 61 | pos[1] = maxY; 62 | pos[2] = [minX, maxX, minY, maxY]; 63 | // console.log(`player position (x, y)= (${pos[0]}, ${pos[1]})`); 64 | return pos; 65 | } 66 | function getNextCenter(data, width, height, curPos) { 67 | let startY = Math.floor(height / 4); 68 | let endY = Math.floor(height * 3 / 4); 69 | 70 | // 去除背景色 71 | let startX = startY * width * 4; 72 | let r = data[startX], 73 | g = data[startX + 1], 74 | b = data[startX + 2]; 75 | 76 | let maxY = -1; 77 | let apex = []; 78 | let pos = [0, 0]; 79 | // 保证从当前小人位置底部点往上 80 | endY = Math.min(endY, curPos[1]); 81 | let endX = width; 82 | let gapCount = 0; 83 | for (let y = startY; y < endY; y++) { 84 | let find = 0; 85 | for (let x = 1; x < endX; x++) { 86 | // 解决小人比下一个台阶高的情况,需要遇见在小人范围内的值就跳出 87 | if (curPos[3] && curPos[3].length) { 88 | let [x1, x2, y1, y2] = curPos[3]; 89 | if (x >= x1 && x <= x2 && y >= y1 && y <= y2) { 90 | continue; 91 | } 92 | } 93 | 94 | let i = y * (width * 4) + x * 4; 95 | let rt = data[i]; 96 | let gt = data[i + 1]; 97 | let bt = data[i + 2]; 98 | // 不是默认背景颜色 99 | if (!tolerenceHelper(rt, gt, bt, r, g, b, 30)) { 100 | if (apex.length === 0) { 101 | if (!tolerenceHelper(data[i + 4], data[i + 5], data[i + 6], r, g, b, 30)) { 102 | //椭圆形找中心,往后找30个像素点 103 | let len = 2; 104 | while (len++ !== 30) { 105 | i += len * 4; 106 | if (tolerenceHelper(data[i + 4], data[i + 5], data[i + 6], r, g, b, 30)) { 107 | break; 108 | } 109 | } 110 | x += len; 111 | } 112 | //找出顶点 113 | apex = [rt, gt, bt, x, y]; 114 | pos[0] = x; 115 | // 减少循环范围 116 | endX = x; 117 | break; 118 | } else if (tolerenceHelper(rt, gt, bt, apex[0], apex[1], apex[2], 5)) { 119 | // 物体顶面边涂红 120 | data[i] = 255; 121 | data[i + 1] = 0; 122 | data[i + 2] = 0; 123 | 124 | //存在顶点了,则根据颜色值开始匹配 125 | maxY = Math.max(maxY, y); 126 | find = x; 127 | break; 128 | } 129 | } else { 130 | // 背景色涂红 131 | // data[i] = 255; 132 | // data[i + 1] = 0; 133 | // data[i + 2] = 0; 134 | } 135 | } 136 | if (apex.length !== 0 && !find) { 137 | gapCount++; 138 | } 139 | if (gapCount === 3) { 140 | break; 141 | } 142 | } 143 | pos[1] = Math.floor((maxY + apex[4]) / 2); 144 | // console.log(points_top, points_left, points_right); 145 | // console.log(`next position center (x,y)=${pos[0]},${pos[1]}`); 146 | return pos; 147 | } 148 | async function getPosition(img) { 149 | let {data, width, height} = await getImageData(img); 150 | let pos1 = getCurCenter(data, width, height); 151 | let pos2 = getNextCenter(data, width, height, pos1); 152 | return {pos1, pos2, data, width, height}; 153 | } 154 | -------------------------------------------------------------------------------- /public/javascripts/jump.js: -------------------------------------------------------------------------------- 1 | function getImageData(src) { 2 | const img = new Image(); 3 | return new Promise((resolve, reject) => { 4 | img.onload = () => { 5 | img.onerror = img.onload = null; 6 | let canvas = document.createElement('canvas'); 7 | let width = (canvas.width = img.width); 8 | let height = (canvas.height = img.height); 9 | let ctx = canvas.getContext('2d'); 10 | ctx.drawImage(img, 0, 0); 11 | let imgData = ctx.getImageData(0, 0, width, height); 12 | resolve(imgData); 13 | }; 14 | img.onerror = (e) => { 15 | img.onerror = img.onload = null; 16 | e.message = '图片获取失败'; 17 | e.code = 1; 18 | reject(e); 19 | }; 20 | img.src = src; 21 | }); 22 | } 23 | function tolerenceHelper(r, g, b, rt, gt, bt, t) { 24 | return r > rt - t && r < rt + t && g > gt - t && g < gt + t && b > bt - t && b < bt + t; 25 | } 26 | 27 | function getCurCenter(data, width, height) { 28 | // 小人的颜色值 29 | const playerR = 40; 30 | const playerG = 43; 31 | const playerB = 86; 32 | 33 | let minX = Infinity; 34 | let minY = Infinity; 35 | let maxX = -1; 36 | let maxY = -1; 37 | // 找到小人当前的底部位置 38 | let pos = [0, 0]; 39 | 40 | let startY = Math.floor(height / 4); 41 | let endY = Math.floor(height * 3 / 4); 42 | for (let x = 0; x < width; x++) { 43 | for (let y = startY; y < endY; y++) { 44 | let i = y * (width * 4) + x * 4; 45 | let r = data[i]; 46 | let g = data[i + 1]; 47 | let b = data[i + 2]; 48 | if (y > pos[1] && tolerenceHelper(r, g, b, playerR, playerG, playerB, 16)) { 49 | minX = Math.min(minX, x); 50 | maxX = Math.max(maxX, x); 51 | maxY = Math.max(maxY, y); 52 | minY = Math.min(minY, y); 53 | } 54 | } 55 | } 56 | pos[0] = Math.floor((maxX + minX) / 2); 57 | pos[1] = maxY; 58 | pos[2] = [minX, maxX, minY, maxY]; 59 | 60 | console.log(`player position (x, y)= (${pos[0]}, ${pos[1]})`); 61 | return pos; 62 | } 63 | function getNextCenter(data, width, height, curPos) { 64 | let startY = Math.floor(height / 4); 65 | let endY = Math.floor(height * 3 / 4); 66 | 67 | // 去除背景色 68 | let startX = startY * width * 4; 69 | let r = data[startX], 70 | g = data[startX + 1], 71 | b = data[startX + 2]; 72 | let maxY = -1; 73 | let apex = []; 74 | let pos = [0, 0]; 75 | // 保证从当前小人位置底部点往上 76 | endY = Math.min(endY, curPos[1]); 77 | let endX = width; 78 | let gapCount = 0; 79 | for (let y = startY; y < endY; y++) { 80 | let find = 0; 81 | for (let x = 1; x < endX; x++) { 82 | // 解决小人比下一个台阶高的情况,需要遇见在小人范围内的值就跳出 83 | if (curPos[3] && curPos[3].length) { 84 | let [x1, x2, y1, y2] = curPos[3]; 85 | if (x >= x1 && x <= x2 && y >= y1 && y <= y2) { 86 | continue; 87 | } 88 | } 89 | let i = y * (width * 4) + x * 4; 90 | let rt = data[i]; 91 | let gt = data[i + 1]; 92 | let bt = data[i + 2]; 93 | // 不是默认背景颜色 94 | if (!tolerenceHelper(rt, gt, bt, r, g, b, 30)) { 95 | if (apex.length === 0) { 96 | if (!tolerenceHelper(data[i + 4], data[i + 5], data[i + 6], r, g, b, 30)) { 97 | //椭圆形找中心,往后找30个像素点 98 | let len = 2; 99 | while (len++ !== 30) { 100 | i += len * 4; 101 | if (tolerenceHelper(data[i + 4], data[i + 5], data[i + 6], r, g, b, 30)) { 102 | break; 103 | } 104 | } 105 | x += len; 106 | } 107 | //找出顶点 108 | apex = [rt, gt, bt, x, y]; 109 | pos[0] = x; 110 | // 减少循环范围 111 | endX = x; 112 | break; 113 | } else if (tolerenceHelper(rt, gt, bt, apex[0], apex[1], apex[2], 5)) { 114 | //存在顶点了,则根据颜色值开始匹配 115 | maxY = Math.max(maxY, y); 116 | find = x; 117 | break; 118 | } 119 | } 120 | } 121 | if (apex.length !== 0 && !find) { 122 | gapCount++; 123 | } 124 | if (gapCount === 3) { 125 | break; 126 | } 127 | } 128 | pos[1] = Math.floor((maxY + apex[4]) / 2); 129 | // console.log(points_top, points_left, points_right); 130 | console.log(`next position center (x,y)=${pos[0]},${pos[1]}`); 131 | return pos; 132 | } 133 | async function getPosition(img) { 134 | let {data, width, height} = await getImageData(img); 135 | let pos1 = getCurCenter(data, width, height); 136 | let pos2 = getNextCenter(data, width, height, pos1); 137 | return {pos1, pos2, data, width, height}; 138 | } 139 | -------------------------------------------------------------------------------- /public/javascripts/jump2.js: -------------------------------------------------------------------------------- 1 | function getImageData(src) { 2 | const img = new Image(); 3 | return new Promise((resolve, reject) => { 4 | img.onload = () => { 5 | img.onerror = img.onload = null; 6 | let canvas = document.createElement('canvas'); 7 | // 720 * 1280 8 | let width = img.width; 9 | let height = img.height; 10 | let scale = width / 720; 11 | height = Math.floor(height / scale); 12 | width = canvas.width = 720; 13 | canvas.height = height; 14 | let ctx = canvas.getContext('2d'); 15 | ctx.drawImage(img, 0, 0, width, height); 16 | let imgData = ctx.getImageData(0, 0, width, height); 17 | resolve(imgData); 18 | }; 19 | img.onerror = (e) => { 20 | img.onerror = img.onload = null; 21 | reject(e); 22 | }; 23 | img.src = src; 24 | }); 25 | } 26 | function tolerenceHelper(r, g, b, rt, gt, bt, t) { 27 | return r > rt - t && r < rt + t && g > gt - t && g < gt + t && b > bt - t && b < bt + t; 28 | } 29 | 30 | function getCurCenter(data, width, height) { 31 | // 小人的颜色值 32 | const playerR = 40; 33 | const playerG = 43; 34 | const playerB = 86; 35 | 36 | let minX = Infinity; 37 | let maxX = -1; 38 | let maxY = -1; 39 | // 找到小人当前的底部位置 40 | let pos = [0, 0]; 41 | 42 | let startY = Math.floor(height / 4); 43 | let endY = Math.floor(height * 3 / 4); 44 | for (let x = 0; x < width; x++) { 45 | for (let y = startY; y < endY; y++) { 46 | let i = y * (width * 4) + x * 4; 47 | let r = data[i]; 48 | let g = data[i + 1]; 49 | let b = data[i + 2]; 50 | if (y > pos[1] && tolerenceHelper(r, g, b, playerR, playerG, playerB, 16)) { 51 | minX = Math.min(minX, x); 52 | maxX = Math.max(maxX, x); 53 | maxY = Math.max(maxY, y); 54 | } 55 | } 56 | } 57 | pos[0] = Math.floor((maxX + minX) / 2); 58 | pos[1] = maxY; 59 | // console.log(`player position (x, y)= (${pos[0]}, ${pos[1]})`); 60 | return pos; 61 | } 62 | function getGray(r, g, b) { 63 | return Math.floor((r + g + b) / 3); 64 | } 65 | function grayHelper(a, b, t) { 66 | if (a > b - t && a < b + t) { 67 | return true; 68 | } 69 | return false; 70 | } 71 | function getNextCenter(data, width, height) { 72 | let startY = Math.floor(height / 4); 73 | let endY = Math.floor(height * 3 / 4); 74 | let startX = startY * width * 4; 75 | 76 | // 边缘检测 77 | for (let i = startX, len = endY * width * 4; i < len; i += 4) { 78 | let r = data[i]; 79 | let g = data[i + 1]; 80 | let b = data[i + 2]; 81 | 82 | if (!tolerenceHelper(r, g, b, data[i + 8], data[i + 9], data[i + 10], 5)) { 83 | data[i] = 255; 84 | data[i + 1] = 0; 85 | data[i + 2] = 0; 86 | } 87 | } 88 | 89 | // 去除背景色 90 | let r = data[startX], 91 | g = data[startX + 1], 92 | b = data[startX + 2]; 93 | 94 | let maxY = -1; 95 | let apex = []; 96 | let pos = [0, 0]; 97 | // 保证从当前小人位置底部点往上 98 | let endX = width; 99 | let gapCount = 0; 100 | 101 | for (let y = startY; y < endY; y++) { 102 | let find = 0; 103 | for (let x = 1; x < endX; x++) { 104 | let i = y * (width * 4) + x * 4; 105 | let rt = data[i]; 106 | let gt = data[i + 1]; 107 | let bt = data[i + 2]; 108 | // 不是默认背景颜色 109 | if (!tolerenceHelper(rt, gt, bt, r, g, b, 30) && !tolerenceHelper(rt, gt, bt, 255, 0, 0, 10)) { 110 | if (apex.length === 0) { 111 | //找出顶点 112 | apex = [rt, gt, bt, x, y]; 113 | pos[0] = x; 114 | // 减少循环范围 115 | endX = x; 116 | maxY = y + 60; 117 | // y+60行,加快循环,找不到中点也保证不会出去 118 | y += 60; 119 | break; 120 | } else if (tolerenceHelper(rt, gt, bt, apex[0], apex[1], apex[2], 10)) { 121 | //存在顶点了,则根据顶点颜色值开始匹配 122 | maxY = Math.max(maxY, y); 123 | find = x; 124 | break; 125 | // console.log(rt, gt, bt, apex.join(',')); 126 | } 127 | } 128 | } 129 | if (apex.length !== 0 && !find) { 130 | // console.log(find, maxY); 131 | gapCount++; 132 | } 133 | if (gapCount === 3) { 134 | //↑次找不到就跳出 135 | break; 136 | } 137 | } 138 | 139 | pos[1] = Math.floor((maxY + apex[4]) / 2); 140 | // console.log(`next position center (x,y)=${pos[0]},${pos[1]}, ${maxY},${apex[4]}`); 141 | return pos; 142 | } 143 | async function getPosition(img) { 144 | let {data, width, height} = await getImageData(img); 145 | let pos1 = getCurCenter(data, width, height); 146 | let pos2 = getNextCenter(data, width, height); 147 | return {pos1, pos2, data, width, height}; 148 | } 149 | -------------------------------------------------------------------------------- /public/javascripts/test.js: -------------------------------------------------------------------------------- 1 | const testUrl = '/images/demos/1.png'; 2 | const canvas = document.createElement('canvas'); 3 | 4 | getPosition(testUrl).then(({pos1, pos2, data, width, height}) => { 5 | canvas.width = width; 6 | canvas.height = height; 7 | const ctx = canvas.getContext('2d'); 8 | const imgData = ctx.createImageData(width, height); 9 | 10 | const start = pos1[1] * width * 4 + pos1[0] * 4; 11 | const end = pos2[1] * width * 4 + pos2[0] * 4; 12 | for (let i = 0; i < data.length; i += 4) { 13 | if (near(i, start, end, 16)) { 14 | imgData.data[i] = 255; 15 | imgData.data[i + 1] = 0; 16 | imgData.data[i + 2] = 0; 17 | } else { 18 | imgData.data[i] = data[i]; 19 | imgData.data[i + 1] = data[i + 1]; 20 | imgData.data[i + 2] = data[i + 2]; 21 | } 22 | imgData.data[i + 3] = 255; 23 | } 24 | 25 | ctx.putImageData(imgData, 0, 0); 26 | $('#canvas').append(canvas) 27 | }); 28 | 29 | function near(i, start, end, t) { 30 | if ((i > start - t && i < start + t) || (i > end - t && i < end + t)) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | -------------------------------------------------------------------------------- /public/javascripts/zepto.min.js: -------------------------------------------------------------------------------- 1 | /* Zepto v1.2.0 - zepto event ajax form ie - zeptojs.com/license */ 2 | !function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t)}):e(t)}(this,function(t){var e=function(){function $(t){return null==t?String(t):S[C.call(t)]||"object"}function F(t){return"function"==$(t)}function k(t){return null!=t&&t==t.window}function M(t){return null!=t&&t.nodeType==t.DOCUMENT_NODE}function R(t){return"object"==$(t)}function Z(t){return R(t)&&!k(t)&&Object.getPrototypeOf(t)==Object.prototype}function z(t){var e=!!t&&"length"in t&&t.length,n=r.type(t);return"function"!=n&&!k(t)&&("array"==n||0===e||"number"==typeof e&&e>0&&e-1 in t)}function q(t){return a.call(t,function(t){return null!=t})}function H(t){return t.length>0?r.fn.concat.apply([],t):t}function I(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function V(t){return t in l?l[t]:l[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function _(t,e){return"number"!=typeof e||h[I(t)]?e:e+"px"}function B(t){var e,n;return c[t]||(e=f.createElement(t),f.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),c[t]=n),c[t]}function U(t){return"children"in t?u.call(t.children):r.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function X(t,e){var n,r=t?t.length:0;for(n=0;r>n;n++)this[n]=t[n];this.length=r,this.selector=e||""}function J(t,r,i){for(n in r)i&&(Z(r[n])||L(r[n]))?(Z(r[n])&&!Z(t[n])&&(t[n]={}),L(r[n])&&!L(t[n])&&(t[n]=[]),J(t[n],r[n],i)):r[n]!==e&&(t[n]=r[n])}function W(t,e){return null==e?r(t):r(t).filter(e)}function Y(t,e,n,r){return F(e)?e.call(t,n,r):e}function G(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function K(t,n){var r=t.className||"",i=r&&r.baseVal!==e;return n===e?i?r.baseVal:r:void(i?r.baseVal=n:t.className=n)}function Q(t){try{return t?"true"==t||("false"==t?!1:"null"==t?null:+t+""==t?+t:/^[\[\{]/.test(t)?r.parseJSON(t):t):t}catch(e){return t}}function tt(t,e){e(t);for(var n=0,r=t.childNodes.length;r>n;n++)tt(t.childNodes[n],e)}var e,n,r,i,O,P,o=[],s=o.concat,a=o.filter,u=o.slice,f=t.document,c={},l={},h={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},p=/^\s*<(\w+|!)[^>]*>/,d=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,m=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,g=/^(?:body|html)$/i,v=/([A-Z])/g,y=["val","css","html","text","data","width","height","offset"],x=["after","prepend","before","append"],b=f.createElement("table"),E=f.createElement("tr"),j={tr:f.createElement("tbody"),tbody:b,thead:b,tfoot:b,td:E,th:E,"*":f.createElement("div")},w=/complete|loaded|interactive/,T=/^[\w-]*$/,S={},C=S.toString,N={},A=f.createElement("div"),D={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},L=Array.isArray||function(t){return t instanceof Array};return N.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=A).appendChild(t),r=~N.qsa(i,e).indexOf(t),o&&A.removeChild(t),r},O=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},P=function(t){return a.call(t,function(e,n){return t.indexOf(e)==n})},N.fragment=function(t,n,i){var o,s,a;return d.test(t)&&(o=r(f.createElement(RegExp.$1))),o||(t.replace&&(t=t.replace(m,"<$1>")),n===e&&(n=p.test(t)&&RegExp.$1),n in j||(n="*"),a=j[n],a.innerHTML=""+t,o=r.each(u.call(a.childNodes),function(){a.removeChild(this)})),Z(i)&&(s=r(o),r.each(i,function(t,e){y.indexOf(t)>-1?s[t](e):s.attr(t,e)})),o},N.Z=function(t,e){return new X(t,e)},N.isZ=function(t){return t instanceof N.Z},N.init=function(t,n){var i;if(!t)return N.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&p.test(t))i=N.fragment(t,RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}else{if(F(t))return r(f).ready(t);if(N.isZ(t))return t;if(L(t))i=q(t);else if(R(t))i=[t],t=null;else if(p.test(t))i=N.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==e)return r(n).find(t);i=N.qsa(f,t)}}return N.Z(i,t)},r=function(t,e){return N.init(t,e)},r.extend=function(t){var e,n=u.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){J(t,n,e)}),t},N.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,s=T.test(o);return t.getElementById&&s&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:u.call(s&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},r.contains=f.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},r.type=$,r.isFunction=F,r.isWindow=k,r.isArray=L,r.isPlainObject=Z,r.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},r.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},r.inArray=function(t,e,n){return o.indexOf.call(e,t,n)},r.camelCase=O,r.trim=function(t){return null==t?"":String.prototype.trim.call(t)},r.uuid=0,r.support={},r.expr={},r.noop=function(){},r.map=function(t,e){var n,i,o,r=[];if(z(t))for(i=0;i=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return o.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return F(t)?this.not(this.not(t)):r(a.call(this,function(e){return N.matches(e,t)}))},add:function(t,e){return r(P(this.concat(r(t,e))))},is:function(t){return this.length>0&&N.matches(this[0],t)},not:function(t){var n=[];if(F(t)&&t.call!==e)this.each(function(e){t.call(this,e)||n.push(this)});else{var i="string"==typeof t?this.filter(t):z(t)&&F(t.item)?u.call(t):r(t);this.forEach(function(t){i.indexOf(t)<0&&n.push(t)})}return r(n)},has:function(t){return this.filter(function(){return R(t)?r.contains(this,t):r(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!R(t)?t:r(t)},last:function(){var t=this[this.length-1];return t&&!R(t)?t:r(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?r(t).filter(function(){var t=this;return o.some.call(n,function(e){return r.contains(e,t)})}):1==this.length?r(N.qsa(this[0],t)):this.map(function(){return N.qsa(this,t)}):r()},closest:function(t,e){var n=[],i="object"==typeof t&&r(t);return this.each(function(r,o){for(;o&&!(i?i.indexOf(o)>=0:N.matches(o,t));)o=o!==e&&!M(o)&&o.parentNode;o&&n.indexOf(o)<0&&n.push(o)}),r(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=r.map(n,function(t){return(t=t.parentNode)&&!M(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return W(e,t)},parent:function(t){return W(P(this.pluck("parentNode")),t)},children:function(t){return W(this.map(function(){return U(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||u.call(this.childNodes)})},siblings:function(t){return W(this.map(function(t,e){return a.call(U(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return r.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=B(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=F(t);if(this[0]&&!e)var n=r(t).get(0),i=n.parentNode||this.length>1;return this.each(function(o){r(this).wrapAll(e?t.call(this,o):i?n.cloneNode(!0):n)})},wrapAll:function(t){if(this[0]){r(this[0]).before(t=r(t));for(var e;(e=t.children()).length;)t=e.first();r(t).append(this)}return this},wrapInner:function(t){var e=F(t);return this.each(function(n){var i=r(this),o=i.contents(),s=e?t.call(this,n):t;o.length?o.wrapAll(s):i.append(s)})},unwrap:function(){return this.parent().each(function(){r(this).replaceWith(r(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var n=r(this);(t===e?"none"==n.css("display"):t)?n.show():n.hide()})},prev:function(t){return r(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return r(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;r(this).empty().append(Y(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=Y(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,r){var i;return"string"!=typeof t||1 in arguments?this.each(function(e){if(1===this.nodeType)if(R(t))for(n in t)G(this,n,t[n]);else G(this,t,Y(this,r,e,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(i=this[0].getAttribute(t))?i:e},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){G(this,t)},this)})},prop:function(t,e){return t=D[t]||t,1 in arguments?this.each(function(n){this[t]=Y(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=D[t]||t,this.each(function(){delete this[t]})},data:function(t,n){var r="data-"+t.replace(v,"-$1").toLowerCase(),i=1 in arguments?this.attr(r,n):this.attr(r);return null!==i?Q(i):e},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=Y(this,t,e,this.value)})):this[0]&&(this[0].multiple?r(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(e){if(e)return this.each(function(t){var n=r(this),i=Y(this,e,t,n.offset()),o=n.offsetParent().offset(),s={top:i.top-o.top,left:i.left-o.left};"static"==n.css("position")&&(s.position="relative"),n.css(s)});if(!this.length)return null;if(f.documentElement!==this[0]&&!r.contains(f.documentElement,this[0]))return{top:0,left:0};var n=this[0].getBoundingClientRect();return{left:n.left+t.pageXOffset,top:n.top+t.pageYOffset,width:Math.round(n.width),height:Math.round(n.height)}},css:function(t,e){if(arguments.length<2){var i=this[0];if("string"==typeof t){if(!i)return;return i.style[O(t)]||getComputedStyle(i,"").getPropertyValue(t)}if(L(t)){if(!i)return;var o={},s=getComputedStyle(i,"");return r.each(t,function(t,e){o[e]=i.style[O(e)]||s.getPropertyValue(e)}),o}}var a="";if("string"==$(t))e||0===e?a=I(t)+":"+_(t,e):this.each(function(){this.style.removeProperty(I(t))});else for(n in t)t[n]||0===t[n]?a+=I(n)+":"+_(n,t[n])+";":this.each(function(){this.style.removeProperty(I(n))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(r(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?o.some.call(this,function(t){return this.test(K(t))},V(t)):!1},addClass:function(t){return t?this.each(function(e){if("className"in this){i=[];var n=K(this),o=Y(this,t,e,n);o.split(/\s+/g).forEach(function(t){r(this).hasClass(t)||i.push(t)},this),i.length&&K(this,n+(n?" ":"")+i.join(" "))}}):this},removeClass:function(t){return this.each(function(n){if("className"in this){if(t===e)return K(this,"");i=K(this),Y(this,t,n,i).split(/\s+/g).forEach(function(t){i=i.replace(V(t)," ")}),K(this,i.trim())}})},toggleClass:function(t,n){return t?this.each(function(i){var o=r(this),s=Y(this,t,i,K(this));s.split(/\s+/g).forEach(function(t){(n===e?!o.hasClass(t):n)?o.addClass(t):o.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var n="scrollTop"in this[0];return t===e?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var n="scrollLeft"in this[0];return t===e?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),i=g.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(r(t).css("margin-top"))||0,n.left-=parseFloat(r(t).css("margin-left"))||0,i.top+=parseFloat(r(e[0]).css("border-top-width"))||0,i.left+=parseFloat(r(e[0]).css("border-left-width"))||0,{top:n.top-i.top,left:n.left-i.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||f.body;t&&!g.test(t.nodeName)&&"static"==r(t).css("position");)t=t.offsetParent;return t})}},r.fn.detach=r.fn.remove,["width","height"].forEach(function(t){var n=t.replace(/./,function(t){return t[0].toUpperCase()});r.fn[t]=function(i){var o,s=this[0];return i===e?k(s)?s["inner"+n]:M(s)?s.documentElement["scroll"+n]:(o=this.offset())&&o[t]:this.each(function(e){s=r(this),s.css(t,Y(this,i,e,s[t]()))})}}),x.forEach(function(n,i){var o=i%2;r.fn[n]=function(){var n,a,s=r.map(arguments,function(t){var i=[];return n=$(t),"array"==n?(t.forEach(function(t){return t.nodeType!==e?i.push(t):r.zepto.isZ(t)?i=i.concat(t.get()):void(i=i.concat(N.fragment(t)))}),i):"object"==n||null==t?t:N.fragment(t)}),u=this.length>1;return s.length<1?this:this.each(function(e,n){a=o?n:n.parentNode,n=0==i?n.nextSibling:1==i?n.firstChild:2==i?n:null;var c=r.contains(f.documentElement,a);s.forEach(function(e){if(u)e=e.cloneNode(!0);else if(!a)return r(e).remove();a.insertBefore(e,n),c&&tt(e,function(e){if(!(null==e.nodeName||"SCRIPT"!==e.nodeName.toUpperCase()||e.type&&"text/javascript"!==e.type||e.src)){var n=e.ownerDocument?e.ownerDocument.defaultView:t;n.eval.call(n,e.innerHTML)}})})})},r.fn[o?n+"To":"insert"+(i?"Before":"After")]=function(t){return r(t)[n](this),this}}),N.Z.prototype=X.prototype=r.fn,N.uniq=P,N.deserializeValue=Q,r.zepto=N,r}();return t.Zepto=e,void 0===t.$&&(t.$=e),function(e){function h(t){return t._zid||(t._zid=n++)}function p(t,e,n,r){if(e=d(e),e.ns)var i=m(e.ns);return(a[h(t)]||[]).filter(function(t){return t&&(!e.e||t.e==e.e)&&(!e.ns||i.test(t.ns))&&(!n||h(t.fn)===h(n))&&(!r||t.sel==r)})}function d(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function m(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function g(t,e){return t.del&&!f&&t.e in c||!!e}function v(t){return l[t]||f&&c[t]||t}function y(t,n,i,o,s,u,f){var c=h(t),p=a[c]||(a[c]=[]);n.split(/\s/).forEach(function(n){if("ready"==n)return e(document).ready(i);var a=d(n);a.fn=i,a.sel=s,a.e in l&&(i=function(t){var n=t.relatedTarget;return!n||n!==this&&!e.contains(this,n)?a.fn.apply(this,arguments):void 0}),a.del=u;var c=u||i;a.proxy=function(e){if(e=T(e),!e.isImmediatePropagationStopped()){e.data=o;var n=c.apply(t,e._args==r?[e]:[e].concat(e._args));return n===!1&&(e.preventDefault(),e.stopPropagation()),n}},a.i=p.length,p.push(a),"addEventListener"in t&&t.addEventListener(v(a.e),a.proxy,g(a,f))})}function x(t,e,n,r,i){var o=h(t);(e||"").split(/\s/).forEach(function(e){p(t,e,n,r).forEach(function(e){delete a[o][e.i],"removeEventListener"in t&&t.removeEventListener(v(e.e),e.proxy,g(e,i))})})}function T(t,n){return(n||!t.isDefaultPrevented)&&(n||(n=t),e.each(w,function(e,r){var i=n[e];t[e]=function(){return this[r]=b,i&&i.apply(n,arguments)},t[r]=E}),t.timeStamp||(t.timeStamp=Date.now()),(n.defaultPrevented!==r?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(t.isDefaultPrevented=b)),t}function S(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===r||(n[e]=t[e]);return T(n,t)}var r,n=1,i=Array.prototype.slice,o=e.isFunction,s=function(t){return"string"==typeof t},a={},u={},f="onfocusin"in t,c={focus:"focusin",blur:"focusout"},l={mouseenter:"mouseover",mouseleave:"mouseout"};u.click=u.mousedown=u.mouseup=u.mousemove="MouseEvents",e.event={add:y,remove:x},e.proxy=function(t,n){var r=2 in arguments&&i.call(arguments,2);if(o(t)){var a=function(){return t.apply(n,r?r.concat(i.call(arguments)):arguments)};return a._zid=h(t),a}if(s(n))return r?(r.unshift(t[n],t),e.proxy.apply(null,r)):e.proxy(t[n],t);throw new TypeError("expected function")},e.fn.bind=function(t,e,n){return this.on(t,e,n)},e.fn.unbind=function(t,e){return this.off(t,e)},e.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var b=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,w={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};e.fn.delegate=function(t,e,n){return this.on(e,t,n)},e.fn.undelegate=function(t,e,n){return this.off(e,t,n)},e.fn.live=function(t,n){return e(document.body).delegate(this.selector,t,n),this},e.fn.die=function(t,n){return e(document.body).undelegate(this.selector,t,n),this},e.fn.on=function(t,n,a,u,f){var c,l,h=this;return t&&!s(t)?(e.each(t,function(t,e){h.on(t,n,a,e,f)}),h):(s(n)||o(u)||u===!1||(u=a,a=n,n=r),(u===r||a===!1)&&(u=a,a=r),u===!1&&(u=E),h.each(function(r,o){f&&(c=function(t){return x(o,t.type,u),u.apply(this,arguments)}),n&&(l=function(t){var r,s=e(t.target).closest(n,o).get(0);return s&&s!==o?(r=e.extend(S(t),{currentTarget:s,liveFired:o}),(c||u).apply(s,[r].concat(i.call(arguments,1)))):void 0}),y(o,t,u,a,n,l||c)}))},e.fn.off=function(t,n,i){var a=this;return t&&!s(t)?(e.each(t,function(t,e){a.off(t,n,e)}),a):(s(n)||o(i)||i===!1||(i=n,n=r),i===!1&&(i=E),a.each(function(){x(this,t,i,n)}))},e.fn.trigger=function(t,n){return t=s(t)||e.isPlainObject(t)?e.Event(t):T(t),t._args=n,this.each(function(){t.type in c&&"function"==typeof this[t.type]?this[t.type]():"dispatchEvent"in this?this.dispatchEvent(t):e(this).triggerHandler(t,n)})},e.fn.triggerHandler=function(t,n){var r,i;return this.each(function(o,a){r=S(s(t)?e.Event(t):t),r._args=n,r.target=a,e.each(p(a,t.type||t),function(t,e){return i=e.proxy(r),r.isImmediatePropagationStopped()?!1:void 0})}),i},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(t){e.fn[t]=function(e){return 0 in arguments?this.bind(t,e):this.trigger(t)}}),e.Event=function(t,e){s(t)||(e=t,t=e.type);var n=document.createEvent(u[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),T(n)}}(e),function(e){function p(t,n,r){var i=e.Event(n);return e(t).trigger(i,r),!i.isDefaultPrevented()}function d(t,e,n,i){return t.global?p(e||r,n,i):void 0}function m(t){t.global&&0===e.active++&&d(t,null,"ajaxStart")}function g(t){t.global&&!--e.active&&d(t,null,"ajaxStop")}function v(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||d(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void d(e,n,"ajaxSend",[t,e])}function y(t,e,n,r){var i=n.context,o="success";n.success.call(i,t,o,e),r&&r.resolveWith(i,[t,o,e]),d(n,i,"ajaxSuccess",[e,n,t]),b(o,e,n)}function x(t,e,n,r,i){var o=r.context;r.error.call(o,n,e,t),i&&i.rejectWith(o,[n,e,t]),d(r,o,"ajaxError",[n,r,t||e]),b(e,n,r)}function b(t,e,n){var r=n.context;n.complete.call(r,e,t),d(n,r,"ajaxComplete",[e,n]),g(n)}function E(t,e,n){if(n.dataFilter==j)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function j(){}function w(t){return t&&(t=t.split(";",2)[0]),t&&(t==c?"html":t==f?"json":a.test(t)?"script":u.test(t)&&"xml")||"text"}function T(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function S(t){t.processData&&t.data&&"string"!=e.type(t.data)&&(t.data=e.param(t.data,t.traditional)),!t.data||t.type&&"GET"!=t.type.toUpperCase()&&"jsonp"!=t.dataType||(t.url=T(t.url,t.data),t.data=void 0)}function C(t,n,r,i){return e.isFunction(n)&&(i=r,r=n,n=void 0),e.isFunction(r)||(i=r,r=void 0),{url:t,data:n,success:r,dataType:i}}function O(t,n,r,i){var o,s=e.isArray(n),a=e.isPlainObject(n);e.each(n,function(n,u){o=e.type(u),i&&(n=r?i:i+"["+(a||"object"==o||"array"==o?n:"")+"]"),!i&&s?t.add(u.name,u.value):"array"==o||!r&&"object"==o?O(t,u,r,n):t.add(n,u)})}var i,o,n=+new Date,r=t.document,s=/)<[^<]*)*<\/script>/gi,a=/^(?:text|application)\/javascript/i,u=/^(?:text|application)\/xml/i,f="application/json",c="text/html",l=/^\s*$/,h=r.createElement("a");h.href=t.location.href,e.active=0,e.ajaxJSONP=function(i,o){if(!("type"in i))return e.ajax(i);var c,p,s=i.jsonpCallback,a=(e.isFunction(s)?s():s)||"Zepto"+n++,u=r.createElement("script"),f=t[a],l=function(t){e(u).triggerHandler("error",t||"abort")},h={abort:l};return o&&o.promise(h),e(u).on("load error",function(n,r){clearTimeout(p),e(u).off().remove(),"error"!=n.type&&c?y(c[0],h,i,o):x(null,r||"error",h,i,o),t[a]=f,c&&e.isFunction(f)&&f(c[0]),f=c=void 0}),v(h,i)===!1?(l("abort"),h):(t[a]=function(){c=arguments},u.src=i.url.replace(/\?(.+)=\?/,"?$1="+a),r.head.appendChild(u),i.timeout>0&&(p=setTimeout(function(){l("timeout")},i.timeout)),h)},e.ajaxSettings={type:"GET",beforeSend:j,success:j,error:j,complete:j,context:null,global:!0,xhr:function(){return new t.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:f,xml:"application/xml, text/xml",html:c,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:j},e.ajax=function(n){var u,f,s=e.extend({},n||{}),a=e.Deferred&&e.Deferred();for(i in e.ajaxSettings)void 0===s[i]&&(s[i]=e.ajaxSettings[i]);m(s),s.crossDomain||(u=r.createElement("a"),u.href=s.url,u.href=u.href,s.crossDomain=h.protocol+"//"+h.host!=u.protocol+"//"+u.host),s.url||(s.url=t.location.toString()),(f=s.url.indexOf("#"))>-1&&(s.url=s.url.slice(0,f)),S(s);var c=s.dataType,p=/\?.+=\?/.test(s.url);if(p&&(c="jsonp"),s.cache!==!1&&(n&&n.cache===!0||"script"!=c&&"jsonp"!=c)||(s.url=T(s.url,"_="+Date.now())),"jsonp"==c)return p||(s.url=T(s.url,s.jsonp?s.jsonp+"=?":s.jsonp===!1?"":"callback=?")),e.ajaxJSONP(s,a);var P,d=s.accepts[c],g={},b=function(t,e){g[t.toLowerCase()]=[t,e]},C=/^([\w-]+:)\/\//.test(s.url)?RegExp.$1:t.location.protocol,N=s.xhr(),O=N.setRequestHeader;if(a&&a.promise(N),s.crossDomain||b("X-Requested-With","XMLHttpRequest"),b("Accept",d||"*/*"),(d=s.mimeType||d)&&(d.indexOf(",")>-1&&(d=d.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(d)),(s.contentType||s.contentType!==!1&&s.data&&"GET"!=s.type.toUpperCase())&&b("Content-Type",s.contentType||"application/x-www-form-urlencoded"),s.headers)for(o in s.headers)b(o,s.headers[o]);if(N.setRequestHeader=b,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=j,clearTimeout(P);var t,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==C){if(c=c||w(s.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)t=N.response;else{t=N.responseText;try{t=E(t,c,s),"script"==c?(1,eval)(t):"xml"==c?t=N.responseXML:"json"==c&&(t=l.test(t)?null:e.parseJSON(t))}catch(r){n=r}if(n)return x(n,"parsererror",N,s,a)}y(t,N,s,a)}else x(N.statusText||null,N.status?"error":"abort",N,s,a)}},v(N,s)===!1)return N.abort(),x(null,"abort",N,s,a),N;var A="async"in s?s.async:!0;if(N.open(s.type,s.url,A,s.username,s.password),s.xhrFields)for(o in s.xhrFields)N[o]=s.xhrFields[o];for(o in g)O.apply(N,g[o]);return s.timeout>0&&(P=setTimeout(function(){N.onreadystatechange=j,N.abort(),x(null,"timeout",N,s,a)},s.timeout)),N.send(s.data?s.data:null),N},e.get=function(){return e.ajax(C.apply(null,arguments))},e.post=function(){var t=C.apply(null,arguments);return t.type="POST",e.ajax(t)},e.getJSON=function(){var t=C.apply(null,arguments);return t.dataType="json",e.ajax(t)},e.fn.load=function(t,n,r){if(!this.length)return this;var a,i=this,o=t.split(/\s/),u=C(t,n,r),f=u.success;return o.length>1&&(u.url=o[0],a=o[1]),u.success=function(t){i.html(a?e("
    ").html(t.replace(s,"")).find(a):t),f&&f.apply(i,arguments)},e.ajax(u),this};var N=encodeURIComponent;e.param=function(t,n){var r=[];return r.add=function(t,n){e.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(t)+"="+N(n))},O(r,t,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;t.getComputedStyle=function(t,e){try{return n(t,e)}catch(r){return null}}}}(),e}); -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font: 14px 'Lucida Grande', Helvetica, Arial, sans-serif; 5 | } 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | a { 11 | color: #00b7ff; 12 | } 13 | #list li { 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | } 18 | li img{ 19 | width: 720px; 20 | } 21 | .point { 22 | background: red; 23 | width: 10px; 24 | height: 10px; 25 | position: absolute; 26 | z-index: 999; 27 | border-radius: 50%; 28 | } 29 | -------------------------------------------------------------------------------- /routes/demo.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('demo', { title: '「跳一跳」Canvas 点识别' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: '跳一跳' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/jump.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var jump = require('../jump'); 4 | 5 | router.post('/', async function(req, res, next) { 6 | try { 7 | await jump.iJump(req.body.distance); 8 | res.json({error: 0}); 9 | } catch (e) { 10 | res.json({error: 1}); 11 | } 12 | }); 13 | 14 | router.post('/getscreencap', async function(req, res, next) { 15 | try { 16 | await jump.refreshScreencap(); 17 | res.json({error: 0}); 18 | } catch (e) { 19 | res.json({error: 1}); 20 | } 21 | }); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /routes/test.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('test', { title: '「跳一跳」Canvas 点识别测试算法准确度' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /screencapture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ksky521/wechat-jump-game-hack/ffe340aa834e964f4556fd503529e94e836e295e/screencapture.png -------------------------------------------------------------------------------- /views/demo.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |
      10 |
    11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

    <%= message %>

    2 |

    <%= error.status %>

    3 |
    <%= error.stack %>
    4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /views/test.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 |
    10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------