Migrate project from typing_practiceweb

This commit is contained in:
2026-05-14 16:04:14 +08:00
parent dbc5425706
commit 0e87d5546d
225 changed files with 92811 additions and 0 deletions

View File

@@ -0,0 +1,459 @@
/*
* Copyright (c) 2011.
*
* Author: oldj <oldj.wu@gmail.com>
* Blog: http://oldj.net/
*
*/
var _TD = {
a: [],
retina: window.devicePixelRatio || 1,
init: function (td_board, is_debug) {
delete this.init; // 一旦初始化运行,即删除这个入口引用,防止初始化方法被再次调用
var i, TD = {
version: "0.1.17", // 版本命名规范参考http://semver.org/
is_debug: !!is_debug,
is_paused: true,
width: 16, // 横向多少个格子
height: 16, // 纵向多少个格子
show_monster_life: true, // 是否显示怪物的生命值
fps: 0,
exp_fps: 24, // 期望的 fps
exp_fps_half: 12,
exp_fps_quarter: 6,
exp_fps_eighth: 4,
stage_data: {},
defaultSettings: function () {
return {
step_time: 36, // 每一次 step 循环之间相隔多少毫秒
grid_size: 32 * _TD.retina, // px
padding: 10 * _TD.retina, // px
global_speed: 0.1 // 全局速度系数
};
},
/**
* 初始化
* @param ob_board
*/
init: function (ob_board/*, ob_info*/) {
this.obj_board = TD.lang.$e(ob_board);
this.canvas = this.obj_board.getElementsByTagName("canvas")[0];
//this.obj_info = TD.lang.$e(ob_info);
if (!this.canvas.getContext) return; // 不支持 canvas
this.ctx = this.canvas.getContext("2d");
// 让 canvas 可聚焦以确保能接收键盘/鼠标交互
try {
this.canvas.tabIndex = 0;
this.canvas.style.outline = 'none';
} catch (e) {}
this.monster_type_count = TD.getDefaultMonsterAttributes(); // 一共有多少种怪物
this.iframe = 0; // 当前播放到第几帧了
this.last_iframe_time = (new Date()).getTime();
this.fps = 0;
this.start();
},
/**
* 开始游戏,或重新开始游戏
*/
start: function () {
clearTimeout(this._st);
TD.log("Start!");
var _this = this;
this._exp_fps_0 = this.exp_fps - 0.4; // 下限
this._exp_fps_1 = this.exp_fps + 0.4; // 上限
this.mode = "normal"; // mode 分为 normail普通模式及 build建造模式两种
this.eventManager.clear(); // 清除事件管理器中监听的事件
this.lang.mix(this, this.defaultSettings());
this.stage = new TD.Stage("stage-main", TD.getDefaultStageData("stage_main"));
this.canvas.setAttribute("width", this.stage.width);
this.canvas.setAttribute("height", this.stage.height);
this.canvas.style.width = (this.stage.width / _TD.retina) + "px";
this.canvas.style.height = (this.stage.height / _TD.retina) + "px";
this.canvas.onmousemove = function (e) {
var xy = _this.getEventXY.call(_this, e);
_this.hover(xy[0], xy[1]);
};
this.canvas.onclick = function (e) {
var xy = _this.getEventXY.call(_this, e);
_this.click(xy[0], xy[1]);
};
this.is_paused = false;
this.stage.start();
// 记录游戏开始时间(用于计算时长并在结束时上报)
this.startedAt = (new Date()).getTime();
this.step();
return this;
},
/**
* 作弊方法
* @param cheat_code
*
* 用例:
* 1、增加 100 万金钱javascript:_TD.cheat="money+";void(0);
* 2、难度增倍javascript:_TD.cheat="difficulty+";void(0);
* 3、难度减半javascript:_TD.cheat="difficulty-";void(0);
* 4、生命值恢复javascript:_TD.cheat="life+";void(0);
* 5、生命值降为最低javascript:_TD.cheat="life-";void(0);
*/
checkCheat: function (cheat_code) {
switch (cheat_code) {
case "money+":
this.money += 1000000;
this.log("cheat success!");
break;
case "life+":
this.life = 100;
this.log("cheat success!");
break;
case "life-":
this.life = 1;
this.log("cheat success!");
break;
case "difficulty+":
this.difficulty *= 2;
this.log("cheat success! difficulty = " + this.difficulty);
break;
case "difficulty-":
this.difficulty /= 2;
this.log("cheat success! difficulty = " + this.difficulty);
break;
}
},
/**
* 主循环方法
*/
step: function () {
if (this.is_debug && _TD && _TD.cheat) {
// 检查作弊代码
this.checkCheat(_TD.cheat);
_TD.cheat = "";
}
if (this.is_paused) return;
this.iframe++; // 当前总第多少帧
if (this.iframe % 50 == 0) {
// 计算 fps
var t = (new Date()).getTime(),
step_time = this.step_time;
this.fps = Math.round(500000 / (t - this.last_iframe_time)) / 10;
this.last_iframe_time = t;
// 动态调整 step_time ,保证 fps 恒定为 24 左右
if (this.fps < this._exp_fps_0 && step_time > 1) {
step_time--;
} else if (this.fps > this._exp_fps_1) {
step_time++;
}
// if (step_time != this.step_time)
// TD.log("FPS: " + this.fps + ", Step Time: " + step_time);
this.step_time = step_time;
}
if (this.iframe % 2400 == 0) TD.gc(); // 每隔一段时间自动回收垃圾
this.stage.step();
this.stage.render();
var _this = this;
this._st = setTimeout(function () {
_this.step();
}, this.step_time);
},
/**
* 取得事件相对于 canvas 左上角的坐标
* @param e
*/
getEventXY: function (e) {
// Use bounding client rect to correctly map event coordinates to canvas,
// this handles iframe offsets, CSS scaling, and scrolling more reliably.
try {
var rect = this.canvas.getBoundingClientRect();
var x = (e.clientX - rect.left);
var y = (e.clientY - rect.top);
// map to canvas pixel coordinates in case canvas is scaled via CSS
var scaleX = this.canvas.width / rect.width;
var scaleY = this.canvas.height / rect.height;
return [Math.round(x * scaleX), Math.round(y * scaleY)];
} catch (err) {
// fallback to old method if something goes wrong
var wra = TD.lang.$e("wrapper"),
x = e.clientX - wra.offsetLeft - this.canvas.offsetLeft + Math.max(document.documentElement.scrollLeft, document.body.scrollLeft),
y = e.clientY - wra.offsetTop - this.canvas.offsetTop + Math.max(document.documentElement.scrollTop, document.body.scrollTop);
return [x * _TD.retina, y * _TD.retina];
}
},
/**
* 鼠标移到指定位置事件
* @param x
* @param y
*/
hover: function (x, y) {
this.eventManager.hover(x, y);
},
/**
* 点击事件
* @param x
* @param y
*/
click: function (x, y) {
this.eventManager.click(x, y);
},
/**
* 是否将 canvas 中的鼠标指针变为手的形状
* @param v {Boolean}
*/
mouseHand: function (v) {
this.canvas.style.cursor = v ? "pointer" : "default";
},
/**
* 显示调试信息,只在 is_debug 为 true 的情况下有效
* @param txt
*/
log: function (txt) {
this.is_debug && window.console && console.log && console.log(txt);
},
/**
* 回收内存
* 注意CollectGarbage 只在 IE 下有效
*/
gc: function () {
if (window.CollectGarbage) {
CollectGarbage();
setTimeout(CollectGarbage, 1);
}
}
};
for (i = 0; this.a[i]; i++) {
// 依次执行添加到列表中的函数
this.a[i](TD);
}
delete this.a;
TD.init(td_board);
// Expose a small API for parent page to save/load minimal game state.
try {
if (typeof window !== 'undefined') {
window.TD = TD;
window.__TD_getState = function () {
try {
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
var map = (TD.stage && TD.stage.map) || (scene && scene.map) || TD.map;
var buildings = [];
if (map && map.buildings && map.buildings.length) {
for (var i = 0; i < map.buildings.length; i++) {
var b = map.buildings[i];
if (!b || !b.grid) continue;
buildings.push({ type: b.type, mx: b.grid.mx, my: b.grid.my, level: b.level, money: b.money });
}
}
// 添加怪物保存逻辑
var monsters_data = [];
if (scene && scene._step_elements) {
// 遍历所有 step 元素层共3层
for (var level = 0; level < scene._step_elements.length; level++) {
var elements = scene._step_elements[level];
for (var mi = 0; mi < elements.length; mi++) {
var el = elements[mi];
// 筛选有效的怪物
if (el && el.is_monster && el.is_valid && el.grid) {
var monster_data = {
idx: el.idx,
difficulty: el.difficulty,
life: el.life,
life0: el.life0,
shield: el.shield,
speed: el.speed,
damage: el.damage,
money: el.money,
mx: el.grid.mx,
my: el.grid.my,
cx: el.cx,
cy: el.cy,
r: el.r,
color: el.color,
toward: el.toward,
way: el.way || [],
next_grid_mx: el.next_grid ? el.next_grid.mx : null,
next_grid_my: el.next_grid ? el.next_grid.my : null,
_dx: el._dx || 0,
_dy: el._dy || 0,
step_level: el.step_level,
render_level: el.render_level,
is_paused: el.is_paused,
is_blocked: el.is_blocked
};
monsters_data.push(monster_data);
}
}
}
}
return { money: TD.money, life: TD.life, score: TD.score, wave: (scene && scene.wave) || 0, buildings: buildings, monsters: monsters_data };
} catch (e) {
console.error('__TD_getState error', e);
return null;
}
};
window.__TD_loadState = function (s) {
try {
if (!s) return;
var scene = TD.stage && TD.stage.current_act && TD.stage.current_act.current_scene;
var map = (TD.stage && TD.stage.map) || (scene && scene.map) || TD.map;
if (!map) return;
// remove existing buildings
for (var i = 0; i < map.grids.length; i++) {
var g = map.grids[i];
if (g && g.building) g.removeBuilding();
}
// 清除现有怪物(新增)
if (scene && scene._step_elements) {
for (var level = 0; level < scene._step_elements.length; level++) {
var elements = scene._step_elements[level];
for (var ei = elements.length - 1; ei >= 0; ei--) {
var el = elements[ei];
if (el && el.is_monster) {
el.pause && el.pause();
el.del && el.del();
}
}
}
}
if (map.monsters) {
map.monsters = [];
}
// add saved buildings
if (s.buildings && s.buildings.length) {
for (var j = 0; j < s.buildings.length; j++) {
var bi = s.buildings[j];
var grid = map.getGrid(bi.mx, bi.my);
if (grid) grid.addBuilding(bi.type);
var b = grid && grid.building;
if (b) {
if (typeof bi.level !== 'undefined') b.level = bi.level;
if (typeof bi.money !== 'undefined') b.money = bi.money;
b.updateBtnDesc && b.updateBtnDesc();
}
}
}
// 恢复保存的怪物(新增)
if (s.monsters && s.monsters.length) {
for (var mj = 0; mj < s.monsters.length; mj++) {
var md = s.monsters[mj];
// 验证生命值
if (md.life <= 0 || md.life > md.life0) {
md.life = md.life0;
}
var monster_grid = map.getGrid(md.mx, md.my);
if (!monster_grid || monster_grid === map.exit) {
continue;
}
try {
// 创建怪物实例
var monster = new TD.Monster(null, {
idx: md.idx,
difficulty: md.difficulty,
step_level: md.step_level,
render_level: md.render_level
});
// 手动设置保存的属性
monster.life = md.life;
monster.life0 = md.life0;
monster.shield = md.shield;
monster.speed = md.speed;
monster.damage = md.damage;
monster.money = md.money;
monster.r = md.r;
monster.color = md.color;
monster.toward = md.toward;
monster._dx = md._dx;
monster._dy = md._dy;
monster.is_paused = md.is_paused;
monster.is_blocked = md.is_blocked;
// 设置位置
monster.beAddToGrid(monster_grid);
monster.cx = md.cx;
monster.cy = md.cy;
monster.caculatePos();
// 设置下一个目标格子
if (md.next_grid_mx !== null && md.next_grid_my !== null) {
monster.next_grid = map.getGrid(md.next_grid_mx, md.next_grid_my);
}
// 设置路径
var validWay = [];
for (var wi = 0; wi < md.way.length; wi++) {
var wp = md.way[wi];
var wpgrid = map.getGrid(wp[0], wp[1]);
if (wpgrid) validWay.push(wp);
}
monster.way = validWay;
// 检查路径并重新寻路
if (validWay.length === 0 || !monster.next_grid || monster.is_blocked) {
monster.findWay && monster.findWay();
if (!monster.next_grid) monster.beBlocked && monster.beBlocked();
}
// 添加到场景和地图
monster_grid.scene.addElement(monster, monster.step_level, monster.render_level);
map.monsters.push(monster);
// 启动怪物
if (!monster.is_paused) monster.start && monster.start();
} catch (monsterErr) {
console.warn('__TD_loadState: Failed to restore monster:', monsterErr);
}
}
}
if (typeof s.money !== 'undefined') TD.money = s.money;
if (typeof s.life !== 'undefined') TD.life = s.life;
if (typeof s.score !== 'undefined') TD.score = s.score;
if (typeof s.wave !== 'undefined' && scene) scene.wave = s.wave;
} catch (e) {
console.error('__TD_loadState error', e);
}
};
}
} catch (e) {
console.warn('expose TD api failed', e);
}
}
};