Skip to main content

Overview

Agent LoL provides comprehensive match statistics analysis, displaying detailed information about each player’s performance, team dynamics, and minute-by-minute game progression. The stats analysis system processes data from the Riot API to give players actionable insights into their matches.

Participant Statistics

The Participants component displays key stats for all 10 players in a match, with visual highlighting for the current user and their teammates.

Displayed Stats

For each participant, the following information is shown:
  • Champion played - with champion icon from Data Dragon
  • Win/Loss status - color-coded badges
  • Summoner name and Riot ID
  • KDA (Kills/Deaths/Assists) - tabular format for easy comparison

Participant Display Code

// src/components/Participants.js:50-91
<div
  key={p.puuid ?? i}
  className={`px-5 py-3 sm:px-6 flex flex-wrap items-center gap-3 sm:gap-4 ${rowHighlight}`}
>
  {championIconUrl && (
    <Image
      src={championIconUrl}
      alt={championName ?? 'Champion'}
      title={championName ?? undefined}
      width={40}
      height={40}
      className="rounded-lg object-cover border border-slate-600/60 shrink-0"
    />
  )}
  {isCurrentUser && (
    <span className="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-semibold bg-amber-600/80 text-amber-100 shrink-0">
      You
    </span>
  )}
  <span
    className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ${
      p.win ? 'bg-emerald-900/50 text-emerald-300' : 'bg-red-900/40 text-red-300'
    }`}
  >
    {p.win ? 'Win' : 'Loss'}
  </span>
  <span
    className={`text-sm font-medium ${
      isCurrentUser ? 'text-amber-100' : isTeammate ? 'text-slate-200' : 'text-white'
    }`}
  >
    {p.summonerName ?? p.riotIdGameName ?? 'Unknown'}
  </span>
  <span className="text-slate-500 text-sm">
    {p.championName ?? `Champion ${p.championId ?? '?'}`}
  </span>
  <span className="text-slate-400 text-sm tabular-nums">
    {p.kills ?? 0} / {p.deaths ?? 0} / {p.assists ?? 0} KDA
  </span>
</div>

Visual Highlighting

The UI uses color-coded highlighting to help users quickly identify key players:
// src/components/Participants.js:44-48
const rowHighlight = isCurrentUser
  ? 'bg-amber-950/30 border-l-4 border-l-amber-500'
  : isTeammate
    ? 'bg-sky-950/35 border-l-2 border-l-sky-500'
    : '';
  • Current user: Amber highlight with thick left border
  • Teammates: Blue highlight with medium left border
  • Opponents: No special highlighting

Champion Icons from Data Dragon

Champion images are loaded from Riot’s Data Dragon CDN:
// src/components/Participants.js:6-10
const championImageId = (championName) => {
  if (!championName || typeof championName !== 'string') return null;
  return championName.replace(/\s+/g, '');
};
// src/components/Participants.js:29-33
const championIconUrl =
  ddragonVersion && cid
    ? `${DATA_DRAGON_BASE}/cdn/${ddragonVersion}/img/champion/${cid}.png`
    : null;
Champion names with spaces (e.g., “Aurelion Sol”) are converted to “AurelionSol” to match Data Dragon’s naming convention.

Timeline Analysis

The TimelineReviewer component provides minute-by-minute analysis of match progression, showing how the game evolved over time.

Minute-by-Minute Data

For each minute of the game, the system tracks:
  • CS (Creep Score) - minions/monsters killed
  • Gold - total gold earned
  • Level - champion level
  • Events - kills, objectives, tower plates, etc.

Building Timeline Data

// src/components/TimelineReviewer.js:7-18
function buildMinuteByMinute(frames, userParticipantId, enemyParticipantId, maxMinutes) {
  const list = [];
  for (let i = 0; i < maxMinutes && i < frames.length; i++) {
    const frame = frames[i];
    const participantFrames = frame?.participantFrames ?? {};
    const events = frame?.events ?? [];
    const userFrame = userParticipantId ? participantFrames[userParticipantId] : null;
    const enemyFrame = enemyParticipantId ? participantFrames[enemyParticipantId] : null;
    list.push({ minute: i, userFrame, enemyFrame, events });
  }
  return list;
}

Event Tracking

The system categorizes important game events:
// src/components/TimelineReviewer.js:25-37
function getEventLabels(events) {
  return events
    .filter((e) => ['CHAMPION_KILL', 'ELITE_MONSTER_KILL', 'BUILDING_KILL', 'TURRET_PLATE_DESTROYED'].includes(e?.type))
    .map((e) =>
      e.type === 'CHAMPION_KILL'
        ? 'Kill'
        : e.type === 'ELITE_MONSTER_KILL'
          ? (e.monsterType ?? 'Objetivo')
          : e.type === 'TURRET_PLATE_DESTROYED'
            ? 'Placa'
            : 'Torre'
    );
}

Tracked Events

  • CHAMPION_KILL - Player eliminations
  • ELITE_MONSTER_KILL - Dragon, Baron, Herald
  • BUILDING_KILL - Tower destructions
  • TURRET_PLATE_DESTROYED - Tower plate gold

Narrative Generation

The system generates human-readable descriptions of each minute:
// src/components/TimelineReviewer.js:39-57
function buildMinuteNarrative(minute, userFrame, enemyFrame, events, userChampion, enemyChampion) {
  const userCs = userFrame?.minionsKilled ?? userFrame?.totalMinionsKilled ?? 0;
  const userGold = userFrame?.totalGold ?? 0;
  const userLevel = userFrame?.level ?? 0;
  const enemyCs = enemyFrame?.minionsKilled ?? enemyFrame?.totalMinionsKilled ?? 0;
  const enemyGold = enemyFrame?.totalGold ?? 0;
  const enemyLevel = enemyFrame?.level ?? 0;
  const eventLabels = getEventLabels(events);
  const userName = userChampion ?? 'Tu personaje';
  const rivalName = enemyChampion ?? 'el rival';

  if (minute === 0) {
    const eventsPhrase = eventLabels.length > 0 ? ` Eventos: ${eventLabels.join(', ')}.` : '';
    return `${userName} comenzó con ${userGold} de oro, ${userCs} minion${userCs !== 1 ? 's' : ''} asesinado${userCs !== 1 ? 's' : ''}, nivel ${userLevel}. ${rivalName} tenía ${enemyGold} de oro, ${enemyCs} minion${enemyCs !== 1 ? 's' : ''}, nivel ${enemyLevel}.${eventsPhrase}`;
  }

  const eventsPhrase = eventLabels.length > 0 ? ` Eventos en este minuto: ${eventLabels.join(', ')}.` : '';
  return `A ${formatGameTime(minute)}, ${userName} tenía ${userCs} CS, ${userGold} de oro, nivel ${userLevel}. ${rivalName}: ${enemyCs} CS, ${enemyGold} de oro, nivel ${enemyLevel}.${eventsPhrase}`;
}

Timeline Display

The timeline is rendered as a scrollable list with formatted game time and narrative descriptions:
// src/components/TimelineReviewer.js:98-110
<div className="space-y-4 max-h-[50vh] overflow-auto">
  {minuteByMinute.map(({ minute, userFrame, enemyFrame, events }) => (
    <div key={minute}>
      <p className="font-mono font-medium text-slate-300 mb-1">
        {formatGameTime(minute)}
      </p>
      <p className="text-slate-400 leading-relaxed">
        {buildMinuteNarrative(minute, userFrame, enemyFrame, events, userChampion, enemyChampion)}
      </p>
    </div>
  ))}
  {/* AI recommendations section... */}
</div>

Configuration

The timeline analysis duration can be configured via environment variables:
// src/components/TimelineReviewer.js:66-70
const timelineCompareMs = Math.max(0, parseInt(process.env.NEXT_PUBLIC_TIMELINE_COMPARE || '0', 10));
const frames = timelineData?.info?.frames ?? [];
const frameInterval = timelineData?.info?.frameInterval ?? 60000;
const maxMinutes =
  timelineCompareMs > 0 ? Math.ceil(timelineCompareMs / frameInterval) : frames.length;
Set NEXT_PUBLIC_TIMELINE_COMPARE in milliseconds to limit the analysis window. For example, 600000 = 10 minutes.

Raw Data Inspection

Users can view raw frame data for debugging or detailed analysis:
// src/components/TimelineReviewer.js:119-132
{timelineCompare?.userFrame && timelineCompare?.enemyFrame && (
  <details className="mt-4">
    <summary className="text-xs text-slate-500 cursor-pointer">
      Ver datos crudos ({formatGameTime(timelineCompare.frameMinute ?? 0)})
    </summary>
    <pre className="mt-2 text-xs overflow-auto max-h-40 bg-slate-900/50 p-3 rounded">
      {JSON.stringify(
        { tú: timelineCompare.userFrame, rival: timelineCompare.enemyFrame },
        null,
        2
      )}
    </pre>
  </details>
)}

Next Steps

AI Coaching

Get personalized coaching from GPT-4 analysis

Replay Viewer

Watch matches in 2.5D with Three.js

Build docs developers (and LLMs) love