3
\$\begingroup\$

I'm a self taught hobbyist programmer, for what it's worth. My latest project is a JavaScript based choose your own adventure game using Twine (Tweego) as the framework. If you're not familiar with Twine, I don't believe it will hinder your understanding. I'll outline the issue in relation to they framework.

As required, my code functions. However, I can't help thinking there must be a better, cleaner way to produce these results. Here is the task:

Twine's recommended variable storage objects are as follows: the setup object for variables that will load every time the game starts (refreshes), and the state object for variables that will be saved to file when the user saves their game. Since my game is planned to be early access, I need to be clever about how I do updates. If I add a new feature to the player, I don't want to force them to start the game over. Rather, handle it in a way that "auto" updates. The obvious way, to me, was leveraging Twine's 'setup' object for default values. For instance, if the player's default hacking skill is 15 and during development I reason that value too low, I can increase it with minimal fuss. I accomplished this by doing the following:

setup.PLAYER_BASE = {
    // primary stats
    charisma: 0,
    manipulation: 0,
    wits: 0,

    // skills
    hacking: 15,
    lockpicking: 0,
    negotiation: 0,
    sneaking: 0,

    // perks
    appearance: 0,
    goodness: 0,
    leadership: 0,
    wickedness: 0
}

setup.PLAYER_INIT = {
    // primary stats
    charisma: 0,
    manipulation: 0,
    wits: 0,

    // skills
    hacking: 0,
    lockpicking: 0,
    negotiation: 0,
    sneaking: 0,

    // perks
    appearance: 0,
    goodness: 0,
    leadership: 0,
    wickedness: 0,

    // Inventory
    credits: 0,
    items: [],
    laptop: {}
}

In the above code I created a constant variables for the PLAYER_BASE stats, so the program knows what the player minimum values are. Then I created a constant variable for 'PLAYER_INIT', which is only used when the game starts. When a new game begins, the following code is run:

:: InitStory
<<set $player = setup.PLAYER_INIT>>

Before I explain the Twine syntax, I'll explain why I store $player with 0 values for all stats. As the game progresses, the player will gain points to apply to these stats, so this object tracks what the player has changed, not what the values originated as, which is what setup.PLAYER_BASE defines. In Twine, the $ signals that a value will be stored to the state object (things that will be stored when the game saves). The :: tells Twine that what follows is a passage. I mention this, because there is another character I'll use in the next bit of code that represents a variable local to the passage, which is _. Next I'll demonstrate how this all ties together.

:: chapterOne
<<set _playerObj = new setup.PlayerObj($player, setup.PLAYER_BASE)>>

Now, here is that PlayerObj code to make sense of it all.

window.setup = window.setup || {};

setup.PlayerObj = function(playerData, playerBase) {
    this.firstName = playerData.firstName;
    this.lastName = playerData.lastName;

    // primary stats
    this.charisma = playerData.charisma + playerBase.charisma;
    this.manipulation = playerData.manipulation + playerBase.manipulation;
    this.wits = playerData.wits + playerBase.wits;

    // skills
    this.hacking = playerData.hacking + playerBase.hacking;
    this.lockpicking = playerData.lockpicking + playerBase.lockpicking;
    this.negotiation = playerData.negotiation + playerBase.negotiation;
    this.sneaking = playerData.sneaking + playerBase.sneaking;

    // perks
    this.appearance = playerData.appearance + playerBase.appearance;
    this.goodness = playerData.goodness + playerBase.appearance;
    this.leadership = playerData.leadership + playerBase.leadership;
    this.wickedness = playerData.wickedness + playerBase.wickedness;

    // items
    this.items = playerData.items;

    // equipment
    this.laptop = playerData.laptop;

    // methods
    this.fullName = function () {
        return this.firstName + " " + this.lastName;
    }
}

Okay, so now every time a game is loaded from a save file, first everything in setup is defined, then the passage the player left off on is loaded. So, if I decided the player's hacking skill should be 30, it will now be 15 points higher than it was when the player saved. This to me feels incredibly sloppy and not DRY whatsoever. First, the setup.PLAYER_INIT feels redundant, because everything except the hacking skill value has no relevance. I'm sure this is a common task among programmers, which is why I'm seeking wisdom to expand my knowledge. Thank you in advance for the reviews.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

This seems like a pretty good arrangement, albeit a little complicated to think through. The only suggestion I have is to maybe use a different naming scheme that might make the code look less weird for lack of a better term.

It's a good idea to separate out per-user values from a base value. You could even extend the scheme to include a scaling value that multiplies the per-user value. Or a selection of different ramps or curves for transforming the stored values into the real value used internally (like a transfer function as used in computer graphics). Given this, my suggestion (meager as it is) is to use 'minimum' rather than 'base'.

So a line like this:

    this.charisma = playerData.charisma + playerBase.charisma;

would instead look like this:

    this.charisma = playerData.charisma + playerBase.charismaMinimum;

It's a slight change. But I think it makes the line of code better express what's happening.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.