因为想要给章节插入剧情小游戏,所以我决定自制一个点入下一章,就自动运行的html游戏。
为此我找到了我学代码的表妹。
当时情况差不多这样。
我:能不能给我做个网页小游戏。
我手机里面,表妹的聊天窗口里传出来一个优美动听的学妹声音,说:
——哥哥,你别为难我们家斤斤了。
怎么办,我也不会。虽然我知道只要我说很小的一个小游戏都可以,但我又不好意思解释是拿来干嘛的。
于是我想了下该如何做。最后打算边查阅资料,边让人工智能为我写代码段落,然后我再进行修改。
得益于曾在网吧玩局域网游戏,修改代码替换装备属性等经验,我忐忑地找到了国内知名AI中的一个。提交了这样的需求:
生成一个像素风格的游戏地图,以及像素人物,我看一下。
它很快就发给了我。

我的意思是,我打算先要一个地图,和简单的人物。这样调整完,然后再继续下一步的游戏功能。
于是我重新提交了需求,并反省了“帮我做一个网页游戏”这样的需求是否也存在歧义。虽然我目前的主要业余职业是小说作者,但因为没有读者反馈,所以我的语言表达能力依旧只有在一段时间的后知后觉后才会猛然惊醒。比如小说里“他”“它”“她”代词用的太多之类。
现在人工智能给了我一份代码:
第一份黑屏代码
<!DOCTYPE html>
<html>
<head>
<title>Web Game Demo</title>
<!-- 引入 Phaser 3 引擎 -->
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
<style>
body {
background-color: #222;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
/* 保持像素风格的关键:关闭图像平滑,使用像素渲染 */
canvas {
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
</style>
</head>
<body>
<script>
// 1. 游戏配置
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-game',
// 开启物理引擎,用于碰撞检测
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
let player;
let cursors;
let map;
// 2. 预加载资源 (这里我们用代码动态绘制,不需要加载外部图片)
function preload() {
// 为了演示,我们在这里动态生成纹理,实际开发中你会 load.image('tree', 'tree.png')
}
// 3. 创建场景
function create() {
// --- A. 创建地图数据 (0:草地, 1:树, 2:水, 3:山) ---
const mapData = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 2, 2, 2, 0, 0, 1, 1, 1, 0, 0],
[0, 1, 1, 0, 0, 0, 2, 2, 2, 0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 2, 2, 2, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
// 定义瓦片大小 (32x32 像素,经典RPG尺寸)
const tileWidth = 32;
const tileHeight = 32;
// --- B. 绘制地图瓦片 (使用 Graphics 对象模拟像素块) ---
// 草地
const grassGraphics = this.add.graphics();
grassGraphics.fillStyle(0x669933, 1);
grassGraphics.fillRect(0, 0, tileWidth, tileHeight);
grassGraphics.generateTexture('grass', tileWidth, tileHeight);
grassGraphics.destroy();
// 树木
const treeGraphics = this.add.graphics();
treeGraphics.fillStyle(0x228B22, 1);
treeGraphics.fillRect(4, 4, 24, 24); // 简单的绿色方块代表树
treeGraphics.generateTexture('tree', tileWidth, tileHeight);
treeGraphics.destroy();
// 水
const waterGraphics = this.add.graphics();
waterGraphics.fillStyle(0x4169E1, 1);
waterGraphics.fillRect(0, 0, tileWidth, tileHeight);
waterGraphics.generateTexture('water', tileWidth, tileHeight);
waterGraphics.destroy();
// 山
const mountainGraphics = this.add.graphics();
mountainGraphics.fillStyle(0x808080, 1);
mountainGraphics.fillTriangle(0, 32, 16, 0, 32, 32);
mountainGraphics.generateTexture('mountain', tileWidth, tileHeight);
mountainGraphics.destroy();
// 创建瓦片地图层
map = this.add.tilemap({ data: mapData, tileWidth: tileWidth, tileHeight: tileHeight });
// 设置碰撞属性 (1, 2, 3 是障碍物)
map.setCollision([1, 2, 3]);
// 渲染地图
const tiles = map.addTilesetImage('grass'); // 基础图块
const layer = map.createLayer(0, tiles, 0, 0);
// 替换特定图块的纹理
for (let y = 0; y < mapData.length; y++) {
for (let x = 0; x < mapData[y].length; x++) {
const tileId = mapData[y][x];
if (tileId === 1) map.putTileAt(x, y, 'tree', true);
if (tileId === 2) map.putTileAt(x, y, 'water', true);
if (tileId === 3) map.putTileAt(x, y, 'mountain', true);
}
}
// --- C. 创建玩家角色 (用代码画一个像素小人) ---
// 创建一个图形对象来绘制玩家
const playerGraphics = this.add.graphics();
// 身体 (肉色)
playerGraphics.fillStyle(0xFFDAB9, 1);
playerGraphics.fillRect(10, 14, 12, 12); // 脸
playerGraphics.fillRect(8, 26, 16, 10); // 身体
// 头发 (棕色)
playerGraphics.fillStyle(0x8B4513, 1);
playerGraphics.fillRect(8, 10, 16, 6);
// 剑 (灰色)
playerGraphics.fillStyle(0xC0C0C0, 1);
playerGraphics.fillRect(22, 18, 4, 14);
// 生成纹理
playerGraphics.generateTexture('player', 32, 32);
playerGraphics.destroy();
// 放置玩家
player = this.physics.add.sprite(100, 100, 'player');
player.setCollideWorldBounds(true);
// 添加名字标签
this.add.text(player.x, player.y - 20, 'Hero', {
fill: '#ffffff',
fontSize: '14px',
fontFamily: '"Arial Black", Gadget, sans-serif',
stroke: '#000000',
strokeThickness: 4
}).setOrigin(0.5);
// 跟随相机
this.cameras.main.startFollow(player, true, 0.1, 0.1);
// --- D. 设置控制 ---
cursors = this.input.keyboard.createCursorKeys();
}
// 4. 游戏循环更新
function update() {
if (!player) return;
const speed = 150;
let velocityX = 0;
let velocityY = 0;
// 停止移动
player.setVelocity(0);
// 上下左右控制
if (cursors.left.isDown) {
velocityX = -speed;
// 翻转精灵朝向 (模拟行走动画的第一帧)
player.setFlipX(true);
} else if (cursors.right.isDown) {
velocityX = speed;
player.setFlipX(false);
}
if (cursors.up.isDown) {
velocityY = -speed;
} else if (cursors.down.isDown) {
velocityY = speed;
}
// 如果斜向移动,归一化速度 (防止走得太快)
if (velocityX !== 0 && velocityY !== 0) {
velocityX *= 0.707;
velocityY *= 0.707;
}
player.setVelocity(velocityX, velocityY);
// 简单的动画帧切换 (这里只是简单的闪烁模拟行走)
if (velocityX !== 0 || velocityY !== 0) {
// 实际项目中这里会切换 player.anims.play('walk')
}
}
</script>
</body>
</html>
以及预览的效果。

没错,我没有放错任何东西。
它告诉我可以下载运行。我当时没那么做,不过看了下里面的内容,这种简单的还是能囫囵地认识其含义,的确只有很简易的地图和角色的行走功能,角色甚至还能动一下,而不是后面DS给我的一个像素块。
我询问了下它:
上面生成的代码预览时一个黑屏,是我需要去替换一些别的东西的意思吗?
经过思考后。
这次可以了,它给了我一个地图与角色生成器。
第二份。是可以运行的代码-地图和角色生成器
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RPG地图与角色生成器</title>
<style>
:root {
--bg-color: #2c3e50;
--primary-color: #3498db;
--secondary-color: #e74c3c;
--accent-color: #f39c12;
--text-color: #ecf0f1;
--pixel-size: 4px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Press Start 2P', monospace;
background-color: var(--bg-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;
}
}
header {
text-align: center;
margin-bottom: 30px;
grid-column: 1 / -1;
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.5);
}
.description {
font-size: 0.9rem;
max-width: 800px;
margin: 0 auto;
}
.panel {
background-color: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.panel h2 {
font-size: 1.2rem;
margin-bottom: 15px;
border-bottom: 2px solid var(--primary-color);
padding-bottom: 8px;
}
.map-container {
position: relative;
width: 100%;
height: 400px;
background-color: rgba(0, 0, 0, 0.2);
border: 2px solid var(--primary-color);
overflow: hidden;
}
.map-grid {
display: grid;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: repeat(12, 1fr);
width: 100%;
height: 100%;
}
.map-cell {
width: 100%;
height: 100%;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
.grass { background-color: #4a7c45; }
.water { background-color: #3498db; }
.mountain { background-color: #7f8c8d; }
.forest { background-color: #27ae60; }
.sand { background-color: #f39c12; }
.snow { background-color: #ecf0f1; }
.character-preview {
width: 100px;
height: 100px;
margin: 20px auto;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
border: 2px solid var(--accent-color);
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
}
.control-group {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 5px;
font-size: 0.8rem;
}
select, button {
padding: 8px;
border: none;
border-radius: 4px;
background-color: var(--primary-color);
color: white;
font-family: inherit;
cursor: pointer;
}
button {
background-color: var(--secondary-color);
font-weight: bold;
transition: background-color 0.3s;
}
button:hover {
background-color: #c0392b;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.action-buttons button {
flex: 1;
}
.instructions {
margin-top: 20px;
font-size: 0.8rem;
background-color: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 5px;
}
.instructions h3 {
margin-bottom: 10px;
color: var(--accent-color);
}
.instructions ul {
padding-left: 20px;
}
.instructions li {
margin-bottom: 5px;
}
/* 像素字体 */
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
</style>
</head>
<body>
<div class="container">
<header>
<h1>RPG地图与角色生成器</h1>
<p class="description">创建复古像素风格的游戏地图和角色</p>
</header>
<div class="panel">
<h2>游戏地图生成器</h2>
<div class="map-container">
<div class="map-grid" id="mapGrid">
<!-- 地图单元格将通过JS生成 -->
</div>
</div>
<div class="controls">
<div class="control-group">
<label for="terrainType">地形类型:</label>
<select id="terrainType">
<option value="grass">草地</option>
<option value="water">水域</option>
<option value="mountain">山脉</option>
<option value="forest">森林</option>
<option value="sand">沙漠</option>
<option value="snow">雪地</option>
</select>
</div>
<div class="control-group">
<label for="mapSize">地图大小:</label>
<select id="mapSize">
<option value="16x12">小 (16x12)</option>
<option value="20x15" selected>中 (20x15)</option>
<option value="24x18">大 (24x18)</option>
</select>
</div>
</div>
<div class="action-buttons">
<button id="generateMap">生成新地图</button>
<button id="randomMap">随机生成地图</button>
</div>
</div>
<div class="panel">
<h2>角色生成器</h2>
<div class="character-preview" id="characterPreview">
<!-- 角色预览将通过JS生成 -->
</div>
<div class="controls">
<div class="control-group">
<label for="characterType">角色类型:</label>
<select id="characterType">
<option value="warrior">战士</option>
<option value="mage">法师</option>
<option value="thief">盗贼</option>
<option value="monk">武僧</option>
</select>
</div>
<div class="control-group">
<label for="characterColor">主色调:</label>
<select id="characterColor">
<option value="red">红色</option>
<option value="blue">蓝色</option>
<option value="green">绿色</option>
<option value="yellow">黄色</option>
<option value="purple">紫色</option>
<option value="white">白色</option>
</select>
</div>
</div>
<div class="action-buttons">
<button id="generateCharacter">生成新角色</button>
<button id="randomCharacter">随机角色</button>
</div>
<div class="instructions">
<h3>使用说明</h3>
<ul>
<li>选择地形类型和地图大小,然后点击"生成新地图"</li>
<li>点击"随机生成地图"创建完全随机的地图</li>
<li>选择角色类型和颜色,然后点击"生成新角色"</li>
<li>点击"随机角色"创建完全随机的角色</li>
<li>生成的地图和角色可以直接用于游戏开发</li>
</ul>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 地图相关变量
const mapGrid = document.getElementById('mapGrid');
const terrainTypeSelect = document.getElementById('terrainType');
const mapSizeSelect = document.getElementById('mapSize');
const generateMapBtn = document.getElementById('generateMap');
const randomMapBtn = document.getElementById('randomMap');
// 角色相关变量
const characterPreview = document.getElementById('characterPreview');
const characterTypeSelect = document.getElementById('characterType');
const characterColorSelect = document.getElementById('characterColor');
const generateCharacterBtn = document.getElementById('generateCharacter');
const randomCharacterBtn = document.getElementById('randomCharacter');
// 地形类型映射
const terrainTypes = {
grass: { color: '#4a7c45', name: '草地' },
water: { color: '#3498db', name: '水域' },
mountain: { color: '#7f8c8d', name: '山脉' },
forest: { color: '#27ae60', name: '森林' },
sand: { color: '#f39c12', name: '沙漠' },
snow: { color: '#ecf0f1', name: '雪地' }
};
// 初始化地图
function initMap() {
const size = mapSizeSelect.value.split('x');
const cols = parseInt(size[0]);
const rows = parseInt(size[1]);
mapGrid.innerHTML = '';
mapGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
mapGrid.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
for (let i = 0; i < cols * rows; i++) {
const cell = document.createElement('div');
cell.classList.add('map-cell');
cell.classList.add('grass');
cell.dataset.terrain = 'grass';
mapGrid.appendChild(cell);
}
}
// 生成地图
function generateMap() {
const terrain = terrainTypeSelect.value;
const cells = mapGrid.querySelectorAll('.map-cell');
cells.forEach(cell => {
cell.className = 'map-cell';
cell.classList.add(terrain);
cell.dataset.terrain = terrain;
});
}
// 随机生成地图
function generateRandomMap() {
const terrains = Object.keys(terrainTypes);
const cells = mapGrid.querySelectorAll('.map-cell');
cells.forEach(cell => {
const randomTerrain = terrains[Math.floor(Math.random() * terrains.length)];
cell.className = 'map-cell';
cell.classList.add(randomTerrain);
cell.dataset.terrain = randomTerrain;
});
}
// 生成角色
function generateCharacter() {
const type = characterTypeSelect.value;
const color = characterColorSelect.value;
// 清空预览区域
characterPreview.innerHTML = '';
// 创建角色容器
const character = document.createElement('div');
character.style.width = '100%';
character.style.height = '100%';
character.style.display = 'grid';
character.style.gridTemplateColumns = 'repeat(8, 1fr)';
character.style.gridTemplateRows = 'repeat(8, 1fr)';
character.style.imageRendering = 'pixelated';
// 根据角色类型生成像素艺术
const characterData = getCharacterData(type, color);
// 填充角色网格
for (let y = 0; y < 8; y++) {
for (let x = 0; x < 8; x++) {
const cell = document.createElement('div');
cell.style.width = '100%';
cell.style.height = '100%';
if (characterData[y][x] === 1) {
cell.style.backgroundColor = getColor(color);
} else {
cell.style.backgroundColor = 'transparent';
}
character.appendChild(cell);
}
}
characterPreview.appendChild(character);
}
// 随机生成角色
function generateRandomCharacter() {
const types = ['warrior', 'mage', 'thief', 'monk'];
const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'white'];
const randomType = types[Math.floor(Math.random() * types.length)];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
characterTypeSelect.value = randomType;
characterColorSelect.value = randomColor;
generateCharacter();
}
// 获取角色数据
function getCharacterData(type, color) {
// 简化版角色数据 - 实际应用中可以更复杂
const characters = {
warrior: [
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,1,1,1,1,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0]
],
mage: [
[0,0,1,1,1,1,0,0],
[0,1,1,0,0,1,1,0],
[0,1,0,0,0,0,1,0],
[0,1,0,0,0,0,1,0],
[0,1,0,0,0,0,1,0],
[0,1,1,1,1,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0]
],
thief: [
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[0,1,0,0,0,0,1,0],
[0,1,0,0,0,0,1,0],
[0,1,1,0,0,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0]
],
monk: [
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0]
]
};
return characters[type] || characters.warrior;
}
// 获取颜色值
function getColor(colorName) {
const colors = {
red: '#e74c3c',
blue: '#3498db',
green: '#2ecc71',
yellow: '#f1c40f',
purple: '#9b59b6',
white: '#ecf0f1'
};
return colors[colorName] || colors.red;
}
// 事件监听
generateMapBtn.addEventListener('click', generateMap);
randomMapBtn.addEventListener('click', generateRandomMap);
generateCharacterBtn.addEventListener('click', generateCharacter);
randomCharacterBtn.addEventListener('click', generateRandomCharacter);
// 初始化
initMap();
generateCharacter();
});
</script>
</body>
</html>

这个窗口的聊天结束。
我找到了做翻译时用的Deepseek。把之前的要求重新梳理了下,提交给了它。
首先试一下需要补充的内容。
我要用▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀智能体,做一个网页游戏,放在我的wordpress的一个页面上,给浏览者休闲玩耍,不登录,随点随玩。我不知道当前能够发挥它的上限是到达什么程度,你有什么好的题材建议吗?我个人是比较喜欢宝藏探险,比如随机生成地图,然后摇色子,让人物发出几个攻击,去攻击怪物。或者是击败什么东西,随机掉落,最好是掉落不同颜色罐子,还可以再摇一次
DS鲸鱼:
嗯,用户想要用▀ ▀ ▀ ▀的▀ ▀ ▀ ▀智能体来做一个网页游戏,放在WordPress页面上,供浏览者休闲玩耍,无需登录,点击即玩。用户想知道当前AI能力的上限,并希望得到题材建议。
用户提到了几个自己喜欢的游戏类型:宝藏探险(随机地图、掷骰子、攻击怪物)、类似暗黑的打怪爆装备(随机掉落不同颜色的罐子,可以再摇一次,最后弹出管理员头像的弹窗)、以及改头换面的卡牌游戏。用户也愿意听取我的其他建议。
我需要拆解用户指令。深层需求是:用户想做一个有互动性和趣味性的小游戏来吸引网站访客,但受限于WordPress页面嵌入、纯文本交互、无需后端和登录的技术约束。核心是评估“▀▀智能体”能用纯文本(HTML/CSS/JS)实现什么程度的游戏,并给出既符合用户喜好又切实可行的题材方案。
我先评估技术边界。在WordPress页面嵌代码块,本质上是前端游戏,一切逻辑靠浏览器JS驱动,没有服务端。所谓“▀▀智能体”在这个场景下,可能指的是AI辅助生成代码的能力,以及可以使用emoji、CSS动画、前端存储来模拟游戏元素。AI可以生成复杂的交互逻辑、随机算法和美观的UI,但不能处理真实的多人在线或服务端持久化数据。上限比用户想象的高,但受限于单页面环境。
……省略…
针对你“WordPress单页、无需登录、点击即玩”的场景,▀ ▀ ▀ ▀▀ ▀▀▀智能体的上限其实很高——它能直接生成完整的HTML/CSS/JS代码,你复制粘贴到网页里就行。
基于你的偏好,我构思了四个方案:
🏆 方案一:《挖宝地牢·罐子实录》(最契合你的描述)
这个方案完美融合了你说的所有元素:随机地图、骰子、攻击、暗黑式掉落、彩色罐子、二次开罐、管理员头像弹窗。
核心循环:掷骰子走格子 → 遇怪 → 选招式攻击 → 爆出彩色罐子 → 点击罐子再次随机 → 小概率开出头像彩蛋
- 随机地图:用emoji绘制的横向卷轴地牢,每次刷新都不一样。
- 战斗:三个技能(劈砍/火球/背刺),掷虚拟骰子比大小决定伤害。
- 掉落罐子:击败怪物后掉落灰/蓝/紫/金四色罐子。金色罐子概率极低。
- 开罐机制:点击任意罐子,内部再摇一次奖。普通罐子出金币、药水;金色罐子有5%概率开出管理员头像,全屏弹窗+撒花效果。
- 头像弹窗:就是你上传的照片,配文“你挖到了本站最深的宝藏——管理员本尊!”
- 趣味点:完全符合你“虽点随玩”的要求,三步一惊喜,五步一彩蛋。
🎴 方案二:《符号召唤师》(卡牌换皮)
把传统卡牌完全改为“符文/符号”,对战就是拼符号组合。
- 牌组:不再是扑克或万智牌,而是☯️⚡🔥💧🌪️等基础符文。
- 对战:每回合随机抽3个符文,排列组合成“法术”(如🔥+💧=蒸汽爆发),攻击怪物。
- 收集:击败怪物有概率掉落新符文,可扩充你的符文库。
- 视觉效果:像奇门遁甲在画符,点击符文会发光并拖出轨迹。
- 适合你:满足“把牌换成别的东西”,且符号体系自带神秘感,与“▀▀▀▀”的调性很搭。
🎲 方案三:《色子猎人》(极简上头版)
如果前两个偏复杂,这个更轻量、更魔性。
- 界面:中间一个大骰子,上方一个怪物,下方三个技能槽。
- 规则:摇骰子,点数放入技能槽。技能需要特定点数激活(比如“重击”需要≥5点,“连击”需要两个相同点数)。
- 掉落:击败怪物后爆出数个骰子,点击骰子再掷一次,根据新点数兑换成不同颜色的“能量罐”。
- 终极彩蛋:集齐7种颜色能量罐,弹出你的头像+“恭喜获得【管理员之骰】限定皮肤(心理)”。
- 优点:点击欲望极强,路径短,反馈快。
🧠 方案四:《密室逃出》(烧脑反差萌)
利用“▀▀”的文本生成能力,现场生成诗词、谜题。
- 你被困在一间虚拟书房,需要破解书架上的谜题才能开门。
- 谜题由AI现场生成(比如藏头诗、断句重组、对联填空),答案就是钥匙。
- 每过一关,暗格里会掉出“残卷”,收集残卷可拼出管理员画像碎片。
- 上限展示:真正用到智能体的实时内容生成能力,而不是固定题库。
- 风险:生成内容可能不稳定,需要做好兜底;但若成功,会非常惊艳。
📌 我的推荐
首选方案一《挖宝地牢·罐子实录》,原因:
- 精准命中你所有的喜好点(摇骰子、攻击、彩色罐子、二次开罐、头像彩蛋)。
- 实现难度在智能体能力范围内,我能为你生成完整可运行代码。
- 游戏循环经典,“再走一步就开罐”的粘性很强。
- 头像弹窗设计极具个人印记,浏览者过目不忘。
如果你选定题材,我可以立刻为你生成完整游戏的HTML文件——你只需在WordPress页面中插入一个“自定义HTML”区块,把代码粘贴进去,游戏就能直接运行。你更倾向哪个方案,或者想在方案一基础上微调哪些细节?
我选了一。
第一份可用代码
<style>
/* 游戏容器独立样式,避免与主题冲突 */
#dungeon-game {
all: initial;
display: block;
max-width: 600px;
margin: 20px auto;
font-family: 'Courier New', monospace;
background: #1a1a1a;
color: #d4af37;
border: 2px solid #d4af37;
border-radius: 12px;
padding: 20px;
box-sizing: border-box;
position: relative;
user-select: none;
}
#dungeon-game * {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.game-title {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 15px;
text-shadow: 0 0 10px #d4af37;
}
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
font-size: 16px;
background: #0d0d0d;
padding: 8px 12px;
border-radius: 6px;
}
.hp-bar {
display: flex;
align-items: center;
gap: 6px;
}
.hp-fill {
width: 120px;
height: 16px;
background: #333;
border-radius: 10px;
overflow: hidden;
}
.hp-fill-inner {
height: 100%;
width: 100%;
background: #c0392b;
transition: width 0.3s;
}
.treasure {
font-size: 18px;
}
.map-container {
background: #0d0d0d;
border-radius: 8px;
padding: 10px;
margin-bottom: 15px;
overflow-x: auto;
white-space: nowrap;
}
.map-row {
display: flex;
gap: 4px;
justify-content: center;
}
.map-cell {
width: 36px;
height: 36px;
background: #2c2c2c;
border: 1px solid #555;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
position: relative;
transition: background 0.2s;
}
.map-cell.current {
border: 2px solid #f1c40f;
box-shadow: 0 0 10px #f1c40f;
background: #3e3a1f;
}
.map-cell.monster {
background: #4a1f1f;
}
.dice-area {
text-align: center;
margin-bottom: 15px;
}
.dice-btn {
background: #d4af37;
border: none;
color: #1a1a1a;
font-size: 22px;
font-weight: bold;
padding: 12px 30px;
border-radius: 50px;
cursor: pointer;
box-shadow: 0 4px 0 #7d6619;
transition: all 0.1s;
}
.dice-btn:active {
transform: translateY(3px);
box-shadow: 0 1px 0 #7d6619;
}
.dice-btn:disabled {
opacity: 0.5;
pointer-events: none;
}
.dice-result {
font-size: 28px;
margin-top: 8px;
height: 36px;
}
.battle-box {
background: #1a0a0a;
border: 2px solid #c0392b;
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
display: none;
}
.battle-box.active {
display: block;
}
.monster-info {
text-align: center;
margin-bottom: 10px;
}
.monster-name {
font-size: 22px;
color: #e74c3c;
}
.skill-btns {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
}
.skill-btn {
background: #333;
border: 1px solid #d4af37;
color: #d4af37;
padding: 10px 14px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: 0.2s;
}
.skill-btn:hover {
background: #d4af37;
color: #1a1a1a;
}
.skill-btn:disabled {
opacity: 0.4;
pointer-events: none;
}
.loot-area {
display: none;
text-align: center;
margin: 15px 0;
}
.loot-area.active {
display: block;
}
.jars-container {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
margin: 10px 0;
}
.jar {
width: 60px;
height: 70px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
cursor: pointer;
transition: transform 0.2s;
position: relative;
}
.jar:hover {
transform: scale(1.1);
}
.jar.gray { background: #5d6d7e; }
.jar.blue { background: #2980b9; }
.jar.purple { background: #8e44ad; }
.jar.gold { background: #f1c40f; }
.jar.opened {
opacity: 0.5;
pointer-events: none;
filter: grayscale(0.8);
}
.gameover-box {
display: none;
text-align: center;
padding: 20px;
}
.gameover-box.active {
display: block;
}
.restart-btn {
background: #c0392b;
border: none;
color: white;
font-size: 18px;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
margin-top: 10px;
}
.modal-overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
z-index: 9999;
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
}
.modal-overlay.show {
display: flex;
}
.modal-content {
background: #2c2c2c;
padding: 30px;
border-radius: 20px;
text-align: center;
border: 3px solid #f1c40f;
}
.modal-content img {
width: 120px;
height: 120px;
border-radius: 50%;
border: 3px solid #f1c40f;
margin-bottom: 15px;
}
.close-modal {
margin-top: 15px;
background: #d4af37;
border: none;
padding: 8px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
canvas.sprinkle {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: 10000;
}
</style>
<div id="dungeon-game">
<div class="game-title">⚔️挖宝实录 ⚔️</div>
<div class="status-bar">
<div class="hp-bar">
❤️ <div class="hp-fill"><div class="hp-fill-inner" id="playerHpBar"></div></div>
<span id="hpText">100/100</span>
</div>
<div class="treasure">💰 <span id="treasureSpan">0</span></div>
</div>
<div class="map-container">
<div class="map-row" id="mapRow"></div>
</div>
<div class="dice-area">
<button class="dice-btn" id="diceBtn">🎲 掷骰子</button>
<div class="dice-result" id="diceDisplay"></div>
</div>
<div class="battle-box" id="battleBox">
<div class="monster-info">
<div class="monster-name" id="monsterName"></div>
<div>HP: <span id="monsterHp"></span></div>
</div>
<div class="skill-btns">
<button class="skill-btn" data-skill="slash">⚔️ 劈砍<br><small>1d6+2</small></button>
<button class="skill-btn" data-skill="fireball">🔥 火球<br><small>1d6+4 (20%失手)</small></button>
<button class="skill-btn" data-skill="backstab">🗡️ 背刺<br><small>1d6+6 并使怪物跳过攻击</small></button>
</div>
</div>
<div class="loot-area" id="lootArea">
<div style="font-size:20px;">🏺 战利品罐子!点击开启</div>
<div class="jars-container" id="jarsContainer"></div>
<div id="lootMessage" style="margin-top:10px; color:#f1c40f;"></div>
</div>
<div class="gameover-box" id="gameoverBox">
<h2>💀 你倒下了...</h2>
<button class="restart-btn" id="restartBtn">重新探索</button>
</div>
</div>
<!-- 头像彩蛋弹窗 -->
<div class="modal-overlay" id="avatarModal">
<div class="modal-content">
<img id="adminAvatarImg" src="" alt="管理员头像">
<p style="color:#f1c40f; font-size:18px;">🎉 你挖到了本站最深的宝藏——! 🎉</p>
<button class="close-modal" id="closeModalBtn">继 续 探 索</button>
</div>
</div>
<script>
(function() {
const adminAvatar = 'https://via.placeholder.com/150/FFD700/000000?text=Admin';
// 游戏状态
const MAP_LENGTH = 20;
let gameState = 'idle'; // idle, rolling, moving, battle, loot, gameover
let playerPos = 0;
let playerHP = 100;
const maxHP = 100;
let diceResult = 0;
let mapData = [];
let currentMonster = null;
let lootJars = [];
let treasureScore = 0;
let battleSkipMonsterAttack = false;
// DOM 元素
const mapRow = document.getElementById('mapRow');
const diceBtn = document.getElementById('diceBtn');
const diceDisplay = document.getElementById('diceDisplay');
const playerHpBar = document.getElementById('playerHpBar');
const hpText = document.getElementById('hpText');
const treasureSpan = document.getElementById('treasureSpan');
const battleBox = document.getElementById('battleBox');
const monsterName = document.getElementById('monsterName');
const monsterHp = document.getElementById('monsterHp');
const lootArea = document.getElementById('lootArea');
const jarsContainer = document.getElementById('jarsContainer');
const lootMessage = document.getElementById('lootMessage');
const gameoverBox = document.getElementById('gameoverBox');
const avatarModal = document.getElementById('avatarModal');
const adminAvatarImg = document.getElementById('adminAvatarImg');
const closeModalBtn = document.getElementById('closeModalBtn');
const restartBtn = document.getElementById('restartBtn');
const skillBtns = document.querySelectorAll('.skill-btn');
// 初始化头像
adminAvatarImg.src = adminAvatar;
function generateMap() {
mapData = [];
for (let i = 0; i < MAP_LENGTH; i++) {
if (Math.random() < 0.3) {
const names = ['骷髅兵', '哥布林', '暗影蜘蛛', '石像鬼'];
const hp = 15 + Math.floor(Math.random() * 20);
mapData.push({
type: 'monster',
monster: { name: names[Math.floor(Math.random() * names.length)], hp: hp, maxHp: hp }
});
} else {
mapData.push({ type: 'empty', monster: null });
}
}
}
function updateUI() {
// 更新血量
const hpPercent = Math.max(0, playerHP) / maxHP * 100;
playerHpBar.style.width = hpPercent + '%';
hpText.textContent = Math.max(0, playerHP) + '/' + maxHP;
treasureSpan.textContent = treasureScore;
// 渲染地图
mapRow.innerHTML = '';
mapData.forEach((cell, index) => {
const cellDiv = document.createElement('div');
cellDiv.className = 'map-cell';
if (index === playerPos) cellDiv.classList.add('current');
if (cell.type === 'monster') {
cellDiv.classList.add('monster');
cellDiv.textContent = '👾';
} else {
cellDiv.textContent = '⬛';
}
mapRow.appendChild(cellDiv);
});
// 根据状态显示/隐藏区域
if (gameState === 'battle') {
battleBox.classList.add('active');
} else {
battleBox.classList.remove('active');
}
if (gameState === 'loot') {
lootArea.classList.add('active');
} else {
lootArea.classList.remove('active');
}
if (gameState === 'gameover') {
gameoverBox.classList.add('active');
diceBtn.disabled = true;
} else {
gameoverBox.classList.remove('active');
}
// 骰子按钮只在 idle 可用
diceBtn.disabled = (gameState !== 'idle');
}
// 更新战斗界面怪物信息
function updateBattleUI() {
if (currentMonster) {
monsterName.textContent = currentMonster.name;
monsterHp.textContent = currentMonster.hp + '/' + currentMonster.maxHp;
}
}
// 掷骰子动画
function rollDice(callback) {
if (gameState !== 'idle' && gameState !== 'battle') return;
let count = 0;
const maxCount = 12 + Math.floor(Math.random() * 8);
const interval = setInterval(() => {
const val = Math.floor(Math.random() * 6) + 1;
diceDisplay.textContent = '🎲 ' + val;
count++;
if (count >= maxCount) {
clearInterval(interval);
const finalVal = Math.floor(Math.random() * 6) + 1;
diceDisplay.textContent = '🎲 ' + finalVal;
callback(finalVal);
}
}, 80);
}
// 玩家移动(异步逐步移动)
async function movePlayer(steps) {
gameState = 'moving';
diceBtn.disabled = true;
for (let i = 0; i < steps; i++) {
await new Promise(resolve => setTimeout(resolve, 150));
playerPos++;
if (playerPos >= MAP_LENGTH) {
playerPos = 0;
generateMap();
treasureScore += 30;
updateUI();
// 简单提示进入下一层
}
updateUI();
}
// 移动结束,检查当前格子
const cell = mapData[playerPos];
if (cell.type === 'monster') {
enterBattle(cell.monster);
} else {
gameState = 'idle';
updateUI();
}
}
function enterBattle(monsterData) {
currentMonster = {
name: monsterData.name,
hp: monsterData.hp,
maxHp: monsterData.maxHp
};
battleSkipMonsterAttack = false;
gameState = 'battle';
updateUI();
updateBattleUI();
}
// 玩家使用技能
function playerAttack(skill) {
if (gameState !== 'battle' || !currentMonster) return;
rollDice((diceValue) => {
let damage = 0;
let miss = false;
let skipMonster = false;
switch(skill) {
case 'slash':
damage = diceValue + 2;
break;
case 'fireball':
if (Math.random() < 0.2) {
miss = true;
} else {
damage = diceValue + 4;
}
break;
case 'backstab':
damage = diceValue + 6;
skipMonster = true;
break;
}
if (miss) {
lootMessage.textContent = '🔥 火球未命中!';
} else {
currentMonster.hp -= damage;
lootMessage.textContent = `造成 ${damage} 点伤害!`;
}
updateBattleUI();
if (currentMonster.hp <= 0) {
// 怪物死亡
currentMonster = null;
generateLoot();
gameState = 'loot';
updateUI();
return;
}
// 怪物攻击(除非背刺跳过)
if (!skipMonster) {
let monsterDmg = Math.floor(Math.random() * 4) + 1;
playerHP -= monsterDmg;
lootMessage.textContent += ` 怪物反击造成 ${monsterDmg} 点伤害。`;
} else {
battleSkipMonsterAttack = true;
lootMessage.textContent += ' 怪物被背刺震慑,未反击。';
}
updateUI();
// 检查玩家死亡
if (playerHP <= 0) {
playerHP = 0;
gameState = 'gameover';
updateUI();
}
});
}
// 生成掉落罐子
function generateLoot() {
const count = 1 + Math.floor(Math.random() * 3); // 1-3个
lootJars = [];
const colors = ['gray', 'blue', 'purple', 'gold'];
const weights = [0.60, 0.25, 0.12, 0.03];
for (let i = 0; i < count; i++) {
const r = Math.random();
let cumulative = 0;
let selectedColor = 'gray';
for (let j = 0; j < colors.length; j++) {
cumulative += weights[j];
if (r <= cumulative) {
selectedColor = colors[j];
break;
}
}
lootJars.push({ color: selectedColor, opened: false });
}
renderLootJars();
}
function renderLootJars() {
jarsContainer.innerHTML = '';
lootJars.forEach((jar, index) => {
const jarDiv = document.createElement('div');
jarDiv.className = 'jar ' + jar.color;
jarDiv.textContent = '🏺';
if (jar.opened) jarDiv.classList.add('opened');
jarDiv.addEventListener('click', () => openJar(index, jarDiv));
jarsContainer.appendChild(jarDiv);
});
}
function openJar(index, element) {
if (lootJars[index].opened) return;
lootJars[index].opened = true;
element.classList.add('opened');
const color = lootJars[index].color;
let rewardText = '';
let gold = 0;
let heal = 0;
switch(color) {
case 'gray':
gold = 5 + Math.floor(Math.random() * 15);
rewardText = `灰色罐子:获得 ${gold} 金币。`;
break;
case 'blue':
gold = 20 + Math.floor(Math.random() * 30);
heal = 10;
rewardText = `蓝色罐子:获得 ${gold} 金币,恢复 ${heal} 点生命。`;
break;
case 'purple':
gold = 50 + Math.floor(Math.random() * 50);
heal = 25;
rewardText = `紫色罐子:获得 ${gold} 金币,恢复 ${heal} 点生命。`;
break;
case 'gold':
if (Math.random() < 0.05) {
// 触发管理员头像彩蛋
gold = 100 + Math.floor(Math.random() * 400);
rewardText = `🌟 金色罐子:获得 ${gold} 金币!而且...?`;
treasureScore += gold;
playerHP = Math.min(maxHP, playerHP + heal);
updateUI();
showAvatarModal();
lootMessage.textContent = rewardText;
checkAllJarsOpened();
return;
} else {
gold = 100 + Math.floor(Math.random() * 400);
heal = 999; // 全恢复
rewardText = `金色罐子:获得 ${gold} 金币,生命全恢复!`;
}
break;
}
treasureScore += gold;
playerHP = Math.min(maxHP, playerHP + heal);
lootMessage.textContent = rewardText;
updateUI();
checkAllJarsOpened();
}
function checkAllJarsOpened() {
if (lootJars.every(j => j.opened)) {
setTimeout(() => {
gameState = 'idle';
lootJars = [];
lootMessage.textContent = '';
updateUI();
}, 1200);
}
}
// 管理员头像弹窗 + 撒花
function showAvatarModal() {
avatarModal.classList.add('show');
startConfetti();
}
function hideAvatarModal() {
avatarModal.classList.remove('show');
stopConfetti();
}
closeModalBtn.addEventListener('click', hideAvatarModal);
// 简单撒花效果
let confettiCanvas, confettiCtx, confettiParticles = [], confettiAnimationId;
function startConfetti() {
if (confettiCanvas) return;
confettiCanvas = document.createElement('canvas');
confettiCanvas.className = 'sprinkle';
document.body.appendChild(confettiCanvas);
confettiCtx = confettiCanvas.getContext('2d');
confettiCanvas.width = window.innerWidth;
confettiCanvas.height = window.innerHeight;
confettiParticles = [];
for (let i = 0; i < 120; i++) {
confettiParticles.push({
x: Math.random() * confettiCanvas.width,
y: Math.random() * confettiCanvas.height - confettiCanvas.height,
r: Math.random() * 4 + 2,
d: Math.random() * 60 + 10,
color: `hsl(${Math.random() * 360}, 80%, 60%)`,
tilt: Math.random() * 10
});
}
function draw() {
confettiCtx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
for (let p of confettiParticles) {
confettiCtx.beginPath();
confettiCtx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
confettiCtx.fillStyle = p.color;
confettiCtx.fill();
p.y += Math.cos(p.d) + 1 + p.r / 2;
p.x += Math.sin(p.d * 0.5) * 0.5;
if (p.y > confettiCanvas.height) {
p.y = -10;
p.x = Math.random() * confettiCanvas.width;
}
}
confettiAnimationId = requestAnimationFrame(draw);
}
draw();
}
function stopConfetti() {
if (confettiAnimationId) cancelAnimationFrame(confettiAnimationId);
if (confettiCanvas) {
confettiCanvas.remove();
confettiCanvas = null;
}
confettiParticles = [];
}
// 重新开始
function restartGame() {
playerHP = maxHP;
playerPos = 0;
treasureScore = 0;
gameState = 'idle';
currentMonster = null;
lootJars = [];
generateMap();
updateUI();
diceDisplay.textContent = '';
}
restartBtn.addEventListener('click', restartGame);
// 骰子按钮
diceBtn.addEventListener('click', () => {
if (gameState !== 'idle') return;
rollDice((value) => {
diceResult = value;
gameState = 'moving';
movePlayer(value);
});
});
// 技能按钮
skillBtns.forEach(btn => {
btn.addEventListener('click', () => {
if (gameState !== 'battle') return;
const skill = btn.dataset.skill;
playerAttack(skill);
});
});
// 初始化
generateMap();
updateUI();
})();
</script>
就在这一瞬间,碍眼的像素块成为了我想要的感觉。
它给了我这个。截图效果如下。

说实话,比我预想的好很多。我稍微有了点信心。实在不行,这个也勉强能用。
然后我提交了我之前在隔壁提交过的需求。为了让它理解,我举例了二代的最终幻想的像素游戏风格。

嗯…
人工智能真的可以办到吗?
或许它需要其它的脑子。
我问了它这个问题。

它给我说了一堆需要注册很多账号的。于是我放弃了,盯着这个像素块想了想,首先是将其中的色素块修复得缩小。
我差不多把它当前的一个像素分为了9个,然后看了别的像素游戏,然后告诉它,每一个格子代表什么,几个相似格子组成一个元素部件。来制作一个像素图片。

还可以,稍微有点感觉。后面又修改了两次,但都差不多。
目前地图完成了,但这样看上去就完全是俯视,我并不是想要一个超级复古的俯视游戏。所以我想了下,尝试将地图进行倾斜旋转。

我很满意,接下来可以做小人儿了。
差不多是这样描述的:这一次,在上面添加一个像素小人主角,由非常细小,但占据了至少高八至十二,宽三到六像素的人制作,可以有两个移动时候的变化动作。另外在地图上添加几个怪物,战斗为触碰后,出现新的弹出菜单框…
DS蓝鲸·思考中:
用户想要一个正对玩家、稍微向前倾斜的梯形视角地图,像素小人主角和怪物…
输出:
这个游戏实现了你提的所有内容:梯形视角的像素地图、可操控的主角、触碰到怪物后弹出式战斗框。你只需将代码完整复制粘贴到 WordPress 页面的“自定义HTML”中就能直接运行。
🎮 游戏操作说明
- 探索阶段:使用键盘的 W/A/S/D 或屏幕上的虚拟摇杆来控制像素小人在地图上移动。地图采用正对玩家且略微前倾的梯形视角,主角有上下两个方向的走路帧动画
· 。
· 战斗阶段:主角碰到地图上的怪物(绿色为弱小,紫色为强大)会立刻弹出战斗框
。战斗中,怪物带有由不同颜色菱形图标组成的护盾点数,你需要用对应的弱点属性攻击才能削减护盾并进入Break状态
· 。
· 战斗操作:你可以发动无消耗的普通攻击,或者使用带有元素属性的技能(会消耗MP)。每次击中弱点,护盾减1,怪物头顶会显示“WEAK”
· 。
· 破防与增幅系统:打破所有护盾,怪物会进入“BREAK(破防)”状态,本回合和下回都无法行动,并且受到的伤害翻倍
。你可以消耗BP点数,使用“增幅(Boost)”来发动更强力的攻击
· 。
· 战斗状态栏:战斗框的顶部会清晰展示敌我双方的生命值(HP)、魔法值(MP)和增幅点数(BP),方便你随时掌握战局。
第二份可用代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>旅人•荒芜广场</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0c0f;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Courier New', 'SimHei', monospace;
padding: 20px;
}
.game-container {
background: #1a1c1e;
border: 3px solid #5a4a3a;
border-radius: 12px;
padding: 10px;
box-shadow: 0 0 30px rgba(0,0,0,0.8);
position: relative;
}
canvas {
display: block;
image-rendering: crisp-edges;
image-rendering: pixelated;
border-radius: 4px;
width: 100%;
height: auto;
}
/* 战斗浮层 */
.battle-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
display: none;
justify-content: center;
align-items: center;
border-radius: 12px;
z-index: 15;
}
.battle-overlay.active {
display: flex;
}
.battle-dialog {
background: #1e2226;
border: 3px solid #c9a96e;
border-radius: 16px;
padding: 20px 25px;
width: 90%;
max-width: 500px;
color: #e8e0d0;
box-shadow: 0 0 25px rgba(0,0,0,0.9);
}
.battle-dialog h3 {
font-size: 20px;
margin-bottom: 12px;
color: #f5c542;
text-align: center;
}
.stats-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 15px;
background: #121418;
padding: 6px 12px;
border-radius: 6px;
}
.monster-shields {
display: flex;
align-items: center;
gap: 6px;
margin: 10px 0;
}
.shield-icon {
width: 18px;
height: 18px;
border-radius: 3px;
background: #7f8c8d;
}
.shield-icon.broken {
background: #2c3e50;
opacity: 0.4;
}
.weakness-hint {
font-size: 13px;
color: #bbb;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.action-btn {
background: #2e353b;
border: 1px solid #5a6b7a;
color: #f0e6d2;
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
font-size: 14px;
transition: 0.2s;
}
.action-btn:hover {
background: #4a5a6a;
}
.action-btn:disabled {
opacity: 0.4;
pointer-events: none;
}
.boost-btn {
background: #674f1e;
border-color: #e6b422;
color: #ffd966;
}
.battle-log {
margin-top: 10px;
font-size: 14px;
min-height: 20px;
color: #f5c542;
}
.dpad {
position: absolute;
bottom: 25px;
left: 25px;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
z-index: 10;
opacity: 0.9;
}
.dpad-row {
display: flex;
gap: 3px;
}
.dpad-btn {
width: 42px;
height: 42px;
background: #2f3640;
border: 2px solid #5a4a3a;
border-radius: 8px;
color: #ddd;
font-size: 22px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
}
.dpad-btn:active {
background: #5a4a3a;
}
</style>
</head>
<body>
<div class="game-container" id="gameContainer">
<canvas id="gameCanvas" width="720" height="500"></canvas>
<!-- 移动方向键 -->
<div class="dpad" id="dpad">
<div class="dpad-row">
<div class="dpad-btn" data-dir="up">▲</div>
</div>
<div class="dpad-row">
<div class="dpad-btn" data-dir="left">◀</div>
<div class="dpad-btn" data-dir="down">▼</div>
<div class="dpad-btn" data-dir="right">▶</div>
</div>
</div>
<!-- 战斗弹出框 -->
<div class="battle-overlay" id="battleOverlay">
<div class="battle-dialog">
<h3>⚔️ 魔物遭遇战 ⚔️</h3>
<div class="stats-row">
<span>❤️ 主角 HP <span id="heroHpBattle">30/30</span></span>
<span>🔷 MP <span id="heroMpBattle">20/20</span></span>
<span>💥 BP <span id="heroBpBattle">2</span></span>
</div>
<div class="stats-row">
<span>👾 <span id="monsterName">哥布林</span></span>
<span>❤️ HP <span id="monsterHpBattle">25/25</span></span>
</div>
<div>🛡️ 护盾:
<span id="shieldDisplay"></span>
</div>
<div class="weakness-hint" id="weaknessHint">弱点: 未知</div>
<div class="action-buttons" id="actionButtons">
<button class="action-btn" data-action="attack">⚡普通攻击</button>
<button class="action-btn" data-action="fire">🔥火球 (4MP)</button>
<button class="action-btn" data-action="ice">❄️冰刺 (4MP)</button>
<button class="action-btn" data-action="slash">⚔️斩击 (3MP)</button>
<button class="action-btn boost-btn" data-action="boost">💥增幅 (+1BP)</button>
</div>
<div class="battle-log" id="battleLog"></div>
</div>
</div>
</div>
<script>
(function() {
// ---------- 画布与视角 ----------
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
// 梯形视角参数:正对玩家,略微前倾
const GROUND_Y = 340; // 地面水平线
const TILE_SIZE = 28; // 基础网格
const MAP_COLS = 18;
const MAP_ROWS = 12;
// 地图数据 (0=草地, 1=石板路, 2=水池, 3=树木, 4=灌木, 5=旅馆)
const mapGrid = [];
function generateMap() {
for (let r = 0; r < MAP_ROWS; r++) {
mapGrid[r] = [];
for (let c = 0; c < MAP_COLS; c++) {
if (c < 3 || c >= MAP_COLS-3) mapGrid[r][c] = 3; // 两侧树林
else if (r === 5 && c >= 8 && c <= 10) mapGrid[r][c] = 5; // 旅馆
else if (r === 8 && c >= 7 && c <= 10) mapGrid[r][c] = 2; // 水池
else if ((r === 7 || r === 8) && (c === 6 || c === 11)) mapGrid[r][c] = 4;
else if (r >= 5 && r <= 9 && c >= 5 && c <= 12) mapGrid[r][c] = 1; // 广场
else mapGrid[r][c] = 0;
}
}
// 蜿蜒小路
for (let r = 1; r < 5; r++) { mapGrid[r][6] = 1; mapGrid[r][11] = 1; }
}
generateMap();
// 坐标转换(梯形映射,前倾)
function tileToScreen(col, row) {
const skew = (row / MAP_ROWS) * 0.25;
const x = 40 + col * TILE_SIZE + row * 8;
const y = GROUND_Y - row * (TILE_SIZE * 0.6) + col * 2;
return { x: Math.round(x), y: Math.round(y) };
}
// ---------- 主角 ----------
const hero = {
col: 6, row: 10,
hp: 30, maxHp: 30,
mp: 20, maxMp: 20,
bp: 2,
dir: 0, // 0=下, 1=左, 2=右, 3=上
frame: 0,
walkTimer: 0
};
// 主角绘制 (高度约10px, 宽4-6px)
function drawHero() {
const pos = tileToScreen(hero.col, hero.row);
const cx = pos.x;
const baseY = pos.y - 18;
ctx.save();
// 根据方向微调身体
const legOffset = hero.frame % 2 === 0 ? 0 : 2;
// 身体
ctx.fillStyle = '#3a6ea5';
ctx.fillRect(cx-2, baseY+4, 4, 5);
// 头
ctx.fillStyle = '#ffdbb4';
ctx.fillRect(cx-2, baseY, 4, 4);
// 眼睛
ctx.fillStyle = '#111';
if (hero.dir === 0) ctx.fillRect(cx, baseY+1, 1, 1);
else if (hero.dir === 3) ctx.fillRect(cx-1, baseY+1, 1, 1);
else ctx.fillRect(cx-1, baseY+1, 1, 1);
// 腿
ctx.fillStyle = '#2c3e50';
if (hero.dir === 0) {
ctx.fillRect(cx-2, baseY+9, 2, 3+legOffset);
ctx.fillRect(cx+1, baseY+9, 2, 3-legOffset);
} else if (hero.dir === 3) {
ctx.fillRect(cx-2, baseY+9, 2, 3-legOffset);
ctx.fillRect(cx+1, baseY+9, 2, 3+legOffset);
} else {
ctx.fillRect(cx-2, baseY+9, 2, 3);
ctx.fillRect(cx+1, baseY+9, 2, 3);
}
// 帽子/头发
ctx.fillStyle = '#5d3a1a';
ctx.fillRect(cx-2, baseY-1, 4, 2);
ctx.restore();
}
// ---------- 怪物 ----------
const monsters = [
{ col: 4, row: 7, type: 'slime', name: '史莱姆', hp: 15, maxHp: 15, shields: 2, maxShields: 2, weaknesses: ['fire','ice'], weaknessKnown: false },
{ col: 13, row: 6, type: 'goblin', name: '哥布林', hp: 22, maxHp: 22, shields: 3, maxShields: 3, weaknesses: ['slash','fire'], weaknessKnown: false },
{ col: 9, row: 2, type: 'bat', name: '暗影蝙蝠', hp: 18, maxHp: 18, shields: 2, maxShields: 2, weaknesses: ['ice','slash'], weaknessKnown: false },
];
function drawMonster(mon) {
const pos = tileToScreen(mon.col, mon.row);
const cx = pos.x;
const baseY = pos.y - 16;
ctx.save();
if (mon.type === 'slime') {
ctx.fillStyle = '#5daa3a';
ctx.beginPath(); ctx.ellipse(cx, baseY+8, 6, 5, 0, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = '#fff'; ctx.fillRect(cx-2, baseY+6, 2, 2); ctx.fillRect(cx+2, baseY+6, 2, 2);
} else if (mon.type === 'goblin') {
ctx.fillStyle = '#8b5a3c'; ctx.fillRect(cx-3, baseY+3, 6, 7);
ctx.fillStyle = '#c49a6c'; ctx.fillRect(cx-2, baseY, 5, 4);
ctx.fillStyle = '#c0392b'; ctx.fillRect(cx-1, baseY+1, 2, 1);
} else if (mon.type === 'bat') {
ctx.fillStyle = '#5d4a7a'; ctx.fillRect(cx-3, baseY+3, 6, 5);
ctx.fillStyle = '#7d6a9a'; ctx.fillRect(cx-4, baseY+1, 8, 3);
ctx.fillStyle = '#f00'; ctx.fillRect(cx-1, baseY+2, 2, 1);
}
ctx.restore();
}
// ---------- 战斗系统----------
let battleActive = false;
let currentMonster = null;
let battleLogMsg = '';
function openBattle(monster) {
currentMonster = monster;
battleActive = true;
battleLogMsg = '遭遇了 ' + monster.name + '!';
document.getElementById('battleOverlay').classList.add('active');
updateBattleUI();
}
function closeBattle() {
battleActive = false;
currentMonster = null;
document.getElementById('battleOverlay').classList.remove('active');
}
function updateBattleUI() {
if (!currentMonster) return;
document.getElementById('heroHpBattle').textContent = hero.hp + '/' + hero.maxHp;
document.getElementById('heroMpBattle').textContent = hero.mp + '/' + hero.maxMp;
document.getElementById('heroBpBattle').textContent = hero.bp;
document.getElementById('monsterName').textContent = currentMonster.name;
document.getElementById('monsterHpBattle').textContent = currentMonster.hp + '/' + currentMonster.maxHp;
let shieldHtml = '';
for (let i = 0; i < currentMonster.maxShields; i++) {
const broken = i >= currentMonster.shields;
shieldHtml += `<span class="shield-icon ${broken ? 'broken' : ''}"></span>`;
}
document.getElementById('shieldDisplay').innerHTML = shieldHtml;
const weaknessText = currentMonster.weaknessKnown ? '弱点: ' + currentMonster.weaknesses.join(', ') : '弱点: ???';
document.getElementById('weaknessHint').textContent = weaknessText;
document.getElementById('battleLog').textContent = battleLogMsg;
}
function playerAction(action) {
if (!battleActive || !currentMonster) return;
if (currentMonster.hp <= 0) { closeBattle(); return; }
let dmg = 0;
let hitWeakness = false;
let mpCost = 0;
let boostUsed = false;
if (action === 'boost') {
hero.bp = Math.min(5, hero.bp + 1);
battleLogMsg = 'BP 提升了!(当前BP: '+hero.bp+')';
updateBattleUI();
return;
}
// 消耗BP增幅伤害
const boostMult = hero.bp > 0 ? 1.8 : 1.0;
if (hero.bp > 0 && action !== 'attack') {
hero.bp--;
boostUsed = true;
}
if (action === 'attack') {
dmg = 5 + Math.floor(Math.random() * 4);
mpCost = 0;
} else if (action === 'fire') {
dmg = 8 + Math.floor(Math.random() * 5);
mpCost = 4;
if (currentMonster.weaknesses.includes('fire')) hitWeakness = true;
} else if (action === 'ice') {
dmg = 7 + Math.floor(Math.random() * 6);
mpCost = 4;
if (currentMonster.weaknesses.includes('ice')) hitWeakness = true;
} else if (action === 'slash') {
dmg = 9 + Math.floor(Math.random() * 3);
mpCost = 3;
if (currentMonster.weaknesses.includes('slash')) hitWeakness = true;
}
if (hero.mp < mpCost) {
battleLogMsg = 'MP 不足!';
updateBattleUI();
return;
}
hero.mp -= mpCost;
dmg = Math.floor(dmg * boostMult);
// 护盾与弱点
if (hitWeakness) {
currentMonster.shields--;
currentMonster.weaknessKnown = true;
battleLogMsg = '击中了弱点!护盾 -1';
if (currentMonster.shields <= 0) {
battleLogMsg = '怪物 BREAK!受到伤害翻倍!';
dmg = Math.floor(dmg * 2.0);
}
}
currentMonster.hp -= dmg;
battleLogMsg += ` 造成 ${dmg} 点伤害。`;
if (boostUsed) battleLogMsg += ' (增幅)';
if (currentMonster.hp <= 0) {
currentMonster.hp = 0;
battleLogMsg = '击败了 ' + currentMonster.name + '!';
updateBattleUI();
setTimeout(closeBattle, 1400);
return;
}
// 怪物反击(如果未被break)
if (currentMonster.shields > 0) {
const monsterDmg = 3 + Math.floor(Math.random() * 4);
hero.hp -= monsterDmg;
battleLogMsg += ` 怪物反击 ${monsterDmg} 点。`;
if (hero.hp <= 0) {
hero.hp = 0;
battleLogMsg = '你倒下了…… 游戏重新开始。';
updateBattleUI();
setTimeout(() => { closeBattle(); resetGame(); }, 1500);
return;
}
}
updateBattleUI();
}
// ---------- 主循环 ----------
function drawMap() {
ctx.fillStyle = '#2d4a1e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < MAP_ROWS; r++) {
for (let c = 0; c < MAP_COLS; c++) {
const { x, y } = tileToScreen(c, r);
const tile = mapGrid[r][c];
let color = '#3d6a2e';
if (tile === 1) color = '#9e8e7e';
else if (tile === 2) color = '#2980b9';
else if (tile === 3) color = '#1e4d2b';
else if (tile === 4) color = '#2a5a1e';
else if (tile === 5) color = '#6d5c4a';
ctx.fillStyle = color;
ctx.fillRect(x-6, y-10, 14, 16);
}
}
}
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawMap();
monsters.forEach(m => drawMonster(m));
drawHero();
// 走路动画计时
if (hero.walkTimer > 0) {
hero.walkTimer--;
if (hero.walkTimer % 6 === 0) hero.frame = (hero.frame + 1) % 2;
}
requestAnimationFrame(gameLoop);
}
// ---------- 移动与碰撞 ----------
function tryMove(dcol, drow, dir) {
if (battleActive) return;
const newCol = hero.col + dcol;
const newRow = hero.row + drow;
if (newCol < 0 || newCol >= MAP_COLS || newRow < 0 || newRow >= MAP_ROWS) return;
const tile = mapGrid[newRow][newCol];
if (tile === 3 || tile === 5) return; // 不可移动
hero.col = newCol;
hero.row = newRow;
hero.dir = dir;
hero.walkTimer = 12;
// 碰撞检测怪物
for (let mon of monsters) {
if (mon.col === hero.col && mon.row === hero.row) {
openBattle(mon);
break;
}
}
}
function resetGame() {
hero.col = 6; hero.row = 10; hero.hp = 30; hero.mp = 20; hero.bp = 2;
monsters.forEach(m => { m.hp = m.maxHp; m.shields = m.maxShields; m.weaknessKnown = false; });
closeBattle();
}
// ---------- 事件绑定 ----------
document.addEventListener('keydown', (e) => {
if (battleActive) return;
if (e.key === 'w' || e.key === 'ArrowUp') { e.preventDefault(); tryMove(0, -1, 3); }
else if (e.key === 's' || e.key === 'ArrowDown') { e.preventDefault(); tryMove(0, 1, 0); }
else if (e.key === 'a' || e.key === 'ArrowLeft') { e.preventDefault(); tryMove(-1, 0, 1); }
else if (e.key === 'd' || e.key === 'ArrowRight') { e.preventDefault(); tryMove(1, 0, 2); }
});
document.querySelectorAll('.dpad-btn').forEach(btn => {
btn.addEventListener('click', () => {
const dir = btn.dataset.dir;
if (dir === 'up') tryMove(0, -1, 3);
else if (dir === 'down') tryMove(0, 1, 0);
else if (dir === 'left') tryMove(-1, 0, 1);
else if (dir === 'right') tryMove(1, 0, 2);
});
});
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.dataset.action;
playerAction(action);
});
});
// 启动
gameLoop();
})();
</script>
</body>
</html>
它长这样。好消息,它动起来了。

有时候现实很骨感就是这个意思。所以说视频教程和图文也很好,至少你可以照着手把手做。
我决定先把这个丢到一边,反正地图已经有了,你们知道的,就是上上面那个。
接下来做游戏的其它部分。到时候找个什么人帮我粘贴在一起就可以了。
我:
先不管地图了。先直接给我一个与不同怪物进行战斗的窗口。有几个技能的释放选择,但可以一键自动攻击。
DS·思考中
我们需要生成一个专注于战斗窗口的代码,剔除地图探索部分,只保留战斗界面。用户要求:直接与不同怪物战斗,有技能选择,但可以一键自动攻击。根据历史,之前已实现过战斗弹出框。现在要一个独立的战斗窗口,可能作为一个完整页面或组件。应该包含以下元素:
- 怪物(随机或切换不同怪物)
- 玩家状态(HP、MP、BP)
- 技能按钮(普通攻击,以及几个技能如火球、冰刺、斩击等,消耗MP)
- 一键自动攻击按钮(比如自动选择最优技能或普通攻击)
- 怪物有护盾和弱点机制
- 战斗日志
用户说“先不管地图了”,所以只需一个战斗界面,可以独立运行。可能以HTML片段呈现…
后面的几个版本

加入自动战斗。

修改数值,加入包裹。

加入装备栏

添加 杨子藏的念力伙伴,白影念龙。
在这个过程中,我已经跑到之前的那个AI里,去将我的地图进行了修改,
这次成功了!就是这个味道。

不过….其实到现在我也没用过这个地图。
不过AI还是非常热情的为我提供了后续的可能,建议我为楼房设置电梯,层数等等各种功能。我用了下,对于现在的AI来说,已经很不错了。虽然都点不进去,大概是链接后面的内容没有做。

(摊手)
鉴于这个AI到这一步,已经好几次出现点不动的情况,我还是回到了能给我作出战斗面板,并且就算单独玩起来也能够比较有趣的玩几分钟的Deepseek这里。
结合上面第一次做好的地图,以及这个在隔壁做出来的长条状地图,我交给了它,并认真地嘱咐了该如何做,就是人物移动,与怪物相遇后进行战斗。
然后它在一次说因为代码太长,给了我一个运行起来黑屏的简易框架,被我要求重做后,这次终于做出来了。

并且给我添加了我没有需要的方向键。
到此我心情非常棒,虽然依旧很简陋,怪物一共才七个,还有一个红色BOSS疑似是被旁边怪物击杀后倒地的贴图,但至少它出现了,并且我点了鼠标后,主角还在动…
等等
角色一直在往下面动。
我点了好多次鼠标,最终唉地一叹,果然没有任何换方向的迹象。它只能往下走。
但能做到这里,已经很棒了。
我把这个由主角没办法自由移动的角色和图,连同之前DS做的只有对战界面的游戏,再次丢给了另外那个AI,让它试着纠正并组合。然后它分析好后,给我返回了移动效果一模一样的东西——依旧没办法自由移动。
所以我差不多确定,当前它们没办法解决很快这个东西。
唉…我失望地点点点,等下,好像在某次过后,鼠标的点动让角色出现了非常笨重的缓慢转弯。
我立即把代码复制到新窗口。
打算继续调整,并想了下这种移动之前都还好好的,不应该这么困难。
呃,会不会是之前要求的45°角度导致的?
在这个窗口第二次聊天的时候,我告诉了它这个问题。
……
以下是分享自跟DS的该窗口聊天记录。
https://chat.deepseek.com/share/c5t4w36e3m5cdd83mk
它的返回没有纠正,于是在第三次的时候,我换了个更精确一些的说法。而这一次,它终于成功了。
也就是这一瞬间,我知道我想要的那个游戏是可以被现在的AI所作出来的了。因为,到目前为止,我一共花了不到三个小时。
现在只需要添加功能就可以。
我把它拿去给了隔壁智能体并提出了具体要求…
返回的东西一言难尽。我不想放画面。大约是,中间一个没办法移动的黄点,然后其它满是数字的红点靠近…但那个代码超级复杂,我不知道是怎么回事,可能是某种专家才看得懂的测试,因为我试过了两外两个有智能体的AI,除了其中一个因为代码太长给我停掉了窗口,另外一个居然在运算后返回了一模一样的粉色小点往中间靠的东西。
好了,我又拿着它找到了DS。
为了给它省token,我开了个新窗口。然后,开始了新功能的添加与调整:
https://chat.deepseek.com/share/6chpv81i8mzcngfy26
重开了个窗口,不断地在能运行、不能运行、流畅打怪怪物死后还一直攻击,每击一次都掉装备,以及完美出现,但点击一下瞬移到屏幕右边之间进行修改,但最终都只是数字在脑袋里飘过的感觉。
https://chat.deepseek.com/share/5qlcpu33s9mjphfm59
后面的其它修改与发布也差不多。值得一提的是WORDPRESS的默认编辑器里html没办法完美的将它发布。因此这个游戏页面我用了Elementor网站主题编辑器里的html功能。然后又是几次好几次调试,它终于能够稍微能挂机很久的玩了。
但是,游戏还是有些小问题,比如页面无法滚动的问题。
以下是第二天上午,我和AI做的第一个网页游戏链接,到此为止,用时不到二十四小时:
杨子藏的首秀之夜 – ESSENCE POLARITY DOSSIER
但后面还是修改了差不多又这么多时间。
这是最后的完整代码,我有在晚上将它翻译为其它两三个语言:
目前最终版·中文html代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>废 都 4 1· 首 秀 之 夜</title>
<style>
:root {
--game-bg: #0a0a0f;
--text-muted: #7a8aaa;
--border-color: #2a2a3a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
/* 关键修复:body允许滚动,移除overflow:hidden */
body {
background: #0a0a0f;
font-family: 'Courier New', monospace;
min-height: 100vh;
/* 允许页面滚动 */
overflow-y: auto;
overflow-x: hidden;
/* 给页面一些额外空间以便测试滚动 */
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
/* 确保在Elementor中也能正常显示 */
position: relative;
width: 100%;
}
/* 游戏整体容器 - 保持固定布局 */
.game-outer-container {
width: 100%;
max-width: 900px;
display: flex;
flex-direction: column;
align-items: center;
/* 关键:给容器一个稳定的最小高度,防止Elementor压缩 */
min-height: 600px;
/* 确保容器在Elementor中不被裁剪 */
overflow: visible;
position: relative;
z-index: 1;
flex-shrink: 0;
}
.map-title {
color: #7a8aaa;
text-align: center;
margin-bottom: 5px;
font-size: 18px;
letter-spacing: 5px;
text-shadow: 0 0 12px rgba(122, 138, 170, 0.5);
flex-shrink: 0;
}
.status-bar {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 6px;
padding: 6px 12px;
background: rgba(10, 10, 18, 0.85);
border: 2px solid #3a3a4a;
border-radius: 6px;
color: #ccc;
font-size: 11px;
letter-spacing: 1px;
flex-wrap: wrap;
align-items: center;
flex-shrink: 0;
width: 100%;
max-width: 880px;
}
.status-item {
display: flex;
align-items: center;
gap: 3px;
}
.status-label {
color: #7a8aaa;
font-size: 9px;
}
.status-value {
font-weight: bold;
font-size: 11px;
}
.chrono-value {
color: #ffd700;
text-shadow: 0 0 8px rgba(255, 215, 0, 0.5);
}
.bank-value {
color: #7ec8e3;
text-shadow: 0 0 8px rgba(126, 200, 227, 0.4);
}
.hp-value {
color: #ff6b6b;
text-shadow: 0 0 8px rgba(255, 107, 107, 0.5);
}
.frag-value {
color: #f39c12;
text-shadow: 0 0 8px rgba(243, 156, 18, 0.5);
}
.level-value {
color: #a78bfa;
text-shadow: 0 0 8px rgba(167, 139, 250, 0.5);
}
.skill-cooldown {
color: #5dade2;
font-size: 11px;
margin-left: 4px;
}
.game-wrapper {
position: relative;
width: 100%;
max-width: 880px;
display: flex;
align-items: flex-start;
justify-content: center;
flex-shrink: 0;
/* 确保在Elementor中不被压缩 */
min-height: 500px;
overflow: visible;
}
.dpad-left {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
margin-right: 6px;
flex-shrink: 0;
position: relative;
z-index: 10;
}
.dpad-row {
display: flex;
gap: 6px;
}
.dpad-btn {
width: 36px;
height: 36px;
background: rgba(20, 20, 35, 0.85);
border: 2px solid #5a6a8a;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #c0d0f0;
cursor: pointer;
transition: all 0.15s;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6);
/* 关键:防止按钮触发页面滚动 */
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
}
.dpad-btn:active {
background: #3a4a6a;
border-color: #a0b8ff;
transform: scale(0.9);
color: #fff;
}
.map-container {
position: relative;
flex-grow: 1;
/* 确保容器可见 */
overflow: visible;
min-width: 200px;
min-height: 300px;
}
canvas {
display: block;
/* 使用固定宽高比,但允许响应式缩放 */
width: 100%;
max-width: 680px;
height: auto;
aspect-ratio: 680 / 480;
border: 3px solid #2a2a3a;
border-radius: 6px;
background: #0a0a0f;
box-shadow: 0 0 40px rgba(0, 0, 0, 0.8);
image-rendering: crisp-edges;
image-rendering: pixelated;
/* 关键:防止canvas上的操作触发页面滚动 */
touch-action: none;
-webkit-user-select: none;
user-select: none;
/* 确保canvas始终可见 */
visibility: visible;
opacity: 1;
}
.battle-ui {
position: absolute;
top: 8px;
right: 8px;
width: 220px;
background: rgba(10, 10, 15, 0.9);
border: 2px solid #ff4444;
border-radius: 8px;
padding: 8px;
color: #fff;
display: none;
pointer-events: none;
z-index: 20;
font-size: 11px;
}
.battle-ui.active {
display: block;
}
.battle-title {
font-size: 13px;
font-weight: bold;
color: #ff4444;
margin-bottom: 4px;
text-align: center;
text-shadow: 0 0 8px rgba(255, 68, 68, 0.8);
}
.hp-bar-container {
width: 100%;
height: 18px;
background: #1a1a1a;
border: 2px solid #444;
border-radius: 4px;
overflow: hidden;
margin-bottom: 4px;
position: relative;
}
.hp-bar {
height: 100%;
background: linear-gradient(90deg, #ff4444, #ff8844);
transition: width 0.3s;
}
.hp-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 10px;
font-weight: bold;
text-shadow: 1px 1px 2px #000;
}
.enemy-info {
font-size: 10px;
color: #aaa;
margin-bottom: 3px;
}
.damage-log {
max-height: 60px;
overflow-y: auto;
font-size: 9px;
color: #ffaa00;
margin-top: 3px;
}
.damage-log div {
margin: 1px 0;
padding: 1px 3px;
background: rgba(255, 170, 0, 0.1);
border-left: 2px solid #ffaa00;
}
.damage-log .chrono-log {
color: #ffd700;
border-left-color: #ffd700;
background: rgba(255, 215, 0, 0.1);
}
.control-hint {
margin-top: 4px;
color: #6a7a9a;
font-size: 9px;
animation: pulse 2s infinite;
text-align: center;
flex-shrink: 0;
}
@keyframes pulse {
0%,
100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
.right-panel {
display: flex;
flex-direction: column;
gap: 3px;
margin-left: 6px;
flex-shrink: 0;
width: 56px;
position: relative;
z-index: 10;
}
.equip-mini {
background: rgba(15, 15, 25, 0.9);
border: 1px solid #3a3a4a;
border-radius: 6px;
padding: 2px;
display: flex;
flex-direction: column;
gap: 1px;
}
.equip-mini-slot {
display: flex;
align-items: center;
gap: 2px;
padding: 2px 3px;
border-radius: 3px;
background: rgba(20, 20, 35, 0.6);
min-height: 18px;
}
.equip-mini-slot .eq-icon {
font-size: 11px;
}
.equip-mini-slot .eq-name {
font-size: 7px;
line-height: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 34px;
}
.equip-mini-slot.empty {
opacity: 0.3;
}
.side-btn {
width: 56px;
height: 30px;
background: rgba(20, 20, 35, 0.85);
border: 2px solid #5a6a8a;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
cursor: pointer;
transition: all 0.15s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
font-size: 9px;
color: #8a9aba;
}
.side-btn:active {
background: #3a4a6a;
border-color: #a0b8ff;
transform: scale(0.9);
}
.side-btn .icon {
font-size: 13px;
}
.side-btn.auto-on {
border-color: #2ecc71;
color: #2ecc71;
box-shadow: 0 0 10px rgba(46, 204, 113, 0.4);
animation: btnGlow 1.5s infinite;
}
.side-btn.recycle-on {
border-color: #e67e22;
color: #e67e22;
box-shadow: 0 0 8px rgba(230, 126, 34, 0.4);
}
.side-btn.ma-chao-glow {
border-color: #FFD700;
color: #FFD700;
box-shadow: 0 0 14px rgba(255, 215, 0, 0.6);
animation: maChaoGlow 1.2s infinite;
}
@keyframes btnGlow {
0%,
100% {
box-shadow: 0 0 6px rgba(46, 204, 113, 0.3);
}
50% {
box-shadow: 0 0 16px rgba(46, 204, 113, 0.7);
}
}
@keyframes maChaoGlow {
0%,
100% {
box-shadow: 0 0 8px rgba(255, 215, 0, 0.4);
}
50% {
box-shadow: 0 0 22px rgba(255, 215, 0, 0.9);
}
}
.panel-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 200;
display: none;
align-items: center;
justify-content: center;
}
.panel-overlay.active {
display: flex;
}
.panel {
background: #111122;
border: 2px solid #4a4a6a;
border-radius: 12px;
padding: 14px;
max-width: 420px;
width: 92%;
max-height: 82vh;
overflow-y: auto;
color: #ccc;
position: relative;
}
.panel-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
border-bottom: 1px solid #3a3a4a;
padding-bottom: 6px;
position: sticky;
top: 0;
background: #111122;
z-index: 5;
}
.panel-title {
font-size: 15px;
font-weight: bold;
color: #a0b8ff;
margin: 0;
}
.panel-close-x {
width: 32px;
height: 32px;
background: rgba(255, 68, 68, 0.2);
border: 2px solid #ff4444;
border-radius: 50%;
color: #ff4444;
font-size: 18px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
flex-shrink: 0;
line-height: 1;
}
.panel-close-x:hover {
background: rgba(255, 68, 68, 0.5);
color: #fff;
border-color: #fff;
box-shadow: 0 0 14px rgba(255, 68, 68, 0.6);
}
.bag-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 4px;
margin-bottom: 10px;
}
.bag-slot {
aspect-ratio: 1;
background: #1a1a2e;
border: 2px solid #3a3a4a;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
font-size: 9px;
transition: all 0.15s;
min-width: 0;
}
.bag-slot:hover,
.bag-slot.selected {
border-color: #a0b8ff;
background: #2a2a3e;
}
.bag-slot .item-icon {
font-size: 18px;
}
.bag-slot .item-name {
font-size: 6px;
color: #aaa;
text-align: center;
line-height: 1.1;
margin-top: 1px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bag-slot.ma-chao-item {
border-color: #FFD700 !important;
box-shadow: 0 0 8px rgba(255, 215, 0, 0.35);
background: #1a1a0a;
}
.item-detail {
background: #1a1a2e;
border: 1px solid #3a3a4a;
border-radius: 8px;
padding: 8px;
margin-bottom: 8px;
z-index: 4;
}
.item-detail .detail-name {
font-weight: bold;
margin-bottom: 3px;
font-size: 12px;
}
.item-detail .detail-stats {
font-size: 10px;
color: #aaa;
margin-bottom: 5px;
}
.item-detail .detail-actions {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.detail-btn {
padding: 4px 10px;
border: 1px solid #5a6a8a;
border-radius: 4px;
background: #2a2a3e;
color: #ccc;
cursor: pointer;
font-size: 10px;
transition: all 0.15s;
}
.detail-btn:hover {
background: #3a4a6a;
border-color: #a0b8ff;
}
.detail-btn.recycle {
border-color: #e67e22;
color: #e67e22;
}
.detail-btn.equip {
border-color: #2ecc71;
color: #2ecc71;
}
.detail-btn.use {
border-color: #3498db;
color: #3498db;
}
.detail-btn.synth {
border-color: #9b59b6;
color: #9b59b6;
}
.detail-btn.artifact {
border-color: #ffd700;
color: #ffd700;
animation: btnGlow 1.5s infinite;
}
.auto-recycle-info {
background: rgba(230, 126, 34, 0.15);
border: 1px solid #e67e22;
border-radius: 6px;
padding: 6px 10px;
margin-bottom: 8px;
font-size: 10px;
text-align: center;
color: #e67e22;
}
.auto-recycle-info.owned {
border-color: #2ecc71;
color: #2ecc71;
background: rgba(46, 204, 113, 0.1);
}
.bag-top-section {
margin-bottom: 8px;
}
.equip-slots {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 6px;
margin-bottom: 10px;
}
.equip-slot {
background: #1a1a2e;
border: 2px solid #3a3a4a;
border-radius: 8px;
padding: 6px;
display: flex;
align-items: center;
gap: 6px;
min-height: 44px;
font-size: 11px;
cursor: pointer;
}
.equip-slot .slot-icon {
font-size: 20px;
}
.equip-slot .slot-info {
flex: 1;
}
.equip-slot .slot-label {
font-size: 8px;
color: #7a8aaa;
}
.equip-slot .slot-name {
font-size: 10px;
font-weight: bold;
}
.equip-slot.ma-chao-equipped {
border-color: #FFD700 !important;
box-shadow: 0 0 10px rgba(255, 215, 0, 0.4);
background: #1a1a10;
}
.painting-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.92);
z-index: 300;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
}
.painting-modal.active {
display: flex;
}
.painting-modal img {
max-width: 90%;
max-height: 60vh;
border-radius: 12px;
border: 3px solid #9b59b6;
box-shadow: 0 0 40px rgba(155, 89, 182, 0.4);
animation: paintingReveal 1s ease-out;
}
@keyframes paintingReveal {
0% {
transform: scale(0.3);
opacity: 0;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.painting-modal .painting-title {
color: #9b59b6;
font-size: 18px;
font-weight: bold;
margin: 12px 0;
text-shadow: 0 0 20px rgba(155, 89, 182, 0.5);
animation: fadeInUp 0.8s ease-out 0.3s both;
}
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.painting-modal .close-btn {
padding: 6px 20px;
border: 2px solid #9b59b6;
border-radius: 6px;
background: transparent;
color: #9b59b6;
cursor: pointer;
font-size: 13px;
margin-top: 8px;
animation: fadeInUp 0.8s ease-out 0.5s both;
}
.painting-modal .close-btn:hover {
background: rgba(155, 89, 182, 0.2);
}
.pickup-hint {
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
border: 1px solid #f39c12;
border-radius: 4px;
padding: 3px 10px;
color: #f39c12;
font-size: 11px;
display: none;
z-index: 15;
pointer-events: none;
white-space: nowrap;
}
.pickup-hint.active {
display: block;
}
.sticky-bottom-bar {
position: sticky;
bottom: 0;
background: #111122;
padding-top: 8px;
border-top: 1px solid #3a3a4a;
z-index: 5;
margin-top: 8px;
}
.ma-chao-set-bonus {
display: none;
text-align: center;
padding: 6px;
background: rgba(255, 215, 0, 0.15);
border: 1px solid #FFD700;
border-radius: 6px;
margin-bottom: 8px;
animation: maChaoGlow 1.5s infinite;
}
.ma-chao-set-bonus.active {
display: block;
}
.recycle-all-btn {
background: #e67e22;
border: 2px solid #f39c12;
color: #fff;
font-weight: bold;
padding: 6px 14px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
}
.recycle-all-btn:hover {
background: #f39c12;
box-shadow: 0 0 12px rgba(243, 156, 18, 0.5);
}
.recycle-options {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
flex-wrap: wrap;
font-size: 10px;
}
.recycle-options label {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
color: #ccc;
}
.recycle-options input[type="checkbox"] {
cursor: pointer;
}
/* Elementor兼容性修复 */
.elementor-widget-container .game-outer-container,
.elementor-widget-wrap .game-outer-container,
.elementor-widget .game-outer-container {
max-width: 100% !important;
overflow: visible !important;
}
/* 确保在Elementor中canvas不会被隐藏 */
.elementor-widget-container canvas,
.elementor-widget-wrap canvas {
visibility: visible !important;
opacity: 1 !important;
display: block !important;
}
/* 响应式调整 */
@media screen and (max-width: 768px) {
.game-wrapper {
flex-wrap: nowrap;
max-width: 100%;
}
.dpad-left {
margin-right: 3px;
}
.right-panel {
margin-left: 3px;
width: 44px;
}
.side-btn {
width: 44px;
height: 26px;
font-size: 7px;
}
.dpad-btn {
width: 28px;
height: 28px;
font-size: 16px;
}
canvas {
max-width: 100%;
border-width: 2px;
}
.battle-ui {
width: 160px;
font-size: 9px;
right: 4px;
top: 4px;
}
}
</style>
</head>
<body>
<!-- 外层容器,确保在Elementor中也能正常渲染 -->
<div class="game-outer-container">
<div class="map-title">◆ 废 都 4 1· 首 秀 之 夜 ◆</div>
<div class="status-bar">
<div class="status-item"><span class="status-label">❤️</span><span class="status-value hp-value" id="playerHpDisplay">100/100</span></div>
<div class="status-item"><span class="status-label">⚡Lv</span><span class="status-value level-value" id="levelDisplay">1</span></div>
<div class="status-item"><span class="status-label">⚔️ATK</span><span class="status-value" id="atkDisplay" style="color:#ff8844">20</span></div>
<div class="status-item"><span class="status-label">🛡️DEF</span><span class="status-value" id="defDisplay" style="color:#88aaff">0</span></div>
<div class="status-item"><span class="status-label">⏳时序</span><span class="status-value chrono-value" id="chronoDisplay">0 N</span></div>
<div class="status-item"><span class="status-label">💎转给队长</span><span class="status-value bank-value" id="bankDisplay">0 N</span></div>
<div class="status-item"><span class="status-label">🔮碎片</span><span class="status-value frag-value" id="fragDisplay">0/1000</span></div>
<div class="status-item" id="skillStatus">✨时序攻击! <span class="skill-cooldown" id="skillCd">就绪</span></div>
</div>
<div class="game-wrapper">
<div class="dpad-left">
<div class="dpad-row"><div class="dpad-btn" id="btnUp">▲</div></div>
<div class="dpad-row">
<div class="dpad-btn" id="btnLeft">◀</div>
<div class="dpad-btn" id="btnDown">▼</div>
<div class="dpad-btn" id="btnRight">▶</div>
</div>
</div>
<div class="map-container" id="mapContainer">
<canvas id="mapCanvas"></canvas>
<div class="battle-ui" id="battleUI">
<div class="battle-title">⚔️ 杨子藏攻击中 ⚔️</div>
<div class="enemy-info" id="enemyName">敌人: -</div>
<div class="hp-bar-container">
<div class="hp-bar" id="hpBar" style="width:100%"></div>
<div class="hp-text" id="hpText">100/100</div>
</div>
<div class="damage-log" id="damageLog"></div>
</div>
<div class="pickup-hint" id="pickupHint">靠近自动拾取</div>
</div>
<div class="right-panel">
<div class="equip-mini" id="equipMini">
<div class="equip-mini-slot empty" id="eqMini_weapon"><span class="eq-icon">🗡️</span><span class="eq-name">空</span></div>
<div class="equip-mini-slot empty" id="eqMini_armor"><span class="eq-icon">🛡️</span><span class="eq-name">空</span></div>
<div class="equip-mini-slot empty" id="eqMini_helmet"><span class="eq-icon">⛑️</span><span class="eq-name">空</span></div>
<div class="equip-mini-slot empty" id="eqMini_boots"><span class="eq-icon">👢</span><span class="eq-name">空</span></div>
<div class="equip-mini-slot empty" id="eqMini_accessory"><span class="eq-icon">💍</span><span class="eq-name">空</span></div>
</div>
<div class="side-btn" id="btnBag"><span class="icon">🎒</span>B</div>
<div class="side-btn" id="btnEquip"><span class="icon">🛡️</span>E</div>
<div class="side-btn" id="btnHeal"><span class="icon">❤️</span>H</div>
<div class="side-btn" id="btnAutoFarm" title="自动挂机"><span class="icon">⚡</span>挂机</div>
<div class="side-btn" id="btnAutoRecycle" title="自动回收" style="display:none;"><span class="icon">♻️</span>回收</div>
<div class="side-btn" id="btnSkill" title="时序攻击 (Q)"><span class="icon">✨</span>时序攻击</div>
</div>
</div>
<div class="control-hint">🖱️ 点击移动 | WASD/方向键 | Q 时序攻击 | 靠近掉落物自动拾取 | B背包 E装备 H治疗</div>
</div>
<div class="panel-overlay" id="bagPanel">
<div class="panel">
<div class="panel-title-row">
<span class="panel-title">🎒 背 包 <span id="bagCount">0</span>/<span id="bagMax">100</span></span>
<button class="panel-close-x" onclick="closeBagPanel()" title="关闭背包">✖</button>
</div>
<div class="recycle-options">
<button class="recycle-all-btn" onclick="oneClickRecycleAll()">♻️ 一键回收全部</button>
<label><input type="checkbox" id="recycleLegendary" checked> 回收传说装备</label>
<label><input type="checkbox" id="recycleMaChao"> 回收马超装备</label>
</div>
<div class="bag-top-section" id="bagTopSection">
<div id="autoRecycleStatus" style="display:none;"></div>
<div id="itemDetail" style="display:none"></div>
</div>
<div class="bag-grid" id="bagGrid"></div>
<div class="sticky-bottom-bar" style="text-align:center;">
<span style="font-size:10px;color:#f39c12;">🔮 完整碎片: <span id="fragCount2">0</span>/1000</span>
<button class="detail-btn synth" id="btnSynth" style="margin-left:8px;display:none;">合成名画</button>
<button class="detail-btn" onclick="closeBagPanel()" style="margin-left:8px;">关闭 [B]</button>
</div>
</div>
</div>
<div class="panel-overlay" id="equipPanel">
<div class="panel">
<div class="panel-title-row">
<span class="panel-title">⚔️ 装 备</span>
<button class="panel-close-x" onclick="closeEquipPanel()" title="关闭装备">✖</button>
</div>
<div id="equipStatsDisplay" style="font-size:10px;color:#aaa;margin-bottom:8px;text-align:center;"></div>
<div class="equip-slots" id="equipSlots"></div>
<div id="setBonusDisplay" style="display:none;text-align:center;padding:6px;background:rgba(243,156,18,0.15);border:1px solid #f39c12;border-radius:6px;margin-bottom:8px;">
<div style="color:#f39c12;font-weight:bold;font-size:12px;">✨ 首秀之夜套装效果</div>
<div style="font-size:10px;color:#e8c860;">自动挂机增强 | ATK+50 DEF+30 HP+500 | 攻速+40%</div>
</div>
<div id="maChaoSetBonusDisplay" class="ma-chao-set-bonus">
<div style="color:#FFD700;font-weight:bold;font-size:13px;">🐴 马超套装效果</div>
<div style="font-size:10px;color:#ffeaa0;">自动挂机增强 | ATK+100 DEF+60 HP+1000 | 攻速+80% | 寻敌范围大幅提升</div>
</div>
<div class="sticky-bottom-bar" style="text-align:center;">
<button class="detail-btn" onclick="closeEquipPanel()">关闭 [E]</button>
</div>
</div>
</div>
<div class="painting-modal" id="paintingModal">
<img id="paintingImg" src="" alt="名画" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22600%22 height=%22400%22><rect fill=%22%231a1a2e%22 width=%22600%22 height=%22400%22/><text fill=%22%23ffd700%22 font-size=%2230%22 text-anchor=%22middle%22 x=%22300%22 y=%22180%22>CCSIT.CC 名画</text><text fill=%22%23ffd700%22 font-size=%2224%22 text-anchor=%22middle%22 x=%22300%22 y=%22220%22>站长的微笑 2.2</text><text fill=%22%239b59b6%22 font-size=%2216%22 text-anchor=%22middle%22 x=%22300%22 y=%22260%22>请替换图片链接</text></svg>';">
<div class="painting-title" id="paintingTitle"></div>
<button class="close-btn" onclick="closePainting()">关 闭</button>
</div>
<script>
(function() {
// ==================== 安全代码:全局错误处理 ====================
window.addEventListener('error', function(e) {
console.warn('[游戏安全] 全局错误捕获:', e.message, '来源:', e.filename, '行:', e.lineno);
if (e.target && e.target.tagName === 'IMG') {
console.warn('[游戏安全] 图片加载失败,已忽略');
}
});
window.addEventListener('unhandledrejection', function(e) {
console.warn('[游戏安全] 未处理的Promise拒绝:', e.reason);
});
// ==================== 安全代码:localStorage安全包装 ====================
function safeLocalStorageSet(key, value) {
try {
const serialized = JSON.stringify(value);
if (serialized.length > 4000000) {
console.warn('[游戏安全] 存档数据过大(' + serialized.length + '字节),尝试压缩...');
}
localStorage.setItem(key, serialized);
return true;
} catch (e) {
console.error('[游戏安全] 存档失败:', e.message);
if (e.name === 'QuotaExceededError' || e.message.includes('quota')) {
console.warn('[游戏安全] localStorage配额不足,尝试清理旧数据...');
try {
const keys = Object.keys(localStorage).filter(k => k.startsWith('abandonedCity'));
for (let i = 0; i < keys.length - 1; i++) {
localStorage.removeItem(keys[i]);
}
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e2) {
console.error('[游戏安全] 清理后仍无法保存');
}
}
return false;
}
}
function safeLocalStorageGet(key, fallback) {
try {
const d = localStorage.getItem(key);
if (d === null) return fallback;
return JSON.parse(d);
} catch (e) {
console.warn('[游戏安全] 读档失败:', e.message);
return fallback;
}
}
const canvas = document.getElementById('mapCanvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
const TILE_SIZE = 14;
const MAP_COLS = 150;
const MAP_ROWS = 140;
const VIEW_W = 680;
const VIEW_H = 480;
canvas.width = VIEW_W;
canvas.height = VIEW_H;
// ==================== 关键修复:响应式Canvas尺寸管理 ====================
function updateCanvasDisplaySize() {
const container = canvas.parentElement;
if (!container) return;
const containerWidth = container.clientWidth;
// 限制最大宽度为VIEW_W,但允许在窄屏幕上缩小
const displayWidth = Math.min(containerWidth - 4, VIEW_W);
const scale = displayWidth / VIEW_W;
const displayHeight = VIEW_H * scale;
canvas.style.width = displayWidth + 'px';
canvas.style.height = displayHeight + 'px';
// 更新map-container的最小高度
const mapContainer = document.getElementById('mapContainer');
if (mapContainer) {
mapContainer.style.minHeight = displayHeight + 'px';
}
}
// 初始设置
updateCanvasDisplaySize();
// 监听窗口大小变化
window.addEventListener('resize', () => {
updateCanvasDisplaySize();
});
// 使用ResizeObserver监听容器大小变化(Elementor可能会动态改变容器大小)
if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(() => {
updateCanvasDisplaySize();
});
const mapContainer = document.getElementById('mapContainer');
if (mapContainer) {
resizeObserver.observe(mapContainer);
}
// 也观察canvas的父元素
if (canvas.parentElement) {
resizeObserver.observe(canvas.parentElement);
}
}
// 监听滚动事件,确保canvas在滚动后仍然正确渲染
let scrollTimeout;
window.addEventListener('scroll', () => {
// 使用防抖,避免频繁触发
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
updateCanvasDisplaySize();
// 强制重绘一帧
if (gameLoopRunning) {
ctx.clearRect(0, 0, VIEW_W, VIEW_H);
drawMap();
drawGroundLoot(ctx);
enemies.forEach(e => { if (e.alive) e.draw(ctx); });
skillEffects.forEach(f => f.draw(ctx));
drawGoldenJar(ctx);
floatingTexts.forEach(f => f.draw(ctx));
drawPlayer(ctx);
drawMist();
}
}, 150);
}, { passive: true });
// 监听visibility变化,确保页面重新可见时canvas恢复渲染
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
updateCanvasDisplaySize();
lastFrameTime = performance.now();
}
});
const WORLD_W = MAP_COLS * TILE_SIZE;
const WORLD_H = MAP_ROWS * TILE_SIZE;
let cameraX = 0,
cameraY = 0;
let gameFrame = 0;
let floatingTexts = [];
let skillEffects = [];
let battleLog = [];
let currentBattleEnemy = null;
let lastSkillTime = -9999;
const SKILL_COOLDOWN = 300;
let farmPatrolTarget = null;
let gameLoopRunning = true;
let lastFrameTime = performance.now();
let frameCountForFPS = 0;
let fpsCheckTimer = 0;
const MAX_FLOATING_TEXTS = 80;
const MAX_SKILL_EFFECTS = 15;
const MAX_GROUND_LOOT = 200;
const MAX_BATTLE_LOG = 8;
const CLEANUP_INTERVAL = 1800;
// ==================== 存档 ====================
const SAVE_KEY = 'abandonedCitySave_v8';
function getDefaultState() {
return {
playerRow: 75,
playerCol: 70,
hp: 100,
maxHp: 100,
atk: 20,
def: 0,
level: 1,
exp: 0,
expToNext: 50,
chronoPoints: 0,
lifeBank: 0,
fragments: 0,
equipment: { weapon: null, armor: null, helmet: null, boots: null, accessory: null },
bag: [],
defeatedBosses: [],
totalKills: 0,
painting221Obtained: false,
goldenJar: null,
autoFarmEnabled: false,
autoRecycleArtifact: false,
autoRecycleEnabled: true,
hasCentennialWeapon: false
};
}
let state = getDefaultState();
function saveGame() {
try {
const success = safeLocalStorageSet(SAVE_KEY, state);
if (!success) {
console.warn('[游戏安全] 保存未成功,将在下次循环重试');
}
} catch (e) {
console.error('[游戏安全] saveGame异常:', e.message);
}
}
function loadGame() {
try {
const defaults = getDefaultState();
const loaded = safeLocalStorageGet(SAVE_KEY, null);
if (loaded) {
state = Object.assign(defaults, loaded);
if (!Array.isArray(state.bag)) state.bag = [];
if (!Array.isArray(state.defeatedBosses)) state.defeatedBosses = [];
if (!state.equipment) state.equipment = { weapon: null, armor: null, helmet: null, boots: null,
accessory: null };
if (state.autoRecycleArtifact === undefined) state.autoRecycleArtifact = false;
if (state.autoRecycleEnabled === undefined) state.autoRecycleEnabled = true;
if (state.autoFarmEnabled === undefined) state.autoFarmEnabled = false;
if (state.hasCentennialWeapon === undefined) state.hasCentennialWeapon = false;
return true;
}
} catch (e) {
console.error('[游戏安全] loadGame异常:', e.message);
}
return false;
}
loadGame();
function grantCentennialWeapon() {
if (state.hasCentennialWeapon) return;
const weapon = {
type: 'equipment',
slot: 'weapon',
name: '百年器',
rarity: '传说',
icon: '🗡️',
stats: { atk: 0 },
isSet: false,
special: 'centennial'
};
if (!state.equipment.weapon) {
state.equipment.weapon = weapon;
} else {
if (state.bag.length < 100) state.bag.push(weapon);
}
state.hasCentennialWeapon = true;
saveGame();
}
if (!state.hasCentennialWeapon) grantCentennialWeapon();
// ==================== 地形 ====================
const TERRAIN = {
ASPHALT: 0,
CRACKED_ROAD: 1,
RUBBLE: 2,
CONCRETE: 3,
DEBRIS: 4,
CAR_WRECK: 5,
BUILDING_RUIN: 6,
SKYSCRAPER: 7,
SKYSCRAPER_WINDOW: 8,
SKYSCRAPER_DOOR: 9,
STREET_LAMP: 10,
WATER_PUDDLE: 11,
BARBED_WIRE: 12,
GRAVEYARD: 13,
PARKING_LOT: 14,
FOUNTAIN_DRY: 15
};
const mapData = [];
function generateMap() {
for (let row = 0; row < MAP_ROWS; row++) {
mapData[row] = [];
for (let col = 0; col < MAP_COLS; col++) mapData[row][col] = TERRAIN.ASPHALT;
}
for (let r = 28; r < 32; r++)
for (let c = 0; c < MAP_COLS; c++)
if (mapData[r][c] === 0) mapData[r][c] = 1;
for (let r = 0; r < MAP_ROWS; r++) {
for (let c = 18; c < 22; c++)
if (mapData[r][c] === 0) mapData[r][c] = 1;
for (let c = 48; c < 52; c++)
if (mapData[r][c] === 0) mapData[r][c] = 1;
}
for (let r = 50; r < 62; r++)
for (let c = 8; c < 20; c++)
if (c >= 0 && c < MAP_COLS && r >= 0 && r < MAP_ROWS) mapData[r][c] = 14;
[
[2, 8, 5, 15],
[2, 8, 55, 65],
[55, 62, 5, 15],
[55, 62, 55, 65],
[35, 42, 25, 35]
].forEach(([rs, re, cs, ce]) => {
for (let r = rs; r < re; r++)
for (let c = cs; c < ce; c++)
if (c >= 0 && c < MAP_COLS && r >= 0 && r < MAP_ROWS && Math.random() < 0.7) mapData[r][
c
] = 6;
});
[
[15, 20],
[25, 22],
[40, 30],
[50, 28],
[10, 45],
[35, 48],
[55, 50],
[20, 55]
].forEach(([r, c]) => {
if (r >= 0 && r < MAP_ROWS && c >= 0 && c < MAP_COLS) { mapData[r][c] = 5; if (c + 1 < MAP_COLS)
mapData[r][c + 1] = 5; if (r + 1 < MAP_ROWS) { mapData[r + 1][c] = 5; if (c + 1 <
MAP_COLS) mapData[r + 1][c + 1] = 5; } }
});
for (let r = 0; r < MAP_ROWS; r++)
for (let c = 0; c < MAP_COLS; c++)
if (mapData[r][c] === 0 && Math.random() < 0.12) mapData[r][c] = 4;
for (let r = 0; r < 29; r++)
for (let c = 27; c < 44; c++) {
if (c >= 0 && c < MAP_COLS && r >= 0 && r < MAP_ROWS) {
if ((r === 0 || r === 28 || c === 27 || c === 43) && mapData[r][c] === 0) mapData[r][c] = 12;
}
}
for (let r = 0; r < MAP_ROWS; r++)
for (let c = 0; c < MAP_COLS; c++)
if (mapData[r][c] === 1 && Math.random() < 0.08) mapData[r][c] = 11;
for (let r = 30; r <= 35; r++)
for (let c = 28; c <= 42; c++)
if (r >= 0 && r < MAP_ROWS && c >= 0 && c < MAP_COLS) { if (r === 30 || r === 35 || c === 28 || c ===
42) mapData[r][c] = 3;
else mapData[r][c] = 11; }
[
[5, 15, 8, 12],
[55, 60, 60, 68]
].forEach(([rs, re, cs, ce]) => { for (let r = rs; r < re; r++)
for (let c = cs; c < ce; c++)
if (c >= 0 && c < MAP_COLS && r >= 0 && r < MAP_ROWS && Math.random() < 0.3) mapData[r][c] =
13; });
for (let r = 1; r <= 26; r++) {
for (let c = 28; c <= 42; c++) {
if (c >= 0 && c < MAP_COLS && r >= 0 && r < MAP_ROWS) {
if (c === 28 || c === 42 || r === 1 || r === 26) {
mapData[r][c] = 7;
} else if ((r - 2) % 3 === 0 && c > 30 && c < 40 && Math.random() < 0.55) {
mapData[r][c] = 8;
} else {
mapData[r][c] = 7;
}
}
}
}
mapData[26][34] = 9;
mapData[26][35] = 9;
mapData[26][36] = 9;
[
[20, 18],
[20, 52],
[40, 18],
[40, 52],
[30, 28],
[30, 42],
[15, 35],
[55, 35]
].forEach(([r, c]) => { if (r >= 0 && r < MAP_ROWS && c >= 0 && c < MAP_COLS) mapData[r][c] = 10; });
for (let r = 110; r <= 130; r++)
for (let c = 110; c <= 130; c++)
if (r >= 0 && r < MAP_ROWS && c >= 0 && c < MAP_COLS) { if (r === 110 || r === 130 || c === 110 || c ===
130) mapData[r][c] = 12;
else mapData[r][c] = 3; }
mapData[130][120] = 1;
mapData[130][121] = 1;
}
generateMap();
// ==================== 坐标 ====================
function worldToScreen(wx, wy) { return { x: wx - cameraX, y: wy - cameraY }; }
function screenToWorld(sx, sy) { return { x: sx + cameraX, y: sy + cameraY }; }
function tileToWorld(r, c) { return { x: c * TILE_SIZE + TILE_SIZE / 2, y: r * TILE_SIZE + TILE_SIZE / 2 }; }
function worldToTile(wx, wy) { return { row: Math.floor(wy / TILE_SIZE), col: Math.floor(wx / TILE_SIZE) }; }
function updateCamera(px, py) {
cameraX = px - VIEW_W / 2;
cameraY = py - VIEW_H / 2;
cameraX = Math.max(0, Math.min(WORLD_W - VIEW_W, cameraX));
cameraY = Math.max(0, Math.min(WORLD_H - VIEW_H, cameraY));
}
function dist(a, b) { return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2); }
// ==================== 飘字 ====================
class FloatingText {
constructor(wx, wy, text, color) { this.wx = wx;
this.wy = wy;
this.text = text;
this.color = color;
this.life = 55;
this.maxLife = 55; }
update() { this.life--;
this.wy -= 1.2; }
draw(ctx) {
const a = Math.max(0, this.life / this.maxLife);
const { x, y } = worldToScreen(this.wx, this.wy);
ctx.save();
ctx.globalAlpha = a;
ctx.fillStyle = this.color;
ctx.font = 'bold 12px Courier New';
ctx.textAlign = 'center';
ctx.fillText(this.text, x, y);
ctx.restore();
}
isDead() { return this.life <= 0; }
}
// ==================== 技能特效 ====================
class SkillEffect {
constructor(wx, wy, type) {
this.wx = wx;
this.wy = wy;
this.type = type;
this.frame = 0;
this.maxFrame = 45;
this.maxSize = 90;
this.currentSize = 0;
}
update() { this.frame++;
this.currentSize = (this.frame / this.maxFrame) * this.maxSize; }
isDead() { return this.frame >= this.maxFrame; }
draw(ctx) {
if (this.isDead()) return;
const { x, y } = worldToScreen(this.wx, this.wy);
const size = this.currentSize;
const alpha = 1 - (this.frame / this.maxFrame);
const halfSize = size / 2;
ctx.save();
ctx.globalAlpha = alpha * 0.9;
ctx.strokeStyle = '#a0d8ff';
ctx.lineWidth = 2;
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 8 + Math.sin(this.frame * 0.6) * 6;
ctx.beginPath();
ctx.rect(x - halfSize, y - halfSize, size, size);
ctx.stroke();
for (let i = 0; i < 4; i++) {
const segLen = size / (6 + Math.floor(Math.random() * 4));
const startOffset = Math.random() * size;
let sx, sy, ex, ey;
if (i === 0) { sx = x - halfSize + startOffset;
sy = y - halfSize;
ex = sx + segLen;
ey = sy; } else if (i === 1) { sx = x + halfSize;
sy = y - halfSize + startOffset;
ex = sx;
ey = sy + segLen; } else if (i === 2) { sx = x + halfSize - startOffset;
sy = y + halfSize;
ex = sx - segLen;
ey = sy; } else { sx = x - halfSize;
sy = y + halfSize - startOffset;
ex = sx;
ey = sy - segLen; }
ctx.globalAlpha = alpha * (0.4 + Math.random() * 0.6);
ctx.strokeStyle = Math.random() < 0.5 ? '#ffffff' : '#5dc8ff';
ctx.lineWidth = 1 + Math.random() * 2;
ctx.shadowBlur = 4 + Math.random() * 10;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(ex, ey);
ctx.stroke();
}
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
ctx.restore();
}
}
// ==================== 地面掉落 ====================
let groundLoot = [];
function dropLoot(wx, wy, item) {
if (groundLoot.length >= MAX_GROUND_LOOT) {
groundLoot.shift();
}
const icon = item.type === 'equipment' ? item.icon : item.type === 'potion' ? '❤️' : '📦';
const color = item.type === 'equipment' ? (RARITY_COLOR[item.rarity] || '#aaa') : item.type === 'potion' ?
'#2ecc71' : '#aaa';
groundLoot.push({
x: wx + (Math.random() - 0.5) * 16,
y: wy + (Math.random() - 0.5) * 16,
item: item,
frame: 0,
minAge: 30,
icon: icon,
color: color,
picked: false
});
}
function updateGroundLoot() {
for (let i = groundLoot.length - 1; i >= 0; i--) {
const loot = groundLoot[i];
loot.frame++;
if (loot.picked) { groundLoot.splice(i, 1); continue; }
if (loot.frame >= loot.minAge) {
if (dist(player.worldPos, loot) < 35) {
if (addToBag(loot.item)) {
loot.picked = true;
addFloatingText(loot.x, loot.y - 15, '拾取 ' + loot.item.name, loot.color);
}
}
}
if (loot.frame > 36000 && !loot.picked) {
loot.picked = true;
}
}
const nearLoot = groundLoot.find(l => !l.picked && l.frame >= l.minAge && dist(player.worldPos, l) < 50);
document.getElementById('pickupHint').classList.toggle('active', !!nearLoot);
}
function drawGroundLoot(ctx) {
groundLoot.forEach(loot => {
if (loot.picked) return;
const { x, y } = worldToScreen(loot.x, loot.y);
const bob = Math.sin(loot.frame * 0.08) * 2;
const alpha = loot.frame < loot.minAge ? 0.4 + (loot.frame / loot.minAge) * 0.6 : 1;
ctx.save();
const pillarHeight = 10 + Math.abs(Math.sin(loot.frame * 0.06)) * 6;
ctx.globalAlpha = alpha * 0.25;
const grad = ctx.createLinearGradient(x, y + bob - pillarHeight, x, y + bob);
grad.addColorStop(0, 'rgba(255,255,255,0)');
grad.addColorStop(0.6, loot.color);
grad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = grad;
ctx.fillRect(x - 3, y + bob - pillarHeight, 6, pillarHeight);
ctx.globalAlpha = alpha * 0.35;
ctx.fillStyle = loot.color;
ctx.beginPath();
ctx.arc(x, y + bob, 9, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = alpha;
ctx.font = '14px serif';
ctx.textAlign = 'center';
ctx.fillText(loot.icon, x, y + bob + 5);
ctx.font = 'bold 7px Courier New';
ctx.fillStyle = loot.color;
ctx.fillText(loot.item.name, x, y + bob - 10);
ctx.restore();
});
}
// ==================== 装备系统 ====================
const RARITY = ['普通', '优秀', '精良', '史诗', '传说'];
const RARITY_COLOR = { '普通': '#aaaaaa', '优秀': '#2ecc71', '精良': '#3498db', '史诗': '#9b59b6', '传说': '#e67e22' };
const RARITY_MUL = { '普通': 1, '优秀': 1.5, '精良': 2, '史诗': 3, '传说': 5 };
const EQUIP_NAMES = {
weapon: ['铁剑', '钢刃', '不会变异的老式匕首', '自制长剑', '低级时序之刃', '首秀之夜·剑'],
armor: ['皮甲', '锁子甲', '陈旧夹克', '捡来的运动衫', '低级时序铠甲', '首秀之夜·甲'],
helmet: ['布帽', '铁盔', '暗沉兜帽', '摩托头盔', '低级时序冠冕', '首秀之夜·冠'],
boots: ['布鞋', '皮靴', '沉旧长靴', '楚强的运动鞋战靴', '低级时序之靴', '首秀之夜·靴'],
accessory: ['铜戒', '银坠', '陈旧之戒', '手机屏幕护符', '低级时序宝珠', '首秀之夜·珠'],
};
const EQUIP_SLOT_NAMES = { weapon: '武器', armor: '护甲', helmet: '头盔', boots: '靴子', accessory: '饰品' };
const EQUIP_ICONS = { weapon: '🗡️', armor: '🛡️', helmet: '⛑️', boots: '👢', accessory: '💍' };
const SET_NAME = '首秀之夜套装';
const SET_PIECES = ['首秀之夜·剑', '首秀之夜·甲', '首秀之夜·冠', '首秀之夜·靴', '首秀之夜·珠'];
// ==================== 马超套装 ====================
const MA_CHAO_SET_NAME = '马超套装';
const MA_CHAO_SET_PIECES = ['马超·剑', '马超·甲', '马超·冠', '马超·靴', '马超·珠'];
const MA_CHAO_EQUIP_NAMES = {
weapon: '马超·剑',
armor: '马超·甲',
helmet: '马超·冠',
boots: '马超·靴',
accessory: '马超·珠'
};
const MA_CHAO_ICONS = {
weapon: '⚔️',
armor: '🛡️',
helmet: '👑',
boots: '👢',
accessory: '💎'
};
function isMaChaoPiece(name) {
return MA_CHAO_SET_PIECES.includes(name);
}
function hasMaChaoSet() {
return MA_CHAO_SET_PIECES.every(n => {
for (const s in state.equipment)
if (state.equipment[s] && state.equipment[s].name === n) return true;
return false;
});
}
function generateMaChaoEquipment(slot) {
const ml = 10;
const base = Math.floor((ml * 3 + 10) * RARITY_MUL['传说']);
const item = {
type: 'equipment',
slot: slot,
name: MA_CHAO_EQUIP_NAMES[slot],
rarity: '传说',
icon: MA_CHAO_ICONS[slot],
stats: {},
isSet: false,
isMaChaoSet: true
};
if (slot === 'weapon') {
item.stats.atk = base + Math.floor(Math.random() * 12) + 20;
} else if (slot === 'armor') {
item.stats.def = base + Math.floor(Math.random() * 8) + 15;
item.stats.hp = Math.floor(base * 3) + 60;
} else if (slot === 'helmet') {
item.stats.def = Math.floor(base * 0.7) + 10;
item.stats.hp = Math.floor(base * 4) + 80;
} else if (slot === 'boots') {
item.stats.def = Math.floor(base * 0.5) + 8;
item.stats.hp = Math.floor(base * 1.5) + 40;
} else {
item.stats.atk = Math.floor(base * 0.7) + 15;
item.stats.hp = Math.floor(base * 2) + 50;
}
return item;
}
function generateEquipment(ml, fr) {
const slots = ['weapon', 'armor', 'helmet', 'boots', 'accessory'];
const slot = slots[Math.floor(Math.random() * slots.length)];
let rarity;
if (fr) { rarity = fr; } else { const r = Math.random(); if (r < 0.30) rarity = '普通';
else if (r < 0.58) rarity = '优秀';
else if (r < 0.80) rarity = '精良';
else if (r < 0.94) rarity = '史诗';
else rarity = '传说'; }
const mul = RARITY_MUL[rarity];
const ri = RARITY.indexOf(rarity);
const names = EQUIP_NAMES[slot];
const name = names[Math.min(ri, names.length - 1)];
const base = Math.floor((ml * 2 + 3) * mul);
const item = { type: 'equipment', slot, name, rarity, icon: EQUIP_ICONS[slot], stats: {}, isSet: SET_PIECES
.includes(name) };
if (slot === 'weapon') { item.stats.atk = base + Math.floor(Math.random() * 5); } else if (slot === 'armor') { item
.stats.def = base + Math.floor(Math.random() * 3);
item.stats.hp = Math.floor(base * 2); } else if (slot === 'helmet') { item.stats.def = Math.floor(base *
0.5);
item.stats.hp = Math.floor(base * 3); } else if (slot === 'boots') { item.stats.def = Math.floor(base *
0.3);
item.stats.hp = Math.floor(base); } else { item.stats.atk = Math.floor(base * 0.5);
item.stats.hp = Math.floor(base * 1.5); }
return item;
}
function generatePotion(a) { return { type: 'potion', name: '生命药水', icon: '❤️', rarity: '普通', hpRestore: a || 30,
desc: '恢复' + (a || 30) + '点生命' }; }
// ==================== 背包 ====================
const BAG_SIZE = 100;
function addToBag(item) { if (state.bag.length >= BAG_SIZE) return false;
state.bag.push(item); return true; }
function recycleItem(index) {
const item = state.bag[index];
if (!item) return;
state.bag.splice(index, 1);
if (item.type === 'equipment') { if (Math.random() < 0.5) { state.fragments++;
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '+1 完整碎片', '#f39c12'); } else
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '回收成功(无碎片)', '#888'); } else
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '回收成功', '#888');
renderBagPanel();
}
function equipItem(index) {
const item = state.bag[index];
if (!item || item.type !== 'equipment') return;
const slot = item.slot;
if (state.equipment[slot]) { state.bag[index] = state.equipment[slot]; } else { state.bag.splice(index, 1); }
state.equipment[slot] = item;
renderBagPanel();
updateEquipMini();
if (item.isMaChaoSet) {
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '装备 ' + item.name + ' 🐴', '#FFD700');
} else {
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '装备 ' + item.name, RARITY_COLOR[item.rarity]);
}
}
function usePotion(index) {
const item = state.bag[index];
if (!item || item.type !== 'potion') return;
state.hp = Math.min(getTotalMaxHp(), state.hp + item.hpRestore);
state.bag.splice(index, 1);
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '+' + item.hpRestore + ' HP', '#2ecc71');
renderBagPanel();
}
function getTotalMaxHp() {
let hp = state.maxHp;
for (const s in state.equipment) { const e = state.equipment[s]; if (e && e.stats.hp) hp += e.stats.hp; }
if (hasFullSet()) hp += 500;
if (hasMaChaoSet()) hp += 1000;
return hp;
}
function getTotalAtk() {
let a = state.atk;
for (const s in state.equipment) { const e = state.equipment[s]; if (e && e.stats.atk) a += e.stats.atk; }
if (hasFullSet()) a += 50;
if (hasMaChaoSet()) a += 100;
return a;
}
function getTotalDef() {
let d = state.def;
for (const s in state.equipment) { const e = state.equipment[s]; if (e && e.stats.def) d += e.stats.def; }
if (hasFullSet()) d += 30;
if (hasMaChaoSet()) d += 60;
return d;
}
function hasFullSet() { return SET_PIECES.every(n => { for (const s in state.equipment)
if (state.equipment[s] && state.equipment[s].name === n) return true; return false; }); }
function hasCentennialWeapon() { return state.equipment.weapon && state.equipment.weapon.name === '百年器'; }
function synthesizePainting() {
if (state.fragments < 1000) return;
state.fragments -= 1000;
state.painting221Obtained = true;
addFloatingText(player.worldPos.x, player.worldPos.y - 50, '🎨 获得站长的名画 2.2.1!', '#9b59b6');
showPainting('https://ccsit.cc/wp-content/uploads/2026/05/2026518.jpg', '站长的名画 2.2.1');
saveGame();
}
function craftAutoRecycleArtifact() {
if (state.fragments < 98) return;
if (state.autoRecycleArtifact) { addFloatingText(player.worldPos.x, player.worldPos.y - 30, '已拥有虚空回收炉!',
'#e67e22'); return; }
state.fragments -= 98;
state.autoRecycleArtifact = true;
state.autoRecycleEnabled = true;
addFloatingText(player.worldPos.x, player.worldPos.y - 40, '🌀 获得虚空回收炉!自动回收已开启', '#ffd700');
updateAutoRecycleButton();
renderBagPanel();
saveGame();
}
const CCSITCC_PAINTING_URL = 'https://ccsit.cc/wp-content/uploads/2026/05/2026518.jpg';
function showPainting(url, title) { document.getElementById('paintingImg').src = url;
document.getElementById('paintingTitle').textContent = title;
document.getElementById('paintingModal').classList.add('active'); }
window.closePainting = function() { document.getElementById('paintingModal').classList.remove('active'); };
function renderBagPanel() {
const grid = document.getElementById('bagGrid');
grid.innerHTML = '';
for (let i = 0; i < BAG_SIZE; i++) {
const slot = document.createElement('div');
slot.className = 'bag-slot';
if (state.bag[i]) {
const item = state.bag[i];
if (item.isMaChaoSet) {
slot.classList.add('ma-chao-item');
}
slot.innerHTML =
`<span class="item-icon">${item.icon||'❓'}</span><span class="item-name">${item.name}</span>`;
slot.style.borderColor = item.isMaChaoSet ? '#FFD700' : (RARITY_COLOR[item.rarity] || '#3a3a4a');
slot.onclick = (function(idx) { return function() { showItemDetail(idx); }; })(i);
}
grid.appendChild(slot);
}
document.getElementById('bagCount').textContent = state.bag.length;
document.getElementById('bagMax').textContent = BAG_SIZE;
document.getElementById('fragCount2').textContent = state.fragments;
document.getElementById('btnSynth').style.display = state.fragments >= 1000 ? 'inline-block' : 'none';
document.getElementById('btnSynth').onclick = synthesizePainting;
const ars = document.getElementById('autoRecycleStatus');
if (state.autoRecycleArtifact) {
ars.style.display = 'block';
ars.className = 'auto-recycle-info owned';
ars.innerHTML = state.autoRecycleEnabled ?
'♻️ 虚空回收炉:<span style="color:#2ecc71;">自动回收中</span>' :
'♻️ 虚空回收炉:<span style="color:#ff4444;">已暂停</span>';
} else {
ars.style.display = 'block';
ars.className = 'auto-recycle-info';
ars.innerHTML =
'♻️ 虚空回收炉:<span style="color:#e67e22;">未获得</span> (98🔮合成/血色猎手掉落)';
}
updateBagTopSection();
}
function updateBagTopSection() {
const topSection = document.getElementById('bagTopSection');
let hasContent = false;
const ars = document.getElementById('autoRecycleStatus');
if (ars.style.display !== 'none') hasContent = true;
const itemDetail = document.getElementById('itemDetail');
if (itemDetail.style.display !== 'none') hasContent = true;
topSection.style.display = hasContent ? 'block' : 'none';
if (!state.autoRecycleArtifact && state.fragments >= 98) {
const existingBtn = document.getElementById('btnCraftArtifactTop');
if (!existingBtn) {
const btn = document.createElement('button');
btn.id = 'btnCraftArtifactTop';
btn.className = 'detail-btn artifact';
btn.textContent = '合成虚空回收炉(98🔮)';
btn.style.display = 'block';
btn.style.margin = '0 auto 8px auto';
btn.onclick = craftAutoRecycleArtifact;
topSection.appendChild(btn);
}
} else {
const existingBtn = document.getElementById('btnCraftArtifactTop');
if (existingBtn) existingBtn.remove();
}
const btnCraftOld = document.getElementById('btnCraftArtifact');
if (btnCraftOld) btnCraftOld.remove();
}
function showItemDetail(index) {
const item = state.bag[index];
if (!item) return;
const div = document.getElementById('itemDetail');
div.style.display = 'block';
let h = `<div class="item-detail">`;
const itemColor = item.isMaChaoSet ? '#FFD700' : (RARITY_COLOR[item.rarity] || '#ccc');
h +=
`<div class="detail-name" style="color:${itemColor}">${item.icon||''} ${item.name} [${item.isMaChaoSet?'马超套装·传说':item.rarity}]${item.isMaChaoSet?' 🐴':''}</div>`;
if (item.type === 'equipment') {
h += `<div class="detail-stats">`;
for (const s in item.stats) h += `${s.toUpperCase()}+${item.stats[s]} `;
if (item.isSet) h += `<br><span style="color:#f39c12">✨ ${SET_NAME}部件</span>`;
if (item.isMaChaoSet) h +=
`<br><span style="color:#FFD700;font-weight:bold;">🐴 ${MA_CHAO_SET_NAME}部件 - 攻击力为普通装备2倍以上</span>`;
if (item.special === 'centennial') h += `<br><span style="color:#c0c0c0">⚡ 每击+100N时序</span>`;
h +=
`</div><div class="detail-actions"><button class="detail-btn recycle" onclick="window._recycleItem(${index})" style="order:-1;">回收(50%碎片)</button><button class="detail-btn equip" onclick="window._equipItem(${index})">装备</button></div>`;
} else if (item.type === 'potion') {
h +=
`${item.desc}</div><div class="detail-actions"><button class="detail-btn recycle" onclick="window._recycleItem(${index})" style="order:-1;">回收</button><button class="detail-btn use" onclick="window._usePotion(${index})">使用</button></div>`;
}
h += `</div>`;
div.innerHTML = h;
updateBagTopSection();
}
window._equipItem = equipItem;
window._recycleItem = recycleItem;
window._usePotion = usePotion;
function openBagPanel() {
renderBagPanel();
document.getElementById('bagPanel').classList.add('active');
document.getElementById('itemDetail').style.display = 'none';
updateBagTopSection();
}
function closeBagPanel() {
document.getElementById('bagPanel').classList.remove('active');
document.getElementById('itemDetail').style.display = 'none';
}
window.closeBagPanel = closeBagPanel;
function renderEquipPanel() {
const c = document.getElementById('equipSlots');
c.innerHTML = '';
['weapon', 'armor', 'helmet', 'boots', 'accessory'].forEach(slot => {
const eq = state.equipment[slot];
const d = document.createElement('div');
d.className = 'equip-slot';
if (eq) {
if (eq.isMaChaoSet) {
d.classList.add('ma-chao-equipped');
}
d.style.borderColor = eq.isMaChaoSet ? '#FFD700' : (RARITY_COLOR[eq.rarity] || '#3a3a4a');
d.innerHTML =
`<span class="slot-icon">${eq.icon}</span><div class="slot-info"><div class="slot-label">${EQUIP_SLOT_NAMES[slot]}${eq.isMaChaoSet?' 🐴':''}</div><div class="slot-name" style="color:${eq.isMaChaoSet?'#FFD700':RARITY_COLOR[eq.rarity]}">${eq.name}</div></div>`;
d.onclick = () => { if (state.bag.length < BAG_SIZE) { state.bag.push(eq);
state.equipment[slot] = null;
renderEquipPanel();
updateEquipMini();
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '卸下 ' + eq.name, '#aaa'); } };
} else d.innerHTML =
`<span class="slot-icon" style="opacity:0.3">${EQUIP_ICONS[slot]}</span><div class="slot-info"><div class="slot-label">${EQUIP_SLOT_NAMES[slot]}</div><div class="slot-name" style="color:#555">空</div></div>`;
c.appendChild(d);
});
document.getElementById('equipStatsDisplay').innerHTML =
`ATK:<span style="color:#ff8844">${getTotalAtk()}</span> DEF:<span style="color:#88aaff">${getTotalDef()}</span> HP:<span style="color:#ff6b6b">${state.hp}/${getTotalMaxHp()}</span>`;
document.getElementById('setBonusDisplay').style.display = hasFullSet() ? 'block' : 'none';
const mcDisplay = document.getElementById('maChaoSetBonusDisplay');
if (hasMaChaoSet()) {
mcDisplay.classList.add('active');
} else {
mcDisplay.classList.remove('active');
}
}
function updateEquipMini() {
['weapon', 'armor', 'helmet', 'boots', 'accessory'].forEach(slot => {
const el = document.getElementById('eqMini_' + slot);
const eq = state.equipment[slot];
if (eq) {
el.classList.remove('empty');
el.style.borderColor = eq.isMaChaoSet ? '#FFD700' : (RARITY_COLOR[eq.rarity] ||
'#3a3a4a');
el.querySelector('.eq-name').textContent = eq.name.substring(0, 4);
el.querySelector('.eq-name').style.color = eq.isMaChaoSet ? '#FFD700' : (RARITY_COLOR[eq
.rarity] || '#ccc');
if (eq.isMaChaoSet && !el.querySelector('.ma-chao-star')) {
const star = document.createElement('span');
star.className = 'ma-chao-star';
star.textContent = '🐴';
star.style.fontSize = '6px';
star.style.marginLeft = '1px';
el.appendChild(star);
}
} else {
el.classList.add('empty');
el.style.borderColor = 'transparent';
el.querySelector('.eq-name').textContent = '空';
el.querySelector('.eq-name').style.color = '#555';
const star = el.querySelector('.ma-chao-star');
if (star) star.remove();
}
});
}
updateEquipMini();
function openEquipPanel() { renderEquipPanel();
document.getElementById('equipPanel').classList.add('active'); }
function closeEquipPanel() { document.getElementById('equipPanel').classList.remove('active'); }
window.closeEquipPanel = closeEquipPanel;
// ==================== 怪物系统 ====================
const MONSTER_TYPES = [
{ name: '雄英组王大哥小组', hp: 80, atk: 8, def: 2, exp: 12, chrono: 4, color: '#8B7355', size: 14, level: 1 },
{ name: '雄英组喽啰·老棍', hp: 150, atk: 12, def: 1, exp: 18, chrono: 5, color: '#6B4226', size: 12, level: 2 },
{ name: '雄英组·暗夜之主(自称)', hp: 130, atk: 16, def: 4, exp: 28, chrono: 7, color: '#2C2C54', size: 13, level: 3 },
{ name: '雄英组喽啰·盾哥', hp: 1380, atk: 14, def: 10, exp: 35, chrono: 9, color: '#4A4A4A', size: 16, level: 4 },
{ name: '雄英组张老二小队', hp: 150, atk: 20, def: 3, exp: 25, chrono: 6, color: '#4B0082', size: 11, level: 3 },
{ name: '雄英组紫小妹小分组', hp: 260, atk: 22, def: 4, exp: 30, chrono: 8, color: '#e4eab6', size: 13, level: 4 },
{ name: '雄英组喽啰·赛车王', hp: 440, atk: 18, def: 5, exp: 26, chrono: 7, color: '#4488CC', size: 12, level: 3 },
{ name: '雄英组喽啰·大佬圈', hp: 120, atk: 25, def: 2, exp: 32, chrono: 10, color: '#CCAA00', size: 11, level: 4 },
];
class Enemy {
constructor(name, row, col, hp, atk, def, exp, chrono, color, size, level, isBoss, isFinalBoss) {
this.name = name;
this.row = row;
this.col = col;
this.spawnRow = row;
this.spawnCol = col;
this.worldPos = tileToWorld(row, col);
this.hp = hp;
this.maxHp = hp;
this.atk = atk;
this.def = def;
this.exp = exp;
this.chrono = chrono;
this.color = color;
this.size = size;
this.level = level || 1;
this.isBoss = isBoss || false;
this.isFinalBoss = isFinalBoss || false;
this.speed = isBoss ? 1.6 : 1.9;
this.frame = 0;
this.patrolTimer = 0;
this.patrolDir = Math.random() > 0.5 ? 1 : -1;
this.aggroRange = isBoss ? 130 : 80;
this.isAggro = false;
this.atkTimer = 0;
this.targetWorld = { ...this.worldPos };
this.hitFlash = 0;
this.alive = true;
this.revivalTimer = 0;
}
revive() {
this.alive = true;
this.hp = this.maxHp;
this.worldPos = tileToWorld(this.spawnRow, this.spawnCol);
this.row = this.spawnRow;
this.col = this.spawnCol;
this.targetWorld = { ...this.worldPos };
this.isAggro = false;
this.atkTimer = 0;
this.hitFlash = 0;
this.frame = 0;
this.revivalTimer = 1;
}
setTarget(r, c) { this.targetWorld = tileToWorld(r, c); }
update(player) {
if (!this.alive) {
if (this.revivalTimer > 0) {
this.revivalTimer--;
if (this.revivalTimer <= 0) this.revive();
}
return;
}
this.frame++;
if (this.hitFlash > 0) this.hitFlash -= 16;
const dx = player.worldPos.x - this.worldPos.x,
dy = player.worldPos.y - this.worldPos.y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < this.aggroRange) { this.isAggro = true;
this.setTarget(player.row, player.col); } else if (this.isAggro && d > this.aggroRange + 80) this
.isAggro = false;
if (!this.isAggro) {
this.patrolTimer++;
if (this.patrolTimer > 70 + Math.floor(Math.random() * 50)) {
this.patrolTimer = 0;
this.patrolDir *= -1;
const nr = this.row + (Math.random() - 0.5) * 2,
nc = this.col + this.patrolDir * (Math.random() * 2 + 1);
this.setTarget(Math.max(0, Math.min(MAP_ROWS - 1, nr)), Math.max(0, Math.min(MAP_COLS -
1, nc)));
}
}
const tdx = this.targetWorld.x - this.worldPos.x,
tdy = this.targetWorld.y - this.worldPos.y;
const td = Math.sqrt(tdx * tdx + tdy * tdy);
if (td > 1.2) { const step = Math.min(this.speed, td);
this.worldPos.x += (tdx / td) * step;
this.worldPos.y += (tdy / td) * step; const t = worldToTile(this.worldPos.x, this.worldPos.y);
this.row = t.row;
this.col = t.col; }
if (this.atkTimer > 0) this.atkTimer--;
if (d < 28 && this.atkTimer <= 0) { const dmg = Math.max(1, this.atk - getTotalDef());
state.hp -= dmg;
this.atkTimer = 55;
floatingTexts.push(new FloatingText(player.worldPos.x, player.worldPos.y - 25, '-' + dmg,
'#ff4444')); }
}
draw(ctx) {
if (!this.alive) return;
const { x, y } = worldToScreen(this.worldPos.x, this.worldPos.y);
const bob = Math.sin(this.frame * 0.15) * 1;
if (this.hitFlash > 0) ctx.globalAlpha = 0.5 + Math.sin(this.hitFlash * 0.5) * 0.5;
ctx.fillStyle = this.color;
ctx.font = 'bold 8px Courier New';
ctx.textAlign = 'center';
ctx.fillText(this.name, x, y - 16);
if (this.isFinalBoss) {
ctx.fillStyle = this.hitFlash > 0 ? '#fff' : '#8b0000';
ctx.fillRect(x - 6, y - 12 + bob, 12, 12);
ctx.fillStyle = '#FFD700';
ctx.fillRect(x - 4, y - 14 + bob, 8, 2);
} else if (this.isBoss) {
ctx.fillStyle = this.hitFlash > 0 ? '#fff' : '#8b0000';
ctx.fillRect(x - 4, y - 9 + bob, 8, 9);
} else {
ctx.fillStyle = this.hitFlash > 0 ? '#fff' : this.color;
ctx.fillRect(x - 3, y - 7 + bob, 6, 7);
}
if (this.hp < this.maxHp) {
const bw = this.isFinalBoss ? 30 : this.isBoss ? 20 : 14;
ctx.fillStyle = '#333';
ctx.fillRect(x - bw / 2, y - 20, bw, 3);
ctx.fillStyle = this.isFinalBoss ? '#ffd700' : this.isBoss ? '#ff4444' : '#44ff44';
ctx.fillRect(x - bw / 2, y - 20, bw * (this.hp / this.maxHp), 3);
}
ctx.globalAlpha = 1;
}
takeDamage(d) {
const dmg = Math.max(1, d - this.def);
this.hp -= dmg;
if (this.hp < 0) this.hp = 0;
this.hitFlash = 150;
return { dmg, killed: this.hp <= 0 };
}
}
let enemies = [];
function spawnMonsters() {
enemies = [];
for (let i = 0; i < 160; i++) {
const mt = MONSTER_TYPES[Math.floor(Math.random() * MONSTER_TYPES.length)];
let r, c, tries = 0;
do { r = Math.floor(Math.random() * 125) + 5;
c = Math.floor(Math.random() * 135) + 5;
tries++; } while (tries < 25 && Math.abs(r - 75) < 18 && Math.abs(c - 70) < 18);
enemies.push(new Enemy(mt.name, r, c, mt.hp, mt.atk, mt.def, mt.exp, mt.chrono, mt.color, mt.size,
mt.level, false, false));
}
enemies.push(new Enemy('雄英组·项兴言', 50, 60, 600, 28, 12, 120, 60, '#8B0000', 22, 5, true, false));
enemies.push(new Enemy('雄英组·血色猎手', 80, 30, 900, 38, 16, 220, 90, '#DC143C', 24, 7, true, false));
enemies.push(new Enemy('孙新', 13, 35, 598800, 555, 22, 5555, 6200, '#FFD700', 30, 10, true, true));
enemies.push(new Enemy('Oil1nstor', 53, 30, 59320, 26, 28, 500, 500, '#FFD400', 26, 6, true, true));
enemies.push(new Enemy('手机被入侵者', 30, 40, 20020, 48, 16, 180, 160, '#DC143C', 24, 7, true, false));
}
spawnMonsters();
function applySaveState() {
for (let i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].isBoss && state.defeatedBosses.includes(enemies[i].name)) {
enemies[i].alive = false;
enemies[i].revivalTimer = 20000;
}
}
}
applySaveState();
// ==================== 金色罐子 ====================
let goldenJar = null;
function spawnGoldenJar(row, col) {
goldenJar = { row, col, worldPos: tileToWorld(row, col), active: true, collected: false, frame: 0,
shaking: false, shakeStart: 0, name: 'CCSITCC' };
state.goldenJar = { row, col, active: true, collected: false, frame: 0, shaking: false, shakeStart: 0,
name: 'CCSITCC' };
addFloatingText(goldenJar.worldPos.x, goldenJar.worldPos.y - 25, '🎁 CCSITCC 金色罐子出现了!', '#FFD700');
saveGame();
}
function drawGoldenJar(ctx) {
if (!goldenJar || !goldenJar.active || goldenJar.collected) return;
goldenJar.frame++;
const { x, y } = worldToScreen(goldenJar.worldPos.x, goldenJar.worldPos.y);
let shakeX = 0,
shakeY = 0;
if (goldenJar.shaking) { const e = goldenJar.frame - goldenJar.shakeStart;
shakeX = Math.sin(e * 1.5) * Math.min(e / 30, 1) * 6;
shakeY = Math.cos(e * 1.8) * Math.min(e / 30, 1) * 6; }
const sx = x + shakeX,
sy = y + shakeY;
const glow = Math.sin(goldenJar.frame * 0.08) * 0.3 + 0.7;
ctx.save();
ctx.globalAlpha = glow * 0.4;
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(sx, sy, 20, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = glow;
ctx.fillStyle = '#DAA520';
ctx.fillRect(sx - 8, sy - 10, 16, 14);
ctx.fillStyle = '#FFD700';
ctx.fillRect(sx - 6, sy - 8, 12, 10);
ctx.fillStyle = '#B8860B';
ctx.fillRect(sx - 10, sy - 13, 20, 4);
ctx.fillStyle = '#FFD700';
ctx.font = 'bold 9px Courier New';
ctx.textAlign = 'center';
ctx.fillText('CCSITCC', sx, sy - 18);
ctx.restore();
}
function checkGoldenJarClick(wt) {
if (!goldenJar || !goldenJar.active || goldenJar.collected) return false;
const d = Math.sqrt((wt.x - goldenJar.worldPos.x) ** 2 + (wt.y - goldenJar.worldPos.y) ** 2);
if (d < 30 && !goldenJar.shaking) {
goldenJar.shaking = true;
goldenJar.shakeStart = goldenJar.frame;
state.goldenJar.shaking = true;
state.goldenJar.shakeStart = goldenJar.frame;
setTimeout(() => { if (goldenJar && goldenJar.active && !goldenJar.collected) { goldenJar
.collected = true;
state.goldenJar.collected = true;
showPainting(CCSITCC_PAINTING_URL, 'CCSIT.CC名画 站长的微笑2.2');
saveGame(); } }, 1200);
return true;
}
return false;
}
// ==================== 玩家 ====================
const player = {
row: state.playerRow,
col: state.playerCol,
targetRow: state.playerRow,
targetCol: state.playerCol,
worldPos: tileToWorld(state.playerRow, state.playerCol),
speed: 2.3,
frame: 0,
dragonActive: true,
atkTimer: 0
};
function setPlayerTarget(r, c) { player.targetRow = Math.max(0, Math.min(MAP_ROWS - 1, r));
player.targetCol = Math.max(0, Math.min(MAP_COLS - 1, c)); }
function updatePlayer() {
if (player.atkTimer > 0) player.atkTimer--;
const tw = tileToWorld(player.targetRow, player.targetCol);
const dx = tw.x - player.worldPos.x,
dy = tw.y - player.worldPos.y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < 1.5) { player.worldPos.x = tw.x;
player.worldPos.y = tw.y;
player.row = player.targetRow;
player.col = player.targetCol; } else { const step = Math.min(player.speed, d);
player.worldPos.x += (dx / d) * step;
player.worldPos.y += (dy / d) * step; const t = worldToTile(player.worldPos.x, player.worldPos.y);
player.row = t.row;
player.col = t.col;
player.frame++; }
state.playerRow = player.row;
state.playerCol = player.col;
}
function drawPlayer(ctx) {
const { x, y } = worldToScreen(player.worldPos.x, player.worldPos.y);
if (player.dragonActive) drawDragon(ctx, x, y);
drawCharacter(ctx, x, y);
if (hasCentennialWeapon()) { ctx.fillStyle = '#C0C0C0';
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 6;
ctx.fillRect(x - 1, y - 8, 2, 16);
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent'; } else { ctx.fillStyle = '#c0c0c0';
ctx.fillRect(x + 4, y - 9, 2, 12); }
ctx.fillStyle = '#ffd700';
ctx.font = 'bold 9px Courier New';
ctx.textAlign = 'center';
ctx.fillText('Yang Zicang', x, y - 18);
if (hasMaChaoSet()) {
ctx.fillStyle = '#FFD700';
ctx.font = 'bold 9px Courier New';
ctx.fillText('🐴马超套装·自动挂机', x, y - 26);
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 8;
ctx.fillText('🐴马超套装·自动挂机', x, y - 26);
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
} else if (hasFullSet()) {
ctx.fillStyle = '#f39c12';
ctx.font = 'bold 8px Courier New';
ctx.fillText('✨自动挂机增强', x, y - 26);
} else if (state.autoFarmEnabled) { ctx.fillStyle = '#2ecc71';
ctx.font = 'bold 8px Courier New';
ctx.fillText('⚡挂机中', x, y - 26); }
}
function drawDragon(ctx, x, y) {
ctx.save();
ctx.globalAlpha = 0.4 + Math.sin(player.frame * 0.1 + gameFrame * 0.05) * 0.15;
const off = Math.sin(player.frame * 0.15 + gameFrame * 0.03) * 3;
ctx.fillStyle = '#e0e0ff';
ctx.fillRect(x - 7, y - 10 + off, 4, 2);
ctx.fillRect(x - 5, y - 8 + off, 4, 2);
ctx.fillRect(x - 3, y - 6 + off, 4, 2);
ctx.fillStyle = '#ffffff';
ctx.fillRect(x - 9, y - 12 + off, 3, 2);
ctx.restore();
}
function drawCharacter(ctx, x, y) {
const bob = Math.sin(player.frame * 0.2 + gameFrame * 0.04) * 1;
ctx.fillStyle = '#3a1a0a';
ctx.fillRect(x - 4, y - 6 + bob, 8, 6);
ctx.fillStyle = '#5a2a10';
ctx.fillRect(x - 3, y - 3 + bob, 6, 4);
ctx.fillStyle = '#ffdbac';
ctx.fillRect(x - 3, y - 12 + bob, 6, 5);
ctx.fillStyle = '#3a1a0a';
ctx.fillRect(x - 2, y - 14 + bob, 4, 2);
ctx.fillStyle = '#000';
ctx.fillRect(x - 2, y - 10 + bob, 2, 2);
ctx.fillRect(x + 1, y - 10 + bob, 2, 2);
ctx.fillStyle = '#ff9966';
ctx.fillRect(x - 1, y - 9 + bob, 1, 1);
ctx.fillRect(x + 2, y - 9 + bob, 1, 1);
if (hasFullSet()) { ctx.save();
ctx.globalAlpha = 0.3 + Math.sin(gameFrame * 0.08) * 0.15;
ctx.strokeStyle = '#f39c12';
ctx.lineWidth = 2;
ctx.strokeRect(x - 5, y - 16 + bob, 10, 18);
ctx.restore(); }
if (hasMaChaoSet()) { ctx.save();
ctx.globalAlpha = 0.4 + Math.sin(gameFrame * 0.06) * 0.2;
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 3;
ctx.shadowColor = '#FFD700';
ctx.shadowBlur = 10;
ctx.strokeRect(x - 6, y - 17 + bob, 12, 20);
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
ctx.restore(); }
}
function playerAttack(enemy) {
if (player.atkTimer > 0) return;
if (!enemy.alive) return;
const atkInterval = hasFullSet() ? 18 : 28;
const result = enemy.takeDamage(getTotalAtk());
player.atkTimer = atkInterval;
floatingTexts.push(new FloatingText(enemy.worldPos.x, enemy.worldPos.y - 20, '-' + result.dmg, '#ffaa00'));
addLog('Yang Zicang 攻击 ' + enemy.name + ' -' + result.dmg);
if (hasCentennialWeapon()) {
state.chronoPoints += 100;
floatingTexts.push(new FloatingText(player.worldPos.x, player.worldPos.y - 35, '+100 N 时序', '#c0c0c0'));
}
if (result.killed) onEnemyDefeat(enemy);
}
function dragonAttack(enemy) {
if (!player.dragonActive) return;
if (!enemy.alive) return;
const result = enemy.takeDamage(Math.floor(getTotalAtk() * 0.3) + Math.floor(Math.random() * 5));
floatingTexts.push(new FloatingText(enemy.worldPos.x - 10, enemy.worldPos.y - 25, '-' + result.dmg, '#e0e0ff'));
addLog('白影念龙 辅助 ' + enemy.name + ' -' + result.dmg);
if (result.killed) onEnemyDefeat(enemy);
}
function useSkill() {
const now = gameFrame;
if (now - lastSkillTime < SKILL_COOLDOWN) return;
if (state.chronoPoints < 100) { addFloatingText(player.worldPos.x, player.worldPos.y - 25,
'时序不足!需要至少100N', '#ff4444'); return; }
const consumedChrono = Math.floor(state.chronoPoints * 0.15);
if (consumedChrono < 100) { addFloatingText(player.worldPos.x, player.worldPos.y - 25, '时序不足!', '#ff4444');
return; }
state.chronoPoints -= consumedChrono;
lastSkillTime = now;
const totalDamage = consumedChrono * 4;
const range = 55;
const px = player.worldPos.x,
py = player.worldPos.y;
let targetsHit = 0;
enemies.forEach(e => { if (e.alive && Math.abs(e.worldPos.x - px) <= range && Math.abs(e.worldPos.y - py) <=
range) targetsHit++; });
const dmgPerTarget = targetsHit > 0 ? Math.floor(totalDamage / targetsHit) : totalDamage;
enemies.forEach(e => { if (e.alive && Math.abs(e.worldPos.x - px) <= range && Math.abs(e.worldPos.y - py) <=
range) { const res = e.takeDamage(dmgPerTarget);
floatingTexts.push(new FloatingText(e.worldPos.x, e.worldPos.y - 20, '-' + res.dmg,
'#5dc8ff')); if (res.killed) onEnemyDefeat(e); } });
skillEffects.push(new SkillEffect(px, py, 'squareLightning'));
addLog('✨ 时序攻击释放!消耗' + consumedChrono + 'N 造成' + totalDamage + '伤害');
floatingTexts.push(new FloatingText(px, py - 20, '时序攻击!-' + consumedChrono + 'N', '#5dc8ff'));
saveGame();
}
function onEnemyDefeat(enemy) {
enemy.alive = false;
if (!enemy.isBoss) {
enemy.revivalTimer = 1800;
} else {
enemy.revivalTimer = 20000;
}
// 记录击败的Boss
if (enemy.isBoss && !state.defeatedBosses.includes(enemy.name)) {
state.defeatedBosses.push(enemy.name);
}
addLog('💀 ' + enemy.name + ' 被击败!');
state.totalKills++;
state.exp += enemy.exp;
while (state.exp >= state.expToNext) { state.exp -= state.expToNext;
state.level++;
state.expToNext = Math.floor(state.expToNext * 1.5);
state.atk += 3;
state.def += 2;
state.maxHp += 20;
state.hp = getTotalMaxHp();
floatingTexts.push(new FloatingText(player.worldPos.x, player.worldPos.y - 30, '升级!Lv.' + state.level,
'#ffdd00')); }
state.chronoPoints += enemy.chrono;
floatingTexts.push(new FloatingText(enemy.worldPos.x, enemy.worldPos.y - 25, '+' + enemy.chrono + 'N',
'#ffd700'));
if (state.chronoPoints >= 10000) { const t = Math.floor(state.chronoPoints / 10000) * 10000;
state.lifeBank += t;
state.chronoPoints -= t;
floatingTexts.push(new FloatingText(player.worldPos.x, player.worldPos.y - 20, '→转给队长+' + t + 'N',
'#7ec8e3')); }
const dropRoll = Math.random();
if (dropRoll < 0.60) dropLoot(enemy.worldPos.x, enemy.worldPos.y, generateEquipment(enemy.level || 1));
else if (dropRoll < 0.6 + 0.40) dropLoot(enemy.worldPos.x, enemy.worldPos.y, generatePotion(20 + (enemy.level ||
1) * 5));
if (enemy.isBoss) {
const bossDropCount = 1 + Math.floor(Math.random() * 2);
for (let i = 0; i < bossDropCount; i++) dropLoot(enemy.worldPos.x + (Math.random() - 0.5) * 30, enemy
.worldPos.y + (Math.random() - 0.5) * 30, generateEquipment(enemy.level || 1, '精良'));
if (Math.random() < 0.28) {
const mcSlot = ['weapon', 'armor', 'helmet', 'boots', 'accessory'][Math.floor(Math.random() *
5)];
const mcEquip = generateMaChaoEquipment(mcSlot);
dropLoot(enemy.worldPos.x + (Math.random() - 0.5) * 35, enemy.worldPos.y + (Math.random() -
0.5) * 35, mcEquip);
addFloatingText(enemy.worldPos.x, enemy.worldPos.y - 35, '🐴 马超套装部件掉落!' + mcEquip.name,
'#FFD700');
}
if (enemy.isFinalBoss) spawnGoldenJar(enemy.row - 2, enemy.col);
if (enemy.name === '雄英组·血色猎手' && !state.autoRecycleArtifact && Math.random() < 0.50) {
state.autoRecycleArtifact = true;
state.autoRecycleEnabled = true;
addFloatingText(enemy.worldPos.x, enemy.worldPos.y - 40, '🌀 血色猎手掉落:虚空回收炉!', '#ffd700');
updateAutoRecycleButton();
}
}
saveGame();
}
function addLog(t, ic) { battleLog.push({ text: t, isChrono: ic }); if (battleLog.length > MAX_BATTLE_LOG) battleLog
.shift(); }
function addFloatingText(wx, wy, t, c) {
floatingTexts.push(new FloatingText(wx, wy, t, c));
if (floatingTexts.length > MAX_FLOATING_TEXTS) floatingTexts.shift();
}
// ==================== 一键回收全部功能 ====================
function oneClickRecycleAll() {
const recycleLegendary = document.getElementById('recycleLegendary').checked;
const recycleMaChao = document.getElementById('recycleMaChao').checked;
const equippedNames = [];
for (const s in state.equipment) {
if (state.equipment[s]) equippedNames.push(state.equipment[s].name);
}
let recycledCount = 0;
let skippedLegendary = 0;
let skippedMaChao = 0;
let skippedEquipped = 0;
for (let i = state.bag.length - 1; i >= 0; i--) {
const item = state.bag[i];
if (!item) continue;
if (item.type === 'equipment' && equippedNames.includes(item.name)) {
skippedEquipped++;
continue;
}
let shouldRecycle = true;
if (item.type === 'equipment') {
if (item.isMaChaoSet && !recycleMaChao) {
skippedMaChao++;
shouldRecycle = false;
}
if (item.rarity === '传说' && !item.isMaChaoSet && !recycleLegendary) {
skippedLegendary++;
shouldRecycle = false;
}
if (item.isSet) {
skippedLegendary++;
shouldRecycle = false;
}
if (item.special === 'centennial') {
skippedLegendary++;
shouldRecycle = false;
}
}
if (shouldRecycle) {
state.bag.splice(i, 1);
if (item.type === 'equipment' && Math.random() < 0.5) {
state.fragments++;
}
recycledCount++;
}
}
let msg = '♻️ 一键回收完成:回收了 ' + recycledCount + ' 件物品';
if (skippedLegendary > 0) msg += ',跳过 ' + skippedLegendary + ' 件传说/套装';
if (skippedMaChao > 0) msg += ',跳过 ' + skippedMaChao + ' 件马超装备';
if (skippedEquipped > 0) msg += ',跳过 ' + skippedEquipped + ' 件已装备';
addFloatingText(player.worldPos.x, player.worldPos.y - 30, msg, '#e67e22');
renderBagPanel();
saveGame();
}
window.oneClickRecycleAll = oneClickRecycleAll;
let autoRecycleTimer = 0;
function doAutoRecycle() {
if (!state.autoRecycleArtifact || !state.autoRecycleEnabled) return;
if (state.bag.length === 0) return;
const equippedNames = [];
for (const s in state.equipment) { if (state.equipment[s]) equippedNames.push(state.equipment[s].name); }
let recycledCount = 0;
for (let i = state.bag.length - 1; i >= 0; i--) {
const item = state.bag[i];
if (!item) continue;
if (item.type === 'equipment') {
if (item.rarity === '传说') continue;
if (item.isSet) continue;
if (equippedNames.includes(item.name)) continue;
state.bag.splice(i, 1);
if (Math.random() < 0.5) { state.fragments++; }
recycledCount++;
} else if (item.type === 'potion') {
state.bag.splice(i, 1);
recycledCount++;
}
}
if (recycledCount > 0) {
addFloatingText(player.worldPos.x, player.worldPos.y - 20, '♻️自动回收了 ' + recycledCount + ' 件物品', '#e67e22');
}
}
function drawMist() {
const g = ctx.createRadialGradient(VIEW_W / 2, VIEW_H / 2, 120, VIEW_W / 2, VIEW_H / 2, 400);
g.addColorStop(0, 'rgba(0,0,0,0)');
g.addColorStop(0.7, 'rgba(0,0,0,0.6)');
g.addColorStop(1, 'rgba(0,0,0,0.9)');
ctx.fillStyle = g;
ctx.fillRect(0, 0, VIEW_W, VIEW_H);
}
function getTerrainColor(t) {
const c = { 0: '#1a1a2a', 1: '#2a2a3a', 2: '#3a3a4a', 3: '#2d2d3d', 4: '#252535', 5: '#4a3a2a', 6: '#2e2e3e',
7: '#1e1e2e', 8: '#5a7a9a', 9: '#8b4513', 10: '#1a1a2a', 11: '#1a2a4a', 12: '#3a3a3a', 13: '#2a2a2a',
14: '#2a2a3a', 15: '#2d2d3d' };
return c[t] || '#1a1a2a';
}
function drawMap() {
const sc = Math.floor(cameraX / TILE_SIZE) - 1,
ec = Math.ceil((cameraX + VIEW_W) / TILE_SIZE) + 1;
const sr = Math.floor(cameraY / TILE_SIZE) - 1,
er = Math.ceil((cameraY + VIEW_H) / TILE_SIZE) + 1;
for (let row = sr; row <= er; row++) {
for (let col = sc; col <= ec; col++) {
if (row < 0 || row >= MAP_ROWS || col < 0 || col >= MAP_COLS) continue;
const { x, y } = worldToScreen(col * TILE_SIZE, row * TILE_SIZE);
const terrain = mapData[row][col];
ctx.fillStyle = getTerrainColor(terrain);
ctx.fillRect(x, y, TILE_SIZE, TILE_SIZE);
}
}
}
function updateBattleUI(enemy) {
document.getElementById('battleUI').classList.add('active');
document.getElementById('enemyName').textContent = '敌人: ' + enemy.name + (enemy.isBoss ? ' [BOSS]' : '');
document.getElementById('hpBar').style.width = Math.max(0, (enemy.hp / enemy.maxHp) * 100) + '%';
document.getElementById('hpText').textContent = enemy.hp + '/' + enemy.maxHp;
const ld = document.getElementById('damageLog');
ld.innerHTML = '';
battleLog.forEach(l => { const d = document.createElement('div');
d.textContent = l.text; if (l.isChrono) d.className = 'chrono-log';
ld.appendChild(d); });
}
function updateStatusUI() {
document.getElementById('playerHpDisplay').textContent = Math.max(0, state.hp) + '/' + getTotalMaxHp();
document.getElementById('levelDisplay').textContent = state.level;
document.getElementById('atkDisplay').textContent = getTotalAtk();
document.getElementById('defDisplay').textContent = getTotalDef();
document.getElementById('chronoDisplay').textContent = state.chronoPoints + ' N';
document.getElementById('bankDisplay').textContent = state.lifeBank + ' N';
document.getElementById('fragDisplay').textContent = state.fragments + '/1000';
const cd = SKILL_COOLDOWN - (gameFrame - lastSkillTime);
document.getElementById('skillCd').textContent = cd <= 0 ? '就绪' : Math.ceil(cd / 60) + 's';
}
function updateAutoRecycleButton() {
const btn = document.getElementById('btnAutoRecycle');
if (state.autoRecycleArtifact) { btn.style.display = 'flex'; if (state.autoRecycleEnabled) { btn.classList.add(
'recycle-on');
btn.querySelector('.icon').textContent = '♻️';
btn.childNodes[btn.childNodes.length - 1].textContent = '回收中'; } else { btn.classList.remove(
'recycle-on');
btn.querySelector('.icon').textContent = '♻️';
btn.childNodes[btn.childNodes.length - 1].textContent = '暂停'; } } else { btn.style.display =
'none'; }
}
function updateAutoFarmButton() {
const btn = document.getElementById('btnAutoFarm');
if (state.autoFarmEnabled || hasFullSet() || hasMaChaoSet()) {
btn.classList.add('auto-on');
if (hasMaChaoSet()) {
btn.classList.add('ma-chao-glow');
btn.querySelector('.icon').textContent = '🐴';
btn.childNodes[btn.childNodes.length - 1].textContent = '马超';
} else {
btn.classList.remove('ma-chao-glow');
btn.querySelector('.icon').textContent = '⚡';
btn.childNodes[btn.childNodes.length - 1].textContent = '挂机中';
}
} else { btn.classList.remove('auto-on');
btn.classList.remove('ma-chao-glow');
btn.querySelector('.icon').textContent = '⚡';
btn.childNodes[btn.childNodes.length - 1].textContent = '挂机'; }
}
// ==================== 输入事件(修复滚动问题)====================
// 关键修复:使用getBoundingClientRect时考虑页面滚动,阻止canvas上的操作触发页面滚动
function getCanvasScale() {
const rect = canvas.getBoundingClientRect();
const scaleX = VIEW_W / rect.width;
const scaleY = VIEW_H / rect.height;
return { rect, scaleX, scaleY };
}
function handleCanvasClick(clientX, clientY) {
const { rect, scaleX, scaleY } = getCanvasScale();
const mx = (clientX - rect.left) * scaleX;
const my = (clientY - rect.top) * scaleY;
// 边界检查
if (mx < 0 || mx > VIEW_W || my < 0 || my > VIEW_H) return;
const w = screenToWorld(mx, my);
if (checkGoldenJarClick(w)) return;
const tile = worldToTile(w.x, w.y);
setPlayerTarget(tile.row, tile.col);
}
canvas.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
handleCanvasClick(e.clientX, e.clientY);
});
canvas.addEventListener('touchstart', e => {
e.preventDefault();
e.stopPropagation();
const t = e.touches[0];
if (t) {
handleCanvasClick(t.clientX, t.clientY);
}
}, { passive: false });
// 阻止canvas上的滚轮事件触发页面滚动(允许在canvas外的区域滚动)
canvas.addEventListener('wheel', e => {
e.preventDefault();
e.stopPropagation();
}, { passive: false });
// 阻止canvas上的触摸移动触发页面滚动
canvas.addEventListener('touchmove', e => {
e.preventDefault();
e.stopPropagation();
}, { passive: false });
function moveDir(dr, dc) { setPlayerTarget(player.row + dr, player.col + dc); }
document.getElementById('btnUp').addEventListener('pointerdown', e => { e.preventDefault();
e.stopPropagation();
moveDir(-2, 0); });
document.getElementById('btnDown').addEventListener('pointerdown', e => { e.preventDefault();
e.stopPropagation();
moveDir(2, 0); });
document.getElementById('btnLeft').addEventListener('pointerdown', e => { e.preventDefault();
e.stopPropagation();
moveDir(0, -2); });
document.getElementById('btnRight').addEventListener('pointerdown', e => { e.preventDefault();
e.stopPropagation();
moveDir(0, 2); });
const keysDown = {};
document.addEventListener('keydown', e => {
keysDown[e.key] = true;
if (e.key === 'b' || e.key === 'B') { const p = document.getElementById('bagPanel');
p.classList.contains('active') ? closeBagPanel() : openBagPanel(); }
if (e.key === 'e' || e.key === 'E') { const p = document.getElementById('equipPanel');
p.classList.contains('active') ? closeEquipPanel() : openEquipPanel(); }
if (e.key === 'h' || e.key === 'H') healPlayer();
if (e.key === 'q' || e.key === 'Q') useSkill();
if (e.key === ' ') pickupNearbyLoot();
});
document.addEventListener('keyup', e => { keysDown[e.key] = false; });
function handleKB() { let dr = 0,
dc = 0; if (keysDown['w'] || keysDown['W'] || keysDown['ArrowUp']) dr = -2; if (keysDown['s'] || keysDown[
'S'] || keysDown['ArrowDown']) dr = 2; if (keysDown['a'] || keysDown['A'] || keysDown['ArrowLeft'])
dc = -2; if (keysDown['d'] || keysDown['D'] || keysDown['ArrowRight']) dc = 2; if (dr || dc)
setPlayerTarget(player.row + dr, player.col + dc); }
function pickupNearbyLoot() { groundLoot.forEach(loot => { if (!loot.picked && loot.frame >= loot.minAge && dist(
player.worldPos, loot) < 145) { if (addToBag(loot.item)) { loot.picked = true;
addFloatingText(loot.x, loot.y - 15, '拾取 ' + loot.item.name, loot.color); } } }); }
document.getElementById('btnBag').addEventListener('click', () => { const p = document.getElementById('bagPanel');
p.classList.contains('active') ? closeBagPanel() : openBagPanel(); });
document.getElementById('btnEquip').addEventListener('click', () => { const p = document.getElementById(
'equipPanel');
p.classList.contains('active') ? closeEquipPanel() : openEquipPanel(); });
document.getElementById('btnHeal').addEventListener('click', () => healPlayer());
document.getElementById('btnAutoFarm').addEventListener('click', () => {
if (hasFullSet() || hasMaChaoSet()) {
addFloatingText(player.worldPos.x, player.worldPos.y - 25, hasMaChaoSet() ? '🐴 马超套装自动挂机已激活' :
'套装自动挂机已激活', hasMaChaoSet() ? '#FFD700' : '#f39c12');
return;
}
state.autoFarmEnabled = !state.autoFarmEnabled;
updateAutoFarmButton();
addFloatingText(player.worldPos.x, player.worldPos.y - 25, state.autoFarmEnabled ? '⚡ 自动挂机开启' :
'⏹️ 自动挂机关闭', state.autoFarmEnabled ? '#2ecc71' : '#ff4444');
saveGame();
});
document.getElementById('btnAutoRecycle').addEventListener('click', () => {
if (!state.autoRecycleArtifact) { addFloatingText(player.worldPos.x, player.worldPos.y - 25, '尚未获得虚空回收炉',
'#e67e22'); return; }
state.autoRecycleEnabled = !state.autoRecycleEnabled;
updateAutoRecycleButton();
addFloatingText(player.worldPos.x, player.worldPos.y - 25, state.autoRecycleEnabled ? '♻️ 自动回收开启' :
'⏸️ 自动回收暂停', state.autoRecycleEnabled ? '#2ecc71' : '#ff4444');
saveGame();
});
document.getElementById('btnSkill').addEventListener('click', () => useSkill());
function healPlayer() {
const pi = state.bag.findIndex(i => i && i.type === 'potion');
if (pi >= 0) { usePotion(pi); return; }
if (state.lifeBank >= 50) { state.lifeBank -= 50;
state.hp = Math.min(getTotalMaxHp(), state.hp + 50);
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '💎转给队长治疗 +50', '#00ddff'); } else
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '没有药水且转给队长不足', '#ff4444');
}
let autoSaveTimer = 0;
let safetyCleanupTimer = 0;
function gameLoop(timestamp) {
if (!gameLoopRunning) return;
try {
gameFrame++;
frameCountForFPS++;
fpsCheckTimer++;
if (fpsCheckTimer >= 3600) {
fpsCheckTimer = 0;
if (frameCountForFPS < 1800) {
console.warn('[游戏安全] 帧率过低:' + frameCountForFPS + '帧/分钟,尝试清理...');
if (groundLoot.length > 50) groundLoot.splice(0, Math.floor(groundLoot.length * 0.3));
if (floatingTexts.length > 30) floatingTexts.splice(0, Math.floor(floatingTexts.length * 0.3));
}
frameCountForFPS = 0;
}
handleKB();
updatePlayer();
updateCamera(player.worldPos.x, player.worldPos.y);
updateGroundLoot();
autoRecycleTimer++;
if (autoRecycleTimer >= 300) { autoRecycleTimer = 0;
doAutoRecycle(); }
safetyCleanupTimer++;
if (safetyCleanupTimer >= CLEANUP_INTERVAL) {
safetyCleanupTimer = 0;
if (groundLoot.length > MAX_GROUND_LOOT) {
groundLoot.splice(0, Math.floor(groundLoot.length * 0.25));
}
if (floatingTexts.length > MAX_FLOATING_TEXTS) {
floatingTexts.splice(0, Math.floor(floatingTexts.length * 0.2));
}
if (skillEffects.length > MAX_SKILL_EFFECTS) {
skillEffects.splice(0, Math.floor(skillEffects.length * 0.3));
}
}
const aliveEnemies = enemies.filter(e => e.alive);
let nearest = null,
nd = Infinity;
aliveEnemies.forEach(e => { const d = dist(player.worldPos, e.worldPos); if (d < nd) { nd = d;
nearest = e; } });
let atkRange;
if (hasMaChaoSet()) {
atkRange = 80;
} else if (hasFullSet()) {
atkRange = 60;
} else {
atkRange = 45;
}
if (nearest && nd < atkRange) {
currentBattleEnemy = nearest;
if (player.atkTimer <= 0) playerAttack(nearest);
if (gameFrame % 30 === 0 && player.dragonActive) dragonAttack(nearest);
if (!nearest.alive) currentBattleEnemy = null;
if (nearest.alive) updateBattleUI(nearest);
else document.getElementById('battleUI').classList.remove('active');
} else { currentBattleEnemy = null;
document.getElementById('battleUI').classList.remove('active'); }
const autoFarmActive = hasFullSet() || hasMaChaoSet() || state.autoFarmEnabled;
if (autoFarmActive) {
let nearestLoot = null,
lootDist = Infinity;
for (const loot of groundLoot) {
if (!loot.picked && loot.frame >= loot.minAge) {
const d = dist(player.worldPos, loot);
if (d < lootDist) { lootDist = d;
nearestLoot = loot; }
}
}
const lootPickupRange = 35;
const lootChaseRange = hasMaChaoSet() ? 180 : 120;
if (nearestLoot && lootDist < lootChaseRange) {
if (lootDist > lootPickupRange) {
const tile = worldToTile(nearestLoot.x, nearestLoot.y);
setPlayerTarget(tile.row, tile.col);
farmPatrolTarget = null;
}
} else if (nearest && nd < (hasMaChaoSet() ? 300 : hasFullSet() ? 220 : 160)) {
setPlayerTarget(nearest.row, nearest.col);
farmPatrolTarget = null;
} else if (!nearest || nd >= (hasMaChaoSet() ? 300 : hasFullSet() ? 220 : 160)) {
if (!farmPatrolTarget || dist(player.worldPos, farmPatrolTarget) < 8) {
const nr = Math.max(5, Math.min(MAP_ROWS - 5, player.row + (Math.random() - 0.5) * 20));
const nc = Math.max(5, Math.min(MAP_COLS - 5, player.col + (Math.random() - 0.5) * 20));
farmPatrolTarget = tileToWorld(nr, nc);
setPlayerTarget(nr, nc);
}
}
}
enemies.forEach(e => {
try { e.update(player); } catch (err) { console.warn('[游戏安全] 敌人更新异常:', err.message); }
});
skillEffects = skillEffects.filter(f => { f.update(); return !f.isDead(); });
floatingTexts = floatingTexts.filter(f => { f.update(); return !f.isDead(); });
updateStatusUI();
updateAutoFarmButton();
updateAutoRecycleButton();
if (state.hp <= 0) {
state.hp = getTotalMaxHp();
state.chronoPoints = Math.floor(state.chronoPoints * 0.5);
player.worldPos = tileToWorld(75, 70);
player.row = 75;
player.col = 70;
player.targetRow = 75;
player.targetCol = 70;
addFloatingText(player.worldPos.x, player.worldPos.y - 30, '💀 死亡!损失一半时序', '#ff4444');
saveGame();
}
autoSaveTimer++;
if (autoSaveTimer >= 1800) { autoSaveTimer = 0;
saveGame(); }
// 始终渲染,即使在Elementor中也不跳过
ctx.clearRect(0, 0, VIEW_W, VIEW_H);
drawMap();
drawGroundLoot(ctx);
enemies.forEach(e => { if (e.alive) e.draw(ctx); });
skillEffects.forEach(f => f.draw(ctx));
drawGoldenJar(ctx);
floatingTexts.forEach(f => f.draw(ctx));
drawPlayer(ctx);
drawMist();
} catch (err) {
console.error('[游戏安全] 游戏循环异常:', err.message, err.stack);
}
// 保持所有敌人(包括等待重生的)
enemies = enemies.filter(e => e.alive || e.revivalTimer > 0);
if (gameLoopRunning) {
requestAnimationFrame(gameLoop);
}
}
updateAutoFarmButton();
updateAutoRecycleButton();
if (state.autoRecycleArtifact) document.getElementById('btnAutoRecycle').style.display = 'flex';
updateBagTopSection();
updateCanvasDisplaySize();
requestAnimationFrame(gameLoop);
console.log('[游戏安全] 废都41·首秀之夜 已启动 - Elementor兼容版');
console.log('[游戏安全] 修复:允许页面滚动 | Canvas防滚动事件 | 响应式尺寸管理 | ResizeObserver | 滚动后重绘');
console.log('[游戏安全] 马超套装:BOSS掉落 | ATK+100 DEF+60 HP+1000 攻速+80% | 虚空回收炉:98碎片合成');
console.log('[游戏安全] 一键回收:背包面板内勾选回收传说/马超装备 | 名画链接:https://ccsit.cc/wp-content/uploads/2026/05/2026518.jpg');
})();
</script>
</body>
</html>
可以TXT保存,修改其中的角色名字,并将后缀修改为html用浏览器打开。
注:不建议随便复制后于本地电脑运行陌生代码。
发表回复