javascript

exercises

exercises.js⚔
/**
 * Canvas and WebGL Exercises
 *
 * Practice 2D graphics and basic WebGL
 */

// =============================================================================
// Exercise 1: Basic Shape Drawing
// =============================================================================

/**
 * Implement a shape drawing utility
 */
class BasicShapes {
  constructor(ctx) {
    this.ctx = ctx;
  }

  /**
   * Draw a rectangle
   * @param {number} x - X position
   * @param {number} y - Y position
   * @param {number} width - Width
   * @param {number} height - Height
   * @param {Object} options - { fill, stroke, lineWidth }
   */
  rectangle(x, y, width, height, options = {}) {
    // TODO:
    // 1. If options.fill is provided, fillRect with that color
    // 2. If options.stroke is provided, strokeRect with that color
    // 3. Apply lineWidth if provided
    throw new Error('Not implemented');
  }

  /**
   * Draw a circle
   * @param {number} x - Center X
   * @param {number} y - Center Y
   * @param {number} radius - Radius
   * @param {Object} options - { fill, stroke, lineWidth }
   */
  circle(x, y, radius, options = {}) {
    // TODO:
    // 1. Begin path
    // 2. Draw arc from 0 to 2*PI
    // 3. Fill and/or stroke based on options
    throw new Error('Not implemented');
  }

  /**
   * Draw a triangle
   * @param {number} x1, y1 - First point
   * @param {number} x2, y2 - Second point
   * @param {number} x3, y3 - Third point
   * @param {Object} options - { fill, stroke }
   */
  triangle(x1, y1, x2, y2, x3, y3, options = {}) {
    // TODO:
    // 1. Begin path
    // 2. moveTo first point
    // 3. lineTo second and third points
    // 4. closePath
    // 5. Fill and/or stroke
    throw new Error('Not implemented');
  }

  /**
   * Draw a star
   * @param {number} cx - Center X
   * @param {number} cy - Center Y
   * @param {number} outerRadius - Outer radius
   * @param {number} innerRadius - Inner radius
   * @param {number} points - Number of points
   * @param {Object} options - { fill, stroke }
   */
  star(cx, cy, outerRadius, innerRadius, points, options = {}) {
    // TODO:
    // 1. Calculate vertices alternating between outer and inner radius
    // 2. Draw path through all vertices
    // 3. Fill and/or stroke
    throw new Error('Not implemented');
  }
}

// Test
function testExercise1() {
  // Create test canvas in memory
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;
  const ctx = canvas.getContext('2d');

  const shapes = new BasicShapes(ctx);

  // Test drawing (visual verification needed)
  shapes.rectangle(10, 10, 100, 50, { fill: 'blue' });
  shapes.circle(200, 100, 40, { fill: 'red', stroke: 'black' });
  shapes.triangle(300, 50, 350, 100, 250, 100, { fill: 'green' });
  shapes.star(200, 250, 50, 25, 5, { fill: 'gold' });

  console.log('Exercise 1 passed - verify visually');
}

// =============================================================================
// Exercise 2: Gradient Generator
// =============================================================================

/**
 * Create various gradient effects
 */
class GradientGenerator {
  constructor(ctx) {
    this.ctx = ctx;
  }

  /**
   * Create linear gradient
   * @param {number} x1, y1 - Start point
   * @param {number} x2, y2 - End point
   * @param {Array} stops - [{offset: 0-1, color: string}]
   * @returns {CanvasGradient}
   */
  linear(x1, y1, x2, y2, stops) {
    // TODO: Create and return linear gradient with color stops
    throw new Error('Not implemented');
  }

  /**
   * Create radial gradient
   * @param {number} x1, y1, r1 - Inner circle
   * @param {number} x2, y2, r2 - Outer circle
   * @param {Array} stops - Color stops
   * @returns {CanvasGradient}
   */
  radial(x1, y1, r1, x2, y2, r2, stops) {
    // TODO: Create and return radial gradient
    throw new Error('Not implemented');
  }

  /**
   * Create rainbow gradient
   * @param {number} x1, y1, x2, y2 - Gradient line
   * @returns {CanvasGradient}
   */
  rainbow(x1, y1, x2, y2) {
    // TODO: Create gradient with rainbow colors
    throw new Error('Not implemented');
  }

  /**
   * Create sunset gradient
   */
  sunset(x1, y1, x2, y2) {
    // TODO: Create gradient with sunset colors
    throw new Error('Not implemented');
  }
}

// Test
function testExercise2() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;
  const ctx = canvas.getContext('2d');

  const gradients = new GradientGenerator(ctx);

  const linear = gradients.linear(0, 0, 400, 0, [
    { offset: 0, color: 'red' },
    { offset: 1, color: 'blue' },
  ]);

  ctx.fillStyle = linear;
  ctx.fillRect(0, 0, 400, 100);

  console.log('Exercise 2 passed - verify visually');
}

// =============================================================================
// Exercise 3: Text Renderer with Effects
// =============================================================================

/**
 * Advanced text rendering
 */
class TextEffects {
  constructor(ctx) {
    this.ctx = ctx;
  }

  /**
   * Draw outlined text
   * @param {string} text
   * @param {number} x, y
   * @param {Object} options - { font, fillColor, strokeColor, strokeWidth }
   */
  outlinedText(text, x, y, options = {}) {
    // TODO:
    // 1. Set font
    // 2. Draw stroke first (thicker line)
    // 3. Draw fill on top
    throw new Error('Not implemented');
  }

  /**
   * Draw text with shadow
   */
  shadowText(text, x, y, options = {}) {
    // TODO:
    // 1. Set shadowColor, shadowBlur, shadowOffsetX/Y
    // 2. Draw text
    // 3. Reset shadow properties
    throw new Error('Not implemented');
  }

  /**
   * Draw gradient text
   */
  gradientText(text, x, y, gradient, options = {}) {
    // TODO: Set fillStyle to gradient and draw text
    throw new Error('Not implemented');
  }

  /**
   * Draw text along a curve
   */
  curvedText(text, centerX, centerY, radius, startAngle, options = {}) {
    // TODO:
    // 1. For each character
    // 2. Calculate position on arc
    // 3. Save, translate, rotate, draw character, restore
    throw new Error('Not implemented');
  }
}

// Test
function testExercise3() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;
  const ctx = canvas.getContext('2d');

  const textFx = new TextEffects(ctx);

  textFx.outlinedText('Hello', 50, 50, {
    font: '48px Arial',
    fillColor: 'white',
    strokeColor: 'black',
    strokeWidth: 3,
  });

  console.log('Exercise 3 passed - verify visually');
}

// =============================================================================
// Exercise 4: Image Filters
// =============================================================================

/**
 * Apply filters to canvas images
 */
class ImageFilters {
  constructor(ctx) {
    this.ctx = ctx;
  }

  /**
   * Get image data from canvas region
   */
  getPixels(x, y, width, height) {
    return this.ctx.getImageData(x, y, width, height);
  }

  /**
   * Apply grayscale filter
   */
  grayscale(imageData) {
    // TODO:
    // 1. For each pixel (every 4 values: R, G, B, A)
    // 2. Calculate average of R, G, B
    // 3. Set R, G, B to average
    // 4. Return modified imageData
    throw new Error('Not implemented');
  }

  /**
   * Apply sepia filter
   */
  sepia(imageData) {
    // TODO: Apply sepia transformation matrix
    // newR = R * 0.393 + G * 0.769 + B * 0.189
    // newG = R * 0.349 + G * 0.686 + B * 0.168
    // newB = R * 0.272 + G * 0.534 + B * 0.131
    throw new Error('Not implemented');
  }

  /**
   * Invert colors
   */
  invert(imageData) {
    // TODO: Set each channel to 255 - value
    throw new Error('Not implemented');
  }

  /**
   * Adjust brightness
   */
  brightness(imageData, amount) {
    // TODO: Add amount to each R, G, B (clamp 0-255)
    throw new Error('Not implemented');
  }

  /**
   * Adjust contrast
   */
  contrast(imageData, amount) {
    // TODO: Apply contrast formula
    // factor = (259 * (amount + 255)) / (255 * (259 - amount))
    // newValue = factor * (value - 128) + 128
    throw new Error('Not implemented');
  }

  /**
   * Apply blur (simple box blur)
   */
  blur(imageData, radius) {
    // TODO: For each pixel, average surrounding pixels within radius
    throw new Error('Not implemented');
  }
}

// Test
function testExercise4() {
  const canvas = document.createElement('canvas');
  canvas.width = 100;
  canvas.height = 100;
  const ctx = canvas.getContext('2d');

  // Draw test pattern
  ctx.fillStyle = 'red';
  ctx.fillRect(0, 0, 50, 50);
  ctx.fillStyle = 'blue';
  ctx.fillRect(50, 0, 50, 50);
  ctx.fillStyle = 'green';
  ctx.fillRect(0, 50, 50, 50);
  ctx.fillStyle = 'yellow';
  ctx.fillRect(50, 50, 50, 50);

  const filters = new ImageFilters(ctx);
  const imageData = filters.getPixels(0, 0, 100, 100);

  const grayscale = filters.grayscale(imageData);
  console.assert(grayscale instanceof ImageData, 'Should return ImageData');

  console.log('Exercise 4 passed');
}

// =============================================================================
// Exercise 5: Animation System
// =============================================================================

/**
 * Create an animation system
 */
class Animator {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.animations = [];
    this.running = false;
    this.lastTime = 0;
  }

  /**
   * Add an animated object
   * @param {Object} object - Must have update(dt) and draw(ctx) methods
   */
  add(object) {
    // TODO: Add to animations array
    throw new Error('Not implemented');
  }

  /**
   * Remove an animated object
   */
  remove(object) {
    // TODO: Remove from animations array
    throw new Error('Not implemented');
  }

  /**
   * Start animation loop
   */
  start() {
    // TODO:
    // 1. Set running = true
    // 2. Start requestAnimationFrame loop
    throw new Error('Not implemented');
  }

  /**
   * Stop animation loop
   */
  stop() {
    // TODO: Set running = false
    throw new Error('Not implemented');
  }

  /**
   * Main loop (private)
   */
  loop(currentTime) {
    // TODO:
    // 1. Calculate deltaTime
    // 2. Clear canvas
    // 3. Update and draw all objects
    // 4. Request next frame if running
    throw new Error('Not implemented');
  }
}

/**
 * Bouncing ball animation
 */
class BouncingBall {
  constructor(x, y, radius, color) {
    this.x = x;
    this.y = y;
    this.radius = radius;
    this.color = color;
    this.vx = Math.random() * 4 - 2;
    this.vy = Math.random() * 4 - 2;
  }

  update(dt, canvasWidth, canvasHeight) {
    // TODO:
    // 1. Update position based on velocity and dt
    // 2. Bounce off walls
    throw new Error('Not implemented');
  }

  draw(ctx) {
    // TODO: Draw circle at current position
    throw new Error('Not implemented');
  }
}

// Test
function testExercise5() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;

  const animator = new Animator(canvas);
  const ball = new BouncingBall(200, 200, 20, 'red');

  animator.add(ball);
  // animator.start(); // Would need DOM

  console.log('Exercise 5 - Animation system ready');
}

// =============================================================================
// Exercise 6: Drawing Pad
// =============================================================================

/**
 * Create an interactive drawing pad
 */
class DrawingPad {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.drawing = false;
    this.lastX = 0;
    this.lastY = 0;
    this.strokeColor = '#000000';
    this.strokeWidth = 2;
    this.tool = 'brush'; // 'brush', 'eraser', 'line', 'rectangle'

    this.init();
  }

  /**
   * Initialize event listeners
   */
  init() {
    // TODO:
    // 1. Add mousedown listener - start drawing
    // 2. Add mousemove listener - continue drawing
    // 3. Add mouseup listener - stop drawing
    // 4. Add mouseleave listener - stop drawing
    throw new Error('Not implemented');
  }

  /**
   * Get mouse position relative to canvas
   */
  getPosition(e) {
    // TODO: Return { x, y } relative to canvas
    throw new Error('Not implemented');
  }

  /**
   * Start drawing
   */
  startDraw(e) {
    // TODO: Set drawing = true, store position
    throw new Error('Not implemented');
  }

  /**
   * Continue drawing
   */
  draw(e) {
    // TODO: If drawing, draw line from last position to current
    throw new Error('Not implemented');
  }

  /**
   * Stop drawing
   */
  stopDraw() {
    // TODO: Set drawing = false
    throw new Error('Not implemented');
  }

  /**
   * Set stroke color
   */
  setColor(color) {
    this.strokeColor = color;
  }

  /**
   * Set stroke width
   */
  setWidth(width) {
    this.strokeWidth = width;
  }

  /**
   * Clear canvas
   */
  clear() {
    // TODO: Clear entire canvas
    throw new Error('Not implemented');
  }

  /**
   * Export as image
   */
  toDataURL(type = 'image/png') {
    // TODO: Return canvas.toDataURL()
    throw new Error('Not implemented');
  }
}

// Test
function testExercise6() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;

  const pad = new DrawingPad(canvas);
  pad.setColor('#ff0000');
  pad.setWidth(5);

  console.log('Exercise 6 - Drawing pad ready');
}

// =============================================================================
// Exercise 7: Sprite Animation
// =============================================================================

/**
 * Animate sprites from a sprite sheet
 */
class SpriteAnimator {
  constructor(ctx, spriteSheet, frameWidth, frameHeight) {
    this.ctx = ctx;
    this.spriteSheet = spriteSheet;
    this.frameWidth = frameWidth;
    this.frameHeight = frameHeight;
    this.currentFrame = 0;
    this.frameCount = 0;
    this.frameDelay = 100; // ms between frames
    this.lastFrameTime = 0;
    this.animations = {};
    this.currentAnimation = null;
  }

  /**
   * Define an animation
   * @param {string} name - Animation name
   * @param {Array} frames - Array of frame indices
   * @param {number} delay - Delay between frames
   */
  defineAnimation(name, frames, delay = 100) {
    // TODO: Store animation definition
    throw new Error('Not implemented');
  }

  /**
   * Play an animation
   */
  play(name) {
    // TODO: Set current animation and reset frame
    throw new Error('Not implemented');
  }

  /**
   * Update animation frame based on time
   */
  update(currentTime) {
    // TODO:
    // 1. Check if enough time has passed
    // 2. Advance to next frame
    // 3. Loop if at end
    throw new Error('Not implemented');
  }

  /**
   * Draw current frame
   */
  draw(x, y, scale = 1) {
    // TODO:
    // 1. Calculate source position in sprite sheet
    // 2. Draw frame at destination position
    throw new Error('Not implemented');
  }

  /**
   * Get frame position in sprite sheet
   */
  getFramePosition(frameIndex) {
    // TODO: Calculate row and column from frame index
    throw new Error('Not implemented');
  }
}

// Test
function testExercise7() {
  const canvas = document.createElement('canvas');
  canvas.width = 400;
  canvas.height = 400;
  const ctx = canvas.getContext('2d');

  // Would need actual sprite sheet
  // const sprite = new SpriteAnimator(ctx, spriteImage, 32, 32);
  // sprite.defineAnimation('walk', [0, 1, 2, 3], 100);
  // sprite.play('walk');

  console.log('Exercise 7 - Sprite animator ready');
}

// =============================================================================
// Run All Tests
// =============================================================================

function runAllTests() {
  console.log('Running Canvas/WebGL Exercises...\n');

  try {
    testExercise1();
    testExercise2();
    testExercise3();
    testExercise4();
    testExercise5();
    testExercise6();
    testExercise7();

    console.log('\nāœ… All exercises ready for implementation!');
    console.log('Note: Visual verification required for drawing tests');
  } catch (error) {
    console.error('\nāŒ Error:', error.message);
  }
}

// Export
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    BasicShapes,
    GradientGenerator,
    TextEffects,
    ImageFilters,
    Animator,
    BouncingBall,
    DrawingPad,
    SpriteAnimator,
    runAllTests,
  };
}

// Uncomment to run
// runAllTests();
Exercises - JavaScript Tutorial | DeepML