经典坦克大战游戏(有源码,可复制)
本游戏是解压小游戏,操作简单,好玩解压,还可以作为案例进行开发讲解。后面我会把源码贴出来,希望点赞转发加关注。



-
– ↑ / W – 向上移动 -
– ↓ / S – 向下移动 -
– ← / A – 向左移动 -
– → / D – 向右移动 -
– 空格键 – 发射子弹 -
– R键 – 游戏结束后重新开始


<!DOCTYPE html><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>坦克大战小游戏</title><linkrel="stylesheet"href="tank_game.css"></head><body><divclass="game-container"><h1>坦克大战小游戏</h1><divclass="game-info"><divclass="score">得分: <spanid="score">0</span></div><divclass="controls">方向键移动 | 空格键射击</div></div><divclass="canvas-container"><canvasid="gameCanvas"width="800"height="600"></canvas><divclass="game-over"id="gameOver"><h2>Game Over</h2><p>最终得分: <spanid="finalScore">0</span></p><buttonclass="restart-btn"onclick="restartGame()">重新开始</button></div></div></div><scriptsrc="tank_game.js"></script></body></html>
* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;background-color: #f0f0f0;display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;padding: 20px;}.game-container {text-align: center;}h1 {color: #333;margin-bottom: 20px;font-size: 2.5rem;text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);}.game-info {display: flex;justify-content: space-between;align-items: center;padding: 15px 30px;background-color: #fff;border-radius: 10px;margin-bottom: 20px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);width: 800px;}.score {font-size: 1.5rem;color: #4CAF50;font-weight: bold;}.controls {font-size: 1rem;color: #666;}#gameCanvas {background-color: #e0e0e0;border: 3px solid #333;border-radius: 10px;display: block;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);}.game-over {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background-color: rgba(0, 0, 0, 0.8);color: white;padding: 40px 60px;border-radius: 15px;text-align: center;display: none;z-index: 10;}.game-over h2 {font-size: 3rem;margin-bottom: 20px;color: #ff4444;}.game-over p {font-size: 1.5rem;margin-bottom: 30px;}.restart-btn {background-color: #4CAF50;color: white;border: none;padding: 15px 40px;font-size: 1.5rem;border-radius: 10px;cursor: pointer;transition: all 0.3s ease;}.restart-btn:hover {background-color: #45a049;transform: scale(1.05);}.restart-btn:active {transform: scale(0.95);}.canvas-container {position: relative;}
const canvas = document.getElementById('gameCanvas');const ctx = canvas.getContext('2d');const scoreElement = document.getElementById('score');const finalScoreElement = document.getElementById('finalScore');const gameOverElement = document.getElementById('gameOver');const GAME_WIDTH = 800;const GAME_HEIGHT = 600;const TANK_SIZE = 40;const BULLET_SIZE = 6;const BULLET_SPEED = 8;const TANK_SPEED = 4;const ENEMY_SPEED = 2;let score = 0;let gameRunning = true;let animationId;const keys = {};class Tank {constructor(x, y, color, isPlayer = false) {this.x = x;this.y = y;this.width = TANK_SIZE;this.height = TANK_SIZE;this.color = color;this.direction = 'up';this.isPlayer = isPlayer;this.speed = isPlayer ? TANK_SPEED : ENEMY_SPEED;this.bullets = [];this.shootCooldown = 0;this.opacity = 1.0;this.scale = 1.0;this.isExploding = false;}draw() {ctx.save();ctx.globalAlpha = this.opacity;ctx.translate(this.x + this.width / 2, this.y + this.height / 2);ctx.scale(this.scale, this.scale);switch(this.direction) {case 'up':ctx.rotate(0);break;case 'right':ctx.rotate(Math.PI / 2);break;case 'down':ctx.rotate(Math.PI);break;case 'left':ctx.rotate(-Math.PI / 2);break;}ctx.fillStyle = this.color;ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);ctx.fillStyle = this.isPlayer ? '#2E7D32' : '#B71C1C';ctx.fillRect(-this.width / 2 + 5, -this.height / 2 + 5, this.width - 10, this.height - 10);ctx.fillStyle = this.isPlayer ? '#1B5E20' : '#8B0000';ctx.fillRect(-4, -this.height / 2 - 8, 8, 12);ctx.restore();}move(dx, dy, obstacles) {const newX = this.x + dx;const newY = this.y + dy;// 边界检查if (newX < 0 || newX + this.width > GAME_WIDTH ||newY < 0 || newY + this.height > GAME_HEIGHT) {return;}// 障碍物碰撞检查if (this.checkCollision(newX, newY, obstacles)) {// 尝试分离移动:先尝试X轴移动,再尝试Y轴移动if (dx !== 0 && !this.checkCollision(newX, this.y, obstacles)) {// 只有X轴移动是安全的this.x = newX;} else if (dy !== 0 && !this.checkCollision(this.x, newY, obstacles)) {// 只有Y轴移动是安全的this.y = newY;}// 如果两个方向都不安全,则不移动return;}// 如果没有碰撞,正常移动this.x = newX;this.y = newY;}checkCollision(x, y, obstacles) {// 添加一个小的缓冲区,防止坦克与障碍物边缘过于接近const buffer = this.isPlayer ? 2 : 1;for (let obstacle of obstacles) {if (x + buffer < obstacle.x + obstacle.width &&x + this.width - buffer > obstacle.x &&y + buffer < obstacle.y + obstacle.height &&y + this.height - buffer > obstacle.y) {return true;}}return false;}shoot() {if (this.shootCooldown <= 0) {let bulletX, bulletY, bulletDx, bulletDy;switch(this.direction) {case 'up':bulletX = this.x + this.width / 2 - BULLET_SIZE / 2;bulletY = this.y - BULLET_SIZE;bulletDx = 0;bulletDy = -BULLET_SPEED;break;case 'down':bulletX = this.x + this.width / 2 - BULLET_SIZE / 2;bulletY = this.y + this.height;bulletDx = 0;bulletDy = BULLET_SPEED;break;case 'left':bulletX = this.x - BULLET_SIZE;bulletY = this.y + this.height / 2 - BULLET_SIZE / 2;bulletDx = -BULLET_SPEED;bulletDy = 0;break;case 'right':bulletX = this.x + this.width;bulletY = this.y + this.height / 2 - BULLET_SIZE / 2;bulletDx = BULLET_SPEED;bulletDy = 0;break;}this.bullets.push(new Bullet(bulletX, bulletY, bulletDx, bulletDy, this.isPlayer));this.shootCooldown = this.isPlayer ? 15 : 60;audioManager.playShootSound();}}updateBullets(obstacles) {for (let i = this.bullets.length - 1; i >= 0; i--) {this.bullets[i].update();if (this.bullets[i].checkCollision(obstacles) ||this.bullets[i].isOutOfBounds()) {this.bullets.splice(i, 1);}}}drawBullets() {this.bullets.forEach(bullet => bullet.draw());}}class Bullet {constructor(x, y, dx, dy, isPlayerBullet) {this.x = x;this.y = y;this.width = BULLET_SIZE;this.height = BULLET_SIZE;this.dx = dx;this.dy = dy;this.isPlayerBullet = isPlayerBullet;}update() {this.x += this.dx;this.y += this.dy;}draw() {ctx.fillStyle = this.isPlayerBullet ? '#FFD700' : '#FF4444';ctx.beginPath();ctx.arc(this.x + this.width / 2, this.y + this.height / 2, this.width / 2, 0, Math.PI * 2);ctx.fill();}checkCollision(obstacles) {for (let obstacle of obstacles) {if (this.x < obstacle.x + obstacle.width &&this.x + this.width > obstacle.x &&this.y < obstacle.y + obstacle.height &&this.y + this.height > obstacle.y) {return true;}}return false;}isOutOfBounds() {return this.x < 0 || this.x > GAME_WIDTH ||this.y < 0 || this.y > GAME_HEIGHT;}}class Obstacle {constructor(x, y, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;}draw() {ctx.fillStyle = '#666';ctx.fillRect(this.x, this.y, this.width, this.height);ctx.strokeStyle = '#444';ctx.lineWidth = 2;ctx.strokeRect(this.x, this.y, this.width, this.height);}}class Particle {constructor(x, y, color, type) {this.x = x;this.y = y;this.color = color;this.type = type;this.size = Math.random() * 8 + 4;this.speedX = (Math.random() - 0.5) * 10;this.speedY = (Math.random() - 0.5) * 10;this.life = 1.0;this.decay = Math.random() * 0.02 + 0.01;this.gravity = 0.1;}update() {this.x += this.speedX;this.y += this.speedY;this.speedY += this.gravity;this.life -= this.decay;this.size *= 0.98;}draw() {ctx.save();ctx.globalAlpha = this.life;ctx.fillStyle = this.color;if (this.type === 'spark') {ctx.beginPath();ctx.arc(this.x, this.y, this.size / 2, 0, Math.PI * 2);ctx.fill();} else if (this.type === 'smoke') {ctx.beginPath();ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);ctx.fill();} else if (this.type === 'debris') {ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);}ctx.restore();}isDead() {return this.life <= 0;}}class Explosion {constructor(x, y, color) {this.x = x;this.y = y;this.color = color;this.particles = [];this.phase = 0;this.phaseTimer = 0;this.duration = 0;this.flashOpacity = 1.0;this.screenShake = 10;this.tankOpacity = 1.0;this.tankScale = 1.0;this.initParticles();}initParticles() {const particleCount = 50;for (let i = 0; i < particleCount; i++) {const angle = (Math.PI * 2 * i) / particleCount;const speed = Math.random() * 8 + 4;const particleType = Math.random() < 0.3 ? 'smoke' : (Math.random() < 0.6 ? 'spark' : 'debris');const particleColor = particleType === 'smoke' ?`rgba(100, 100, 100, ${Math.random() * 0.5 + 0.3})` :(particleType === 'spark' ?`rgba(255, ${Math.floor(Math.random() * 200)}, 0, 1)` :this.color);const particle = new Particle(this.x, this.y, particleColor, particleType);particle.speedX = Math.cos(angle) * speed;particle.speedY = Math.sin(angle) * speed;this.particles.push(particle);}}update() {this.duration++;this.phaseTimer++;if (this.phaseTimer < 10) {this.phase = 0;this.flashOpacity = 1.0 - (this.phaseTimer / 10);} else if (this.phaseTimer < 40) {this.phase = 1;this.flashOpacity = 0;this.screenShake = Math.max(0, this.screenShake - 0.5);} else {this.phase = 2;this.flashOpacity = 0;this.screenShake = 0;}if (this.phaseTimer < 30) {this.tankOpacity = 1.0 - (this.phaseTimer / 30);this.tankScale = 1.0 + (this.phaseTimer / 30) * 0.3;} else {this.tankOpacity = 0;}this.particles.forEach(particle => particle.update());this.particles = this.particles.filter(particle => !particle.isDead());}draw() {if (this.phase === 0) {ctx.save();ctx.globalAlpha = this.flashOpacity;ctx.fillStyle = '#FFFFFF';ctx.beginPath();ctx.arc(this.x, this.y, 60, 0, Math.PI * 2);ctx.fill();ctx.restore();}this.particles.forEach(particle => particle.draw());}isComplete() {return this.duration >= 120;}getScreenShake() {return this.screenShake;}}class AudioManager {constructor() {this.audioContext = null;this.initialized = false;}init() {if (!this.initialized) {this.audioContext = new (window.AudioContext || window.webkitAudioContext)();this.initialized = true;}}playExplosionSound() {if (!this.initialized) return;const oscillator = this.audioContext.createOscillator();const gainNode = this.audioContext.createGain();const filterNode = this.audioContext.createBiquadFilter();oscillator.type = 'sawtooth';oscillator.frequency.setValueAtTime(150, this.audioContext.currentTime);oscillator.frequency.exponentialRampToValueAtTime(50, this.audioContext.currentTime + 0.5);filterNode.type = 'lowpass';filterNode.frequency.setValueAtTime(1000, this.audioContext.currentTime);filterNode.frequency.exponentialRampToValueAtTime(100, this.audioContext.currentTime + 0.5);gainNode.gain.setValueAtTime(0.3, this.audioContext.currentTime);gainNode.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.5);oscillator.connect(filterNode);filterNode.connect(gainNode);gainNode.connect(this.audioContext.destination);oscillator.start();oscillator.stop(this.audioContext.currentTime + 0.5);}playShootSound() {if (!this.initialized) return;const oscillator = this.audioContext.createOscillator();const gainNode = this.audioContext.createGain();oscillator.type = 'square';oscillator.frequency.setValueAtTime(800, this.audioContext.currentTime);oscillator.frequency.exponentialRampToValueAtTime(200, this.audioContext.currentTime + 0.1);gainNode.gain.setValueAtTime(0.1, this.audioContext.currentTime);gainNode.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.1);oscillator.connect(gainNode);gainNode.connect(this.audioContext.destination);oscillator.start();oscillator.stop(this.audioContext.currentTime + 0.1);}}let player;let enemies = [];let obstacles = [];let explosions = [];let audioManager = new AudioManager();let screenShakeX = 0;let screenShakeY = 0;let lastObstacleChangeScore = 0;let obstacleChangeNotification = null;function initGame() {score = 0;lastObstacleChangeScore = 0;gameRunning = true;scoreElement.textContent = score;gameOverElement.style.display = 'none';audioManager.init();player = new Tank(GAME_WIDTH / 2 - TANK_SIZE / 2, GAME_HEIGHT - TANK_SIZE - 10, '#4CAF50', true);enemies = [];obstacles = [];explosions = [];createObstacles();ensurePlayerNotInObstacle(); // 确保玩家坦克不与障碍物重叠spawnEnemies(3);}function createObstacles() {obstacles = [];// 创建随机障碍物布局const obstacleCount = 8 + Math.floor(score / 10);for (let i = 0; i < obstacleCount; i++) {let validPosition = false;let attempts = 0;while (!validPosition && attempts < 100) {attempts++;const width = Math.random() * 60 + 40;const height = Math.random() * 60 + 40;const x = Math.random() * (GAME_WIDTH - width);const y = Math.random() * (GAME_HEIGHT - height - 100) + 50; // 避免底部区域const newObstacle = new Obstacle(x, y, width, height);// 检查是否与现有障碍物重叠let overlapping = false;for (let obstacle of obstacles) {if (x < obstacle.x + obstacle.width &&x + width > obstacle.x &&y < obstacle.y + obstacle.height &&y + height > obstacle.y) {overlapping = true;break;}}// 检查是否与玩家初始位置重叠if (!overlapping) {const playerX = GAME_WIDTH / 2 - TANK_SIZE / 2;const playerY = GAME_HEIGHT - TANK_SIZE - 10;if (!(x < playerX + TANK_SIZE &&x + width > playerX &&y < playerY + TANK_SIZE &&y + height > playerY)) {obstacles.push(newObstacle);validPosition = true;}}}}}function ensurePlayerNotInObstacle() {// 确保玩家坦克不与任何障碍物重叠let overlapping = true;let attempts = 0;while (overlapping && attempts < 100) {overlapping = false;attempts++;for (let obstacle of obstacles) {if (player.x < obstacle.x + obstacle.width &&player.x + player.width > obstacle.x &&player.y < obstacle.y + obstacle.height &&player.y + player.height > obstacle.y) {overlapping = true;// 尝试将玩家移动到安全位置player.x = Math.random() * (GAME_WIDTH - TANK_SIZE);player.y = Math.random() * (GAME_HEIGHT - TANK_SIZE - 100) + 50;break;}}}}function spawnEnemies(count) {for (let i = 0; i < count; i++) {spawnEnemy();}}function spawnEnemy() {let validPosition = false;let attempts = 0;while (!validPosition && attempts < 100) {attempts++;const x = Math.random() * (GAME_WIDTH - TANK_SIZE);const y = Math.random() * (GAME_HEIGHT / 2 - TANK_SIZE); // 只在上半部分生成const enemy = new Tank(x, y, '#FF4444');// 检查是否与障碍物重叠let overlapping = false;for (let obstacle of obstacles) {if (enemy.checkCollision(enemy.x, enemy.y, [obstacle])) {overlapping = true;break;}}// 检查是否与其他敌人重叠for (let otherEnemy of enemies) {if (enemy.x < otherEnemy.x + otherEnemy.width &&enemy.x + enemy.width > otherEnemy.x &&enemy.y < otherEnemy.y + otherEnemy.height &&enemy.y + enemy.height > otherEnemy.y) {overlapping = true;break;}}if (!overlapping) {enemies.push(enemy);validPosition = true;}}}function showObstacleChangeNotification() {obstacleChangeNotification = {text: '障碍物已更新!',opacity: 1.0,y: GAME_HEIGHT / 2 - 50};}function updateObstacleChangeNotification() {if (obstacleChangeNotification) {obstacleChangeNotification.opacity -= 0.02;obstacleChangeNotification.y -= 1;if (obstacleChangeNotification.opacity <= 0) {obstacleChangeNotification = null;}}}function drawObstacleChangeNotification() {if (obstacleChangeNotification) {ctx.save();ctx.globalAlpha = obstacleChangeNotification.opacity;ctx.fillStyle = '#FFFFFF';ctx.font = 'bold 24px Arial';ctx.textAlign = 'center';ctx.fillText(obstacleChangeNotification.text, GAME_WIDTH / 2, obstacleChangeNotification.y);ctx.restore();}}function handleInput() {let dx = 0, dy = 0;if (keys['ArrowUp'] || keys['w'] || keys['W']) {dy = -player.speed;player.direction = 'up';}if (keys['ArrowDown'] || keys['s'] || keys['S']) {dy = player.speed;player.direction = 'down';}if (keys['ArrowLeft'] || keys['a'] || keys['A']) {dx = -player.speed;player.direction = 'left';}if (keys['ArrowRight'] || keys['d'] || keys['D']) {dx = player.speed;player.direction = 'right';}if (dx !== 0 || dy !== 0) {player.move(dx, dy, obstacles);}if (keys[' ']) {player.shoot();}}function updateEnemies() {enemies.forEach(enemy => {if (enemy.shootCooldown > 0) {enemy.shootCooldown--;}if (Math.random() < 0.02) {enemy.shoot();}// 只有在随机概率下或者刚碰到障碍物时才改变方向if (Math.random() < 0.01 || enemy.shouldChangeDirection) {const directions = ['up', 'down', 'left', 'right'];const randomDirection = directions[Math.floor(Math.random() * directions.length)];enemy.direction = randomDirection;enemy.shouldChangeDirection = false;}let dx = 0, dy = 0;switch(enemy.direction) {case 'up': dy = -enemy.speed; break;case 'down': dy = enemy.speed; break;case 'left': dx = -enemy.speed; break;case 'right': dx = enemy.speed; break;}// 检查移动是否会碰到障碍物或边界const newX = enemy.x + dx;const newY = enemy.y + dy;if (newX < 0 || newX + enemy.width > GAME_WIDTH ||newY < 0 || newY + enemy.height > GAME_HEIGHT ||enemy.checkCollision(newX, newY, obstacles)) {// 如果会碰到障碍物或边界,反向移动enemy.direction = getOppositeDirection(enemy.direction);enemy.shouldChangeDirection = true;// 重新计算反向移动dx = 0; dy = 0;switch(enemy.direction) {case 'up': dy = -enemy.speed; break;case 'down': dy = enemy.speed; break;case 'left': dx = -enemy.speed; break;case 'right': dx = enemy.speed; break;}}enemy.move(dx, dy, obstacles);enemy.updateBullets(obstacles);});}function getOppositeDirection(direction) {switch(direction) {case 'up': return 'down';case 'down': return 'up';case 'left': return 'right';case 'right': return 'left';default: return 'up';}}function checkBulletCollisions() {for (let i = enemies.length - 1; i >= 0; i--) {const enemy = enemies[i];for (let j = enemy.bullets.length - 1; j >= 0; j--) {const bullet = enemy.bullets[j];if (bullet.x < player.x + player.width &&bullet.x + bullet.width > player.x &&bullet.y < player.y + player.height &&bullet.y + bullet.height > player.y) {gameOver();return;}}for (let j = player.bullets.length - 1; j >= 0; j--) {const bullet = player.bullets[j];if (bullet.x < enemy.x + enemy.width &&bullet.x + bullet.width > enemy.x &&bullet.y < enemy.y + enemy.height &&bullet.y + bullet.height > enemy.y) {player.bullets.splice(j, 1);const explosion = new Explosion(enemy.x + enemy.width / 2,enemy.y + enemy.height / 2,enemy.color);explosions.push(explosion);audioManager.playExplosionSound();enemy.isExploding = true;enemy.opacity = 0;setTimeout(() => {if (gameRunning) {const enemyIndex = enemies.indexOf(enemy);if (enemyIndex > -1) {enemies.splice(enemyIndex, 1);score++;scoreElement.textContent = score;spawnEnemy();}}}, 1000);break;}}}}function gameOver() {const playerExplosion = new Explosion(player.x + player.width / 2,player.y + player.height / 2,player.color);explosions.push(playerExplosion);audioManager.playExplosionSound();let explosionAnimation = 0;const explosionDuration = 60;function animateExplosion() {explosionAnimation++;ctx.save();let shakeX = 0;let shakeY = 0;explosions.forEach(explosion => {explosion.update();const shakeAmount = explosion.getScreenShake();if (shakeAmount > 0) {shakeX = (Math.random() - 0.5) * shakeAmount;shakeY = (Math.random() - 0.5) * shakeAmount;}});ctx.translate(shakeX, shakeY);ctx.clearRect(-shakeX, -shakeY, GAME_WIDTH, GAME_HEIGHT);obstacles.forEach(obstacle => obstacle.draw());enemies.forEach(enemy => {enemy.draw();enemy.drawBullets();});explosions.forEach(explosion => explosion.draw());ctx.restore();if (explosionAnimation < explosionDuration) {requestAnimationFrame(animateExplosion);} else {gameRunning = false;finalScoreElement.textContent = score;gameOverElement.style.display = 'block';explosions = [];}}animateExplosion();}function restartGame() {initGame();gameLoop();}function draw() {let shakeX = 0;let shakeY = 0;explosions.forEach(explosion => {const shakeAmount = explosion.getScreenShake();if (shakeAmount > 0) {shakeX = (Math.random() - 0.5) * shakeAmount;shakeY = (Math.random() - 0.5) * shakeAmount;}});ctx.save();ctx.translate(shakeX, shakeY);ctx.clearRect(-shakeX, -shakeY, GAME_WIDTH, GAME_HEIGHT);obstacles.forEach(obstacle => obstacle.draw());player.draw();player.drawBullets();enemies.forEach(enemy => {enemy.draw();enemy.drawBullets();});explosions.forEach(explosion => explosion.draw());ctx.restore();drawObstacleChangeNotification();}function gameLoop() {if (!gameRunning) return;if (player.shootCooldown > 0) {player.shootCooldown--;}handleInput();player.updateBullets(obstacles);updateEnemies();checkBulletCollisions();// 检查是否需要变换障碍物checkObstacleChange();explosions.forEach(explosion => explosion.update());explosions = explosions.filter(explosion => !explosion.isComplete());updateObstacleChangeNotification();draw();animationId = requestAnimationFrame(gameLoop);}function checkObstacleChange() {// 每增加10分变换一次障碍物if (score - lastObstacleChangeScore >= 10) {createObstacles();lastObstacleChangeScore = score;showObstacleChangeNotification();ensurePlayerNotInObstacle();}}// 事件监听器document.addEventListener('keydown', (e) => {keys[e.key] = true;// 处理R键重新开始游戏if (e.key === 'r' || e.key === 'R') {if (!gameRunning) {restartGame();}e.preventDefault();}if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {e.preventDefault();}});document.addEventListener('keyup', (e) => {keys[e.key] = false;});// 初始化游戏initGame();gameLoop();
夜雨聆风
