|
| 1 | +// Toxic Zone Survival - Sprig Edition (Lowercase-only IDs) |
| 2 | + |
| 3 | +const p = "p" |
| 4 | +const t = "t" |
| 5 | +const o = "o" |
| 6 | +const w = "w" |
| 7 | + |
| 8 | +setLegend( |
| 9 | + [p, bitmap` |
| 10 | +................ |
| 11 | +................ |
| 12 | +.......33....... |
| 13 | +......3333...... |
| 14 | +.....333333..... |
| 15 | +....33222333.... |
| 16 | +....33222333.... |
| 17 | +.....333333..... |
| 18 | +......3333...... |
| 19 | +.......33....... |
| 20 | +................ |
| 21 | +................ |
| 22 | +................ |
| 23 | +................ |
| 24 | +................ |
| 25 | +................`], |
| 26 | + |
| 27 | + [t, bitmap` |
| 28 | +................ |
| 29 | +................ |
| 30 | +....22222222.... |
| 31 | +...2222222222... |
| 32 | +..222222222222.. |
| 33 | +..222211112222.. |
| 34 | +..222211112222.. |
| 35 | +..222222222222.. |
| 36 | +...2222222222... |
| 37 | +....22222222.... |
| 38 | +................ |
| 39 | +................ |
| 40 | +................ |
| 41 | +................ |
| 42 | +................ |
| 43 | +................`], |
| 44 | + |
| 45 | + [o, bitmap` |
| 46 | +................ |
| 47 | +................ |
| 48 | +.......44....... |
| 49 | +......4444...... |
| 50 | +.....444444..... |
| 51 | +......4444...... |
| 52 | +.......44....... |
| 53 | +................ |
| 54 | +................ |
| 55 | +................ |
| 56 | +................ |
| 57 | +................ |
| 58 | +................ |
| 59 | +................ |
| 60 | +................ |
| 61 | +................`], |
| 62 | + |
| 63 | + [w, bitmap` |
| 64 | +1111111111111111 |
| 65 | +1111111111111111 |
| 66 | +1111111111111111 |
| 67 | +1111111111111111 |
| 68 | +1111111111111111 |
| 69 | +1111111111111111 |
| 70 | +1111111111111111 |
| 71 | +1111111111111111 |
| 72 | +1111111111111111 |
| 73 | +1111111111111111 |
| 74 | +1111111111111111 |
| 75 | +1111111111111111 |
| 76 | +1111111111111111 |
| 77 | +1111111111111111 |
| 78 | +1111111111111111 |
| 79 | +1111111111111111`] |
| 80 | +) |
| 81 | + |
| 82 | +setMap(map` |
| 83 | +wwwwwwwwwwwwww |
| 84 | +w............w |
| 85 | +w............w |
| 86 | +w............w |
| 87 | +w............w |
| 88 | +w............w |
| 89 | +w............w |
| 90 | +w............w |
| 91 | +w............w |
| 92 | +w............w |
| 93 | +wwwwwwwwwwwwww`) |
| 94 | + |
| 95 | +const W = width() |
| 96 | +const H = height() |
| 97 | + |
| 98 | +// CONFIG |
| 99 | +const cfg = { |
| 100 | + tick: 140, |
| 101 | + oxyMax: 100, |
| 102 | + hpMax: 100, |
| 103 | + oxyDrain: 3, |
| 104 | + cloudMul: 4, |
| 105 | + dmg: 10, |
| 106 | + tankOxy: 36, |
| 107 | + tankSpawn: 6000, |
| 108 | + tankLife: 14000, |
| 109 | + cloudSpawn: 4200, |
| 110 | + cloudMin: 900, |
| 111 | + grow: 0.28 |
| 112 | +} |
| 113 | + |
| 114 | +let state = "idle" |
| 115 | +let pl = { x: Math.floor(W/2), y: Math.floor(H/2), oxy: cfg.oxyMax, hp: cfg.hpMax } |
| 116 | +let dir = {x:0,y:0} |
| 117 | +let nextDir = null |
| 118 | +let clouds = [] |
| 119 | +let tanks = [] |
| 120 | +let tickLoop = null |
| 121 | +let nextTank = performance.now() + 1000 |
| 122 | +let nextCloud = performance.now() + 1200 |
| 123 | +let cloudRate = cfg.cloudSpawn |
| 124 | +let start = 0 |
| 125 | +let time = 0 |
| 126 | +let best = Number(localStorage.getItem("highScoreSprigTZS") || 0) |
| 127 | + |
| 128 | +// UTIL |
| 129 | +function r(a,b){ return Math.random()*(b-a)+a } |
| 130 | +function solid(x,y){ return getTile(x,y).some(s=>s.type===w) } |
| 131 | + |
| 132 | +// CLOUD/TANK SPAWN |
| 133 | +function makeCloud(){ |
| 134 | + const edge = Math.floor(Math.random()*4) |
| 135 | + let x,y |
| 136 | + if(edge===0){ x=0; y=Math.floor(r(1,H-2)) } |
| 137 | + if(edge===1){ x=W-1; y=Math.floor(r(1,H-2)) } |
| 138 | + if(edge===2){ x=Math.floor(r(1,W-2)); y=0 } |
| 139 | + if(edge===3){ x=Math.floor(r(1,W-2)); y=H-1 } |
| 140 | + const ang = Math.atan2(H/2-y, W/2-x) |
| 141 | + clouds.push({ x, y, r:r(1.2,2.4), vx:Math.cos(ang)*r(0.02,0.18), vy:Math.sin(ang)*r(0.02,0.18) }) |
| 142 | +} |
| 143 | + |
| 144 | +function makeTank(){ |
| 145 | + const arr = [] |
| 146 | + for(let x=1;x<W-1;x++){ |
| 147 | + for(let y=1;y<H-1;y++){ |
| 148 | + if(!(x===pl.x && y===pl.y) && !solid(x,y)) arr.push({x,y}) |
| 149 | + } |
| 150 | + } |
| 151 | + if(arr.length===0) return |
| 152 | + const p0 = arr[Math.floor(Math.random()*arr.length)] |
| 153 | + tanks.push({ x:p0.x, y:p0.y, born:performance.now() }) |
| 154 | +} |
| 155 | + |
| 156 | +// DRAW |
| 157 | +function clearInside(){ |
| 158 | + for(let x=1;x<W-1;x++){ |
| 159 | + for(let y=1;y<H-1;y++){ |
| 160 | + clearTile(x,y) |
| 161 | + } |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +function draw(){ |
| 166 | + for(const tn of tanks) addSprite(tn.x, tn.y, o) |
| 167 | + for(const c of clouds){ |
| 168 | + const R = c.r |
| 169 | + const xmin = Math.max(1, Math.floor(c.x - R)) |
| 170 | + const xmax = Math.min(W-2, Math.floor(c.x + R)) |
| 171 | + const ymin = Math.max(1, Math.floor(c.y - R)) |
| 172 | + const ymax = Math.min(H-2, Math.floor(c.y + R)) |
| 173 | + for(let xx=xmin; xx<=xmax; xx++){ |
| 174 | + for(let yy=ymin; yy<=ymax; yy++){ |
| 175 | + const dx = xx - c.x, dy = yy - c.y |
| 176 | + if(Math.sqrt(dx*dx+dy*dy) <= R+0.3) addSprite(xx,yy,t) |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + addSprite(pl.x, pl.y, p) |
| 181 | +} |
| 182 | + |
| 183 | +// GAME LOGIC |
| 184 | +function step(){ |
| 185 | + if(state!=="play") return |
| 186 | + const now = performance.now() |
| 187 | + |
| 188 | + // movement |
| 189 | + if(nextDir){ |
| 190 | + if(!(dir.x===-nextDir.x && dir.y===-nextDir.y)) dir = nextDir |
| 191 | + nextDir = null |
| 192 | + } |
| 193 | + if(dir.x||dir.y){ |
| 194 | + const nx = pl.x + dir.x, ny = pl.y + dir.y |
| 195 | + if(!solid(nx,ny)){ pl.x = nx; pl.y = ny } |
| 196 | + } |
| 197 | + |
| 198 | + // clouds |
| 199 | + for(const c of clouds){ |
| 200 | + c.r += cfg.grow |
| 201 | + c.x += c.vx |
| 202 | + c.y += c.vy |
| 203 | + } |
| 204 | + clouds = clouds.filter(c => !(c.x<-3||c.x>W+3||c.y<-3||c.y>H+3||c.r>10)) |
| 205 | + |
| 206 | + // spawn clouds |
| 207 | + cloudRate = Math.max(cfg.cloudMin, cfg.cloudSpawn - (time/60)*300) |
| 208 | + if(now>=nextCloud){ |
| 209 | + makeCloud() |
| 210 | + nextCloud = now + r(cloudRate*0.7, cloudRate*1.2) |
| 211 | + } |
| 212 | + |
| 213 | + // tanks |
| 214 | + if(now>=nextTank){ |
| 215 | + makeTank() |
| 216 | + nextTank = now + r(cfg.tankSpawn*0.6, cfg.tankSpawn*1.4) |
| 217 | + } |
| 218 | + tanks = tanks.filter(tk => now - tk.born <= cfg.tankLife) |
| 219 | + |
| 220 | + // pickup |
| 221 | + for(let i=tanks.length-1;i>=0;i--){ |
| 222 | + if(tanks[i].x===pl.x && tanks[i].y===pl.y){ |
| 223 | + pl.oxy = Math.min(cfg.oxyMax, pl.oxy + cfg.tankOxy) |
| 224 | + tanks.splice(i,1) |
| 225 | + try{ playTune("c5:6") }catch(e){} |
| 226 | + } |
| 227 | + } |
| 228 | + |
| 229 | + // oxygen drain |
| 230 | + let inside=false |
| 231 | + for(const c of clouds){ |
| 232 | + const dx=pl.x-c.x, dy=pl.y-c.y |
| 233 | + if(Math.sqrt(dx*dx+dy*dy)<=c.r+0.2){ inside=true; break } |
| 234 | + } |
| 235 | + const dt = cfg.tick/1000 |
| 236 | + pl.oxy = Math.max(0, pl.oxy - cfg.oxyDrain * (inside?cfg.cloudMul:1) * dt) |
| 237 | + if(pl.oxy<=0) pl.hp = Math.max(0, pl.hp - cfg.dmg*dt) |
| 238 | + |
| 239 | + // score |
| 240 | + time = (now-start)/1000 |
| 241 | + |
| 242 | + // redraw |
| 243 | + clearInside() |
| 244 | + draw() |
| 245 | + |
| 246 | + try{ clearText() }catch(e){} |
| 247 | + addText("TIME "+time.toFixed(1), {x:1,y:0,color:color`3`}) |
| 248 | + addText("OXY "+Math.floor(pl.oxy), {x:9,y:0,color:color`4`}) |
| 249 | + addText("HP "+Math.floor(pl.hp), {x:14,y:0,color:color`2`}) |
| 250 | + addText("BEST "+Math.floor(best), {x:1,y:H-1,color:color`5`}) |
| 251 | + |
| 252 | + // game over |
| 253 | + if(pl.hp<=0){ |
| 254 | + endGame() |
| 255 | + } |
| 256 | +} |
| 257 | + |
| 258 | +function endGame(){ |
| 259 | + state="end" |
| 260 | + if(tickLoop) clearInterval(tickLoop) |
| 261 | + const final = Math.floor(time) |
| 262 | + if(final>best){ |
| 263 | + best=final |
| 264 | + localStorage.setItem("highScoreSprigTZS",String(best)) |
| 265 | + } |
| 266 | + try{ clearText() }catch(e){} |
| 267 | + addText("GAME OVER", {x:5,y:3,color:color`2`}) |
| 268 | + addText("TIME "+final+"s", {x:5,y:5,color:color`3`}) |
| 269 | + addText("PRESS ANY KEY", {x:3,y:7,color:color`4`}) |
| 270 | + try{ playTune("e4:6 r:2 c4:8") }catch(e){} |
| 271 | +} |
| 272 | + |
| 273 | +function startGame(){ |
| 274 | + pl.x=Math.floor(W/2); pl.y=Math.floor(H/2) |
| 275 | + pl.oxy=cfg.oxyMax; pl.hp=cfg.hpMax |
| 276 | + clouds=[]; tanks=[] |
| 277 | + dir={x:0,y:0}; nextDir=null |
| 278 | + start=performance.now() |
| 279 | + time=0 |
| 280 | + state="play" |
| 281 | + if(tickLoop) clearInterval(tickLoop) |
| 282 | + tickLoop=setInterval(step,cfg.tick) |
| 283 | + makeCloud() |
| 284 | + makeTank() |
| 285 | +} |
| 286 | + |
| 287 | +// INPUT HANDLERS |
| 288 | +function move(x,y){ nextDir={x,y}; if(state==="idle") startGame(); if(state==="end") startGame() } |
| 289 | + |
| 290 | +onInput("w",()=>move(0,-1)) |
| 291 | +onInput("s",()=>move(0,1)) |
| 292 | +onInput("a",()=>move(-1,0)) |
| 293 | +onInput("d",()=>move(1,0)) |
| 294 | +onInput("i",()=>move(0,-1)) |
| 295 | +onInput("k",()=>move(0,1)) |
| 296 | +onInput("j",()=>move(-1,0)) |
| 297 | +onInput("l",()=>move(1,0)) |
| 298 | + |
| 299 | +// START SCREEN |
| 300 | +try{ clearText() }catch(e){} |
| 301 | +addText("TOXIC ZONE SURVIVAL",{x:2,y:2,color:color`3`}) |
| 302 | +addText("Move: WASD / IJKL",{x:3,y:4,color:color`4`}) |
| 303 | +addText("Press any key",{x:4,y:6,color:color`5`}) |
0 commit comments