Javascript - Structure for a look-up table


I am not quite sure if the wording in the title accurately describes what I am looking to do, allow me to explain:

Suppose in a game, you get different type of points from leveling up. But the amount of points of each type you get each level can be arbitrary.

For example, I get 3 offensive point for every 2 level. But I get 2 defensive point at level 2, 4, 5, and 6 say. And perhaps 1 supportive point at every level except the first.

<hr />

Now here's what I've done:

//Suppose I have my hero var Sylin = new Hero(); Sylin.Level = 5; //My goal is then to set // Sylin.OffensivePoint to 6 // Sylin.DefensivePoint to 6 // Sylin.SupportivePoint to 4 Sylin.prototype.OffensivePoint = function() { return 3*Math.floor(this.Level/2); }; Sylin.prototype.DefensivePoint = function() { var defPoint = 0; for(var i=2; i <= this.Level; i++) { //maximum level being 6 if(i==2 || i >= 4) { defPoint += 2; } } return defPoint; }; Sylin.prototype.SupportivePoint = function() { return this.Level - 1; };

It's all fine and dandy, but if the maximum level is extended, the points lists will be updated and then it gets really clumsy, especially if I have things like: 2 points every 3 level, but 3 point on the 9th and 13th level or something apparently lacking in pattern so I can't always do it like what I have for OffensivePoint().

<hr />

What I have in mind for this type of problems in general is a structure like so:

<strong>Level</strong> <strong>TotalPoint</strong><br /> . . 1 . . . . . a<br /> . . 2 . . . . . b<br /> . . 3 . . . . . c<br /> . . 4 . . . . . d<br /> . . 5 . . . . . e<br /> and so on until the maximum level

In the code, I could then perhaps do:

Sylin.prototype.talentPoint = function() { return readTalentPointTable(this.Level); //? };

But then this can still get quite convoluted if there's 20 level with 5 different types of points you can get, say :/

.<br /> .

<hr />


<hr />

Ok, so I could do something like:

var OffensivePointTable = [0,0,2,2,4,6,8,8,8,10,12]; function getOffensivePoint(level) { return OffensivePointTable[level]; }

Would it be easier if I store the data by the level in which a point is increased, or by the running total as above?

.<br /> .

<hr />

<strong>EDIT 2</strong>

<hr />

Ok, can I perhaps reverse the order of the structure to look at the type first, then level?

var theTable = { o: [0,1,0,1,1,0,0,0], d: [0,0,2,0,2,0,2,0], s: [0,1,2,3,4,5,6,7]} //then your CalculateStats: Sylin.prototype.CalculateStats = function() { this.offensivePoint = 0; for(var i=1; i<= this.Level; i++) { this.offensivePoint += theTable[o][i]; } }


You could use an object to store the amount of points to increment at each table (I didn't use your exact numbers, but you get the idea):

var LevelPoints = { '1': { o: 1, d: 2, s: 1 } '2': { o: 3, d: 1, s: 1 } '3': { o: 1, d: 1, s: 0 } '4': { o: 2, d: 3, s: 1 } //etc. }

For example, to access the offensive point increase at level 2, use LevelPoints['2'].o.

This requires a lot of typing I suppose, but sometimes just having all the data there makes things easier. Making your code readable to <em>you</em> and easy to change is always nice. It's also useful as a quick reference—if you're wondering how many offensive points will be gained at level 6, you can know immediately. No need to decipher any procedural code. Of course, this is personal preference. Procedural approaches are faster and use less memory, so it's up to you whether that's worth it. In this case the difference will be negligible, so I recommend the data-driven approach.

Also, note that I used var to set this object. Because it can be used by all instances of the Sylin constructor, setting it as an instance variable (using this) is wasteful, as it will create the object for every instance of Sylin. Using var lets them all share it, saving memory.

Alternately, you could store the running total at each level, but IMO this requires more effort for no good reason. It would take less of your time to write a function:

Sylin.prototype.CalculateStats = function() { this.OffensivePoint = 0; this.DefensivePoint = 0; this.SupportivePoint = 0; for (var i = 1; i <= this.Level; i++) { this.OffensivePoint += LevelPoints[i].o; this.DefensivePoint += LevelPoints[i].d; this.SupportivePoint += LevelPoints[i].s; } }

Then just run this function any time the user changes the level of the character. No need to pass the level, as the function will already have access to the this.Level variable.


Why not store the points in an array of objects -

var pointsTable = [{offensivePionts: 1, defensivePoints: 1}, {offensivePoints: 1, defensivePoints: 2}]; //extend for any level

And then just get return points by referencing the corrent property -

function getOffensivePoints(level) { return pointsTable[level]['offensivePoints']; }

You can easily extend the datastructure with methods like addLevel etc.


Sure you could always create an hardcoded array of all points, however you could also simply hardcode the exceptions and stick with an algorithm when you can.

Just an idea... that would require your hero to keep track of his points instead of recompiling them dynamically however, but that's probably a good thing.

//have a map for exceptions var pointExceptionsMap = { '9': { off: 3 //3 points of offense on level 9 } }; Sylin.prototype.levelUp = function () { var ex = pointExceptionsMap[++this.level]; //update offense points (should be in another function) this.offense += (ex && typeof ex.off === 'number')? ex.o /*exception points*/: this.level % 2? 0 : 2; //2 points every 2 levels };

Then to level up, you do hero.levelUp() and to get the points hero.offense. I haven't tested anything, but that's the idea. However, if you require to be able to set the level directly, you could either have a setLevel function that would call levelUp the right amount of times but, you would have to use a modifier to allow you leveling down as well.

You could also use my current idea and find an efficient way of implementing exceptionnal algorithms. For instance, you could still dynamically compile the number of offense points, and then add or remove points from that result based on exceptions. So if you need 2 points every 2 levels, but 3 for the level 9, that means adding 1 additionnal point to the compiled points. However, since when you reach higher levels, you wan to retain that exception, you would have to keep track of all added exception points as well.

EDIT: Also, nothing prevents you from using a function as a new exceptionnal algorithm instead of a simple number and if you plan to make the algorithms configurable, you can simply allow users to override the defaults. For instance, you could have a public updateOffense function that encapsulates the logic, so that it can be overriden. That would be something similar to the <a href="http://en.wikipedia.org/wiki/Strategy_pattern" rel="nofollow">Strategy design pattern</a>.

EDIT2: Here's a <a href="http://jsfiddle.net/HMr8t/4/" rel="nofollow">complete example</a> of what I was trying to explain, hope it helps!

var Hero = (function () { function Hero() { this.level = 0; this.stats = { off: 1, def: 0 }; } Hero.prototype = { statsExceptions: { '3': { off: 3 //get 3 points }, '6': { def: function () { //some algorithm, here we just return 4 def points return 4; } } }, levelUp: function () { ++this.level; updateStats.call(this, 1); }, levelDown: function () { updateStats.call(this, -1); --this.level; }, setLevel: function (level) { var levelFn = 'level' + (this.level < level? 'Up' : 'Down'); while (this.level !== level) { this[levelFn](); } }, statsFns: { off: function () { return (this.level % 2? 0 : 2); }, def: function () { return 1; } } }; function updateStats(modifier) { var stats = this.stats, fns = this.statsFns, exs = this.statsExceptions, level = this.level, k, ex, exType; for (k in stats) { if (stats.hasOwnProperty(k)) { ex = exs[level]; ex = ex? ex[k] : void(0); exType = typeof ex; stats[k] += (exType === 'undefined'? /*no exception*/ fns[k].call(this) : /*exception*/ exType === 'function' ? ex.call(this) : ex) * modifier; } } } return Hero; })(); var h = new Hero(); console.log(h.stats); h.setLevel(6); console.log(h.stats); h.setLevel(0); console.log(h.stats); h.levelUp(); console.log(h.stats); //create another type of Hero, with other rules function ChuckNorris() { Hero.call(this); //call parent constructor } ChuckNorris.prototype = Object.create(Hero.prototype); //Chuck gets 200 offense points per level y default ChuckNorris.prototype.statsFns.off = function () { return 200; }; //Not exceptions for him! ChuckNorris.prototype.statsExceptions = {}; console.info('Chuck is coming!'); var c = new ChuckNorris(); c.setLevel(10); console.log(c.stats);


  • Determining the length of a read stream in node js
  • Load image without autoscaling in Android
  • Is there a way to choose which files are displayed to the user via the standard OPENFILE dialogs?
  • python: forcing relative imports to search from script file
  • Write output of for loop to multiple files
  • Python cosine function precision [duplicate]
  • HttpClient: disabling chunked encoding
  • Plotting densities in R
  • Consuming a WCF service in a Java Client using wsHttpBinding
  • SonarQube: Cannot deactivate rule with missing quality profile
  • Trying to get the char code of ENTER key
  • Overlapping controls in Windows XP
  • Jquery popup on mouse over of calendar control
  • Firefox Extension - Monitor refresh and change of tab
  • MySQL Order by column = x, column asc?
  • Ensure fsync did its job
  • How do I get HTML corresponding to current DOM tree?
  • How to run “Deployd” on port 80 instead of port 5000 in webserver.
  • Magento Fatal error: Maximum execution error solution, on WAMP
  • How to set ini file attributes during an Inno install
  • Yii2: Config params vs. const/define
  • How to create a file in java without a extension
  • Python CGI os.system causing malformed header
  • NetLogo BehaviorSpace - Measure runs using reporters
  • Is my CUDA kernel really runs on device or is being mistekenly executed by host in emulation?
  • C# - Serializing and deserializing static member
  • recyclerView does not call the onBindViewHolder when scroll in the view
  • Java applet as stand-alone Windows application?
  • WinForms: two way TextBox problem
  • Build own AppleScript numerical error handling
  • Apache 2.4 - remove | delete | uninstall
  • Calling of Constructors in a Java
  • Traverse Array and Display in markup
  • Transpose CSV data with awk (pivot transformation)
  • Windows forms listbox.selecteditem displaying “System.Data.DataRowView” instead of actual value
  • Unit Testing MVC Web Application in Visual Studio and Problem with QTAgent
  • Benchmarking RAM performance - UWP and C#
  • Error creating VM instance in Google Compute Engine
  • Why can't I rebase on to an ancestor of source changesets if on a different branch?
  • Does armcc optimizes non-volatile variables with -O0?