🎮ArcadeLab

Fisch 2D

by MegaFlare51
2676 lines32.7 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:#031b2d;
  font-family:Arial,sans-serif;
  touch-action:none;
  color:white;
}

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

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

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

.box{
  background:#03182ddd;
  border:1px solid #35dfff;
  border-radius:13px;
  padding:7px 9px;
  color:white;
  font-weight:bold;
  font-size:13px;
  white-space:nowrap;
  box-shadow:0 0 15px #00cfff33;
}

.spacer{
  flex:1;
}

button{
  pointer-events:auto;
  border:0;
  border-radius:14px;
  padding:10px 12px;
  font-size:14px;
  font-weight:bold;
  background:#09baff;
  color:#002033;
  box-shadow:0 4px #05678c;
}

button:active{
  transform:translateY(4px);
  box-shadow:0 1px #05678c;
}

button:disabled{
  opacity:.45;
}

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

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

#effects{
  position:fixed;
  top:140px;
  left:50%;
  transform:translateX(-50%);
  display:flex;
  gap:6px;
}

.effect{
  display:none;
  background:#041a2cf5;
  border:1px solid #20cfff;
  border-radius:14px;
  padding:6px 9px;
  font-size:12px;
  font-weight:bold;
}

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

#start{
  font-size:20px;
  padding:14px 30px;
}

#menu{
  display:none;
  position:fixed;
  z-index:40;
  left:50%;
  top:50%;
  transform:translate(-50%,-50%);
  width:min(94vw,520px);
  max-height:86vh;
  overflow:auto;
  background:#041a2cf9;
  border:2px solid #20cfff;
  border-radius:21px;
  padding:16px;
  box-shadow:0 0 40px #00bfff55;
  pointer-events:auto;
}

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

.tabs{
  display:flex;
  gap:6px;
  margin-bottom:10px;
}

.tabs button{
  flex:1;
}

.item{
  background:#082c47;
  border:1px solid #176786;
  border-radius:14px;
  padding:12px;
  margin:8px 0;
  text-align:center;
}

.fishIcon{
  font-size:36px;
}

.fishName{
  font-size:19px;
  font-weight:bold;
  margin:4px 0;
}

.small{
  font-size:13px;
  line-height:1.65;
}

.shopItem{
  border-color:#29d8ff;
}

.buy{
  width:100%;
  margin-top:9px;
}

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

input{
  width:100%;
  margin-top:9px;
  padding:11px;
  border-radius:11px;
  border:1px solid #25d8ff;
  background:#021522;
  color:white;
  text-align:center;
  font-size:18px;
  outline:none;
}

.tokenButton{
  width:100%;
  margin-top:8px;
}

.cooldown{
  color:#ffcf45;
  font-weight:bold;
}

.stage{
  display:inline-block;
  padding:5px 10px;
  border-radius:12px;
  background:#062238;
  border:1px solid #21cfff;
  margin-top:6px;
}

@media(max-width:600px){

  .box{
    font-size:10px;
    padding:6px 7px;
  }

  button{
    font-size:12px;
    padding:9px 9px;
  }

  #hint{
    font-size:11px;
  }

}

</style>
</head>

<body>

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

<div id="ui">

  <div class="top">

    <div class="box">
      ⭐ Nível <span id="level">1</span>
    </div>

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

    <div class="box">
      💰 R$ <span id="money">0</span>
    </div>

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

    <button id="inventory">
      🎒
    </button>

    <button id="shop">
      🏪
    </button>

    <button id="mutations">
      🧬
    </button>

  </div>

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

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

  <div id="effects">

    <div class="effect" id="kingEffect">
      👑 KING: <span></span>
    </div>

    <div class="effect" id="lingsEffect">
      🔥 LINGS: <span></span>
    </div>

    <div class="effect" id="token1Effect">
      🎟️ ×5: <span></span>
    </div>

    <div class="effect" id="token2Effect">
      💀 ×10: <span></span>
    </div>

  </div>

  <div id="buttons">

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

  </div>

</div>


<div id="menu">

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

  <div class="tabs">

    <button id="inventoryTab">
      🎒 INVENTÁRIO
    </button>

    <button id="shopTab">
      🏪 LOJA
    </button>

    <button id="mutationTab">
      🧬 MUTAÇÕES
    </button>

  </div>

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

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

</div>


<script>


// ======================================================
// CANVAS
// ======================================================

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

let W=innerWidth;
let H=innerHeight;

function resize(){

  W=innerWidth;
  H=innerHeight;

  const d=devicePixelRatio||1;

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

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

  ctx.setTransform(d,0,0,d,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,
    money:0,
    xpMoneyProgress:0,
    total:0,
    caught:[]
  };

}

if(typeof save.level!=="number")
  save.level=1;

if(typeof save.xp!=="number")
  save.xp=0;

if(typeof save.money!=="number")
  save.money=0;

if(typeof save.xpMoneyProgress!=="number")
  save.xpMoneyProgress=0;

if(typeof save.total!=="number")
  save.total=0;

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


function saveGame(){

  localStorage.setItem(
    "Fisch2D_Save",
    JSON.stringify(save)
  );

}


// ======================================================
// PEIXES
// ======================================================

const fishTypes=[

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

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

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

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

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

  {
    name:"Ukitronn",
    emoji:"🐡",
    xp:200,
    chance:1,
    value:200,
    speed:11
  },

  {
    name:"Ukinato",
    emoji:"🐟",
    xp:300,
    chance:0.002,
    value:300,
    speed:12
  },

  {
    name:"Unki",
    emoji:"🐠",
    xp:500,
    chance:0.0001,
    value:500,
    speed:13
  },

  {
    name:"Rei",
    emoji:"👑🐟",
    xp:1000,
    chance:0.0000001,
    value:1000,
    speed:14
  }

];


// ======================================================
// NÍVEIS
// ======================================================

function xpNeeded(){

  const l=save.level;

  if(l>=10000)
    return 3000;

  if(l>=5000)
    return 2000;

  if(l>=2001)
    return 1200;

  if(l>=1001)
    return 1000;

  if(l>=701)
    return 900;

  if(l>=501)
    return 830;

  if(l>=201)
    return 700;

  if(l>=51)
    return 450;

  if(l>=11)
    return 300;

  return 100;

}


// ======================================================
// ESTÁGIO
// ======================================================

function getStage(){

  if(save.level>=10000)
    return "👑 FULL KETER — ESTÁGIO 2";

  if(save.level>=5000)
    return "🟣 KETER 3";

  return "🌊 KETER";

}


// ======================================================
// XP / DINHEIRO
// ======================================================

function addXP(amount){

  save.xp+=amount;

  // A cada 25 XP = R$1
  save.xpMoneyProgress+=amount;

  const moneyEarned=
    Math.floor(
      save.xpMoneyProgress/25
    );

  if(moneyEarned>0){

    save.money+=moneyEarned;

    save.xpMoneyProgress%=
      25;

  }

  let levels=0;

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

    save.xp-=xpNeeded();

    save.level++;

    levels++;

  }

  if(levels>0){

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

  }

  updateUI();
  saveGame();

}


// ======================================================
// UI
// ======================================================

function updateUI(){

  document.getElementById("level")
    .textContent=
    save.level.toLocaleString("pt-BR");

  document.getElementById("xp")
    .textContent=
    Math.floor(save.xp).toLocaleString("pt-BR");

  document.getElementById("need")
    .textContent=
    xpNeeded().toLocaleString("pt-BR");

  document.getElementById("money")
    .textContent=
    save.money.toLocaleString("pt-BR");

}


// ======================================================
// MENSAGEM
// ======================================================

function message(text){

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

  el.textContent=text;

  el.style.opacity=1;

  clearTimeout(window.messageTimer);

  window.messageTimer=
    setTimeout(()=>{

      el.style.opacity=0;

    },1800);

}


// ======================================================
// EFEITOS
// ======================================================

let kingUntil=0;
let lingsUntil=0;

let token1Until=0;
let token2Until=0;

let token1Cooldown=0;
let token2Cooldown=0;


function active(end){

  return Date.now()<end;

}


function secondsLeft(end){

  return Math.max(
    0,
    Math.ceil(
      (end-Date.now())/1000
    )
  );

}


function updateEffects(){

  const effects=[

    [
      "kingEffect",
      kingUntil
    ],

    [
      "lingsEffect",
      lingsUntil
    ],

    [
      "token1Effect",
      token1Until
    ],

    [
      "token2Effect",
      token2Until
    ]

  ];

  effects.forEach(([id,end])=>{

    const el=
      document.getElementById(id);

    if(active(end)){

      el.style.display="block";

      el.querySelector("span")
        .textContent=
        secondsLeft(end)+"s";

    }else{

      el.style.display="none";

    }

  });

}

setInterval(
  updateEffects,
  200
);


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

function chooseFish(){

  let weights=
    fishTypes.map(f=>{

      let w=f.chance;

      // LINGS:
      // peixes abaixo de 20 passam a ter peso 20

      if(
        active(lingsUntil) &&
        w<20
      ){

        w=20;

      }

      return w;

    });


  // TOKEN 1:
  // durante 7 segundos,
  // os peixes aparecem 5x mais

  // TOKEN 2:
  // durante 3 segundos,
  // os peixes aparecem 10x mais

  let total=
    weights.reduce(
      (a,b)=>a+b,
      0
    );

  let r=
    Math.random()*total;

  for(
    let i=0;
    i<fishTypes.length;
    i++
  ){

    r-=weights[i];

    if(r<=0)
      return fishTypes[i];

  }

  return fishTypes[0];

}


// ======================================================
// TAMANHO
// ======================================================

function getFishSize(){

  let r=Math.random()*100;


  // KING
  // 99% grande ou maior

  if(active(kingUntil)){

    if(r<99){

      let x=Math.random()*100;

      if(x<90){

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

      }

      if(x<99){

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

      }

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

    }

  }


  // NORMAL

  if(r<0.001){

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

  }


  if(r<3.001){

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

  }


  if(r<33.001){

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

  }


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

}


// ======================================================
// GAME
// ======================================================

let gameRunning=false;

let activeFish=[];

let bubbles=[];

let particles=[];

let spawnTimer=0;

let waveTime=0;


// ======================================================
// BOLHAS
// ======================================================

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

  bubbles.push({

    x:Math.random()*W,

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

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

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

  });

}


// ======================================================
// SPAWN
// ======================================================

function spawnFish(){

  if(!gameRunning)
    return;


  const type=
    chooseFish();

  const size=
    getFishSize();

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


  activeFish.push({

    type:type,

    x:
      fromLeft
      ? -100
      : W+100,

    y:
      H*.49+
      Math.random()*H*.36,

    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

  });

}


// ======================================================
// CAPTURAR
// ======================================================

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,

    xp:f.type.xp,

    time:Date.now()

  });


  save.total++;


  // XP
  addXP(f.type.xp);


  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();

}


// ======================================================
// TOQUE
// ======================================================

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
);


// ======================================================
// PARTÍCULAS
// ======================================================

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

    });

  }

}


// ======================================================
// START
// ======================================================

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

  if(gameRunning)
    return;


  gameRunning=true;


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


  message(
    "🌊 OS PEIXES ESTÃO PASSANDO!"
  );


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

    setTimeout(
      spawnFish,
      i*200
    );

  }

};


// ======================================================
// MENU
// ======================================================

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

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

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


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

function openInventory(){

  menu.style.display="block";

  menuTitle.textContent=
    "🎒 INVENTÁRIO";


  const counts={};


  fishTypes.forEach(f=>{

    counts[f.name]={

      normal:0,
      big:0,
      giant:0,
      mega:0,
      total:0

    };

  });


  save.caught.forEach(f=>{

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


    const multiplier=
      typeof f==="string"
      ? 1
      : Number(f.multiplier)||1;


    if(!counts[name])
      return;


    counts[name].total++;


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

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

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

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

  });


  let html=`


    <div class="item">

      <div style="font-size:21px">
        🐟 PEIXES CAPTURADOS
      </div>

      <br>

      <b style="font-size:25px">
        ${save.total.toLocaleString("pt-BR")}
      </b>

      <br><br>

      💰 R$ ${save.money.toLocaleString("pt-BR")}

      <br>

      <span class="stage">
        ${getStage()}
      </span>

    </div>


    <div class="item">

      ⭐ NÍVEL
      <b>${save.level.toLocaleString("pt-BR")}</b>

      <br>

      XP:
      <b>${save.xp.toLocaleString("pt-BR")}</b>
      /
      ${xpNeeded().toLocaleString("pt-BR")}

    </div>

  `;


  fishTypes.forEach(f=>{

    const c=
      counts[f.name];


    html+=`

      <div class="item">

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

        <div class="fishName">
          ${f.name}
        </div>

        <div class="small">

          ⭐ ${f.xp} XP

          <br>

          🎯 Chance:
          ${formatChance(f.chance)}

          <br><br>

          📦 Total:
          <b>${c.total}</b>

          <br>

          Normal:
          ${c.normal}

          <br>

          🔵 2× Grande:
          ${c.big}

          <br>

          🟣 3× Gigante:
          ${c.giant}

          <br>

          🟡 4× Mega:
          ${c.mega}

        </div>

      </div>

    `;

  });


  menuContent.innerHTML=
    html;

}


function formatChance(n){

  if(n>=1)
    return n+"%";


  return n.toLocaleString(
    "pt-BR",
    {
      maximumFractionDigits:10
    }
  )+"%";

}


// ======================================================
// LOJA
// ======================================================

function openShop(){

  menu.style.display="block";

  menuTitle.textContent=
    "🏪 LOJA";

  renderShop();

}


function renderShop(){

  menuContent.innerHTML=`


    <div class="item">

      <div style="font-size:21px">
        💰 SEU DINHEIRO
      </div>

      <br>

      <b style="font-size:26px">
        R$ ${save.money.toLocaleString("pt-BR")}
      </b>

      <br><br>

      <div class="small">

        💡 A cada <b>25 XP</b> ganhos:
        <b>+R$1</b>

      </div>

    </div>


    <div class="item shopItem">

      <div style="font-size:42px">
        👑
      </div>

      <div class="fishName">
        KING
      </div>

      <div class="small">

        Aumenta a chance de peixes
        grandes ou maiores para
        <b>99%</b>.

        <br><br>

        ⏱️ Duração:
        <b>50 segundos</b>

        <br>

        💰 Preço:
        <b>R$30</b>

      </div>

      <button
        class="buy"
        id="buyKing"
        ${save.money<30?"disabled":""}
      >
        COMPRAR 👑 — R$30
      </button>

    </div>


    <div class="item shopItem">

      <div style="font-size:42px">
        🔥
      </div>

      <div class="fishName">
        LINGS
      </div>

      <div class="small">

        Todos os peixes com chance
        abaixo de 20% passam a ter
        peso de <b>20%</b>.

        <br><br>

        ⏱️ Duração:
        <b>10 segundos</b>

        <br>

        💰 Preço:
        <b>R$25</b>

      </div>

      <button
        class="buy"
        id="buyLings"
        ${save.money<25?"disabled":""}
      >
        COMPRAR 🔥 — R$25
      </button>

    </div>


    <div class="item">

      <b>📋 TABELA DE PEIXES</b>

      <br><br>

      ${fishTypes.map(f=>`

        <div style="
          display:flex;
          justify-content:space-between;
          padding:7px;
          border-bottom:1px solid #176786;
        ">

          <span>
            ${f.emoji} ${f.name}
          </span>

          <span>
            ${f.xp} XP
          </span>

        </div>

      `).join("")}

    </div>

  `;


  document.getElementById(
    "buyKing"
  ).onclick=
    buyKing;


  document.getElementById(
    "buyLings"
  ).onclick=
    buyLings;

}


function buyKing(){

  if(save.money<30){

    message(
      "❌ Você precisa de R$30!"
    );

    return;

  }


  save.money-=30;


  kingUntil=
    Math.max(
      Date.now(),
      kingUntil
    )+
    50000;


  saveGame();
  updateUI();
  updateEffects();
  renderShop();


  message(
    "👑 KING ativado por 50 segundos!"
  );

}


function buyLings(){

  if(save.money<25){

    message(
      "❌ Você precisa de R$25!"
    );

    return;

  }


  save.money-=25;


  lingsUntil=
    Math.max(
      Date.now(),
      lingsUntil
    )+
    10000;


  saveGame();
  updateUI();
  updateEffects();
  renderShop();


  message(
    "🔥 LINGS ativado por 10 segundos!"
  );

}


// ======================================================
// MUTAÇÕES
// ======================================================

function openMutations(){

  menu.style.display="block";

  menuTitle.textContent=
    "🧬 MUTAÇÕES";

  renderMutations();

}


function renderMutations(){

  const now=Date.now();


  const token1Ready=
    now>=token1Cooldown;


  const token2Ready=
    now>=token2Cooldown;


  menuContent.innerHTML=`


    <div class="item">

      <div style="font-size:25px">
        🧬 MUTAÇÕES
      </div>

      <br>

      <div class="small">

        Digite o código correto
        para ativar a mutação.

        <br><br>

        ⚠️ Cada código possui
        seu próprio tempo de espera.

      </div>

    </div>


    <!-- TOKEN 1 -->

    <div class="item shopItem">

      <div style="font-size:38px">
        🎟️
      </div>

      <div class="fishName">
        TOKENS 1
      </div>

      <div class="small">

        Código necessário.

        <br><br>

        🎁 <b>GRÁTIS</b>

        <br>

        ⏱️ <b>7 segundos</b>

        <br>

        🐟 Peixes:
        <b>×5</b>

        <br>

        ⭐ XP:
        <b>1.000</b>

        <br><br>

        ${
          token1Ready
          ? "🟢 PRONTO"
          : "🔒 Aguarde "+
            Math.ceil(
              (token1Cooldown-now)/1000
            )+
            " segundos"
        }

      </div>


      <input
        id="tokenInput1"
        inputmode="numeric"
        maxlength="10"
        placeholder="Código do Token 1"
      >


      <button
        class="tokenButton"
        id="tokenButton1"
        ${token1Ready?"":"disabled"}
      >
        ATIVAR TOKEN 1
      </button>

    </div>


    <!-- TOKEN 2 -->

    <div class="item shopItem">

      <div style="font-size:38px">
        💀
      </div>

      <div class="fishName">
        TOKENS 2
      </div>

      <div class="small">

        Código necessário.

        <br><br>

        🎁 <b>GRÁTIS</b>

        <br>

        ⏱️ <b>3 segundos</b>

        <br>

        🐟 Peixes:
        <b>×10</b>

        <br>

        ⭐ XP:
        <b>XP normal dos peixes</b>

        <br><br>

        ${
          token2Ready
          ? "🟢 PRONTO"
          : "🔒 Aguarde "+
            Math.ceil(
              (token2Cooldown-now)/1000
            )+
            " segundos"
        }

      </div>


      <input
        id="tokenInput2"
        inputmode="numeric"
        maxlength="10"
        placeholder="Código do Token 2"
      >


      <button
        class="tokenButton"
        id="tokenButton2"
        ${token2Ready?"":"disabled"}
      >
        ATIVAR TOKEN 2
      </button>

    </div>


    <div class="item">

      <b>📜 EFEITOS ATUAIS</b>

      <br><br>

      👑 King:
      ${
        active(kingUntil)
        ? secondsLeft(kingUntil)+"s"
        : "inativo"
      }

      <br>

      🔥 Lings:
      ${
        active(lingsUntil)
        ? secondsLeft(lingsUntil)+"s"
        : "inativo"
      }

      <br>

      🎟️ Token 1:
      ${
        active(token1Until)
        ? secondsLeft(token1Until)+"s"
        : "inativo"
      }

      <br>

      💀 Token 2:
      ${
        active(token2Until)
        ? secondsLeft(token2Until)+"s"
        : "inativo"
      }

    </div>

  `;


  document.getElementById(
    "tokenButton1"
  ).onclick=
    useToken1;


  document.getElementById(
    "tokenButton2"
  ).onclick=
    useToken2;

}


function useToken1(){

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


  if(Date.now()<token1Cooldown){

    message(
      "🔒 Token 1 ainda está bloqueado!"
    );

    return;

  }


  if(input.value!=="9373"){

    message(
      "❌ Código do Token 1 incorreto!"
    );

    return;

  }


  /*
    Token 1:
    7 segundos
    peixes ×5
    1000 XP
  */

  token1Until=
    Date.now()+7000;


  token1Cooldown=
    Date.now()+180000;


  addXP(1000);


  message(
    "🎟️ TOKEN 1 ATIVADO! ×5 por 7s!"
  );


  saveGame();

  renderMutations();

}


function useToken2(){

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


  if(Date.now()<token2Cooldown){

    message(
      "🔒 Token 2 ainda está bloqueado!"
    );

    return;

  }


  if(input.value!=="93783"){

    message(
      "❌ Código do Token 2 incorreto!"
    );

    return;

  }


  /*
    Token 2:
    3 segundos
    peixes ×10
    XP normal
  */

  token2Until=
    Date.now()+3000;


  token2Cooldown=
    Date.now()+300000;


  message(
    "💀 TOKEN 2 ATIVADO! ×10 por 3s!"
  );


  saveGame();

  renderMutations();

}


// ======================================================
// BOTÕES DO MENU
// ======================================================

document.getElementById(
  "inventory"
).onclick=
  openInventory;


document.getElementById(
  "inventoryTab"
).onclick=
  openInventory;


document.getElementById(
  "shop"
).onclick=
  openShop;


document.getElementById(
  "shopTab"
).onclick=
  openShop;


document.getElementById(
  "mutations"
).onclick=
  openMutations;


document.getElementById(
  "mutationTab"
).onclick=
  openMutations;


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

  menu.style.display="none";

};


// ======================================================
// LOOP DO TOKEN 1 / TOKEN 2
// ======================================================

let specialSpawnTimer=0;


function getSpawnInterval(){

  if(active(token2Until))
    return 2;

  if(active(token1Until))
    return 5;

  return 24;

}


function getSpawnAmount(){

  if(active(token2Until))
    return 10;

  if(active(token1Until))
    return 5;

  return 1;

}


// ======================================================
// DESENHO
// ======================================================

function draw(){

  waveTime+=.02;


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


  // ==================================================
  // CÉU
  // ==================================================

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


  sky.addColorStop(
    0,
    "#62d9ff"
  );


  sky.addColorStop(
    1,
    "#1688b8"
  );


  ctx.fillStyle=sky;


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


  // ==================================================
  // SOL
  // ==================================================

  ctx.fillStyle="#ffe58a";


  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,
    "#08a8d7"
  );


  sea.addColorStop(
    .45,
    "#0575ae"
  );


  sea.addColorStop(
    1,
    "#022f55"
  );


  ctx.fillStyle=sea;


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


  // ==================================================
  // ONDAS
  // ==================================================

  ctx.strokeStyle="#a0f4ff88";

  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
  // ==================================================

  ctx.fillStyle="#d5a45a";


  ctx.beginPath();


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


  ctx.fill();


  ctx.fillStyle="#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="#b9f6ff88";

    ctx.lineWidth=1;


    ctx.beginPath();


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


    ctx.stroke();

  });


  // ==================================================
  // PEIXES
  // ==================================================

  activeFish.forEach(f=>{

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


    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
      );

    }


    // BRILHO

    if(f.multiplier===4){

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

    }

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

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

    }

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

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

    }


    ctx.font=
      size+
      "px Arial";


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


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


    ctx.shadowBlur=0;


    // NOME

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


    ctx.fillStyle="#ffffff";


    ctx.fillText(
      f.type.name,
      0,
      size*.62
    );


    // TAMANHO

    if(f.multiplier>1){

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


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


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

    }


    ctx.restore();

  });


  // ==================================================
  // REMOVER PEIXES
  // ==================================================

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


  // ==================================================
  // SPAWN
  // ==================================================

  if(gameRunning){

    spawnTimer++;


    const interval=
      getSpawnInterval();


    if(spawnTimer>=interval){

      spawnTimer=0;


      const amount=
        getSpawnAmount();


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

        spawnFish();

      }

    }

  }


  // ==================================================
  // PARTÍCULAS
  // ==================================================

  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);

}


draw();

updateUI();

updateEffects();

</script>

</body>
</html>

Game Source: Fisch 2D

Creator: MegaFlare51

Libraries: none

Complexity: complex (2676 lines, 32.7 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" to link back to the original. Then publish at arcadelab.ai/publish.