mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 10:01:23 +01:00
Merge branch 'master' of github.com-personal:Azgaar/Fantasy-Map-Generator into burg-groups
This commit is contained in:
commit
8e13a3a0de
8 changed files with 211 additions and 32 deletions
89
.github/copilot-instructions.md
vendored
Normal file
89
.github/copilot-instructions.md
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# Fantasy Map Generator
|
||||||
|
|
||||||
|
Azgaar's Fantasy Map Generator is a client-side JavaScript web application for creating fantasy maps. It generates detailed fantasy worlds with countries, cities, rivers, biomes, and cultural elements.
|
||||||
|
|
||||||
|
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
|
||||||
|
|
||||||
|
## Working Effectively
|
||||||
|
|
||||||
|
- **CRITICAL**: This is a static web application - NO build process needed. No npm install, no compilation, no bundling.
|
||||||
|
- Run the application using HTTP server (required - cannot run with file:// protocol):
|
||||||
|
- `python3 -m http.server 8000` - takes 2-3 seconds to start
|
||||||
|
- Access at: `http://localhost:8000`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Always manually validate any changes by:
|
||||||
|
1. Starting the HTTP server (NEVER CANCEL - wait for full startup)
|
||||||
|
2. Navigate to the application in browser
|
||||||
|
3. Click the "►" button to open the menu and generate a new map
|
||||||
|
4. **CRITICAL VALIDATION**: Verify the map generates with countries, cities, roads, and geographic features
|
||||||
|
5. Test UI interaction: click "Layers" button, verify layer controls work
|
||||||
|
6. Test regeneration: click "New Map!" button, verify new map generates correctly
|
||||||
|
- **Known Issues**: Google Analytics and font loading errors are normal (blocked external resources)
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
### Core Files
|
||||||
|
|
||||||
|
- `index.html` - Main application entry point
|
||||||
|
- `main.js` - Core application logic
|
||||||
|
- `versioning.js` - Version management and update handling
|
||||||
|
|
||||||
|
### Key Directories
|
||||||
|
|
||||||
|
- `modules/` - core functionality modules:
|
||||||
|
- `modules/ui/` - UI components (editors, tools, style management)
|
||||||
|
- `modules/dynamic/` - runtime modules (export, installation)
|
||||||
|
- `modules/renderers/` - drawing and rendering logic
|
||||||
|
- `utils/` - utility libraries (math, arrays, strings, etc.)
|
||||||
|
- `styles/` - visual style presets (JSON files)
|
||||||
|
- `libs/` - Third-party libraries (D3.js, TinyMCE, etc.)
|
||||||
|
- `images/` - backgrounds, UI elements
|
||||||
|
- `charges/` - heraldic symbols and coat of arms elements
|
||||||
|
- `config/` - Heightmap templates and configurations
|
||||||
|
- `heightmaps/` - Terrain generation data
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Making Code Changes
|
||||||
|
|
||||||
|
1. Edit JavaScript files directly (no compilation needed)
|
||||||
|
2. Refresh browser to see changes immediately
|
||||||
|
3. **ALWAYS test map generation** after making changes
|
||||||
|
4. Update version in `versioning.js` for all changes
|
||||||
|
5. Update file hashes in `index.html` for changed files (format: `file.js?v=1.108.1`)
|
||||||
|
|
||||||
|
### Debugging Map Generation
|
||||||
|
|
||||||
|
- Open browser developer tools console
|
||||||
|
- Look for timing logs, e.g. "TOTAL: ~0.76s"
|
||||||
|
- Map generation logs show each step (heightmap, rivers, states, etc.)
|
||||||
|
- Error messages will indicate specific generation failures
|
||||||
|
|
||||||
|
### Testing Different Map Types
|
||||||
|
|
||||||
|
- Use "New Map!" button for quick regeneration
|
||||||
|
- Access "Layers" menu to change map visualization
|
||||||
|
- Available presets: Political, Cultural, Religions, Biomes, Heightmap, Physical, Military
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Application Won't Load
|
||||||
|
|
||||||
|
- Ensure using HTTP server (not file://)
|
||||||
|
- Check console for JavaScript errors
|
||||||
|
- Verify all files are present in repository
|
||||||
|
|
||||||
|
### Map Generation Fails
|
||||||
|
|
||||||
|
- Check browser console for error messages
|
||||||
|
- Look for specific module failures in generation logs
|
||||||
|
- Try refreshing page and generating new map
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
|
||||||
|
- Map generation should complete in ~1 second for standard configurations
|
||||||
|
- If slower, check browser console for errors
|
||||||
|
|
||||||
|
Remember: This is a sophisticated client-side application that generates complete fantasy worlds with political systems, geography, cultures, and detailed cartographic elements. Always validate that your changes preserve the core map generation functionality.
|
||||||
19
index.html
19
index.html
|
|
@ -5009,11 +5009,16 @@
|
||||||
</label>
|
</label>
|
||||||
<label for="aiGeneratorKey"
|
<label for="aiGeneratorKey"
|
||||||
>Key:
|
>Key:
|
||||||
<input id="aiGeneratorKey" placeholder="Enter API key" class="icon-key" />
|
<input
|
||||||
|
id="aiGeneratorKey"
|
||||||
|
placeholder="Enter API key"
|
||||||
|
class="icon-key"
|
||||||
|
data-tip="Enter API key. Note: the Generator doesn't store the key or any generated data"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
id="aiGeneratorKeyHelp"
|
id="aiGeneratorKeyHelp"
|
||||||
class="icon-help-circled"
|
class="icon-help-circled"
|
||||||
data-tip="Open provider's website to get the API key there. Note: the Map Genenerator doesn't store the key or any generated data"
|
data-tip="Click to see the usage instructions"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -8329,7 +8334,7 @@
|
||||||
<script src="utils/polyfills.js?v=1.99.00"></script>
|
<script src="utils/polyfills.js?v=1.99.00"></script>
|
||||||
<script src="utils/probabilityUtils.js?v=1.99.05"></script>
|
<script src="utils/probabilityUtils.js?v=1.99.05"></script>
|
||||||
<script src="utils/stringUtils.js?v=1.106.0"></script>
|
<script src="utils/stringUtils.js?v=1.106.0"></script>
|
||||||
<script src="utils/languageUtils.js?v=1.99.00"></script>
|
<script src="utils/languageUtils.js?v=1.108.11"></script>
|
||||||
<script src="utils/unitUtils.js?v=1.99.00"></script>
|
<script src="utils/unitUtils.js?v=1.99.00"></script>
|
||||||
<script src="utils/pathUtils.js?v=1.106.0"></script>
|
<script src="utils/pathUtils.js?v=1.106.0"></script>
|
||||||
<script defer src="utils/debugUtils.js?v=1.106.0"></script>
|
<script defer src="utils/debugUtils.js?v=1.106.0"></script>
|
||||||
|
|
@ -8389,9 +8394,9 @@
|
||||||
<script defer src="modules/ui/relief-editor.js?v=1.99.00"></script>
|
<script defer src="modules/ui/relief-editor.js?v=1.99.00"></script>
|
||||||
<script defer src="modules/ui/burg-group-editor.js?v=1.106.0"></script>
|
<script defer src="modules/ui/burg-group-editor.js?v=1.106.0"></script>
|
||||||
<script defer src="modules/ui/burg-editor.js?v=1.106.6"></script>
|
<script defer src="modules/ui/burg-editor.js?v=1.106.6"></script>
|
||||||
<script defer src="modules/ui/units-editor.js?v=1.104.0"></script>
|
<script defer src="modules/ui/units-editor.js?v=1.108.12"></script>
|
||||||
<script defer src="modules/ui/notes-editor.js?v=1.107.3"></script>
|
<script defer src="modules/ui/notes-editor.js?v=1.107.3"></script>
|
||||||
<script defer src="modules/ui/ai-generator.js?v=1.105.22"></script>
|
<script defer src="modules/ui/ai-generator.js?v=1.108.8"></script>
|
||||||
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
|
<script defer src="modules/ui/diplomacy-editor.js?v=1.99.00"></script>
|
||||||
<script defer src="modules/ui/zones-editor.js?v=1.105.20"></script>
|
<script defer src="modules/ui/zones-editor.js?v=1.105.20"></script>
|
||||||
<script defer src="modules/ui/burgs-overview.js?v=1.105.15"></script>
|
<script defer src="modules/ui/burgs-overview.js?v=1.105.15"></script>
|
||||||
|
|
@ -8399,7 +8404,7 @@
|
||||||
<script defer src="modules/ui/rivers-overview.js?v=1.99.00"></script>
|
<script defer src="modules/ui/rivers-overview.js?v=1.99.00"></script>
|
||||||
<script defer src="modules/ui/military-overview.js?v=1.108.5"></script>
|
<script defer src="modules/ui/military-overview.js?v=1.108.5"></script>
|
||||||
<script defer src="modules/ui/regiments-overview.js?v=1.108.5"></script>
|
<script defer src="modules/ui/regiments-overview.js?v=1.108.5"></script>
|
||||||
<script defer src="modules/ui/markers-overview.js?v=1.108.5"></script>
|
<script defer src="modules/ui/markers-overview.js?v=1.108.10"></script>
|
||||||
<script defer src="modules/ui/regiment-editor.js?v=1.108.5"></script>
|
<script defer src="modules/ui/regiment-editor.js?v=1.108.5"></script>
|
||||||
<script defer src="modules/ui/battle-screen.js?v=1.108.5"></script>
|
<script defer src="modules/ui/battle-screen.js?v=1.108.5"></script>
|
||||||
<script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script>
|
<script defer src="modules/ui/emblems-editor.js?v=1.99.00"></script>
|
||||||
|
|
@ -8414,7 +8419,7 @@
|
||||||
<script defer src="modules/io/save.js?v=1.107.4"></script>
|
<script defer src="modules/io/save.js?v=1.107.4"></script>
|
||||||
<script defer src="modules/io/load.js?v=1.108.0"></script>
|
<script defer src="modules/io/load.js?v=1.108.0"></script>
|
||||||
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
<script defer src="modules/io/cloud.js?v=1.106.0"></script>
|
||||||
<script defer src="modules/io/export.js?v=1.100.00"></script>
|
<script defer src="modules/io/export.js?v=1.108.11"></script>
|
||||||
|
|
||||||
<script defer src="modules/renderers/draw-features.js?v=1.108.2"></script>
|
<script defer src="modules/renderers/draw-features.js?v=1.108.2"></script>
|
||||||
<script defer src="modules/renderers/draw-borders.js?v=1.104.0"></script>
|
<script defer src="modules/renderers/draw-borders.js?v=1.104.0"></script>
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,40 @@ async function getMapURL(
|
||||||
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// replace external marker icons
|
||||||
|
const externalMarkerImages = cloneEl.querySelectorAll('#markers image[href]:not([href=""])');
|
||||||
|
const imageHrefs = Array.from(externalMarkerImages).map(img => img.getAttribute("href"));
|
||||||
|
|
||||||
|
for (const url of imageHrefs) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
getBase64(url, base64 => {
|
||||||
|
externalMarkerImages.forEach(img => {
|
||||||
|
if (img.getAttribute("href") === url) img.setAttribute("href", base64);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// replace external regiment icons
|
||||||
|
const externalRegimentImages = cloneEl.querySelectorAll('#armies image[href]:not([href=""])');
|
||||||
|
const imageHrefs = Array.from(externalRegimentImages).map(img => img.getAttribute("href"));
|
||||||
|
|
||||||
|
for (const url of imageHrefs) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
getBase64(url, base64 => {
|
||||||
|
externalRegimentImages.forEach(img => {
|
||||||
|
if (img.getAttribute("href") === url) img.setAttribute("href", base64);
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
|
||||||
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
|
||||||
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
|
||||||
|
|
@ -495,11 +529,10 @@ function saveGeoJsonCells() {
|
||||||
function saveGeoJsonRoutes() {
|
function saveGeoJsonRoutes() {
|
||||||
const features = pack.routes.map(({i, points, group, name = null}) => {
|
const features = pack.routes.map(({i, points, group, name = null}) => {
|
||||||
const coordinates = points.map(([x, y]) => getCoordinates(x, y, 4));
|
const coordinates = points.map(([x, y]) => getCoordinates(x, y, 4));
|
||||||
const id = `route${i}`;
|
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {type: "LineString", coordinates},
|
geometry: {type: "LineString", coordinates},
|
||||||
properties: {id, group, name}
|
properties: {id: i, group, name}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const json = {type: "FeatureCollection", features};
|
const json = {type: "FeatureCollection", features};
|
||||||
|
|
@ -514,11 +547,10 @@ function saveGeoJsonRivers() {
|
||||||
if (!cells || cells.length < 2) return;
|
if (!cells || cells.length < 2) return;
|
||||||
const meanderedPoints = Rivers.addMeandering(cells, points);
|
const meanderedPoints = Rivers.addMeandering(cells, points);
|
||||||
const coordinates = meanderedPoints.map(([x, y]) => getCoordinates(x, y, 4));
|
const coordinates = meanderedPoints.map(([x, y]) => getCoordinates(x, y, 4));
|
||||||
const id = `river${i}`;
|
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: {type: "LineString", coordinates},
|
geometry: {type: "LineString", coordinates},
|
||||||
properties: {id, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, name, type}
|
properties: {id: i, source, mouth, parent, basin, widthFactor, sourceWidth, discharge, name, type}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -532,9 +564,8 @@ function saveGeoJsonMarkers() {
|
||||||
const features = pack.markers.map(marker => {
|
const features = pack.markers.map(marker => {
|
||||||
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
const {i, type, icon, x, y, size, fill, stroke} = marker;
|
||||||
const coordinates = getCoordinates(x, y, 4);
|
const coordinates = getCoordinates(x, y, 4);
|
||||||
const id = `marker${i}`;
|
|
||||||
const note = notes.find(note => note.id === id);
|
const note = notes.find(note => note.id === id);
|
||||||
const properties = {id, type, icon, x, y, ...note, size, fill, stroke};
|
const properties = {id: i, type, icon, x, y, ...note, size, fill, stroke};
|
||||||
return {type: "Feature", geometry: {type: "Point", coordinates}, properties};
|
return {type: "Feature", geometry: {type: "Point", coordinates}, properties};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ const PROVIDERS = {
|
||||||
anthropic: {
|
anthropic: {
|
||||||
keyLink: "https://console.anthropic.com/account/keys",
|
keyLink: "https://console.anthropic.com/account/keys",
|
||||||
generate: generateWithAnthropic
|
generate: generateWithAnthropic
|
||||||
|
},
|
||||||
|
ollama: {
|
||||||
|
keyLink: "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Ollama-text-generation",
|
||||||
|
generate: generateWithOllama
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -18,11 +22,16 @@ const MODELS = {
|
||||||
"chatgpt-4o-latest": "openai",
|
"chatgpt-4o-latest": "openai",
|
||||||
"gpt-4o": "openai",
|
"gpt-4o": "openai",
|
||||||
"gpt-4-turbo": "openai",
|
"gpt-4-turbo": "openai",
|
||||||
"o1-preview": "openai",
|
o3: "openai",
|
||||||
"o1-mini": "openai",
|
"o3-mini": "openai",
|
||||||
|
"o3-pro": "openai",
|
||||||
|
"o4-mini": "openai",
|
||||||
|
"claude-opus-4-20250514": "anthropic",
|
||||||
|
"claude-sonnet-4-20250514": "anthropic",
|
||||||
"claude-3-5-haiku-latest": "anthropic",
|
"claude-3-5-haiku-latest": "anthropic",
|
||||||
"claude-3-5-sonnet-latest": "anthropic",
|
"claude-3-5-sonnet-latest": "anthropic",
|
||||||
"claude-3-opus-latest": "anthropic"
|
"claude-3-opus-latest": "anthropic",
|
||||||
|
"ollama (local models)": "ollama"
|
||||||
};
|
};
|
||||||
|
|
||||||
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
const SYSTEM_MESSAGE = "I'm working on my fantasy map.";
|
||||||
|
|
@ -76,10 +85,36 @@ async function generateWithAnthropic({key, model, prompt, temperature, onContent
|
||||||
await handleStream(response, getContent);
|
await handleStream(response, getContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateWithOllama({key, model, prompt, temperature, onContent}) {
|
||||||
|
const ollamaModelName = key; // for Ollama, 'key' is the actual model name entered by the user
|
||||||
|
|
||||||
|
const response = await fetch("http://localhost:11434/api/generate", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: ollamaModelName,
|
||||||
|
prompt,
|
||||||
|
system: SYSTEM_MESSAGE,
|
||||||
|
options: {temperature},
|
||||||
|
stream: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const getContent = json => {
|
||||||
|
if (json.response) onContent(json.response);
|
||||||
|
};
|
||||||
|
|
||||||
|
await handleStream(response, getContent);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleStream(response, getContent) {
|
async function handleStream(response, getContent) {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
let errorMessage = `Failed to generate (${response.status} ${response.statusText})`;
|
||||||
|
try {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
throw new Error(json?.error?.message || "Failed to generate");
|
errorMessage = json.error?.message || json.error || errorMessage;
|
||||||
|
} catch {}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const reader = response.body.getReader();
|
const reader = response.body.getReader();
|
||||||
|
|
@ -95,13 +130,14 @@ async function handleStream(response, getContent) {
|
||||||
|
|
||||||
for (let i = 0; i < lines.length - 1; i++) {
|
for (let i = 0; i < lines.length - 1; i++) {
|
||||||
const line = lines[i].trim();
|
const line = lines[i].trim();
|
||||||
if (line.startsWith("data: ") && line !== "data: [DONE]") {
|
if (!line) continue;
|
||||||
|
if (line === "data: [DONE]") break;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(line.slice(6));
|
const parsed = line.startsWith("data: ") ? JSON.parse(line.slice(6)) : JSON.parse(line);
|
||||||
getContent(json);
|
getContent(parsed);
|
||||||
} catch (jsonError) {
|
} catch (error) {
|
||||||
ERROR && console.error(`Failed to parse JSON:`, jsonError, `Line: ${line}`);
|
ERROR && console.error("Failed to parse line:", line, error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -214,15 +214,15 @@ function overviewMarkers() {
|
||||||
|
|
||||||
const body = pack.markers.map(marker => {
|
const body = pack.markers.map(marker => {
|
||||||
const {i, type, icon, x, y} = marker;
|
const {i, type, icon, x, y} = marker;
|
||||||
const id = `marker${i}`;
|
|
||||||
const note = notes.find(note => note.id === id);
|
const note = notes.find(note => note.id === "marker" + i);
|
||||||
const name = note ? quote(note.name) : "Unknown";
|
const name = note ? quote(note.name) : "Unknown";
|
||||||
const legend = note ? quote(note.legend) : "";
|
const legend = note ? quote(note.legend) : "";
|
||||||
|
|
||||||
const lat = getLatitude(y, 2);
|
const lat = getLatitude(y, 2);
|
||||||
const lon = getLongitude(x, 2);
|
const lon = getLongitude(x, 2);
|
||||||
|
|
||||||
return [id, type, icon, name, legend, x, y, lat, lon].join(",");
|
return [i, type, icon, name, legend, x, y, lat, lon].join(",");
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = headers + body.join("\n");
|
const data = headers + body.join("\n");
|
||||||
|
|
|
||||||
|
|
@ -121,11 +121,16 @@ function editUnits() {
|
||||||
|
|
||||||
function addRuler() {
|
function addRuler() {
|
||||||
if (!layerIsOn("toggleRulers")) toggleRulers();
|
if (!layerIsOn("toggleRulers")) toggleRulers();
|
||||||
|
|
||||||
|
const width = Math.min(graphWidth, svgWidth);
|
||||||
|
const height = Math.min(graphHeight, svgHeight);
|
||||||
const pt = byId("map").createSVGPoint();
|
const pt = byId("map").createSVGPoint();
|
||||||
(pt.x = graphWidth / 2), (pt.y = graphHeight / 4);
|
pt.x = width / 2;
|
||||||
|
pt.y = height / 4;
|
||||||
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
|
const p = pt.matrixTransform(viewbox.node().getScreenCTM().inverse());
|
||||||
const dx = graphWidth / 4 / scale;
|
|
||||||
const dy = (rulers.data.length * 40) % (graphHeight / 2);
|
const dx = width / 4 / scale;
|
||||||
|
const dy = (rulers.data.length * 40) % (height / 2);
|
||||||
const from = [(p.x - dx) | 0, (p.y + dy) | 0];
|
const from = [(p.x - dx) | 0, (p.y + dy) | 0];
|
||||||
const to = [(p.x + dx) | 0, (p.y + dy) | 0];
|
const to = [(p.x + dx) | 0, (p.y + dy) | 0];
|
||||||
rulers.create(Ruler, [from, to]).draw();
|
rulers.create(Ruler, [from, to]).draw();
|
||||||
|
|
|
||||||
13
run_python_server.sh
Normal file
13
run_python_server.sh
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
PYTHON=python3
|
||||||
|
elif command -v python >/dev/null 2>&1; then
|
||||||
|
PYTHON=python
|
||||||
|
else
|
||||||
|
echo "Neither 'python' nor 'python3' was found. Please install Python 3 package." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chromium http://localhost:8000
|
||||||
|
|
||||||
|
$PYTHON -m http.server 8000
|
||||||
|
|
@ -135,7 +135,7 @@ const adjectivizationRules = [
|
||||||
{
|
{
|
||||||
name: "an",
|
name: "an",
|
||||||
probability: 0.5,
|
probability: 0.5,
|
||||||
condition: new RegExp("^[a-zA-Z]{0-7}$"),
|
condition: new RegExp("^[a-zA-Z]{0,7}$"),
|
||||||
action: noun => trimVowels(noun) + "an"
|
action: noun => trimVowels(noun) + "an"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue