Team Ai
Apppublic

broadfield-dev/stack-overflow-game

sourceHugging Faceupdated 4mo agoView on Hugging Face
0likes
SpriteBlockRenderer.js237 linesDownload Raw Back to utils
1import { BLOCK_SIZE } from '../constants.js';2 3/**4 * Renders Tetris blocks using sprite sheet with color palettes from backdrops5 */6export default class SpriteBlockRenderer {7  /**8   * Create a block texture from sprite sheet with palette colors9   * @param {Phaser.Scene} scene - The Phaser scene10   * @param {number[]} colorPalette - Array of 7 colors extracted from backdrop11   * @param {number} level - Current level (1-10) determines which sprite to use12   * @param {string} key - Texture key to create13   * @param {number} colorIndex - Which color from palette to use (0-6)14   */15  static createBlockTexture(scene, colorPalette, level, key, colorIndex) {16    const canvas = document.createElement('canvas');17    const ctx = canvas.getContext('2d');18    ctx.imageSmoothingEnabled = false;19    canvas.width = BLOCK_SIZE;20    canvas.height = BLOCK_SIZE;21 22    // Get the color to use23    const color = colorPalette[colorIndex % colorPalette.length];24    const r = (color >> 16) & 0xFF;25    const g = (color >> 8) & 0xFF;26    const b = color & 0xFF;27 28    // Get the sprite sheet and extract pattern29    const spriteSheet = scene.textures.get('blocks-spritesheet').getSourceImage();30    const spriteX = (level - 1) * BLOCK_SIZE;31 32    const tempCanvas = document.createElement('canvas');33    const tempCtx = tempCanvas.getContext('2d');34    tempCtx.imageSmoothingEnabled = false;35    tempCanvas.width = spriteSheet.width;36    tempCanvas.height = spriteSheet.height;37    tempCtx.drawImage(spriteSheet, 0, 0);38 39    const spriteData = tempCtx.getImageData(spriteX, 0, BLOCK_SIZE, BLOCK_SIZE);40    const pixels = spriteData.data;41 42    // Create output image data43    const outputData = ctx.createImageData(BLOCK_SIZE, BLOCK_SIZE);44    const output = outputData.data;45 46    // Colorize: use grayscale brightness to modulate the base color47    // Grayscale values create depth (lighter/darker variations)48    for (let i = 0; i < pixels.length; i += 4) {49      const alpha = pixels[i + 3];50 51      if (alpha > 0) {52        // Get grayscale brightness (0-255)53        const brightness = pixels[i]; // R channel (grayscale, so R=G=B)54 55        // Normalize brightness to a multiplier (0.5 to 1.5)56        // 128 (50% gray) = 1.0x (base color)57        // 0 (black) = 0.5x (darkest)58        // 255 (white) = 1.5x (lightest)59        const multiplier = 0.5 + (brightness / 255) * 1.0;60 61        // Apply brightness multiplier to base color62        output[i] = Math.min(255, Math.floor(r * multiplier));63        output[i + 1] = Math.min(255, Math.floor(g * multiplier));64        output[i + 2] = Math.min(255, Math.floor(b * multiplier));65        output[i + 3] = 255;66      } else {67        // Transparent pixel68        output[i] = 0;69        output[i + 1] = 0;70        output[i + 2] = 0;71        output[i + 3] = 0;72      }73    }74 75    ctx.putImageData(outputData, 0, 0);76 77    // Create texture and set nearest neighbor78    const texture = scene.textures.addCanvas(key, canvas);79    texture.setFilter(Phaser.Textures.FilterMode.NEAREST);80  }81 82  /**83   * Create a crush animation frame texture84   * @param {Phaser.Scene} scene - The Phaser scene85   * @param {number} color - The color to apply86   * @param {number} frameIndex - Which frame (0-4)87   * @param {string} key - Texture key to create88   */89  static createCrushTexture(scene, color, frameIndex, key) {90    // Check if texture already exists91    if (scene.textures.exists(key)) {92      return;93    }94 95    const canvas = document.createElement('canvas');96    const ctx = canvas.getContext('2d');97    ctx.imageSmoothingEnabled = false;98    canvas.width = BLOCK_SIZE;99    canvas.height = BLOCK_SIZE;100 101    const r = (color >> 16) & 0xFF;102    const g = (color >> 8) & 0xFF;103    const b = color & 0xFF;104 105    // Get the crush sprite sheet106    const spriteSheet = scene.textures.get('crush-spritesheet').getSourceImage();107    const spriteX = frameIndex * BLOCK_SIZE;108 109    const tempCanvas = document.createElement('canvas');110    const tempCtx = tempCanvas.getContext('2d');111    tempCtx.imageSmoothingEnabled = false;112    tempCanvas.width = spriteSheet.width;113    tempCanvas.height = spriteSheet.height;114    tempCtx.drawImage(spriteSheet, 0, 0);115 116    const spriteData = tempCtx.getImageData(spriteX, 0, BLOCK_SIZE, BLOCK_SIZE);117    const pixels = spriteData.data;118 119    const outputData = ctx.createImageData(BLOCK_SIZE, BLOCK_SIZE);120    const output = outputData.data;121 122    // Apply grayscale brightness to color (darker = more visible, lighter/white = transparent)123    for (let i = 0; i < pixels.length; i += 4) {124      const brightness = pixels[i]; // Grayscale R channel125      const alpha = pixels[i + 3];126 127      // Light pixels or transparent become fully transparent128      if (brightness >= 200 || alpha === 0) {129        output[i] = 0;130        output[i + 1] = 0;131        output[i + 2] = 0;132        output[i + 3] = 0;133      } else {134        // Darker pixels get colored - use brightness to modulate color intensity135        // Darker sprite pixels = darker colored blocks136        const multiplier = 0.3 + (brightness / 255) * 0.9;137        output[i] = Math.min(255, Math.floor(r * multiplier));138        output[i + 1] = Math.min(255, Math.floor(g * multiplier));139        output[i + 2] = Math.min(255, Math.floor(b * multiplier));140        output[i + 3] = 255;141      }142    }143 144    ctx.putImageData(outputData, 0, 0);145 146    const texture = scene.textures.addCanvas(key, canvas);147    if (texture) {148      texture.setFilter(Phaser.Textures.FilterMode.NEAREST);149    }150  }151 152  /**153   * Subtly enhance colors with 20% extra contrast154   * @param {number[]} palette - Original palette from backdrop155   * @returns {number[]} Enhanced palette with subtle contrast boost156   */157  static enhancePalette(palette) {158    const enhanced = [];159 160    for (let i = 0; i < palette.length; i++) {161      let color = palette[i];162      let r = (color >> 16) & 0xFF;163      let g = (color >> 8) & 0xFF;164      let b = color & 0xFF;165 166      // Add 20% contrast: push values away from middle gray (128)167      const contrastFactor = 0.2;168      r = Math.min(255, Math.max(0, Math.floor(128 + (r - 128) * (1 + contrastFactor))));169      g = Math.min(255, Math.max(0, Math.floor(128 + (g - 128) * (1 + contrastFactor))));170      b = Math.min(255, Math.max(0, Math.floor(128 + (b - 128) * (1 + contrastFactor))));171 172      enhanced.push((r << 16) | (g << 8) | b);173    }174 175    return enhanced;176  }177 178  /**179   * Ensure colors in palette are distinct from each other180   * @param {number[]} palette - Color palette181   * @returns {number[]} Palette with distinct colors182   */183  static ensureDistinctColors(palette) {184    const result = [palette[0]];185    186    for (let i = 1; i < palette.length; i++) {187      let color = palette[i];188      let attempts = 0;189      190      // Check if too similar to existing colors191      while (attempts < 10) {192        let tooSimilar = false;193        194        for (let j = 0; j < result.length; j++) {195          if (this.colorDistance(color, result[j]) < 100) {196            tooSimilar = true;197            break;198          }199        }200        201        if (!tooSimilar) break;202        203        // Adjust color204        let r = (color >> 16) & 0xFF;205        let g = (color >> 8) & 0xFF;206        let b = color & 0xFF;207        208        r = (r + 60) % 256;209        g = (g + 40) % 256;210        b = (b + 80) % 256;211        212        color = (r << 16) | (g << 8) | b;213        attempts++;214      }215      216      result.push(color);217    }218    219    return result;220  }221 222  /**223   * Calculate color distance224   */225  static colorDistance(c1, c2) {226    const r1 = (c1 >> 16) & 0xFF;227    const g1 = (c1 >> 8) & 0xFF;228    const b1 = c1 & 0xFF;229    const r2 = (c2 >> 16) & 0xFF;230    const g2 = (c2 >> 8) & 0xFF;231    const b2 = c2 & 0xFF;232    233    return Math.sqrt((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2);234  }235}236 237