
# Service Library - `gameplay`

This document provides a complete reference of the custom code library for the `gameplay` service. It includes all library functions, edge functions with their REST endpoints, templates, and assets.


## Library Functions

Library functions are reusable modules available to all business APIs and other custom code within the service via `require("lib/<moduleName>")`.


### `processGameResult.js`

```js
const { interserviceCall } = require("serviceCommon");

/**
 * Mindbricks interservice calls return the same envelope as REST (e.g. { playerStats: {...} }).
 * Unwrap so we read id, eloRating, wins, etc. from the actual row.
 */
function unwrapPlayerStats(res) {
  if (res == null || typeof res !== "object") return null;
  const inner = res.playerStats;
  if (inner && typeof inner === "object" && inner.id) return inner;
  if (res.id && res.userId) return res;
  return null;
}

/**
 * Processes a completed/terminated chess game result:
 * - Computes ELO changes for both players
 * - Calls leaderboard service to update playerStats for registered (non-guest) players
 * - Calls leaderboard service to update leaderboardEntry eloRating and rank
 *
 * @param {object} context - The API context (this)
 */
module.exports = async function processGameResult(context) {
  const game = context.chessGame;
  const updatedFields = context;

  const status = updatedFields.status || game.status;
  const result = updatedFields.result || game.result;

  if (!["completed", "terminated"].includes(status) || !result) {
    return null;
  }

  if (result === "aborted") {
    return null;
  }

  const playerWhiteId = game.playerWhiteId;
  const playerBlackId = game.playerBlackId;
  const isWhiteGuest = game.guestPlayerWhite === true;
  const isBlackGuest = game.guestPlayerBlack === true;

  let whiteOutcome;
  let blackOutcome;
  if (result === "whiteWin") {
    whiteOutcome = "win";
    blackOutcome = "loss";
  } else if (result === "blackWin") {
    whiteOutcome = "loss";
    blackOutcome = "win";
  } else if (result === "draw") {
    whiteOutcome = "draw";
    blackOutcome = "draw";
  } else {
    return null;
  }

  let whiteStats = null;
  let blackStats = null;

  if (!isWhiteGuest && playerWhiteId) {
    try {
      const raw = await interserviceCall("leaderboard", "getPlayerStats", { userId: playerWhiteId });
      whiteStats = unwrapPlayerStats(raw);
    } catch (e) {
      /* player may not have stats yet */
    }
  }
  if (!isBlackGuest && playerBlackId) {
    try {
      const raw = await interserviceCall("leaderboard", "getPlayerStats", { userId: playerBlackId });
      blackStats = unwrapPlayerStats(raw);
    } catch (e) {
      /* player may not have stats yet */
    }
  }

  const whiteElo = whiteStats ? (Number(whiteStats.eloRating) || 1200) : 1200;
  const blackElo = blackStats ? (Number(blackStats.eloRating) || 1200) : 1200;

  const K = 32;
  const expectedWhite = 1 / (1 + Math.pow(10, (blackElo - whiteElo) / 400));
  const expectedBlack = 1 / (1 + Math.pow(10, (whiteElo - blackElo) / 400));

  let actualWhite;
  let actualBlack;
  if (result === "whiteWin") {
    actualWhite = 1;
    actualBlack = 0;
  } else if (result === "blackWin") {
    actualWhite = 0;
    actualBlack = 1;
  } else {
    actualWhite = 0.5;
    actualBlack = 0.5;
  }

  const newWhiteElo = Math.round(whiteElo + K * (actualWhite - expectedWhite));
  const newBlackElo = Math.round(blackElo + K * (actualBlack - expectedBlack));

  const now = new Date().toISOString();

  function buildUpdatePayload(stats, outcome, newElo) {
    const wins = (stats ? stats.wins : 0) + (outcome === "win" ? 1 : 0);
    const losses = (stats ? stats.losses : 0) + (outcome === "loss" ? 1 : 0);
    const draws = (stats ? stats.draws : 0) + (outcome === "draw" ? 1 : 0);
    const totalGames = wins + losses + draws;
    let streak = stats ? stats.streak : 0;
    if (outcome === "win") streak = streak > 0 ? streak + 1 : 1;
    else if (outcome === "loss") streak = streak < 0 ? streak - 1 : -1;
    else streak = 0;
    return { eloRating: newElo, totalGames, wins, losses, draws, streak, lastGameAt: now };
  }

  if (!isWhiteGuest && playerWhiteId && whiteStats) {
    try {
      const payload = buildUpdatePayload(whiteStats, whiteOutcome, newWhiteElo);
      await interserviceCall("leaderboard", "updatePlayerStats", { id: whiteStats.id, ...payload });
    } catch (e) {
      console.error("Failed to update white player stats:", e.message);
    }
  }

  if (!isBlackGuest && playerBlackId && blackStats) {
    try {
      const payload = buildUpdatePayload(blackStats, blackOutcome, newBlackElo);
      await interserviceCall("leaderboard", "updatePlayerStats", { id: blackStats.id, ...payload });
    } catch (e) {
      console.error("Failed to update black player stats:", e.message);
    }
  }

  return { whiteElo: newWhiteElo, blackElo: newBlackElo, result };
};
```














---

*This document was generated from the service library configuration and should be kept in sync with design changes.*
