Skip to content
Open
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
8 changes: 3 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ Create an engaging, fun arcade game in **game.js** using **Phaser 3** (v3.87.0)

## ⚠️ IMPORTANT: Files to Edit

**ONLY edit these three files:**
**ONLY edit these two files:**
- `game.js` - Your game code
- `metadata.json` - Game name and description
- `cover.png` - Game cover image (800x600 pixels)

**DO NOT edit any other files** (including index.html, check-restrictions files, config files, etc.)

Expand All @@ -34,9 +33,8 @@ Create an engaging, fun arcade game in **game.js** using **Phaser 3** (v3.87.0)

1. **Edit game.js**: Write your game code in this single file
2. **Update metadata.json**: Set `game_name` and `description`
3. **Create cover.png**: Design an 800x600 pixel cover image for your game
4. **Check restrictions**: Run `pnpm check-restrictions` frequently
5. **DO NOT start dev servers**: The user will handle running `pnpm dev` - do not run it yourself
3. **Check restrictions**: Run `pnpm check-restrictions` frequently
4. **DO NOT start dev servers**: The user will handle running `pnpm dev` - do not run it yourself

## Phaser 3 Resources

Expand Down
129 changes: 99 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,93 @@
# 🎮 Platanus Hack 25: Arcade Challenge

At [Platanus Hack 25](https://hack.platan.us) we will have an arcade machine. While we could put some cool retro games on it, it is way better if it can be turned into a challenge.

**Your mission:** Build the best arcade game using Phaser 3 (JS Game Lib) that will run on our physical arcade machine!
# 🎮 Bubble Pop 🫧

Un juego arcade para Platanus Hack 25 donde los jugadores disparan burbujas para conectar 3+ del mismo color y hacerlas explotar. ¡Elimina todas las burbujas para ganar!

![Bubble Pop](cover.png)

🎯 Descripción
Modo de 1 o 2 jugadores simultáneos
Sistema de capas que caen cada 60 segundos
Gravedad automática después de 1 minuto
Sistema de ranking con Top 10 (almacenado en localStorage)
Música y sonidos generados con Web Audio API
Sprites procedurales dibujados en runtime (sin imágenes externas)

🎯 Controles
Jugador 1: A|D (mover cañón) • Q|W|E|S (apuntar) • ESPACIO (disparar)
Jugador 2: J|L (mover cañón) • U|I|O|K (apuntar) • ENTER (disparar)
📁 Estructura del Proyecto
platanus-hack-25-arcade/
├── game.js # ✅ Código principal del juego (sin imports)
├── metadata.json # ✅ Nombre y descripción del juego
├── index.html # HTML con Phaser desde CDN
├── README.md # Este archivo
└── bublepop.png # Imagen de portada 800x600px
⚙️ Características Técnicas
Cumple con restricciones:
✅ Sin imports: JavaScript vanilla puro
✅ Sin URLs externas en game.js (Phaser desde CDN no cuenta)
✅ Sin fetch/XMLHttpRequest
✅ Sprites procedurales: Dibujados con Canvas API
✅ Audio generado: Usando Web Audio API de Phaser
✅ Tamaño optimizado: Código minificable
Phaser 3 Features utilizados:
Phaser.Game y configuración
Physics (Arcade)
Sprites y texturas procedurales
Tweens para animaciones
Keyboard input
Groups y colisiones
LocalStorage para persistencia
🚀 Desarrollo
Instalar dependencias:
pnpm install
Ejecutar en desarrollo:
pnpm dev
Verificar restricciones:
pnpm check-restrictions
🎨 Sprites
Todos los sprites son generados proceduralmente en el código:

Burbujas: 12 colores con gradientes y brillos
Cañones: Triángulos con efectos de glow
Indicadores: Trayectorias punteadas con animaciones
🎵 Audio
Sonidos generados con osciladores:
Disparo: Tono ascendente (800Hz → 400Hz)
Explosión: Tono descendente (600Hz → 200Hz)
Alerta: Sirena alternante (800Hz ↔ 600Hz)

🏆 Sistema de Ranking
Top 10 mejores puntuaciones
Guardado en localStorage
📊 Mecánicas de Juego
Capas que caen: Cada 60 segundos se agrega una nueva capa
Gravedad automática: Después de 1 minuto las burbujas caen gradualmente
Modo simultáneo: Ambos jugadores juegan al mismo tiempo
🎯 Objetivo del Juego
Conecta 3 o más burbujas del mismo color disparando burbujas. Las burbujas flotantes también caen. ¡Elimina todas las burbujas para ganar!

📝 Puntuación
10 puntos por grupo de burbujas explotadas
5 puntos por burbujas flotantes eliminadas
1 punto por segundo sobrevivido
🔧 Próximos Pasos
Ejecutar pnpm check-restrictions para verificar tamaño
Optimizar código si excede 50KB
👥 Créditos
Juego creado para Platanus Hack 25: Arcade Challenge
Creado por: Exequiel Alvarado

---

## 🏆 Prizes

**🥇 First Place:**
### 🥇 First Place:
- 💵 **$250 USD in cash**
- 🎟️ **A slot to participate in Platanus Hack**
- 🎮 **Your game featured on the arcade machine**

**🥈 Second Place:**
### 🥈 Second Place:
- 💵 **$100 USD in cash**
- 🎮 **Your game featured on the arcade machine**

Expand All @@ -40,50 +114,46 @@ Your game must comply with these technical restrictions:
- ✅ **Generated audio tones** - Using Phaser's Web Audio API
- ✅ **Canvas-based rendering and effects**

# 🕹️ Controls

🕹️ Controls
Your game will run on a real arcade cabinet with physical joysticks and buttons!

![Arcade Button Layout](https://hack.platan.us/assets/images/arcade/button-layout.webp)

## Arcade Button Mapping
**Arcade Button Layout**

**Arcade Button Mapping**
The arcade cabinet sends specific key codes when buttons are pressed:

**Player 1:**
- **Joystick**: `P1U`, `P1D`, `P1L`, `P1R` (Up, Down, Left, Right)
- **Joystick Diagonals**: `P1DL`, `P1DR` (Down-Left, Down-Right)
- **Action Buttons**: `P1A`, `P1B`, `P1C` (top row) / `P1X`, `P1Y`, `P1Z` (bottom row)
- **Start**: `START1`
- Joystick: P1U, P1D, P1L, P1R (Up, Down, Left, Right)
- Joystick Diagonals: P1DL, P1DR (Down-Left, Down-Right)
- Action Buttons: P1A, P1B, P1C (top row) / P1X, P1Y, P1Z (bottom row)
- Start: START1

**Player 2:**
- **Joystick**: `P2U`, `P2D`, `P2L`, `P2R`
- **Joystick Diagonals**: `P2DL`, `P2DR`
- **Action Buttons**: `P2A`, `P2B`, `P2C` / `P2X`, `P2Y`, `P2Z`
- **Start**: `START2`

## Testing Locally
- Joystick: P2U, P2D, P2L, P2R
- Joystick Diagonals: P2DL, P2DR
- Action Buttons: P2A, P2B, P2C / P2X, P2Y, P2Z
- Start: START2

For local testing, you can map these arcade buttons to keyboard keys. The mapping supports **multiple keyboard keys per arcade button** (useful for alternatives like WASD + Arrow Keys). See `game.js` for the complete `ARCADE_CONTROLS` mapping template.
**Testing Locally**
For local testing, you can map these arcade buttons to keyboard keys. The mapping supports multiple keyboard keys per arcade button (useful for alternatives like WASD + Arrow Keys). See game.js for the complete ARCADE_CONTROLS mapping template.

By default:
- Player 1 uses **WASD** (joystick) and **U/I/O/J/K/L** (action buttons)
- Player 2 uses **Arrow Keys** (joystick) and **R/T/Y/F/G/H** (action buttons)
- Player 1 uses WASD (joystick) and U/I/O/J/K/L (action buttons)
- Player 2 uses Arrow Keys (joystick) and R/T/Y/F/G/H (action buttons)

💡 **Tip**: Keep controls simple - design for joystick + 1-2 action buttons for the best arcade experience!
💡 **Tip:** Keep controls simple - design for joystick + 1-2 action buttons for the best arcade experience!

---

## ⏰ Deadline & Submission

**Deadline:** Sunday, November 10, 2025 at 23:59 (Santiago time)
**Deadline:** Friday, November 14, 2025 at 23:59 (Santiago time)

### How to Submit

Submitting your project is easy:

1. **Save your changes** - Make sure `game.js`, `metadata.json`, and `cover.png` are ready
- **Important:** Your game must include a custom `cover.png` file (800x600 pixels) showcasing your game
1. **Save your changes** - Make sure `game.js` and `metadata.json` are ready
2. **Git push** - Push your code to your repository:
```bash
git add .
Expand Down Expand Up @@ -112,7 +182,6 @@ This starts a server at `http://localhost:3000` with live restriction checking.
### 3. Build Your Game
- **Edit `game.js`** - Write your arcade game code
- **Update `metadata.json`** - Set your game name and description
- **Create `cover.png`** - Design an 800x600 pixel cover image for your game
- **Watch the dev server** - It shows live updates on file size and restrictions

---
Expand Down
Binary file added bublepop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion check-restrictions.lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ export async function checkRestrictions(gameJsPath: string = './game.js'): Promi
compress: true,
mangle: true
});


console.log(gameCode);

minifiedCode = minifyResult.code || '';
minifiedSize = Buffer.byteLength(minifiedCode, 'utf-8');
const sizeKB = minifiedSize / 1024;
Expand Down
Binary file modified cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 0 additions & 71 deletions dev-server.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
import { createServer, IncomingMessage, ServerResponse } from 'http';
import { readFileSync, watch } from 'fs';
import { execSync } from 'child_process';
import { createHash } from 'crypto';
import { checkRestrictions, CheckResults } from './check-restrictions.js';

const DEFAULT_COVER_SHA256 = 'b97a843d173c8fe4bfccbb7645d54d174a19f69dcd02b10af3111df07744a642';

// Check PNG dimensions by reading PNG header
function checkPNGDimensions(buffer: Buffer): { width: number; height: number; isPNG: boolean } {
// Check PNG signature
const pngSignature = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
if (!buffer.subarray(0, 8).equals(pngSignature)) {
return { width: 0, height: 0, isPNG: false };
}

// Read IHDR chunk (width at bytes 16-19, height at bytes 20-23)
const width = buffer.readUInt32BE(16);
const height = buffer.readUInt32BE(20);

return { width, height, isPNG: true };
}

const PORT = 3000;
let cachedChecks: CheckResults | null = null;

Expand Down Expand Up @@ -155,59 +137,6 @@ const server = createServer(async (req, res) => {
return;
}

// API endpoint for cover check
if (url === '/api/cover-check') {
try {
const coverPath = './cover.png';
const coverBuffer = readFileSync(coverPath);
const coverHash = createHash('sha256').update(coverBuffer).digest('hex');
const isChanged = coverHash !== DEFAULT_COVER_SHA256;

// Check PNG dimensions
const { width, height, isPNG } = checkPNGDimensions(coverBuffer);
const isValidSize = width === 800 && height === 600;

let message = '';
let isValid = false;

if (!isPNG) {
message = 'cover.png is not a valid PNG file';
} else if (!isChanged) {
message = 'Default cover detected';
} else if (!isValidSize) {
message = `Cover is ${width}x${height}, must be 800x600`;
} else {
message = 'Custom cover provided';
isValid = true;
}

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
exists: true,
isChanged: isChanged,
isPNG: isPNG,
width: width,
height: height,
isValidSize: isValidSize,
isValid: isValid,
message: message
}));
} catch (error) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
exists: false,
isChanged: false,
isPNG: false,
width: 0,
height: 0,
isValidSize: false,
isValid: false,
message: 'cover.png not found'
}));
}
return;
}

// SSE endpoint for live reload
if (url === '/sse') {
res.writeHead(200, {
Expand Down
Loading