8e8478e45b
Full-stack multiplayer Shelem (Iranian trick-taking card game) with Socket.IO, JWT auth, bot players, joker mode, and mobile-friendly UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.7 KiB
JavaScript
112 lines
3.7 KiB
JavaScript
'use strict';
|
||
// Generates PNG icons with a Joker card motif on dark green background.
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const zlib = require('zlib');
|
||
|
||
const outDir = path.join(__dirname, 'public', 'icons');
|
||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||
|
||
function writePNG(filePath, size) {
|
||
const channels = 4;
|
||
const row = size * channels;
|
||
const raw = Buffer.alloc(size * row);
|
||
|
||
// Background: dark forest green #133025
|
||
const bgR = 0x13, bgG = 0x30, bgB = 0x25;
|
||
// Gold color for Joker diamond: #f5c518
|
||
const gR = 0xf5, gG = 0xc5, gB = 0x18;
|
||
|
||
const cx = size / 2, cy = size / 2;
|
||
const dh = size * 0.35; // half-height of diamond
|
||
const dw = size * 0.28; // half-width
|
||
|
||
for (let y = 0; y < size; y++) {
|
||
for (let x = 0; x < size; x++) {
|
||
const px = (y * size + x) * channels;
|
||
const nx = x + 0.5, ny = y + 0.5;
|
||
|
||
// Rounded corners
|
||
const cr = size * 0.2;
|
||
const inCorner =
|
||
(nx < cr && ny < cr && Math.hypot(nx - cr, ny - cr) > cr) ||
|
||
(nx > size - cr && ny < cr && Math.hypot(nx - (size - cr), ny - cr) > cr) ||
|
||
(nx < cr && ny > size - cr && Math.hypot(nx - cr, ny - (size - cr)) > cr) ||
|
||
(nx > size - cr && ny > size - cr && Math.hypot(nx - (size - cr), ny - (size - cr)) > cr);
|
||
if (inCorner) { raw[px + 3] = 0; continue; }
|
||
|
||
// Draw a diamond / rhombus shape (Joker card symbol)
|
||
const dx = Math.abs(nx - cx) / dw;
|
||
const dy = Math.abs(ny - cy) / dh;
|
||
const inDiamond = dx + dy <= 1;
|
||
|
||
if (inDiamond) {
|
||
raw[px] = gR;
|
||
raw[px + 1] = gG;
|
||
raw[px + 2] = gB;
|
||
raw[px + 3] = 255;
|
||
} else {
|
||
raw[px] = bgR;
|
||
raw[px + 1] = bgG;
|
||
raw[px + 2] = bgB;
|
||
raw[px + 3] = 255;
|
||
}
|
||
}
|
||
}
|
||
|
||
const chunks = [];
|
||
chunks.push(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
|
||
|
||
function crc32(buf) {
|
||
let c = 0xffffffff;
|
||
const table = crc32.table || (crc32.table = (() => {
|
||
const t = new Uint32Array(256);
|
||
for (let i = 0; i < 256; i++) {
|
||
let v = i;
|
||
for (let k = 0; k < 8; k++) v = v & 1 ? 0xedb88320 ^ (v >>> 1) : v >>> 1;
|
||
t[i] = v;
|
||
}
|
||
return t;
|
||
})());
|
||
for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
||
return (c ^ 0xffffffff) >>> 0;
|
||
}
|
||
|
||
function chunk(type, data) {
|
||
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
|
||
const tp = Buffer.from(type);
|
||
const crc = Buffer.alloc(4);
|
||
crc.writeUInt32BE(crc32(Buffer.concat([tp, data])));
|
||
return Buffer.concat([len, tp, data, crc]);
|
||
}
|
||
|
||
const ihdr = Buffer.alloc(13);
|
||
ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4);
|
||
ihdr[8] = 8; ihdr[9] = 6;
|
||
chunks.push(chunk('IHDR', ihdr));
|
||
|
||
const filtered = Buffer.alloc(size * (row + 1));
|
||
for (let y = 0; y < size; y++) {
|
||
filtered[y * (row + 1)] = 0;
|
||
raw.copy(filtered, y * (row + 1) + 1, y * row, (y + 1) * row);
|
||
}
|
||
chunks.push(chunk('IDAT', zlib.deflateSync(filtered)));
|
||
chunks.push(chunk('IEND', Buffer.alloc(0)));
|
||
|
||
fs.writeFileSync(filePath, Buffer.concat(chunks));
|
||
console.log(`${path.basename(filePath)} written (${size}×${size})`);
|
||
}
|
||
|
||
writePNG(path.join(outDir, 'icon-192.png'), 192);
|
||
writePNG(path.join(outDir, 'icon-512.png'), 512);
|
||
|
||
// SVG icon (scalable, used as favicon)
|
||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||
<rect width="100" height="100" rx="20" fill="#133025"/>
|
||
<polygon points="50,10 80,50 50,90 20,50" fill="#f5c518"/>
|
||
<text x="50" y="56" font-size="22" text-anchor="middle" fill="#133025" font-family="serif" font-weight="bold">J</text>
|
||
</svg>`;
|
||
fs.writeFileSync(path.join(outDir, 'icon.svg'), svg);
|
||
console.log('icon.svg written');
|
||
console.log('Icons generated.');
|