Concept:
For my midterm project, I’m creating a grid version of Color by Number game. I’ve always loved these types of games because they help me relax, focus, and feel super satisfied as the artwork slowly comes to life. My goal is to bring that same experience to users by making a game that’s easy to use, fun, and visually appealing.
The game gives users a color palette, where each color is linked to a letter or number. The image itself is made up of a grid, with each cell labeled to show which color should go there. All users have to do is click on a grid cell, and the color fills in automatically. It’s quite simple for users to use.
Challenging Aspects and Risk Prevention
1. One of the biggest challenges was ensuring precise click detection on the image grid. Since the image itself doesn’t inherently support click interactions, I had to create an invisible grid and lay it on top of the image. This allowed the game to register mouse clicks accurately while keeping the visual presentation clean. However, this was difficult because I had to align the overlay perfectly so that the user wouldn’t see it, but it would still work effectively for detecting clicks and filling in colors.
2. Another challenge was allowing users to select colors from the palette and ensuring the correct color was applied to the chosen grid cell. I had to implement logic that detected which color was clicked on and then stored that color as the “active” selection. The game then needed to apply this color to any grid cell the user clicked until a new color was chosen. The difficulty came in precisely mapping click positions to the correct palette color while keeping the interface responsive and user-friendly.
class ColoringPage { constructor(name, imagePath, rows, cols, palette) { this.name = name; this.img = loadImage(imagePath); this.rows = rows; this.cols = cols; this.cellSize = 600 / this.cols; this.grid = Array.from({ length: this.rows }, () => Array(this.cols).fill(null)); this.palette = palette; this.selectedColor = Object.values(palette)[0].color; } display() { this.drawPalette(); image(this.img, 100, 90, 600, 600); this.drawGrid(); this.drawColoredGrid(); } drawGrid() { stroke(0, 50); noFill() for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } drawColoredGrid() { for (let row = 0; row < this.rows; row++) { for (let col = 0; col < this.cols; col++) { if (this.grid[row][col]) { fill(this.grid[row][col]); rect(100 + col * this.cellSize, 90 + row * this.cellSize, this.cellSize, this.cellSize); } } } } drawPalette() { let keys = Object.keys(this.palette); let x = (width - keys.length * 60) / 2; // Decrease 70 to 60 for less spacing let y = 20; noStroke(); for (let i = 0; i < keys.length; i++) { let colorValue = this.palette[keys[i]].color; fill(colorValue); ellipse(x + i * 60 + 30, y + 25, 50, 50); let c = color(colorValue); let brightnessValue = (red(c) * 0.299 + green(c) * 0.587 + blue(c) * 0.114); // Standard luminance formula fill(brightnessValue < 128 ? 255 : 0); // White text for dark colors, black text for light colors textSize(14); textAlign(CENTER, CENTER); text(this.palette[keys[i]].label, x + i * 60 + 30, y + 25); // Adjusted positioning to match circles } }