1// Player avatars, drawn rather than uploaded. 2// 3// Every wallet gets a distinct identicon derived from its own address, so a 4// profile picture exists from the first play with nothing to set up. A player 5// can instead pick one of the arcade sprites below. Nothing is uploaded, so 6// there is no storage, no hotlinking and no moderation surface. 7(() => { 8 // FNV-1a: small, stable, and identical everywhere â the avatar must not 9 // change between page loads or between devices. 10 function hash(str) { 11 let h = 0x811c9dc5; 12 for (let i = 0; i < str.length; i++) { 13 h ^= str.charCodeAt(i); 14 h = Math.imul(h, 0x01000193) >>> 0; 15 } 16 return h; 17 } 18 19 const PALETTE = [ 20 '#ff2bd6', '#2bfff2', '#ffe12b', '#3bff9d', '#ff6a3b', '#b13bff', 21 '#3bd6ff', '#7dff3b', '#ff3b3b', '#ffb13b', '#6a8cff', '#ffd27a', 22 ]; 23 24 // 8x8 sprites, one glyph per cabinet flavour. '.' is transparent. 25 const SPRITES = [ 26 { name: 'GHOST', c: '#ff3b3b', p: ['..1111..','.111111.','11011011','11111111','11111111','11111111','11111111','1.11.11.'] }, 27 { name: 'INVADER', c: '#3bff9d', p: ['..1..1..','...11...','..1111..','.11011..','11111111','1.1111.1','1.1..1.1','..11.11.'] }, 28 { name: 'TILE', c: '#ffd27a', p: ['11111111','1......1','1.1111.1','1.1..1.1','1.1..1.1','1.1111.1','1......1','11111111'] }, 29 { name: 'LANDER', c: '#cfcfe8', p: ['...11...','..1111..','.111111.','11111111','.1.11.1.','1.1111.1','1......1','.1....1.'] }, 30 { name: 'BARREL', c: '#ffb13b', p: ['.111111.','11111111','1.1111.1','11111111','1.1111.1','11111111','1.1111.1','.111111.'] }, 31 { name: 'MUSHROOM', c: '#7dff3b', p: ['..1111..','.111111.','11111111','11011011','.111111.','...11...','...11...','..1111..'] }, 32 { name: 'FLIPPER', c: '#6a8cff', p: ['1......1','11....11','.11..11.','..1111..','..1111..','.11..11.','11....11','1......1'] }, 33 { name: 'FROG', c: '#3bff3b', p: ['1.1..1.1','11111111','1.1111.1','11111111','.111111.','1.1..1.1','1......1','11....11'] }, 34 { name: 'PADDLE', c: '#3bd6ff', p: ['........','........','...11...','........','11111111','11111111','........','........'] }, 35 { name: 'CUBE', c: '#ff8c1a', p: ['...11...','..1111..','.111111.','11111111','11111111','.111111.','..1111..','...11...'] }, 36 { name: 'ROCKET', c: '#ff2bd6', p: ['...11...','..1111..','..1111..','.111111.','11111111','1.1111.1','...11...','..1..1..'] }, 37 { name: 'SNAKE', c: '#7dffb0', p: ['1111....','1..1....','1..1111.','1.....1.','1111..1.','...1..1.','...1111.','........'] }, 38 ]; 39 40 // GitHub-style identicon: a 5x5 grid mirrored down the middle so it always 41 // reads as a face-like shape rather than noise. 42 function drawIdenticon(ctx, addr, size) { 43 const h = hash(String(addr)); 44 const fg = PALETTE[h % PALETTE.length]; 45 const bg = '#12122a'; 46 const cell = size / 5; 47 ctx.fillStyle = bg; 48 ctx.fillRect(0, 0, size, size); 49 ctx.fillStyle = fg; 50 for (let x = 0; x < 3; x++) { 51 for (let y = 0; y < 5; y++) { 52 // one bit of the hash per cell, reused across the mirror 53 const bit = (hash(addr + ':' + x + ':' + y) >>> 3) & 1; 54 if (!bit) continue; 55 ctx.fillRect(Math.round(x * cell), Math.round(y * cell), Math.ceil(cell), Math.ceil(cell)); 56 ctx.fillRect(Math.round((4 - x) * cell), Math.round(y * cell), Math.ceil(cell), Math.ceil(cell)); 57 } 58 } 59 } 60 61 function drawSprite(ctx, index, size) { 62 const s = SPRITES[index]; 63 if (!s) return; 64 const cell = size / 8; 65 ctx.fillStyle = '#12122a'; 66 ctx.fillRect(0, 0, size, size); 67 ctx.fillStyle = s.c; 68 for (let y = 0; y < 8; y++) { 69 for (let x = 0; x < 8; x++) { 70 if (s.p[y][x] === '1') { 71 ctx.fillRect(Math.round(x * cell), Math.round(y * cell), Math.ceil(cell), Math.ceil(cell)); 72 } 73 } 74 } 75 } 76 77 // Render into a canvas element. avatar === null means the generated identicon. 78 function render(canvas, addr, avatar) { 79 if (!canvas || !canvas.getContext) return; 80 const ctx = canvas.getContext('2d'); 81 const size = canvas.width; 82 ctx.clearRect(0, 0, size, size); 83 if (avatar === null || avatar === undefined) drawIdenticon(ctx, addr, size); 84 else drawSprite(ctx, Number(avatar), size); 85 } 86 87 // an <img>-able data URL, for places that cannot host a live canvas 88 function dataUrl(addr, avatar, size) { 89 const c = document.createElement('canvas'); 90 c.width = c.height = size || 32; 91 render(c, addr, avatar); 92 return c.toDataURL(); 93 } 94 95 window.ARCADE_AVATAR = { render, dataUrl, SPRITES, count: SPRITES.length }; 96})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.