Giter VIP home page Giter VIP logo

gamification's Introduction

Build Status Coverage Status GitHub license Greenkeeper badge

Gamification

A reusable microservice for gamification.

This project provides a microservice to manage gamification for your application. It does not provide any graphics or pre-defined content, but only a backend REST API. At its core, the service listens to events sent either via HTTP POST calls or retrieved from RabbitMQ. Then, a list of user-defined achievement rules is tested against the newly received event. Should an achievement rule be evaluated to true, a new achievement is granted to the user.

Terminology

User

All gamification data is associated with a user. Users are identified by a unique string we commonly call user_id. Each user has

  • a level
  • many granted achievements
  • xp in multiple xp-pools

Please note that there is no explicit user table. User data is distributed over multiple other tables and aggregated on demand.

Achievement

Achievements are the core object of the gamification service. Users strive to collect achievements. Achievements can be thought of as badges, belts, or whatever other representation of progress you can think of. To the gamification service, however, these are all the same: achievements. Achievements usually define one or multiple requirements to be granted, for example:

  • the user reached 10 XP
  • the user achieved the FooAchievement
  • a FooEvent is received and the user has less than 42 XP

The same achievement can be granted multiple times to the same user, if desired. Achievements can also be revoked after being granted. This can be used to replace an easy to acquire FooSilver achievement with a harder to get FooGold achievement. Some achievements may be granted again even after being revoked, others may not be granted again after being revoked. The behaviour can be configured using the maxAwarded and maxAwardedTotal configuration options, respectively.

XP-Pool

The application may define multiple xp-pools. The default pool is called "XP" and is always defined. Each user has a number of xp in each xp-pool. A new user has 0 xp in all xp-pools. XP may be incremented as well as decremented, but should never go below 0. The different xp-pools may be used for purposes such as

  • counting some form of experience, as is commonly done in games
  • representing some form of a currency the user can spend and earn
  • counting the number of times something happened, i.e. a specific event was received. This counter can then be used in more complex achievement requirements.

Level

Each user has a level. The user starts at level 1 and can never go below that. The level is strictly tied to the user's main xp "XP". The required xp for each level can be configured to either be linearly increasing, exponentially increasing, or completely custom-defined. The level is not stored anywhere but always calculated on the fly. If the user, for whatever reason, loses xp, their level may also decrease.

Workflow

This section describes the general concept of the gamification service in more detail. The service is designed to be fully event-driven and integrate well with RabbitMQ. It is thought to be integrated into your existing microservice architecture. It listens to configurable events emitted by your other services. These events are explicitly not gamification-specific. For example, normally, your application wouldn't send a "Grant10XP". Instead, you should send events like "ForumPost" or "UserSignup". You need to configure rules (see next section) detailing what happens after a "ForumPost" or "UserSignup" event (e.g., giving 10 XP). Your existing services do not need to be aware of their events being used for gamification purposes.

The gamification service subscribes to a RabbitMQ queue from which it processes events. Whenever the service receives an event, it checks whether or not the event is relevant to gamification. If yes, the service executes any configured immediate actions, if there are any. Then, it checks all configured achievement rules to see whether or not a new achievement should be granted to the user. It is your job to setup the events to listen to, event actions and achievement rules for your application as you see fit. Once an achievement or xp are granted, they are persisted to MongoDB. It is planned to also send an event back to RabbitMQ when an achievement or xp are granted.

The gamification service also provides a (mostly) read-only REST API, which can be used to ask for a user's achievements, xp and level. In the future, the API may also support more advanced use-cases like leaderboards. The API is documented at http://localhost:3030/docs.

Gamification Configuration

The gamification rules must be configured within the config/gamification.yml file. This file is parsed on application start and must be adjusted to your gamification use-case. The most basic configuration looks like this:

XPs: []
levels:
  type: linear
  interval: 100
events: {}
achievements: {}

Detailed configuration options follow. Please note that there currently is no validation logic in place to check for errors. The application will likely crash if you make an error in your configuration!

XP-Pools

Define the different xp-pools there are within your application. The "XP" xp-pool is always defined.

XPs:
  - money
  - myOtherXP
  - myXP

Note: The xp-pool names defined here are not currently validated by the configuration parser, i.e. not specifying the available xp-pools or specifying additional xp-pools does not raise an error currently. It is, however, planned to validate the configuration more thoroughly, which would then raise an error if you use an xp-pool not defined here.

Levels

Define when to increase a user's level based on their XP. The level is always purely based on the user's current XP. Each user, regardless of this configuration, always starts at level 1. That means that the first configuration value specifies when the user reaches level 2, not level 1. There are three ways to configure levels:

levels:
  # Manual level steps: Level 2 will be reached at 10 XP, level 3 at 100 XP
  # and level 4 at 1000 XP.
  type: manual
  steps: [10,100,1000]
  # Linear level steps: The level is increased for every 100 XP, i.e. level 2
  # is reached at 100 XP, level 3 is reached at 200 XP, and so on.
  type: linear
  interval: 100
  # Exponential level steps: The XP required to reach the next level doubles
  # after every level. While the second level takes 100 XP, the next level will
  # take 200 XP, then 400 XP, and so on.
  type: exponential
  starting_value: 100

Events

Define which events to listen to. All other events are ignored by the service. Each key is an event name to listen to. The associated value describes immediate actions to take when receiving the event.

You can use the actions array to specify actions to be executed as soon as the event is received.

events:
  # In its simplest form, an event name
  # can simply be followed by `: ~`, in which case no further immediate actions
  # are executed when receiving the event.
  MyEvent: ~
  # This is equivalent to
  MyEvent:
    actions: []

Each action is an object with a single top-level key denoting the action type. Currently, only the "xp" action is supported. Implicitly, the awardee is always assumed to be defined in the event's top-level user_id field. When you don't want the user that triggers the event getting the xp, you can use the awardee_id configuration option to instead get the awardee's id from the event's payload.

actions:
  - xp:
    # The name of the xp-pool
    name: myXP
    # The amount to add. You can also specify a negative amount here
    # to subtract xp.
    amount: 1
    # Optional: The field inside the payload containing the awardee's user_id.
    awardee_id: receiver_id

Achievement Rules

# Define all available achievements. These definitions are called
# "achievement rules". Each key is the unique name of an achievement. The
# associated value describes further details related to this achievement.
achievements:
  # In its simplest, but useless form, an achievement looks like this:
  MyAchievement: ~
  # This is equivalent to the following configuration:
  MyAchievement:
    # List of requirements for the achievement.
    requirements: []
    # List of other achievement names to un-grant, should the user have them,
    # when the achievement is granted.
    replaces: []
    maxAwarded: 1
    maxAwardedTotal: 1
    actions: []
    hidden: false
    # Not yet implemented: scope
    scope: [user_id]

Achievement Rule requirements

The requirements key specifies all requirements that must be met for the achievement to be granted to the user. Each requirement consists a single top-level key denoting the type of the requirement. The corresponding value sets further configuration options. By default, all requirements must be met for the achievement to be granted. In addition, to the requirements defined here, the maxAwarded and maxAwardedTotal options described below may also restrict whether the achievement can be granted.

The supported four requirement types are detailed in the following sections. Some of them support the amount setting. The amount can either be a number x, which will be seen as >= x, or one of the following strings:

  • "== x": exactly x
  • "!= x": not exactly x
  • ">= x": at least x
  • "> x": more than x
  • "<= x": at most x
  • "< x": less than x
xp Achievement Rule requirements

The xp requirement allows you to specify the amount of xp from one of the xp-pools required for this achievement to be granted. You must specify the name of the xp-pool as well as the amount needed. The following example is fulfilled if the user has less than 42 XP:

- xp:
    name: XP
    amount: "< 42"
achievement Achievement Rule requirements

The achievement requirement allows you to specify other achievements required for this achievement to be granted. You must specify the name of the other achievement as well as the amount needed. The following example is fulfilled if the user has two MyAchievement achievements:

- achievement:
    name: MyAchievement
    amount: 2
AnyOf Achievement Rule requirements

The AnyOf requirement allows you to specify an "OR" semantic between requirements. The requirement's value is a list of child-requirements. The requirement is fulfilled if any of the child-requirements is fulfilled. The following example is fulfilled if the user has at least 10 yourXP or at least 20 XP:

- AnyOf:
  - xp:
    name: yourXP
    amount: 10
  - xp:
    name: XP
    amount: 20
event Achievement Rule requirements

The event requirement allows you to specify events that must have been received in order for this achievement to be granted. You must specify the name of the event as well as the amount needed. You can optionally also define "conditions" on the received event. Here's a complex example showing the behaviour:

- event:
  name: YourEventName
  amount: >= 10
  conditions:
    # Conditions can have two types: `parameter` and `AnyOf`. As with
    # requirements, conditions are using the AND semantic.
    - parameter: someParameter
      value: 100
    - AnyOf:
      - parameter: someOtherParameter
        value: 10
      - parameter: someOtherParameter2
        value: 100
      - AnyOf:
        - parameter: someOtherParameter3
          value: 10
        - parameter: someOtherParameter4
          value: 100

Achievement Rule replaces

The replaces key allows you to specify an array of other achievement names. Should the user receiving the new achievement have one of the other achievements specified in here, they are taken away from the user. This is useful for multi-stage achievements where the best "gold" achievement replaces the "silver" achievement. If the user has more than one of the replaced achievements, they are all removed.

Achievement Rule maxAwarded and maxAwardedTotal

These two settings configure how often the achievement can be granted to a user. maxAwarded specifies how often a user can have the achievement at the same time. maxAwardedTotal specifies how often a user can be granted the achievement over time. Both of them are optional, however, if both are given, make sure that maxAwarded is less or equal than maxAwardedTotal. If both maxAwarded and maxAwardedTotal are not set, they default to 1. If only maxAwarded is set, maxAwardedTotal defaults to positive infinity. If only maxAwardedTotal is set, maxAwarded defaults to maxAwardedTotal.

Achievement Rule actions

You can use the actions array to specify actions to be executed whenever the achievement is granted. Each action is an object with a single top-level key denoting the action type. Currently, only the "xp" action is supported:

actions:
  - xp:
    # The name of the xp-pool
    name: myXP
    # The amount to add. You can also specify a negative amount here
    # to subtract xp.
    amount: 1

Achievement Rule hidden

This boolean describes whether or not the achievement is "public" and returned when the /user endpoint is used. It defaults to false. This is useful for achievements which are only used to represent state and should not be visible to the user.

Achievement Rule scope

This is not yet implemented. It is thought to cover cases where the achievement is granted in the scope of an entity, for example a course or classroom.

Internals

This project uses Feathers as API framework and MongoDB for storing data. Mongoose is used to validate the stored data.

Feathers services

The gamification service is built using four Feathers services:

  1. Achievements: The achievements service handles achievement data and stores granted achievements in the achivements collection. Each row consists of the user_id, the name (achievement name), current_amount (how often the user currently has the achievement), total_amount (how often the user has received the achievement) and scope (not yet implemented).

  2. Events: The events service handles event data and stores all incoming events in the events collection. Each row consists of the user_id, the name (event name), and the payload (event payload).

  3. XP: The xp-pool service handles xp-pool data and stores all granted xp in the xp collection. Each row consists of the user_id, the name (xp-pool name), and the amount (amount of xp the user has in this xp-pool).

  4. User: The user service aggregates data from the other three services. It can be used to retrieve gamification information for a single user. It is the only service without its own collection. It is also responsible for calculating the user's level.

Development

Local Development

  1. Make sure you have NodeJS >= 8.0.0, npm >= 6.1.0 and MongoDB installed.

  2. If you don't have a local RabbitMQ installation, temporarily remove the AmqpConnector.connect() call from src/index.js.

  3. Install npm dependencies

    cd path/to/gamification; npm install
    
  4. Start the app

    npm dev
    

Docker Development

This project provides two docker-compose files, one meant for development and one meant for production. The production docker-compose.yml configuration does not contain definitions for the RabbitMQ service, because the gamification service is thought to connect to an already existing RabbitMQ instance. The development docker-compose.dev.yml file, however, also contains a simple RabbitMQ definition to make development easier.

First run npm install. Then start the docker environment.

docker-compose -f docker-compose.dev.yml up

This starts the containers for the app, MongoDB and RabbitMQ. The app is then available at http://localhost:3030/.

RabbitMQ: Sending events manually

The RabbitMQ management interface is available at http://localhost:15672. In development mode, use Username guest and Password guest to login.

You can send events manually in the Exchanges section. Select the exchange and then publish your message at Publish message. Don't forget to insert the routing key.

Testing

Run npm test to run all linters and tests. Use npm coverage to also generate coverage.

Running in Production

It is recommended to use docker-compose to run this microservice in production. Execute RABBITMQ_HOST=rabbit-host:5672 docker-compose up in the project's root directory to start the Node.js app and a MongoDB container. It is your responsibility to start a separate RabbitMQ container and set the RABBITMQ_HOST environment variable respectively. If you don't want to use the RabbitMQ integration, simply omit the environment variable.

License

Copyright (c) 2018 Kim-Pascal Borchart, Christian Flach, Corinna Jaschek, Sebastian Kliem, Mandy Klingbeil, Marcus Konrad, Frederike Ramin.

Licensed under the MIT license.

gamification's People

Contributors

ceev avatar cmfcmf avatar corinnaj avatar goesontangents avatar greenkeeper[bot] avatar janrenz avatar sebastiankliem avatar tofixx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gamification's Issues

Running under IIS?

I've tried to run the app using IIS rewrites rules (and Node, of course).

My "achievement" is to get it running using an URL like this: "SERVER/api2/gamification/user/user345" and a rule like this:

<rewrite> <rules> <rule name="ReverseProxyInboundRule1" stopProcessing="true"> <match url="gamification/(.*)" /> <action type="Rewrite" url="http://localhost:3030/{R:1}" logRewrittenUrl="true" /> </rule> </rules> </rewrite>

However, when I try to get the XP (which works under Node with http://localhost:3030/xp) or the Events, all I get is IIS 404 (The resource cannot be found, Requested URL: /api2/gamification/xp).

Have you tried this? Could you help me, please?

Thank you,
Catalin

Dockerize app

  • Node app as docker container
  • connect mongoDB container
  • connect RabbitMQ container

find better error handling for AMQP connection

In AmqpConnector.connect() the error handling causes unclear return behavior.

In case the connection fails the first time, the method returns a resolved empty promise. Callers of this method, however, expect the returned promise to only be resolved once the connection is successful.
-- @cmfcmf in #44

...
} catch (error) {
  console.warn(error);
  console.log('RabbitMQ connection failed. Reconnecting in 1 s ...');
  setTimeout(() => {
    this.connect();
  }, 1000);
}

Required Achievement don't yet have scope check

Currently

Scope is defined on .scope and regards events from <achievement.requirements.event, but not <achievement.requirements.achievements

Issue

  • It is unclear for a configurator where the service will check for correct scopes
    1. scope should regard every requirement (both events AND, newly, achievements)
    1. achievement scopes could be handled in requirements.achievements..scope

Test amqp

Currently there are no tests for amqp.

Can be implemented via Dependency Injection.

Scoped XP

Have XPs that are counted per scope, e.g. javaCourseXP, cppCourseXP etc.

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on Greenkeeper branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet.
We recommend using:

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

replace MaxAwarded with maxAwardedTotal AND maxAmount

Situation

Currently, maxAwarded is not time-sensitive, meaning an achievement A with maxAwarded: 1 can be awarded infinite times as long as it's replaced by another achievement B with replaces: A.

Proposal:

Two fields for achievements:

  • maxAwardedTotal (total number of times that achievement can be had by someone EVER, even if currently 0)
  • maxAmount (number of times the achievement can be held AT ONCE)

fix replaces

In case of Achievement B replaces: Achievement A

  • add achievement A to requirements of Achievements B (otherwise achievements could be skipped when getting e.g. a lot of XP at once)
  • replace should use number instead of true, to replace a certain amount of Achievement A

Rename contet db field to payload

We dont want to be too specific with openHPIs "context" fields to only store that, but rather store the whole payload of the event.

How to get VoluntarySelfTestTakenAward?

In your demo data you have an achievement called "VoluntarySelfTestTakenAward".

What data (POST requests) should be made in order to obtain it? Where and how to pass "completion_percentage"?

Thank you

preparations for going live on Schul-Cloud

  • Docker: in production, mount config folder into container
  • create minimal config for Schul-Cloud
    • Event: Homework submitted
    • Achievements: submitted first homeworks, submitted 5 homeworks in total

An in-range update of amqplib is breaking the build 🚨

The dependency amqplib was updated from 0.5.2 to 0.5.3.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

amqplib is a direct dependency of this project, and it is very likely causing it to break. If other packages depend on yours, this update is probably also breaking those in turn.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 38 commits.

There are 38 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Config Validator

Validate that the gamification.yml is correct (e.g. includes XPs, events and achievements) when starting the service. Give helpful error messages, instead of simply failing.

How to specify the userid?

Hi,

I would like to use this micro-service as a REST api for a mobile application. For this I would need to send the UserID to the API via a GET/POST request. Please tell me how can I achieve this.

Also, where is the config. for MongoDB?

(Win server 2012 - Docker is not available for this version. No RabbitMQ either)

Thank you.

Levels

  • How to define when to give next level?
  • Implement logic

First achievement (CommunicatorBronze) is not obtained from the database

Steps for initial database:

2 x POST events, data:
{ "user_id": "user123", "name": "ForumPost", "payload": {} }

1 x GET user/user123 >> response=
{ "user_id": "user123", "achievements": [], "xp": [ { "name": "XP", "amount": 43 }, { "name": "postXP", "amount": 2 } ], "level": 1 }

but in database >>
{"_id":"5be13f399acbc00fb8e473fb","scope":null,"user_id":"user123","name":"CommunicatorBronze","current_amount":1,"total_amount":1,"__v":0}

Expected: the document from the database

Subsequent requests for event=ForumPost are executed correctly
--MongoDB data:
{"_id":"5be13f399acbc00fb8e473fb","scope":null,"user_id":"user123","name":"CommunicatorBronze","current_amount":0,"total_amount":1,"__v":0} {"_id":"5be140329acbc00fb8e473ff","scope":null,"user_id":"user123","name":"CommunicatorSilver","current_amount":1,"total_amount":1,"__v":0}

and GET user/user123 >>>
{ "user_id": "user123", "achievements": [ { "name": "CommunicatorSilver", "amount": 1, "scope": null } ], "xp": [ { "name": "XP", "amount": 73 }, { "name": "postXP", "amount": 5 } ], "level": 1 }

Scope

Currently scope is not taken into consideration when granting achievements.

Achievement cycle

Every time an achievement is granted we should enter another cycle of achievement checking to ensure now enabled achievements are granted correctly.

Scoped Replace

How does this currently work?
Bekommst bronze pro java kurs, machst Folgekurs, nur das eine replacen.
--> Replaces is always in the scope of the achievement it is defined in

Documentation

  • Sketch out the system
  • Describe configuration
  • Describe message flow
  • Describe how to configure yaml
  • Generate Swagger documentation

An in-range update of mongoose is breaking the build 🚨

The dependency mongoose was updated from 5.4.13 to 5.4.14.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

mongoose is a direct dependency of this project, and it is very likely causing it to break. If other packages depend on yours, this update is probably also breaking those in turn.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 18 commits.

  • 35b90d2 chore: release 5.4.14
  • 8dc47a5 docs(schema): add examples for remaining functions
  • 764735b fix(documentarray): report validation errors that occur in an array subdoc created using create() and then set()
  • 3fec456 test(documentarray): repro #7504
  • 660fe60 chore: remove unnecessary print statements
  • 13c7a00 docs(schema): add examples to schema functions
  • 270732e docs(migrating_to_5): link to migrating to 5 docs on the mongoosejs.com website
  • db79cfc Merge branch 'master' of github.com:Automattic/mongoose
  • 67754bd style: fix lint
  • 8e30004 Merge pull request #7530 from sarpik/master
  • 3e44bc2 Merge branch 'master' of github.com:Automattic/mongoose
  • aa43200 docs: add MongooseError to API docs and add list of error names
  • 0daf626 Merge pull request #7521 from nocksapp/master
  • 8752502 fix anchor tag
  • b5f1723 chore: now working on 5.4.14

There are 18 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.