|
<!doctype html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
|
<meta http-equiv="X-UA-Compatible" content="ie=edge"> |
|
<title>Pokewar - Pocket Monster Battleroyale</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script> |
|
<script src="https://unpkg.com/[email protected]/dist/datepicker.js"></script> |
|
<script>window.onbeforeunload = () => "Do you really want to close?"</script> |
|
</head> |
|
|
|
<body class="w-min-full h-min-full"> |
|
<main class="container mx-auto"> |
|
<!-- LOADER --> |
|
<div id="loader-component" class="flex items-center justify-center h-screen"> |
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
|
</svg> |
|
loading please wait . . . |
|
</div> |
|
|
|
<!-- INTRO --> |
|
<div id="intro-component" class="flex flex-col items-center justify-center h-screen hidden"> |
|
<!-- HEADER --> |
|
<section class="flex flex-row items-center gap-2"> |
|
<img |
|
class="w-20 h-20" |
|
src="https://www.freepnglogos.com/uploads/pokemon-symbol-logo-png-31.png" |
|
alt="logo" |
|
> |
|
<h1 class="text-4xl"> |
|
<span class="italic font-semibold">Pocket Monster</span> |
|
<br><span class="font-bold text-[2.75rem] tracking-wide ">Battleroyale</span> |
|
</h1> |
|
</section> |
|
|
|
<!-- NAVIGATION --> |
|
<section id="navigation" class="flex flex-row gap-6 mt-8"> |
|
<button onclick="displaySection('monsters')" class="font-bold ml-1 my-4 cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300 flex items-center gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" /> |
|
</svg> |
|
Monsters |
|
</button> |
|
<button onclick="displaySection('battles')" class="font-bold ml-1 my-4 cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300 flex items-center gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" /> |
|
</svg> |
|
Battles |
|
</button> |
|
<button onclick="displaySection('ranks')" class="font-bold ml-1 my-4 cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300 flex items-center gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18L9 11.25l4.306 4.307a11.95 11.95 0 015.814-5.519l2.74-1.22m0 0l-5.94-2.28m5.94 2.28l-2.28 5.941" /> |
|
</svg> |
|
Ranks |
|
</button> |
|
<button onclick="displaySection('play')" class="font-bold ml-1 my-4 cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300 flex items-center gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" /> |
|
</svg> |
|
Play |
|
</button> |
|
</section> |
|
|
|
<!-- FOOTER --> |
|
<section class="absolute bottom-5 left-1/2 transform -translate-x-1/2 flex flex-col gap-2 items-center"> |
|
<div class="flex flex-row gap-2"> |
|
[<a href="https:/github.com/aasumitro/pokewar" target="_blank" class="font-bold cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300">Source Code</a>] |
|
[<!--suppress HtmlUnknownTarget --> |
|
<a href="/docs/index.html" target="_blank" class="font-bold cursor-pointer tracking-tighter text-black border-b-2 border-red-200 hover:border-red-400 dark:text-gray-300">API Spec</a>] |
|
</div> |
|
<div class="flex flex-row gap-2 "> |
|
<p class="font-bold">Pokewar</p> — |
|
<p class="font-medium italic">API Powered by</p> |
|
<a href="https://pokeapi.co/" target="_blank"> |
|
<img |
|
class="h-4" |
|
src="https://raw.githubusercontent.com/PokeAPI/media/master/logo/pokeapi_256.png" |
|
alt="pokeapi-logo" |
|
> |
|
</a> |
|
</div> |
|
|
|
</section> |
|
</div> |
|
|
|
<!-- MAIN --> |
|
<div id="main-component" class="flex flex-col hidden p-12 w-full"> |
|
<!-- NAVIGATION --> |
|
<div class="flex flex-row w-full mb-8 items-center"> |
|
<button onclick="onBackPressed()" class="rounded-full p-2 bg-gray-50 hover:bg-gray-200"> |
|
<svg |
|
xmlns="http://www.w3.org/2000/svg" |
|
fill="none" viewBox="0 0 24 24" |
|
stroke-width="1.5" |
|
stroke="currentColor" |
|
class="w-6 h-6" |
|
> |
|
<path |
|
stroke-linecap="round" |
|
stroke-linejoin="round" |
|
d="M15.75 19.5L8.25 12l7.5-7.5" |
|
/> |
|
</svg> |
|
</button> |
|
<h5 id="section-title" class="text-lg font-bold text-black ml-4"></h5> |
|
</div> |
|
|
|
<!-- MONSTER SECTION --> |
|
<div id="monster-section" class="py-4 md:py-7 px-4 md:px-8 xl:px-10 w-full hidden"> |
|
<div class="flex flex-row items-center justify-center mb-12 gap-2 w-full"> |
|
<div class="relative text-gray-400 focus-within:text-gray-600"> |
|
<span class="absolute inset-y-0 left-0 flex items-center pl-2 p-1 focus:outline-none focus:shadow-outline"> |
|
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" class="w-6 h-6"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg> |
|
</span> |
|
<label for="search"></label> |
|
<input type="search" name="search" id="search" class="py-2 text-sm text-black bg-white rounded-md pl-10 pr-2 border-solid border-2 border-gray-400 focus:bg-white focus:border-gray-600 focus:outline-none" placeholder="Search..." autocomplete="off"> |
|
</div> |
|
<button class="px-4 py-[8px] rounded-md border-solid border-2 text-gray-600 border-gray-400 bg-gray-100 flex items-center gap-2 hover:bg-gray-200 focus:bg-gray-600 focus:text-white text-sm" id="sync-monster-button" onclick="syncMoreData()"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5m8.25 3v6.75m0 0l-3-3m3 3l3-3M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" /> |
|
</svg> |
|
More (+10) |
|
</button> |
|
</div> |
|
<div class="flex flex-wrap gap-8 w-full items-center justify-center" id="monsters-list"></div> |
|
</div> |
|
|
|
<!-- BATTLE SECTION --> |
|
<div id="battle-section" class="flex flex-col w-full hidden"> |
|
<div class="sm:flex items-center justify-between w-full py-4 md:py-7 px-4 md:px-8 xl:px-10"> |
|
<div class="flex items-center"> |
|
<a onclick="displayBattles(5)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800"> |
|
<div id="last5rank" class="py-2 px-8 rounded-full">Last 5</div> |
|
</a> |
|
<a onclick="displayBattles(10)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800 ml-4 sm:ml-8"> |
|
<div id="last10rank" class="py-2 px-8 rounded-full">Last 10</div> |
|
</a> |
|
<a onclick="displayBattles(null)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800 ml-4 sm:ml-8"> |
|
<div id="all-match" class="py-2 px-8 rounded-full">All</div> |
|
</a> |
|
</div> |
|
|
|
<div class="flex flex-row mt-6 lg:mt-0 gap-2 hidden" id="date-range"> |
|
<div date-rangepicker class="flex items-center"> |
|
<div class="relative"> |
|
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> |
|
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path></svg> |
|
</div> |
|
<input name="start" id="start-between" type="text" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Select date start"> |
|
</div> |
|
<span class="mx-4 text-gray-500">to</span> |
|
<div class="relative"> |
|
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> |
|
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path></svg> |
|
</div> |
|
<input name="end" id="end-between" type="text" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Select date end"> |
|
</div> |
|
</div> |
|
<button class="px-4 py-[8px] rounded-md border-solid border-2 text-gray-600 border-gray-400 bg-gray-100 flex items-center gap-2 hover:bg-gray-200 focus:bg-gray-600 focus:text-white text-sm" onclick="displayBattles(null)"> |
|
Submit |
|
</button> |
|
</div> |
|
</div> |
|
<div class="mt-7 flex flex-wrap gap-6 w-full" id="match-list"></div> |
|
</div> |
|
|
|
<!-- RANK SECTION --> |
|
<div id="rank-section" class="flex flex-col items-center justify-center hidden"> |
|
<div class="bg-white py-4 md:py-7 px-4 md:px-8 xl:px-10"> |
|
<div class="sm:flex items-center justify-between"> |
|
<div class="flex items-center"> |
|
<a onclick="displayRanks(5)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800"> |
|
<div id="top5rank" class="py-2 px-8 rounded-full">Top 5</div> |
|
</a> |
|
<a onclick="displayRanks(10)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800 ml-4 sm:ml-8"> |
|
<div id="top10rank" class="py-2 px-8 rounded-full">Top 10</div> |
|
</a> |
|
<a onclick="displayRanks(null)" class="rounded-full focus:outline-none focus:ring-2 focus:bg-red-50 focus:ring-red-800 ml-4 sm:ml-8"> |
|
<div id="all-rank" class="py-2 px-8 rounded-full">All</div> |
|
</a> |
|
</div> |
|
</div> |
|
<div class="mt-7 overflow-x-auto"> |
|
<table class="whitespace-nowrap" id="rank-list-table"> |
|
<tbody></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<!-- PLAY SECTION --> |
|
<div id="play-section" class="flex flex-col items-center justify-center hidden w-full"> |
|
<div class="flex flex-col gap-4 items-center justify-center w-full"> |
|
<div class="flex flex-row items-center justify-center my-12 gap-2" id="battleroyale-actions"> |
|
<button class="px-4 py-[8px] rounded-md border-solid border-2 text-gray-600 border-gray-400 bg-gray-100 flex items-center gap-2 hover:bg-gray-200 focus:bg-gray-600 focus:text-white text-sm" onclick="sendMessageToWsServer('prepare')"> |
|
RANDOM PLAYER |
|
</button> |
|
<button class="px-4 py-[8px] rounded-md border-solid border-2 text-gray-600 border-gray-400 bg-gray-100 flex items-center gap-2 hover:bg-gray-200 focus:bg-gray-600 focus:text-white text-sm hidden" onclick="sendMessageToWsServer('start')" id="play-game"> |
|
PLAY BATTLE |
|
</button> |
|
</div> |
|
<div id="battleroyale-instruction"> |
|
<h5>INSTRUCTION</h5> |
|
<p> |
|
Click [RANDOM PLAYER] Button <br> to get random player, after random player displayed. |
|
<br>Click [PLAY BATTLE] Button <br> to play the battleroyale round. Enjoy your game! |
|
</p> |
|
</div> |
|
<div class="flex flex-row justify-center items-center w-full my-6 gap-2 hidden" id="battleroyale-player"></div> |
|
<div id="counting-next-battle" class="mb-5 hidden text-center"> |
|
Data will reset after <span id="counting-next-battle-time"></span>, |
|
<br>annulled button will be hide after 10s |
|
<br>and data will be proceeded. |
|
<br>the result will be proceeded, show |
|
<br>and you can play again the game after 15s. |
|
</div> |
|
<div class="flex w-full h-full rounded-lg border-2 border-solid border-gray-200 divide-x divide-solid hidden" id="battleroyale-playground"> |
|
<div class="basis-1/6 w-full p-4"> |
|
<h5 class="font-bold text-lg mb-4">Players Eliminated </h5> |
|
<div id="battleroyale-eliminated" class="w-full"></div> |
|
</div> |
|
<div class="basis-3/5 w-full p-4"> |
|
<h5 class="font-bold text-lg mb-4">Battle Logs <span id="log-id"></span></h5> |
|
<div id="battleroyale-logs" class="overflow-y-auto h-96"></div> |
|
</div> |
|
<div class="basis-1/4 w-full p-4"> |
|
<h5 class="font-bold text-lg mb-4">Battle History</h5> |
|
<div id="battleroyale-history"></div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</main> |
|
|
|
<script> |
|
const restUrl = 'http://localhost:8000/api/v1' |
|
const wsUrl = 'ws://localhost:8000/api/v1/ws' |
|
let wsConn = null |
|
let isWsConnReady = false |
|
let connId = null |
|
|
|
const loaderComponent = document.getElementById('loader-component') |
|
const introComponent = document.getElementById('intro-component') |
|
const mainComponent = document.getElementById('main-component') |
|
const sectionTitle = document.getElementById('section-title') |
|
const sectionMonster = document.getElementById('monster-section') |
|
const sectionBattle = document.getElementById('battle-section') |
|
const sectionRank = document.getElementById('rank-section') |
|
const sectionPlay = document.getElementById('play-section') |
|
const monsterList = document.getElementById('monsters-list') |
|
const matchList = document.getElementById('match-list') |
|
const rankTable = document.getElementById('rank-list-table') |
|
|
|
setTimeout(() => { |
|
loaderComponent.classList.add('hidden') |
|
introComponent.classList.remove('hidden') |
|
}, 1000) |
|
|
|
function displaySection(section) { |
|
introComponent.classList.add('hidden') |
|
mainComponent.classList.remove('hidden') |
|
sectionTitle.innerText = section[0].toUpperCase() + section.slice(1) |
|
if (section === 'play') sectionTitle.append(" Battleroyale") |
|
|
|
switch (section) { |
|
case "monsters": |
|
displayMonsters() |
|
break |
|
case "battles": |
|
displayBattles(5) |
|
break |
|
case "ranks": |
|
displayRanks(5) |
|
break |
|
case "play": |
|
displayPlayground() |
|
break |
|
default: |
|
alert("something went wrong!") |
|
onBackPressed() |
|
break |
|
} |
|
} |
|
|
|
function onBackPressed() { |
|
introComponent.classList.remove('hidden') |
|
mainComponent.classList.add('hidden') |
|
matchList.innerHTML = "" |
|
monsterList.innerHTML = "" |
|
actionSectionUI.classList.remove("hidden") |
|
playGameButton.classList.add("hidden") |
|
if (isWsConnReady && !connId) { |
|
wsConn.close() |
|
} |
|
} |
|
|
|
// ================================================================ |
|
// START MONSTER SECTION |
|
// ================================================================ |
|
let monsters = [] |
|
let monstersTemp = [] |
|
function displayMonsters() { |
|
sectionMonster.classList.remove('hidden') |
|
sectionBattle.classList.add('hidden') |
|
sectionRank.classList.add('hidden') |
|
sectionPlay.classList.add('hidden') |
|
|
|
// TODO: ADD PAGINATION |
|
this.fetchData('monsters').then((resp) => { |
|
monsters = resp.data.data |
|
monstersTemp = monsters |
|
if (monstersTemp.length >= 50) { |
|
document.getElementById("sync-monster-button") |
|
.classList.add("hidden") |
|
} |
|
showListOfMonsters() |
|
}).catch((_) => onBackPressed()) |
|
} |
|
|
|
function syncMoreData() { |
|
if (confirm("Are you sure want to add more data?!") === true) { |
|
loaderComponent.classList.remove('hidden') |
|
mainComponent.classList.add('hidden') |
|
this.fetchData('monsters/sync').then((resp) => { |
|
loaderComponent.classList.add('hidden') |
|
mainComponent.classList.remove('hidden') |
|
monsters = [...monsters, ...resp.data.data] |
|
monstersTemp = monsters |
|
showListOfMonsters() |
|
alert("sync monster success") |
|
}).catch((_) => onBackPressed()) |
|
} |
|
} |
|
|
|
document.getElementById('search').addEventListener('input', function(event) { |
|
const inputValue = event.target.value; |
|
|
|
monsters = (inputValue !== "") |
|
? monstersTemp.filter(monster => |
|
monster.name.toLowerCase() |
|
.includes(inputValue.toLowerCase())) |
|
: monstersTemp |
|
|
|
showListOfMonsters() |
|
}) |
|
|
|
function showListOfMonsters () { |
|
monsterList.innerHTML = "" |
|
sectionTitle.innerText = ` Monsters (${monsters.length})` |
|
|
|
if (monsters.length === 0) { |
|
monsterList.innerHTML += |
|
"<div class='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'>" + |
|
" Data Not Available </div>" |
|
} else { |
|
monsters.forEach(monster => { |
|
const item = monstersItemComponent(monster) |
|
monsterList.innerHTML += item |
|
}) |
|
} |
|
} |
|
|
|
function monstersItemComponent(data, isMini = false) { |
|
const types = data.types.map(item => capitalize(item)) |
|
let hp = "" |
|
data.stats.forEach(item => { |
|
if (item.name === "hp") { |
|
hp = item.name.toUpperCase() |
|
+ `(${item.base_stat})` |
|
} |
|
}) |
|
const skills = data.skills.map((item, i) => '<br>' + (i+1) + `. ${capitalize(item.name)} (${item.pp})`) |
|
const cardWidth = isMini ? "w-54" : "w-72" |
|
const imgSize = isMini ? "w-20 h-20" : "w-32 h-32" |
|
return ` |
|
<div class="`+cardWidth+` border border-gray-200 rounded-lg shadow-md"> |
|
<div class="bg-gray-800 text-white p-4"> |
|
<h2 class="text-2xl font-bold">`+capitalize(data.name)+` - `+hp+`</h2> |
|
</div> |
|
<div class="display flex flex-row"> |
|
<img |
|
src="`+data.avatar+`" |
|
alt="`+data.name+`" |
|
class="mt-4 `+imgSize+` mx-auto" |
|
/> |
|
</div> |
|
<div class="p-4"> |
|
<p class="font-bold text-lg">Type: <span class="font-normal">`+types+`</span></p> |
|
<p class="font-bold text-lg">Skills: |
|
<span class="font-normal">`+skills+`</span> |
|
</p> |
|
</div> |
|
</div> |
|
` |
|
} |
|
// ================================================================ |
|
// END MONSTER SECTION |
|
// ================================================================ |
|
|
|
// ================================================================ |
|
// START MATCH SECTION |
|
// ================================================================ |
|
function displayBattles(total = null) { |
|
const filterElement = document.getElementById("date-range") |
|
const startBetween = document.getElementById("start-between") |
|
const endBetween = document.getElementById("end-between") |
|
|
|
sectionBattle.classList.remove('hidden') |
|
sectionMonster.classList.add('hidden') |
|
sectionRank.classList.add('hidden') |
|
sectionPlay.classList.add('hidden') |
|
|
|
if (total === null) { |
|
filterElement.classList.remove("hidden") |
|
} else { |
|
filterElement.classList.add("hidden") |
|
startBetween.value = null |
|
endBetween.value = null |
|
} |
|
|
|
matchList.innerHTML = "" |
|
let path = total ? `battles?limit=${total}` : 'battles' |
|
if (endBetween?.value?.length > 0 && startBetween?.value?.length > 0) { |
|
const start = dayjs(startBetween.value).unix() * 1000000 ?? null |
|
const end = dayjs(endBetween.value).unix() * 1000000 ?? null |
|
path = `${path}?between=${start},${end}` |
|
} |
|
|
|
this.fetchData(path).then((resp) => { |
|
const battles = resp.data.data |
|
|
|
if (battles === null) { |
|
matchList.innerHTML += |
|
"<div class='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'>" + |
|
" Data Not Available </div>" |
|
} else { |
|
battles.forEach((rank, i) => { |
|
const item = matchItemComponent(rank, i+1) |
|
matchList.innerHTML += item |
|
}) |
|
} |
|
}).catch((_) => onBackPressed()) |
|
|
|
applyBadgeMatchFilter(total) |
|
} |
|
|
|
function applyBadgeMatchFilter(total) { |
|
const last5FilterBadge = document.getElementById('last5rank') |
|
const last10FilterBadge = document.getElementById('last10rank') |
|
const allMatchFilterBadge = document.getElementById('all-match') |
|
last5FilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', 'text-gray-600', |
|
'hover:text-red-700', 'hover:bg-red-100') |
|
last10FilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', |
|
'text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allMatchFilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', |
|
'text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
switch (total) { |
|
case 5: |
|
last5FilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
last10FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allMatchFilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
break; |
|
case 10: |
|
last5FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
last10FilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
allMatchFilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
break; |
|
default: |
|
last5FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
last10FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allMatchFilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
break; |
|
} |
|
} |
|
|
|
function matchItemComponent(data) { |
|
let players = "" |
|
data.players.forEach(player => players += matchPlayerComponent(player, data.started_at, data.ended_at)) |
|
return ` |
|
<div class="flex flex-col block bg-white border border-gray-200 rounded-lg shadow-md h-fit divide-y "> |
|
<div class="pokemon-header text-black p-4"> |
|
<h2 class="pokemon-name text-2xl font-bold">Battle</h2> |
|
<p class="pokemon-number font-medium">#`+data.id+`</p> |
|
</div> |
|
<div class="pokemon-header bg-gray-800 text-white p-4"> |
|
<h1 class="text-lg font-semibold">`+ formatDate(data.started_at) +`</h1> |
|
<p class="italic font-light">`+ formatTime(data.started_at) +` - `+ formatTime(data.ended_at)+`</p> |
|
<div class="flex flex-row gap-2 mt-4"> |
|
<button class="inline-flex items-center px-4 py-2 text-sm font-medium text-center text-gray-900 bg-white border border-gray-100 hover:border-gray-200 rounded-lg flex gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /> |
|
</svg> |
|
`+ diffTime(data.ended_at, data.started_at) +` |
|
</button> |
|
<button class="inline-flex items-center px-4 py-2 text-sm font-medium text-center text-gray-900 bg-white border border-gray-100 hover:border-gray-200 rounded-lg flex gap-1"> |
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> |
|
<path d="M19.889,12.818l-1.414,1.414c-0.195,0.195-0.512,0.195-0.707,0s-0.195-0.512,0-0.707l1.414-1.414 c0.195-0.195,0.512-0.195,0.707,0C20.084,12.306,20.084,12.623,19.889,12.818z M16.353,15.647c-0.195-0.195-0.512-0.195-0.707,0 |
|
l-1.414,1.414c-0.195,0.195-0.195,0.512,0,0.707s0.512,0.195,0.707,0l1.414-1.414C16.549,16.158,16.549,15.842,16.353,15.647z M12.818,19.182c-0.195-0.195-0.512-0.195-0.707,0l-1.414,1.414c-0.195,0.195-0.195,0.512,0,0.707s0.512,0.195,0.707,0l1.414-1.414 C13.013,19.694,13.013,19.377,12.818,19.182z M28.374,11.404l-1.414,1.414c-0.195,0.195-0.512,0.195-0.707,0l-1.736-1.736 |
|
l-14.498,17.26c-0.073,0.087-0.174,0.147-0.285,0.169l-7.071,1.414c-0.164,0.033-0.334-0.019-0.452-0.137 c-0.118-0.118-0.17-0.288-0.137-0.452l1.414-7.071c0.022-0.111,0.082-0.212,0.169-0.285l17.26-14.498l-1.736-1.736 |
|
c-0.195-0.195-0.195-0.512,0-0.707l1.414-1.414c0.195-0.195,0.512-0.195,0.707,0l1.768,1.768l3.182-3.182 c0.195-0.195,0.512-0.195,0.707,0l2.828,2.828c0.195,0.195,0.195,0.512,0,0.707l-3.182,3.182l1.768,1.768 C28.57,10.892,28.57,11.208,28.374,11.404z M23.778,6.101l2.121,2.121l2.828-2.828l-2.121-2.121L23.778,6.101z M23.808,10.373 l-2.18-2.18L4.435,22.634l-1.233,6.164l6.164-1.233L23.808,10.373z M27.314,11.05L20.95,4.686l-0.707,0.707l6.364,6.364 |
|
L27.314,11.05z"/> |
|
</svg> |
|
`+data.logs.length+`x |
|
</button> |
|
</div> |
|
</div> |
|
<div class="p-4"> |
|
<table class="w-full whitespace-nowrap"> |
|
<tbody>`+players+`</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
` |
|
} |
|
|
|
function matchPlayerComponent(player, battleStart, battleEnd) { |
|
const rank = player.annulled_at > 0 ? "X" : `#${player.rank}` |
|
const color = player.annulled_at > 0 ? 'bg-red-100' : "" |
|
return ` |
|
<tr tabindex="0" class="focus:outline-none h-16 border border-gray-100 rounded `+color+`"> |
|
<td class="pl-4"> |
|
<p class="mx-auto">`+rank+`</p> |
|
</td> |
|
<td> |
|
<div class="flex flex-row gap-2 items-center pr-6"> |
|
<img class="h-6 w-6 ml-2" src="`+player.avatar+`" alt="`+player.name+`"> |
|
<div class="flex flex-col pl-2"> |
|
<p class="text-base font-medium leading-none text-gray-700 mr-2">`+ capitalize(player.name) +`</p> |
|
<div class="flex flex-row gap-2 mt-2"> |
|
<span class="inline-flex items-center px-2 text-sm font-medium text-center text-gray-900 bg-white border border-gray-200 rounded-lg">`+ player.point +` pts</span> |
|
<span class="inline-flex items-center px-2 text-sm font-medium text-center text-gray-900 bg-white border border-gray-200 rounded-lg">`+ diffTime(player.eliminated_at === 0 ? battleEnd : player.eliminated_at, battleStart, true) +`</span> |
|
</div> |
|
</div> |
|
</div> |
|
</td> |
|
</tr> |
|
<tr class="h-1"></tr> |
|
` |
|
} |
|
// ================================================================ |
|
// END MATCH SECTION |
|
// ================================================================ |
|
|
|
// ================================================================ |
|
// START RANK SECTION |
|
// ================================================================ |
|
function displayRanks(total = null) { |
|
sectionMonster.classList.add('hidden') |
|
sectionBattle.classList.add('hidden') |
|
sectionRank.classList.remove('hidden') |
|
sectionPlay.classList.add('hidden') |
|
|
|
const rankTableBody = rankTable.getElementsByTagName('tbody')[0] |
|
rankTableBody.innerHTML = "" |
|
const path = total ? `ranks?limit=${total}` : 'ranks' |
|
|
|
this.fetchData(path).then((resp) => { |
|
const ranks = resp.data.data |
|
|
|
if (ranks === null) { |
|
monsterList.innerHTML += |
|
"<div class='absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2'>" + |
|
" Data Not Available </div>" |
|
} else { |
|
ranks.forEach((rank, i) => { |
|
const item = rankItemComponent(rank, i+1) |
|
rankTableBody.innerHTML += item |
|
}) |
|
} |
|
}).catch((_) => onBackPressed()) |
|
|
|
applyBadgeRankFilterStyle(total) |
|
} |
|
|
|
function applyBadgeRankFilterStyle(total) { |
|
const top5FilterBadge = document.getElementById('top5rank') |
|
const top10FilterBadge = document.getElementById('top10rank') |
|
const allFilterBadge = document.getElementById('all-rank') |
|
top5FilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', 'text-gray-600', |
|
'hover:text-red-700', 'hover:bg-red-100') |
|
top10FilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', |
|
'text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allFilterBadge.classList.remove( |
|
'bg-red-100', 'text-red-700', |
|
'text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
switch (total) { |
|
case 5: |
|
top5FilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
top10FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allFilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
break; |
|
case 10: |
|
top5FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
top10FilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
allFilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
break; |
|
default: |
|
top5FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
top10FilterBadge.classList.add('text-gray-600', 'hover:text-red-700', 'hover:bg-red-100') |
|
allFilterBadge.classList.add('bg-red-100', 'text-red-700') |
|
break; |
|
} |
|
} |
|
|
|
function rankItemComponent(data, no) { |
|
let types = "" |
|
data.types.forEach(type => { |
|
types += `<span class="rounded-full bg-gray-100 border border-gray-300 text-xs font-extralight px-2 text-gray-700">`+capitalize(type)+`</span>` |
|
}) |
|
return ` |
|
<tr tabindex="0" class="focus:outline-none h-16 border border-gray-100 rounded"> |
|
<td class="pl-5"> |
|
<div class="bg-gray-50 rounded-sm p-2"> |
|
<p class="mx-auto">#`+ (data.total_battles > 0 ? no : '') +`</p> |
|
</div> |
|
</td> |
|
<td> |
|
<div class="flex flex-row gap-2 items-center"> |
|
<img class="h-6 w-6 ml-4" src="`+data.avatar+`" alt="`+data.name+`"> |
|
<div class="flex flex-col ml-2"> |
|
<p class="text-base font-medium leading-none text-gray-700 mr-2">`+capitalize(data.name)+`</p> |
|
<div class="flex flex-row gap-2 mt-2">`+types+`</div> |
|
</div> |
|
</div> |
|
</td> |
|
<td class="pl-24"> |
|
<div class="flex items-center"> |
|
Play[<p class="text-sm leading-none text-yellow-600">`+data.total_battles+`x</p>] |
|
</div> |
|
</td> |
|
<td class="pl-5"> |
|
<div class="flex items-center"> |
|
Win[<p class="text-sm leading-none text-green-600">`+data.win_battles+`x</p>] |
|
</div> |
|
</td> |
|
<td class="pl-5"> |
|
<div class="flex items-center"> |
|
L[<p class="text-sm leading-none text-red-600">`+data.lose_battles+`x</p>] |
|
</div> |
|
</td> |
|
<td class="px-5"> |
|
<button class="py-3 px-3 text-sm focus:outline-none leading-none text-gray-700 bg-gray-100 rounded">`+data.points+` pts</button> |
|
</td> |
|
</tr> |
|
<tr class="h-3"></tr> |
|
` |
|
} |
|
// ================================================================ |
|
// END RANK SECTION |
|
// ================================================================ |
|
|
|
// ================================================================ |
|
// TODO: START BATTLE SECTION |
|
// ================================================================ |
|
const instructionSectionUI = document.getElementById("battleroyale-instruction") |
|
const monsterSectionUI = document.getElementById("battleroyale-player") |
|
const playgroundSectionUI = document.getElementById("battleroyale-playground") |
|
const actionSectionUI = document.getElementById("battleroyale-actions") |
|
const logsUI = document.getElementById("battleroyale-logs") |
|
const historyUI = document.getElementById("battleroyale-history") |
|
const eliminatedUI = document.getElementById("battleroyale-eliminated") |
|
const playGameButton = document.getElementById("play-game") |
|
const nextBattleInfo = document.getElementById("counting-next-battle") |
|
function displayPlayground() { |
|
sectionMonster.classList.add('hidden') |
|
sectionBattle.classList.add('hidden') |
|
sectionRank.classList.add('hidden') |
|
sectionPlay.classList.remove('hidden') |
|
instructionSectionUI.classList.remove('hidden') |
|
playgroundSectionUI.classList.add('hidden') |
|
nextBattleInfo.classList.add('hidden') |
|
|
|
if (connId === null) { connId = generateRandomId() } |
|
wsConn = new WebSocket(`${wsUrl}/${connId}`); |
|
|
|
wsConn.onopen = () => { |
|
isWsConnReady = true |
|
monsterSectionUI.innerHTML = "" |
|
logsUI.innerHTML = "" |
|
historyUI.innerHTML = "" |
|
eliminatedUI.innerHTML = "" |
|
sendMessageToWsServer("histories") |
|
} |
|
|
|
wsConn.onmessage = (event) => proceedWsData(event) |
|
|
|
wsConn.onclose = () => { |
|
isWsConnReady = false |
|
monsterSectionUI.innerHTML = "" |
|
logsUI.innerHTML = "" |
|
monsterSectionUI.innerHTML = "" |
|
historyUI.innerHTML = "" |
|
eliminatedUI.innerHTML = "" |
|
} |
|
} |
|
|
|
function sendMessageToWsServer(message, data = 0) { |
|
if (!isWsConnReady) { |
|
alert("Websocket connection is closed") |
|
onBackPressed() |
|
} |
|
|
|
wsConn.send(JSON.stringify({ |
|
'id': connId, |
|
'action': message, |
|
'data': data |
|
})) |
|
} |
|
|
|
function proceedWsData(event) { |
|
if (!isJsonString(event.data)) { |
|
return; |
|
} |
|
|
|
const callback = JSON.parse(event.data) |
|
|
|
if (callback.status === "error") { |
|
alert(callback.message) |
|
return |
|
} |
|
|
|
switch (callback.data_type) { |
|
case "monsters": |
|
proceedMonsterUI(callback.data) |
|
playGameButton.classList.remove("hidden") |
|
break; |
|
case "battle_histories": |
|
proceedBattleHistoryUI(callback.data) |
|
break; |
|
case "battle_logs": |
|
proceedLogsUI(callback.data) |
|
break; |
|
case "eliminated_player": |
|
proceedEliminatedPlayer(callback.data) |
|
break; |
|
case "eliminated_result": |
|
proceedEliminatedResult(callback.data) |
|
break; |
|
case "battle_result": |
|
playGameButton.classList.add("hidden") |
|
proceedMatchResultUI(callback.data) |
|
break; |
|
} |
|
} |
|
|
|
let monstersRandomTemp = [] |
|
function proceedMonsterUI(monsters) { |
|
instructionSectionUI.classList.add('hidden') |
|
playgroundSectionUI.classList.remove('hidden') |
|
monsterSectionUI.classList.remove("hidden") |
|
monsterSectionUI.innerHTML = "" |
|
logsUI.innerHTML = "" |
|
eliminatedUI.innerHTML = "" |
|
monstersRandomTemp = monsters |
|
monsters.forEach(monster => { |
|
const item = monstersItemComponent(monster, true) |
|
monsterSectionUI.innerHTML += item |
|
}) |
|
} |
|
|
|
function proceedBattleHistoryUI(histories) { |
|
if (!histories) return |
|
historyUI.innerHTML = "" |
|
histories.forEach(history => { |
|
historyUI.innerHTML += ` |
|
<p>`+history+`</p> |
|
` |
|
}) |
|
} |
|
|
|
function proceedLogsUI(log) { |
|
logsUI.innerHTML += `<p>`+log+`</p>` |
|
} |
|
|
|
function proceedEliminatedPlayer(player) { |
|
eliminatedUI.innerHTML += `<p>`+player+`</p>` |
|
} |
|
|
|
function proceedMatchResultUI(battle) { |
|
monstersRandomTemp.forEach(monster => { |
|
const player = battle.players.find(player => |
|
player.name.toLowerCase() === |
|
monster.name.toLowerCase()) |
|
monster.rank = player.rank |
|
monster.point = player.point |
|
}) |
|
monstersRandomTemp.sort((a, b) => a.rank - b.rank) |
|
actionSectionUI.classList.add('hidden') |
|
showMonsterResultUI() |
|
nextBattleInfo.classList.remove('hidden') |
|
let time = 15 |
|
const interval = setInterval(() => { |
|
document.getElementById('counting-next-battle-time') |
|
.innerText = `${time}s` |
|
if (time === 10) { |
|
showMonsterResultUI(false) |
|
sendMessageToWsServer("save") |
|
} |
|
if (time === 0) { |
|
nextBattleInfo.classList.add('hidden') |
|
actionSectionUI.classList.remove('hidden') |
|
sendMessageToWsServer("histories") |
|
clearInterval(interval) |
|
} |
|
time -= 1 |
|
}, 1000) |
|
} |
|
|
|
function proceedEliminatedResult(battle) { |
|
monstersRandomTemp.forEach(monster => { |
|
const player = battle.players.find(player => |
|
player.name.toLowerCase() === |
|
monster.name.toLowerCase()) |
|
monster.rank = player.rank |
|
monster.point = player.point |
|
monster.annulled = player.annulled_at > 0 |
|
}) |
|
monstersRandomTemp.sort((a, b) => a.rank - b.rank) |
|
} |
|
|
|
function showMonsterResultUI(displayButton = true) { |
|
monsterSectionUI.innerHTML = "" |
|
monstersRandomTemp.forEach((monster) => { |
|
let button = "" |
|
const statusColor = monster.annulled ? "border-red-100 bg-red-200" : "border-gray-200" |
|
if (displayButton) { |
|
button += ` |
|
<button class="mt-4 px-4 py-[8px] rounded-md border-solid border-2 text-red-600 border-red-400 bg-red-100 flex items-center justify-center gap-2 hover:bg-red-200 focus:bg-red focus:text-white text-sm disabled:bg-red-100 disabled:opacity-25" onclick="annulledPlayer(`+monster.id+`)" id="annulled-button-`+monster.id+`">ANNULLED</button> |
|
` |
|
} |
|
const item = monstersItemComponent(monster, true) |
|
monsterSectionUI.innerHTML += ` |
|
<div class="flex flex-col""> |
|
`+item+` |
|
<div class="mt-4 flex flex-row border-2 border-solid `+statusColor+` divide-x divide-solid w-full" id="battle-status-`+monster.id+`"> |
|
<div class="basis-1/2 w-full text-center py-6">#`+monster.rank+`</div> |
|
<div class="basis-1/2 w-full text-center py-6">`+monster.point+` pts</div> |
|
</div> |
|
`+button+` |
|
</div> |
|
` |
|
}) |
|
} |
|
|
|
function annulledPlayer(id) { |
|
const player = monstersRandomTemp.find(player => |
|
player.id === id) |
|
if (player) { player.annulled = true } |
|
const playerStatus = document.getElementById(`battle-status-${id}`) |
|
const annulledActionButton = document.getElementById(`annulled-button-${id}`) |
|
annulledActionButton.disabled = true |
|
playerStatus.classList.remove("border-gray-200") |
|
playerStatus.classList.add("border-red-100", "bg-red-200") |
|
sendMessageToWsServer('annulled', id); |
|
} |
|
// ================================================================ |
|
// END BATTLE SECTION |
|
//================================================================ |
|
|
|
|
|
// ================================================================ |
|
// API CALL SECTION |
|
// ================================================================ |
|
async function fetchData(path) { |
|
try { |
|
return await axios.get(`${restUrl}/${path}`) |
|
} catch (error) { |
|
alert(error) |
|
} |
|
} |
|
|
|
// ================================================================ |
|
// HELPER SECTION |
|
// ================================================================ |
|
function capitalize(s) { |
|
return s.toLowerCase().replace( /\b./g, function(a){ return a.toUpperCase() }) |
|
} |
|
|
|
function formatDate(time) { |
|
const date = dayjs(time / 1000) |
|
return date.format('ddd, MM/DD-YYYY') |
|
} |
|
|
|
function formatTime(time) { |
|
const date = dayjs(time / 1000) |
|
return date.format('hh:mm:ss') |
|
} |
|
|
|
function diffTime(start, end, eliminate = false) { |
|
if (start === 0) { |
|
return "-" |
|
} |
|
|
|
const pref = eliminate ? 'Stand: ' : '' |
|
|
|
return pref + |
|
dayjs(dayjs.unix(start / 1000)) |
|
.diff(dayjs.unix(end / 1000), 'microsecond') |
|
+ 'ms' |
|
} |
|
|
|
function generateRandomId() { |
|
const randLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26)); |
|
return randLetter + Date.now(); |
|
} |
|
|
|
function isJsonString(str) { |
|
try { |
|
JSON.parse(str) |
|
return true |
|
} catch (e) { |
|
return false |
|
} |
|
} |
|
</script> |
|
</body> |
|
</html> |
"+(t+1)+`. ${capitalize(e.name)} (${e.pp})`)),o=t?"w-20 h-20":"w-32 h-32";return'\n
'+capitalize(e.name)+" - "+s+'
\nType: '+n+'
\nSkills:\n '+a+"\n
\nBattle
\n#'+e.id+'
\n'+formatDate(e.started_at)+'
\n'+formatTime(e.started_at)+" - "+formatTime(e.ended_at)+'
\n'+s+'
\n \n \n'+capitalize(e.name)+'
\n#'+(e.total_battles>0?t:"")+'
\n'+capitalize(e.name)+'
\n'+e.total_battles+'x
]\n'+e.win_battles+'x
]\n'+e.lose_battles+'x
]\n"+e+"
\n "})))}function proceedLogsUI(e){logsUI.innerHTML=""+e+"
"+logsUI.innerHTML}function proceedEliminatedPlayer(e){eliminatedUI.innerHTML=""+e+"
"+eliminatedUI.innerHTML}function proceedMatchResultUI(e){monstersRandomTemp.forEach((t=>{const n=e.players.find((e=>e.name.toLowerCase()===t.name.toLowerCase()));t.rank=n.rank,t.point=n.point})),monstersRandomTemp.sort(((e,t)=>e.rank-t.rank)),actionSectionUI.classList.add("hidden"),showMonsterResultUI(),nextBattleInfo.classList.remove("hidden");let t=15;const n=setInterval((()=>{document.getElementById("counting-next-battle-time").innerText=`${t}s`,10===t&&(showMonsterResultUI(!1),sendMessageToWsServer("save")),0===t&&(nextBattleInfo.classList.add("hidden"),actionSectionUI.classList.remove("hidden"),sendMessageToWsServer("histories"),clearInterval(n)),t-=1}),1e3)}function proceedEliminatedResult(e){monstersRandomTemp.forEach((t=>{const n=e.players.find((e=>e.name.toLowerCase()===t.name.toLowerCase()));t.rank=n.rank,t.point=n.point,t.annulled=n.annulled_at>0})),monstersRandomTemp.sort(((e,t)=>e.rank-t.rank))}function showMonsterResultUI(e=!0){monsterSectionUI.innerHTML="",monstersRandomTemp.forEach((t=>{let n="";const s=t.annulled?"border-red-100 bg-red-200":"border-gray-200";e&&(n+='\n ANNULLED\n ');const a=monstersItemComponent(t,!0);monsterSectionUI.innerHTML+='\n