<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Galactic Tycoon</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
<style>
body { margin: 0; font-family: 'Space Mono', monospace; background: #000; color: #0f0; overflow: hidden; }
#game-panel {
position: absolute;
top: 10px;
left: 10px;
width: 250px;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 8px;
border: 2px solid #0f0;
box-shadow: 0 0 15px #0f0;
}
#game-panel h3 {
color: #0f0;
text-align: center;
margin-top: 0;
border-bottom: 1px solid #0f0;
padding-bottom: 8px;
}
#game-panel button {
display: block;
width: calc(100% - 10px);
padding: 10px;
margin: 8px 5px;
background: #004d00;
color: #0f0;
border: 1px solid #0f0;
border-radius: 5px;
cursor: pointer;
font-family: 'Space Mono', monospace;
font-size: 1em;
transition: background 0.2s, color 0.2s;
}
#game-panel button:hover {
background: #008000;
color: #fff;
}
#game-panel button:disabled {
background: #333;
color: #666;
border-color: #666;
cursor: not-allowed;
}
.resource-display {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 1.1em;
}
canvas { display: block; }
</style>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono&display=swap" rel="stylesheet">
</head>
<body>
<div id="game-panel">
<h3>Galactic Tycoon</h3>
<p class="resource-display">Credits: <span id="credits">0</span></p>
<p class="resource-display">Metal: <span id="metal">0</span></p>
<p class="resource-display">Water: <span id="water">0</span></p>
<hr style="border-color: #0f0;">
<button id="collect-button" onclick="window.collectResources()">Collect Resources</button>
<button id="buy-asteroid-button" onclick="window.buyBody('Asteroid')" disabled>Buy Asteroid (100 C)</button>
<button id="buy-water-asteroid-button" onclick="window.buyBody('Water Asteroid')" disabled>Buy Water Asteroid (150 C)</button>
<button id="buy-planet-button" onclick="window.buyBody('Planet')" disabled>Buy Planet (500 M, 200 W)</button>
<button onclick="window.resetGame()">Reset Game</button>
</div>
<script>
let bodies = [];
const G = 0.5; // Gravitational constant
const AU = 100; // Astronomical Unit (scaling factor for distances)
let credits = 100; // Starting credits
let metal = 0;
let water = 0;
let cameraZoom = 1;
let cameraOffset = { x: 0, y: 0 };
let draggedBody = null;
let dragStartPos = null;
// Simplified star type for the game's initial setup
const initialStarType = { type: 'Yellow Dwarf', mass: 5000, radius: 40, color: '#FFFF00' };
function setup() {
createCanvas(windowWidth, windowHeight);
resetGame();
}
function draw() {
background(0);
push();
translate(width / 2, height / 2);
scale(cameraZoom);
translate(-width / 2 + cameraOffset.x, -height / 2 + cameraOffset.y);
// Update and draw bodies
for (let i = 0; i < bodies.length; i++) {
for (let j = 0; j < bodies.length; j++) {
if (i !== j && bodies[i] && bodies[j]) {
bodies[i].applyGravity(bodies[j]);
}
}
}
for (let body of bodies) {
if (body) {
body.update();
body.draw();
}
}
pop();
updateGamePanel();
}
class CelestialBody {
constructor(x, y, mass, radius, color, type, velocity = null, starType = null, name = null, parent = null) {
this.pos = createVector(x, y);
this.vel = velocity || createVector(0, 0);
this.mass = mass;
this.radius = radius;
this.color = color;
this.type = type;
this.starType = starType;
this.name = name || this.generateName();
this.star = type === 'star' ? null : parent || bodies.find(b => b && b.type === 'star') || null;
this.trail = [];
this.trailLength = type === 'comet' ? 50 : 20;
}
generateName() {
const prefixes = ['Astro', 'Cosmo', 'Nova', 'Star', 'Zenith', 'Orion'];
const suffixes = ['rock', 'field', 'belt', 'cluster', 'prime', 'ia'];
return prefixes[Math.floor(Math.random() * prefixes.length)] + suffixes[Math.floor(Math.random() * suffixes.length)];
}
applyGravity(other) {
if (this === other || !other || this.type === 'star' && other.type === 'star') return; // Stars don't apply gravity to each other in this simplified version
let force = p5.Vector.sub(other.pos, this.pos);
let distance = force.mag();
// Prevent division by zero and handle very close objects
if (distance < this.radius + other.radius) {
this.handleCollision(other);
return;
}
let strength = (G * this.mass * other.mass) / (distance * distance);
force.setMag(strength);
let acc = force.copy().div(this.mass);
this.vel.add(acc);
}
handleCollision(other) {
if (this.mass <= other.mass) return; // Only the larger body absorbs the smaller
// Simple merge: larger body grows
this.mass += other.mass;
this.radius = Math.pow(this.radius ** 3 + other.radius ** 3, 1 / 3);
// Remove the absorbed body
const index = bodies.indexOf(other);
if (index !== -1) {
bodies.splice(index, 1);
}
// If a player-placed body collides, give some resources
if (other.type === 'asteroid') {
metal += 10;
} else if (other.type === 'water_asteroid') {
water += 10;
}
}
update() {
if (this !== draggedBody) { // Only update position if not being dragged
this.pos.add(this.vel);
}
this.trail.push(this.pos.copy());
if (this.trail.length > this.trailLength) {
this.trail.shift();
}
}
draw() {
push();
// Draw trail
if (this.trail.length > 1) {
noFill();
stroke(color(this.color).setAlpha(50));
strokeWeight(1);
beginShape();
for (let pos of this.trail) {
vertex(pos.x, pos.y);
}
endShape();
}
// Draw body
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
// Add a small pulsating effect for player-collectible asteroids
if (this.type === 'asteroid' || this.type === 'water_asteroid') {
stroke(0, 255, 0, map(sin(frameCount * 0.1), -1, 1, 50, 150));
strokeWeight(2);
noFill();
ellipse(this.pos.x, this.pos.y, this.radius * 2 + 5 + sin(frameCount * 0.1) * 2);
}
pop();
}
}
// --- Game Functions ---
window.collectResources = function() {
let collectedMetal = 0;
let collectedWater = 0;
let bodiesToRemove = [];
for (let i = bodies.length - 1; i >= 0; i--) {
const body = bodies[i];
if (body.type === 'asteroid') {
collectedMetal += Math.floor(body.mass);
bodiesToRemove.push(i);
} else if (body.type === 'water_asteroid') {
collectedWater += Math.floor(body.mass);
bodiesToRemove.push(i);
}
}
// Remove collected bodies from the array
for (let i of bodiesToRemove.sort((a, b) => b - a)) {
bodies.splice(i, 1);
}
metal += collectedMetal;
water += collectedWater;
credits += (collectedMetal * 0.5) + (collectedWater * 0.7); // Credits for collected resources
updateGamePanel();
};
window.buyBody = function(type) {
let costCredits = 0;
let costMetal = 0;
let costWater = 0;
let newBody = null;
const star = bodies[0]; // Assuming the first body is always the star
// Place new bodies at a random offset from the center
let x = width / 2 + (Math.random() * 300 - 150) / cameraZoom + cameraOffset.x;
let y = height / 2 + (Math.random() * 300 - 150) / cameraZoom + cameraOffset.y;
let vel = p5.Vector.random2D().mult(Math.random() * 2 + 1);
switch (type) {
case 'Asteroid':
costCredits = 100;
if (credits >= costCredits) {
newBody = new CelestialBody(x, y, 5 + Math.random() * 5, 5, '#964B00', 'asteroid', vel, null, null, star);
credits -= costCredits;
}
break;
case 'Water Asteroid':
costCredits = 150;
if (credits >= costCredits) {
newBody = new CelestialBody(x, y, 5 + Math.random() * 5, 6, '#00BFFF', 'water_asteroid', vel, null, null, star);
credits -= costCredits;
}
break;
case 'Planet':
costMetal = 500;
costWater = 200;
if (metal >= costMetal && water >= costWater) {
newBody = new CelestialBody(x, y, 200 + Math.random() * 100, 20 + Math.random() * 5, '#1E90FF', 'planet', vel, null, null, star); // A simple planet type
metal -= costMetal;
water -= costWater;
}
break;
}
if (newBody) {
bodies.push(newBody);
// Give the new body an initial velocity to orbit the star
let distToStar = p5.Vector.dist(newBody.pos, star.pos);
if (distToStar > 0) {
let orbitSpeed = Math.sqrt(G * star.mass / distToStar);
let direction = p5.Vector.sub(star.pos, newBody.pos).normalize();
newBody.vel = createVector(-direction.y, direction.x).mult(orbitSpeed);
}
}
updateGamePanel();
};
window.resetGame = function() {
bodies = [];
credits = 100;
metal = 0;
water = 0;
cameraZoom = 1;
cameraOffset = { x: 0, y: 0 };
// Add the initial star
const sun = new CelestialBody(width / 2, height / 2, initialStarType.mass, initialStarType.radius, initialStarType.color, 'star', null, initialStarType.type);
bodies.push(sun);
// Add some initial asteroids for the player to collect
for (let i = 0; i < 5; i++) {
const distFromStar = 200 + Math.random() * 100;
const angle = Math.random() * TWO_PI;
const x = width / 2 + distFromStar * Math.cos(angle);
const y = height / 2 + distFromStar * Math.sin(angle);
const mass = 5 + Math.random() * 5;
const radius = 5;
const type = Math.random() < 0.5 ? 'asteroid' : 'water_asteroid';
const color = type === 'asteroid' ? '#964B00' : '#00BFFF';
const asteroidVel = createVector(-Math.sin(angle), Math.cos(angle)).mult(Math.sqrt(G * sun.mass / distFromStar));
bodies.push(new CelestialBody(x, y, mass, radius, color, type, asteroidVel, null, null, sun));
}
updateGamePanel();
};
function updateGamePanel() {
document.getElementById('credits').textContent = credits.toFixed(0);
document.getElementById('metal').textContent = metal.toFixed(0);
document.getElementById('water').textContent = water.toFixed(0);
// Enable/disable buttons based on resources
document.getElementById('buy-asteroid-button').disabled = credits < 100;
document.getElementById('buy-water-asteroid-button').disabled = credits < 150;
document.getElementById('buy-planet-button').disabled = metal < 500 || water < 200;
document.getElementById('buy-asteroid-button').textContent = `Buy Asteroid (${100} C)`;
document.getElementById('buy-water-asteroid-button').textContent = `Buy Water Asteroid (${150} C)`;
document.getElementById('buy-planet-button').textContent = `Buy Planet (${500} M, ${200} W)`;
}
// --- Camera Controls (simplified from original) ---
function mouseWheel(event) {
let scaleFactor = 1.05;
if (event.delta > 0) {
cameraZoom /= scaleFactor;
} else {
cameraZoom *= scaleFactor;
}
cameraZoom = constrain(cameraZoom, 0.1, 5); // Prevent extreme zoom
}
function mousePressed() {
draggedBody = null; // Reset dragged body on new click
let worldMouse = screenToWorld(mouseX, mouseY);
for (let body of bodies) {
if (dist(worldMouse.x, worldMouse.y, body.pos.x, body.pos.y) < body.radius) {
draggedBody = body;
dragStartPos = worldMouse.copy();
break;
}
}
}
function mouseDragged() {
if (draggedBody && draggedBody.type !== 'star') { // Prevent dragging the star
let worldMouse = screenToWorld(mouseX, mouseY);
draggedBody.pos.set(worldMouse);
draggedBody.trail = []; // Clear trail when dragging
} else if (!draggedBody) { // If no body is dragged, drag the camera
cameraOffset.x += (mouseX - pmouseX) / cameraZoom;
cameraOffset.y += (mouseY - pmouseY) / cameraZoom;
}
}
function mouseReleased() {
if (draggedBody && dragStartPos) {
let worldMouse = screenToWorld(mouseX, mouseY);
let dragDistance = p5.Vector.sub(worldMouse, dragStartPos);
// Apply a velocity based on drag
draggedBody.vel = dragDistance.mult(0.1);
}
draggedBody = null;
dragStartPos = null;
}
// Helper to convert screen coordinates to world coordinates
function screenToWorld(sx, sy) {
let wx = (sx - width / 2) / cameraZoom + width / 2 - cameraOffset.x;
let wy = (sy - height / 2) / cameraZoom + height / 2 - cameraOffset.y;
return createVector(wx, wy);
}
</script>
</body>
</html>