Jump to content

Suggestions on storing data in localStorage, but also prevent cheating?


hlohrenz
 Share

Recommended Posts

So I have a huge question that I just can't seem to find the answer to. A player has 5 characters they can move across the board. Each character has their own movement allowance (some can only move 1 square per turn while others can move 2 or 4). The player has a TOTAL of 5 moves per turn. So they can move their characters 5 squares however they please.

My question is, after every turn, I want to make a database call to track their position so in case if they refresh the browser or come back to the game later, their characters are in the same position.

If I store this information in localStorage, can't they just update their character's position manually and potentially move a character that can only move 1 square up to 10 if they please? I don't know how to secure this without making a database call EVERY TIME they click a square which would be way too many requests and could slow the game down a LOT, right?

And when the character moves, I'll be doing a check that looks similar to:

if (path.length > character.movement_allowance) {
  // Then don't allow them to move
}

Couldn't they just update it to 10 via dev tools if they wanted to? My players object will look similar to this:

players: {
  0: {
    name: 'Red Fox',
    movement_allowance: 2,
    pos_x: 2,
    pos_y: 3
  }
}

Couldn't they just update their x and y position? And even their movement_allowance?

I'm just out of ideas here unless I check the server side every time they move to make sure it's correct and validate it..

Link to comment
Share on other sites

Yes, you can't secure local storage. You could try some obscure encryption method that mutates the data when you stuff it in to/out of local storage so that manually changing local storage is more likely to result in invalid data (which you can check for) than anything else.

Why would sending to a DB be prohibitive performance wise? These are fire-and-forget messages, you might want to time them and only store the latest if you are going to run into a situation where you send stuff faster than you store it. It could potentially get expensive if you have loads and loads of users, but I doubt even that.

If you are stuffing in to a remote DB then local storage would just be used to speed up load times, you could load things from LS whilst making a DB call and ensure data is synced when the DB call returns, if it usually is then this is perhaps a nice mechanic to speed things up, probably not worth the effort though.

Link to comment
Share on other sites

@mattstyles so you are saying it won't impact performance and speed if I make a database request EVERY time the user moves one of their character's? I want to make sure I'm understanding you correctly. If not, then this would be easy for me to validate and update the database. But I feel making a database request every time the user clicks on a tile would impact performance negatively. I was just hoping there was a better way of doing it to only update the database when the user ends their turn instead of every time they click on a tile to move one of their character's.

Link to comment
Share on other sites

tl;dr: You have turns. Just validate at the end of the game. Read on 'redux'

Let's start with your initial state.

players: {
  0: {
    name: 'Red Fox',
    movement_allowance: 2,
    pos_x: 2,
    pos_y: 3
  }
}

and an action defined as

//sample action
{type: 'MOVE_UP', player: 0}

 

What you need is an initialState, a series of actions, a way to use the actions to get to a new state, and an end state.

Instead of doing everything to prevent cheating at once, I think you should start with this implementation:

//to be called by your game
function performAction(action){
  //INTERESTING STUFF GOES HERE
 _performInterestingStuff(game.myState, action);

  //BORING PART
 _performActualAction(game.myState, action);
}

//actually perform the action
_performActualAction(state, action){
 switch(action.type){
   case 'GO_UP':
    var newState = {};
   return goUp(state);
 }
  return state;
}

Assume goUp doesn't modify state (critical, use something like lodash deepClone).

Now comes the actual important stuff, and you can do whatever you want from now on:

1) Just validate at the end of the game. Most people won't care if someone cheats in the middle of the game if they can quit, and later be notified that the last game was invalid. Just do that in

_interestingStuff. check if win conditions happened => send to the server your initialState, your actions, and your final state.

your server can simply do

finalState = actions.reduce((previousState, nextAction) => _performActualAction(previousState, nextAction), initialState);

checkValidity(initialState, finalState);

or you can check after every action is sent, after every 2,3,4 actions, after some random time, you can first check in the client and checkValidity returns a 'confidenceLevel' and if the confidenceLevel is too low, you validate in the server, etc.

 

The radical, most important point is that YOU DO NOT modify your initialState, you log every action and check if you can actually get to a newState. You do this whenever you feel like, but at the end of the day, people are still going to cheat.

Don't prevent cheating. It'll consume your life. Just identify it (hopefully) and then substract leaderboard points or something. 

ps: you just implemented redo, and replay functionality if you follow my advice, and that is far more interesting than trying to prevent cheating. Multimillion companies cannot do it, I doubt you or I can do it.

Link to comment
Share on other sites

if it's turn based you won't actually notice it anyways.

user -> server -> db -> server -> user (up to 200ms), suitable for turn based

user -> server -> user (up to 50ms), suitable for realtime fps shooting

 

also there's a lot of other factors btw:

- location of your servers (us/eu/au/asia)

- size of your servers (cores & ram)

- throughput of your servers (mb/s or gb/s, both in & out)

- how sexy your code is

 

btw, bandwidth-wise, you can just json.stringify the whole object, then locally hash it, then send the hash to the server, 

then if the server sees a hash mismatch, the server corrects it

otherwise if it matches then everything's ok ;)

https://www.npmjs.com/package/xxhashjs

 

another bandwidth-wise move too is to load client side assets on a separate server, and use a separate server for the game itself.

ie: static site on cdn like cloudflare & the server ip's are just embedded in the scripts, us/eu/au/asia

Link to comment
Share on other sites

11 hours ago, hlohrenz said:

@mattstyles so you are saying it won't impact performance and speed if I make a database request EVERY time the user moves one of their character's? I want to make sure I'm understanding you correctly. If not, then this would be easy for me to validate and update the database. But I feel making a database request every time the user clicks on a tile would impact performance negatively. I was just hoping there was a better way of doing it to only update the database when the user ends their turn instead of every time they click on a tile to move one of their character's.

Won't impact perf much at all (it costs almost nothing in terms of execution speed to fire off an ajax operation). It will affect your server though in terms of both bandwidth and CPU, but your user won't notice this because these are all writes and not reads, if you were reading stuff then you'd have to wait for the round-trip, but you're not, you're just writing data.

If your master state is going to be held in a remote DB and replicated in the client then you need to tell the DB about everything.

However, if your game is turn-based and users' turn consists of issuing orders then you only need to send those orders (or even, send the result of those orders) to the server when the user has finished making those orders (completed their turn). Somewhere (client or server, up to you based on what you need) you work out the result of orders i.e. a move 10 squares left order would result in entity X moving from <20, 5> to <10, 5>, for example, so, you might choose to implement this logic (in this case, current pos - desired movement) on the client and then just fire the new position to the server so that you remain in sync, you do this when the user hits the 'Im done for this turn' button and then run the simulation to do your animation (or whatever) to actually move the unit on screen.

There is a separation here between the data driving your game and the visual representation, your game might allow units to move during the turn phase (rather than issuing orders), in which case you might want to send a request for every user action (i.e. moving the unit), again, this is for syncing. You could choose to store all those actions and send them batched when the turn ends, the downside being that if someone performed some actions and then restarted the browser then their game would start again from the start of their turn as if the actions hadn't occurred due to the state that the server knows about, if this is a problem then fire update events to the server every time an action occurs. This is all just about saving stuff, i.e. DB writes. You could choose to send actions to the server and have the server perform the logic, this would be great for a multiplayer game, but you'd have to wait for a round-trip on the client (necessary for a multiplayer game as all clients would need updates), so if your game is single player it would make sense just to do the calcs on the client and send state changes to the DB to write (you could send the entire state each turn, which would be easier, but likely prohibitive bandwidth-wise, and depend on being able to serialise your entire game state, which you should be able to do but often becomes tricky depending on how you've structured your data).

Both of the answers above go into more implementation detail but follow pretty much the same: differentiate between state and user actions and fire off state changes to your DB to be saved.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...