badgerific
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.4 • Public • Published

    Badgerific

    logo

    Overview

    Badgerific is the badges/achievements library for Node.js that you've been looking for. Great for games and app gamification.

    To see samples of the types of rules that Badgerific supports, see sample rules.

    Install

    Install the library:

    npm install badgerific --save

    Add it to your JavaScript project:

    const Badges = require('badgerific')
    const rules = require('./badgeRules.json')
    
    const badges = new Badges(rules);

    Add it to your TypeScript project:

    import { Badges } from 'badgerific';
    import rules from './badgeRules.json';
    
    const badges = new Badges(rules);

    Concepts

    Locale

    In the constructor, optionally pass the user's time zone which is used for time-based rules. Default is 'UTC'.

    const tz = 'America/Phoenix';
    const badges = new Badges(rules, tz);

    Data

    The data about which badges were earned and the current state of properties and bookmarks are stored external to the library.

    It is important that badges.setData() is called before any other method on badges.

    // set badge data
    badges.setData(badgeData);
    
    // get badge data
    const data = badges.toJson();

    Periods (Timeline)

    Periods

    The periods are:

    • Global - From January 1, 1970 to present to all future dates.
    • Year - A year.
    • Month - A month.
    • Week - A week. Can span border between months.
    • Day - A day.
    • Hour - An hour.
    • Session - A user's session. Can span border between hours, days, weeks, months, or years. Starts with badges.startSession() and ends with badges.endSession().
    • Game - A game. Can span border between hours, days, weeks, months, or years. Starts with badges.startGame() and ends with badges.endGame().

    Each time the badges.evaluate() method is called, a check is made to determine if any of the time-based periods are starting a new period. For example, is today a new day compared to the last time that evaluate() was called. This updates any system properties. See Rules.

    Each time a new period starts, the ISO 8601 UTC value is stored for the period.

    Periods are used in the following ways:

    • In a rule to limit the period in which a badge can be earned. Example: this badge can only be earned once per Period.Game.
    • As a filter to badges.getEarnedBadges() to list all badges earned for the current period.

    Rules

    Rules are defined as JSON and passed to the constructor. Here is a sample rules file which defines a single rule that can only be earned once and can only be updated once per GLOBAL period.

    See a list of sample rules.

    [
        {
            "id": "b01",
            "description": "Playing your first game",
            "active": true,
            "max": 1,
            "updatePeriod": "GLOBAL",
            "condition": "gameCount == 1"
        }
    ]

    Rule Properties

    • id (required) - Unique identifier of the rule.
    • active (required) - Only rules set to true are evaluated. This is more of a convenience during developer testing.
    • condition (required) - Conditional statement(s) to be evaluated. If condition is true, the badge is earned.
    • updatePeriod (required) - Each badge can be earned only once per period. Value: Global, Year, Month, Week, Day, Hour, Session, Game.
    • max (optional) - If set, the max times that the badge can be earned. If missing, no maximum.
    • description (optional) - Internal description of the rule.

    Rule Condition

    The condition is evaluated by Jexl and can consist of:

    • custom property or properties
    • system property or properties
    • system functions
    • operators
    • comparisons

    The expression must evaluate to true or false. If a property is used in a condition but it hasn't been defined yet, the expression will evaluate to false.

    Custom Properties

    The developer can define any property of type number, boolean, or string by passing the propName to badges.setValue(), badges.addValue() or badges.subtractValue().

    // set a property
    
    badges.setValue('prop1', 1);
    badges.setValue('prop2', true);
    badges.setValue('prop3', 'test');
    
    badges.addValue('prop4');
    badges.addValue('prop5', 1);
    badges.addValue('prop6', 2);
    
    badges.subtractValue('prop7');
    badges.subtractValue('prop8', 1);
    badges.subtractValue('prop9', 2);
    
    // get the value of a property or default
    const value = badges.getValue('prop1', 0);
    const value = badges.getValue('prop2', false);
    const value = badges.getValue('prop3', 'default value');

    Any of the above methods has a skipEval parameter that you can set to true to skip the call to evaluate(). You would use this in those cases where multiple properties are set at the same time and you only want the evaluation to happen for the last property set/changed.

    A rule with the condition of "gameCount == 1" would match a property called with badges.addValue('gameCount')

    System Properties

    Some properties are defined by the system and can be used in a condition by using the system. prefix: "system.isNewDay"

    Here is a list of system properties:

    Property Value Description Limit
    system.isNewYear boolean Start of a new year. Only true on first evaluate() after a new year.
    system.isNewMonth boolean Start of a new month. Only true on first evaluate() after a new month.
    system.isNewWeek boolean Start of a new week. Only true on first evaluate() after a new week.
    system.isNewDay boolean Start of a new day. Only true on first evaluate() after a new day.
    system.isNewHour boolean Start of a new hour. Only true on first evaluate() after a new hour.
    system.date string Date in format: yyyy-MM-dd ex: "2022-07-04"
    system.time string Time in format: HH:mm ex: "18:15"
    system.dayOfWeek number 1-7 (Monday is 1, Sunday is 7)
    system.isWeekDay boolean Is current day Monday - Friday
    system.isWeekEnd boolean Is current day Saturday or Sunday
    system.lifetimeSessions number Number of total sessions.
    system.lifetimeGames number Number of total games.
    system.lifetimeGamesEnded number Number of total games ended for whatever reason.
    system.lifetimeGameWins number Number of total games ended as a win.
    system.lifetimeGameLoses number Number of total games ended as a loss.
    system.lifetimeGamesCanceled number Number of total games ended by cancelling.
    system.isNewSession boolean Start of a session. Only true on first evaluate() after startSession(). The value of system.sessionStatus is set to 'STARTED'
    system.isSessionEnd boolean End of a session. Only true on first evaluate() after endSession(). The value of system.sessionStatus is set to 'ENDED'
    system.sessionStatus string 'NONE', 'STARTED', 'IN_PROGRESS', 'ENDED'
    system.isNewGame boolean Start of a game. Only true on first evaluate() after startGame(). The value of system.gameStatus is set to 'STARTED'
    system.isGameEnd boolean End of a game. Only true on first evaluate() after endGame(). The value of system.gameStatus is set to 'ENDED'
    system.gameStatus string 'NONE', 'STARTED', 'IN_PROGRESS', 'ENDED'
    system.gameEndReason string 'WIN', 'LOSE', 'CANCEL', 'GAME_START'

    NOTE: The following system properties only return true on the first badges.evaluate() call when the new period starts: isNewYear, isNewMonth, isNewWeek, isNewDay, isNewHour. Don't use these in expressions that use other non-time-based properties. Similarly, the following are only set to true for the first call to badges.evaluate() after a session\game is started\ended: isNewSession, isSessionEnd, isNewGame, isGameEnd.

    System Functions

    Here is a list of functions that can be used in conditions. These allow rules based on whether the player has earned another badge.

    Function Returns Description
    badgeCount('b01') number How many times the badge has been earned.
    hasEarnedBadge('b01') boolean If the badge has been earned or not.

    Rule Evaluation

    Rules are only evaluated when the badges.evaluate() method is called. In most cases you will not call this method directly. Whenever badges.setValue(), badges.addValue() or badges.subtractValue() is called then evaluate() is called unless true is passed to the skipEval parameter. The badges.evaluate() method is also called for badges.startSession(), badges.endSession(), badges.startGame(), badges.endGame().

    On evaluation, every active rule is checked even if the condition doesn't include that property. If the condition is true, the updatePeriod is checked to see if the badge has been earned already since the start of the current period. If it has, then it will not be earned again. Finally, if the rule has set max then the count of the times a badge was earned is checked against this max value. If all thoses checks pass, the badge is earned.

    New Time Period

    The onNewTimePeriod callback is called when a time period (year, month, week, day, hour) has changed. The custom and system properties are passed as parameters. Check systemProps to see which time period(s) changed: isNewYear, isNewMonth, isNewWeek, isNewDay, isNewHour. These values will be true for only the next call to evaluate() when the time period has changed and not again until the next time the period changes.

    The parameters to the callback are readonly/frozen so you must use badges.setValue(), badges.addValue() or badges.subtractValue() to change a value. Set the skipEval flag to true to not trigger extra calls to evaluate().

    badges.onNewTimePeriod = (props: ReadonlyBadgeProperties, systemProps: ReadonlyBadgeProperties) => {
      badges.setValue('prop1', 0, true);
      if (systemProps.time > '17:00') {
        // do something
      }
    };

    NOTE: It is important to understand that onNewTimePeriod is not called at a specific time (such as the top of the hour) but when the time periods are evaluated and if the period has changed since the last time it was checked.

    Start/End Session

    The developer determines what a session is and controls the start and end of that session using badges.startSession() and badges.endSession(). Calling these methods will cause a call to evaluate() to check all badge rules.

    The onSessionStart and onSessionEnd callbacks can be used to see the values of custom and system properties, update those properties or whatever you want when the session starts/ends. The callback happens before the call to evaluate(). The parameters to the callback are readonly/frozen so you must use badges.setValue(), badges.addValue() or badges.subtractValue() to change a value. Set the skipEval flag to true to not trigger extra calls to evaluate().

    badges.onSessionStart = (props: ReadonlyBadgeProperties, systemProps: ReadonlyBadgeProperties) => {
      badges.setValue('prop1', 0, true);
      if (systemProps.time > '17:00') {
        // do something
      }
    };
    badges.onSessionEnd = (props: ReadonlyBadgeProperties, systemProps: ReadonlyBadgeProperties) => {
      // do something
    };

    Start/End Game

    The developer determines what a game is and controls the start and end of that game using badges.startGame() and badges.endGame(). Calling these methods will cause a call to evaluate() to check all badge rules.

    The onGameStart and onGameEnd callbacks can be used to see the values of custom and system properties, update those properties or whatever you want when the game starts/ends. The callback happens before the call to evaluate(). The parameters to the callback are readonly/frozen so you must use badges.setValue(), badges.addValue() or badges.subtractValue() to change a value. Set the skipEval flag to true to not trigger extra calls to evaluate().

    badges.onGameStart = (props: ReadonlyBadgeProperties, systemProps: ReadonlyBadgeProperties) => {
      badges.setValue('prop1', 0, true);
      if (systemProps.time > '17:00') {
        // do something
      }
    };

    The call to badges.endGame() requires a reason parameter that can be: GameEndReason.Win, GameEndReason.Lose, GameEndReason.Cancel, GameEndReason.GameStart. This reason is also passed to onGameEnd. The GameEndReason.GameStart is set by the system when badges.startGame() is called while a game is in progress and badges.endGame() wasn't explicitly called.

    badges.onGameEnd = (props: ReadonlyBadgeProperties, systemProps: ReadonlyBadgeProperties, reason: GameEndReason) => {
      // do something
    };

    Get Earned Badges

    To get a list of earned badges, call one of the following:

    • badges.getEarnedBadges() - all badges earned for this game in a player's lifetime. Same as badges.getEarnedBadges(Period.Global).
    • badges.getEarnedBadges(period: Period) - all badges earned since the start of the current period of a certain period type. To get all the badges earned for the current, active game call: badges.getEarnedBadges(Period.Game).
    • badges.getEarnedBadges(lastTimestamp: string) - all badges earned since a specific timestamp is ISO 8601 UTC format.
    • badges.getEarnedBadgesSinceBookmark(name: string) - all badges earned since a named timestamp bookmark. See Bookmarks.

    When you call badges.setValue(), badges.addValue() badges.subtractValue(), badges.startSession(), badges.endSession(), badges.startGame(), badges.endGame() then any badges earned during that call are returned:

    const earned = badges.setValue('prop1', 'test');
    
    const earned = badges.addValue('prop2');
    
    const earned = badges.subtractValue('prop3');
    
    const earned = badges.startSession('prop3');
    
    const earned = badges.endSession('prop3');
    
    const earned = badges.startGame('prop3');
    
    const earned = badges.endGame('prop3', GameEndReason.Lose);

    You can also set a callback that will be called whenever a badge is earned:

    badges.onBadgeEarned = (badge: ReadonlyEarnedBadge) => {
      // badge includes: id, lastEarned, count
    };

    Bookmarks

    To get a list of badges starting at a time other than those defined in by a period, use a bookmark. Bookmarks are meant to be temporary and are cleared at the start of each session.

    A bookmark is really just a named point in time. Here is an example of how to use a bookmark:

    badges.setBookmark('mark1');
    
    // multiple actions; earn badge(s)
    
    const earned = badges.getEarnedBadgesSinceBookmark('mark1');

    The value returned when setting a bookmark is the ISO 8601 UTC timestamp.

    You can clear a specific bookmark with badges.clearBookmark('mark1') or clear all bookmarks with badges.clearAllBookmarks().

    Badge Name and Rewards

    The badge system focuses on rules and keeping track of badges earned with associated custom properties. It is recommended that you create a badges.json file that includes info such as:

    • id - match id in this file with id in rules.json
    • badge name, description, image URL (UI)
    • any rewards (points, credits, coins) that are earned when the badge is earned

    This allows you to translate the badge info as needed and keeps it separate from the badge rules.

    Here is a sample:

    [
      {
        "id": "b01",
        "name": "Let's Go!",
        "description": "Earn this badge by finishing your first game.",
        "imageUrl": "https://example.com/images/b01.png",
        "sortId": "001",
        "credits": "1"
      },
      {
        "id": "b02",
        "name": "New Year’s Eve",
        "description": "Play a game on New Year's Eve.",
        "imageUrl": "https://example.com/images/b02.png",
        "sortId": "002",
        "credits": "3"
      }
    ]
    

    Install

    npm i badgerific

    DownloadsWeekly Downloads

    1

    Version

    1.0.4

    License

    MIT

    Unpacked Size

    58.4 kB

    Total Files

    9

    Last publish

    Collaborators

    • rmtuckerphx