🎮ArcadeLab

Fisch 2D

by MegaFlare51
2346 lines28.9 KB
▶ Play
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Fisch 2D</title>

<style>
*{
 box-sizing:border-box;
 margin:0;
 padding:0;
 user-select:none;
 -webkit-tap-highlight-color:transparent;
}

html,body{
 width:100%;
 height:100%;
 overflow:hidden;
 background:#020817;
 font-family:Arial,sans-serif;
 touch-action:none;
 color:white;
}

canvas{
 position:fixed;
 inset:0;
 width:100%;
 height:100%;
}

button{
 border:0;
 border-radius:13px;
 padding:10px 14px;
 font-size:15px;
 font-weight:bold;
 background:#18bfff;
 color:#001522;
 box-shadow:0 4px #05658b;
 pointer-events:auto;
}

button:active{
 transform:translateY(3px);
 box-shadow:0 1px #05658b;
}

.hidden{display:none!important}

/* ================= LOADING ================= */

#loading{
 position:fixed;
 inset:0;
 z-index:100;
 display:flex;
 flex-direction:column;
 justify-content:center;
 align-items:center;
 background:linear-gradient(135deg,#020817,#062a45,#130d32);
}

.logo{
 font-size:42px;
 font-weight:900;
 text-shadow:0 0 20px #19cfff;
 margin-bottom:10px;
}

#loadText{
 font-size:18px;
 font-weight:bold;
 margin-bottom:18px;
}

.loadBox{
 width:min(85vw,430px);
 height:22px;
 border:2px solid #29d8ff;
 border-radius:20px;
 overflow:hidden;
 background:#02111e;
}

#loadBar{
 width:0%;
 height:100%;
 background:linear-gradient(90deg,#079fff,#ff263f);
}

#loadPercent{
 margin-top:8px;
 font-weight:bold;
}

/* ================= LOBBY ================= */

#lobby{
 position:fixed;
 inset:0;
 z-index:90;
 display:none;
 background:linear-gradient(135deg,#04152d,#10103b,#2b0714);
}

.lobbyTitle{
 text-align:center;
 margin-top:25px;
 font-size:34px;
 text-shadow:0 0 18px #ff2745;
}

.lobbySub{
 text-align:center;
 margin-top:6px;
 color:#aeeeff;
}

.lobbyCard{
 position:absolute;
 left:50%;
 top:50%;
 transform:translate(-50%,-50%);
 width:min(90vw,500px);
 padding:22px;
 border:2px solid #1bd3ff;
 border-radius:22px;
 background:#03182cf0;
 text-align:center;
}

.lobbyCard button{
 width:100%;
 margin-top:12px;
 font-size:19px;
}

/* ================= UI ================= */

#ui{
 position:fixed;
 inset:0;
 z-index:10;
 pointer-events:none;
}

.top{
 display:flex;
 gap:6px;
 align-items:center;
 padding:8px;
}

.box{
 background:#021827dd;
 border:1px solid #20d7ff;
 border-radius:12px;
 padding:7px 9px;
 font-size:13px;
 font-weight:bold;
}

.spacer{flex:1}

.top button{
 pointer-events:auto;
 padding:9px 11px;
}

#dateEvent{
 position:fixed;
 top:58px;
 left:50%;
 transform:translateX(-50%);
 background:#05192bed;
 border:1px solid #ff3150;
 border-radius:18px;
 padding:7px 14px;
 font-size:13px;
 font-weight:bold;
 white-space:nowrap;
}

#hint{
 position:fixed;
 top:94px;
 left:50%;
 transform:translateX(-50%);
 background:#03182deb;
 border:1px solid #35dcff;
 border-radius:20px;
 padding:8px 14px;
 font-weight:bold;
 white-space:nowrap;
}

#message{
 position:fixed;
 top:135px;
 left:50%;
 transform:translateX(-50%);
 background:#031827f5;
 border:1px solid white;
 border-radius:20px;
 padding:9px 15px;
 font-weight:bold;
 opacity:0;
 transition:.2s;
 white-space:nowrap;
}

#buttons{
 position:fixed;
 bottom:14px;
 left:0;
 right:0;
 display:flex;
 justify-content:center;
}

#start{
 font-size:19px;
 padding:13px 28px;
}

/* ================= MENU ================= */

#menu{
 position:fixed;
 z-index:50;
 left:50%;
 top:50%;
 transform:translate(-50%,-50%);
 width:min(92vw,470px);
 max-height:82vh;
 overflow:auto;
 display:none;
 background:#031727fa;
 border:2px solid #1bd5ff;
 border-radius:21px;
 padding:16px;
 pointer-events:auto;
}

#menu h2{
 text-align:center;
 margin-bottom:12px;
}

.item{
 background:#072b45;
 border:1px solid #167294;
 border-radius:14px;
 padding:12px;
 margin:8px 0;
 text-align:center;
}

.bigFish{font-size:32px}

.close{
 width:100%;
 margin-top:8px;
 background:#e72e4b;
 color:white;
}

.event{
 border-color:#ff3154;
 background:#35101b;
}

.progress{
 height:13px;
 background:#020b13;
 border-radius:20px;
 overflow:hidden;
 margin:8px 0;
 border:1px solid #35dfff;
}

.progress div{
 height:100%;
 background:linear-gradient(90deg,#11bfff,#ff2444);
}

#tokenToast{
 position:fixed;
 left:50%;
 bottom:80px;
 transform:translateX(-50%);
 background:#10152c;
 border:1px solid #ffd83d;
 padding:10px 15px;
 border-radius:15px;
 display:none;
 z-index:60;
 font-weight:bold;
}
</style>
</head>

<body>

<canvas id="game"></canvas>

<!-- ================= LOADING ================= -->

<div id="loading">

 <div class="logo">🎣 FISCH 2D</div>

 <div id="loadText">Carregando sistemas...</div>

 <div class="loadBox">
  <div id="loadBar"></div>
 </div>

 <div id="loadPercent">0%</div>

</div>

<!-- ================= LOBBY ================= -->

<div id="lobby">

 <div class="lobbyTitle">FISCH 2D</div>

 <div class="lobbySub">
  🌊 Temporada especial
 </div>

 <div class="lobbyCard">

  <h2>🎮 LOBBY</h2>

  <p style="margin-top:8px">
   Dia 1 até 5 de agosto
  </p>

  <button id="playLobby">
   🎣 JOGAR
  </button>

 </div>

</div>

<!-- ================= GAME ================= -->

<div id="ui" class="hidden">

 <div class="top">

  <div class="box">
   ⭐ Lv <span id="level">1</span>
  </div>

  <div class="box">
   XP <span id="xp">0</span>/<span id="need">100</span>
  </div>

  <div class="box">
   💰 <span id="coins">0</span>
  </div>

  <div class="spacer"></div>

  <button id="inventory">🎒</button>
  <button id="shop">🏪</button>
  <button id="events">🎪</button>

 </div>

 <div id="dateEvent">
  📅 Dia 1 até 5 de agosto
 </div>

 <div id="hint">
  🎯 TOQUE NO PEIXE!
 </div>

 <div id="message"></div>

 <div id="buttons">
  <button id="start">▶️ COMEÇAR</button>
 </div>

</div>

<!-- ================= MENU ================= -->

<div id="menu">

 <h2 id="menuTitle">🎒 INVENTÁRIO</h2>

 <div id="menuContent"></div>

 <button class="close" id="close">
  FECHAR
 </button>

</div>

<div id="tokenToast"></div>

<script>

/* =====================================================
   CANVAS
===================================================== */

const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");

let W=innerWidth;
let H=innerHeight;
let DPR=devicePixelRatio||1;

function resize(){

 W=innerWidth;
 H=innerHeight;
 DPR=devicePixelRatio||1;

 canvas.width=W*DPR;
 canvas.height=H*DPR;

 canvas.style.width=W+"px";
 canvas.style.height=H+"px";

 ctx.setTransform(DPR,0,0,DPR,0,0);
}

addEventListener("resize",resize);
resize();


/* =====================================================
   SAVE
===================================================== */

let save;

try{
 save=JSON.parse(
  localStorage.getItem("Fisch2D_Save")
 );
}catch(e){
 save=null;
}

if(!save){

 save={
  level:1,
  xp:0,
  coins:0,
  caught:[],
  total:0,
  event1:0,
  event2:0,
  event3:0,
  tokens:0,
  superTokens:0,
  king:false,
  lings:false
 };

}

function normalizeSave(){

 if(!Array.isArray(save.caught))
  save.caught=[];

 const defaults={
  level:1,
  xp:0,
  coins:0,
  total:0,
  event1:0,
  event2:0,
  event3:0,
  tokens:0,
  superTokens:0,
  king:false,
  lings:false
 };

 for(const k in defaults){

  if(typeof save[k]==="undefined")
   save[k]=defaults[k];

 }

}

normalizeSave();

function saveGame(){

 try{
  localStorage.setItem(
   "Fisch2D_Save",
   JSON.stringify(save)
  );
 }catch(e){}

}


/* =====================================================
   EVENTO ATIVO
   IMPORTANTE:
   O GAME NORMAL COMEÇA SEM EVENTO.
===================================================== */

let activeEvent=0;


/* =====================================================
   PEIXES
===================================================== */

const fishTypes=[

 {
  name:"Clow",
  emoji:"🐟",
  xp:30,
  chance:90,
  value:35,
  speed:5
 },

 {
  name:"Winder",
  emoji:"🐠",
  xp:50,
  chance:70,
  value:55,
  speed:6
 },

 {
  name:"Xisper",
  emoji:"🐡",
  xp:70,
  chance:55,
  value:80,
  speed:7
 },

 {
  name:"Fhsg",
  emoji:"🐟",
  xp:90,
  chance:35,
  value:100,
  speed:8
 },

 {
  name:"Klingh",
  emoji:"🐠",
  xp:120,
  chance:20,
  value:150,
  speed:9
 },

 {
  name:"Ukitronn",
  emoji:"🦈",
  xp:200,
  chance:1,
  value:300,
  speed:10
 },

 {
  name:"Ukinato",
  emoji:"🐉",
  xp:300,
  chance:.002,
  value:500,
  speed:11
 },

 {
  name:"Unki",
  emoji:"👾",
  xp:500,
  chance:.0001,
  value:900,
  speed:12
 },

 {
  name:"Rei",
  emoji:"👑",
  xp:1000,
  chance:.0000001,
  value:2000,
  speed:13
 }

];


/* =====================================================
   TAMANHO
===================================================== */

function getFishSize(){

 let r=Math.random()*100;

 if(save.king){

  if(r<10)
   return {
    multiplier:4,
    text:"4× MAIOR",
    rarity:"MEGA"
   };

  if(r<40)
   return {
    multiplier:3,
    text:"3× MAIOR",
    rarity:"GIGANTE"
   };

  return {
   multiplier:2,
   text:"2× MAIOR",
   rarity:"GRANDE"
  };

 }

 if(r<.001){

  return {
   multiplier:4,
   text:"4× MAIOR",
   rarity:"MEGA"
  };

 }

 if(r<3.001){

  return {
   multiplier:3,
   text:"3× MAIOR",
   rarity:"GIGANTE"
  };

 }

 if(r<33.001){

  return {
   multiplier:2,
   text:"2× MAIOR",
   rarity:"GRANDE"
  };

 }

 return {
  multiplier:1,
  text:"NORMAL",
  rarity:"NORMAL"
 };

}


/* =====================================================
   XP
===================================================== */

function xpNeeded(){

 const l=save.level;

 if(l<=10) return 100;
 if(l<=50) return 300;
 if(l<=200) return 450;
 if(l<=500) return 700;
 if(l<=700) return 700;
 if(l<=1000) return 900;
 if(l<=2000) return 1000;
 if(l<=2500) return 1200;
 if(l<=5000) return 1500;
 if(l<=10000) return 2000;

 return 3000;

}

function addXP(amount){

 save.xp+=amount;

 /*
  A cada 25 XP:
  +R$1
 */

 const reais=Math.floor(save.xp/25);

 if(reais>0){

  save.coins+=reais;
  save.xp-=reais*25;

 }

 let leveled=false;

 while(save.xp>=xpNeeded()){

  save.xp-=xpNeeded();
  save.level++;
  leveled=true;

 }

 if(leveled){

  message(
   "⬆️ NÍVEL "+save.level+"!"
  );

 }

 updateUI();
 saveGame();

}


/* =====================================================
   UI
===================================================== */

function updateUI(){

 document.getElementById("level").textContent=
  save.level;

 document.getElementById("xp").textContent=
  save.xp;

 document.getElementById("need").textContent=
  xpNeeded();

 document.getElementById("coins").textContent=
  Math.floor(save.coins);

}


/* =====================================================
   MESSAGE
===================================================== */

let messageTimer;

function message(text){

 const el=document.getElementById("message");

 el.textContent=text;
 el.style.opacity=1;

 clearTimeout(messageTimer);

 messageTimer=setTimeout(()=>{
  el.style.opacity=0;
 },1800);

}


/* =====================================================
   GAME
===================================================== */

let gameRunning=false;
let activeFish=[];
let bubbles=[];
let particles=[];
let spawnTimer=0;
let waveTime=0;


/* =====================================================
   BOLHAS
===================================================== */

function createBubbles(){

 bubbles=[];

 for(let i=0;i<55;i++){

  bubbles.push({

   x:Math.random()*W,

   y:H*.38+
     Math.random()*H*.58,

   r:1+
     Math.random()*4,

   speed:.2+
     Math.random()*.8

  });

 }

}

createBubbles();


/* =====================================================
   ESCOLHER PEIXE
===================================================== */

function chooseFish(){

 let list=fishTypes.map(f=>{

  let chance=f.chance;

  if(save.lings && chance<20)
   chance=20;

  return {
   fish:f,
   chance:chance
  };

 });

 let total=list.reduce(
  (sum,x)=>sum+x.chance,
  0
 );

 let r=Math.random()*total;

 for(const item of list){

  r-=item.chance;

  if(r<=0)
   return item.fish;

 }

 return fishTypes[0];

}


/* =====================================================
   SPAWN
   MÁXIMO DE 4
===================================================== */

function spawnFish(){

 if(!gameRunning)
  return;

 if(activeFish.length>=4)
  return;

 const type=chooseFish();
 const size=getFishSize();

 const fromLeft=Math.random()<.5;

 activeFish.push({

  type:type,

  x:fromLeft?-100:W+100,

  y:H*.48+
    Math.random()*H*.38,

  direction:fromLeft?1:-1,

  speed:type.speed+
    Math.random()*2.5,

  baseSize:30,

  multiplier:size.multiplier,

  sizeText:size.text,

  sizeRarity:size.rarity,

  phase:Math.random()*10

 });

}


/* =====================================================
   PEGAR PEIXE
===================================================== */

function catchFish(f){

 const index=activeFish.indexOf(f);

 if(index===-1)
  return;

 activeFish.splice(index,1);

 save.caught.push({
  name:f.type.name,
  multiplier:f.multiplier
 });

 save.total++;

 /*
  PROGRESSO DOS EVENTOS
  Só aumenta quando o evento estiver ativo.
 */

 if(activeEvent===1){

  save.event1++;

 }

 if(activeEvent===2){

  save.event2++;

 }

 /*
  EVENTO 3 pode continuar contando normalmente.
 */

 save.event3++;

 addXP(f.type.xp);

 save.coins+=Math.round(
  f.type.value*f.multiplier
 );

 /* EVENTO 1 */

 if(activeEvent===1 && save.event1>=600){

  save.event1=0;
  save.coins+=1000;

  message(
   "🎪 EVENTO 1 COMPLETO! +R$1000"
  );

 }

 /* EVENTO 2 */

 if(activeEvent===2 && save.event2>=5000){

  save.event2=0;
  save.tokens++;

  message(
   "⚡ EVENTO 2 COMPLETO! +1 TOKEN"
  );

 }

 /* EVENTO 3 */

 if(save.event3>=30){

  const tokens=
   Math.floor(save.event3/30);

  save.event3%=30;

  save.tokens+=tokens;

  showToken(
   "🎟️ +"+tokens+" TOKEN(S)!"
  );

 }

 createParticles(f.x,f.y);

 let extra="";

 if(f.multiplier>1)
  extra=" 🔥 "+f.sizeText;

 message(
  f.type.emoji+
  " "+f.type.name+
  " +"+f.type.xp+
  " XP"+
  extra
 );

 saveGame();
 updateUI();

}


/* =====================================================
   TOUCH
===================================================== */

function touchGame(e){

 if(!gameRunning)
  return;

 const rect=canvas.getBoundingClientRect();

 const x=
  (e.clientX-rect.left)*
  W/rect.width;

 const y=
  (e.clientY-rect.top)*
  H/rect.height;

 for(
  let i=activeFish.length-1;
  i>=0;
  i--
 ){

  const f=activeFish[i];

  const size=
   f.baseSize*f.multiplier;

  const dx=x-f.x;
  const dy=y-f.y;

  const distance=
   Math.sqrt(dx*dx+dy*dy);

  if(distance<size+28){

   catchFish(f);
   return;

  }

 }

}

canvas.addEventListener(
 "pointerdown",
 touchGame
);


/* =====================================================
   PARTICULAS
===================================================== */

function createParticles(x,y){

 for(let i=0;i<18;i++){

  particles.push({

   x:x,
   y:y,

   vx:(Math.random()-.5)*5,
   vy:(Math.random()-.5)*5,

   life:1

  });

 }

}


/* =====================================================
   COMEÇAR JOGO NORMAL
===================================================== */

document.getElementById("start").onclick=()=>{

 if(gameRunning)
  return;

 /*
  IMPORTANTE:
  sempre começa no modo normal.
  Nenhum evento é ativado automaticamente.
 */

 activeEvent=0;

 gameRunning=true;

 document.getElementById("start").textContent=
  "🎣 PESCANDO...";

 message(
  "🌊 MODO NORMAL!"
 );

 for(let i=0;i<4;i++){

  setTimeout(
   spawnFish,
   i*250
  );

 }

};


/* =====================================================
   MENU
===================================================== */

const menu=document.getElementById("menu");
const menuTitle=document.getElementById("menuTitle");
const menuContent=document.getElementById("menuContent");

function openMenu(title,html){

 menuTitle.textContent=title;
 menuContent.innerHTML=html;
 menu.style.display="block";

}

document.getElementById("close").onclick=()=>{

 menu.style.display="none";

};


/* =====================================================
   INVENTÁRIO
===================================================== */

document.getElementById("inventory").onclick=()=>{

 const counts={};

 save.caught.forEach(f=>{

  const name=
   typeof f==="string"
   ?f
   :f.name;

  const multiplier=
   typeof f==="string"
   ?1
   :f.multiplier;

  if(!counts[name]){

   counts[name]={
    normal:0,
    big:0,
    giant:0,
    mega:0
   };

  }

  if(multiplier===1)
   counts[name].normal++;

  if(multiplier===2)
   counts[name].big++;

  if(multiplier===3)
   counts[name].giant++;

  if(multiplier===4)
   counts[name].mega++;

 });

 let html=`

 <div class="item">

  🎣 <b>PEIXES CAPTURADOS</b>

  <br><br>

  Total: ${save.total}

 </div>

 `;

 fishTypes.forEach(f=>{

  if(!counts[f.name])
   return;

  const c=counts[f.name];

  html+=`

  <div class="item">

   <div class="bigFish">
    ${f.emoji}
   </div>

   <b>${f.name}</b>

   <br>

   ⭐ ${f.xp} XP

   <br><br>

   Normal: ${c.normal}

   <br>
   🔵 2×: ${c.big}

   <br>
   🟣 3×: ${c.giant}

   <br>
   🟡 4×: ${c.mega}

  </div>

  `;

 });

 html+=`

 <div class="item">

  🎟️ Tokens: ${save.tokens}

  <br>

  🟨 Tokens Super: ${save.superTokens}

 </div>

 `;

 openMenu(
  "🎒 INVENTÁRIO",
  html
 );

};


/* =====================================================
   LOJA
===================================================== */

document.getElementById("shop").onclick=()=>{

 let html=`

 <div class="item">

  <b>👑 KING</b>

  <br><br>

  Aumenta a chance de peixes grandes.

  <br>

  🎯 Até 99% de chance.

  <br>

  💰 R$30

  <br>

  <button onclick="buyKing()">
   ${save.king?"COMPRADO":"COMPRAR"}
  </button>

 </div>

 <div class="item">

  <b>🪽 LINGS</b>

  <br><br>

  Peixes abaixo de 20%
  passam para 20%.

  <br>

  💰 R$25

  <br>

  <button onclick="buyLings()">
   ${save.lings?"COMPRADO":"COMPRAR"}
  </button>

 </div>

 <div class="item">

  <b>💵 PRESOS</b>

  <br><br>

  Preso 1: R$30

  <br>
  ⏱️ 50 segundos

  <br><br>

  Preso 2: R$25

  <br>
  ⏱️ 10 segundos

 </div>

 <div class="item">

  💡 A cada 25 XP você ganha R$1.

 </div>

 `;

 openMenu(
  "🏪 LOJA",
  html
 );

};


/* =====================================================
   COMPRAS
===================================================== */

window.buyKing=function(){

 if(save.king){

  message("👑 Você já possui KING!");
  return;

 }

 if(save.coins<30){

  message("❌ Dinheiro insuficiente!");
  return;

 }

 save.coins-=30;
 save.king=true;

 saveGame();
 updateUI();

 message("👑 KING comprado!");

 document.getElementById("shop").click();

};

window.buyLings=function(){

 if(save.lings){

  message("🪽 Você já possui LINGS!");
  return;

 }

 if(save.coins<25){

  message("❌ Dinheiro insuficiente!");
  return;

 }

 save.coins-=25;
 save.lings=true;

 saveGame();
 updateUI();

 message("🪽 LINGS comprado!");

 document.getElementById("shop").click();

};


/* =====================================================
   EVENTOS
===================================================== */

document.getElementById("events").onclick=()=>{

 const p1=Math.min(
  100,
  save.event1/600*100
 );

 const p2=Math.min(
  100,
  save.event2/5000*100
 );

 let html=`

 <div class="item event">

  <b>🎪 EVENTO 1 — SÓ ENTRA</b>

  <br><br>

  <button onclick="startEvent(1)">
   ENTRAR NO EVENTO
  </button>

  <br><br>

  Pegue <b>600 peixes</b>.

  <br>

  🏆 Recompensa: R$1000

  <br><br>

  Progresso:

  <div class="progress">
   <div style="width:${p1}%"></div>
  </div>

  ${save.event1}/600

  <br><br>

  🎟️ A cada 100 peixes:
  senha aleatória.

 </div>


 <div class="item event">

  <b>⚡ EVENTO 2 — REFLEXO</b>

  <br><br>

  <button onclick="startEvent(2)">
   ENTRAR NO EVENTO
  </button>

  <br><br>

  Peixes passam
  <b>4× mais rápido</b>.

  <br>

  Pegue <b>5000 peixes</b>.

  <br>

  🏆 Recompensa: Tokens

  <br><br>

  Progresso:

  <div class="progress">
   <div style="width:${p2}%"></div>
  </div>

  ${save.event2}/5000

  <br><br>

  🎟️ A cada 500:
  token aleatório.

 </div>


 <div class="item event">

  <b>🎟️ EVENTO 3 — TOKENS</b>

  <br><br>

  A cada 30 peixes:
  +1 Token.

  <br><br>

  Tokens atuais:
  <b>${save.tokens}</b>

 </div>


 <div class="item">

  <b>🟨 TOKENS SUPER</b>

  <br><br>

  <input id="superCode"
   inputmode="numeric"
   maxlength="4"
   placeholder="Insira código"
   style="
    width:100%;
    padding:12px;
    border-radius:12px;
    border:1px solid #25d9ff;
    background:#02111c;
    color:white;
    text-align:center;
    font-size:18px;
   ">

  <button onclick="redeemSuper()">
   RESGATAR
  </button>

  <br><br>

  🟨 Tokens Super:
  ${save.superTokens}

 </div>

 `;

 openMenu(
  "🎪 EVENTOS",
  html
 );


};


/* =====================================================
   ATIVAR EVENTO
===================================================== */

window.startEvent=function(eventNumber){

 if(!gameRunning){

  message(
   "🎣 Comece o jogo primeiro!"
  );

  return;

 }

 activeEvent=eventNumber;

 activeFish=[];

 spawnTimer=0;

 if(eventNumber===1){

  message(
   "🎪 EVENTO 1 ATIVO — PISTA NOVA!"
  );

 }

 if(eventNumber===2){

  message(
   "⚡ EVENTO 2 ATIVO — PISTA RÁPIDA!"
  );

 }

 menu.style.display="none";

};


/* =====================================================
   TOKENS SUPER
===================================================== */

window.redeemSuper=function(){

 const input=document.getElementById(
  "superCode"
 );

 if(!input)
  return;

 const code=input.value.trim();

 const rewards={

  "9264":{
   chance:99,
   money:100000
  },

  "9153":{
   chance:30,
   money:200000
  },

  "8253":{
   chance:4,
   money:10000
  },

  "2838":{
   chance:.1,
   money:1000
  }

 };

 if(!rewards[code]){

  message("❌ Código inválido!");
  return;

 }

 const reward=rewards[code];

 if(Math.random()*100<reward.chance){

  save.coins+=reward.money;
  save.superTokens++;

  message(
   "🟨 TOKEN SUPER! +R$"+
   reward.money.toLocaleString("pt-BR")
  );

 }else{

  message(
   "😢 O código não ativou."
  );

 }

 saveGame();
 updateUI();

};


/* =====================================================
   DESENHO DO MAPA
===================================================== */

function draw(){

 waveTime+=.02;

 ctx.clearRect(0,0,W,H);


 /* =================================================
    MODO NORMAL
 ================================================= */

 let skyTop="#59d9ff";
 let skyBottom="#1375aa";

 let seaTop="#09add7";
 let seaMid="#0573aa";
 let seaBottom="#022c50";


 /* =================================================
    EVENTO 1
    PISTA DIFERENTE
 ================================================= */

 if(activeEvent===1){

  skyTop="#ff9b58";
  skyBottom="#9c315d";

  seaTop="#c4498c";
  seaMid="#6b2b82";
  seaBottom="#24153f";

 }


 /* =================================================
    EVENTO 2
    PISTA DIFERENTE
 ================================================= */

 if(activeEvent===2){

  skyTop="#ff314f";
  skyBottom="#4b1238";

  seaTop="#1d8cff";
  seaMid="#153b91";
  seaBottom="#080e3b";

 }


 /* CÉU */

 const sky=ctx.createLinearGradient(
  0,0,0,H*.4
 );

 sky.addColorStop(
  0,
  skyTop
 );

 sky.addColorStop(
  1,
  skyBottom
 );

 ctx.fillStyle=sky;

 ctx.fillRect(
  0,0,W,H*.4
 );


 /* SOL */

 ctx.fillStyle=
  activeEvent===1
  ?"#ffdb68"
  :"#ffe68a";

 ctx.beginPath();

 ctx.arc(
  W*.82,
  H*.14,
  34,
  0,
  Math.PI*2
 );

 ctx.fill();


 /* MAR */

 const sea=ctx.createLinearGradient(
  0,H*.35,
  0,H
 );

 sea.addColorStop(0,seaTop);
 sea.addColorStop(.45,seaMid);
 sea.addColorStop(1,seaBottom);

 ctx.fillStyle=sea;

 ctx.fillRect(
  0,
  H*.35,
  W,
  H*.65
 );


 /* =================================================
    PISTA EVENTO 1
 ================================================= */

 if(activeEvent===1){

  ctx.fillStyle="#ffcf72";

  ctx.beginPath();

  ctx.ellipse(
   W*.72,
   H*.43,
   145,
   27,
   0,
   0,
   Math.PI*2
  );

  ctx.fill();

  ctx.fillStyle="#8fda67";

  for(let i=0;i<6;i++){

   ctx.beginPath();

   ctx.arc(
    W*.65+i*25,
    H*.39,
    18,
    0,
    Math.PI*2
   );

   ctx.fill();

  }

 }


 /* =================================================
    PISTA EVENTO 2
 ================================================= */

 if(activeEvent===2){

  ctx.strokeStyle="#ff3355aa";
  ctx.lineWidth=5;

  for(let i=0;i<7;i++){

   ctx.beginPath();

   ctx.moveTo(
    i*W/7,
    H*.45
   );

   ctx.lineTo(
    i*W/7+W*.12,
    H
   );

   ctx.stroke();

  }

 }


 /* ONDAS */

 ctx.strokeStyle=
  activeEvent===2
  ?"#ff6e85aa"
  :"#b4f6ff88";

 ctx.lineWidth=2;

 for(
  let y=H*.36;
  y<H*.43;
  y+=12
 ){

  ctx.beginPath();

  for(
   let x=0;
   x<=W;
   x+=18
  ){

   const yy=
    y+
    Math.sin(
     x*.025+
     waveTime*2+
     y
    )*3;

   if(x===0)
    ctx.moveTo(x,yy);
   else
    ctx.lineTo(x,yy);

  }

  ctx.stroke();

 }


 /* ILHA NORMAL */

 ctx.fillStyle=
  activeEvent===1
  ?"#e8a14f"
  :"#d5a45a";

 ctx.beginPath();

 ctx.ellipse(
  W*.13,
  H*.39,
  100,
  24,
  0,
  0,
  Math.PI*2
 );

 ctx.fill();


 ctx.fillStyle=
  activeEvent===2
  ?"#c93152"
  :"#29964e";

 for(let i=0;i<5;i++){

  ctx.beginPath();

  ctx.arc(
   W*.06+i*25,
   H*.35,
   22,
   0,
   Math.PI*2
  );

  ctx.fill();

 }


 /* BOLHAS */

 bubbles.forEach(b=>{

  b.y-=b.speed;

  if(b.y<H*.37){

   b.y=
    H*.95+
    Math.random()*50;

  }

  ctx.strokeStyle="#c2f8ff88";
  ctx.lineWidth=1;

  ctx.beginPath();

  ctx.arc(
   b.x,
   b.y,
   b.r,
   0,
   Math.PI*2
  );

  ctx.stroke();

 });


 /* =================================================
    PEIXES
 ================================================= */

 activeFish.forEach(f=>{

  /*
   SOMENTE EVENTO 2:
   4× velocidade.
  */

  const speedMultiplier=
   activeEvent===2
   ?4
   :1;

  f.x+=
   f.speed*
   f.direction*
   speedMultiplier;

  f.y+=
   Math.sin(
    waveTime*3+
    f.phase
   )*.35;

  const size=
   f.baseSize*
   f.multiplier;

  ctx.save();

  ctx.translate(
   f.x,
   f.y
  );

  if(f.direction<0)
   ctx.scale(-1,1);

  if(f.multiplier===4){

   ctx.shadowBlur=25;
   ctx.shadowColor="#ffd83d";

  }

  else if(f.multiplier===3){

   ctx.shadowBlur=18;
   ctx.shadowColor="#bd6cff";

  }

  else if(f.multiplier===2){

   ctx.shadowBlur=12;
   ctx.shadowColor="#39ddff";

  }

  ctx.font=
   size+"px Arial";

  ctx.textAlign="center";
  ctx.textBaseline="middle";

  ctx.fillText(
   f.type.emoji,
   0,
   0
  );

  ctx.shadowBlur=0;

  if(f.multiplier>1){

   ctx.font=
    Math.max(
     10,
     size*.28
    )+
    "px Arial";

   ctx.fillStyle=
    f.multiplier===4
    ?"#ffd83d"
    :f.multiplier===3
    ?"#d59aff"
    :"#7deaff";

   ctx.fillText(
    f.sizeText,
    0,
    -size*.75
   );

  }

  ctx.restore();

 });


 /* REMOVE PEIXES */

 activeFish=
  activeFish.filter(f=>
   f.x>-180 &&
   f.x<W+180
  );


 /* SPAWN */

 if(gameRunning){

  spawnTimer++;

  if(
   spawnTimer>=20 &&
   activeFish.length<4
  ){

   spawnTimer=0;

   spawnFish();

  }

 }


 /* PARTICULAS */

 particles=
  particles.filter(p=>{

   p.x+=p.vx;
   p.y+=p.vy;

   p.vy+=.08;

   p.life-=.035;

   ctx.globalAlpha=
    Math.max(0,p.life);

   ctx.fillStyle="#fff";

   ctx.beginPath();

   ctx.arc(
    p.x,
    p.y,
    4,
    0,
    Math.PI*2
   );

   ctx.fill();

   ctx.globalAlpha=1;

   return p.life>0;

  });


 requestAnimationFrame(draw);

}


/* =====================================================
   TOKEN TOAST
===================================================== */

function showToken(text){

 const el=document.getElementById(
  "tokenToast"
 );

 el.textContent=text;
 el.style.display="block";

 setTimeout(()=>{

  el.style.display="none";

 },1800);

}


/* =====================================================
   LOADING
===================================================== */

let loadingStart=Date.now();

function loadingLoop(){

 const elapsed=
  Date.now()-loadingStart;

 const percent=
  Math.min(
   100,
   elapsed/10000*100
  );

 document.getElementById(
  "loadBar"
 ).style.width=
  percent+"%";

 document.getElementById(
  "loadPercent"
 ).textContent=
  Math.floor(percent)+"%";

 if(percent<30){

  document.getElementById(
   "loadText"
  ).textContent=
   "Carregando sistemas...";

 }

 else if(percent<70){

  document.getElementById(
   "loadText"
  ).textContent=
   "Carregando tudo...";

 }

 else{

  document.getElementById(
   "loadText"
  ).textContent=
   "Carregando resto...";

 }

 if(percent>=100){

  document.getElementById(
   "loading"
  ).style.display="none";

  document.getElementById(
   "lobby"
  ).style.display="block";

  return;

 }

 requestAnimationFrame(
  loadingLoop
 );

}

loadingLoop();


/* =====================================================
   ENTRAR NO GAME
===================================================== */

document.getElementById("playLobby").onclick=()=>{

 document.getElementById(
  "lobby"
 ).style.display="none";

 document.getElementById(
  "ui"
 ).classList.remove("hidden");

 /*
  GARANTE MODO NORMAL
 */

 activeEvent=0;

 updateUI();

 message(
  "🌊 MODO NORMAL!"
 );

};


/* =====================================================
   INICIAR
===================================================== */

draw();
updateUI();

</script>

</body>
</html>

Game Source: Fisch 2D

Creator: MegaFlare51

Libraries: none

Complexity: complex (2346 lines, 28.9 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: fisch-2d-megaflare51-mtiyv2w8" to link back to the original. Then publish at arcadelab.ai/publish.