Play this beta version of Daphnia Dash by copying and pasting into your html browser (e.g. Chrome)
<div class="wrap">
<h1 style="margin:0">Daphnia Dash — playable prototype</h1>
<div class="meta">Use Space / ↑ to swim up, ↓ to dive. Tap on mobile.</div>
<canvas id="game" width="900" height="300"></canvas>
<div class="controls">
<button id="startBtn">Start / Restart</button>
<div class="hint">Score: <span id="score">0</span> ⋅ Best: <span id="best">0</span></div>
</div>
<div class="hint">Obstacles: hydra (green tentacles) and small fish. Collect algae for +points.</div>
</div>
<script>
/* Daphnia Dash — single-file HTML/JS prototype
- Canvas-based endless runner (swimmer)
- No external assets
- Touch + keyboard support
*/
(()=>{
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// Game state
let running = false;
let score = 0, best = localStorage.getItem('daphnia_best')||0;
let speed = 2.2; // world speed
const gravity = 0.22;
// Daphnia player
const player = {
x: 120, y: H/2, vy:0, radius:14, alive:true
};
// Entities
const obstacles = [];
const pickups = [];
let spawnTimer = 0;
let dayNightTimer = 0;
// Controls
let pressing = false;
// UI refs
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
bestEl.textContent = best;
// Sound setup
const jumpSound = new Audio('https://freesound.org/data/previews/66/66717_931655-lq.mp3');
const gameOverSound = new Audio('https://freesound.org/data/previews/331/331912_3248244-lq.mp3');
let jumpSoundPlaying = false;
function reset(){
running = true; score=0; speed=2.2; player.y = H/2; player.vy=0; player.alive=true;
obstacles.length=0; pickups.length=0; spawnTimer=0; dayNightTimer=0;
scoreEl.textContent = 0;
loop();
}
document.getElementById('startBtn').addEventListener('click', ()=>{ reset(); });
// Input
window.addEventListener('keydown', e=>{
if(e.code==='Space' || e.key==='ArrowUp') { press(); e.preventDefault(); }
if(e.key==='ArrowDown') { dive(); }
if(pressing && !jumpSoundPlaying){
jumpSound.currentTime = 0;
jumpSound.play();
jumpSoundPlaying = true;
} else if(!pressing){
jumpSoundPlaying = false;
}
});
window.addEventListener('keyup', e=>{ if(e.code==='Space' || e.key==='ArrowUp') release(); });
canvas.addEventListener('touchstart', e=>{ press(); e.preventDefault(); });
canvas.addEventListener('touchend', e=>{ release(); e.preventDefault(); });
canvas.addEventListener('mousedown', e=>{ press(); });
canvas.addEventListener('mouseup', e=>{ release(); });
function press(){ pressing = true; player.vy = -3.6; }
function release(){ pressing = false; }
function dive(){ player.vy += 1.6; }
// Helpers
function rand(min,max){return Math.random()*(max-min)+min}
// Spawn obstacles & pickups
function spawn(dt){
spawnTimer -= dt;
if(spawnTimer<=0){
spawnTimer = rand(900,1600) - Math.min(score,200); // faster spawns as score grows
// choose obstacle type
if(Math.random()<0.65){
// hydra (tangled tentacles) - tall thin
obstacles.push({type:'hydra', x:W+40, w:18, h:rand(40,140), gap:rand(60,110)});
} else {
// fish - small fast moving
obstacles.push({type:'fish', x:W+40, w:36, h:18, y:rand(70,H-70)});
}
// occasionally spawn algae pickup
if(Math.random()<0.28){ pickups.push({x:W+80, y:rand(60,H-80), r:9, claimed:false}); }
}
}
// Collision detection simple circle vs rect
function circleRect(cx,cy,r,rx,ry,rw,rh){
const nearestX = Math.max(rx, Math.min(cx, rx+rw));
const nearestY = Math.max(ry, Math.min(cy, ry+rh));
const dx = cx - nearestX; const dy = cy - nearestY;
return (dx*dx + dy*dy) < (r*r);
}
// Draw water plants background
function drawBackground(t){
// subtle waves via gradient already set by CSS; draw floating particles/bubbles
for(let i=0;i<6;i++){
const bx = (t*0.03 + i*120) % (W+60) - 30;
ctx.beginPath(); ctx.globalAlpha = 0.06; ctx.arc(bx, 40 + (i%3)*18, 18,0,Math.PI*2); ctx.fillStyle='#ffffff'; ctx.fill(); ctx.globalAlpha =1;
}
// sandy bottom
ctx.fillStyle='rgba(18,10,6,0.06)'; ctx.fillRect(0,H-20,W,20);
}
// Draw Daphnia (stylized)
function drawDaphnia(x,y,angle=0){
ctx.save(); ctx.translate(x,y); ctx.rotate(angle);
// carapace (oval)
ctx.beginPath(); ctx.ellipse(0,0,16,11,0,0,Math.PI*2); ctx.fillStyle='#fff9e6'; ctx.fill();
ctx.strokeStyle='#0b4b4b'; ctx.lineWidth=1; ctx.stroke();
// eye
ctx.beginPath(); ctx.arc(3,-2,2.6,0,Math.PI*2); ctx.fillStyle='#08303a'; ctx.fill();
// tail-spine
ctx.beginPath(); ctx.moveTo(-14,2); ctx.quadraticCurveTo(-22,6,-28,3); ctx.stroke();
// antennae
ctx.beginPath(); ctx.moveTo(7,-8); ctx.quadraticCurveTo(14,-18,20,-16); ctx.moveTo(6,-6); ctx.quadraticCurveTo(12,-12,18,-10); ctx.stroke();
ctx.restore();
}
// Draw obstacle hydra
function drawHydra(o){
// base stem
ctx.fillStyle='#255b2b'; ctx.fillRect(o.x, H - 20 - o.h, o.w, o.h);
// tentacles (simple bezier lines)
ctx.strokeStyle='#1a6a2b'; ctx.lineWidth=3;
for(let i=0;i<5;i++){
const sx = o.x + i*(o.w/4);
ctx.beginPath(); ctx.moveTo(sx, H - 20 - o.h);
ctx.quadraticCurveTo(sx-12 + i*6, H - 10 - o.h/2, sx - 20 + i*8, H-20);
ctx.stroke();
}
}
// Draw fish obstacle
function drawFish(o){
ctx.save(); ctx.translate(o.x, o.y);
ctx.beginPath(); ctx.ellipse(0,0, o.w/2, o.h/2, 0,0,Math.PI*2); ctx.fillStyle='#ffb86b'; ctx.fill();
ctx.beginPath(); ctx.moveTo(-o.w/2,0); ctx.lineTo(-o.w/2-8,-6); ctx.lineTo(-o.w/2-8,6); ctx.closePath(); ctx.fill();
ctx.restore();
}
// Draw algae pickup
function drawAlgae(p){
ctx.save(); ctx.translate(p.x,p.y);
ctx.beginPath(); ctx.moveTo(0,0); ctx.bezierCurveTo(-6,-6,-10,-16,-2,-20); ctx.bezierCurveTo(4,-10,10,-6,6,0); ctx.fillStyle='#6ab04c'; ctx.fill();
ctx.restore();
}
// Update world
let last = performance.now();
function loop(now=performance.now()){
if(!running) return;
const dt = now - last; last = now;
// update timers
dayNightTimer += dt;
// speed up slowly
speed += dt * 0.00005;
spawn(dt);
// physics: player
player.vy += gravity;
player.y += player.vy;
// clamp
if(player.y < 18) { player.y = 18; player.vy = 0; }
if(player.y > H-28) { player.y = H-28; player.vy = 0; }
// move obstacles
for(let i=obstacles.length-1;i>=0;i--){
const o = obstacles[i];
o.x -= speed * (o.type==='fish'?1.6:1);
if(o.type==='hydra'){
// gap is the safe space above bottom
const rx = o.x; const ry = H - 20 - o.h; const rw = o.w; const rh = o.h;
// check collision against carapace circle
if(circleRect(player.x, player.y, player.radius, rx, ry, rw, rh)) {
player.alive = false; running = false; endGame(); return; }
} else if(o.type==='fish'){
if(circleRect(player.x, player.y, player.radius, o.x - o.w/2, o.y - o.h/2, o.w, o.h)) { player.alive = false; running = false; endGame(); return; }
}
if(o.x < -80) obstacles.splice(i,1);
}
// move pickups
for(let i=pickups.length-1;i>=0;i--){
const p = pickups[i]; p.x -= speed*1.1;
const d2 = (player.x - p.x)*(player.x - p.x) + (player.y - p.y)*(player.y - p.y);
if(d2 < (player.radius + p.r)*(player.radius + p.r)){
score += 12; pickups.splice(i,1); continue;
}
if(p.x < -40) pickups.splice(i,1);
}
// scoring by distance
score += Math.floor(dt * 0.02 * (speed/2));
scoreEl.textContent = score;
// render
ctx.clearRect(0,0,W,H);
// subtle night overlay
const nightAlpha = Math.max(0, Math.sin(dayNightTimer*0.0006))*0.15;
drawBackground(dayNightTimer);
// draw pickups
for(const p of pickups) drawAlgae(p);
// draw obstacles
for(const o of obstacles){ if(o.type==='hydra') drawHydra(o); else drawFish(o); }
// draw player with small bobbing angle
const angle = Math.sin(performance.now()*0.006) * 0.08;
drawDaphnia(player.x, player.y, angle);
// HUD
ctx.fillStyle='#023544'; ctx.font='14px sans-serif'; ctx.fillText('Score: '+score, 12,22);
// loop
if(running) requestAnimationFrame(loop);
}
function endGame(){
// show game over overlay
ctx.fillStyle='rgba(0,0,0,0.35)'; ctx.fillRect(0,0,W,H);
ctx.fillStyle='white'; ctx.font='28px sans-serif'; ctx.fillText('Game Over', W/2 -74, H/2 -6);
ctx.font='14px sans-serif'; ctx.fillText('Click Start/Restart to play again', W/2 -116, H/2 +18);
if(score > best){ best = score; localStorage.setItem('daphnia_best', best); bestEl.textContent = best; }
gameOverSound.currentTime = 0;
gameOverSound.play();
}
// One-time draw (before start)
(function intro(){
ctx.clearRect(0,0,W,H);
drawBackground(0);
drawDaphnia(player.x, player.y);
ctx.fillStyle='#023544'; ctx.font='16px sans-serif'; ctx.fillText('Press Start to play', 16, 36);
})();
})();
</script>
</body>
</html>