// ==UserScript==
// @name 炸飞机Hud
// @namespace http://tampermonkey.net/
// @version 2025-04-19
// @description 计算炸飞机的机头概率
// @author You
// @match https://game.hullqin.cn/zfj/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=hullqin.cn
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let found = 0
let board = Array.from(Array(10), () => new Array(10).fill(0))
let pred = Array.from(Array(10), () => new Array(10).fill(0))
let pred_head = Array.from(Array(10), () => new Array(10).fill(0))
let pred_body = Array.from(Array(10), () => new Array(10).fill(0))
let pred_null = Array.from(Array(10), () => new Array(10).fill(0))
let square = Array.from(Array(10), () => new Array(10).fill(0))
let table = document.createElement("table")
table.style = "position: fixed; left: 5%; top: 10%; background: #ffffffaa; border-radius: 0.5em; padding: 0.5em; box-shadow: gray 4px 4px 10px"
const gameStart = () => {
return new Promise((resolve) => {
setInterval(() => {
if (document.getElementsByClassName("max-w-md").length === 2) resolve()
}, 200)
})
}
const getColor = (pred) => {
let r = 195 - pred * 180
let g = 255 - pred * pred * 180
let b = 180 + pred * pred * 175
return "rgb("+Math.floor(r)+","+Math.floor(g)+","+Math.floor(b)+")"
}
const update = (event) => {
setTimeout((event) => {
let x = (parseInt(event.srcElement.getAttribute("x")) + 50)/10
let y = (parseInt(event.srcElement.getAttribute("y")) + 50)/10
let fill = event.srcElement.getAttribute("fill")
let type, type_chn
switch (fill) {
case "#23c343cc":
type = 2
type_chn = "机身"
break;
case "#f53f3fdd":
type = 1
type_chn = "机头"
break;
default:
type = 3
type_chn = "(未选中)"
}
console.log("[ZFJHud] 已点击位于("+x+","+y+")的方块,类型为:"+type_chn)
readBoard()
predict()
writeBoard()
if(type === 1) {
console.log("[ZFJHud] 共计已找到%c"+found+"个机头", 'color:#f53f3f')
}
}, 20, event)
}
const readBoard = () => {
let widget = document.getElementsByClassName("max-w-md")[1]
found = 0
Array.from(widget.children[0].children).forEach( (elem) => {
if (elem.tagName === "rect") {
if (elem.className === "prediction") {
elem.remove()
} else {
let x = (parseInt(elem.getAttribute("x")) + 50) / 10
let y = (parseInt(elem.getAttribute("y")) + 50) / 10
let fill = elem.getAttribute("fill")
let type
switch (fill) {
case "#23c343cc":
type = 2
break;
case "#f53f3fdd":
type = 1
found++
break;
case "#ccc":
type = 3
break;
default:
type = 0
}
if (x % 1 === 0 && y % 1 === 0) board[x][y] = type;
}
}
})
}
const writeBoard = () => {
let html = ""
let high = 0
let high_square = 2147483648000
let sq_x = 0
let sq_y = 0
for (let x = 0; x < 10; x++) {
for (let y = 0; y < 10; y++) {
let square_l = pred_head[x][y] * pred_head[x][y] * 0.75 + pred_body[x][y] * pred_body[x][y] +
pred_null[x][y] * pred_null[x][y]
square[x][y] = square_l
if(pred[x][y] > high && board[x][y] !== 1) {
high = pred[x][y]
}
if(square_l < high_square) {
high_square = square_l
sq_x = x;
sq_y = y;
}
}
}
for (let x = 0; x < 10; x++) {
html += "<tr><td style='text-align: center; font-size: smaller'>"+(10-x)+"</td>"
for (let y = 0; y < 10; y++) {
if (high === pred[y][x] && high > 0.05 && board[y][x] !== 1) html += "<td style='width: 1.4em; height: 1.4em; background: "+getColor(pred[y][x])+"; border: solid 2px red; opacity: 1'></td>"
else if (high_square === square[y][x] && board[y][x] !== 1) html += "<td style='width: 1.4em; height: 1.4em; background: "+getColor(pred[y][x])+"; border: solid 2px blue; opacity: 0.8'></td>"
else html += "<td style='width: 1.4em; height: 1.4em; background: "+getColor(pred[y][x])+"; opacity: 0.8;'></td>"
}
html += "</tr>"
}
html += "<tr><td></td>"
const alphabet = ['A','B','C','D','E','F','G','H','I','J']
for (let sq = 0; sq < 10; sq ++) {
html += "<td style='text-align: center; font-size: smaller;'>"+alphabet[sq]+"</td>"
}
html += "</tr><tr><td colspan=\"11\"><b>总可能性:</b>"+allScenarios.length+"</td></tr>"
html += "</tr><tr><td colspan=\"11\"><b>最高概率(红色):</b>"+Math.round(high*1000)/10+"%</td></tr>"
html += "</tr><tr><td colspan=\"11\"><b>最快排除(蓝色):</b><br/>头"+
Math.round(pred_head[sq_x][sq_y]/allScenarios.length*1000)/10+ "% 身"+
Math.round(pred_body[sq_x][sq_y]/allScenarios.length*1000)/10+ "% 无"+
Math.round(pred_null[sq_x][sq_y]/allScenarios.length*1000)/10+"%</td></tr>"
table.innerHTML = html;
}
const GRID_SIZE = 10; // 棋盘尺寸
const NUM_PLANES = 3; // 飞机数量
const PLANE_PARTS = 10; // 每个飞机的部件数(1头+9机身)
const DIRECTIONS = {
RIGHT: 0, UP: 1, LEFT: 2, DOWN: 3
};
const PLANE_OFFSETS = [
// 向右方向的部件偏移
[[0,0], [1,-2], [1,-1], [1,0], [1,1], [1,2], [2,0], [3,-1], [3,0], [3,1]],
// 向上方向
[[0,0], [-2,1], [-1,1], [0,1], [1,1], [2,1], [0,2], [-1,3], [0,3], [1,3]],
// 向左方向
[[0,0], [-1,-2], [-1,-1], [-1,0], [-1,1], [-1,2], [-2,0], [-3,-1], [-3,0], [-3,1]],
// 向下方向
[[0,0], [-2,-1], [-1,-1], [0,-1], [1,-1], [2,-1], [0,-2], [-1,-3], [0,-3], [1,-3]]
];
/**
* 检查单个飞机的有效性(是否完全在棋盘内)
* @param {number} x - 机头X坐标 (0-9)
* @param {number} y - 机头Y坐标 (0-9)
* @param {number} dir - 飞机方向
*/
const isValidPlane = (x, y, dir) => {
for (let i = 0; i < PLANE_PARTS; i++) {
const dx = PLANE_OFFSETS[dir][i][0];
const dy = PLANE_OFFSETS[dir][i][1];
const nx = x + dx;
const ny = y + dy;
if (nx < 0 || nx >= GRID_SIZE || ny < 0 || ny >= GRID_SIZE) return false;
}
return true;
};
/**
* 检查场景有效性(所有飞机不重叠)
* @param {Array} planes - 飞机配置数组
*/
const isValidScenario = (planes) => {
const grid = Array.from({length: GRID_SIZE}, () =>
Array(GRID_SIZE).fill(false));
for (const plane of planes) {
for (let i = 0; i < PLANE_PARTS; i++) {
const dx = PLANE_OFFSETS[plane.dir][i][0];
const dy = PLANE_OFFSETS[plane.dir][i][1];
const x = plane.x + dx;
const y = plane.y + dy;
if (grid[x][y]) return false;
grid[x][y] = true;
}
}
return true;
};
/**
* 生成所有可能的有效场景(性能关键函数)
*/
const generateAllScenarios = () => {
const scenarios = [];
// 生成所有可能的飞机配置
const generate = (planes, count, xinit, yinit) => {
if (count === NUM_PLANES) {
scenarios.push([...planes]);
return;
}
// 遍历所有可能的位置和方向
for (let x = xinit; x < GRID_SIZE; x++) {
for (let y = 0; y < GRID_SIZE; y++) {
if(x !== xinit || y > yinit) for (let dir = 0; dir < 4; dir++) {
if (!isValidPlane(x, y, dir)) continue;
// 创建新飞机配置
const newPlane = {x, y, dir};
const newPlanes = [...planes, newPlane];
// 检查碰撞
if (isValidScenario(newPlanes)) {
generate(newPlanes, count + 1, x, y);
}
}
}
}
};
generate([], 0,0,0);
return scenarios;
};
let allScenarios = [];
/**
* 根据棋盘状态过滤有效场景
* @param {Array} scenarios - 所有可能场景
*/
const filterScenarios = (scenarios) => {
return scenarios.filter(scenario => {
const grid = Array.from({length: GRID_SIZE}, () =>
Array(GRID_SIZE).fill(0)); // 0:空 1:头 2:身
// 标记场景中的飞机部件
for (const plane of scenario) {
// 标记机头
grid[plane.x][plane.y] = 1;
// 标记机身
for (let i = 1; i < PLANE_PARTS; i++) {
const dx = PLANE_OFFSETS[plane.dir][i][0];
const dy = PLANE_OFFSETS[plane.dir][i][1];
const x = plane.x + dx;
const y = plane.y + dy;
if (x >=0 && x < GRID_SIZE && y >=0 && y < GRID_SIZE) {
grid[x][y] = 2;
}
}
}
// 验证棋盘约束
for (let x = 0; x < GRID_SIZE; x++) {
for (let y = 0; y < GRID_SIZE; y++) {
const state = board[x][y];
if (state === 0) continue; // 未知格子不验证
if (state === 1 && grid[x][y] !== 1) return false;
if (state === 2 && grid[x][y] !== 2) return false;
if (state === 3 && grid[x][y] !== 0) return false;
}
}
return true;
});
};
/**
* 主预测函数
* @returns {Array} pred - 10x10预测概率数组
*/
const predict = () => {
const validScenarios = filterScenarios(allScenarios);
allScenarios = validScenarios;
if (validScenarios.length === 0) return pred;
const total = validScenarios.length;
for (let x=0;x<10;x++) for (let y=0;y<10;y++) {
pred[x][y] = 0
pred_head[x][y] = 0
pred_body[x][y] = 0
pred_null[x][y] = 0
}
// 统计各格子出现次数
validScenarios.forEach(scenario => {
const grid = Array.from({length: GRID_SIZE}, () =>
Array(GRID_SIZE).fill(0));
// 标记当前场景
scenario.forEach(plane => {
grid[plane.x][plane.y] = 1; // 标记机头
for (let i = 1; i < PLANE_PARTS; i++) {
const dx = PLANE_OFFSETS[plane.dir][i][0];
const dy = PLANE_OFFSETS[plane.dir][i][1];
const x = plane.x + dx;
const y = plane.y + dy;
if (x >=0 && x < GRID_SIZE && y >=0 && y < GRID_SIZE) {
grid[x][y] = 2; // 标记机身
}
}
});
// 更新统计
for (let x = 0; x < GRID_SIZE; x++) {
for (let y = 0; y < GRID_SIZE; y++) {
if(grid[x][y] === 1) {
pred[x][y] += 1/total;
pred_head[x][y] ++
}
else if(grid[x][y] === 2) pred_body[x][y] ++
else pred_null[x][y] ++
}
}
});
};
const gameEnd = () => {
return new Promise((resolve) => {
setInterval(() => {
if (document.getElementsByClassName("max-w-md").length !== 2) resolve()
}, 200)
})
}
const gameRunning = () => {
console.log("[ZFJHud] 游戏已开始!")
let widget = document.getElementsByClassName("max-w-md")[1]
widget.addEventListener("click",(event) => {update(event)},false)
document.body.append(table)
allScenarios = generateAllScenarios()
readBoard()
predict()
writeBoard()
gameEnd().then(() => {
table.innerHTML = "<tr><td>正在等待游戏开始</td></tr><tr><td>游戏刚开始时需要约5秒加载情况,请耐心等待</td></tr>"
gameStart().then(gameRunning)
})
}
table.innerHTML = "<tr><td>正在等待游戏开始</td></tr><tr><td>游戏刚开始时需要约5秒加载情况,请耐心等待</td></tr>"
gameStart().then(gameRunning)
// Your code here...
})();