diff --git a/automation/game-progress.tsx b/automation/game-progress.tsx new file mode 100644 index 0000000..521bbd8 --- /dev/null +++ b/automation/game-progress.tsx @@ -0,0 +1,73 @@ +import progress from "./progress.json"; + +type LogEntry = { + date: string; + hours: number; + completion: number; + deltaHours?: number; + deltaCompletion?: number; + trackables?: Record; + deltaTrackables?: Record; +}; + +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); + const sortedGames = games.sort(([, a], [, b]) => parseDate(b.lastUpdated).getTime() - parseDate(a.lastUpdated).getTime()); + + return ( +
+

🎮 Game Progress Tracker

+ + {sortedGames.map(([title, info]) => ( +
+

{title}

+

+ Started: {info.startDate}
+ Last Updated: {info.lastUpdated} by {info.lastUpdatedBy} +

+
    + {info.logs.map((entry, i) => ( +
  • + {entry.hours}h, {entry.completion}% + {(entry.deltaHours || entry.deltaCompletion) && ( + <> (+{entry.deltaHours ?? 0}h, +{entry.deltaCompletion ?? 0}%) + )} + {" — "} + {entry.date} + {entry.trackables && ( +
      + {Object.entries(entry.trackables).map(([item, value]) => { + const showTotal = item === "3-Star GP" && info.cupTotal; + return ( +
    • + {item}: {value}{showTotal ? `/${info.cupTotal}` : ""} + {entry.deltaTrackables?.[item] !== undefined && ( + <> (+{entry.deltaTrackables[item]}) + )} +
    • + ); + })} +
    + )} +
  • + ))} +
+
+ ))} +
+ ); +} diff --git a/automation/progress.json b/automation/progress.json new file mode 100644 index 0000000..8631de2 --- /dev/null +++ b/automation/progress.json @@ -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 + } + } + ] + } +} diff --git a/automation/update-gamelog.yml b/automation/update-gamelog.yml new file mode 100644 index 0000000..fa80050 --- /dev/null +++ b/automation/update-gamelog.yml @@ -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 "actions@github.com" + 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 diff --git a/automation/update-progress.js b/automation/update-progress.js new file mode 100644 index 0000000..449fb3f --- /dev/null +++ b/automation/update-progress.js @@ -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}`);