Skip to content

Conversation

@atomicra1n
Copy link

Author name

Author: atomicra1n

About your game

What is your game about?
You are a shepherd who must get all sheep into the pen. However, your herd has been contaminated by radioactive sheep who will try to infect your herd! Get as many sheep inside the pen and make sure that no radioactive sheep enters the pen.

How do you play your game?
Simply move using WASD, you can restart the level with J. Get as many healthy sheep inside the pen as possible. If a radioactive sheep enters the pen, or all of your sheep get infected, you will lose and the level will restart.

Copilot AI review requested due to automatic review settings December 4, 2025 09:45
Copilot finished reviewing on behalf of atomicra1n December 4, 2025 09:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces "Glowy Sheep", a puzzle/simulation game where players shepherd healthy sheep into a pen while avoiding radioactive sheep that can infect the herd. The game features 9 progressive levels with increasing difficulty and various spatial challenges.

Key changes:

  • Complete game implementation with sprite definitions, movement mechanics, and infection system
  • 9 levels with different layouts featuring walls, pens, healthy sheep, and radioactive sheep
  • AI behavior for sheep (pen attraction, player avoidance, random movement) and infection mechanics

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

function getClosestPen(s) {
const allPens = getAll(pen)
let closest = null
let minDst = 999
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The magic number 999 is used as an initial maximum distance value. This should be defined as a named constant like MAX_DISTANCE or INFINITY_DISTANCE to clarify its purpose and make the code more maintainable.

Copilot uses AI. Check for mistakes.
Comment on lines +286 to +309
if (nearestPen && nearestPen.dist < 5) {
moved = moveToTarget(s, nearestPen.tile.x, nearestPen.tile.y)
}
}

if (!moved) {
const distToPlayer = Math.abs(p.x - s.x) + Math.abs(p.y - s.y)

if (distToPlayer < 4) {
let dx = 0; let dy = 0;
if (p.x < s.x) dx = 1; else if (p.x > s.x) dx = -1;
if (p.y < s.y) dy = 1; else if (p.y > s.y) dy = -1;

if (!tryMove(s, dx, dy)) {
if (!tryMove(s, dx, 0)) {
if (!tryMove(s, 0, dy)) {
if (dx !== 0 && !tryMove(s, 0, 1)) tryMove(s, 0, -1);
else if (dy !== 0 && !tryMove(s, 1, 0)) tryMove(s, -1, 0);
}
}
}
}
else {
if (Math.random() < 0.15) {
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The magic numbers 5, 4, and 0.15 appear throughout the code without explanation. These control important game mechanics (pen attraction distance, player influence range, random movement probability) and should be defined as named constants at the top of the file (e.g., PEN_ATTRACTION_DISTANCE = 5, PLAYER_INFLUENCE_RANGE = 4, RANDOM_MOVE_PROBABILITY = 0.15) for easier tuning and better code clarity.

Copilot uses AI. Check for mistakes.
if (dx === 0 && dy === 0) return false;
const destX = s.x + dx;
const destY = s.y + dy;
if (destX < 0 || destX > 15 || destY < 0 || destY > 15) return false;
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The boundary check destX > 15 || destY > 15 assumes a fixed map size of 16x16, but the actual maps in this game are 14x9 (14 columns, 9 rows). This will allow sheep to move into positions that don't exist on the actual map, potentially causing unexpected behavior. The bounds should match the actual map dimensions or use width() and height() methods if available in the Sprig API.

Suggested change
if (destX < 0 || destX > 15 || destY < 0 || destY > 15) return false;
// Map is 14 columns (0-13), 9 rows (0-8)
if (destX < 0 || destX > 13 || destY < 0 || destY > 8) return false;

Copilot uses AI. Check for mistakes.
Comment on lines +375 to +378
const bads = getAll(radSheep)
const goods = getAll(sheep)
goods.forEach(g => {
bads.forEach(b => {
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable names bads and goods are informal and unclear. Consider using more descriptive names like radioactiveSheep and healthySheep to improve code readability.

Suggested change
const bads = getAll(radSheep)
const goods = getAll(sheep)
goods.forEach(g => {
bads.forEach(b => {
const radioactiveSheep = getAll(radSheep)
const healthySheep = getAll(sheep)
healthySheep.forEach(g => {
radioactiveSheep.forEach(b => {

Copilot uses AI. Check for mistakes.
if (!levelActive) return;
levelActive = false
addText(msg, {x: 2, y: 7, color: color`6`})
setTimeout(() => loadLevel(currentLevel), 2000)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using setTimeout in a game loop can cause issues if the player triggers multiple win/loss conditions rapidly. If the player loses twice in quick succession (e.g., by moving quickly), multiple timeouts will be queued, causing the level to reload multiple times. Consider adding a check to prevent multiple simultaneous timeouts or clearing existing timeouts before setting new ones.

Copilot uses AI. Check for mistakes.
targets.forEach(t => t.remove())
addSprite(x, y, radSheep)
addText("INFECTED!", {x: 4, y: 14, color: color`5`})
setTimeout(() => clearText(), 800)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clearText() call in sfx_infect happens after an 800ms delay, but this text could be overwritten by subsequent infection events or win/loss messages before the timeout completes. This creates a race condition where text management becomes unpredictable. Consider managing text state more explicitly or ensuring only one message is active at a time.

Copilot uses AI. Check for mistakes.
Comment on lines +380 to +385
if (dist <= 1) sfx_infect(g.x, g.y);
})
})
}

function sfx_infect(x, y) {
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name sfx_infect is misleading - it suggests this function only plays sound effects, but it actually handles the core infection logic (removing healthy sheep, adding radioactive sheep, and displaying text). Consider renaming to infectSheep or convertToRadioactive to better reflect its actual purpose.

Suggested change
if (dist <= 1) sfx_infect(g.x, g.y);
})
})
}
function sfx_infect(x, y) {
if (dist <= 1) infectSheep(g.x, g.y);
})
})
}
function infectSheep(x, y) {

Copilot uses AI. Check for mistakes.
levelActive = false
currentLevel++
addText("LEVEL CLEAR", {x: 4, y: 7, color: color`3`})
setTimeout(() => loadLevel(currentLevel), 2000)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same setTimeout issue exists here. Multiple rapid wins could queue multiple level transitions. While the levelActive flag helps prevent re-entry, it doesn't prevent multiple timeouts from being queued before the first one executes.

Copilot uses AI. Check for mistakes.
Comment on lines +326 to +336
let minDst = 999

allPens.forEach(p => {
const d = Math.abs(p.x - s.x) + Math.abs(p.y - s.y)
if (d < minDst) {
minDst = d
closest = p
}
})

if (closest) return { tile: closest, dist: minDst }
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable minDst is an unclear abbreviation. Consider using the full word minDistance for better readability.

Suggested change
let minDst = 999
allPens.forEach(p => {
const d = Math.abs(p.x - s.x) + Math.abs(p.y - s.y)
if (d < minDst) {
minDst = d
closest = p
}
})
if (closest) return { tile: closest, dist: minDst }
let minDistance = 999
allPens.forEach(p => {
const d = Math.abs(p.x - s.x) + Math.abs(p.y - s.y)
if (d < minDistance) {
minDistance = d
closest = p
}
})
if (closest) return { tile: closest, dist: minDistance }

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant