diff --git a/index.html b/index.html index b7f91b1..b4f239a 100644 --- a/index.html +++ b/index.html @@ -594,21 +594,58 @@ return [r, g, b, a]; } + +/* +>>>> NEW Prompt: +Rewrite this function so that I can pass in multiple colors in the color argument, note how 'color' is already an array. +So, valid arguments would be: +[ 0xff, 0xff, 0xff ] or [[ 0xff, 0xff, 0xff ], [0xee, 0xee, 0xee]], ... + +Use getPixelColor(imageData, x, y) to get the color of the pixel in the data. + function pixelIsColor(imageData, x, y, color, debug) { x = Math.round(x); y = Math.round(y); const [r, g, b, alpha] = getPixelColor(imageData, x, y); + // if(r === undefined) { + // console.warn("Should not happen:", "lemming:", debug, "len:", lemmings.length, "image:", imageData, r, g, b, alpha, "x:"+x, "y:"+y, "comparing:"+color, "index:"+Math.floor((y * width + x) * 4)); + // debug.draw(); + // throw "This should not happen" + // } + + return r === color[0] && g === color[1] && b === color[2]; +} + +>>>>> HUMAN CHEAT: Arrrgh. After 5-6 attempts, I give up. I rewrote the function myself! It kept omitting, e.g.: if(!Array.isArray(color[0])) { color = [ color ]; } +*/ +function pixelIsColor(imageData, x, y, color, debug) { + x = Math.round(x); + y = Math.round(y); + + const [r, g, b, alpha] = getPixelColor(imageData, x, y); + if(r === undefined) { console.warn("Should not happen:", "lemming:", debug, "len:", lemmings.length, "image:", imageData, r, g, b, alpha, "x:"+x, "y:"+y, "comparing:"+color, "index:"+Math.floor((y * width + x) * 4)); - debug.draw(); - throw "This should not happen" + throw "This should not happen"; } - return r === color[0] && g === color[1] && b === color[2]; + if(!Array.isArray(color[0])) { + color = [ color ]; + } + + for(let i = 0; i < color.length; i++) { + if(r === color[i][0] && g === color[i][1] && b === color[i][2]) { + return true; + } + } + + return false; } + + /* >>> prompt Given this example: @@ -744,6 +781,20 @@ */ +function isColorOneOf(needle, haystack) { + for (let i = 0; i < haystack.length; i++) { + const color = haystack[i]; + + if (color[0] === needle[0] && + color[1] === needle[1] && + color[2] === needle[2]) { + return true; + } + } + + return false; +} + const blueBox = [0x00, 0x00, 0xff]; const greenBox = [0x00, 0xff, 0x00]; const GRAVITY = 0.03; // Adjust this until falling looks good @@ -753,14 +804,8 @@ const rockColorBytes = [0x88, 0x88, 0x88]; // [136, 136, 136]; const dirtColorBytes = [0x4a, 0x2e, 0x00]; // [74, 46, 0]; +const terrainColorBytes = [ rockColorBytes, dirtColorBytes ]; -/* ->>>> new prompt to make changes to Lemming.update() regarding bounds-checking -Rewrite this whole method to make sure we do not attempt to check pixels that are out of bounds of the canvas: -Make sure to check bounds so that e.g. getPixelIndex() does not attempt to get out of bounds -pixels either. -(and then the full update() method was passed in) -*/ let lastLemmingId = 0; @@ -779,6 +824,13 @@ onGround: false, isDead: false, action: null, + /* + >>>> new prompt +Rewrite this whole method to make sure we do not attempt to check pixels that are out of bounds of the canvas: +Make sure to check bounds so that e.g. getPixelIndex() does not attempt to get out of bounds +pixels either. +(and then the full update() method was passed in) + */ update: function() { if (this.y >= canvas.height - (this.height + this.velY + 1)) { this.isDead = true; @@ -787,24 +839,56 @@ // Check if ground is under us or not let isGroundUnderneath = - pixelIsColor( - oldImgData, - this.x + this.width / 2, - this.y + this.height + 1, - dirtColorBytes - ) || - pixelIsColor( - oldImgData, - this.x + this.width / 2, - this.y + this.height + 1, - rockColorBytes - ); + pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, dirtColorBytes) || + pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, rockColorBytes); let heightAdjustment = 0; + let canClimb = false; + + if (this.action === "Climber") { + // Determine if there is a climbable surface to our left and/or right + const climbLeft = pixelIsColor(oldImgData, this.x - 1, this.y, terrainColorBytes); + const climbRight = pixelIsColor(oldImgData, this.x + this.width + 1, this.y, terrainColorBytes); + + // If we can climb either way, we choose the steeper slope + if (climbLeft || climbRight) { + let ascentLength = 0; + let maxAscentLength = 0; + let stepInDirection; + + console.log("climber"); + + if (climbLeft) { + stepInDirection = -1; // moving left + } else if (climbRight) { + stepInDirection = 1; // moving right + } - if (isGroundUnderneath) { - let distanceToGround = -(this.height / 4); + while ( + pixelIsColor( + oldImgData, + this.x + (this.width/2) + (ascentLength * stepInDirection), + this.y - (ascentLength + 1), + terrainColorBytes + ) + ) { + ascentLength++; + } + maxAscentLength = (this.height / 2) + 1; // don't climb higher than half of the sprite's height + + if (ascentLength <= maxAscentLength) { + canClimb = true; + + // Adjust height and x position accordingly + heightAdjustment = ascentLength + 0.1; + this.x += stepInDirection * 3; + } + } + } + + if (!canClimb && isGroundUnderneath) { + let distanceToGround = -(this.height / 4); while ( this.y + distanceToGround + 1 >= 0 && this.y + distanceToGround + 1 < canvas.height && @@ -824,36 +908,20 @@ distanceToGround++; } - if (distanceToGround !== 0) - heightAdjustment = - this.height - distanceToGround + 0.1; // subtract half the sprite's height + if (distanceToGround !== 0) { + heightAdjustment = this.height - distanceToGround + 0.1; // subtract half the sprite's height + } } // Check if we hit a wall on the x axis - const hitWallOnLeft = - pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, dirtColorBytes) || - pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, rockColorBytes); + const hitWallOnLeft = pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, dirtColorBytes) || + pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, rockColorBytes); const hitWallOnRight = - pixelIsColor( - oldImgData, - this.x + this.width + 1, - this.y + this.height / 2, - dirtColorBytes - ) || - pixelIsColor( - oldImgData, - this.x + this.width + 1, - this.y + this.height / 2, - rockColorBytes - ); + pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, dirtColorBytes) || + pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, rockColorBytes); // Check if we've fallen in water - const isWaterBelow = pixelIsColor( - oldImgData, - this.x + this.width / 2, - this.y + this.height + 1, - waterColorBytes - ); + const isWaterBelow = pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, waterColorBytes); // Update velocity according to the collision rules if (!this.onGround) { @@ -863,7 +931,21 @@ this.velY += GRAVITY; } - if (isGroundUnderneath) { + if (canClimb) { + this.y -= heightAdjustment; // move sprite up + + // Don't fall off the edge of the climbable surface + if ( + !pixelIsColor( + oldImgData, + this.x + (this.width/2), + this.y + this.height + 1, + terrainColorBytes + ) + ) { + canClimb = false; + } + } else if (isGroundUnderneath) { // console.log(this.velY); if (this.velY > this.deadlyVelY) { // splat @@ -910,7 +992,7 @@ } // Apply height adjustment - if (heightAdjustment !== 0) { + if (heightAdjustment !== 0 && !canClimb) { if (this.y - heightAdjustment >= 0) { this.y -= heightAdjustment; // move sprite up } else { @@ -926,6 +1008,8 @@ }, + + draw: function() { // Draw the new lemming position ctx.fillStyle = 'rgb(' + blueBox.join(',') + ')'; @@ -961,6 +1045,13 @@ newLemming.y = 0; // newLemming.y = Math.random() * canvas.height / 4; newLemming.velX = newLemming.maxVelX; // Walk to the right by default + + //if(newLemming.id % 2) { + if(newLemming.id === 1) { + console.log("Making new lemming a climber: ", newLemming); + newLemming.action = "Climber"; + } + lemmings.push(newLemming); } @@ -1440,15 +1531,62 @@ /* >>> Resetting chat history -- it keeps getting confused by the Floater action. So will prefix with more context. -We are making a game with lemmings, a specific type of lemming is a Climber. What would we need to add to the lemming's update function below (see original below). -The Climber action should allow a Lemming to climb steep dirt or rock walls (compare their colors against the canvas data), and it will continue climbing as far as it can until it can walk along the X axis again. -It can only climb upwards. +We are making a game with lemmings. A specific type of lemming is a Climber. A lemming that is a Climber +will climb up (not down) steep dirt or rock obstacles (compare their colors against the canvas data, always +use oldImgData for that -- not ctx), it will walk like any other lemming, the only difference is that it +can also ascend steeper obstacles. + +When it is climbing these steep obstacles, move it upwards along Y axis, but make sure the lemming move +along the pixels it is climbing on (that is, on either side of the lemming). + +If a climber is climbing, you should probably not do the heightAdjustment as that only applies if the lemming +is walking on the ground. + +As a hint, since you are struggling, you want to put the functionality for this inside if(this.action === "Climber") { ... }, +in the appropriate place in the update function. + +To illustrate with ASCII: + + |----------------G +S o c | +-------------------------| + +"S" is where the lemmings start walking +"-" is a platform (rock or dirt) +"|" is either rock or dirt too, but it's very steep and would stop a normal lemming from going further +"o" is a normal walking lemming +"c" is a Climber +"G" is the goal where the lemmings want to go + +The Climber (c) in the above illustration would be able to reach the goal, but the normal lemming (o) would not, +it would simply walk back and forth between the steep obstacle and S. + +The following functionality is declared implemented: +function isColorOneOf(needle, haystack) { + for (let i = 0; i < haystack.length; i++) { + const color = haystack[i]; + + if (color[0] === needle[0] && + color[1] === needle[1] && + color[2] === needle[2]) { + return true; + } + } + + return false; +} + +const terrainColorBytes = [ + [102, 102, 102], // rock color + [150, 75, 0] // dirt color +]; -You probably need to figure out some trickery here to determine whether the Lemming ran into something it should climb... +Don't use pixelIsColor to check if pixels are climbable surfaces. Use isColorOneOf(needle, haystack) instead, where +needle is the pixel's color, and haystack is an array of several colors (e.g. the ones in terrainColorBytes). -The following is declared already: const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); + const Lemming = { id: -49152, age: 0, @@ -1465,106 +1603,153 @@ isDead: false, action: null, update: function() { - if(this.y >= (canvas.height - (this.height+this.velY+1))) { - this.isDead = true; - return; - } + if (this.y >= canvas.height - (this.height + this.velY + 1)) { + this.isDead = true; + return; + } - // Check if ground is under us or not - let isGroundUnderneath = pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, dirtColorBytes) || - pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, rockColorBytes); + // Check if ground is under us or not + let isGroundUnderneath = + pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, dirtColorBytes) || + pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, rockColorBytes); - let heightAdjustment = 0; + let heightAdjustment = 0; - if (isGroundUnderneath) { - let distanceToGround = -(this.height/4); + if (isGroundUnderneath) { + let distanceToGround = -(this.height / 4); - // TODO need safety guards for canvas boundary and probably a sanity check to not iterate more than N pixels (N = height?) - while (!pixelIsColor(oldImgData, this.x + this.width / 2, this.y + distanceToGround + 1, dirtColorBytes) - && !pixelIsColor(oldImgData, this.x + this.width / 2, this.y + distanceToGround + 1, rockColorBytes)) { - distanceToGround++; - } - if(distanceToGround !== 0) - heightAdjustment = (this.height) - distanceToGround + 0.1; // subtract half the sprite's height + while ( + this.y + distanceToGround + 1 >= 0 && + this.y + distanceToGround + 1 < canvas.height && + !pixelIsColor( + oldImgData, + this.x + this.width / 2, + this.y + distanceToGround + 1, + dirtColorBytes + ) && + !pixelIsColor( + oldImgData, + this.x + this.width / 2, + this.y + distanceToGround + 1, + rockColorBytes + ) + ) { + distanceToGround++; } - // Check if we hit a wall on the x axis - const hitWallOnLeft = - pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, dirtColorBytes) || - pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, rockColorBytes); - const hitWallOnRight = - pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, dirtColorBytes) || - pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, rockColorBytes); + if (distanceToGround !== 0) { + heightAdjustment = this.height - distanceToGround + 0.1; // subtract half the sprite's height + } + } - // Check if we've fallen in water - const isWaterBelow = - pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, waterColorBytes); - - // Update velocity according to the collision rules - if (!this.onGround) { - if (this.action === "Floater") { - this.velY += GRAVITY * 0.1; - } else { - this.velY += GRAVITY; - } + // Check if we hit a wall on the x axis + const hitWallOnLeft = pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, dirtColorBytes) || + pixelIsColor(oldImgData, this.x - 1, this.y + this.height / 2, rockColorBytes); + const hitWallOnRight = + pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, dirtColorBytes) || + pixelIsColor(oldImgData, this.x + this.width + 1, this.y + this.height / 2, rockColorBytes); - if (isGroundUnderneath) { - // console.log(this.velY); - if(this.velY > this.deadlyVelY) { - // splat - this.isDead = true; - return; - } - this.onGround = true; - this.velY = 0; - } - } else if (this.onGround && !isGroundUnderneath) { - // Start falling if there's no ground - this.onGround = false; - } + // Check if we've fallen in water + const isWaterBelow = pixelIsColor(oldImgData, this.x + this.width / 2, this.y + this.height + 1, waterColorBytes); - // Check if there are other lemmings that are blockers - for (let i = 0; i < lemmings.length; i++) { - const otherLemming = lemmings[i]; + // Update velocity according to the collision rules + if (!this.onGround) { + if (this.action === "Floater") { + this.velY += GRAVITY * 0.1; + } else { + this.velY += GRAVITY; + } - if ( - otherLemming !== this && - !otherLemming.isDead && - otherLemming.action === "Blocker" && - this.x + this.width > otherLemming.x && - this.x < otherLemming.x + otherLemming.width && - this.y + this.height > otherLemming.y && - this.y < otherLemming.y + otherLemming.height - ) { - console.log("Ran into a blocker?", this.x, this.y) - this.velX *= -1; + if (isGroundUnderneath) { + // console.log(this.velY); + if (this.velY > this.deadlyVelY) { + // splat + this.isDead = true; + return; } + this.onGround = true; + this.velY = 0; } + } else if (this.onGround && !isGroundUnderneath) { + // Start falling if there's no ground + this.onGround = false; + } - if (hitWallOnLeft || hitWallOnRight || this.x <= this.width || this.x >= canvas.width-this.width) { + // Check if there are other lemmings that are blockers + for (let i = 0; i < lemmings.length; i++) { + const otherLemming = lemmings[i]; + + if ( + otherLemming !== this && + !otherLemming.isDead && + otherLemming.action === "Blocker" && + this.x + this.width > otherLemming.x && + this.x < otherLemming.x + otherLemming.width && + this.y + this.height > otherLemming.y && + this.y < otherLemming.y + otherLemming.height + ) { + console.log("Ran into a blocker?", this.x, this.y); this.velX *= -1; } - - if (isWaterBelow || this.y >= canvas.height-this.height) { + } + + if ( + hitWallOnLeft || + hitWallOnRight || + this.x <= this.width || + this.x >= canvas.width - this.width + ) { + this.velX *= -1; + } + + if (isWaterBelow || this.y >= canvas.height - this.height) { + this.isDead = true; + } + + // Apply height adjustment + if (heightAdjustment !== 0) { + if (this.y - heightAdjustment >= 0) { + this.y -= heightAdjustment; // move sprite up + } else { this.isDead = true; + return; } + } - // Apply height adjustment - if (heightAdjustment !== 0) { - this.y -= heightAdjustment; // move sprite up + // Move the lemming + this.x += this.velX; + this.y += this.velY; + this.age++; +}, + draw: function() { + // Draw the new lemming position + ctx.fillStyle = 'rgb(' + blueBox.join(',') + ')'; + ctx.fillRect(this.x, this.y, this.width, this.height); + + ctx.fillStyle = 'rgb(' + greenBox.join(',') + ')'; + ctx.fillRect(this.x, this.y, (this.width/3) - 1, (this.height/4) - 1); + ctx.fillRect(this.x + this.width - (this.width/3), this.y, (this.width/3) - 1, (this.height/4) - 1); + + // add this to the draw method of the Lemming object + if (this.isSelected) { + ctx.strokeStyle = 'red'; + ctx.strokeRect(this.x, this.y, this.width, this.height); } - - // Move the lemming - this.x += this.velX; - this.y += this.velY; - this.age++; + + // Debug + ctx.strokeStyle = "white"; + ctx.strokeText(this.id, this.x + 1, this.y + 13) } -} +}; -I only want to change the update() function in the lemming, any boilerplate around that you can omit. +I only want to change the update() function in the lemming. Show me the changed update() function, the +rest of the Lemming you don't have to give me code for. -I don't want pseudo-code, I want real code. It could be that you need more context from the existing code-base, please let me know if so. +I don't want pseudo-code, I want real code. It could be that you need more context from the existing +code-base, please let me know if so. */ + selectedLemming.action = "Climber"; + selectedLemming.isSelected = false; break; case 'Floater': //add floating behavior here