From 530f4d9ca6c766c1110385ec670f10b8b10a6862 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 9 Nov 2024 09:16:10 -0800 Subject: [PATCH 1/3] Order challenge headers --- src/pages/Leaderboard.tsx | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/pages/Leaderboard.tsx b/src/pages/Leaderboard.tsx index 9ed54b7e..c9c0f4a1 100644 --- a/src/pages/Leaderboard.tsx +++ b/src/pages/Leaderboard.tsx @@ -286,6 +286,33 @@ class Leaderboard extends React.Component { return anonomizedUsers; }; + + private customSort = (list: string[]): string[] => { + return list.sort((a, b) => { + // Regular expression to extract name, number, and subset + const regex = /^([a-zA-Z]+)(\d+)?([a-zA-Z]*)$/; + + const matchA = regex.exec(a); + const matchB = regex.exec(b); + + if (!matchA || !matchB) return 0; + + const [, nameA, numA, subsetA] = matchA; + const [, nameB, numB, subsetB] = matchB; + + // Compare the names alphabetically + if (nameA !== nameB) return nameA.localeCompare(nameB); + + // Compare the numbers numerically + const numValueA = numA ? parseInt(numA, 10) : 0; + const numValueB = numB ? parseInt(numB, 10) : 0; + + if (numValueA !== numValueB) return numValueA - numValueB; + + // Compare the subsets alphabetically + return subsetA.localeCompare(subsetB); + }); + }; private renderLeaderboard = () => { const users = this.state.users || this.getDefaultUsers(); @@ -295,17 +322,17 @@ class Leaderboard extends React.Component { if (!sortedUsers) return null; const userArray = Object.values(sortedUsers); - const challengeArray = Object.entries(challenges); + const challengeArray = this.customSort(Object.keys(challenges)); return ( Challenges: - {challengeArray.map(([id,challenge]) => ( + {challengeArray.map((id) => ( - {challenge.name['en-US']} + {challenges[id].name['en-US']} ))} @@ -315,8 +342,8 @@ class Leaderboard extends React.Component { {userArray.map((user) => ( {user.name} - {challengeArray.map(([id,challenge]) => { - const userScore = user.scores.find(score => score.name['en-US'] === challenge.name['en-US']); + {challengeArray.map((id) => { + const userScore = user.scores.find(score => score.name['en-US'] === challenges[id].name['en-US']); return ( {userScore?.completed ? ( From 825e8b16244d57e7c8db9c2f104b4f3ae03ed55e Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 9 Nov 2024 10:12:24 -0800 Subject: [PATCH 2/3] Enhance leaderboard user display and anonymization logic --- src/pages/Leaderboard.tsx | 74 +++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/src/pages/Leaderboard.tsx b/src/pages/Leaderboard.tsx index c9c0f4a1..d6351e97 100644 --- a/src/pages/Leaderboard.tsx +++ b/src/pages/Leaderboard.tsx @@ -14,6 +14,7 @@ import tr from '@i18n'; import db from '../db'; +const SELFIDENTIFIER = "My Scores!" ; interface Challenge { name: LocalizedString; @@ -91,6 +92,12 @@ const TableHeaderContainer = styled('div', { width: '50px', // Set the width of the container }); +const UserHeaderContainer = styled('div', { + display: 'inline-block', + whiteSpace: 'nowrap', // Prevent text wrapping + width: '100px', // Set the width of the container +}); + const Table = styled('table', { width: '80%', borderCollapse: 'collapse', @@ -104,10 +111,11 @@ const TableHeader = styled('th', { borderBottom: '2px solid #ddd', padding: '8px', textAlign: 'center', + width: '50px', }); -const StyledTableRow = styled('tr', (props: { self: boolean }) => ({ +const StyledTableRow = styled('tr', (props: { key: string, self: string }) => ({ borderBottom: '1px solid #ddd', - backgroundColor: props.self ? '#555' : '#000', // Highlight the current user + backgroundColor: props.self === SELFIDENTIFIER ? '#555' : '#000', // Highlight the current user })); const TableRow = styled('tr', { borderBottom: '1px solid #ddd', @@ -194,13 +202,12 @@ class Leaderboard extends React.Component { } const currentUserData: User = { id: 'currentUser', - name: 'THIS IS ME', + name: SELFIDENTIFIER, scores: currentUserScores, }; users[currentUserData.id] = currentUserData; - // console.log("Queried:", users, challenges); this.setState({ users, challenges }); return { users, challenges }; @@ -265,25 +272,66 @@ class Leaderboard extends React.Component { return completedChallengesB - completedChallengesA; }); - console.log("Sorted:", userArray); return userArray; }; private anonomizeUsers = (users: Record): Record => { const anonomizedUsers: Record = {}; - const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown', 'black', 'white']; - const elements = ['fire', 'water', 'earth', 'air', 'light', 'dark', 'metal', 'wood', 'ice', 'electricity']; - const animals = ['tiger', 'bear', 'wolf', 'eagle', 'shark', 'whale', 'lion', 'panther', 'cheetah', 'jaguar']; + const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink', 'brown', 'black', 'white', + 'cyan', 'magenta', 'lime', 'teal', 'indigo', 'violet', 'gold', 'silver', 'bronze', 'maroon', 'tan', 'navy', 'aqua']; + const elements = ['fire', 'water', 'earth', 'air', 'light', 'dark', 'metal', 'wood', 'ice', + 'shadow', 'spirit', 'void', 'plasma', 'gravity', 'time', 'space', 'aether', 'chaos', 'order']; + const animals = ['tiger', 'bear', 'wolf', 'eagle', 'shark', 'whale', 'lion', 'panther', 'jaguar', + 'fox', 'owl', 'hawk', 'dolphin', 'rhino', 'hippo', 'giraffe', 'zebra', + 'koala', 'panda', 'leopard', 'lynx', 'bison', 'buffalo', 'camel', + 'raven', 'sparrow', 'swan', 'toucan', 'vulture', 'walrus', 'yak']; + + + + const stringTo32BitInt = (id: string): number => { + const FNV_PRIME = 0x01000193; // 16777619 + let hash = 0x811c9dc5; // FNV offset basis + + for (let i = 0; i < id.length; i++) { + hash ^= id.charCodeAt(i); // XOR with byte value of character + hash = (hash * FNV_PRIME) >>> 0; // Multiply by FNV prime and apply unsigned right shift to keep it 32-bit + } + + return hash >>> 0; // Ensure the result is a positive 32-bit integer + }; Object.values(users).forEach((user) => { + const hash = Math.abs(stringTo32BitInt(user.id)); + const color = colors[hash % colors.length]; + const element = elements[hash % elements.length]; + const animal = animals[hash % animals.length]; + const number = hash % 97; + anonomizedUsers[user.id] = { id: user.id, - name: `${colors[Math.floor(Math.random() * colors.length)]}-${elements[Math.floor(Math.random() * elements.length)]}-${animals[Math.floor(Math.random() * animals.length)]}-${Math.floor(Math.random() * 100)}`, + name: `${color}-${element}-${animal}-${number}`, scores: user.scores }; }); + const nameSet = new Set(); + const duplicateNames: string[] = []; + + Object.values(anonomizedUsers).forEach((user) => { + if (nameSet.has(user.name)) { + duplicateNames.push(user.name); + } else { + nameSet.add(user.name); + } + }); + + const duplicateIds = Object.values(anonomizedUsers).filter(u => duplicateNames.includes(u.name)); + + if (duplicateNames.length > 0) { + console.warn('Duplicate names found after anonymization:', duplicateNames, duplicateIds); + } + return anonomizedUsers; }; @@ -328,7 +376,11 @@ class Leaderboard extends React.Component {
- Challenges: + + + Users + + {challengeArray.map((id) => ( @@ -340,7 +392,7 @@ class Leaderboard extends React.Component { {userArray.map((user) => ( - + {user.name} {challengeArray.map((id) => { const userScore = user.scores.find(score => score.name['en-US'] === challenges[id].name['en-US']); From 83ecb9bed8b992580337676f22d5683b46174759 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 9 Nov 2024 11:30:06 -0800 Subject: [PATCH 3/3] Refactor leaderboard user handling and scoring logic --- src/pages/Leaderboard.tsx | 50 ++++++++++++++----------- static/icons/botguy-bw-trans-32x32.png | Bin 0 -> 1298 bytes 2 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 static/icons/botguy-bw-trans-32x32.png diff --git a/src/pages/Leaderboard.tsx b/src/pages/Leaderboard.tsx index d6351e97..62de3d55 100644 --- a/src/pages/Leaderboard.tsx +++ b/src/pages/Leaderboard.tsx @@ -168,13 +168,14 @@ class Leaderboard extends React.Component { }; } - for (const [userId, challenges] of Object.entries(groupData)) { + for (const [userId, userChallenges] of Object.entries(groupData)) { const user: User = { id: userId, name: userId, scores: [], }; - for (const [challengeId, challenge] of Object.entries(challenges as ChallengeData[])) { + + for (const [challengeId, challenge] of Object.entries(userChallenges as ChallengeData[])) { const challengeCompletion = challenge?.success?.exprStates?.completion ?? false; const score: Score = { @@ -183,6 +184,7 @@ class Leaderboard extends React.Component { }; user.scores.push(score); } + if (!users[userId]) { users[userId] = user; } @@ -190,23 +192,27 @@ class Leaderboard extends React.Component { users = this.anonomizeUsers(users); - const currentUser = userData; - const currentUserScores: Score[] = []; - for (const [challengeId, challenge] of Object.entries(currentUser as ChallengeData[])) { - const challengeCompletion = challenge?.success?.exprStates?.completion ?? false; - const score: Score = { - name: tr(challengeId), - completed: challengeCompletion + for (const [userId, userChallenges] of Object.entries(userData)) { + const user: User = { + id: userId, + name: SELFIDENTIFIER, + scores: [], }; - currentUserScores.push(score); + + for (const [challengeId, challenge] of Object.entries(userChallenges as ChallengeData[])) { + const challengeCompletion = challenge?.success?.exprStates?.completion ?? false; + const score: Score = { + name: tr(challengeId), + completed: challengeCompletion + }; + user.scores.push(score); + } + + if (!users[userId]) { + users[userId] = user; + } } - const currentUserData: User = { - id: 'currentUser', - name: SELFIDENTIFIER, - scores: currentUserScores, - }; - users[currentUserData.id] = currentUserData; this.setState({ users, challenges }); @@ -267,8 +273,8 @@ class Leaderboard extends React.Component { const userArray = Object.values(users); userArray.sort((a, b) => { - const completedChallengesA = a.scores.filter(score => score.completed).length; - const completedChallengesB = b.scores.filter(score => score.completed).length; + const completedChallengesA = a.scores.filter(score => score.completed).length * 100 + a.scores.length; + const completedChallengesB = b.scores.filter(score => score.completed).length * 100 + b.scores.length; return completedChallengesB - completedChallengesA; }); @@ -398,14 +404,16 @@ class Leaderboard extends React.Component { const userScore = user.scores.find(score => score.name['en-US'] === challenges[id].name['en-US']); return ( - {userScore?.completed ? ( + {!userScore && '-'} + {userScore?.completed && ( <> Favicon {/*
Score: {userScore.score ?? '-'}
Time: {userScore.completionTime ?? '-'}
*/} - ) : ( - '-' + )} + {userScore && !userScore.completed && ( + Favicon )}
); diff --git a/static/icons/botguy-bw-trans-32x32.png b/static/icons/botguy-bw-trans-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..87eb80f57f3cf5ec4708a43fb82f75da5a7893bc GIT binary patch literal 1298 zcmV+t1?~EYP)P001Be1^@s6=bY0900001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vG?BLDy{BLR4&KXw2B1dT~VK~zXfrB+Ex zq*oNZH8{^xJ5!33QX<3wQ6eam8?zBL3)cY`9SFD#Zd@A@dXYddWNAb<;t#Ml+91h7 z5w#Qzids08mSf7P9dgd~RY1DFuIkQ#SM`Sb?tJh291A;v_Dh@17BpKiBW5An+uH|yeSK5q<>ixRD`vzjgh)B$)w7?7Nt99dXckmBNE>Fw>6P$(o_U0rf|dMd~^X>V_r@$qpN z_K#3Me;F1p5XoJ($H&KTYXElEj9I_~f9)n&*qjZs=!+8vnJZ)|M%%-s)QnH7VBgWp2iAI;Jd5Ew-4 zTxJ;&mO_v{Y5Dp2ZnLxmTp1c29bGfai1?@_=)Aw0r6u69V1Ivqu31KeWicYSsz6m$ zl{7Ro=-%fdYVTqOG0^`9QH8J=5gW7mJVX_O-X+0cP>YVjP(NBDpMt1DSR*J-9W^yI zT8!*xp8_YkZG1{fN|{+JN=i!3%v5%FchzNnzh4#?7j+L^SQ;A}Wocbvyx^tJE+!!@{P&*$@fO#)UtTMGZf1D!yJId>i(AODHU z_d(gE!oorw=nPwUs+ETwL|-slIq;z%AY{vMTwGjuNdWCM4s4WR;33mJp55HsWPnFK z?{Y93A&_J?DCgmXF$wJL?P)8gmgnc^VIl>ELsRWp+-HnPhh8OjcXx*BId)78C?|nH zK=&Y2Cw4G7r1tb}B*1hjDk_rF(o$(^YSQ4l-EM)u<^B8jhDzIuT5D=p0cD!er!6AQ~oKOIL$;@Do zNr)#^c6N4thLx6&5pX{>HI;|A#LIhhA9;nX!Bef4II$53A5ai7OjD41BO@a}!S{Z> zVFA?7Z^$Aq>1hE3G8yy??o&%>mrm$kW(~DrkO^Fj+D}MB#&jD!!28BsA$)xN!^wX_ zct8vIPirU;c*UP6c*Q#${E}heG8^e3b1rt|x>jXa3#KrEFL=f$Ll++bn5FYB2cKuw z4!&Aw;WoNSfyd)dA_ao^1ym<{9 literal 0 HcmV?d00001