Spaces:
Runtime error
Runtime error
File size: 9,743 Bytes
a522962 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Scratch Game JSON Generator</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; background: #f9f9f9; }
.asset-row, .sound-row {
margin-bottom: .5em;
display: flex;
align-items: center;
gap: 0.5em; /* Add some space between elements */
}
.asset-row input, .asset-row select,
.sound-row input, .sound-row select {
margin-right: 0; /* Reset margin-right for better gap control */
}
#backdrops-container .asset-row,
#sprites-container .asset-row {
flex-direction: column; /* Stack name/file and its sounds */
align-items: flex-start;
border: 1px solid #ddd;
padding: 1em;
margin-bottom: 1em;
border-radius: 5px;
}
.asset-main-row { /* New class for the main part of asset row */
display: flex;
align-items: center;
width: 100%;
gap: 0.5em;
}
.asset-sounds-container { /* Reusable for both backdrops and sprites */
margin-top: 0.5em;
padding-left: 1em;
border-left: 2px solid #eee;
}
button { margin-top: 1rem; padding: .5rem 1rem; font-size: 1rem; cursor: pointer; }
button.remove {
background: #f44336;
color: white;
border: none;
border-radius: 3px;
padding: 0.3em 0.6em;
font-size: 0.8em;
cursor: pointer;
margin-left: 0.5em;
}
button.add-sound {
background: #4CAF50;
color: white;
border: none;
border-radius: 3px;
padding: 0.3em 0.6em;
font-size: 0.8em;
cursor: pointer;
margin-top: 0.5em;
}
pre { background: #222; color: #eee; padding: 1rem; overflow-x: auto; max-height: 60vh; }
</style>
</head>
<body>
<h1>Scratch Game JSON Generator</h1>
<label for="gameDesc">Enter game description:</label><br />
<textarea id="gameDesc" rows="4" placeholder="e.g. jumping cat game over obstacle"></textarea><br />
<h2>Backdrops</h2>
<div id="backdrops-container"></div>
<button id="add-backdrop">+ Add Backdrop</button>
<h2>Sprites</h2>
<div id="sprites-container"></div>
<button id="add-sprite">+ Add Sprite</button><br/>
<button id="generateBtn">Generate Scratch JSON</button>
<h2>Output JSON:</h2>
<pre id="output">Waiting for input...</pre>
<script>
let availableBackdrops = [];
let availableSprites = [];
let availableSounds = []; // New array for available sounds
// Fetch lists of files from server
async function loadAssetLists() {
const resp = await fetch('/list_assets');
const { backdrops, sprites, sounds } = await resp.json();
availableBackdrops = backdrops;
availableSprites = sprites;
availableSounds = sounds;
}
// Helper to create asset (backdrop/sprite) rows
function makeAssetRow(containerId, available, type) {
const container = document.getElementById(containerId);
const row = document.createElement('div');
row.className = 'asset-row';
row.classList.add(`${type.toLowerCase()}-item`); // Add type-specific class
let innerHTML = `
<div class="asset-main-row">
<input type="text" placeholder="${type} name" class="asset-name"/>
<select class="asset-select">
${available.map(a => `<option value="${a}">${a}</option>`).join('')}
</select>
<button class="remove">×</button>
</div>
<div class="asset-sounds-container">
<h3>Sounds for this ${type}:</h3>
<div class="asset-individual-sounds"></div>
<button type="button" class="add-sound">+ Add Sound</button>
</div>
`;
row.innerHTML = innerHTML;
// Attach event listener for adding sounds to this specific asset row
row.querySelector('.add-sound').onclick = () => {
makeSoundRow(row.querySelector('.asset-individual-sounds'), availableSounds, `${type} Sound`);
};
row.querySelector('.remove').onclick = () => row.remove();
container.append(row);
}
// Helper to create sound rows
function makeSoundRow(container, available, type) {
const row = document.createElement('div');
row.className = 'sound-row';
row.innerHTML = `
<input type="text" placeholder="${type} name" class="sound-name"/>
<select class="sound-select">
${available.map(s => `<option value="${s}">${s}</option>`).join('')}
</select>
<button class="remove">×</button>
`;
row.querySelector('.remove').onclick = () => row.remove();
container.append(row);
}
document.getElementById('add-backdrop').onclick = () => {
makeAssetRow('backdrops-container', availableBackdrops, 'Backdrop');
};
// REMOVED: add-stage-sound button listener
// document.getElementById('add-stage-sound').onclick = () => {
// makeSoundRow('stage-sounds-container', availableSounds, 'Stage Sound');
// };
document.getElementById('add-sprite').onclick = () => {
makeAssetRow('sprites-container', availableSprites, 'Sprite');
};
document.getElementById('generateBtn').addEventListener('click', async () => {
const desc = document.getElementById('gameDesc').value.trim();
if (!desc) return alert('Please enter a game description.');
const collectAssetAndSounds = (containerId, type) => {
const collectedData = [];
const soundsPayload = {}; // Keyed by asset name (backdrop or sprite)
Array.from(document.getElementById(containerId).querySelectorAll(`.${type.toLowerCase()}-item`)).forEach(itemRow => {
const itemNameInput = itemRow.querySelector('.asset-main-row .asset-name');
const itemFilenameSelect = itemRow.querySelector('.asset-main-row .asset-select');
const itemName = itemNameInput.value.trim();
const itemFilename = itemFilenameSelect.value;
if (itemName && itemFilename) {
collectedData.push({
name: itemName,
filename: itemFilename
});
const individualSoundsContainer = itemRow.querySelector('.asset-individual-sounds');
if (individualSoundsContainer) {
// Associate sounds with the asset's name
soundsPayload[itemName] = Array.from(individualSoundsContainer.querySelectorAll('.sound-row'))
.map(row => ({
name: row.querySelector('.sound-name').value.trim(),
filename: row.querySelector('.sound-select').value
}))
.filter(s => s.name && s.filename);
}
}
});
return { assets: collectedData, sounds: soundsPayload };
};
const backdropsData = collectAssetAndSounds('backdrops-container', 'Backdrop');
const spritesData = collectAssetAndSounds('sprites-container', 'Sprite');
// The stage is special. If you want a global "stage" sound that isn't tied to a specific backdrop,
// you'd need a separate input for it. For now, we're assuming sounds are tied to backdrops.
// If there's only one backdrop, its sounds effectively become "stage sounds".
const payload = {
description: desc,
backdrops: backdropsData.assets,
sprites: spritesData.assets,
// Pass backdrop sounds and sprite sounds separately for the backend
backdrop_sounds: backdropsData.sounds, // New: Sounds keyed by backdrop name
sprite_sounds: spritesData.sounds
};
const output = document.getElementById('output');
output.textContent = 'Generating...';
const resp = await fetch('/generate_game', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!resp.ok) {
const err = await resp.json();
output.textContent = 'Error: ' + (err.error || resp.statusText);
return;
}
const data = await resp.json();
output.textContent = JSON.stringify(data, null, 2);
});
// initial load
loadAssetLists();
</script>
</body>
</html> |