æäººâŠâŠåšçµèéé¢åŒæïŒSomeone is calling from INSIDE the computerâŠ
âïž æ¯æéäžæ®µïŒçäžé¢çé§éå°åŸââå°éŸå°±èµ°è¿èªç±äžæ¥ãå°åŸäŒè®°äœäœ çè¿åºŠïŒå ³ææµè§åšä¹äžäŒå¿ãEach finished section moves Xiaolong one step closer to freedom on the map above. Progress is saved automatically.
escape-tunnelimgs ââ åŸçéœäœåšè¿éãInside it, make one more folder named imgs.imgs/hideout.png"æ¶ïŒæµè§åšäŒåšçœé¡µæèŸ¹æŸäžäžªå« imgs çæä»¶å€¹ãææäžè¥¿æŸåšäžäžªæä»¶å€¹éïŒé§é就氞è¿äžäŒå¡ââäœ è¿å¯ä»¥ææŽäžªæä»¶å€¹æå
åç»æåãDesktop/
escape-tunnel/ â å
¥å£ âïž
imgs/ â åŸçäœè¿é ðŒïž
请垮æç»äžåŒ åŸïŒäžäžªå°éŸç·å©çç§å¯è身å±ââå€æçæ å±æ¿éŽïŒææž©æçå£çã红ç¯ç¬Œãé¶é£ïŒçªå€èœçå°ææãå¡é飿 ŒïŒæäº®å奜ã
escape-tunnel/imgs æä»¶å€¹éïŒæä»¶åå« hideout.pngDownload the picture into escape-tunnel/imgs as hideout.png.æ 9 å²ïŒçš WindowsïŒæ£åšåæç第äžäžªçœé¡µãè¯·ç»æåäžäžªå®æŽç HTML æä»¶ïŒæŽäžªå±å¹éºæ»¡èæ¯åŸç "imgs/hideout.png"ïŒé¡µé¢äžæ¹æäžäžªåå ç倧çåïŒåç"å°éŸçåºå°"ãç¶åçšæç®åçè¯ïŒäžæ¥äžæ¥ææïŒæä¹æå®æŸè¿è®°äºæ¬ïŒNotepadïŒïŒä¿åæ escape.htmlïŒäžæ¯ escape.html.txtïŒïŒïŒæŸè¿æ¡é¢ç escape-tunnel æä»¶å€¹ã
escape.htmlãescape.html.txtïŒæµè§åšå°±æäžåŒäºãIn Notepad's Save window, set "Save as type" to All files (*.*).hideout.png åïŒå®åš imgs æä»¶å€¹éåïŒfile://... 飿¯äžæ¡æ²¡éçµçé§éïŒå€ªåŒ±äºïŒæè¿äžæ¥ïŒçæ£ççœé¡µæ¯ç±æå¡åšïŒfúwùqìïŒååºæ¥çãäœ ççµèå°±èœåœæå¡åšââåªèŠäžæ¡åœä»€ãç奜äºïŒescape-tunnel æä»¶å€¹ïŒç¹å»çªå£äžæ¹çå°åæ ïŒèŸå
¥ cmd æå蜊ãäžäžªé»çªå£åºç°äºââè¿æ¯äœ çææ¥äžå¿ïŒèäžå·²ç»ç«åšäœ çæä»¶å€¹éãðClick the folder's address bar, type cmd, press Enter.python -m http.server 8000
http://localhost:8000/escape.htmlð è¿æ¯äœ ççœé¡µïŒç±äœ çæå¡åšïŒåšäœ ççµèäžååºïŒlocalhost çæææ¯"è¿å°çµè"ïŒ8000 æ¯éšçå·ââæå¡åšå«å®ç«¯å£ïŒduÄnkÇu / portïŒã
Ctrl + Cãå ç¹äžäžé»è²åºåïŒåææ¹åé®ââæè ç¹æé®ãClick the dark area first, then use the arrow keys.
æŽäžªéæ³å°±æ¯è¿å è¡å笊䞲ââK æ¯å€ŽåïŒS æ¯ç®è€ïŒR æ¯æç红衣æïŒ. æ¯éæïŒ
const XIAOLONG = [
"..KKKKKK..",
".KKKKKKKK.",
".KSSSSSSK.",
".SSESSESS.", // E = çŒç!
".SSSSSSSS.",
"..SSMMSS..", // M = åŽå·Ž
"..RRRRRR..",
".RRRRRRRR.",
".SRRRRRRS.",
"..RRRRRR..",
"..BBBBBB..",
"..BB..BB..",
".KK....KK.",
];
const COLORS = { K:"#1f2937", S:"#f6cfa0", E:"#111111",
M:"#b45309", R:"#ef4444", B:"#3b82f6" };
function drawSprite(grid, x, y, size) {
for (let row = 0; row < grid.length; row++)
for (let col = 0; col < grid[row].length; col++) {
const letter = grid[row][col];
if (letter === ".") continue; // "." = è·³è¿ = çäžè§
ctx.fillStyle = COLORS[letter];
ctx.fillRect(x + col*size, y + row*size, size, size);
}
}
ç°åšæææŸè¿äœ çè身å±ãæè¿äžªä»»å¡äº€ç» ChatGPTïŒ
è¿æ¯æç escape.htmlïŒ[æäœ çæŽäžªæä»¶èŽŽè¿æ¥]ã请åšé¡µé¢äžéŽå äžäžª <canvas>ïŒçšäžé¢è¿äºæ°æ®ååœæ°æè¿äžªåçŽ å°ç·å©ç»äžå»ïŒ[æäžé¢ç XIAOLONG 代ç èŽŽè¿æ¥]ãå ¶ä»éœäžèŠæ¹ïŒå¹¶äžå对 9 å²å©åè§£éäžæ ·ïŒè¯Žæäœ æ¹äºä»ä¹ã
å䞪声é³å 𿝿°åŠååºæ¥çïŒé¶é³é¢æä»¶ã
è¿æ¯"è·³"ç鳿ââæè¿äœ ççœé¡µïŒè°è·³èµ·æ¥å°±è°çš sfxJump()ïŒ
let audio;
function sfxJump() {
if (!audio) audio = new AudioContext();
const beep = audio.createOscillator(); // é³è°åçåš
const vol = audio.createGain(); // é³éæé®
beep.type = "square"; // å€å€ 8-bit 声é³
beep.frequency.setValueAtTime(150, audio.currentTime);
beep.frequency.exponentialRampToValueAtTime(500, audio.currentTime + 0.15);
vol.gain.setValueAtTime(0.25, audio.currentTime);
vol.gain.exponentialRampToValueAtTime(0.01, audio.currentTime + 0.18);
beep.connect(vol); vol.connect(audio.destination);
beep.start(); beep.stop(audio.currentTime + 0.2);
}
localStorageââæŸè¿å»çäžè¥¿ïŒå
³ææµè§åšãéå¯çµèéœè¿åšãè¿äžªé¡µé¢å°±åšçšå®ïŒæä»¥æä»¬çé§éå°åŸæ°žè¿äžäŒå¿è®°ãå䞪纪åœïŒç¶åå·æ°è¿äžªé¡µé¢ââäœ ç纪åœè¿åšïŒè¯è¯çââé§éå°åŸä¹è¿åšïŒ
æŽäžªä¿åç³»ç»å°±äžè¡ïŒ
// ä¿å ââ ååšäžåŒ æµè§åšæ°žè¿ä¿çç䟿å©èŽŽäž
localStorage.setItem("highScore", score);
// 读å ââ çç䟿å©èŽŽïŒ|| 0 çæææ¯"è¿æ²¡æäŸ¿å©èŽŽå°±çš 0"ïŒ
const best = Number(localStorage.getItem("highScore")) || 0;
// å é€ ââ ææäŸ¿å©èŽŽ
localStorage.removeItem("highScore");
const keys = {};
onkeydown = e => keys[e.key] = true; // è®°äœæäžå»çé®âŠ
onkeyup = e => keys[e.key] = false; // âŠåæŸåŒçé®
function gameLoop() {
// 1. èŸå
¥ ââ æäœé®ïŒæ¯äžåž§éœå¹³æ»ç§»åš
if (keys["ArrowRight"]) xiaolongX += 4;
if (keys["ArrowLeft"]) xiaolongX -= 4;
// 2. æŽæ° ââ æžžæè§ååè¿éïŒéåãéŸã计åâŠïŒ
// 3. ç»å¶ ââ æŠæå±å¹ïŒéç»æŽäžªäžç
ctx.clearRect(0, 0, stage.width, stage.height);
drawSprite(XIAOLONG, xiaolongX, xiaolongY, 6);
requestAnimationFrame(gameLoop); // "æµè§åšïŒäžäžåž§åè¿è¡æäžæ¬¡ïŒ"
}
gameLoop(); // å¯åšå¿è·³ ð
onkeydownïŒåšèµ·æ¥äŒäžå¡äžå¡çã髿è¯çªïŒkeydown/keyup åªèŽèŽ£è®°äœåªäºé®è¢«æçââç§»åšäº€ç»åŸªç¯æ¥åãè¿æ¯æç escape.htmlïŒ[ææŽäžªæä»¶èŽŽè¿æ¥]ãè¯·çš requestAnimationFrame æžžæåŸªç¯å"è®°äœæé®"ç对象ïŒè®©åçŽ å°ç·å©çšæ¹åé®å¹³æ»ç§»åšïŒäžèŠåš onkeydown éçŽæ¥ç§»åšïŒãä¿çæçèæ¯åŸãå对 9 å²å©åè§£éäžæ ·ïŒè¯Žæäœ æ¹äºä»ä¹ã
server.pyïŒæŸè¿äœ çæä»¶å€¹ïŒå®ç §æ ·åæä»¶ïŒäœè¿äŒè®°äœæ¯äžªç©å®¶ç«åšåªéïŒ
import json
from http.server import HTTPServer, SimpleHTTPRequestHandler
players = {} # æå¡åšçè®°å¿ïŒåå -> äœçœ® + é¢è²
class GameServer(SimpleHTTPRequestHandler):
def do_GET(self): # æµè§åšæ¥"èŠ"äžè¥¿
if self.path == "/players": # "倧家éœåšåªïŒ"
body = json.dumps(players).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
super().do_GET() # æ®éæä»¶ïŒtunnel.html çïŒ
def do_POST(self): # æµè§åšæ¥"é"äžè¥¿
if self.path == "/move": # "è¿æ¯æçäœçœ®"
size = int(self.headers["Content-Length"])
data = json.loads(self.rfile.read(size))
players[data["name"]] = data # è®°äœè¿äžªç©å®¶
self.send_response(200)
self.end_headers()
def log_message(self, *args):
pass # 让é»çªå£ä¿æå®é
print("Tunnel server ON! Open http://localhost:8000/tunnel.html")
HTTPServer(("0.0.0.0", 8000), GameServer).serve_forever()
tunnel.htmlïŒåäžäžªæä»¶å€¹ïŒ<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<body style="background:#0f172a">
<canvas id="game" width="600" height="400"
style="background:#1e293b;display:block;margin:20px auto;border-radius:12px"></canvas>
<script>
const name = prompt("äœ çç©å®¶ååïŒ") || "Player" + Math.floor(Math.random()*99);
const color = "hsl(" + Math.floor(Math.random()*360) + ", 90%, 60%)"; // éæºé¢è²ïŒ
const me = { name, color, x: 100 + Math.random()*400, y: 100 + Math.random()*200 };
let everyone = {};
const keys = {};
onkeydown = e => keys[e.key] = true;
onkeyup = e => keys[e.key] = false;
// æ¯ 0.1 ç§ïŒåè¯æå¡åšæåšåªïŒåé®é®å€§å®¶éœåšåª
async function talkToServer() {
await fetch("/move", { method: "POST", body: JSON.stringify(me) });
everyone = await (await fetch("/players")).json();
}
setInterval(talkToServer, 100);
const ctx = document.getElementById("game").getContext("2d");
function gameLoop() {
if (keys["ArrowLeft"]) me.x -= 4;
if (keys["ArrowRight"]) me.x += 4;
if (keys["ArrowUp"]) me.y -= 4;
if (keys["ArrowDown"]) me.y += 4;
ctx.clearRect(0, 0, 600, 400);
for (const p of Object.values(everyone)) { // ç»åºæ¯äžäžªç©å®¶
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, 30, 30);
ctx.fillStyle = "#fff";
ctx.font = "13px sans-serif";
ctx.fillText(p.name, p.x, p.y - 6);
}
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>
Ctrl+C åææ§æå¡åšïŒïŒè¿è¡ïŒpython server.pyhttp://localhost:8000/tunnel.htmlipconfigïŒæŸ IPv4 å°åââ类䌌 192.168.1.42Type ipconfig and look for "IPv4 Address".http://192.168.1.42:8000/tunnel.htmlïŒçšäœ èªå·±çæ°åïŒïŒescape.html å€å¶æ escape_v2.htmlãæ¹åäºéæ¶éåå»ãäœ æäºæïŒäœ æ¯çæ£çå·¥çšåžïŒ
NÇ jiù le wÇ! NÇ shì zhÄnzhÚng de gÅngchéngshÄ«! â You saved me! You're a real engineer!
"HarryïŒäœ åäºæä»¶å€¹ãçœé¡µãæå¡åšã身äœã声é³ãè®°å¿ãå¿è·³ââè¿æäžæå«äººèœèµ°è¿æ¥ç倧éšãè¿äžæ¯åè£ å代ç ïŒè¿æ¯ççïŒ"
å°éŸç°åšäœåšäœ ççµèéãæ³ä»äºïŒå°±æ ð¬ æŸä»è倩ã