Skip to content

Instantly share code, notes, and snippets.

@shricodev
Created May 25, 2025 14:24
Show Gist options
  • Save shricodev/0604cb50cee0599e08e337522378715f to your computer and use it in GitHub Desktop.
Save shricodev/0604cb50cee0599e08e337522378715f to your computer and use it in GitHub Desktop.
Blog - Mario (Claude Opus 4)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Super Mario Bros Clone</title>
<style>
body {
margin: 0;
padding: 0;
background: #5c94fc;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: Arial, sans-serif;
}
#gameContainer {
position: relative;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
canvas {
display: block;
background: #5c94fc;
image-rendering: pixelated;
}
#gameUI {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 20px;
font-weight: bold;
text-shadow: 2px 2px 0px rgba(0, 0, 0, 0.5);
}
#gameOver {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 36px;
font-weight: bold;
text-align: center;
text-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
display: none;
}
#gameOver button {
margin-top: 20px;
padding: 10px 20px;
font-size: 20px;
background: #e45826;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 4px 0 #b73e1d;
}
#gameOver button:active {
transform: translateY(2px);
box-shadow: 0 2px 0 #b73e1d;
}
</style>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas"></canvas>
<div id="gameUI">
<div>SCORE: <span id="score">0</span></div>
<div>COINS: <span id="coins">0</span></div>
<div>LIVES: <span id="lives">3</span></div>
<div>TIME: <span id="timer">300</span></div>
</div>
<div id="gameOver">
<div id="gameOverText">GAME OVER!</div>
<button onclick="restartGame()">RESTART</button>
</div>
</div>
<script>
// Canvas setup
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = 800;
canvas.height = 400;
// Game state
let gameState = "playing"; // playing, gameOver, levelComplete
let score = 0;
let coins = 0;
let lives = 3;
let timer = 300;
let lastTimerUpdate = Date.now();
// Camera
let camera = { x: 0, y: 0 };
// Input handling
const keys = {};
window.addEventListener("keydown", (e) => {
keys[e.key.toLowerCase()] = true;
// Prevent space from scrolling
if (e.key === " ") e.preventDefault();
});
window.addEventListener("keyup", (e) => {
keys[e.key.toLowerCase()] = false;
});
// Player object
const player = {
x: 100,
y: 200,
width: 24,
height: 32,
velocityX: 0,
velocityY: 0,
speed: 4,
jumpPower: 12,
isGrounded: false,
isDead: false,
animFrame: 0,
animTimer: 0,
facing: 1, // 1 for right, -1 for left
invulnerable: 0,
update: function () {
if (this.isDead) return;
// Handle invulnerability frames
if (this.invulnerable > 0) {
this.invulnerable--;
}
// Input handling
if (keys["arrowleft"] || keys["a"]) {
this.velocityX = -this.speed;
this.facing = -1;
} else if (keys["arrowright"] || keys["d"]) {
this.velocityX = this.speed;
this.facing = 1;
} else {
this.velocityX *= 0.8; // Friction
}
// Jumping
if ((keys[" "] || keys["w"]) && this.isGrounded) {
this.velocityY = -this.jumpPower;
this.isGrounded = false;
}
// Apply gravity
this.velocityY += 0.6;
if (this.velocityY > 15) this.velocityY = 15;
// Update position
this.x += this.velocityX;
this.y += this.velocityY;
// Animation
if (Math.abs(this.velocityX) > 0.5) {
this.animTimer++;
if (this.animTimer > 8) {
this.animTimer = 0;
this.animFrame = (this.animFrame + 1) % 2;
}
} else {
this.animFrame = 0;
}
// Check if fallen off the world
if (this.y > canvas.height + 100) {
this.die();
}
},
draw: function () {
ctx.save();
// Flash when invulnerable
if (this.invulnerable % 10 < 5) {
ctx.globalAlpha = 0.5;
}
// Draw Mario
ctx.translate(this.x - camera.x, this.y);
// Body
ctx.fillStyle = "#E45826";
ctx.fillRect(
-this.width / 2,
-this.height,
this.width,
this.height * 0.6,
);
// Overalls
ctx.fillStyle = "#3B48E0";
ctx.fillRect(
-this.width / 2,
-this.height * 0.4,
this.width,
this.height * 0.4,
);
// Face
ctx.fillStyle = "#FDB383";
ctx.fillRect(
-this.width / 2 + 2,
-this.height + 2,
this.width - 4,
this.height * 0.3,
);
// Hat
ctx.fillStyle = "#E45826";
ctx.fillRect(-this.width / 2, -this.height - 4, this.width, 6);
// Eye
ctx.fillStyle = "#000";
ctx.fillRect(this.facing > 0 ? 2 : -6, -this.height + 8, 3, 3);
// Mustache
ctx.fillRect(this.facing > 0 ? -2 : -8, -this.height + 12, 8, 2);
// Arms & legs animation
if (this.animFrame === 1) {
// Leg movement
ctx.fillStyle = "#3B48E0";
ctx.fillRect(-this.width / 2 - 2, -4, 4, 4);
ctx.fillRect(this.width / 2 - 2, -8, 4, 4);
}
ctx.restore();
},
die: function () {
if (this.invulnerable > 0) return;
lives--;
if (lives <= 0) {
gameState = "gameOver";
document.getElementById("gameOver").style.display = "block";
} else {
// Reset position
this.x = 100;
this.y = 200;
this.velocityX = 0;
this.velocityY = 0;
this.invulnerable = 120; // 2 seconds of invulnerability
}
updateUI();
},
takeDamage: function () {
this.die();
},
};
// Enemy class
class Enemy {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 28;
this.height = 28;
this.velocityX = -1;
this.velocityY = 0;
this.isDead = false;
this.animFrame = 0;
this.animTimer = 0;
}
update() {
if (this.isDead) return;
// Apply gravity
this.velocityY += 0.6;
if (this.velocityY > 15) this.velocityY = 15;
// Update position
this.x += this.velocityX;
this.y += this.velocityY;
// Animation
this.animTimer++;
if (this.animTimer > 15) {
this.animTimer = 0;
this.animFrame = (this.animFrame + 1) % 2;
}
// Simple AI - reverse direction at edges
const tileBelow = getTileAt(
this.x + this.velocityX * 20,
this.y + this.height / 2 + 10,
);
if (!tileBelow || tileBelow.type === "empty") {
this.velocityX *= -1;
}
}
draw() {
if (this.isDead) return;
ctx.save();
ctx.translate(this.x - camera.x, this.y);
// Shell
ctx.fillStyle = "#4B8B3B";
ctx.fillRect(
-this.width / 2,
-this.height / 2,
this.width,
this.height / 2,
);
ctx.fillStyle = "#6BAA5C";
ctx.fillRect(
-this.width / 2 + 2,
-this.height / 2 + 2,
this.width - 4,
this.height / 2 - 4,
);
// Body
ctx.fillStyle = "#FFD93D";
ctx.fillRect(
-this.width / 2 + 4,
0,
this.width - 8,
this.height / 2 - 4,
);
// Feet animation
if (this.animFrame === 0) {
ctx.fillRect(-this.width / 2 - 2, this.height / 2 - 4, 6, 4);
ctx.fillRect(this.width / 2 - 4, this.height / 2 - 4, 6, 4);
} else {
ctx.fillRect(-this.width / 2 + 2, this.height / 2 - 4, 6, 4);
ctx.fillRect(this.width / 2 - 8, this.height / 2 - 4, 6, 4);
}
// Eyes
ctx.fillStyle = "#000";
ctx.fillRect(this.velocityX > 0 ? 4 : -8, -4, 3, 3);
ctx.restore();
}
die() {
this.isDead = true;
score += 100;
updateUI();
}
}
// Coin class
class Coin {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 20;
this.height = 20;
this.collected = false;
this.animTimer = 0;
}
update() {
if (!this.collected) {
this.animTimer += 0.1;
}
}
draw() {
if (this.collected) return;
ctx.save();
ctx.translate(this.x - camera.x, this.y);
// Animated coin
const scale = Math.abs(Math.sin(this.animTimer));
ctx.scale(scale, 1);
ctx.fillStyle = "#FFD700";
ctx.beginPath();
ctx.arc(0, 0, this.width / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#FFA500";
ctx.beginPath();
ctx.arc(0, 0, this.width / 2 - 3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
collect() {
if (!this.collected) {
this.collected = true;
coins++;
score += 10;
updateUI();
}
}
}
// Level data
const TILE_SIZE = 32;
const level = [
" ",
" ",
" $ $$$ ",
" === === ",
" $ === $ $ ",
" === === $ ",
" = E ===== E === ",
" E ===== E $ | ",
"============== ================ ========================= =======================",
"============== ================ ========================= =======================",
];
// Parse level and create objects
const platforms = [];
const enemies = [];
const coinsList = [];
let flagPole = null;
for (let row = 0; row < level.length; row++) {
for (let col = 0; col < level[row].length; col++) {
const char = level[row][col];
const x = col * TILE_SIZE;
const y = row * TILE_SIZE;
switch (char) {
case "=":
platforms.push({
x,
y,
width: TILE_SIZE,
height: TILE_SIZE,
type: "ground",
});
break;
case "E":
enemies.push(new Enemy(x + TILE_SIZE / 2, y));
break;
case "$":
coinsList.push(new Coin(x + TILE_SIZE / 2, y + TILE_SIZE / 2));
break;
case "|":
flagPole = { x: x + TILE_SIZE / 2, y: y };
break;
}
}
}
// Get tile at position
function getTileAt(x, y) {
const col = Math.floor(x / TILE_SIZE);
const row = Math.floor(y / TILE_SIZE);
if (
row < 0 ||
row >= level.length ||
col < 0 ||
col >= level[row].length
) {
return { type: "empty" };
}
const char = level[row][col];
if (char === "=") {
return {
type: "ground",
x: col * TILE_SIZE,
y: row * TILE_SIZE,
width: TILE_SIZE,
height: TILE_SIZE,
};
}
return { type: "empty" };
}
// Collision detection
function checkCollision(rect1, rect2) {
return (
rect1.x - rect1.width / 2 < rect2.x + rect2.width / 2 &&
rect1.x + rect1.width / 2 > rect2.x - rect2.width / 2 &&
rect1.y - rect1.height < rect2.y + rect2.height / 2 &&
rect1.y > rect2.y - rect2.height / 2
);
}
function checkPlatformCollision(entity) {
entity.isGrounded = false;
// Check all platforms
for (let platform of platforms) {
const platRect = {
x: platform.x + platform.width / 2,
y: platform.y + platform.height / 2,
width: platform.width,
height: platform.height,
};
if (checkCollision(entity, platRect)) {
// Top collision (landing)
if (entity.velocityY > 0 && entity.y - entity.height < platform.y) {
entity.y = platform.y;
entity.velocityY = 0;
entity.isGrounded = true;
}
// Bottom collision (hitting head)
else if (
entity.velocityY < 0 &&
entity.y > platform.y + platform.height
) {
entity.y = platform.y + platform.height + entity.height;
entity.velocityY = 0;
}
// Side collisions
else if (entity.x < platform.x + platform.width / 2) {
entity.x = platform.x - entity.width / 2;
entity.velocityX = 0;
} else {
entity.x = platform.x + platform.width + entity.width / 2;
entity.velocityX = 0;
}
}
}
}
// Update camera
function updateCamera() {
camera.x = player.x - canvas.width / 2;
if (camera.x < 0) camera.x = 0;
if (camera.x > level[0].length * TILE_SIZE - canvas.width) {
camera.x = level[0].length * TILE_SIZE - canvas.width;
}
}
// Update UI
function updateUI() {
document.getElementById("score").textContent = score;
document.getElementById("coins").textContent = coins;
document.getElementById("lives").textContent = lives;
document.getElementById("timer").textContent = timer;
}
// Draw background
function drawBackground() {
// Sky gradient
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
gradient.addColorStop(0, "#5C94FC");
gradient.addColorStop(1, "#8ED2FF");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Clouds
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
for (let i = 0; i < 5; i++) {
const x = (i * 200 - camera.x * 0.3) % (canvas.width + 100);
const y = 50 + (i % 2) * 30;
// Simple cloud shape
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI * 2);
ctx.arc(x + 25, y, 35, 0, Math.PI * 2);
ctx.arc(x + 50, y, 25, 0, Math.PI * 2);
ctx.fill();
}
// Hills in background
ctx.fillStyle = "#3EAD3E";
for (let i = 0; i < 3; i++) {
const x = (i * 300 - camera.x * 0.5) % (canvas.width + 200);
const y = canvas.height - 100;
ctx.beginPath();
ctx.arc(x, y, 80, 0, Math.PI, true);
ctx.fill();
}
}
// Draw platforms
function drawPlatforms() {
platforms.forEach((platform) => {
const x = platform.x - camera.x;
// Only draw visible platforms
if (x + platform.width > 0 && x < canvas.width) {
// Ground block
ctx.fillStyle = "#C84C0C";
ctx.fillRect(x, platform.y, platform.width, platform.height);
// Inner detail
ctx.fillStyle = "#FC9838";
ctx.fillRect(
x + 2,
platform.y + 2,
platform.width - 4,
platform.height - 4,
);
// Grid pattern
ctx.strokeStyle = "#C84C0C";
ctx.lineWidth = 2;
ctx.strokeRect(
x + 2,
platform.y + 2,
platform.width - 4,
platform.height - 4,
);
// Brick lines
ctx.beginPath();
ctx.moveTo(x + platform.width / 2, platform.y + 2);
ctx.lineTo(
x + platform.width / 2,
platform.y + platform.height - 2,
);
ctx.moveTo(x + 2, platform.y + platform.height / 2);
ctx.lineTo(
x + platform.width - 2,
platform.y + platform.height / 2,
);
ctx.stroke();
}
});
}
// Draw flag
function drawFlag() {
if (!flagPole) return;
const x = flagPole.x - camera.x;
// Pole
ctx.fillStyle = "#4B8B3B";
ctx.fillRect(x - 2, flagPole.y, 4, 200);
// Flag
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(x + 2, flagPole.y);
ctx.lineTo(x + 40, flagPole.y + 20);
ctx.lineTo(x + 2, flagPole.y + 40);
ctx.closePath();
ctx.fill();
// Flag detail
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.moveTo(x + 2, flagPole.y + 10);
ctx.lineTo(x + 25, flagPole.y + 20);
ctx.lineTo(x + 2, flagPole.y + 30);
ctx.closePath();
ctx.fill();
}
// Game loop
function gameLoop() {
if (gameState === "playing") {
// Update timer
const now = Date.now();
if (now - lastTimerUpdate > 1000) {
timer--;
lastTimerUpdate = now;
updateUI();
if (timer <= 0) {
player.die();
}
}
// Update player
player.update();
checkPlatformCollision(player);
// Update enemies
enemies.forEach((enemy) => {
enemy.update();
checkPlatformCollision(enemy);
// Check collision with player
if (!enemy.isDead && checkCollision(player, enemy)) {
// Check if player is jumping on enemy
if (player.velocityY > 0 && player.y < enemy.y) {
enemy.die();
player.velocityY = -8; // Bounce
} else {
player.takeDamage();
}
}
});
// Update coins
coinsList.forEach((coin) => {
coin.update();
if (!coin.collected && checkCollision(player, coin)) {
coin.collect();
}
});
// Check for level complete
if (flagPole && Math.abs(player.x - flagPole.x) < 20) {
gameState = "levelComplete";
score += timer * 10;
document.getElementById("gameOverText").textContent =
"LEVEL COMPLETE!";
document.getElementById("gameOver").style.display = "block";
}
// Update camera
updateCamera();
}
// Render
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw everything
drawBackground();
drawPlatforms();
drawFlag();
// Draw coins
coinsList.forEach((coin) => coin.draw());
// Draw enemies
enemies.forEach((enemy) => enemy.draw());
// Draw player
player.draw();
requestAnimationFrame(gameLoop);
}
// Restart game
function restartGame() {
// Reset game state
gameState = "playing";
score = 0;
coins = 0;
lives = 3;
timer = 300;
lastTimerUpdate = Date.now();
// Reset player
player.x = 100;
player.y = 200;
player.velocityX = 0;
player.velocityY = 0;
player.isDead = false;
player.invulnerable = 0;
// Reset enemies
enemies.forEach((enemy, index) => {
enemy.isDead = false;
enemy.x =
platforms.find(
(p) =>
level[Math.floor(enemy.y / TILE_SIZE)][
Math.floor(enemy.x / TILE_SIZE)
] === "E",
).x +
TILE_SIZE / 2;
});
// Reset coins
coinsList.forEach((coin) => (coin.collected = false));
// Hide game over screen
document.getElementById("gameOver").style.display = "none";
// Update UI
updateUI();
}
// Start game
updateUI();
gameLoop();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment