Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

dumduel: practice dummy when solo, light theme

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+84 -37
+84 -37
system/public/aesthetic.computer/disks/dumduel.mjs
··· 37 37 let opponent = null; // { x, y, targetX, alive, fireTimer, handle } 38 38 let bullets = []; // { x, y, vx, owner: "me"|"them" } 39 39 let mySlot = -1; // 0 = left, 1 = right 40 + let dummy = false; // true when practicing solo against a dummy 40 41 41 42 function isDueling() { 42 43 return roster.length >= 2 && ··· 66 67 }; 67 68 } 68 69 70 + function startPractice() { 71 + dummy = true; 72 + // Add dummy to roster as slot 1 73 + if (!roster.find((r) => r.handle === "dummy")) { 74 + roster.push({ id: "dummy", handle: "dummy" }); 75 + } 76 + mySlot = 0; 77 + phase = "countdown"; 78 + countdownTimer = COUNTDOWN_FRAMES; 79 + spawnDuelists(); 80 + if (opponent) opponent.handle = "dummy"; 81 + } 82 + 83 + function stopPractice() { 84 + dummy = false; 85 + roster = roster.filter((r) => r.handle !== "dummy"); 86 + me = null; 87 + opponent = null; 88 + bullets = []; 89 + } 90 + 69 91 function startCountdown() { 70 92 phase = "countdown"; 71 93 countdownTimer = COUNTDOWN_FRAMES; ··· 105 127 if (roster.length >= 2) { 106 128 startCountdown(); 107 129 } else { 108 - phase = "waiting"; 130 + // Back to solo — restart practice 131 + startPractice(); 109 132 } 110 133 } 111 134 ··· 153 176 const msg = typeof content === "string" ? JSON.parse(content) : content; 154 177 155 178 if (type === "duel:join" && msg.handle !== myHandle) { 179 + // Stop practice if a real player joins 180 + if (dummy) stopPractice(); 156 181 if (!roster.find((r) => r.handle === msg.handle)) { 157 182 roster.push({ id, handle: msg.handle }); 158 183 } ··· 225 250 } 226 251 }); 227 252 228 - // Add self to roster 253 + // Add self to roster and start practice 229 254 roster.push({ id: myId, handle: myHandle }); 255 + startPractice(); 230 256 231 - wipe(30, 25, 40); 257 + wipe(240, 238, 232); 232 258 } 233 259 234 260 function sim() { ··· 247 273 } 248 274 } 249 275 250 - if (phase !== "fight" || !isDueling() || !me) return; 276 + if (phase !== "fight" || !me || (!isDueling() && !dummy)) return; 277 + 278 + // Dummy AI — wander randomly 279 + if (dummy && opponent?.alive) { 280 + opponent.fireTimer--; 281 + // Pick a new random target every ~2s 282 + if (frameCount % 120 === 0) { 283 + opponent.targetX = 20 + Math.random() * (ARENA_W - 40); 284 + } 285 + const odx = opponent.targetX - opponent.x; 286 + if (Math.abs(odx) > 1) opponent.x += Math.sign(odx) * MOVE_SPEED * 0.8; 287 + // Dummy fires 288 + if (opponent.fireTimer <= 0) { 289 + opponent.fireTimer = FIRE_INTERVAL; 290 + const dir = Math.sign(me.x - opponent.x); 291 + bullets.push({ 292 + x: opponent.x + dir * 4, 293 + y: opponent.y - FIGURE_H / 2, 294 + vx: dir * BULLET_SPEED, 295 + owner: "them", 296 + }); 297 + } 298 + } 251 299 252 300 // Move toward target 253 301 if (me.alive) { ··· 331 379 sw = screen.width; 332 380 sh = screen.height; 333 381 334 - if (e.is("touch") && phase === "fight" && isDueling() && me?.alive) { 382 + if (e.is("touch") && phase === "fight" && (isDueling() || dummy) && me?.alive) { 335 383 // Tap to set run target (screen -> arena coords) 336 384 const ox = Math.floor(sw / 2 - ARENA_W / 2); 337 385 const tx = e.x - ox; ··· 342 390 function paint({ wipe, ink, box, write, line, circle, screen }) { 343 391 sw = screen.width; 344 392 sh = screen.height; 345 - wipe(30, 25, 40); 393 + wipe(240, 238, 232); 346 394 347 395 const ox = Math.floor(sw / 2 - ARENA_W / 2); 348 396 const oy = Math.floor(sh / 2 - ARENA_H / 2); 349 397 350 398 // Arena bg 351 - ink(45, 38, 55).box(ox, oy, ARENA_W, ARENA_H); 399 + ink(255, 253, 248).box(ox, oy, ARENA_W, ARENA_H); 352 400 // Ground 353 - ink(60, 52, 70).box(ox, oy + GROUND_Y, ARENA_W, ARENA_H - GROUND_Y); 401 + ink(220, 215, 205).box(ox, oy + GROUND_Y, ARENA_W, ARENA_H - GROUND_Y); 354 402 // Arena border 355 - ink(70, 62, 85).box(ox, oy, ARENA_W, ARENA_H, "outline"); 356 - 357 - if (phase === "waiting") { 358 - ink(110, 105, 130).write("dumduel", { x: ox + 72, y: oy + 50 }); 359 - ink(80, 75, 100).write("waiting...", { x: ox + 66, y: oy + 65 }); 360 - } 403 + ink(180, 175, 165).box(ox, oy, ARENA_W, ARENA_H, "outline"); 361 404 362 405 if (phase === "countdown") { 363 406 const secs = Math.ceil(countdownTimer / 60); 364 407 const numStr = "" + secs; 365 - ink(255, 220, 100).write(numStr, { 408 + ink(60, 55, 45).write(numStr, { 366 409 x: ox + Math.floor(ARENA_W / 2 - numStr.length * 3), 367 410 y: oy + 30, 368 411 }); 369 412 370 - // Draw duelists standing still 371 - if (isDueling() && me && opponent) { 372 - drawStickFigure(ink, line, circle, box, ox, oy, me, [120, 180, 255]); 373 - drawStickFigure(ink, line, circle, box, ox, oy, opponent, [255, 130, 100]); 413 + if (me && opponent) { 414 + drawStickFigure(ink, line, circle, box, ox, oy, me, [50, 120, 200]); 415 + drawStickFigure(ink, line, circle, box, ox, oy, opponent, [200, 70, 60]); 374 416 } 375 417 376 - // Show who vs who 377 418 const d0 = roster[0]?.handle || "?"; 378 419 const d1 = roster[1]?.handle || "?"; 379 420 const vsStr = d0 + " vs " + d1; 380 - ink(150, 145, 170).write(vsStr, { 421 + ink(140, 135, 125).write(vsStr, { 381 422 x: ox + Math.floor(ARENA_W / 2 - vsStr.length * 3), 382 423 y: oy + 50, 383 424 }); ··· 386 427 if (phase === "fight" || phase === "roundover") { 387 428 // Bullets 388 429 for (const b of bullets) { 389 - if (b.owner === "me") ink(120, 180, 255); 390 - else ink(255, 130, 100); 430 + if (b.owner === "me") ink(50, 120, 200); 431 + else ink(200, 70, 60); 391 432 circle(ox + Math.floor(b.x), oy + Math.floor(b.y), BULLET_R, true); 392 433 } 393 434 394 - // Draw figures 395 - if (isDueling() && me && opponent) { 396 - drawStickFigure(ink, line, circle, box, ox, oy, me, [120, 180, 255]); 397 - drawStickFigure(ink, line, circle, box, ox, oy, opponent, [255, 130, 100]); 435 + if (me && opponent) { 436 + drawStickFigure(ink, line, circle, box, ox, oy, me, [50, 120, 200]); 437 + drawStickFigure(ink, line, circle, box, ox, oy, opponent, [200, 70, 60]); 398 438 } 399 439 400 - // Round over text 401 440 if (phase === "roundover" && roundWinner) { 402 441 const won = roundWinner === myHandle; 403 442 const msg = won ? "you got em!" : "you died!"; 404 - if (won) ink(100, 220, 130); else ink(220, 100, 100); 443 + if (won) ink(50, 160, 80); else ink(200, 70, 60); 405 444 write(msg, { 406 445 x: ox + Math.floor(ARENA_W / 2 - msg.length * 3), 407 446 y: oy + 20, 408 447 }); 409 448 } 410 449 411 - // Show who vs who 412 450 if (roster.length >= 2) { 413 451 const d0 = roster[0]?.handle || "?"; 414 452 const d1 = roster[1]?.handle || "?"; 415 - ink(80, 75, 100).write(d0, { x: ox + 2, y: oy + ARENA_H + 4 }); 416 - ink(80, 75, 100).write(d1, { 453 + ink(140, 135, 125).write(d0, { x: ox + 2, y: oy + ARENA_H + 4 }); 454 + ink(140, 135, 125).write(d1, { 417 455 x: ox + ARENA_W - d1.length * 6 - 2, y: oy + ARENA_H + 4, 418 456 }); 419 457 } 420 458 } 421 459 460 + // Practice label 461 + if (dummy && (phase === "fight" || phase === "countdown")) { 462 + ink(190, 185, 175).write("practice", { 463 + x: ox + Math.floor(ARENA_W / 2 - 24), 464 + y: oy - 10, 465 + }); 466 + } 467 + 422 468 // -- Stack (queue) display -- 423 469 const stackX = ox + ARENA_W + 8; 424 470 const stackY = oy; 425 - ink(80, 75, 100).write("stack", { x: stackX, y: stackY }); 471 + ink(160, 155, 145).write("stack", { x: stackX, y: stackY }); 426 472 for (let i = 0; i < roster.length; i++) { 427 473 const r = roster[i]; 474 + if (r.handle === "dummy") continue; // don't show dummy in stack 428 475 const isMe = r.handle === myHandle; 429 476 const isDuelist = i < 2 && roster.length >= 2; 430 - if (isDuelist) ink(255, 220, 100); 431 - else if (isMe) ink(150, 145, 170); 432 - else ink(100, 95, 120); 477 + if (isDuelist) ink(60, 55, 45); 478 + else if (isMe) ink(120, 115, 105); 479 + else ink(170, 165, 155); 433 480 write(r.handle, { x: stackX, y: stackY + 12 + i * 10 }); 434 481 } 435 482 } ··· 440 487 441 488 if (!fig.alive) { 442 489 // Dead — fallen over 443 - ink(col[0], col[1], col[2], 120); 490 + ink(col[0], col[1], col[2], 80); 444 491 line(fx - 6, fy - 1, fx + 6, fy - 1); 445 492 circle(fx + 7, fy - 2, 2, true); 446 493 return;