repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
Robin-hood Game
stars
latest
clone command
git clone gitlawb://did:key:z6MkwMR9...adEc/robin-hood-gamegit clone gitlawb://did:key:z6MkwMR9.../robin-hood-gamec8777c8csync from playground5h ago| #1 | import { useRef, useEffect } from 'react'; |
| #2 | import { GAME_SIZE } from './game/constants'; |
| #3 | import type { LeaderboardEntry } from './game/types'; |
| #4 | import { loadLeaderboard } from './game/storage'; |
| #5 | import { playClick } from './game/sound'; |
| #6 | |
| #7 | interface LeaderboardProps { |
| #8 | onBack: () => void; |
| #9 | } |
| #10 | |
| #11 | export default function Leaderboard({ onBack }: LeaderboardProps) { |
| #12 | const canvasRef = useRef<HTMLCanvasElement>(null); |
| #13 | |
| #14 | useEffect(() => { |
| #15 | const canvas = canvasRef.current; |
| #16 | if (!canvas) return; |
| #17 | const ctx = canvas.getContext('2d'); |
| #18 | if (!ctx) return; |
| #19 | |
| #20 | const entries = loadLeaderboard(); |
| #21 | |
| #22 | // Background |
| #23 | ctx.fillStyle = '#0d1a0d'; |
| #24 | ctx.fillRect(0, 0, GAME_SIZE, GAME_SIZE); |
| #25 | |
| #26 | // Title |
| #27 | ctx.fillStyle = '#DAA520'; |
| #28 | ctx.font = 'bold 24px monospace'; |
| #29 | ctx.textAlign = 'center'; |
| #30 | ctx.fillText('LEADERBOARD', GAME_SIZE / 2, 40); |
| #31 | |
| #32 | // Decorative line |
| #33 | ctx.fillStyle = '#3a5a30'; |
| #34 | ctx.fillRect(40, 50, GAME_SIZE - 80, 2); |
| #35 | |
| #36 | if (entries.length === 0) { |
| #37 | ctx.fillStyle = '#666'; |
| #38 | ctx.font = '14px monospace'; |
| #39 | ctx.fillText('No scores yet!', GAME_SIZE / 2, 120); |
| #40 | ctx.fillText('Play a round to set', GAME_SIZE / 2, 145); |
| #41 | ctx.fillText('the first record.', GAME_SIZE / 2, 165); |
| #42 | } else { |
| #43 | // Header |
| #44 | ctx.fillStyle = '#888'; |
| #45 | ctx.font = 'bold 11px monospace'; |
| #46 | ctx.textAlign = 'left'; |
| #47 | ctx.fillText('#', 30, 75); |
| #48 | ctx.fillText('Name', 55, 75); |
| #49 | ctx.textAlign = 'right'; |
| #50 | ctx.fillText('Score', GAME_SIZE - 60, 75); |
| #51 | ctx.fillText('Coins', GAME_SIZE - 15, 75); |
| #52 | |
| #53 | ctx.fillStyle = '#3a5a30'; |
| #54 | ctx.fillRect(30, 80, GAME_SIZE - 60, 1); |
| #55 | |
| #56 | // Entries |
| #57 | const visible = entries.slice(0, 10); |
| #58 | visible.forEach((entry: LeaderboardEntry, i: number) => { |
| #59 | const y = 100 + i * 28; |
| #60 | const isTop3 = i < 3; |
| #61 | const colors = ['#FFD700', '#C0C0C0', '#CD7F32']; |
| #62 | const nameColor = isTop3 ? colors[i] : '#ccc'; |
| #63 | |
| #64 | ctx.fillStyle = isTop3 ? 'rgba(255,215,0,0.06)' : 'transparent'; |
| #65 | ctx.fillRect(30, y - 12, GAME_SIZE - 60, 26); |
| #66 | |
| #67 | ctx.fillStyle = nameColor; |
| #68 | ctx.font = 'bold 12px monospace'; |
| #69 | ctx.textAlign = 'left'; |
| #70 | ctx.fillText(`${i + 1}.`, 30, y); |
| #71 | |
| #72 | ctx.font = '12px monospace'; |
| #73 | ctx.fillText(entry.name.slice(0, 12), 55, y); |
| #74 | |
| #75 | ctx.textAlign = 'right'; |
| #76 | ctx.fillStyle = '#fff'; |
| #77 | ctx.fillText(`${entry.score}`, GAME_SIZE - 60, y); |
| #78 | |
| #79 | ctx.fillStyle = '#DAA520'; |
| #80 | ctx.fillText(`${entry.coins}`, GAME_SIZE - 15, y); |
| #81 | }); |
| #82 | } |
| #83 | |
| #84 | // Back button |
| #85 | ctx.fillStyle = '#1a3a1a'; |
| #86 | ctx.fillRect(GAME_SIZE / 2 - 60, GAME_SIZE - 55, 120, 34); |
| #87 | ctx.strokeStyle = '#3a5a30'; |
| #88 | ctx.lineWidth = 2; |
| #89 | ctx.strokeRect(GAME_SIZE / 2 - 60, GAME_SIZE - 55, 120, 34); |
| #90 | ctx.fillStyle = '#ccc'; |
| #91 | ctx.font = 'bold 14px monospace'; |
| #92 | ctx.textAlign = 'center'; |
| #93 | ctx.fillText('Back', GAME_SIZE / 2, GAME_SIZE - 33); |
| #94 | }, []); |
| #95 | |
| #96 | const handleClick = (e: React.PointerEvent) => { |
| #97 | const rect = (e.target as HTMLElement).getBoundingClientRect(); |
| #98 | const scaleY = GAME_SIZE / rect.height; |
| #99 | const y = (e.clientY - rect.top) * scaleY; |
| #100 | if (y >= GAME_SIZE - 55 && y <= GAME_SIZE - 21) { |
| #101 | playClick(); |
| #102 | onBack(); |
| #103 | } |
| #104 | }; |
| #105 | |
| #106 | return ( |
| #107 | <canvas |
| #108 | ref={canvasRef} |
| #109 | width={GAME_SIZE} |
| #110 | height={GAME_SIZE} |
| #111 | onPointerDown={handleClick} |
| #112 | style={{ |
| #113 | width: 'min(100vw, 100vh)', |
| #114 | height: 'min(100vw, 100vh)', |
| #115 | maxWidth: '100%', |
| #116 | maxHeight: '100%', |
| #117 | imageRendering: 'pixelated', |
| #118 | cursor: 'pointer', |
| #119 | touchAction: 'none', |
| #120 | }} |
| #121 | /> |
| #122 | ); |
| #123 | } |
| #124 |