Files
EndlessSea/js/main.js
bybrooklyn 366fbd7c49
Some checks failed
desktop-builds / build-linux (push) Failing after 0s
desktop-builds / build-windows (push) Failing after 0s
feat: add Tauri desktop builds, improve gameplay UX, and clean repo
2026-03-18 22:46:45 -04:00

1484 lines
39 KiB
JavaScript

/*******************Reset Previous Content******************/
$('body')[0].innerHTML='<audio id="music" src="music/PilgrimsOnALongJourney.mp3" loop="loop"></audio>'
+'<audio id="diesound" src="music/glass.wav"></audio>'
+'<audio id="killsound" src="music/kill.wav"></audio>'
+'<audio id="diamondsound" src="music/diamond.wav"></audio>';
if (timer) window.clearInterval(timer);
/*******************Create Canvas**********************/
var width=document.documentElement.clientWidth;
var height=document.documentElement.clientHeight;
$('body').prepend('<canvas id="canv" tabindex="0" style="position:absolute;left:0px;top:0px;" width="'+width+'" height="'+height+'">Please use a modern browser.</canvas>');
var canvasEl=$('#canv')[0];
var cv=canvasEl.getContext('2d');
/*******************Math Helpers********************/
var cos=Math.cos, sin=Math.sin, random=Math.random, PI=Math.PI, atan2=Math.atan2, floor=Math.floor, sqrt=Math.sqrt;
function cube(x)
{
return x*x;
}
function rad(d)
{
return d/180*PI;
}
function xy(u)
{
return {x:u.r*cos(u.t), y:u.r*sin(u.t)};
}
function dis2(x1,y1,x2,y2)
{
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
function ran(a,b)
{
return a+(b-a)*random();
}
function ranInt(a,b)
{
return floor(a+(b-a+1)*random());
}
function min(a,b)
{
return a>b?b:a;
}
/*******************Strings*************************/
var _gamename='ENDLESS SEA';
var _instructions='TUTORIAL';
var _about='ABOUT';
var _startgame='START GAME';
var _pause='PAUSE';
var _continue='CONTINUE';
var _gameover='DREAM AWAKE';
var _scoreis='YOUR SCORE: ';
var _ranking='RANKING';
var _tryagain='TRY AGAIN';
var _settings='SETTINGS';
var _fullscreen='FULLSCREEN';
var _windowed='WINDOWED';
var _scorereset='RESET SCORES';
var _autopause='AUTO PAUSED';
var _top_score='Score: ';
var _top_life='Life: ';
var _top_level='Level: ';
var _life='Life: ';
var _wudi='Superfish';
var _jiansu='Speed Down';
var _qingping='Big Bomb';
var _1up='1 UP';
var _bianxiao='Small World';
/*******************Globals And Constants***************/
var mx,my,bg,pl,plSize,cursorSize,ballSize,bigBallSize,bSize,butterflySize,starSize,d1,d2,d3,t1,t2,score,u1,u2,
fps,planeShape,butterflyLine,butterflyShape,diamondShape,Star_6,balls,bigBalls,butterflys,stars,diamonds,waves,ballSpeed,
bigBallSpeed,butterflySpeed,starSpeed,waveSpeed,waveRSpeed,waveWidth,waveMaxR,waveR,waveNum,ballDensity,bigBallDensity,butterflyDensity,starDensity,diamondDensity,ballStyle,instructionsContent,aboutContent,
clock,died,level,judge,startBgColor=ranInt(0,359),bgColorTimer=0,life,wudi,wudiTimer,smallTimer,slowTimer,timer,flash,pause,pauseTimes,scoreArr,gameStarted,slowFactor,smallFactor,pixelRatio,pauseMessage;
var scoreArr = [];
var STORAGE_KEYS = {
scores: 'endlessSea.scores',
settings: 'endlessSea.settings'
};
var DEFAULT_SETTINGS = {
musicVolume: 55,
sfxVolume: 80,
muted: false,
autoPause: true,
keyboardControl: true
};
var settings = loadSettings();
var keyState = {left:false,right:false,up:false,down:false};
function loadScores()
{
var raw = window.localStorage.getItem(STORAGE_KEYS.scores);
if (!raw) return [];
try
{
var parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
return parsed.filter(function(item) { return typeof item === 'number' && !isNaN(item); });
}
catch (err)
{
return [];
}
}
function saveScores(scores)
{
window.localStorage.setItem(STORAGE_KEYS.scores, JSON.stringify(scores));
}
function clamp(value, lower, upper)
{
return min(max(value, lower), upper);
}
function loadSettings()
{
var raw = window.localStorage.getItem(STORAGE_KEYS.settings);
if (!raw) return {
musicVolume: DEFAULT_SETTINGS.musicVolume,
sfxVolume: DEFAULT_SETTINGS.sfxVolume,
muted: DEFAULT_SETTINGS.muted,
autoPause: DEFAULT_SETTINGS.autoPause,
keyboardControl: DEFAULT_SETTINGS.keyboardControl
};
try
{
var parsed = JSON.parse(raw);
return {
musicVolume: clamp(Number(parsed.musicVolume === undefined ? DEFAULT_SETTINGS.musicVolume : parsed.musicVolume), 0, 100),
sfxVolume: clamp(Number(parsed.sfxVolume === undefined ? DEFAULT_SETTINGS.sfxVolume : parsed.sfxVolume), 0, 100),
muted: !!parsed.muted,
autoPause: parsed.autoPause === undefined ? DEFAULT_SETTINGS.autoPause : !!parsed.autoPause,
keyboardControl: parsed.keyboardControl === undefined ? DEFAULT_SETTINGS.keyboardControl : !!parsed.keyboardControl
};
}
catch (err)
{
return {
musicVolume: DEFAULT_SETTINGS.musicVolume,
sfxVolume: DEFAULT_SETTINGS.sfxVolume,
muted: DEFAULT_SETTINGS.muted,
autoPause: DEFAULT_SETTINGS.autoPause,
keyboardControl: DEFAULT_SETTINGS.keyboardControl
};
}
}
function saveSettings()
{
window.localStorage.setItem(STORAGE_KEYS.settings, JSON.stringify(settings));
}
function recordScore(totalScore)
{
var scores = loadScores();
scores.push(totalScore);
scores.sort(function(a, b) { return b - a; });
saveScores(scores);
return scores;
}
function getTopScores(limit)
{
var scores = loadScores().sort(function(a, b) { return b - a; });
while (scores.length < limit) scores.push(0);
return scores.slice(0, limit);
}
function renderRanking(scores)
{
var rankContent = '';
for (var i = 0; i < scores.length; i++)
{
rankContent += '<div class="right2"' + (i === 0 ? ' style="margin-top:12px"' : '') + '>' + (i + 1) + '. ' + scores[i] + '</div>';
}
$('#right2')[0].innerHTML = rankContent;
}
function clearLoop()
{
if (timer)
{
window.clearInterval(timer);
timer = null;
}
}
function syncCursor()
{
$('html').css({cursor: (gameStarted && !pause && !died) ? 'none' : 'default'});
}
function safePlayAudio(audio)
{
if (!audio) return;
var playPromise = audio.play();
if (playPromise && playPromise.catch) playPromise.catch(function() {});
}
function applyAudioSettings()
{
var music = $('#music')[0];
if (music)
{
music.volume = settings.musicVolume / 100;
music.muted = settings.muted;
}
var soundIds = ['diesound', 'killsound', 'diamondsound'];
for (var i = 0; i < soundIds.length; i++)
{
var sound = $('#' + soundIds[i])[0];
if (sound)
{
sound.volume = settings.sfxVolume / 100;
sound.muted = settings.muted;
}
}
}
function playMusic()
{
applyAudioSettings();
safePlayAudio($('#music')[0]);
}
function pauseMusic()
{
var music = $('#music')[0];
if (music) music.pause();
}
function playSound(id)
{
var sound = $('#' + id)[0];
if (!sound) return;
applyAudioSettings();
try
{
sound.pause();
sound.currentTime = 0;
}
catch (err)
{
}
safePlayAudio(sound);
}
function syncCanvasResolution()
{
pixelRatio = max(window.devicePixelRatio || 1, 1);
canvasEl.width = floor(width * pixelRatio);
canvasEl.height = floor(height * pixelRatio);
$('#canv').css({width: width+'px', height: height+'px'});
cv.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}
function isFullscreen()
{
return !!document.fullscreenElement;
}
function toggleFullscreen()
{
if (isFullscreen())
{
if (document.exitFullscreen) document.exitFullscreen();
return;
}
var target = document.documentElement;
if (target.requestFullscreen) target.requestFullscreen().catch(function() {});
}
function renderSettingsPanel()
{
if (!$('#settingspanel').length) return;
$('#music-volume').val(settings.musicVolume);
$('#music-volume-value').text(settings.musicVolume+'%');
$('#sfx-volume').val(settings.sfxVolume);
$('#sfx-volume-value').text(settings.sfxVolume+'%');
$('#mute-toggle').prop('checked', settings.muted);
$('#autopause-toggle').prop('checked', settings.autoPause);
$('#keyboard-toggle').prop('checked', settings.keyboardControl);
$('#fullscreen-toggle').text(isFullscreen() ? _windowed : _fullscreen);
}
function addSettingsPanel()
{
$('body').append(
'<div id="settingspanel">' +
'<div class="settings-title">'+_settings+'</div>' +
'<div class="settings-row">' +
'<label class="settings-label" for="music-volume"><span>Music Volume</span><span id="music-volume-value"></span></label>' +
'<input class="settings-range" id="music-volume" type="range" min="0" max="100" step="1">' +
'</div>' +
'<div class="settings-row">' +
'<label class="settings-label" for="sfx-volume"><span>SFX Volume</span><span id="sfx-volume-value"></span></label>' +
'<input class="settings-range" id="sfx-volume" type="range" min="0" max="100" step="1">' +
'</div>' +
'<div class="settings-row settings-toggle"><span>Mute Audio</span><input id="mute-toggle" type="checkbox"></div>' +
'<div class="settings-row settings-toggle"><span>Auto Pause On Blur</span><input id="autopause-toggle" type="checkbox"></div>' +
'<div class="settings-row settings-toggle"><span>Keyboard Steering</span><input id="keyboard-toggle" type="checkbox"></div>' +
'<div class="settings-actions">' +
'<div id="fullscreen-toggle" class="settings-button"></div>' +
'<div id="reset-scores" class="settings-button">'+_scorereset+'</div>' +
'<div id="close-settings" class="settings-button">CLOSE</div>' +
'</div>' +
'<div class="settings-note">Desktop and browser settings are saved in local storage. Press F for fullscreen and M to mute at any time.</div>' +
'</div>'
);
renderSettingsPanel();
}
function toggleSettingsPanel(forceOpen)
{
if (!$('#settingspanel').length) return;
var shouldOpen = forceOpen;
if (typeof shouldOpen !== 'boolean') shouldOpen = $('#settingspanel').css('display') === 'none';
if (shouldOpen)
{
renderSettingsPanel();
$('#settingspanel').fadeIn(150);
}
else
{
$('#settingspanel').fadeOut(120);
}
}
function resetScores()
{
window.localStorage.removeItem(STORAGE_KEYS.scores);
scoreArr = [];
addRankingInfo();
}
function updateKeyboardCursor()
{
if (!settings.keyboardControl) return;
var dx = 0;
var dy = 0;
if (keyState.left) dx -= 1;
if (keyState.right) dx += 1;
if (keyState.up) dy -= 1;
if (keyState.down) dy += 1;
if (!dx && !dy) return;
var speed = 10;
if (dx && dy) speed *= 0.78;
mx = clamp(mx + dx * speed, 0, width);
my = clamp(my + dy * speed, 0, height);
}
function startGame()
{
$('#startgame').remove();
$('.title').remove();
$('#left').fadeOut(300);
$('#right').fadeOut(300);
toggleSettingsPanel(false);
gameStarted = true;
pause = false;
syncCursor();
clockStart();
playMusic();
}
function showPauseOverlay(message)
{
pauseMessage = message || '';
if ($('#pause').length) $('#pause').remove();
if ($('.title').length) $('.title').remove();
$('body').append('<div class="title">'+_pause+'</div><div id="pause"><div id="instructions">'+_instructions+'</div>'+
'<div id="about">'+_about+'</div><div id="settingsbutton" class="button">'+_settings+'</div><div id="fullscreenbutton" class="button">'+_fullscreen+'</div><div id="continue" class="button">'+_continue+'</div><div id="pausemessage">'+pauseMessage+'</div></div>');
layoutPanels();
}
function pauseGame(consumePause, message)
{
if (pause || !gameStarted || died || $('#startgame').length) return;
if (consumePause && pauseTimes < 0) return;
pause = true;
if (consumePause) pauseTimes--;
showPauseOverlay(message);
syncCursor();
pauseMusic();
}
function layoutPanels()
{
var sideWidth = max(width/2-240, 150);
var rightLeft = max(width/2+220, 20);
if ($('#startgame').length)
{
$('#startgame').css({left: width/2-200+'px', top: height/2-100+'px'});
}
if ($('#pause').length)
{
$('#pause').css({left: width/2-200+'px', top: height/2-100+'px'});
}
if ($('#die').length)
{
$('#die').css({left: width/2-200+'px', top: height/2-100+'px'});
}
if ($('.title').length)
{
$('.title').css({top: height/2-200+'px', left: width/2-$('.title').width()/2+'px'});
}
if ($('#left').length)
{
$('#left').css({left: '20px', width: sideWidth+'px', top: height/2-100+'px'});
}
if ($('#right').length)
{
$('#right').css({left: rightLeft+'px', width: sideWidth+'px', top: height/2-100+'px'});
}
if ($('#right2').length)
{
$('#right2').css({left: rightLeft+'px', top: height/2-100+'px'});
if (!$('#die').length)
{
$('#right2').css({width: sideWidth+'px', 'padding-top': 0+'px'});
}
}
if ($('#settingspanel').length)
{
$('#settingspanel').css({left: max(width-340, 20)+'px', top: '20px'});
}
}
function resizeGame()
{
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
syncCanvasResolution();
mx = clamp(mx || 0, 0, width);
my = clamp(my || 0, 0, height);
if (pl)
{
pl.x = clamp(pl.x, 0, width);
pl.y = clamp(pl.y, 0, height);
}
layoutPanels();
renderSettingsPanel();
drawBG();
}
function bindOverlayHover(triggerSelector, panelSelector)
{
$(document).off('mouseenter mouseleave', triggerSelector);
$(document).on('mouseenter', triggerSelector, function()
{
$(triggerSelector).css('background','#1954c0');
$(panelSelector).fadeIn(500);
});
$(document).on('mouseleave', triggerSelector, function()
{
$(triggerSelector).css('background','#3369cd');
$(panelSelector).fadeOut(300);
});
}
function max(a,b)
{
return a>b?a:b;
}
function rebuildScaledShapes()
{
Star_6 = [{r:starSize,t:rad(90)},{r:starSize/2*1.5,t:rad(60)},{r:starSize,t:rad(30)},{r:starSize/2*1.5,t:rad(0)},{r:starSize,t:rad(-30)},{r:starSize/2*1.5,t:rad(-60)},{r:starSize,t:rad(-90)},{r:starSize/2*1.5,t:rad(-120)},{r:starSize,t:rad(-150)},{r:starSize/2*1.5,t:rad(-180)},{r:starSize,t:rad(-210)},{r:starSize/2*1.5,t:rad(-240)}];
planeShape=[{r:plSize*1,t:PI+3.14},{r:plSize*0.716,t:PI+-2.98},{r:plSize*0.443,t:PI+-2.49},{r:plSize*0.443,t:PI-0.65},{r:plSize*0.716,t:PI-0.25},{r:plSize*1,t:PI+0},{r:plSize*1,t:PI+0},{r:plSize*0.716,t:PI+0.25},{r:plSize*0.443,t:PI+0.65},{r:plSize*0.443,t:PI+2.49},{r:plSize*0.716,t:PI+2.98}];
}
function rebuildJudge(diamondRadius)
{
judge={
ball:cube(plSize/4+ballSize),
bigBall:cube(plSize/4+bigBallSize),
butterfly:cube(plSize/4+butterflySize),
star:cube(plSize/4+starSize),
diamond:cube(plSize/4+diamondRadius)
};
}
function resetGameState()
{
mx=width/7,my=height/2;
bg;
pl={x:width/9,y:height/2,vx:0,vy:0,ax:0,ay:0,arc:0};
plSize=20;
cursorSize=6;
ballSize=4;
bigBallSize=40;
bSize=10;
butterflySize=bSize-2;
starSize=6;
d1 = 25;d2 = d1/25*30;d3 = d1/25*27;t1 = 20;t2 = 40;
u1=6,u2=80;
fps=60;
ballStyle='#eef';
butterflyLine=[{r:3,t:rad(15)},{r:3,t:rad(345)}];
butterflyShape=[{r:1,t:0},{r:2.7,t:rad(35)},{r:3.5,t:rad(45)},{r:3.2,t:rad(55)},{r:2.33,t:rad(95)},{r:1,t:rad(90)},{r:2,t:rad(120)},{r:2.1,t:rad(150)},{r:1,t:rad(180)},{r:2.1,t:rad(210)},{r:2,t:rad(240)},{r:1,t:rad(270)},{r:2.33,t:rad(265)},{r:3.2,t:rad(305)},{r:3.5,t:rad(315)},{r:2.7,t:rad(325)}];
diamondShape = [{r:d1,t:PI+rad(90+t1+t2/2)},{r:d2,t:PI+rad(90+t2/2)},{r:d2,t:PI+rad(90-t2/2)},{r:d1,t:PI+rad(90-t1-t2/2)},{r:0,t:PI}];
rebuildScaledShapes();
for (var i in butterflyShape) butterflyShape[i].r*=bSize;
balls=[];
bigBalls=[];
butterflys=[];
stars=[];
diamonds=[];
waves=[];
ballSpeed=4.2;
bigBallSpeed=3.8;
butterflySpeed=0.35;
starSpeed=6;
waveSpeed=ballSpeed;
waveNum=8;
waveR=5;
waveRSpeed=0.7;
waveWidth=6;
waveMaxR=100;
ballDensity=0.5;
bigBallDensity=0.08;
butterflyDensity=5;
starDensity=8;
diamondDensity=0.0012;
rebuildJudge(20);
clock=0;
score=0;
died=false;
level=0;
life=5;
wudi=false;
window.clearTimeout(wudiTimer);
window.clearTimeout(smallTimer);
window.clearTimeout(slowTimer);
flash=0;
instructionsContent='<b style="font-size:22px;"><p>TUTORIAL</p></b><p>Move the mouse or use WASD / arrow keys.</p><p>Collect diamonds for power-ups.</p><p>Press Space, Enter or P to pause.</p><p>Press F for fullscreen and M to mute.</p>';
aboutContent='<b style="font-size:22px;"><p>ABOUT</p></b><p>Original project by Bobwei Zhou and Yangmei Lin.</p><p>All visuals are drawn in code without image sprites.</p><p>Desktop and browser builds share the same game logic.</p>';
pause=false;
pauseMessage = '';
pauseTimes=30;
gameStarted = false;
keyState = {left:false,right:false,up:false,down:false};
slowFactor = 1;
smallFactor = 1;
scoreArr = [];
}
/*******************Start Game************************/
resetGameState();
syncCanvasResolution();
applyAudioSettings();
drawBG();
$('body').append('<div class="title">'+_gamename+'</div><div id="startgame">'
+'<div id="instructions">'+_instructions+'</div>'+
'<div id="about">'+_about+'</div>'+
'<div id="settingsbutton" class="button">'+_settings+'</div>'+
'<div id="fullscreenbutton" class="button">'+_fullscreen+'</div>'+
'<div id="startbutton" class="button">'+_startgame+'</div></div>');
$('body').append('<div class="moreinfo" id="left"></div>');
$('body').append('<div class="moreinfo" id="right"></div>');
$('body').append('<div class="moreinfo" id="right2"></div>');
addSettingsPanel();
layoutPanels();
addHelpInfo();
addAboutInfo();
addRankingInfo();
/*******************Drawing Functions********************/
function hsvToRgb(h,s,v)
{
var hi = floor(h/60);
var f = h/60-hi;
var u = floor(255*v);
var p = floor(255*v*(1-s));
var q = floor(255*v*(1-f*s));
var t = floor(255*v*(1-(1-f)*s));
var res=[
{r:u,g:t,b:p},
{r:q,g:u,b:p},
{r:p,g:u,b:t},
{r:p,g:q,b:u},
{r:t,g:p,b:u},
{r:u,g:p,b:q}
];
return res[hi];
}
function drawBG()
{
if (bgColorTimer%10==0)
{
var b=hsvToRgb((startBgColor+bgColorTimer/10)%360,0.14,0.92);
var c=hsvToRgb((startBgColor+bgColorTimer/10)%360,0.57,0.77);
bg=cv.createLinearGradient(0,0,0,height);
bg.addColorStop(0,'rgb('+b.r+','+b.g+','+b.b+')');
bg.addColorStop(1,'rgb('+c.r+','+c.g+','+c.b+')');
}
cv.save();
cv.fillStyle=bg;
cv.fillRect(0,0,width,height);
cv.restore();
}
function drawItem(p,x,y,d)
{
cv.beginPath();
var len=p.length;
cv.moveTo(x+p[0].r*cos(p[0].t+d), y+p[0].r*sin(p[0].t+d));
for (var i=0;i<len-1;i++)
{
cv.lineTo(x+p[i+1].r*cos(p[i+1].t+d), y+p[i+1].r*sin(p[i+1].t+d));
}
cv.lineTo(x+p[0].r*cos(p[0].t+d), y+p[0].r*sin(p[0].t+d));
cv.closePath();
}
function drawCursor()
{
cv.save();
cv.beginPath();
cv.lineWidth=1;
cv.strokeStyle='#000';
cv.shadowOffsetX = 2;
cv.shadowOffsetY = 2;
cv.shadowBlur = 2;
cv.shadowColor='#888';
var u=cursorSize;
cv.moveTo(mx-u,my);
cv.lineTo(mx+u,my);
cv.moveTo(mx,my-u);
cv.lineTo(mx,my+u);
cv.stroke();
cv.restore();
}
function drawPlane()
{
cv.save();
cv.fillStyle='#e44';
if (wudi&&((clock/4)&1)) cv.fillStyle='rgba(238,68,68,0.15)';
drawItem(planeShape,pl.x,pl.y,pl.arc);
cv.fill();
var tail=8*cos(rad(clock*7));
var tail0=0.05*cos(rad(clock*7));
var fishTail = [{r:plSize*1,t:PI+0},{r:(1.67-tail0)*plSize,t:PI+rad(10+tail)},{r:(1.67+tail0)*plSize,t:PI+rad(-10+tail)}];
drawItem(fishTail,pl.x,pl.y,pl.arc);
cv.fill();
cv.beginPath();
cv.arc(pl.x+plSize*0.5*cos(pl.arc), pl.y+plSize*0.5*sin(pl.arc), 0.1*plSize, 0, Math.PI*2, true);
cv.fillStyle = "#000000";
cv.closePath();
cv.fill();
if (wudi)
{
cv.beginPath();
var bigBallStyle=cv.createRadialGradient(pl.x,pl.y,0,pl.x,pl.y,plSize*1.3);
bigBallStyle.addColorStop(0,"rgba(255,255,238,0)");
bigBallStyle.addColorStop(0.84,"rgba(255,255,238,0.1)");
bigBallStyle.addColorStop(1,"rgba(255,255,238,1)");
cv.fillStyle=bigBallStyle;
cv.arc(pl.x,pl.y,plSize*1.3,0,PI*2,true);
cv.closePath();
cv.fill();
}
cv.restore();
}
function drawOneBall(x,y,r)
{
cv.save();
cv.beginPath();
cv.fillStyle = ballStyle;
cv.arc(x, y, r, 0, PI*2, true);
cv.closePath();
cv.fill();
cv.restore();
}
function drawBalls()
{
for (var i in balls)
{
drawOneBall(balls[i].pos.x,balls[i].pos.y,balls[i].size);
}
}
function drawBigBallLightCircle(x,y,r)
{
cv.save();
cv.beginPath();
var bigBallStyle=cv.createRadialGradient(x,y,0,x,y,r);
bigBallStyle.addColorStop(0,"rgba(255,255,255,1)");
bigBallStyle.addColorStop(0.8,"rgba(255,255,255,0.23)");
bigBallStyle.addColorStop(1,"rgba(255,255,255,0)");
cv.fillStyle=bigBallStyle;
cv.arc(x,y,r,0,PI*2,true);
cv.closePath();
cv.fill();
cv.restore();
}
function drawOneBigBall(x,y,r)
{
cv.save();
cv.beginPath();
var bigBallStyle=cv.createRadialGradient(x,y,0,x,y,r);
bigBallStyle.addColorStop(0,"rgba(238,238,255,0)");
bigBallStyle.addColorStop(0.84,"rgba(238,238,255,0.1)");
bigBallStyle.addColorStop(1,"rgba(238,238,255,1)");
cv.fillStyle=bigBallStyle;
cv.arc(x,y,r,0,PI*2,true);
cv.closePath();
cv.fill();
cv.restore();
drawBigBallLightCircle(x-0.614*r,y-0.2*r,0.17*r);
drawBigBallLightCircle(x-0.57*r,y-0.323*r,0.17*r);
drawBigBallLightCircle(x-0.462*r,y-0.43*r,0.17*r);
drawBigBallLightCircle(x-0.2*r,y-0.615*r,0.17*r);
drawBigBallLightCircle(x+0.461*r,y+0.492*r,0.17*r);
drawBigBallLightCircle(x+0.554*r,y+0.415*r,0.17*r);
}
function drawBigBalls()
{
for (var i in bigBalls)
{
drawOneBigBall(bigBalls[i].pos.x,bigBalls[i].pos.y,bigBalls[i].size);
}
}
function drawOneButterfly(x,y,deg,color)
{
cv.save();
cv.strokeStyle='white';
cv.lineWidth=0.5;
cv.beginPath();
for (var i in butterflyLine)
{
cv.moveTo(x,y);
cv.lineTo(x+butterflyLine[i].r*bSize*cos(butterflyLine[i].t+deg),y+butterflyLine[i].r*bSize*sin(butterflyLine[i].t+deg));
}
cv.closePath();
cv.stroke();
cv.lineWidth=1;
cv.fillStyle=color;
cv.strokeStyle='#fff';
drawItem(butterflyShape,x,y,deg);
cv.stroke();
cv.fill();
cv.restore();
var s=ballStyle;
ballStyle='rgba(119,119,221,0.2)';
drawOneBall(x,y,bSize);
ballStyle='rgba(238,238,255,0.8)';
drawOneBall(x,y,bSize-2);
ballStyle=s;
}
function drawButterflys()
{
for (var i in butterflys)
{
drawOneButterfly(butterflys[i].x,butterflys[i].y,butterflys[i].deg,butterflys[i].color);
}
}
function drawOneStar(x,y,deg)
{
cv.save();
cv.fillStyle='rgba(255,255,128,0.9)';
cv.strokeStyle='rgba(250,250,255,0.9)';
cv.lineWidth=5;
drawItem(Star_6,x,y,deg);
cv.stroke();
cv.fill();
cv.restore();
}
function drawStars()
{
for (var i in stars)
{
drawOneStar(stars[i].x,stars[i].y,stars[i].deg);
}
}
function drawOneDiamond(dia_x,dia_y)
{
cv.save();
var u=0.8+0.2*sin(rad(clock*7));
var v=floor(245+10*sin(rad(clock*7)));
cv.fillStyle='rgba('+v+','+v+',255,'+u+')';
cv.strokeStyle='rgba(72,233,236,'+u+')';
cv.lineWidth=1;
drawItem(diamondShape,dia_x,dia_y,0);
cv.stroke();
cv.fill();
var diamond_xy = [{},{},{},{},{}];
cv.beginPath();
cv.strokeStyle='rgba(72,233,236,'+u+')';
cv.lineWidth=0.6;
for(i = 0; i<diamondShape.length;i++)
diamond_xy[i] = xy(diamondShape[i]);
cv.moveTo(dia_x+diamond_xy[0].x,dia_y+diamond_xy[0].y);
cv.lineTo(dia_x+diamond_xy[3].x,dia_y+diamond_xy[3].y);
cv.moveTo(dia_x+diamond_xy[2].x,dia_y+diamond_xy[2].y);
cv.lineTo(dia_x+diamond_xy[4].x,dia_y+diamond_xy[4].y);
cv.lineTo(dia_x+diamond_xy[1].x,dia_y+diamond_xy[1].y);
cv.moveTo(dia_x,dia_y+diamond_xy[1].y);
cv.lineTo(dia_x-d2*cos(t2/2)/2,dia_y+diamond_xy[0].y);
cv.moveTo(dia_x,dia_y+diamond_xy[1].y);
cv.lineTo(dia_x+d2*cos(t2/2)/2,dia_y+diamond_xy[0].y);
cv.stroke();
cv.restore();
}
function drawDiamonds()
{
for (var i in diamonds)
{
drawOneDiamond(diamonds[i].x,diamonds[i].y);
}
}
function drawOneWave(x,y,r,i)
{
cv.save();
cv.beginPath();
var w=cv.createRadialGradient(x,y,0,x,y,r);
w.addColorStop(0.5,'rgba(233,233,233,0)');
var u=(waveMaxR-r)/waveMaxR;
w.addColorStop(1-waveWidth/r/2,'rgba(233,233,233,'+0.4*u+')');
w.addColorStop(1,'rgba(233,233,233,0)');
cv.fillStyle = w;
cv.arc(x, y, r, 0, PI*2, true);
cv.closePath();
cv.fill();
cv.restore();
}
function drawWaves()
{
for (var i in waves)
{
drawOneWave(waves[i].x,waves[i].y,waves[i].r,i);
}
}
/*********************Spawn Hazards******************************/
function addBall(degree)
{
var d=(degree==undefined?PI:degree);
var r=sqrt(dis2(width/2,height/2,0,0));
var t=ran(-r,r);
var b={size:ran(ballSize,1.3*ballSize),color:'#eef',speed:ballSpeed+ran(0,1),pos:{x:width/2+t*cos(d-PI/2)+r*cos(PI-d),y:height/2+t*sin(d-PI/2)-r*sin(PI-d)},degree:d};
balls.push(b);
}
function addBigBall(degree)
{
var d=(degree==undefined?PI:degree);
var r=sqrt(dis2(width/2,height/2,0,0));
var t=ran(-r,r);
var b={size:ran(bigBallSize,1.2*bigBallSize),color:'#eef',speed:bigBallSpeed+ran(0,1),pos:{x:width/2+t*cos(d-PI/2)+r*cos(PI-d),y:height/2+t*sin(d-PI/2)-r*sin(PI-d)},degree:d};
bigBalls.push(b);
}
function addButterfly()
{
var z=width/butterflyDensity;
var u=ran(0,z);
var r=1.2*height;
for (var i=0;i<butterflyDensity;i++)
{
var c;
var t=[255,255,255];
t[c=ranInt(0,2)]=244;
t[(c+ranInt(1,2))%3]=ranInt(100,244);
var clr='rgba('+t[0]+','+t[1]+','+t[2]+',0.4)';
butterflys.push({x:u+r+i*z,y:r,cx:u+r+i*z,cy:0,r:r,color:clr,rspeed:rad(butterflySpeed-i*0.02),deg:rad(180-i*10),pos:rad(90-i*10)});
}
u=ran(0,z);
for (var i=0;i<butterflyDensity;i++)
{
var c;
var t=[255,255,255];
t[c=ranInt(0,2)]=244;
t[(c+ranInt(1,2))%3]=ranInt(100,244);
var clr='rgba('+t[0]+','+t[1]+','+t[2]+',0.4)';
butterflys.push({x:u+r+i*z,y:height-r,cx:u+r+i*z,cy:height,r:r,color:clr,rspeed:-rad(butterflySpeed-i*0.02),deg:rad(180+i*10),pos:rad(270+i*10)});
}
}
function addStar()
{
var x=width+50;
var y=height/2+height*cos(rad(clock)*5)*0.45;
stars.push({x:x,y:y,deg:ran(0,1),aim:atan2(pl.y-y,pl.x-x)+(clock%3-1)*rad(3),rspeed:ran(rad(-10),rad(10))});
}
function addDiamond()
{
var a=width/10,b=9*a;
var x=b-(cube(ran(a,b))-a*a)/(b*b-a*a)*(b-a);
diamonds.push({x:x,y:-50,speed:ran(1.5,2),func:ranInt(0,5)});
}
function addWave()
{
waves.push({x:pl.x,y:pl.y,r:waveR});
if (waves.length>waveNum) waves.splice(0,1);
}
/*********************Movement And Collision********************/
function ballMove()
{
for (var i=balls.length-1;i>=0;i--)
{
balls[i].pos.x+=balls[i].speed*cos(balls[i].degree);
balls[i].pos.y+=balls[i].speed*sin(balls[i].degree);
if (dis2(balls[i].pos.x,balls[i].pos.y,pl.x,pl.y)<judge.ball) kill();
if (balls[i].pos.x<-50) balls.splice(i,1);
}
}
function bigBallMove()
{
for (var i=bigBalls.length-1;i>=0;i--)
{
bigBalls[i].pos.x+=bigBalls[i].speed*cos(bigBalls[i].degree);
bigBalls[i].pos.y+=bigBalls[i].speed*sin(bigBalls[i].degree);
if (dis2(bigBalls[i].pos.x,bigBalls[i].pos.y,pl.x,pl.y)<judge.bigBall) kill();
if (bigBalls[i].pos.x<-50) bigBalls.splice(i,1);
}
}
function butterflyMove()
{
for (var i=butterflys.length-1;i>=0;i--)
{
butterflys[i].pos+=butterflys[i].rspeed;
butterflys[i].deg=(butterflys[i].rspeed>0)?(butterflys[i].pos+rad(90)):(butterflys[i].pos-rad(90));
butterflys[i].x=butterflys[i].cx+butterflys[i].r*cos(butterflys[i].pos);
butterflys[i].y=butterflys[i].cy+butterflys[i].r*sin(butterflys[i].pos);
if (dis2(butterflys[i].x,butterflys[i].y,pl.x,pl.y)<judge.butterfly) kill();
if (butterflys[i].rspeed>0&&butterflys[i].y<-50||butterflys[i].rspeed<0&&butterflys[i].y>height+50) butterflys.splice(i,1);
}
}
function starMove()
{
for (var i=stars.length-1;i>=0;i--)
{
stars[i].deg+=stars[i].rspeed;
stars[i].x+=starSpeed*cos(stars[i].aim);
stars[i].y+=starSpeed*sin(stars[i].aim);
if (dis2(stars[i].x,stars[i].y,pl.x,pl.y)<judge.star) kill();
if (stars[i].x<-50) stars.splice(i,1);
}
}
function diamondMove()
{
for (var i=diamonds.length-1;i>=0;i--)
{
diamonds[i].y+=diamonds[i].speed;
if (dis2(diamonds[i].x,diamonds[i].y-15,pl.x,pl.y)<judge.diamond)
{
eatDiamond(diamonds[i].func);
diamonds.splice(i,1);
}
else if (diamonds[i].y>height+50) diamonds.splice(i,1);
}
}
function waveMove()
{
for (var i=waves.length-1;i>=0;i--)
{
waves[i].x-=waveSpeed;
waves[i].r+=waveRSpeed;
}
}
function planeMove()
{
var dd=dis2(mx,my,pl.x,pl.y);
pl.ax=(mx-(pl.x+plSize*cos(pl.arc)))-pl.vx/u1;
pl.ay=(my-(pl.y+plSize*sin(pl.arc)))-pl.vy/u1;
var vv=dis2(pl.vx,pl.vy,0,0);
pl.x+=pl.vx/u2;
pl.y+=pl.vy/u2;
pl.vx+=pl.ax;
pl.vy+=pl.ay;
pl.arc=atan2(100,-(my-(pl.y+plSize*sin(pl.arc))))-PI/2;
}
/*********************Main Loop**********************/
function clockStart(){
clearLoop();
gameStarted = true;
timer=setInterval(function() {
if (!pause)
{
drawBG();
drawCursor();
waveMove();
drawWaves();
updateKeyboardCursor();
planeMove();
if (!died) drawPlane();
ballMove();
drawBalls();
bigBallMove();
drawBigBalls();
butterflyMove();
drawButterflys();
starMove();
drawStars();
diamondMove();
drawDiamonds();
if (!died&&(level==1&&clock%25==0||level>1&&clock%20==0)) addWave();
if (random()<diamondDensity) addDiamond();
if (level<1)
{
if (clock<1200&&random()<ballDensity*clock/1200||clock>=1200&&random()<ballDensity)
addBall(rad(ran(175,185)));
}
else
{
if (random()<ballDensity/2) addBall(rad(ran(175,185)));
}
if (level==1||level==4||level==5||level>6)
{
if (random()<bigBallDensity) addBigBall(rad(ran(175,185)));
}
if (level==2||level==5||level>=6)
{
if (clock%100==0) addButterfly();
}
if (level==3||level==4||level>=6)
{
if (clock%starDensity==0) addStar();
}
if (!died)
{
cv.save();
var txt=_top_score+(clock+score)+'0 '+_top_life;
txt+=' x'+life;
txt+=' '+_top_level+(level>6?'MAX':level+1);
cv.font="20px Arial";
var u=min(1,dis2(pl.x,pl.y,0,0)/cube(height));
cv.fillStyle='rgba(221,51,85,'+u+')';
cv.fillText(txt,30,30);
cv.font="14px Arial";
cv.fillStyle='rgba(17,31,61,0.7)';
cv.fillText('Mouse / WASD / Arrows | P Pause | F Fullscreen | M Mute',30,height-20);
cv.restore();
}
clock++;
bgColorTimer++;
if (clock==1800) level++;
if (clock>1800&&clock%1200==600) level++;
if (flash==8||flash==7||flash==2||flash==1)
{
cv.save();
cv.fillStyle='rgba(255,255,255,0.5)';
cv.fillRect(0,0,width,height);
cv.restore();
flash--;
}
else if (flash)
{
flash--;
}
}
}, 1000/fps);
}
/*********************Event Listeners**************************/
document.addEventListener('mousemove',function(e)
{
mx=e.clientX;
my=e.clientY;
});
document.addEventListener('touchstart',function(e)
{
if (!e.touches.length) return;
mx=e.touches[0].clientX;
my=e.touches[0].clientY;
}, {passive:true});
document.addEventListener('touchmove',function(e)
{
if (!e.touches.length) return;
mx=e.touches[0].clientX;
my=e.touches[0].clientY;
}, {passive:true});
document.addEventListener('click',function(e)
{
if (e.target.id=='retry')
retry();
else if (e.target.id=='startbutton')
{
startGame();
}
else if (e.target.id=='continue')
{
stopPause();
}
else if (e.target.id=='settingsbutton')
{
toggleSettingsPanel();
}
else if (e.target.id=='close-settings')
{
toggleSettingsPanel(false);
}
else if (e.target.id=='fullscreenbutton' || e.target.id=='fullscreen-toggle')
{
toggleFullscreen();
}
else if (e.target.id=='reset-scores')
{
resetScores();
}
});
document.addEventListener('keydown',startPause);
document.addEventListener('keydown',function(e)
{
var key = e.key.toLowerCase();
if (key === 'arrowleft' || key === 'a') keyState.left = true;
if (key === 'arrowright' || key === 'd') keyState.right = true;
if (key === 'arrowup' || key === 'w') keyState.up = true;
if (key === 'arrowdown' || key === 's') keyState.down = true;
if (key === 'f')
{
e.preventDefault();
toggleFullscreen();
}
if (key === 'm')
{
settings.muted = !settings.muted;
saveSettings();
applyAudioSettings();
renderSettingsPanel();
}
});
document.addEventListener('keyup',function(e)
{
var key = e.key.toLowerCase();
if (key === 'arrowleft' || key === 'a') keyState.left = false;
if (key === 'arrowright' || key === 'd') keyState.right = false;
if (key === 'arrowup' || key === 'w') keyState.up = false;
if (key === 'arrowdown' || key === 's') keyState.down = false;
});
$(document).on('input', '#music-volume', function()
{
settings.musicVolume = clamp(parseInt(this.value, 10), 0, 100);
saveSettings();
applyAudioSettings();
renderSettingsPanel();
});
$(document).on('input', '#sfx-volume', function()
{
settings.sfxVolume = clamp(parseInt(this.value, 10), 0, 100);
saveSettings();
applyAudioSettings();
renderSettingsPanel();
});
$(document).on('change', '#mute-toggle', function()
{
settings.muted = !!this.checked;
saveSettings();
applyAudioSettings();
renderSettingsPanel();
});
$(document).on('change', '#autopause-toggle', function()
{
settings.autoPause = !!this.checked;
saveSettings();
renderSettingsPanel();
});
$(document).on('change', '#keyboard-toggle', function()
{
settings.keyboardControl = !!this.checked;
saveSettings();
if (!settings.keyboardControl) keyState = {left:false,right:false,up:false,down:false};
renderSettingsPanel();
});
window.addEventListener('resize', resizeGame);
window.addEventListener('fullscreenchange', renderSettingsPanel);
document.addEventListener('visibilitychange', function()
{
if (document.hidden && settings.autoPause) pauseGame(false, _autopause);
});
window.addEventListener('blur', function()
{
if (settings.autoPause) pauseGame(false, _autopause);
});
/*********************Help And About************************/
bindOverlayHover('#instructions', '#left');
bindOverlayHover('#about', '#right');
function addHelpInfo()
{
$('#left').append(instructionsContent);
}
function addAboutInfo()
{
$('#right').append(aboutContent);
}
function addRankingInfo(t)
{
if (typeof t === 'number')
{
scoreArr = recordScore(t);
}
else
{
scoreArr = loadScores();
}
renderRanking(getTopScores(5));
}
/*********************Pause*************************/
function startPause(e)
{
if (e.repeat) return;
if (e.keyCode!=13&&e.keyCode!=32&&e.keyCode!=80)
return;
if (pause)
{
stopPause();
return;
}
if ($('#die').length)
{
retry();
return;
}
if ($('#startgame').length)
{
startGame();
return;
}
pauseGame(true, 'Paused');
}
function stopPause()
{
pause=false;
pauseMessage = '';
$('#pause').remove();
$('.title').remove();
$('#left').fadeOut(300);
$('#right').fadeOut(300);
toggleSettingsPanel(false);
syncCursor();
playMusic();
}
/*********************Diamond Effects***************************/
function eatDiamond(f)
{
if (!died)
{
playSound('diamondsound');
switch(f)
{
case 0: var s=ranInt(3,6);showInfo('Score +'+s+'000');func_addScore(s);break;
case 1: showInfo(_jiansu);func_slow(2);break;
case 2: showInfo(_1up);func_oneUp();break;
case 3: showInfo(_wudi);func_wudi(6000);break;
case 4: showInfo(_qingping);func_clear();break;
case 5: showInfo(_bianxiao);func_small(2);break;
}
}
}
function showInfo(s)
{
var t=clock;
$('body').append('<div id="info'+t+'" class="info">'+s+'</div>');
$('#info'+t).css('left',pl.x-100+'px');
$('#info'+t).css('top',pl.y-50+'px');
$('#info'+t).fadeOut(1500);
}
function func_addScore(s)
{
score+=s*100;
}
function func_slow(t)
{
var i;
if (slowFactor !== 1)
{
for (i = 0; i < balls.length; i++) balls[i].speed *= slowFactor;
for (i = 0; i < bigBalls.length; i++) bigBalls[i].speed *= slowFactor;
for (i = 0; i < butterflys.length; i++) butterflys[i].rspeed *= slowFactor;
ballSpeed *= slowFactor;
bigBallSpeed *= slowFactor;
butterflySpeed *= slowFactor;
starSpeed *= slowFactor;
ballDensity *= slowFactor;
bigBallDensity *= slowFactor;
butterflyDensity *= slowFactor;
starDensity /= slowFactor;
waveSpeed *= slowFactor;
window.clearTimeout(slowTimer);
slowFactor = 1;
}
for (i = 0; i < balls.length; i++) balls[i].speed/=t;
for (i = 0; i < bigBalls.length; i++) bigBalls[i].speed/=t;
for (i = 0; i < butterflys.length; i++) butterflys[i].rspeed/=t;
ballSpeed/=t;
bigBallSpeed/=t;
butterflySpeed/=t;
starSpeed/=t;
ballDensity/=t;
bigBallDensity/=t;
butterflyDensity/=t;
starDensity*=t;
waveSpeed/=t;
slowFactor = t;
slowTimer=setTimeout(function()
{
for (var resetBall = 0; resetBall < balls.length; resetBall++) balls[resetBall].speed *= slowFactor;
for (var resetBigBall = 0; resetBigBall < bigBalls.length; resetBigBall++) bigBalls[resetBigBall].speed *= slowFactor;
for (var resetButterfly = 0; resetButterfly < butterflys.length; resetButterfly++) butterflys[resetButterfly].rspeed *= slowFactor;
ballSpeed*=slowFactor;
bigBallSpeed*=slowFactor;
butterflySpeed*=slowFactor;
starSpeed*=slowFactor;
ballDensity*=slowFactor;
bigBallDensity*=slowFactor;
butterflyDensity*=slowFactor;
starDensity/=slowFactor;
waveSpeed*=slowFactor;
slowFactor = 1;
},8000);
flash=8;
}
function func_oneUp()
{
life++;
}
function func_wudi(time)
{
if (wudiTimer) window.clearTimeout(wudiTimer);
wudi=true;
wudiTimer=setTimeout(function()
{
wudi=false;
},time);
}
function func_clear()
{
balls=[];
bigBalls=[];
butterflys=[];
stars=[];
flash=8;
}
function func_small(t)
{
var i;
if (smallFactor !== 1)
{
for (i = 0; i < bigBalls.length; i++) bigBalls[i].size*=smallFactor;
bigBallSize*=smallFactor;
bSize*=smallFactor;
butterflySize*=smallFactor;
starSize*=smallFactor;
plSize*=smallFactor;
for (i = 0; i < butterflyShape.length; i++) butterflyShape[i].r*=smallFactor;
rebuildScaledShapes();
rebuildJudge(20);
window.clearTimeout(smallTimer);
smallFactor = 1;
}
for (i = 0; i < bigBalls.length; i++) bigBalls[i].size/=t;
bigBallSize/=t;
bSize/=t;
butterflySize/=t;
starSize/=t;
plSize/=t;
for (i = 0; i < butterflyShape.length; i++) butterflyShape[i].r/=t;
rebuildScaledShapes();
rebuildJudge(15);
smallFactor = t;
smallTimer=setTimeout(function()
{
for (var resetSize = 0; resetSize < bigBalls.length; resetSize++) bigBalls[resetSize].size*=smallFactor;
bigBallSize*=smallFactor;
bSize*=smallFactor;
butterflySize*=smallFactor;
starSize*=smallFactor;
plSize*=smallFactor;
for (var resetShape = 0; resetShape < butterflyShape.length; resetShape++) butterflyShape[resetShape].r*=smallFactor;
rebuildScaledShapes();
rebuildJudge(15);
smallFactor = 1;
},8000);
flash=8;
}
/*********************Player Death*****************************/
function kill()
{
if (!wudi&&!died)
{
life--;
showInfo(_life+life);
func_wudi(2500);
playSound('killsound');
}
if (!life)
{
die();
}
}
function die()
{
if (died) return;
died=true;
clearLoop();
toggleSettingsPanel(false);
syncCursor();
var t=clock+score;
$('body').append('<div class="title">'+_gameover+'</div><div id="die"><div id="score">'+_scoreis+(t)+'0</div>'+
'<div id="ranking">'+_ranking+'</div><div id="settingsbutton" class="button">'+_settings+'</div><div id="fullscreenbutton" class="button">'+_fullscreen+'</div><div id="retry" class="button">'+_tryagain+'</div></div>');
addRankingInfo(t*10);
layoutPanels();
$('#right2').css('width',150+'px');
$('#right2').css('vertical-align','center');
bindOverlayHover('#ranking', '#right2');
playSound('diesound');
}
function retry()
{
$('#die').remove();
$('.title').remove();
$('.info').remove();
toggleSettingsPanel(false);
resetGameState();
applyAudioSettings();
pauseMusic();
$('#music')[0].currentTime=0;
clockStart();
playMusic();
syncCursor();
$("#right2").fadeOut(300);
}