Skip to content

Instantly share code, notes, and snippets.

@aasumitro
Last active February 5, 2023 07:50
Show Gist options
  • Save aasumitro/412d1f57b09d1159c9a702a914226939 to your computer and use it in GitHub Desktop.
Save aasumitro/412d1f57b09d1159c9a702a914226939 to your computer and use it in GitHub Desktop.
Pokewar main index.html script
<!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></script>
</body>
</html>
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()
}
}
let monsters = []
let monstersTemp = []
function displayMonsters() {
sectionMonster.classList.remove('hidden')
sectionBattle.classList.add('hidden')
sectionRank.classList.add('hidden')
sectionPlay.classList.add('hidden')
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>
`
}
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>
`
}
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>
`
}
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);
}
async function fetchData(path) {
try {
return await axios.get(`${restUrl}/${path}`)
} catch (error) {
alert(error)
}
}
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
}
}
<!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>
@aasumitro
Copy link
Author

<script>const restUrl="http://localhost:8000/api/v1",wsUrl="ws://localhost:8000/api/v1/ws";let wsConn=null,isWsConnReady=!1,connId=null;const loaderComponent=document.getElementById("loader-component"),introComponent=document.getElementById("intro-component"),mainComponent=document.getElementById("main-component"),sectionTitle=document.getElementById("section-title"),sectionMonster=document.getElementById("monster-section"),sectionBattle=document.getElementById("battle-section"),sectionRank=document.getElementById("rank-section"),sectionPlay=document.getElementById("play-section"),monsterList=document.getElementById("monsters-list"),matchList=document.getElementById("match-list"),rankTable=document.getElementById("rank-list-table");function displaySection(e){switch(introComponent.classList.add("hidden"),mainComponent.classList.remove("hidden"),sectionTitle.innerText=e[0].toUpperCase()+e.slice(1),"play"===e&§ionTitle.append(" Battleroyale"),e){case"monsters":displayMonsters();break;case"battles":displayBattles(5);break;case"ranks":displayRanks(5);break;case"play":displayPlayground();break;default:alert("something went wrong!"),onBackPressed()}}function onBackPressed(){introComponent.classList.remove("hidden"),mainComponent.classList.add("hidden"),matchList.innerHTML="",monsterList.innerHTML="",actionSectionUI.classList.remove("hidden"),playGameButton.classList.add("hidden"),isWsConnReady&&!connId&&wsConn.close()}setTimeout((()=>{loaderComponent.classList.add("hidden"),introComponent.classList.remove("hidden")}),1e3);let monsters=[],monstersTemp=[];function displayMonsters(){sectionMonster.classList.remove("hidden"),sectionBattle.classList.add("hidden"),sectionRank.classList.add("hidden"),sectionPlay.classList.add("hidden"),this.fetchData("monsters").then((e=>{monsters=e.data.data,monstersTemp=monsters,monstersTemp.length>=50&&document.getElementById("sync-monster-button").classList.add("hidden"),showListOfMonsters()})).catch((e=>onBackPressed()))}function syncMoreData(){!0===confirm("Are you sure want to add more data?!")&&(loaderComponent.classList.remove("hidden"),mainComponent.classList.add("hidden"),this.fetchData("monsters/sync").then((e=>{loaderComponent.classList.add("hidden"),mainComponent.classList.remove("hidden"),monsters=[...monsters,...e.data.data],monstersTemp=monsters,showListOfMonsters(),alert("sync monster success")})).catch((e=>onBackPressed())))}function showListOfMonsters(){monsterList.innerHTML="",sectionTitle.innerText=` Monsters (${monsters.length})`,0===monsters.length?monsterList.innerHTML+="
Data Not Available
":monsters.forEach((e=>{const t=monstersItemComponent(e);monsterList.innerHTML+=t}))}function monstersItemComponent(e,t=!1){const n=e.types.map((e=>capitalize(e)));let s="";e.stats.forEach((e=>{"hp"===e.name&&(s=e.name.toUpperCase()+`(${e.base_stat})`)}));const a=e.skills.map(((e,t)=>"
"+(t+1)+`. ${capitalize(e.name)} (${e.pp})`)),o=t?"w-20 h-20":"w-32 h-32";return'\n
\n
\n

'+capitalize(e.name)+" - "+s+'

\n
\n
\n \n
\n
\n

Type: '+n+'

\n

Skills:\n '+a+"\n

\n
\n
\n "}function displayBattles(e=null){const t=document.getElementById("date-range"),n=document.getElementById("start-between"),s=document.getElementById("end-between");sectionBattle.classList.remove("hidden"),sectionMonster.classList.add("hidden"),sectionRank.classList.add("hidden"),sectionPlay.classList.add("hidden"),null===e?t.classList.remove("hidden"):(t.classList.add("hidden"),n.value=null,s.value=null),matchList.innerHTML="";let a=e?`battles?limit=${e}`:"battles";if(s?.value?.length>0&&n?.value?.length>0){a=`${a}?between=${1e6*dayjs(n.value).unix()??null},${1e6*dayjs(s.value).unix()??null}`}this.fetchData(a).then((e=>{const t=e.data.data;null===t?matchList.innerHTML+="
Data Not Available
":t.forEach(((e,t)=>{const n=matchItemComponent(e,t+1);matchList.innerHTML+=n}))})).catch((e=>onBackPressed())),applyBadgeMatchFilter(e)}function applyBadgeMatchFilter(e){const t=document.getElementById("last5rank"),n=document.getElementById("last10rank"),s=document.getElementById("all-match");switch(t.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),e){case 5:t.classList.add("bg-red-100","text-red-700"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;case 10:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("bg-red-100","text-red-700"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;default:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("bg-red-100","text-red-700")}}function matchItemComponent(e){let t="";return e.players.forEach((n=>t+=matchPlayerComponent(n,e.started_at,e.ended_at))),'\n
\n
\n

Battle

\n

#'+e.id+'

\n
\n
\n

'+formatDate(e.started_at)+'

\n

'+formatTime(e.started_at)+" - "+formatTime(e.ended_at)+'

\n
\n \n \n \n \n '+diffTime(e.ended_at,e.started_at)+'\n \n \n \n \n \n '+e.logs.length+'x\n \n
\n
\n
\n \n '+t+"\n
\n
\n
\n "}function matchPlayerComponent(e,t,n){const s=e.annulled_at>0?"X":`#${e.rank}`;return'\n \n \n

'+s+'

\n \n \n
\n '+e.name+'\n
\n

'+capitalize(e.name)+'

\n
\n '+e.point+' pts\n '+diffTime(0===e.eliminated_at?n:e.eliminated_at,t,!0)+'\n
\n
\n
\n \n \n \n '}function displayRanks(e=null){sectionMonster.classList.add("hidden"),sectionBattle.classList.add("hidden"),sectionRank.classList.remove("hidden"),sectionPlay.classList.add("hidden");const t=rankTable.getElementsByTagName("tbody")[0];t.innerHTML="";const n=e?`ranks?limit=${e}`:"ranks";this.fetchData(n).then((e=>{const n=e.data.data;null===n?monsterList.innerHTML+="
Data Not Available
":n.forEach(((e,n)=>{const s=rankItemComponent(e,n+1);t.innerHTML+=s}))})).catch((e=>onBackPressed())),applyBadgeRankFilterStyle(e)}function applyBadgeRankFilterStyle(e){const t=document.getElementById("top5rank"),n=document.getElementById("top10rank"),s=document.getElementById("all-rank");switch(t.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),e){case 5:t.classList.add("bg-red-100","text-red-700"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;case 10:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("bg-red-100","text-red-700"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;default:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("bg-red-100","text-red-700")}}function rankItemComponent(e,t){let n="";return e.types.forEach((e=>{n+=''+capitalize(e)+""})),'\n \n \n
\n

#'+(e.total_battles>0?t:"")+'

\n
\n \n \n
\n '+e.name+'\n
\n

'+capitalize(e.name)+'

\n
'+n+'
\n
\n
\n \n \n
\n Play[

'+e.total_battles+'x

]\n
\n \n \n
\n Win[

'+e.win_battles+'x

]\n
\n \n \n
\n L[

'+e.lose_battles+'x

]\n
\n \n \n '+e.points+' pts\n \n \n \n '}document.getElementById("search").addEventListener("input",(function(e){const t=e.target.value;monsters=""!==t?monstersTemp.filter((e=>e.name.toLowerCase().includes(t.toLowerCase()))):monstersTemp,showListOfMonsters()}));const instructionSectionUI=document.getElementById("battleroyale-instruction"),monsterSectionUI=document.getElementById("battleroyale-player"),playgroundSectionUI=document.getElementById("battleroyale-playground"),actionSectionUI=document.getElementById("battleroyale-actions"),logsUI=document.getElementById("battleroyale-logs"),historyUI=document.getElementById("battleroyale-history"),eliminatedUI=document.getElementById("battleroyale-eliminated"),playGameButton=document.getElementById("play-game"),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"),null===connId&&(connId=generateRandomId()),wsConn=new WebSocket(`${wsUrl}/${connId}`),wsConn.onopen=()=>{isWsConnReady=!0,monsterSectionUI.innerHTML="",logsUI.innerHTML="",historyUI.innerHTML="",eliminatedUI.innerHTML="",sendMessageToWsServer("histories")},wsConn.onmessage=e=>proceedWsData(e),wsConn.onclose=()=>{isWsConnReady=!1,monsterSectionUI.innerHTML="",logsUI.innerHTML="",monsterSectionUI.innerHTML="",historyUI.innerHTML="",eliminatedUI.innerHTML=""}}function sendMessageToWsServer(e,t=0){isWsConnReady||(alert("Websocket connection is closed"),onBackPressed()),wsConn.send(JSON.stringify({id:connId,action:e,data:t}))}function proceedWsData(e){if(!isJsonString(e.data))return;const t=JSON.parse(e.data);if("error"!==t.status)switch(t.data_type){case"monsters":proceedMonsterUI(t.data),playGameButton.classList.remove("hidden");break;case"battle_histories":proceedBattleHistoryUI(t.data);break;case"battle_logs":proceedLogsUI(t.data);break;case"eliminated_player":proceedEliminatedPlayer(t.data);break;case"eliminated_result":proceedEliminatedResult(t.data);break;case"battle_result":playGameButton.classList.add("hidden"),proceedMatchResultUI(t.data)}else alert(t.message)}let monstersRandomTemp=[];function proceedMonsterUI(e){instructionSectionUI.classList.add("hidden"),playgroundSectionUI.classList.remove("hidden"),monsterSectionUI.classList.remove("hidden"),monsterSectionUI.innerHTML="",logsUI.innerHTML="",eliminatedUI.innerHTML="",monstersRandomTemp=e,e.forEach((e=>{const t=monstersItemComponent(e,!0);monsterSectionUI.innerHTML+=t}))}function proceedBattleHistoryUI(e){e&&(historyUI.innerHTML="",e.forEach((e=>{historyUI.innerHTML+="\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
\n '+a+'\n
\n
#'+t.rank+'
\n
'+t.point+" pts
\n
\n "+n+"\n
\n "}))}function annulledPlayer(e){const t=monstersRandomTemp.find((t=>t.id===e));t&&(t.annulled=!0);const n=document.getElementById(`battle-status-${e}`);document.getElementById(`annulled-button-${e}`).disabled=!0,n.classList.remove("border-gray-200"),n.classList.add("border-red-100","bg-red-200"),sendMessageToWsServer("annulled",e)}async function fetchData(e){try{return await axios.get(`${restUrl}/${e}`)}catch(e){alert(e)}}function capitalize(e){return e.toLowerCase().replace(/\b./g,(function(e){return e.toUpperCase()}))}function formatDate(e){return dayjs(e/1e3).format("ddd, MM/DD-YYYY")}function formatTime(e){return dayjs(e/1e3).format("hh:mm:ss")}function diffTime(e,t,n=!1){if(0===e)return"-";return(n?"Stand: ":"")+dayjs(dayjs.unix(e/1e3)).diff(dayjs.unix(t/1e3),"microsecond")+"ms"}function generateRandomId(){return String.fromCharCode(65+Math.floor(26*Math.random()))+Date.now()}function isJsonString(e){try{return JSON.parse(e),!0}catch(e){return!1}}</script>

@aasumitro
Copy link
Author

<!doctype html><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>

loading please wait . . .
logo

Pocket Monster
Battleroyale

MonstersBattlesRanksPlay

Pokewar

API Powered by

pokeapi-logo
More (+10)
Last 5
Last 10
All
to
Submit
Top 5
Top 10
All
RANDOM PLAYERPLAY BATTLE
INSTRUCTION

Click [RANDOM PLAYER] Button
to get random player, after random player displayed.
Click [PLAY BATTLE] Button
to play the battleroyale round. Enjoy your game!

Data will reset after ,
annulled button will be hide after 10s
and data will be proceeded.
the result will be proceeded, show
and you can play again the game after 15s.
Players Eliminated
Battle Logs
Battle History
<script>const restUrl="https://pokewar.azurewebsites.net/api/v1",wsUrl="wss://pokewar.azurewebsites.net/api/v1/ws";let wsConn=null,isWsConnReady=!1,connId=null;const loaderComponent=document.getElementById("loader-component"),introComponent=document.getElementById("intro-component"),mainComponent=document.getElementById("main-component"),sectionTitle=document.getElementById("section-title"),sectionMonster=document.getElementById("monster-section"),sectionBattle=document.getElementById("battle-section"),sectionRank=document.getElementById("rank-section"),sectionPlay=document.getElementById("play-section"),monsterList=document.getElementById("monsters-list"),matchList=document.getElementById("match-list"),rankTable=document.getElementById("rank-list-table");function displaySection(e){switch(introComponent.classList.add("hidden"),mainComponent.classList.remove("hidden"),sectionTitle.innerText=e[0].toUpperCase()+e.slice(1),"play"===e&&sectionTitle.append(" Battleroyale"),e){case"monsters":displayMonsters();break;case"battles":displayBattles(5);break;case"ranks":displayRanks(5);break;case"play":displayPlayground();break;default:alert("something went wrong!"),onBackPressed()}}function onBackPressed(){introComponent.classList.remove("hidden"),mainComponent.classList.add("hidden"),matchList.innerHTML="",monsterList.innerHTML="",actionSectionUI.classList.remove("hidden"),playGameButton.classList.add("hidden"),isWsConnReady&&!connId&&wsConn.close()}setTimeout((()=>{loaderComponent.classList.add("hidden"),introComponent.classList.remove("hidden")}),1e3);let monsters=[],monstersTemp=[];function displayMonsters(){sectionMonster.classList.remove("hidden"),sectionBattle.classList.add("hidden"),sectionRank.classList.add("hidden"),sectionPlay.classList.add("hidden"),this.fetchData("monsters").then((e=>{monsters=e.data.data,monstersTemp=monsters,monstersTemp.length>=50&&document.getElementById("sync-monster-button").classList.add("hidden"),showListOfMonsters()})).catch((e=>onBackPressed()))}function syncMoreData(){!0===confirm("Are you sure want to add more data?!")&&(loaderComponent.classList.remove("hidden"),mainComponent.classList.add("hidden"),this.fetchData("monsters/sync").then((e=>{loaderComponent.classList.add("hidden"),mainComponent.classList.remove("hidden"),monsters=[...monsters,...e.data.data],monstersTemp=monsters,showListOfMonsters(),alert("sync monster success")})).catch((e=>onBackPressed())))}function showListOfMonsters(){monsterList.innerHTML="",sectionTitle.innerText= Monsters (${monsters.length}),0===monsters.length?monsterList.innerHTML+="
Data Not Available
":monsters.forEach((e=>{const t=monstersItemComponent(e);monsterList.innerHTML+=t}))}function monstersItemComponent(e,t=!1){const n=e.types.map((e=>capitalize(e)));let s="";e.stats.forEach((e=>{"hp"===e.name&&(s=e.name.toUpperCase()+(${e.base_stat}))}));const a=e.skills.map(((e,t)=>"
"+(t+1)+. ${capitalize(e.name)} (${e.pp}))),o=t?"w-20 h-20":"w-32 h-32";return'\n <div class="'+(t?"w-54":"w-72")+' border border-gray-200 rounded-lg shadow-md">\n
\n

'+capitalize(e.name)+" - "+s+'

\n
\n
\n <img\n src="'+e.avatar+'"\n alt="'+e.name+'"\n class="mt-4 '+o+' mx-auto"\n />\n
\n
\n

Type: '+n+'

\n

Skills:\n '+a+"\n

\n
\n \n "}function displayBattles(e=null){const t=document.getElementById("date-range"),n=document.getElementById("start-between"),s=document.getElementById("end-between");sectionBattle.classList.remove("hidden"),sectionMonster.classList.add("hidden"),sectionRank.classList.add("hidden"),sectionPlay.classList.add("hidden"),null===e?t.classList.remove("hidden"):(t.classList.add("hidden"),n.value=null,s.value=null),matchList.innerHTML="";let a=e?battles?limit=${e}:"battles";if(s?.value?.length>0&&n?.value?.length>0){a=${a}?between=${1e6*dayjs(n.value).unix()??null},${1e6*dayjs(s.value).unix()??null}}this.fetchData(a).then((e=>{const t=e.data.data;null===t?matchList.innerHTML+="
Data Not Available
":t.forEach(((e,t)=>{const n=matchItemComponent(e,t+1);matchList.innerHTML+=n}))})).catch((e=>onBackPressed())),applyBadgeMatchFilter(e)}function applyBadgeMatchFilter(e){const t=document.getElementById("last5rank"),n=document.getElementById("last10rank"),s=document.getElementById("all-match");switch(t.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),e){case 5:t.classList.add("bg-red-100","text-red-700"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;case 10:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("bg-red-100","text-red-700"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;default:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("bg-red-100","text-red-700")}}function matchItemComponent(e){let t="";return e.players.forEach((n=>t+=matchPlayerComponent(n,e.started_at,e.ended_at))),'\n
\n
\n

Battle

\n

#'+e.id+'

\n
\n
\n

'+formatDate(e.started_at)+'

\n

'+formatTime(e.started_at)+" - "+formatTime(e.ended_at)+'

\n
\n \n \n \n \n '+diffTime(e.ended_at,e.started_at)+'\n \n \n \n \n \n '+e.logs.length+'x\n \n
\n
\n
\n \n '+t+"\n
\n
\n
\n "}function matchPlayerComponent(e,t,n){const s=e.annulled_at>0?"X":#${e.rank};return'\n <tr tabindex="0" class="focus:outline-none h-16 border border-gray-100 rounded '+(e.annulled_at>0?"bg-red-100":"")+'">\n \n

'+s+'

\n \n \n
\n '+e.name+'\n
\n

'+capitalize(e.name)+'

\n
\n '+e.point+' pts\n '+diffTime(0===e.eliminated_at?n:e.eliminated_at,t,!0)+'\n
\n
\n
\n \n \n \n '}function displayRanks(e=null){sectionMonster.classList.add("hidden"),sectionBattle.classList.add("hidden"),sectionRank.classList.remove("hidden"),sectionPlay.classList.add("hidden");const t=rankTable.getElementsByTagName("tbody")[0];t.innerHTML="";const n=e?ranks?limit=${e}:"ranks";this.fetchData(n).then((e=>{const n=e.data.data;null===n?monsterList.innerHTML+="
Data Not Available
":n.forEach(((e,n)=>{const s=rankItemComponent(e,n+1);t.innerHTML+=s}))})).catch((e=>onBackPressed())),applyBadgeRankFilterStyle(e)}function applyBadgeRankFilterStyle(e){const t=document.getElementById("top5rank"),n=document.getElementById("top10rank"),s=document.getElementById("all-rank");switch(t.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.remove("bg-red-100","text-red-700","text-gray-600","hover:text-red-700","hover:bg-red-100"),e){case 5:t.classList.add("bg-red-100","text-red-700"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;case 10:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("bg-red-100","text-red-700"),s.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100");break;default:t.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),n.classList.add("text-gray-600","hover:text-red-700","hover:bg-red-100"),s.classList.add("bg-red-100","text-red-700")}}function rankItemComponent(e,t){let n="";return e.types.forEach((e=>{n+=''+capitalize(e)+""})),'\n \n \n
\n

#'+(e.total_battles>0?t:"")+'

\n
\n \n \n
\n '+e.name+'\n
\n

'+capitalize(e.name)+'

\n
'+n+'
\n
\n
\n \n \n
\n Play[

'+e.total_battles+'x

]\n
\n \n \n
\n Win[

'+e.win_battles+'x

]\n
\n \n \n
\n L[

'+e.lose_battles+'x

]\n
\n \n \n '+e.points+' pts\n \n \n \n '}document.getElementById("search").addEventListener("input",(function(e){const t=e.target.value;monsters=""!==t?monstersTemp.filter((e=>e.name.toLowerCase().includes(t.toLowerCase()))):monstersTemp,showListOfMonsters()}));const instructionSectionUI=document.getElementById("battleroyale-instruction"),monsterSectionUI=document.getElementById("battleroyale-player"),playgroundSectionUI=document.getElementById("battleroyale-playground"),actionSectionUI=document.getElementById("battleroyale-actions"),logsUI=document.getElementById("battleroyale-logs"),historyUI=document.getElementById("battleroyale-history"),eliminatedUI=document.getElementById("battleroyale-eliminated"),playGameButton=document.getElementById("play-game"),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"),null===connId&&(connId=generateRandomId()),wsConn=new WebSocket(${wsUrl}/${connId}),wsConn.onopen=()=>{isWsConnReady=!0,monsterSectionUI.innerHTML="",logsUI.innerHTML="",historyUI.innerHTML="",eliminatedUI.innerHTML="",sendMessageToWsServer("histories")},wsConn.onmessage=e=>proceedWsData(e),wsConn.onclose=()=>{isWsConnReady=!1,monsterSectionUI.innerHTML="",logsUI.innerHTML="",monsterSectionUI.innerHTML="",historyUI.innerHTML="",eliminatedUI.innerHTML=""}}function sendMessageToWsServer(e,t=0){isWsConnReady||(alert("Websocket connection is closed"),onBackPressed()),((e==="start")?actionSectionUI.classList.add('hidden'): ""),wsConn.send(JSON.stringify({id:connId,action:e,data:t}))}function proceedWsData(e){if(!isJsonString(e.data))return;const t=JSON.parse(e.data);if("error"!==t.status)switch(t.data_type){case"monsters":proceedMonsterUI(t.data),playGameButton.classList.remove("hidden");break;case"battle_histories":proceedBattleHistoryUI(t.data);break;case"battle_logs":proceedLogsUI(t.data);break;case"eliminated_player":proceedEliminatedPlayer(t.data);break;case"eliminated_result":proceedEliminatedResult(t.data);break;case"battle_result":playGameButton.classList.add("hidden"),proceedMatchResultUI(t.data)}else alert(t.message)}let monstersRandomTemp=[];function proceedMonsterUI(e){instructionSectionUI.classList.add("hidden"),playgroundSectionUI.classList.remove("hidden"),monsterSectionUI.classList.remove("hidden"),monsterSectionUI.innerHTML="",logsUI.innerHTML="",eliminatedUI.innerHTML="",monstersRandomTemp=e,e.forEach((e=>{const t=monstersItemComponent(e,!0);monsterSectionUI.innerHTML+=t}))}function proceedBattleHistoryUI(e){e&&(historyUI.innerHTML="",e.forEach((e=>{historyUI.innerHTML+="\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 <div class="flex flex-col"">\n '+a+'\n
\n
#'+t.rank+'
\n
'+t.point+" pts
\n
\n "+n+"\n \n "}))}function annulledPlayer(e){const t=monstersRandomTemp.find((t=>t.id===e));t&&(t.annulled=!0);const n=document.getElementById(battle-status-${e});document.getElementById(annulled-button-${e}).disabled=!0,n.classList.remove("border-gray-200"),n.classList.add("border-red-100","bg-red-200"),sendMessageToWsServer("annulled",e)}async function fetchData(e){try{return await axios.get(${restUrl}/${e})}catch(e){alert(e)}}function capitalize(e){return e.toLowerCase().replace(/\b./g,(function(e){return e.toUpperCase()}))}function formatDate(e){return dayjs(e/1e3).format("ddd, MM/DD-YYYY")}function formatTime(e){return dayjs(e/1e3).format("hh:mm:ss")}function diffTime(e,t,n=!1){if(0===e)return"-";return(n?"Stand: ":"")+dayjs(dayjs.unix(e/1e3)).diff(dayjs.unix(t/1e3),"microsecond")+"ms"}function generateRandomId(){return String.fromCharCode(65+Math.floor(26*Math.random()))+Date.now()}function isJsonString(e){try{return JSON.parse(e),!0}catch(e){return!1}}</script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment