Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Simple WW2 RTS</title>
- <style>
- body { margin: 0; overflow: hidden; background-color: #222; color: #eee; font-family: sans-serif; }
- canvas { display: block; background-color: #6B8E23; /* Olive Drab */ cursor: crosshair; }
- #ui { position: absolute; bottom: 10px; left: 10px; background-color: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; color: #fff; }
- #info { position: absolute; top: 10px; right: 10px; background-color: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; color: #fff; min-width: 150px; }
- button { margin: 2px; padding: 5px; cursor: pointer; }
- </style>
- </head>
- <body>
- <canvas id="gameCanvas"></canvas>
- <div id="ui">
- <div>Supplies: <span id="supplies">0</span></div>
- <div id="build-buttons">
- <!-- Buttons added dynamically based on selection -->
- </div>
- </div>
- <div id="info">
- Selected: <span id="selected-info">None</span><br>
- HP: <span id="selected-hp">-</span>
- </div>
- <script>
- const canvas = document.getElementById('gameCanvas');
- const ctx = canvas.getContext('2d');
- const ui = document.getElementById('ui');
- const suppliesDisplay = document.getElementById('supplies');
- const buildButtonsDiv = document.getElementById('build-buttons');
- const selectedInfoDisplay = document.getElementById('selected-info');
- const selectedHpDisplay = document.getElementById('selected-hp');
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- // --- Game Constants ---
- const TILE_SIZE = 32; // Not really used for grid, just scaling
- const HQ_COST = 0; // Starting building
- const BARRACKS_COST = 150;
- const WORKER_COST = 50;
- const RIFLEMAN_COST = 75;
- const GATHER_AMOUNT = 10;
- const GATHER_RADIUS = 50;
- const UNIT_SPEED = 2;
- const ATTACK_RANGE = 80;
- const ATTACK_DAMAGE = 5;
- const ATTACK_COOLDOWN = 60; // frames
- const AI_UPDATE_INTERVAL = 120; // frames
- // --- Game State ---
- let gameObjects = [];
- let resources = [];
- let playerSupplies = 200;
- let aiSupplies = 200;
- let selectedObject = null;
- let frameCount = 0;
- let gameOver = false;
- let winner = null;
- // --- Helper Functions ---
- function distance(x1, y1, x2, y2) {
- return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
- }
- function isColliding(obj1, obj2) {
- if (!obj1 || !obj2 || obj1 === obj2) return false;
- const dist = distance(obj1.x, obj1.y, obj2.x, obj2.y);
- return dist < (obj1.size / 2 + obj2.size / 2);
- }
- function findClosest(obj, list, filterFn = () => true) {
- let closest = null;
- let minDist = Infinity;
- for (const target of list) {
- if (obj === target || !filterFn(target)) continue;
- const d = distance(obj.x, obj.y, target.x, target.y);
- if (d < minDist) {
- minDist = d;
- closest = target;
- }
- }
- return { target: closest, distance: minDist };
- }
- function getObjectsInRadius(x, y, radius, list, filterFn = () => true) {
- const nearby = [];
- for (const obj of list) {
- if (!filterFn(obj)) continue;
- const d = distance(x, y, obj.x, obj.y);
- if (d <= radius) {
- nearby.push(obj);
- }
- }
- return nearby;
- }
- // --- Game Object Classes (using functions for simplicity) ---
- function createBuilding(x, y, type, owner) {
- const building = {
- id: Date.now() + Math.random(),
- x, y, type, owner,
- maxHp: type === 'HQ' ? 1000 : 500,
- hp: type === 'HQ' ? 1000 : 500,
- size: type === 'HQ' ? 80 : 60,
- isBuilding: true,
- productionQueue: [],
- productionProgress: 0,
- rallyPoint: { x: x + 60, y: y + 60},
- target: null, // Needed for AI targeting consistency
- draw() {
- ctx.fillStyle = owner === 'player' ? 'blue' : 'red';
- ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
- ctx.strokeStyle = 'black';
- ctx.strokeRect(this.x - this.size / 2, this.y - this.size / 2, this.size, this.size);
- // Health bar
- const hpRatio = this.hp / this.maxHp;
- ctx.fillStyle = 'red';
- ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2 - 10, this.size, 5);
- ctx.fillStyle = 'green';
- ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2 - 10, this.size * hpRatio, 5);
- // Rally point
- if (selectedObject === this && this.rallyPoint) {
- ctx.strokeStyle = 'yellow';
- ctx.beginPath();
- ctx.moveTo(this.x, this.y);
- ctx.lineTo(this.rallyPoint.x, this.rallyPoint.y);
- ctx.stroke();
- ctx.strokeRect(this.rallyPoint.x - 5, this.rallyPoint.y - 5, 10, 10);
- }
- // Production progress
- if (this.productionQueue.length > 0) {
- const progressRatio = this.productionProgress / getProductionTime(this.productionQueue[0]);
- ctx.fillStyle = 'grey';
- ctx.fillRect(this.x - this.size / 2, this.y + this.size / 2 + 2, this.size, 5);
- ctx.fillStyle = 'lightblue';
- ctx.fillRect(this.x - this.size / 2, this.y + this.size / 2 + 2, this.size * progressRatio, 5);
- }
- },
- update() {
- if (this.hp <= 0) {
- this.remove();
- if (this.type === 'HQ') {
- gameOver = true;
- winner = this.owner === 'player' ? 'AI' : 'Player';
- }
- return;
- }
- // Production
- if (this.productionQueue.length > 0) {
- this.productionProgress++;
- const neededTime = getProductionTime(this.productionQueue[0]);
- if (this.productionProgress >= neededTime) {
- const unitType = this.productionQueue.shift();
- const spawnPos = this.rallyPoint || {x: this.x + this.size/2 + 20, y: this.y};
- const newUnit = createUnit(spawnPos.x, spawnPos.y, unitType, this.owner);
- gameObjects.push(newUnit);
- // If rally point is on an enemy, set attack move
- const rallyTarget = findClosest(newUnit, gameObjects, o => o.owner !== this.owner && distance(spawnPos.x, spawnPos.y, o.x, o.y) < 15);
- if(rallyTarget.target){
- newUnit.command = { type: 'attack', target: rallyTarget.target };
- } else {
- newUnit.command = { type: 'move', x: spawnPos.x, y: spawnPos.y }; // Move to rally point initially
- }
- this.productionProgress = 0;
- }
- }
- },
- queueUnit(unitType) {
- if ( (this.type === 'HQ' && unitType === 'Worker') ||
- (this.type === 'Barracks' && unitType === 'Rifleman') ) {
- const cost = unitType === 'Worker' ? WORKER_COST : RIFLEMAN_COST;
- if (this.owner === 'player' && playerSupplies >= cost) {
- playerSupplies -= cost;
- this.productionQueue.push(unitType);
- } else if (this.owner === 'ai' && aiSupplies >= cost) {
- aiSupplies -= cost;
- this.productionQueue.push(unitType);
- }
- }
- },
- setRallyPoint(x, y) {
- this.rallyPoint = {x, y};
- // If right click on enemy unit/building, set as attack target for rally
- let targetObject = null;
- for (const obj of gameObjects) {
- if (obj.owner !== this.owner && distance(x, y, obj.x, obj.y) < obj.size / 2) {
- targetObject = obj;
- break;
- }
- }
- // We won't implement attack-rally here for simplicity, just move rally.
- },
- remove() {
- gameObjects = gameObjects.filter(o => o.id !== this.id);
- if (selectedObject === this) selectedObject = null;
- }
- };
- return building;
- }
- function getProductionTime(unitType) {
- switch(unitType) {
- case 'Worker': return 180; // 3 seconds at 60fps
- case 'Rifleman': return 300; // 5 seconds at 60fps
- default: return 60;
- }
- }
- function createUnit(x, y, type, owner) {
- const unit = {
- id: Date.now() + Math.random(),
- x, y, type, owner,
- maxHp: type === 'Worker' ? 50 : 100,
- hp: type === 'Worker' ? 50 : 100,
- size: type === 'Worker' ? 15 : 20,
- speed: UNIT_SPEED,
- isBuilding: false,
- command: { type: 'idle' }, // { type: 'move', x, y }, { type: 'gather', targetResource, targetHQ }, { type: 'attack', target }
- target: null, // Current move/attack target object
- attackCooldown: 0,
- resourceCarried: 0,
- draw() {
- ctx.beginPath();
- ctx.arc(this.x, this.y, this.size / 2, 0, Math.PI * 2);
- ctx.fillStyle = owner === 'player' ? (type === 'Worker' ? 'lightblue' : 'darkblue') : (type === 'Worker' ? 'lightcoral' : 'darkred');
- ctx.fill();
- ctx.strokeStyle = 'black';
- ctx.stroke();
- // Health bar
- const hpRatio = this.hp / this.maxHp;
- ctx.fillStyle = 'red';
- ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2 - 8, this.size, 3);
- ctx.fillStyle = 'green';
- ctx.fillRect(this.x - this.size / 2, this.y - this.size / 2 - 8, this.size * hpRatio, 3);
- // Resource carried indicator (for worker)
- if (this.type === 'Worker' && this.resourceCarried > 0) {
- ctx.fillStyle = 'gold';
- ctx.beginPath();
- ctx.arc(this.x + this.size/3, this.y - this.size/3, 4, 0, Math.PI*2);
- ctx.fill();
- }
- },
- update() {
- if (this.hp <= 0) {
- this.remove();
- return;
- }
- this.attackCooldown = Math.max(0, this.attackCooldown - 1);
- // --- Command Execution ---
- if (this.command.type === 'move') {
- this.target = null; // Clear attack target when moving
- const dx = this.command.x - this.x;
- const dy = this.command.y - this.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > this.speed) {
- this.x += (dx / dist) * this.speed;
- this.y += (dy / dist) * this.speed;
- } else {
- this.x = this.command.x;
- this.y = this.command.y;
- this.command = { type: 'idle' };
- }
- } else if (this.command.type === 'gather') {
- if (this.resourceCarried === 0) { // Move to resource
- const targetRes = this.command.targetResource;
- if (!targetRes || targetRes.amount <= 0) { // Resource depleted or destroyed
- this.command = { type: 'idle'};
- // Find new nearby resource? (Simplification: just idle)
- return;
- }
- const dx = targetRes.x - this.x;
- const dy = targetRes.y - this.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > this.speed + targetRes.size/2) {
- this.x += (dx / dist) * this.speed;
- this.y += (dy / dist) * this.speed;
- } else {
- // Arrived at resource node
- this.resourceCarried = GATHER_AMOUNT;
- targetRes.amount -= GATHER_AMOUNT; // Simplification: instant gather
- if (targetRes.amount <= 0) {
- resources = resources.filter(r => r.id !== targetRes.id);
- // Potentially need to re-assign other workers targeting this
- }
- }
- } else { // Have resources, move to HQ
- const targetHQ = this.command.targetHQ;
- if(!targetHQ || targetHQ.hp <= 0) { // HQ destroyed
- this.command = { type: 'idle'};
- // Drop resources? (Simplification: just idle)
- this.resourceCarried = 0; // Lose resources
- return;
- }
- const dx = targetHQ.x - this.x;
- const dy = targetHQ.y - this.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > this.speed + targetHQ.size/2) {
- this.x += (dx / dist) * this.speed;
- this.y += (dy / dist) * this.speed;
- } else {
- // Arrived at HQ
- if (this.owner === 'player') playerSupplies += this.resourceCarried;
- else aiSupplies += this.resourceCarried;
- this.resourceCarried = 0;
- // Automatically go back to gathering? (Simplification: yes)
- if (this.command.targetResource && this.command.targetResource.amount > 0) {
- // command stays 'gather', loop continues
- } else {
- // Find new resource if original depleted? (Simplification: idle)
- this.command = {type: 'idle'};
- }
- }
- }
- } else if (this.command.type === 'attack') {
- this.target = this.command.target;
- if (!this.target || this.target.hp <= 0) {
- this.command = { type: 'idle' };
- this.target = null;
- return;
- }
- const distToTarget = distance(this.x, this.y, this.target.x, this.target.y);
- if (distToTarget <= ATTACK_RANGE) {
- // Stop moving and attack
- if (this.attackCooldown <= 0) {
- this.target.hp -= ATTACK_DAMAGE;
- this.attackCooldown = ATTACK_COOLDOWN;
- // Basic attack animation/sound placeholder
- // console.log(`${this.owner} ${this.type} attacking ${this.target.type}`);
- }
- } else {
- // Move towards target
- const dx = this.target.x - this.x;
- const dy = this.target.y - this.y;
- const dist = Math.sqrt(dx * dx + dy * dy);
- if (dist > this.speed) {
- this.x += (dx / dist) * this.speed;
- this.y += (dy / dist) * this.speed;
- }
- }
- } else { // Idle or finished command
- // Auto-attack nearby enemies if not worker
- if (this.type === 'Rifleman' && this.attackCooldown <= 0) {
- const nearbyEnemies = getObjectsInRadius(this.x, this.y, ATTACK_RANGE * 1.5, gameObjects, // Scan slightly further than attack range
- (obj) => obj.owner !== this.owner && obj.hp > 0);
- if (nearbyEnemies.length > 0) {
- // Find the closest enemy
- let closestEnemy = null;
- let minDist = Infinity;
- for(const enemy of nearbyEnemies){
- const d = distance(this.x, this.y, enemy.x, enemy.y);
- if(d < minDist){
- minDist = d;
- closestEnemy = enemy;
- }
- }
- if (closestEnemy) {
- this.command = { type: 'attack', target: closestEnemy };
- }
- }
- }
- }
- // Basic collision avoidance (push apart) - VERY basic
- for (const other of gameObjects) {
- if (this !== other && !other.isBuilding && isColliding(this, other)) {
- const dx = other.x - this.x;
- const dy = other.y - this.y;
- const dist = Math.sqrt(dx*dx + dy*dy);
- const overlap = (this.size / 2 + other.size / 2) - dist;
- if (overlap > 0 && dist > 0.1) { // prevent division by zero
- const pushX = (dx / dist) * overlap * 0.1; // Push gently
- const pushY = (dy / dist) * overlap * 0.1;
- this.x -= pushX;
- this.y -= pushY;
- // other.x += pushX; // Avoid double processing
- // other.y += pushY;
- } else if (dist <= 0.1) { // Directly on top? Nudge randomly
- this.x += (Math.random() - 0.5) * 2;
- this.y += (Math.random() - 0.5) * 2;
- }
- }
- }
- // Keep units within bounds
- this.x = Math.max(this.size / 2, Math.min(canvas.width - this.size / 2, this.x));
- this.y = Math.max(this.size / 2, Math.min(canvas.height - this.size / 2, this.y));
- },
- remove() {
- gameObjects = gameObjects.filter(o => o.id !== this.id);
- // If this unit was a target, clear the attacker's command
- gameObjects.forEach(obj => {
- if (obj.command && obj.command.target === this) {
- obj.command = { type: 'idle' };
- obj.target = null;
- }
- if(obj.target === this) {
- obj.target = null;
- }
- });
- if (selectedObject === this) selectedObject = null;
- },
- setCommand(command) {
- // If gathering, find closest HQ
- if (command.type === 'gather') {
- const { target: closestHQ } = findClosest(this, gameObjects, obj => obj.owner === this.owner && obj.type === 'HQ');
- if (closestHQ) {
- command.targetHQ = closestHQ;
- this.command = command;
- } else {
- console.log("No HQ found for drop-off!"); // Cannot gather without HQ
- this.command = { type: 'idle' };
- }
- } else {
- this.command = command;
- }
- }
- };
- return unit;
- }
- function createResourceNode(x, y, amount) {
- const resource = {
- id: Date.now() + Math.random(),
- x, y, amount,
- maxAmount: amount,
- size: 40,
- isResource: true, // For type checking if needed
- draw() {
- ctx.fillStyle = 'gold';
- ctx.beginPath();
- // Simple representation: multiple small circles
- for(let i=0; i< 5; i++){
- ctx.arc(
- this.x + (Math.random() - 0.5) * this.size * 0.6,
- this.y + (Math.random() - 0.5) * this.size * 0.6,
- this.size / 5, 0, Math.PI * 2);
- }
- ctx.fill();
- ctx.strokeStyle = 'darkgoldenrod';
- ctx.stroke();
- // Optional: Show remaining amount visually (e.g., text or bar)
- ctx.fillStyle = 'black';
- ctx.font = '10px sans-serif';
- ctx.textAlign = 'center';
- ctx.fillText(Math.round(this.amount), this.x, this.y + this.size/2 + 10);
- },
- update() {
- // Resources themselves don't do much, maybe regenerate slowly?
- if (this.amount <= 0 && resources.includes(this)) {
- resources = resources.filter(r => r.id !== this.id);
- // Ensure workers targeting this stop
- gameObjects.forEach(obj => {
- if (obj.command && obj.command.type === 'gather' && obj.command.targetResource === this) {
- obj.command = { type: 'idle' };
- }
- });
- }
- }
- };
- return resource;
- }
- // --- AI Logic ---
- let aiState = {
- lastActionFrame: 0,
- desiredWorkers: 5,
- desiredSoldiers: 10,
- attackTriggerThreshold: 5, // Attack when having this many soldiers
- isAttacking: false
- };
- function updateAI() {
- if (gameOver) return;
- if (frameCount - aiState.lastActionFrame < AI_UPDATE_INTERVAL) return;
- aiState.lastActionFrame = frameCount;
- const aiHQ = gameObjects.find(o => o.owner === 'ai' && o.type === 'HQ');
- const aiBarracks = gameObjects.find(o => o.owner === 'ai' && o.type === 'Barracks');
- const aiWorkers = gameObjects.filter(o => o.owner === 'ai' && o.type === 'Worker');
- const aiSoldiers = gameObjects.filter(o => o.owner === 'ai' && o.type === 'Rifleman');
- const playerBuildings = gameObjects.filter(o => o.owner === 'player' && o.isBuilding);
- const playerUnits = gameObjects.filter(o => o.owner === 'player' && !o.isBuilding);
- const allPlayerObjects = [...playerBuildings, ...playerUnits];
- if (!aiHQ) return; // AI lost
- // 1. Build Workers if needed and possible
- if (aiWorkers.length < aiState.desiredWorkers && aiSupplies >= WORKER_COST && aiHQ.productionQueue.length === 0) {
- aiHQ.queueUnit('Worker');
- // console.log("AI queuing Worker");
- return; // Do one action per update interval for simplicity
- }
- // 2. Assign Idle Workers to gather
- const idleWorkers = aiWorkers.filter(w => w.command.type === 'idle' || (w.command.type === 'gather' && (!w.command.targetResource || w.command.targetResource.amount <= 0)));
- if (idleWorkers.length > 0) {
- const { target: closestResource } = findClosest(idleWorkers[0], resources, r => r.amount > 0);
- if (closestResource) {
- idleWorkers[0].setCommand({type: 'gather', targetResource: closestResource});
- // console.log("AI assigning Worker to gather");
- return;
- }
- }
- // 3. Build Barracks if none exists and affordable
- if (!aiBarracks && aiSupplies >= BARRACKS_COST) {
- // Find a suitable location near HQ, not overlapping
- const buildX = aiHQ.x + aiHQ.size + 40;
- const buildY = aiHQ.y;
- const newBarracks = createBuilding(buildX, buildY, 'Barracks', 'ai');
- gameObjects.push(newBarracks);
- aiSupplies -= BARRACKS_COST;
- // console.log("AI building Barracks");
- return;
- }
- // 4. Build Soldiers if Barracks exists and affordable
- if (aiBarracks && aiSoldiers.length < aiState.desiredSoldiers && aiSupplies >= RIFLEMAN_COST && aiBarracks.productionQueue.length === 0) {
- aiBarracks.queueUnit('Rifleman');
- // console.log("AI queuing Rifleman");
- return;
- }
- // 5. Basic Attack Logic
- const idleSoldiers = aiSoldiers.filter(s => s.command.type === 'idle');
- if ((aiSoldiers.length >= aiState.attackTriggerThreshold || aiState.isAttacking) && idleSoldiers.length > 0) {
- aiState.isAttacking = true; // Latch attacking state once triggered
- const playerHQ = gameObjects.find(o => o.owner === 'player' && o.type === 'HQ');
- let target = playerHQ; // Default target HQ
- // Find closest player object if no HQ or want more dynamic target
- if (!target && allPlayerObjects.length > 0) {
- const { target: closestPlayerThing } = findClosest(idleSoldiers[0], allPlayerObjects);
- target = closestPlayerThing;
- }
- if (target) {
- // Send all idle soldiers to attack
- idleSoldiers.forEach(soldier => {
- soldier.setCommand({ type: 'attack', target: target });
- });
- // console.log(`AI sending ${idleSoldiers.length} soldiers to attack`);
- } else {
- // No targets left? Stop attacking state maybe?
- aiState.isAttacking = false;
- }
- }
- // If not attacking and many soldiers idle, maybe move them defensively? (Simplification: skip)
- }
- // --- Input Handling ---
- function handleMouseClick(event) {
- if (gameOver) return;
- const rect = canvas.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- let clickedObject = null;
- // Check units first (smaller targets)
- for (const obj of gameObjects.filter(o => !o.isBuilding).reverse()) { // Check top-most first
- if (distance(x, y, obj.x, obj.y) < obj.size / 2) {
- clickedObject = obj;
- break;
- }
- }
- // Check buildings if no unit clicked
- if (!clickedObject) {
- for (const obj of gameObjects.filter(o => o.isBuilding).reverse()) {
- if (x >= obj.x - obj.size / 2 && x <= obj.x + obj.size / 2 &&
- y >= obj.y - obj.size / 2 && y <= obj.y + obj.size / 2) {
- clickedObject = obj;
- break;
- }
- }
- }
- if (clickedObject && clickedObject.owner === 'player') {
- selectedObject = clickedObject;
- } else {
- selectedObject = null; // Clicked empty space or enemy unit
- }
- updateUI();
- }
- function handleRightClick(event) {
- event.preventDefault(); // Prevent context menu
- if (gameOver || !selectedObject || selectedObject.owner !== 'player') return;
- const rect = canvas.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- // Check if right-clicking on an enemy unit/building
- let targetObject = null;
- for (const obj of gameObjects) {
- if (obj.owner === 'ai' && distance(x, y, obj.x, obj.y) < obj.size / 2) {
- targetObject = obj;
- break;
- }
- }
- // Check if right-clicking on a resource node (only for workers)
- let targetResource = null;
- if (selectedObject.type === 'Worker') {
- for (const res of resources) {
- if (distance(x, y, res.x, res.y) < res.size / 2) {
- targetResource = res;
- break;
- }
- }
- }
- if (selectedObject.isBuilding) {
- selectedObject.setRallyPoint(x, y);
- } else { // Selected object is a Unit
- if (targetObject) { // Right-clicked an enemy
- if (selectedObject.type === 'Rifleman') { // Only riflemen attack
- selectedObject.setCommand({ type: 'attack', target: targetObject });
- } else { // Workers just move near enemy if right-clicked
- selectedObject.setCommand({ type: 'move', x: x, y: y });
- }
- } else if (targetResource && selectedObject.type === 'Worker') { // Right-clicked a resource with worker selected
- selectedObject.setCommand({ type: 'gather', targetResource: targetResource });
- }
- else { // Right-clicked empty ground or friendly unit
- selectedObject.setCommand({ type: 'move', x: x, y: y });
- }
- }
- updateUI(); // Update UI in case command changed something relevant
- }
- // --- UI Update ---
- function updateUI() {
- suppliesDisplay.textContent = playerSupplies;
- if (selectedObject) {
- selectedInfoDisplay.textContent = `${selectedObject.owner}'s ${selectedObject.type}`;
- selectedHpDisplay.textContent = `${selectedObject.hp} / ${selectedObject.maxHp}`;
- buildButtonsDiv.innerHTML = ''; // Clear previous buttons
- if (selectedObject.type === 'HQ') {
- const btn = document.createElement('button');
- btn.textContent = `Build Worker (${WORKER_COST} S)`;
- btn.onclick = () => {
- if (playerSupplies >= WORKER_COST) {
- selectedObject.queueUnit('Worker');
- updateUI(); // Refresh supply count immediately
- } else {
- console.log("Not enough supplies!"); // Basic feedback
- }
- };
- if (playerSupplies < WORKER_COST || selectedObject.productionQueue.length > 0) btn.disabled = true; // Basic check
- buildButtonsDiv.appendChild(btn);
- const btnBarracks = document.createElement('button');
- btnBarracks.textContent = `Build Barracks (${BARRACKS_COST} S)`;
- btnBarracks.onclick = () => {
- if (playerSupplies >= BARRACKS_COST) {
- // Simplification: Build next to HQ
- const buildX = selectedObject.x + selectedObject.size + 40;
- const buildY = selectedObject.y;
- // TODO: Add placement logic / ghost building
- const newBarracks = createBuilding(buildX, buildY, 'Barracks', 'player');
- gameObjects.push(newBarracks);
- playerSupplies -= BARRACKS_COST;
- updateUI();
- } else {
- console.log("Not enough supplies!");
- }
- };
- if (playerSupplies < BARRACKS_COST) btnBarracks.disabled = true;
- buildButtonsDiv.appendChild(btnBarracks);
- } else if (selectedObject.type === 'Barracks') {
- const btn = document.createElement('button');
- btn.textContent = `Train Rifleman (${RIFLEMAN_COST} S)`;
- btn.onclick = () => {
- if (playerSupplies >= RIFLEMAN_COST) {
- selectedObject.queueUnit('Rifleman');
- updateUI();
- } else {
- console.log("Not enough supplies!");
- }
- };
- if (playerSupplies < RIFLEMAN_COST || selectedObject.productionQueue.length > 0) btn.disabled = true;
- buildButtonsDiv.appendChild(btn);
- }
- // Add more buttons for other selected types here...
- } else {
- selectedInfoDisplay.textContent = 'None';
- selectedHpDisplay.textContent = '-';
- buildButtonsDiv.innerHTML = ''; // Clear buttons
- }
- }
- // --- Game Loop ---
- function gameLoop() {
- if (gameOver) {
- ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.fillStyle = "white";
- ctx.font = "48px sans-serif";
- ctx.textAlign = "center";
- ctx.fillText(`${winner} Wins!`, canvas.width / 2, canvas.height / 2);
- ctx.font = "24px sans-serif";
- ctx.fillText("Refresh page to play again", canvas.width / 2, canvas.height / 2 + 50);
- return; // Stop the loop
- }
- // Clear canvas
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // Update AI
- updateAI();
- // Update game objects
- resources.forEach(res => res.update());
- gameObjects.forEach(obj => obj.update()); // Update positions, handle commands, production etc.
- // Draw game objects
- resources.forEach(res => res.draw());
- gameObjects.sort((a, b) => a.y - b.y); // Basic depth sorting (draw lower objects first)
- gameObjects.forEach(obj => obj.draw());
- // Draw selection indicator
- if (selectedObject) {
- ctx.strokeStyle = 'yellow';
- ctx.lineWidth = 2;
- if (selectedObject.isBuilding) {
- ctx.strokeRect(selectedObject.x - selectedObject.size / 2, selectedObject.y - selectedObject.size / 2, selectedObject.size, selectedObject.size);
- } else {
- ctx.beginPath();
- ctx.arc(selectedObject.x, selectedObject.y, selectedObject.size / 2 + 3, 0, Math.PI * 2);
- ctx.stroke();
- }
- ctx.lineWidth = 1; // Reset line width
- }
- // Update UI (supplies are updated constantly here, specific selections on click/action)
- suppliesDisplay.textContent = playerSupplies;
- // AI supplies not shown in this simple UI
- frameCount++;
- requestAnimationFrame(gameLoop);
- }
- // --- Initialization ---
- function init() {
- // Create starting HQs
- const playerHQ = createBuilding(150, canvas.height / 2, 'HQ', 'player');
- const aiHQ = createBuilding(canvas.width - 150, canvas.height / 2, 'HQ', 'ai');
- gameObjects.push(playerHQ, aiHQ);
- // Create starting workers
- gameObjects.push(createUnit(playerHQ.x + playerHQ.size, playerHQ.y, 'Worker', 'player'));
- gameObjects.push(createUnit(playerHQ.x + playerHQ.size, playerHQ.y + 30, 'Worker', 'player'));
- gameObjects.push(createUnit(aiHQ.x - aiHQ.size, aiHQ.y, 'Worker', 'ai'));
- gameObjects.push(createUnit(aiHQ.x - aiHQ.size, aiHQ.y + 30, 'Worker', 'ai'));
- // Create resource patches
- resources.push(createResourceNode(300, canvas.height / 2 - 100, 500));
- resources.push(createResourceNode(300, canvas.height / 2 + 100, 500));
- resources.push(createResourceNode(canvas.width - 300, canvas.height / 2 - 100, 500));
- resources.push(createResourceNode(canvas.width - 300, canvas.height / 2 + 100, 500));
- resources.push(createResourceNode(canvas.width/2, 100, 800)); // Center contested?
- resources.push(createResourceNode(canvas.width/2, canvas.height - 100, 800));
- // Add event listeners
- canvas.addEventListener('click', handleMouseClick);
- canvas.addEventListener('contextmenu', handleRightClick); // Use contextmenu for right-click
- window.addEventListener('resize', () => {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- // Could potentially reposition camera or objects on resize, but keep simple
- });
- updateUI();
- // Start the game loop
- gameLoop();
- }
- // --- Start the game ---
- init();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement