Adding and Retrieving Players in a simple Pre-Flop Poker Hud with Local Storage I am building a browser based Pre-Flop HUD that for now just tracks VPIP, PFR and number of hands played for an opponent (https://pokercopilot.com/poker-statistics/vpip-pfr). I am currently using local storage to store my data. In the future I will use a better persistence strategy, add more features, and find a way to de-couple the user interface without just putting it in the functions I have now. I used an object literal approach without classes right now to try out some basic decomposition with simple functions for practice. Right now the only functionality I have is adding a player to Local Storage and getting all players from Local Storage.

Any suggestions for improvement are welcome.
But I do have a couple questions:


Is there a benefit to using Classes?  Right now I don't see the benefit of having a handle, like, player1 = new Player(arg,arg,etc...), player2 = new Player(arg,arg, etc...)...
Is there a better way to store to Local Storage? I don't think storing each player with their own key and having a ton of entries is a good idea.
In my function addPlayer() should I be calling savePlayersToStorage in my localStorageUtils.js?


Main.js file = entry point for my program

    /**********************************
 * This is the program driver, that
 * will call other functions
 * *******************************/

import { addPlayer } from "./player.js";
import {
  savePlayersToStorage,
  getPlayersFromStorage,
} from "./LocalStorageUtilsObjectLiteral.js";

function main() {
  let newPlayer = "Doug Polk";
  const localStorageKey = "stored_players";
  let players = getPlayersFromStorage(localStorageKey); //array of player objects in localStorage
  players.push(addPlayer(newPlayer));
  savePlayersToStorage(localStorageKey, JSON.stringify(players));
}


main();


Player.js addPlayer creates and returns an object literal

function addPlayer(name) {
  let player = {
    uuid: Date.now(),
    name: name,
    vpipActionCount: 0, //The number of times a player voluntarily put money into the pot Pre-Flop, whether calling or raising.
    pfrActionCount: 0, //The number of times a player raised pre-flop, this includes 3-bets.
    vpipPercentage: 0, //The percentage of hands a player has played, can be between 0 and 100%
    pfrPercentage: 0, //The percentage of hands a player has raised pre-flop, can be between 0 and 100%, but can never be higher than vpipPercentage
    totalHandsTracked: 0, //The number of hands tracked for a player

    callAction: function () {
      this.totalHands++;
      this.vpipActionCount++;
    },
    raiseAction: function () {
      this.totalHands++;
      this.pfrActionCount++;
    },
    foldAction: function () {
      this.totalHands++;
    },
    calculateVpipPercentage: function () {
      this.vpip = (this.vpipActionCount * 100) / this.totalHands;
    },
    calculatePfrPercentage: function () {
      this.pfrPercentage = (this.pfrActionCount * 100) / this.totalHands;
    },
    getVpipPercentage: function () {
      return this.vpipPercentage;
    },
    getPfrPercentage: function () {
      return this.pfrPercentage;
    },
  };
  return player;
}

export { addPlayer };


LocalStorageUtils.js Functions to get and save players to Local Storage

function savePlayersToStorage(localStorageKey, playersArray) {
  if (localStorage.getItem(localStorageKey === null)) {
    localStorage.setItem(localStorageKey);
  }
  localStorage.setItem(localStorageKey, playersArray);
}

function getPlayersFromStorage(localStorageKey) {
  if (localStorage.getItem(localStorageKey === null)) {
    localStorage.setItem(localStorageKey);
  }
  let players = JSON.parse(localStorage.getItem(localStorageKey));

  return players;
}

export { savePlayersToStorage, getPlayersFromStorage };
To Your Questions


Is there a benefit to using Classes? Right now I don't see the benefit of having a handle, like, player1 = new Player(arg,arg,etc...), player2 = new Player(arg,arg, etc...)...


Maybe there are benefits, but I can't think of any serious ones now.
The current version is similar to the factory pattern, where the creation of an object is abstracted by a factory - in this case addPlayer:
const player = addPlayer('Harry Potter');




Is there a better way to store to Local Storage? I don't think storing each player with their own key and having a ton of entries is a good idea.


I think storing all players under one key is sufficient and the easiest to manage. But you can get the best of both worlds:
Currently you are saving an array of players. Instead you could save a map where the key is a unique attribute of the player (maybe the name or an id) and the player itself. The benefit would be that you do not need to loop through an array to find a player:
function getPlayersFromStorage(key) {
  /* ... read from storage ... */;
  /* return has the form:
     {
        playername: player
     }
  */
}

const players = getPlayersFromStorage(localStorageKey);
const harryPotter = players[&quot;Harry Potter&quot;];

This is only necessary if you need to find players directly.



In my function addPlayer() should I be calling savePlayersToStorage in my localStorageUtils.js?


It depends. Currently the method name addPlayer does not represent what the method does. When I first read the name I thought: &quot;Ah ok, this methods put a player in some collection or maybe the local storage&quot;. But then I read the following line and get confused:

players.push(addPlayer(newPlayer));


A better name for addPlayer would be createPlayer and I would not call savePlayersToStorage in it, to have smaller and side effect free functions.
Change Persistenz

I am currently using local storage to store my data. In the future I will use a better persistence strategy

Currently, only the main.js depends on localStorage, and knows the implementation details, which would make it easier to change it in the future. However, to make it easier to change, especially as the code grows and more files depend on the persistence implementation, it should be abstracted as much as possible:
class LocalStorage {
    getAll() { /* ... */ }
    getByName() { /* ... */ }
    save()  { /* ... */ }
}

class OtherStorage {
    getAll() { /* ... */ }
    getByName() { /* ... */ }
    save()  { /* ... */ }
}

When your different storage strategies share the same interface they are easy interchange able:
// Main.js

const storage = new LocalStorage('my-storage-key');
# const storage = new OtherStorage(/* dependencies */);

function main() {
    const players = storage.getAllPlayers();
    const harryPotter = storage.getPlayerByName('Harry Potter');

    const newPlayer = createPlayer('Ron Weasley');
    const storage.save(newPlayer);
}

Read from Store

function getPlayersFromStorage(localStorageKey) {
 if (localStorage.getItem(localStorageKey === null)) {
   localStorage.setItem(localStorageKey);
 }
 let players = JSON.parse(localStorage.getItem(localStorageKey));

 return players;
}


When reading from the store we set an item if the key does not exists with localStorage.setItem(localStorageKey). To set an item is not the responsibility for reading data. Instead we could add some logic which is related for reading. For example if there is stored data under the given key:
function getPlayersFromStorage(localStorageKey) {
    const fromStore = localStorage.getItem(localStorageKey);
   
    if (fromSore) {
        return [];
    }
    
    return JSON.parse(fromStore);
}