/** * Configuration object for application-wide settings * Centralizes magic numbers and configuration values * @constant {Object} */ const CONFIG = { canvas: { dpi: 300, width: 8.5 * 300, // 8.5in at 300dpi height: 11 * 300, // 11in at 300dpi }, grid: { rows: 4, columns: 5, topMargin: 100, bottomMargin: 300, patternSize: 400, gutterRatio: 0.1, // 10% of pattern size patternRatio: 0.8 // 80% of space for pattern }, style: { strokeWidth: 4, strokeColor: '#000' } }; /** * Canvas Setup and Configuration * This section initializes a high-resolution canvas optimized for both screen display and printing. */ const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = CONFIG.canvas.width; canvas.height = CONFIG.canvas.height; /** * Utility Functions Module * Pure functions for common operations * @namespace Utils */ const Utils = { /** * Generates a random number within a range * @param {number} min - Minimum value * @param {number} max - Maximum value * @returns {number} */ random: (min, max) => Math.random() * (max - min) + min, /** * Generates a random integer within a range * @param {number} min - Minimum value * @param {number} max - Maximum value * @returns {number} */ randomInt: (min, max) => Math.floor(Utils.random(min, max)), /** * Randomly selects an item from an array * @param {Array} arr - Source array * @returns {*} */ randomChoice: arr => arr[Math.floor(Math.random() * arr.length)], /** * Fisher-Yates shuffle implementation * @param {Array} arr - Array to shuffle * @returns {Array} New shuffled array */ shuffle: arr => [...arr].sort(() => Math.random() - 0.5), /** * Creates a range array of numbers * @param {number} start - Start value * @param {number} end - End value * @returns {Array} */ range: (start, end) => Array.from( { length: end - start }, (_, i) => start + i ) }; /** * Drawing Context Manager * Handles canvas context state management using functional composition * @namespace ContextManager */ const ContextManager = { /** * Executes a drawing operation with saved context state * @param {Function} drawFn - Drawing function to execute * @returns {Function} */ withContext: drawFn => (...args) => { ctx.save(); drawFn(...args); ctx.restore(); }, /** * Applies a translation transformation * @param {number} x - X translation * @param {number} y - Y translation * @returns {Function} */ withTranslation: (x, y) => drawFn => (...args) => { ctx.translate(x, y); drawFn(...args); }, /** * Applies a rotation transformation around a point * @param {number} angle - Rotation angle in radians * @param {number} x - Center X coordinate * @param {number} y - Center Y coordinate * @returns {Function} */ withRotation: (angle, x, y) => drawFn => (...args) => { ctx.translate(x, y); ctx.rotate(angle); ctx.translate(-x, -y); drawFn(...args); } }; /** * Shape Factory Pattern * @namespace Shapes */ const Shapes = { /** * Creates a circle with optional fill * @param {number} x - Center X coordinate * @param {number} y - Center Y coordinate * @param {number} size - Reference size for radius calculation * @param {Object} params - Optional parameters for customization * @param {number} [params.radius] - Optional explicit radius * @param {boolean} [params.fill] - Whether to fill the circle */ circle: (x, y, size, params = {}) => { const radius = params.radius || size/3; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); params.fill ? ctx.fill() : ctx.stroke(); }, line: (x1, y1, x2, y2) => { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); }, triangle: (x, y, size) => { ctx.beginPath(); ctx.moveTo(x, y + size); ctx.lineTo(x + size/2, y); ctx.lineTo(x + size, y + size); ctx.closePath(); ctx.stroke(); }, square: (x, y, size) => { ctx.strokeRect(x, y, size, size); } }; /** * Pattern Generator System * @namespace Patterns */ const Patterns = { /** * Creates a pattern generator with transformation capabilities * @param {Function} patternFn - Base pattern drawing function * @returns {Function} Enhanced pattern generator */ createGenerator: patternFn => { return ContextManager.withContext((x, y, size) => { const rotation = Math.PI/2 * Utils.randomInt(0, 4); if (rotation > 0) { ContextManager.withRotation( rotation, x + size/2, y + size/2 )(patternFn)(x, y, size); } else { patternFn(x, y, size); } }); }, /** * Collection of base pattern implementations * @type {Array} */ types: [ /** * Grid-based pattern strategy * Demonstrates use of nested loops for regular grid generation * @param {number} x - Starting X coordinate * @param {number} y - Starting Y coordinate * @param {number} size - Pattern size */ (x, y, size) => { const spacing = size/3; for(let i = 0; i < 3; i++) { for(let j = 0; j < 3; j++) { if((i + j) % 2 === 0) { // Checkerboard pattern Shapes.circle( x + spacing/2 + i * spacing, y + spacing/2 + j * spacing, spacing/2 ); } } } }, // Nested squares (x, y, size) => { for(let i = 3; i > 0; i--) { const offset = (3 - i) * size/6; const squareSize = size - offset * 2; Shapes.square(x + offset, y + offset, squareSize); } }, // Simple flower pattern (x, y, size) => { const center = size/2; const radius = size/4; // Center circle Shapes.circle(x + center, y + center, size/6); // Petals for(let i = 0; i < 6; i++) { const angle = (i / 6) * Math.PI * 2; const petalX = x + center + Math.cos(angle) * radius; const petalY = y + center + Math.sin(angle) * radius; Shapes.circle(petalX, petalY, size/6); } }, // Triangles in a row (x, y, size) => { const triSize = size/3; for(let i = 0; i < 3; i++) { Shapes.triangle( x + i * triSize, y + (i % 2) * triSize/2, triSize ); } }, // Simple grid of squares (x, y, size) => { const gridSize = size/2; for(let i = 0; i < 2; i++) { for(let j = 0; j < 2; j++) { Shapes.square( x + i * gridSize + size/8, y + j * gridSize + size/8, gridSize * 0.75 ); } } }, // Alternating circles and squares (x, y, size) => { const spacing = size/2; for(let i = 0; i < 2; i++) { for(let j = 0; j < 2; j++) { if((i + j) % 2 === 0) { Shapes.circle( x + spacing/2 + i * spacing, y + spacing/2 + j * spacing, spacing/3 ); } else { Shapes.square( x + i * spacing + spacing/6, y + j * spacing + spacing/6, spacing * 2/3 ); } } } }, // Simple star pattern (x, y, size) => { const center = size/2; // Horizontal and vertical lines Shapes.line(x, y + center, x + size, y + center); Shapes.line(x + center, y, x + center, y + size); // Diagonal lines Shapes.line(x, y, x + size, y + size); Shapes.line(x + size, y, x, y + size); }, // Nested arcs (x, y, size) => { const center = size/2; for(let i = 1; i <= 4; i++) { const radius = (size/2) * (i/4); ctx.beginPath(); ctx.arc(x + center, y + center, radius, 0, Math.PI); ctx.stroke(); } }, // Quarter circles in corners (x, y, size) => { const radius = size/2; // Top left ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI/2); ctx.stroke(); // Top right ctx.beginPath(); ctx.arc(x + size, y, radius, Math.PI/2, Math.PI); ctx.stroke(); // Bottom right ctx.beginPath(); ctx.arc(x + size, y + size, radius, Math.PI, Math.PI * 3/2); ctx.stroke(); // Bottom left ctx.beginPath(); ctx.arc(x, y + size, radius, Math.PI * 3/2, Math.PI * 2); ctx.stroke(); }, // Concentric circles (x, y, size) => { const center = size/2; for(let i = 1; i <= 3; i++) { Shapes.circle( x + center, y + center, size, {radius: (size/2) * (i/3)} ); } }, // Nested diamonds (x, y, size) => { const center = size/2; for(let i = 1; i <= 3; i++) { const offset = (size/2) * (i/3); ctx.beginPath(); ctx.moveTo(x + center, y + center - offset); ctx.lineTo(x + center + offset, y + center); ctx.lineTo(x + center, y + center + offset); ctx.lineTo(x + center - offset, y + center); ctx.closePath(); ctx.stroke(); } }, // Radiating arcs (x, y, size) => { const center = size/2; const radius = size/3; for(let i = 0; i < 4; i++) { const startAngle = (Math.PI/2) * i; ctx.beginPath(); ctx.arc(x + center, y + center, radius, startAngle, startAngle + Math.PI/2); ctx.stroke(); } }, // Stacked semicircles (x, y, size) => { const width = size * 0.8; for(let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc( x + size/2, y + (size/3) * (i + 1), width/2, 0, Math.PI, i % 2 === 0 ); ctx.stroke(); } } ] }; /** * Layout System * Handles grid layout and composition * @namespace Layout */ const Layout = { /** * Calculates layout metrics for the grid * @returns {Object} Layout calculations */ calculateMetrics: () => { const availableHeight = CONFIG.canvas.height - (CONFIG.grid.topMargin + CONFIG.grid.bottomMargin); const totalPatternHeight = CONFIG.grid.rows * CONFIG.grid.patternSize; const totalGapHeight = availableHeight - totalPatternHeight; const rowGap = totalGapHeight / (CONFIG.grid.rows - 1); const totalWidth = CONFIG.grid.patternSize * CONFIG.grid.columns; const xOffset = (CONFIG.canvas.width - totalWidth) / 2; return { rowGap, xOffset }; }, /** * Draws a row of patterns */ drawRow: (xOffset, y, size) => { const gutter = size * CONFIG.grid.gutterRatio; const patternSize = size * CONFIG.grid.patternRatio; const patterns = Utils.shuffle(patternGenerators); Utils.range(0, CONFIG.grid.columns).forEach(i => { const xPos = xOffset + i * size + gutter; const yPos = y + gutter; patterns[i](xPos, yPos, patternSize); }); }, /** * Draws the complete grid */ drawGrid: () => { ctx.clearRect(0, 0, CONFIG.canvas.width, CONFIG.canvas.height); ctx.strokeStyle = CONFIG.style.strokeColor; ctx.lineWidth = CONFIG.style.strokeWidth; const { rowGap, xOffset } = Layout.calculateMetrics(); Utils.range(0, CONFIG.grid.rows).forEach(i => { const y = CONFIG.grid.topMargin + i * (CONFIG.grid.patternSize + rowGap); Layout.drawRow(xOffset, y, CONFIG.grid.patternSize); }); } }; // Generate pattern instances const patternGenerators = Utils.range(0, 10) .map(() => Patterns.createGenerator( Utils.randomChoice(Patterns.types) )); // Initialize and set up interaction Layout.drawGrid(); canvas.addEventListener('click', Layout.drawGrid);