Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# Factory_Launch
# Factory Launch

Factory Launch is a research-driven resource management and automation prototype inspired by **Factorio** and **Immortality Factory**. The project now includes a browser-playable build that can be hosted on GitHub Pages for quick playtesting alongside the original Python simulation code.

## What's Included

- **Static Web Client** – `index.html`, `css/`, and `js/` provide a standalone build that runs entirely in the browser. The interface now renders a grid-based launch site where you click deposits to gather materials, place machines, contribute to research, and refuel your production line.
- **Simulation Logic in JavaScript** – Modular ES6 scripts (`js/gameState.js`, `js/machines.js`, `js/storage.js`, and `js/constants.js`) mirror the original game systems and drive the UI. Everything runs locally with no server dependencies, making it ideal for GitHub Pages hosting.
- **Python Core Simulation (Legacy)** – The earlier Python game-state package and tests remain in place for reference and future backend tooling.

## Getting Started

Open `index.html` directly in a browser or serve the repository with any static web host. For GitHub Pages, place the repository on a branch configured for Pages (for example `main` or `gh-pages`) and enable Pages in the repository settings—the client works without any build step.

Local development using a lightweight HTTP server:

```bash
python -m http.server 8000
# then visit http://localhost:8000
```

## Gameplay Overview

1. **Manual Gathering** – Click resource deposits on the grid to collect the starting stone, coal, iron, and copper required for the first research node.
2. **Research Progression** – Contribute resources to unlock coal-powered drills and the stone smelter. Progress bars reflect how close you are to completing the initial tech.
3. **Coal-Powered Drills** – Select the drill tool from the build toolbar and place drills directly on deposits to automate mining, then keep them fueled with coal.
4. **Stone Smelter** – Place the smelter on open ground, switch between iron and copper plate recipes, and keep it powered with coal to transform raw ore into processed materials.
5. **Storage & Inventory** – The UI surfaces player inventory and the starting storage chests, honoring stack limits of 50 (player) and 999 (chests).

## Automated Tests

The Python simulation is still covered by a pytest suite:

```bash
pip install -r requirements-dev.txt
pytest
```

The tests validate drill clustering, smelting throughput, research costs, and inventory limits in the Python reference implementation.
345 changes: 345 additions & 0 deletions css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
:root {
--bg: #0e1016;
--bg-panel: rgba(23, 27, 38, 0.95);
--accent: #ffae42;
--accent-dark: #d98b1a;
--text: #f4f5f7;
--muted: #9aa0b5;
--success: #4caf50;
--danger: #ff6b6b;
font-size: 16px;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
background: radial-gradient(circle at top, #1f2a44, #0e1016 60%);
color: var(--text);
min-height: 100vh;
display: flex;
flex-direction: column;
}

img {
max-width: 100%;
display: block;
}

.app-header,
.app-footer {
text-align: center;
padding: 1.5rem 1rem;
background: rgba(15, 19, 29, 0.82);
backdrop-filter: blur(8px);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.app-footer {
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-bottom: none;
margin-top: auto;
}

.tagline {
margin: 0.35rem 0 0;
color: var(--muted);
font-size: 1rem;
}

.layout {
flex: 1;
display: grid;
gap: 1.25rem;
padding: 1.5rem;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
align-content: start;
}

.panel {
background: var(--bg-panel);
border-radius: 14px;
padding: 1.2rem 1.35rem 1.6rem;
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.25);
display: flex;
flex-direction: column;
gap: 1rem;
min-height: 0;
}

.panel h2 {
margin: 0;
font-size: 1.35rem;
letter-spacing: 0.02em;
}

.panel-header {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 1rem;
}

.world-panel {
grid-column: span 2;
}

@media (max-width: 960px) {
.layout {
grid-template-columns: 1fr;
}
.world-panel {
grid-column: span 1;
}
}

button {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.08);
color: var(--text);
border-radius: 10px;
padding: 0.55rem 0.85rem;
cursor: pointer;
transition: transform 0.15s ease, background 0.2s ease, border 0.2s ease;
font: inherit;
}

button:hover:not(:disabled) {
transform: translateY(-1px);
background: rgba(255, 255, 255, 0.14);
}

button:disabled {
cursor: not-allowed;
opacity: 0.6;
}

button.primary {
background: var(--accent);
border-color: var(--accent-dark);
color: #1b1e27;
font-weight: 600;
}

.build-button {
display: grid;
gap: 0.25rem;
align-items: center;
text-align: left;
min-width: 9.5rem;
}

.build-button.active {
border-color: var(--accent);
background: rgba(255, 174, 66, 0.2);
}

.build-button--cancel {
justify-self: flex-end;
}

.build-label {
font-weight: 600;
}

.build-count {
font-size: 0.8rem;
color: var(--muted);
}

.world-toolbar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 0.75rem;
align-items: stretch;
}

.world-grid {
display: grid;
grid-template-columns: repeat(12, minmax(52px, 1fr));
grid-auto-rows: minmax(52px, 1fr);
gap: 0.65rem;
background: rgba(12, 15, 23, 0.7);
padding: 0.85rem;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.05);
}

.world-tile {
position: relative;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(255, 255, 255, 0.04);
display: flex;
align-items: center;
justify-content: center;
aspect-ratio: 1 / 1;
min-height: 52px;
overflow: hidden;
padding: 0.35rem;
transition: border 0.2s ease, transform 0.15s ease;
color: var(--text);
font: inherit;
}

.world-tile::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
pointer-events: none;
}

.world-tile img {
width: 72%;
pointer-events: none;
}

.world-tile:hover:not(:disabled) {
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
}

.world-tile .tile-label {
position: absolute;
bottom: 0.35rem;
left: 0.4rem;
right: 0.4rem;
padding: 0.15rem 0.35rem;
border-radius: 6px;
background: rgba(10, 13, 19, 0.8);
font-size: 0.65rem;
text-align: center;
}

.world-tile--eligible {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(255, 174, 66, 0.3);
}

.world-tile:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

.tile-info {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
padding: 0.75rem;
min-height: 3rem;
}

.inventory-grid,
.storage-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.75rem;
}

.inventory-item,
.storage-item {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 0.75rem;
border: 1px solid rgba(255, 255, 255, 0.06);
display: grid;
gap: 0.45rem;
}

.inventory-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 600;
}

.inventory-title img {
width: 28px;
height: 28px;
}

.meta {
color: var(--muted);
font-size: 0.85rem;
}

.machine-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px;
padding: 0.9rem;
display: grid;
gap: 0.65rem;
}

.machine-card header {
display: flex;
justify-content: space-between;
align-items: baseline;
}

.machine-card select {
width: 100%;
padding: 0.4rem 0.5rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(0, 0, 0, 0.25);
color: var(--text);
}

.machine-card .status {
font-size: 0.9rem;
color: var(--muted);
line-height: 1.4;
}

.badge {
padding: 0.2rem 0.45rem;
border-radius: 999px;
font-size: 0.75rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
}

.research-status {
display: grid;
gap: 0.6rem;
font-size: 0.95rem;
background: rgba(255, 255, 255, 0.05);
padding: 0.9rem;
border-radius: 12px;
}

.progress {
height: 8px;
border-radius: 999px;
overflow: hidden;
background: rgba(255, 255, 255, 0.1);
}

.progress-bar {
height: 100%;
width: 0;
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
transition: width 0.3s ease;
}

.event-log {
display: grid;
gap: 0.4rem;
max-height: 16rem;
overflow-y: auto;
}

.event-log-item {
font-size: 0.85rem;
padding: 0.5rem 0.6rem;
border-radius: 8px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.04);
color: var(--muted);
}
Loading