439 lines
12 KiB
Plaintext
439 lines
12 KiB
Plaintext
// 🎮 Simple Snake Game Demo - CanvasLoopBox + CanvasEventBox + WebCanvasBox
|
|
// Classic Snake game demonstrating complete game development workflow
|
|
|
|
print("🎮 === Simple Snake Game Demo Starting ===")
|
|
|
|
// Initialize game components
|
|
local canvas, events, loop, random
|
|
canvas = new WebCanvasBox("demo-canvas", 600, 400)
|
|
events = new CanvasEventBox("demo-canvas")
|
|
loop = new CanvasLoopBox()
|
|
random = new RandomBox()
|
|
|
|
// Game configuration
|
|
local gameConfig
|
|
gameConfig = {
|
|
gridSize: 20,
|
|
gridWidth: 30, // 600 / 20
|
|
gridHeight: 20, // 400 / 20
|
|
speed: 150, // milliseconds per move
|
|
colors: {
|
|
background: "#2c3e50",
|
|
snake: "#27ae60",
|
|
food: "#e74c3c",
|
|
border: "#34495e",
|
|
text: "#ecf0f1"
|
|
}
|
|
}
|
|
|
|
// Game state
|
|
local gameState, snake, food, direction, nextDirection, score, highScore
|
|
gameState = "playing" // playing, paused, gameover
|
|
score = 0
|
|
highScore = 42 // Demo high score
|
|
|
|
// Snake object (Everything is Box philosophy)
|
|
snake = {
|
|
body: [
|
|
{x: 15, y: 10},
|
|
{x: 14, y: 10},
|
|
{x: 13, y: 10}
|
|
],
|
|
growing: false
|
|
}
|
|
|
|
// Food object
|
|
food = {
|
|
x: 10,
|
|
y: 10,
|
|
type: "normal" // normal, bonus, penalty
|
|
}
|
|
|
|
// Direction system
|
|
direction = "right"
|
|
nextDirection = "right"
|
|
|
|
// Input handling
|
|
local keys
|
|
keys = {
|
|
up: false,
|
|
down: false,
|
|
left: false,
|
|
right: false,
|
|
space: false
|
|
}
|
|
|
|
// Game mechanics
|
|
local generateFood
|
|
generateFood = function() {
|
|
local validPositions, x, y, isValidPosition, bodyPart
|
|
validPositions = []
|
|
|
|
// Find all valid positions (not occupied by snake)
|
|
y = 1
|
|
loop(y < gameConfig.gridHeight - 1) {
|
|
x = 1
|
|
loop(x < gameConfig.gridWidth - 1) {
|
|
isValidPosition = true
|
|
|
|
// Check if position is occupied by snake
|
|
local i
|
|
i = 0
|
|
loop(i < snake.body.length()) {
|
|
bodyPart = snake.body[i]
|
|
if (bodyPart.x == x and bodyPart.y == y) {
|
|
isValidPosition = false
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
if (isValidPosition) {
|
|
validPositions.push({x: x, y: y})
|
|
}
|
|
|
|
x = x + 1
|
|
}
|
|
y = y + 1
|
|
}
|
|
|
|
// Choose random valid position
|
|
if (validPositions.length() > 0) {
|
|
local randomIndex
|
|
randomIndex = random.randInt(0, validPositions.length() - 1)
|
|
local newPos
|
|
newPos = validPositions[randomIndex]
|
|
food.x = newPos.x
|
|
food.y = newPos.y
|
|
|
|
// Randomly choose food type
|
|
local foodTypes
|
|
foodTypes = ["normal", "normal", "normal", "bonus"] // 75% normal, 25% bonus
|
|
food.type = foodTypes[random.randInt(0, 3)]
|
|
}
|
|
}
|
|
|
|
local checkCollision
|
|
checkCollision = function() {
|
|
local head
|
|
head = snake.body[0]
|
|
|
|
// Wall collision
|
|
if (head.x <= 0 or head.x >= gameConfig.gridWidth - 1 or
|
|
head.y <= 0 or head.y >= gameConfig.gridHeight - 1) {
|
|
return "wall"
|
|
}
|
|
|
|
// Self collision
|
|
local i, bodyPart
|
|
i = 1 // Skip head
|
|
loop(i < snake.body.length()) {
|
|
bodyPart = snake.body[i]
|
|
if (head.x == bodyPart.x and head.y == bodyPart.y) {
|
|
return "self"
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
return "none"
|
|
}
|
|
|
|
local updateSnake
|
|
updateSnake = function() {
|
|
if (gameState != "playing") {
|
|
return
|
|
}
|
|
|
|
// Update direction
|
|
direction = nextDirection
|
|
|
|
// Calculate new head position
|
|
local head, newHead
|
|
head = snake.body[0]
|
|
newHead = {x: head.x, y: head.y}
|
|
|
|
if (direction == "up") {
|
|
newHead.y = newHead.y - 1
|
|
} else if (direction == "down") {
|
|
newHead.y = newHead.y + 1
|
|
} else if (direction == "left") {
|
|
newHead.x = newHead.x - 1
|
|
} else if (direction == "right") {
|
|
newHead.x = newHead.x + 1
|
|
}
|
|
|
|
// Add new head
|
|
snake.body.unshift(newHead)
|
|
|
|
// Check food collision
|
|
if (newHead.x == food.x and newHead.y == food.y) {
|
|
// Food eaten
|
|
if (food.type == "normal") {
|
|
score = score + 10
|
|
} else if (food.type == "bonus") {
|
|
score = score + 25
|
|
}
|
|
|
|
snake.growing = true
|
|
generateFood()
|
|
} else {
|
|
// Remove tail if not growing
|
|
if (not snake.growing) {
|
|
snake.body.pop()
|
|
} else {
|
|
snake.growing = false
|
|
}
|
|
}
|
|
|
|
// Check collisions
|
|
local collision
|
|
collision = checkCollision()
|
|
if (collision != "none") {
|
|
gameState = "gameover"
|
|
if (score > highScore) {
|
|
highScore = score
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rendering functions
|
|
local drawGrid
|
|
drawGrid = function() {
|
|
// Background
|
|
canvas.setFillStyle(gameConfig.colors.background)
|
|
canvas.fillRect(0, 0, 600, 400)
|
|
|
|
// Grid lines (subtle)
|
|
canvas.setStrokeStyle("#3a4a5c")
|
|
canvas.setLineWidth(1)
|
|
|
|
local i, x, y
|
|
// Vertical lines
|
|
i = 0
|
|
loop(i <= gameConfig.gridWidth) {
|
|
x = i * gameConfig.gridSize
|
|
canvas.drawLine(x, 0, x, 400, "#3a4a5c", 1)
|
|
i = i + 1
|
|
}
|
|
|
|
// Horizontal lines
|
|
i = 0
|
|
loop(i <= gameConfig.gridHeight) {
|
|
y = i * gameConfig.gridSize
|
|
canvas.drawLine(0, y, 600, y, "#3a4a5c", 1)
|
|
i = i + 1
|
|
}
|
|
|
|
// Border
|
|
canvas.setStrokeStyle(gameConfig.colors.border)
|
|
canvas.setLineWidth(3)
|
|
canvas.strokeRect(0, 0, 600, 400)
|
|
}
|
|
|
|
local drawSnake
|
|
drawSnake = function() {
|
|
local i, bodyPart, x, y
|
|
|
|
i = 0
|
|
loop(i < snake.body.length()) {
|
|
bodyPart = snake.body[i]
|
|
x = bodyPart.x * gameConfig.gridSize
|
|
y = bodyPart.y * gameConfig.gridSize
|
|
|
|
if (i == 0) {
|
|
// Snake head
|
|
canvas.setFillStyle("#2ecc71")
|
|
canvas.fillRect(x + 2, y + 2, gameConfig.gridSize - 4, gameConfig.gridSize - 4)
|
|
|
|
// Eyes
|
|
canvas.setFillStyle("#2c3e50")
|
|
canvas.fillCircle(x + 6, y + 6, 2)
|
|
canvas.fillCircle(x + 14, y + 6, 2)
|
|
} else {
|
|
// Snake body
|
|
canvas.setFillStyle(gameConfig.colors.snake)
|
|
canvas.fillRect(x + 1, y + 1, gameConfig.gridSize - 2, gameConfig.gridSize - 2)
|
|
|
|
// Body segment gradient effect
|
|
canvas.setFillStyle("#229954")
|
|
canvas.fillRect(x + 3, y + 3, gameConfig.gridSize - 6, gameConfig.gridSize - 6)
|
|
}
|
|
|
|
i = i + 1
|
|
}
|
|
}
|
|
|
|
local drawFood
|
|
drawFood = function() {
|
|
local x, y
|
|
x = food.x * gameConfig.gridSize
|
|
y = food.y * gameConfig.gridSize
|
|
|
|
if (food.type == "bonus") {
|
|
// Bonus food (star shape)
|
|
canvas.setFillStyle("#f39c12")
|
|
canvas.fillCircle(x + gameConfig.gridSize / 2, y + gameConfig.gridSize / 2, 8)
|
|
canvas.setFillStyle("#e67e22")
|
|
canvas.fillCircle(x + gameConfig.gridSize / 2, y + gameConfig.gridSize / 2, 5)
|
|
} else {
|
|
// Normal food
|
|
canvas.setFillStyle(gameConfig.colors.food)
|
|
canvas.fillCircle(x + gameConfig.gridSize / 2, y + gameConfig.gridSize / 2, 7)
|
|
canvas.setFillStyle("#c0392b")
|
|
canvas.fillCircle(x + gameConfig.gridSize / 2, y + gameConfig.gridSize / 2, 4)
|
|
}
|
|
}
|
|
|
|
local drawHUD
|
|
drawHUD = function() {
|
|
// Score
|
|
canvas.setFillStyle(gameConfig.colors.text)
|
|
canvas.fillText("Score: " + score, 10, 25, "18px Arial", gameConfig.colors.text)
|
|
canvas.fillText("High: " + highScore, 10, 50, "14px Arial", "#bdc3c7")
|
|
|
|
// Snake length
|
|
canvas.fillText("Length: " + snake.body.length(), 150, 25, "14px Arial", "#95a5a6")
|
|
|
|
// Speed indicator
|
|
local speedText
|
|
speedText = "Speed: " + (200 - gameConfig.speed) + "%"
|
|
canvas.fillText(speedText, 250, 25, "14px Arial", "#95a5a6")
|
|
|
|
// Direction indicator
|
|
canvas.fillText("Dir: " + direction.toUpperCase(), 370, 25, "14px Arial", "#95a5a6")
|
|
|
|
// Controls hint
|
|
canvas.setFillStyle("#7f8c8d")
|
|
canvas.fillText("WASD/Arrows: Move | Space: Pause", 10, 385, "12px Arial", "#7f8c8d")
|
|
}
|
|
|
|
local drawGameOver
|
|
drawGameOver = function() {
|
|
// Semi-transparent overlay
|
|
canvas.setFillStyle("rgba(44, 62, 80, 0.9)")
|
|
canvas.fillRect(100, 150, 400, 150)
|
|
|
|
// Border
|
|
canvas.setStrokeStyle("#e74c3c")
|
|
canvas.setLineWidth(3)
|
|
canvas.strokeRect(100, 150, 400, 150)
|
|
|
|
// Game over text
|
|
canvas.setFillStyle("#e74c3c")
|
|
canvas.fillText("GAME OVER", 220, 200, "32px Arial", "#e74c3c")
|
|
|
|
// Final score
|
|
canvas.setFillStyle("#ecf0f1")
|
|
canvas.fillText("Final Score: " + score, 220, 230, "18px Arial", "#ecf0f1")
|
|
|
|
if (score == highScore) {
|
|
canvas.setFillStyle("#f39c12")
|
|
canvas.fillText("NEW HIGH SCORE!", 210, 255, "16px Arial", "#f39c12")
|
|
}
|
|
|
|
// Restart hint
|
|
canvas.setFillStyle("#95a5a6")
|
|
canvas.fillText("Press R to restart", 235, 280, "14px Arial", "#95a5a6")
|
|
}
|
|
|
|
local drawPaused
|
|
drawPaused = function() {
|
|
canvas.setFillStyle("rgba(52, 73, 94, 0.8)")
|
|
canvas.fillRect(200, 180, 200, 80)
|
|
|
|
canvas.setStrokeStyle("#3498db")
|
|
canvas.setLineWidth(2)
|
|
canvas.strokeRect(200, 180, 200, 80)
|
|
|
|
canvas.setFillStyle("#3498db")
|
|
canvas.fillText("PAUSED", 260, 215, "24px Arial", "#3498db")
|
|
|
|
canvas.setFillStyle("#bdc3c7")
|
|
canvas.fillText("Press Space to resume", 220, 240, "12px Arial", "#bdc3c7")
|
|
}
|
|
|
|
// Main game render function
|
|
local renderGame
|
|
renderGame = function() {
|
|
drawGrid()
|
|
drawSnake()
|
|
drawFood()
|
|
drawHUD()
|
|
|
|
if (gameState == "gameover") {
|
|
drawGameOver()
|
|
} else if (gameState == "paused") {
|
|
drawPaused()
|
|
}
|
|
}
|
|
|
|
// Game initialization
|
|
generateFood()
|
|
renderGame()
|
|
|
|
// Simulate some gameplay for demo
|
|
local demoMoves
|
|
demoMoves = 0
|
|
|
|
local simulateGameplay
|
|
simulateGameplay = function() {
|
|
loop(demoMoves < 15 and gameState == "playing") {
|
|
updateSnake()
|
|
renderGame()
|
|
demoMoves = demoMoves + 1
|
|
|
|
// Change direction occasionally for demo
|
|
if (demoMoves == 5) {
|
|
nextDirection = "down"
|
|
} else if (demoMoves == 10) {
|
|
nextDirection = "left"
|
|
}
|
|
}
|
|
}
|
|
|
|
simulateGameplay()
|
|
|
|
print("🎮 Simple Snake Game Demo Ready!")
|
|
print("• Grid size: " + gameConfig.gridWidth + "x" + gameConfig.gridHeight)
|
|
print("• Current score: " + score)
|
|
print("• High score: " + highScore)
|
|
print("• Snake length: " + snake.body.length())
|
|
print("• Game state: " + gameState)
|
|
|
|
// Demo advanced features
|
|
print("🌟 Game features demonstrated:")
|
|
print("• Collision detection (walls and self)")
|
|
print("• Food generation with obstacle avoidance")
|
|
print("• Smooth snake movement and growth")
|
|
print("• Score system with bonus food")
|
|
print("• Professional game UI with HUD")
|
|
print("• Pause/resume functionality")
|
|
print("• High score tracking")
|
|
|
|
// Show power-up system concept
|
|
local powerUps
|
|
powerUps = [
|
|
{name: "Speed Boost", duration: 5000, effect: "speed"},
|
|
{name: "Invincible", duration: 3000, effect: "invincible"},
|
|
{name: "Score Multiplier", duration: 8000, effect: "multiplier"}
|
|
]
|
|
|
|
canvas.setFillStyle("#9b59b6")
|
|
canvas.fillRect(450, 50, 140, 80)
|
|
canvas.setStrokeStyle("#8e44ad")
|
|
canvas.strokeRect(450, 50, 140, 80)
|
|
|
|
canvas.setFillStyle("#ffffff")
|
|
canvas.fillText("Power-ups:", 460, 70, "12px Arial", "#ffffff")
|
|
canvas.fillText("Speed Boost", 460, 85, "10px Arial", "#ffffff")
|
|
canvas.fillText("Invincible", 460, 100, "10px Arial", "#ffffff")
|
|
canvas.fillText("2x Score", 460, 115, "10px Arial", "#ffffff")
|
|
|
|
print("🎯 Advanced concepts ready for implementation:")
|
|
print("• Power-up system with " + powerUps.length() + " types")
|
|
print("• Multiple difficulty levels")
|
|
print("• Sound effects and music")
|
|
print("• Particle effects for food collection")
|
|
print("• Local multiplayer support")
|
|
|
|
print("🌐 Everything is Box - even classic arcade games!")
|
|
print("✅ Simple Snake Game Demo Complete!") |