Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions automation/game-progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import progress from "./progress.json";

type LogEntry = {
date: string;
hours: number;
completion: number;
deltaHours?: number;
deltaCompletion?: number;
trackables?: Record<string, number>;
deltaTrackables?: Record<string, number>;
};

type GameProgress = {
startDate: string;
lastUpdated: string;
lastUpdatedBy: string;
trackables?: string[];
cupTotal?: number;
logs: LogEntry[];
};

function parseDate(dateString: string): Date {
const [day, month, year] = dateString.split("/");
return new Date(`${year}-${month}-${day}`);
}

export default function GameProgressPage() {
const games = Object.entries(progress as Record<string, GameProgress>);
const sortedGames = games.sort(([, a], [, b]) => parseDate(b.lastUpdated).getTime() - parseDate(a.lastUpdated).getTime());

return (
<main>
<h1>🎮 Game Progress Tracker</h1>

{sortedGames.map(([title, info]) => (
<section key={title}>
<h2>{title}</h2>
<p>
<strong>Started:</strong> {info.startDate}<br />
<strong>Last Updated:</strong> {info.lastUpdated} by {info.lastUpdatedBy}
</p>
<ul>
{info.logs.map((entry, i) => (
<li key={i}>
{entry.hours}h, {entry.completion}%
{(entry.deltaHours || entry.deltaCompletion) && (
<> (<em>+{entry.deltaHours ?? 0}h, +{entry.deltaCompletion ?? 0}%</em>)</>
)}
{" — "}
<small>{entry.date}</small>
{entry.trackables && (
<ul style={{ marginLeft: "1rem" }}>
{Object.entries(entry.trackables).map(([item, value]) => {
const showTotal = item === "3-Star GP" && info.cupTotal;
return (
<li key={item}>
{item}: {value}{showTotal ? `/${info.cupTotal}` : ""}
{entry.deltaTrackables?.[item] !== undefined && (
<> (<em>+{entry.deltaTrackables[item]}</em>)</>
)}
</li>
);
})}
</ul>
)}
</li>
))}
</ul>
</section>
))}
</main>
);
}
110 changes: 110 additions & 0 deletions automation/progress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"Breath of the Wild": {
"startDate": "20/06/2025",
"lastUpdated": "20/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Korok Seeds"],
"logs": [
{
"date": "20/06/2025",
"hours": 10,
"completion": 15,
"trackables": {
"Korok Seeds": 42
}
}
]
},
"Tears of the Kingdom": {
"startDate": "21/06/2025",
"lastUpdated": "21/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Korok Seeds"],
"logs": [
{
"date": "21/06/2025",
"hours": 12,
"completion": 20,
"trackables": {
"Korok Seeds": 55
}
}
]
},
"Link's Awakening": {
"startDate": "22/06/2025",
"lastUpdated": "22/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Heart Pieces", "Seashells"],
"logs": [
{
"date": "22/06/2025",
"hours": 6,
"completion": 40,
"trackables": {
"Heart Pieces": 8,
"Seashells": 30
}
}
]
},
"Echoes of Wisdom": {
"startDate": "23/06/2025",
"lastUpdated": "23/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Instruments", "Upgrades"],
"logs": [
{
"date": "23/06/2025",
"hours": 3,
"completion": 12,
"trackables": {
"Instruments": 2,
"Upgrades": 1
}
}
]
},
"MarioKart World": {
"startDate": "24/06/2025",
"lastUpdated": "24/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Princess Coins", "P-Switches", "? Blocks", "VR", "3-Star GP"],
"cupTotal": 18,
"logs": [
{
"date": "24/06/2025",
"hours": 5,
"completion": 25,
"trackables": {
"Princess Coins": 80,
"P-Switches": 12,
"? Blocks": 15,
"VR": 1300,
"3-Star GP": 4
}
}
]
},
"MarioKart 8 Deluxe": {
"startDate": "25/06/2025",
"lastUpdated": "25/06/2025",
"lastUpdatedBy": "howlsome",
"trackables": ["Vehicle Unlocks", "Characters", "Tracks", "VR", "3-Star GP"],
"cupTotal": 24,
"logs": [
{
"date": "25/06/2025",
"hours": 9,
"completion": 30,
"trackables": {
"Vehicle Unlocks": 6,
"Characters": 9,
"Tracks": 20,
"VR": 1450,
"3-Star GP": 5
}
}
]
}
}
70 changes: 70 additions & 0 deletions automation/update-gamelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Update Game Log

on:
workflow_dispatch:
inputs:
game:
description: 'Game title'
required: true
type: string
hours:
description: 'Total hours played'
required: true
type: string
completion:
description: 'Completion percentage'
required: true
type: string
trackables:
description: 'Trackables (JSON string)'
required: false
type: string

jobs:
update-log:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- run: npm ci

- name: Run update script
run: |
node scripts/update-progress.js \
"${{ github.event.inputs.game }}" \
"${{ github.event.inputs.hours }}" \
"${{ github.event.inputs.completion }}" \
"${{ github.actor }}" \
'${{ github.event.inputs.trackables || "{}" }}'

- name: Commit changes
run: |
git config user.name "github-actions"
git config user.email "[email protected]"
git checkout -b update-${{ github.run_number }}
git add _data/progress.json
git commit -m "Game log: ${{ github.event.inputs.game }} by ${{ github.actor }}"
git push origin update-${{ github.run_number }}

- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: update-${{ github.run_number }}
title: "Game log update: ${{ github.event.inputs.game }}"
body: |
Update submitted by @${{ github.actor }}
- Hours: ${{ github.event.inputs.hours }}
- Completion: ${{ github.event.inputs.completion }}%

- name: Enable auto-merge
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash
47 changes: 47 additions & 0 deletions automation/update-progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fs = require("fs");
const path = require("path");

const [,, game, hoursStr, completionStr, actor, trackablesRaw] = process.argv;

const hours = parseFloat(hoursStr);
const completion = parseFloat(completionStr);
const date = new Date().toLocaleDateString("en-GB");
const trackablesInput = trackablesRaw ? JSON.parse(trackablesRaw) : {};

const filePath = path.join(__dirname, "../_data/progress.json");
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));

if (!data[game]) {
data[game] = {
startDate: date,
lastUpdated: date,
lastUpdatedBy: actor,
trackables: Object.keys(trackablesInput),
logs: []
};
}

const logs = data[game].logs;
const last = logs[logs.length - 1] || { hours: 0, completion: 0, trackables: {} };

const deltaTrackables = {};
for (const key in trackablesInput) {
const prev = last.trackables?.[key] ?? 0;
deltaTrackables[key] = trackablesInput[key] - prev;
}

logs.push({
date,
hours,
completion,
deltaHours: hours - last.hours,
deltaCompletion: completion - last.completion,
trackables: trackablesInput,
deltaTrackables
});

data[game].lastUpdated = date;
data[game].lastUpdatedBy = actor;

fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
console.log(`✅ ${game} updated by ${actor}`);