Pochinkizer - How I made this project?

April 17, 2024

Card background

This text is a repost. Previously published on Personal site (July 6, 2019)

PUBG is a massive hit. Thousands of people playing it every day. Not gonna lie - myself included. For those, who is not familiar with the game - it is battle royale FPS. Like in most BR games PUBG got big maps, where players can decide where they will land to start the game.

It’s fun to explore map at first, but after hundreds of games it becomes boring to decide where to land again and again.

To eliminate this problem forever I created randomizer app, that will help players decide where they should land.

In this article I will tell you how I created this application and my logic behind certain decisions.

Randomizer

First of all, we need to create randomizer, so this app could give us random place on any map.

For example, let’s take Erangel - first PUBG map. It got 15 major locations where players could land. Let’s put this locations into an array:

let erangel = ['Georgopol', 'Novorepnoye', 'Pochinki', 'Yasnaya Polyana', 'Gatka', 'Kameshki', 'Lipovka', 'Mylta', 'Primorsk', 'Rozhok', 'Severny', 'Stalber', 'Zharki', 'Stalber', 'Sosnovka Military Base']

With this array we could write a function, that will give us random value from this array.

Step 1. Get random number with Math.random() and multiply it on the length of the array. Then - round this number. As a result - it will give us random number from 0-14.

Step 2. Use this number as an index for getting certain item form the array.

const randomPlace = (map) => {
 let randomNumber = Math.floor(Math.random() * map.length);
 return(map[randomNumber]);
};

This function is also usable for the other maps because length of the map array isn’t hard coded. Even if map got 1000 locations - it doesn’t matter because we getting its length with map.length method.

addEventlistener for each map

I wanted this app to be as easy as possible in terms of use, so user could get landing location in just one click. PUBG has four maps, so index page should have four buttons - one for each map. It means that every map now got two properties - not only array of locations, but also a button. It gives us opportunity to create class.

class Map {
 constructor(locations, btn) {
   this.locations = locations;
   this.btn = btn;
 }
};

This class allow us to create as many maps as we want. As an example, let’s continue with Erangel and transform it into the class.

let erangel = new Map (
 locations = ['Georgopol', 'Novorepnoye', 'Pochinki', 'Yasnaya Polyana', 'Gatka', 'Kameshki', 'Lipovka', 'Mylta', 'Primorsk', 'Rozhok', 'Severny', 'Stalber', 'Zharki', 'Stalber', 'Sosnovka Military Base'],
 btn = document.querySelector('#map__e')
);

So we could use function on all the maps we need to create array with all maps:

let mapsArray = [erangel];

Then using .forEach method we could create a function for each map in array:

mapsArray.forEach( (map) => {
 listenAllMaps(map);
});

In our case - we need this function to listen to clicks on the buttons. When user clicks - pickmap function runs.

function listenAllMaps(map) { map.btn.addEventListener('click', pickMap); };

pickMap function

On this point user picked certain map, so we need to show it in UI. PickMap function is responsible for this.

Important - we need to work on pickMap function inside of listenAllMaps function because they will share map variable so we need them to be in one scope.

function listenAllMaps(map) {
 map.btn.addEventListener('click', pickMap);
 function pickMap () {
   document.getElementById('mapImg') = 'images/' + map.folder +'/' + map.name + '.jpg';
   pickDestination(map);
 };
};

Once again, so we could use this functions on all the maps we shouldn’t hard code map’s image destination. Instead of it we should add two more properties for Map class.

let erangel = new Map (
 locations = ['Georgopol', 'Novorepnoye', 'Pochinki', 'Yasnaya Polyana', 'Gatka', 'Kameshki', 'Lipovka', 'Mylta', 'Primorsk', 'Rozhok', 'Severny', 'Stalber', 'Zharki', 'Stalber', 'Sosnovka Military Base'],
 name = 'erangel',
 folder = 'erangelDS',
 btn = document.querySelector('#map__e')
);

Now it will look for erangel.jpg in erangelDS folder in images folder and put this image into HTML.

pickDestination function

After map is picked and showed in the DOM we could move on to final step of getting random location on map. For this we need to create pickDestination function. It doesn’t have to be inside listenAllMaps function because we could pass map as argument.

function pickDestination (map) {
 let place = randomPlace(map.locations);
 document.getElementById('result').innerHTML = place;
 document.getElementById('destination').src = 'images/' + map.folder +'/' + place + '.png';
};

Here we use randomPlace function we wrote earlier to get random destination. We put this destination into the variable and use it:

  • In the DOM as text to show user as destination.
  • In the path to get relevant location marked on map.

Map image and destination image

You might noticed that we are getting two pictures as a result.

Why so? Wouldn’t it be easy to just download .jpg at pickDestination step?

No, because app should have an option so user could get another location on selected map. And if user chooses this option, then browser need to download jpg with new location.

But map stays the same - why should we download this part of image once again if nothing changes except small dot?

So I find it more convenient to divide image in two parts.

First - jpg map(~900kb). Downloads with pickMap function and stays on the screen.

Second - destination only(~20kb). Transparent image with red dot and text. Downloads with pickDestination function.

If user wants another location it downloads new png, while jpg map stays the same.

From HTML point of view it looks like two images in the same div, stacked on top of each other. One with relative position, second - with absolute.

#placeForMap {
 max-width: 600px;
 background-position: top center;
 position: relative;
 margin: auto;
 top: 0;
 left: 0;
}

#mapImg {
 width: 100%;
 position: relative;
 top: 0;
 left: 0;
}

#destination {
 width: 100%;
 position: absolute;
 top: 0px;
 left: 0px;
}

Final steps

After core mechanics is done, app needs final adjustments.

We should divide app into stages - one stage where user selects map, second - where user gets result. All html elements need to be placed in certain divs. These divs should be placed in functions - showing and hiding them.

const chooseMapDOM = document.getElementById('chooseMap');
const ideasForDropDOM = document.getElementById('ideasForDrop');
const choosingPlace = () => {
 chooseMapDOM.style.display = 'block';
 ideasForDropDOM.style.display = 'none';
};
const ideasForDrop = () => {
 chooseMapDOM.style.display = 'none';
 ideasForDropDOM.style.display = 'block';
};

choosingPlace - should be run in the beginning and in case of changing maps. ideasForDrop - when user clicked in one of the map.

All of the DOM elements should be assigned to certain variables. It’s not necessary, but more convenient for maintenance. For example:

const erangelBTNDOM = document.querySelector('#map__e');

Create variables for other maps using Map class and add them into mapsArray.

After all this done, code should be updated with new stage functions, new DOM variables and organized.

And this is it. This randomizer is applicable even for other BR games - all you have to do us create relevant Map class.

If you are interested in looking closely at the code you could do it in Git repository of this project.

Thanks for reading.