fictorial / dueling-monkeys

A scalable backend for real-time matchplay games
5 stars 2 forks source link
elo-rating game-server iap-verification matchmaking mobile multiplayer nodejs pubsub redis socket-io socketio socketio-server virtual-currency websockets

Dueling Monkeys 🐵 🕹 🐵

A scalable backend for real-time matchplay games.

Users 🙋

Matches

Coins 💰

Player Ratings 📈

Rules of Play 📖

Matchmaking 🤝

Matchplay 🌎

Refereeing, Voting 🗳

Trolls, Flagging 😈 🚩

Betting 💰

Timeouts ⏰

Tech

Scalability 🤗

Socket.io Messages

Client to Server 📥

once('signup', ())

once('checkin', (token))

on('automatch', (rules, bet))

on('match event', (eventType, eventData))

on('vote for winner', (winnerId))

on('flag opponent', (reason))

on('get products', ())

on('verify purchase', (productId, receipt))

on('change display name', (newName))

Server to Client 📤

emit('name', userDisplayName)

emit('coins', delta, balance, reason)

emit('token', token)

emit('error', context, message)

emit('match', $match)

    $match = {
      id: string,
      rules: string,
      bet: int,

      status: string,
      outcome: string?,

      flagReason: string?,

      p1: string,
      vote1: string,
      name1: string,
      stats1: string,

      p2: string?,
      vote2: string?,
      name2: string?,
      stats2: string?,

      winnerId: string?,

      created: string,
      started: string?,
      ended: string?
    }

emit('products', [$product])

    $product = {
      id: string,
      coins: int
    }

emit('match event', senderId, eventType, eventData)

emit('presence', userId, isPresent)

emit('match started', $match)

emit('match ended', $match)

emit('match started', matchId)

emit('match stats', [$stats])

emit('server metadata', $serverMetadata)

    $serverMetadata = {
      usersOnline: int
    }

Redis Schema 🗄

Users 👥

u/$userId => hash

  name    => string          Generated or user-defined non-unique display name
  coins   => int             Current balance of virtual coins
  matchId => string?         Current (only one!) match id if any

Pending Matches ⏱

pm/$rules/$bet => set of $matchId

Pending Matches for Quarantined Users 😷

q/pm/$rules/$bet => set of $matchId

All Matches

m/$matchId => hash

  id       => string        e.g. "01BY10N9HQ01EFGZARHE0MK4YF"
  rules    => string        rules of play
  bet      => int           > 0

  p1       => string        $userId
  name1    => string        cached $p1.name
  vote1    => string        p1 | p2

  p2       => string?       $user != p1
  name2    => string?       cached $p2.name
  vote2    => string?       $p1 | $p2

  status   => string        "pending" | "active" | "ended"
  outcome  => string?       "normal" | "conflict" | "flagged"

  created  => string        ISO-8601; e.g. "2017-11-03T13:21:59.788Z"
  started  => string?       set when status "pending" => "active"
  ended    => string?       set when status "active" => "ended"

  winnerId => string?       nil | $p1 | $p2

To be explicit, expiring a match means removing it from Redis

User Stats 📊

us/$userId/$rules => hash

  elo      => int           current Elo rating (default 1200; range ~ 0-5000)
  played   => int           matches played (all-time)
  won      => int           matches won (all-time)
  winnings => int           coins earned from winning matches (all-time)

Metadata

metadata => hash

  sockets => int            Number of currently connected users (CCU)

Configuration 🎛

Some elements of Dueling Monkeys are configurable via environment variables:

PORT=3000                          Socket.io port

REDIS_URL=redis://localhost:6379   Redis instance to use

SECRET=foobar                      JSON Web Token signing key

SIGNUP_BONUS=1000                  Coins to give new user on signup

PENDING_TIMEOUT=120                Cancel pending automatches after seconds
ACTIVE_TIMEOUT=43200               Cancel active matches after seconds
ENDED_TIMEOUT=43200                Delete ended matches from Redis after seconds

FLAGGED_LIMIT=20                   A player flagged this many times gets quarantined

CLEAN_NAMES=1                      Replace profanity in usernames with '*' if 1
MIN_NAME=3                         Min length of user display names
MAX_NAME=32                        Max length of user display names

REDIS_PRODUCTS_KEY=products        Where coin products JSON is stored in Redis

Coin Products for In-App Purchases 💰

Coin products for sale are configured in Redis. The idea is that you can update which products are available without a new server code deployment. The format of the value is JSON of the form [{"id":string, "coins":int}]. These are made available to all clients regardless of platform.

Applicability

Think Chess, Space Invaders, ".io" games... not Quake 3 😬

License

MIT

Copyright

Copyright (C) 2017 Fictorial LLC.