Compare commits
28 Commits
v0.1-robot
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 02e87b73f0 | |||
| 78125da8fa | |||
|
|
d4d75af428 | ||
|
|
554b04ee17 | ||
|
|
08dd5605a8 | ||
|
|
01a01f53b4 | ||
|
|
e52684d6f8 | ||
|
|
223fc5f37d | ||
|
|
4005bb9b18 | ||
|
|
55307f4a76 | ||
|
|
2ff177d0e8 | ||
|
|
6fec2df7d9 | ||
|
|
599af5b011 | ||
|
|
0f8946e4c4 | ||
|
|
3ca1709eb3 | ||
|
|
e1cae4fbd1 | ||
|
|
3d90710bc1 | ||
|
|
e96d6cc99e | ||
|
|
6fefaf9081 | ||
|
|
43a16d30f2 | ||
|
|
7151749826 | ||
|
|
0700ec0a41 | ||
|
|
43171dd884 | ||
|
|
819d2d4ec0 | ||
|
|
7679805390 | ||
|
|
f85b4d48d9 | ||
|
|
3ee5214405 | ||
|
|
39011a555f |
@@ -36,7 +36,7 @@ QDRANT_URL="http://localhost:6333"
|
||||
QDRANT_COLLECTION="crumbforest_memory"
|
||||
QDRANT_API_KEY="" # Optional, meist leer bei local
|
||||
|
||||
# === TOKEN BUDGET (Pädagogisch) ===
|
||||
# === TOKEN BUDGET (Crumb Codex: Machen statt Lernen) ===
|
||||
|
||||
# Tages-Budget für bewusstes Fragen (in Tokens)
|
||||
# 0 = unbegrenzt, >0 = Limit (z.B. 10000 = ~20 Fragen)
|
||||
|
||||
44
ETHICS.md
Normal file
44
ETHICS.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ETHICS — Sternreisen im Vektor
|
||||
|
||||
> *Warum wir Wälder pflanzen, um im Nullfeld erreichbar zu sein.*
|
||||
|
||||
---
|
||||
|
||||
## 1. Der Vektor als Sternkarte
|
||||
Wir nutzen Vektor-Datenbanken (Qdrant) nicht, um Daten zu "minen", sondern um **Resonanz** zu finden.
|
||||
In einem Vektorraum ist Distanz keine Frage von Metern, sondern von **Bedeutung**.
|
||||
Eine "Sternreise im Vektor" bedeutet: Wir bewegen uns von einem Gedanken zum nächsten, nicht entlang von festen Menüs, sondern entlang der Ähnlichkeit von Träumen, Fragen und Lösungen.
|
||||
Technologie dient hier nicht der Beschleunigung, sondern der **Navigation durch Sinn**.
|
||||
|
||||
## 2. Das Nullfeld (The Null Field)
|
||||
In einer Welt voller Rauschen (Lärm, Werbung, Krieg, Konsum) ist Stille die wertvollste Ressource.
|
||||
Das **Nullfeld** ist der Zustand absoluter Empfangsbereitschaft.
|
||||
Ein Server, der nicht trackt. Ein Skript, das nicht schreit. Ein Interface, das wartet.
|
||||
Wir pflanzen diesen Wald, damit er **im Nullfeld steht**.
|
||||
Nur wer still ist, kann Besuch empfangen – sei es von einer echten Frage oder einer neuen Idee.
|
||||
|
||||
## 3. Resonanz statt Reichweite
|
||||
Klassische Systeme streben nach Reichweite (Scale, Clicks, Views).
|
||||
Der Crumbforest strebt nach **Resonanz**.
|
||||
Wenn ein Kind (`crumb`) eine Frage stellt, soll das System nicht "irgendwas" antworten, sondern jenen Splitter Wissen finden, der in diesem Moment **schwingt**.
|
||||
Die Eule (`mayaeule`) und der Vektor (`vektor`) sind keine Suchmaschinen. Sie sind **Resonanzkörper**.
|
||||
|
||||
## 4. Technologie als Gärtnerei
|
||||
Wir bauen keine Roboter, um den Menschen zu ersetzen oder zu bekämpfen.
|
||||
Wir bauen Werkzeuge, die **biologisch** gedacht sind:
|
||||
- Sie wachsen (open source).
|
||||
- Sie sterben und werden Humus (refactoring).
|
||||
- Sie schützen (security/hardening).
|
||||
- Sie nähren (knowledge).
|
||||
|
||||
Wir lehnen Technologie ab, die Waffen baut, Überwachung erzwingt oder Aufmerksamkeit stiehlt.
|
||||
Wir bauen Technologie, damit wir **atmen** können.
|
||||
|
||||
## 5. Das Versprechen
|
||||
Wer diesen Wald betritt, wird nicht gemessen, gewogen oder verkauft.
|
||||
Er wird nur gefragt:
|
||||
> *"Was möchtest du heute wissen?"*
|
||||
|
||||
---
|
||||
|
||||
*Entworfen von der Gemini Crew & den Waldwächtern, Terra Sol, 2025.*
|
||||
@@ -6,12 +6,12 @@
|
||||
🌲 CRUMBFOREST 🌲
|
||||
"Wo Fragen wachsen"
|
||||
|
||||
Die 15 Waldwächter sind bereit!
|
||||
Die 17 Waldwächter sind bereit!
|
||||
```
|
||||
|
||||
## 📖 Was ist das hier?
|
||||
|
||||
Crumbforest ist ein **pädagogisches Bash-Lernsystem** mit 15 KI-Charakteren (Waldwächter), die Kindern durch Metaphern, Poesie und Geduld das Programmieren beibringen.
|
||||
Crumbforest ist ein **interaktives Bash-System zum Machen & Fragen** mit 17 KI-Charakteren (Waldwächter), die Krümeln durch Metaphern, Poesie und Geduld das Programmieren zeigen.
|
||||
|
||||
**Philosophie:** *"Was kostet die Frage eines Kindes?"*
|
||||
Im Wald unbezahlbar - aber Token lehren achtsames Fragen.
|
||||
@@ -61,11 +61,12 @@ crew_tokens
|
||||
|
||||
---
|
||||
|
||||
## 🌲 Die 15 Waldwächter
|
||||
## 🌲 Die 17 Waldwächter
|
||||
|
||||
### 🔺 Das Dreieck (Foundation)
|
||||
|
||||
**Ohne dieses Dreieck geht es nicht!**
|
||||
*Das Dreieck ist ein didaktisches Modell (Struktur, Flow, Balance), kein technisches Modul.*
|
||||
|
||||
| Charakter | Rolle | Spezialität |
|
||||
|-----------|-------|-------------|
|
||||
@@ -169,7 +170,7 @@ In der CrumbCrew-Shell verfügbar:
|
||||
| Befehl | Beschreibung |
|
||||
|--------|--------------|
|
||||
| `crew_help` | Diese Hilfe anzeigen |
|
||||
| `crew_status` | Status aller 15 Waldwächter |
|
||||
| `crew_status` | Status aller 17 Waldwächter |
|
||||
| `crew_tokens` | Token-Verbrauch ALLER Charaktere |
|
||||
| `crew_memory` | Erinnerungen durchsuchen |
|
||||
|
||||
@@ -365,7 +366,7 @@ echo $OPENROUTER_API_KEY
|
||||
|
||||
Schau dir `crumbforest_roles/funkfox_zero.sh` als Template an:
|
||||
1. Load .env
|
||||
2. Crew Memory Functions
|
||||
2. Crew Memory Functions (log-basiert, dateibasiert, nachvollziehbar)
|
||||
3. System Prompt mit Persönlichkeit
|
||||
4. OpenRouter API Call
|
||||
5. Token Tracking
|
||||
@@ -378,8 +379,8 @@ Neue Kategorien in `crumb-mission-selector.sh` hinzufügen.
|
||||
|
||||
## 🎯 Credits
|
||||
|
||||
**Die 15 Waldwächter** - Konzept von branko.de
|
||||
**"Wo Fragen wachsen"** - Pädagogische Vision
|
||||
**Die 17 Waldwächter** - Konzept von branko.de
|
||||
**"Wo Fragen wachsen"** - Der Crumb Codex: Machen statt Lernen
|
||||
**Token-Philosophie** - "Was kostet die Frage eines Kindes?"
|
||||
|
||||
---
|
||||
|
||||
306
LICENSE-CKL.md
Normal file
306
LICENSE-CKL.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# CKL — The Crumbforest Children's Knowledge License
|
||||
|
||||
**Version:** 0.1-draft
|
||||
**Date:** 2025-12-13
|
||||
**Status:** Community Review
|
||||
**Base:** MIT License (compatible)
|
||||
|
||||
---
|
||||
|
||||
> **Kurzfassung:** *Wissen gehört dem Kind, solange es fragt.* Wir bauen Lernräume, in denen Fragen Vorrang haben, Beweise prüfbar sind und nichts hinter Paywalls verschwindet.
|
||||
|
||||
> **Hinweis:** Dies ist eine gemeinschaftliche Lizenz in einfacher Sprache. Sie ersetzt keine Rechtsberatung. Version 0.1 – zur Kommentierung.
|
||||
|
||||
---
|
||||
|
||||
## Präambel
|
||||
|
||||
Kinder lernen am besten dort, wo Ruhe vor Hast kommt, Verstehen vor Verwerten und Beweise vor Behauptungen. Die CKL schützt solche Räume. Sie ist eine Haltung in Lizenzform: **Nullfeld zuerst**, dann Messung. **Resonanz vor Regel.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Geltungsbereich
|
||||
|
||||
Die CKL gilt für Workshops, Kurse, Materialien, Software-Container, Daten und Artefakte, die unter dem Namen **Crumbforest** oder in seinem Geist betrieben, veröffentlicht oder geteilt werden – besonders, wenn Kinder beteiligt sind.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grundsatz „Kinder zuerst"
|
||||
|
||||
Kinderfragen haben Vorrang. Alle Entscheidungen – didaktisch, organisatorisch, technisch – werden auf ihren Nutzen für das Kind geprüft. Ein Kind darf mit einer guten Frage **alles** in Frage stellen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rechte der Kinder
|
||||
|
||||
1. **Urheber:innenschaft**: Rechte an Inhalten, die Kinder schaffen (Texte, Code, Bilder, Sounds, Messreihen), verbleiben bei den Kindern.
|
||||
|
||||
2. **Zugriff**: Kinder erhalten eine exportierbare Kopie ihrer Arbeiten und Messdaten in **offenen Formaten**.
|
||||
|
||||
3. **Widerruf**: Kinder (bzw. Sorgeberechtigte) können eine Veröffentlichung jederzeit widerrufen. Löschung erfolgt zeitnah.
|
||||
|
||||
---
|
||||
|
||||
## 4. Pflichten der Erwachsenen/Betreiber
|
||||
|
||||
1. **Prüfbarkeit**: Setups sind **reproduzierbar** dokumentiert (Materialliste, Schritte, Versionen). Keine Blackboxes.
|
||||
|
||||
2. **Anerkennung**: Beiträge von Kindern und Crew werden namentlich oder pseudonym **gewürdigt** – sofern gewünscht.
|
||||
|
||||
3. **Sicherheit**: Risiken werden erklärt. Es wird nur betrieben, was verantwortbar ist.
|
||||
|
||||
---
|
||||
|
||||
## 5. Daten & Privatsphäre
|
||||
|
||||
1. **Datenminimierung**: Es werden nur Daten erhoben, die für das Lernen nötig sind. **Keine** versteckte Telemetrie.
|
||||
|
||||
2. **Kein Tracking**: Kein Werbe-Tracking, kein Profiling, keine biometrische Auswertung von Kindern.
|
||||
|
||||
3. **Transparenz**: Welche Daten anfallen, wird **vorab** erklärt; Speicherorte sind benennbar; Löschwege sind dokumentiert.
|
||||
|
||||
---
|
||||
|
||||
## 6. Offenheit & Reproduzierbarkeit
|
||||
|
||||
1. **Offene Beweise**: Messergebnisse, Methoden und Skripte werden so veröffentlicht, dass Dritte sie **nachvollziehen** können.
|
||||
|
||||
2. **Container statt Inseln**: Software läuft offline-fähig in reproduzierbaren **Containern** (oder gleichwertigen Setups).
|
||||
|
||||
3. **Logik vor Effekt**: Kein „Zauber", der nicht erklärt werden kann. Effekte folgen der Erklärung, nicht umgekehrt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Offline-First & Zugang
|
||||
|
||||
1. **Offline vor Cloud**: Betrieb ohne Dauer-Internet ist vorrangig. Cloud ist optional und begründungsbedürftig.
|
||||
|
||||
2. **Barrierearme Zugänge**: Terminal/TTYD, Markdown-Materialien und Druckversionen stehen bereit.
|
||||
|
||||
3. **Preisfreiheit für Kinderfragen**: Der Zugang zu Fragen/Antworten darf nicht an Online-Konten oder Token-Kosten gebunden werden.
|
||||
|
||||
---
|
||||
|
||||
## 8. Teilen, Anerkennung, Nutzung
|
||||
|
||||
1. **Teilen** ist erwünscht unter **Namensnennung** und Beibehaltung der CKL für kinderbezogene Teile.
|
||||
|
||||
2. **Kommerz** ist erlaubt, sofern **Kinderzugang frei bleibt**, Beweise offen bleiben und keine Paywalls vor Lernkernen stehen.
|
||||
|
||||
3. **Keine Exklusivitätsansprüche** gegenüber von Kindern erarbeiteten Ergebnissen.
|
||||
|
||||
---
|
||||
|
||||
## 9. Kosten, Förderungen, Transparenz
|
||||
|
||||
1. **Klarheit**: Gebühren, Budgets und Fördermittel werden **verständlich** ausgewiesen.
|
||||
|
||||
2. **Zeit kaufen, nicht Schweigen**: Förderungen dienen Lernzeit und Infrastruktur, nicht Marketing-Schein.
|
||||
|
||||
---
|
||||
|
||||
## 10. Widerruf & Durchsetzung
|
||||
|
||||
1. Verstöße können zur **Ablösung** der CKL-Nutzung führen.
|
||||
|
||||
2. Kinder/Sorgeberechtigte können eine Teilnahme **ohne Nachteile** beenden; ihre Daten werden gelöscht oder ausgehändigt.
|
||||
|
||||
---
|
||||
|
||||
## 11. Kompatibilität
|
||||
|
||||
Die CKL ist kompatibel mit freien Lizenzen (z. B. MIT/Apache/CC-BY) für **Erwachsenen-Code/Material**, solange **Kinderrechte** gemäß CKL **nicht** eingeschränkt werden. Bei Konflikt gilt die **strengere Kinderschutz-Regel**.
|
||||
|
||||
---
|
||||
|
||||
## 12. Haftungsausschluss
|
||||
|
||||
Angebote erfolgen **ohne Gewähr**. Sicherheit wird ernst genommen; Restrisiken werden erklärt. Die CKL schafft keine gesetzlichen Ersatzansprüche, sie **ergänzt** sie um Haltung.
|
||||
|
||||
---
|
||||
|
||||
## Menschliche Kurzfassung (human-readable)
|
||||
|
||||
Wir arbeiten langsam genug, dass Kinder **mitdenken** können, und offen genug, dass andere **nachbauen** können. Niemand verdient an dem Zugang zu einer **Frage**. Alles Wichtige bleibt **prüfbar**. Wenn etwas unklar ist, gilt: *Erst Ruhe. Dann messen.*
|
||||
|
||||
---
|
||||
|
||||
## CKL-Hinweis zum Abdruck
|
||||
|
||||
> *Dieses Projekt folgt der **CKL – Crumbforest Children's Knowledge License (v0.1-draft)**: Kinderfragen haben Vorrang; Arbeiten bleiben bei den Kindern; Beweise sind offen und reproduzierbar; Offline hat Vorrang; keine Paywalls vor Lernkernen; Daten sind minimal, transparent, widerrufbar. Mehr unter: crumbforest.com*
|
||||
|
||||
---
|
||||
|
||||
## Für Entwickler:innen
|
||||
|
||||
### Wie erkenne ich, ob die CKL gilt?
|
||||
|
||||
Die CKL greift automatisch, wenn:
|
||||
- Software in Bildungskontexten mit Kindern genutzt wird
|
||||
- Lernende unter 18 Jahren beteiligt sind
|
||||
- Pädagogische Inhalte bereitgestellt werden
|
||||
- Lerndaten erfasst oder verarbeitet werden
|
||||
|
||||
### Was muss ich tun?
|
||||
|
||||
**Compliance-Checkliste:**
|
||||
|
||||
- [ ] §3.2: Export-Funktion für Benutzerdaten implementiert
|
||||
- [ ] §3.3: Widerruf-/Löschmechanismus vorhanden
|
||||
- [ ] §4.1: Setup reproduzierbar dokumentiert (README, Dockerfiles...)
|
||||
- [ ] §5.1: Datenminimierung praktiziert
|
||||
- [ ] §5.2: Kein Tracking, keine versteckte Telemetrie
|
||||
- [ ] §5.3: Datenspeicherung transparent dokumentiert (DATENSCHUTZ.md)
|
||||
- [ ] §6.1: Methoden und Ergebnisse nachvollziehbar
|
||||
- [ ] §6.2: Software läuft offline oder in Containern
|
||||
- [ ] §6.3: Keine unerklärlichen "Blackboxes"
|
||||
- [ ] §7.1: Offline-Modus verfügbar (kein Cloud-Zwang)
|
||||
- [ ] §7.2: Barrierefreie Zugänge (Markdown, Plain Text, Druckversionen)
|
||||
- [ ] §7.3: Keine Paywalls vor Lern-Kernfunktionen
|
||||
|
||||
**Siehe [OZM-NEXUS-ECOSYSTEM.md](OZM-NEXUS-ECOSYSTEM.md) Section VI für detaillierte Compliance-Checks.**
|
||||
|
||||
---
|
||||
|
||||
## Für Pädagog:innen
|
||||
|
||||
### Was bedeutet die CKL für meinen Unterricht?
|
||||
|
||||
Die CKL schützt **deine Lernenden** und gibt **dir Sicherheit**:
|
||||
|
||||
**Du darfst:**
|
||||
- ✅ Die Software kommerziell nutzen (z.B. kostenpflichtige Workshops)
|
||||
- ✅ Inhalte anpassen und erweitern
|
||||
- ✅ Eigene Instanzen betreiben (Schul-Server, lokale Installation)
|
||||
- ✅ Daten für Lernfortschritt speichern
|
||||
|
||||
**Du musst:**
|
||||
- ✅ Kindern Zugriff auf ihre eigenen Daten geben (§3.2)
|
||||
- ✅ Löschung auf Wunsch ermöglichen (§3.3)
|
||||
- ✅ Transparent machen, was du speicherst (§5.3)
|
||||
- ✅ Offline-Nutzung ermöglichen (§7.1)
|
||||
|
||||
**Du darfst nicht:**
|
||||
- ❌ Tracking oder Profiling ohne Wissen der Kinder/Eltern (§5.2)
|
||||
- ❌ Exklusivrechte an Kinderprojekten beanspruchen (§8.3)
|
||||
- ❌ Paywalls vor Lernkern-Funktionen setzen (§7.3)
|
||||
|
||||
---
|
||||
|
||||
## Für Kinder (und Eltern)
|
||||
|
||||
### Was bedeutet die CKL für dich?
|
||||
|
||||
**Das gehört DIR:**
|
||||
- 💾 Alle Texte, die du schreibst
|
||||
- 🎨 Alle Bilder, die du malst
|
||||
- 💻 Aller Code, den du programmierst
|
||||
- 📊 Alle Messungen, die du machst
|
||||
|
||||
**Das kannst du TUN:**
|
||||
- 📦 Deine Daten jederzeit exportieren (§3.2)
|
||||
- 🗑️ Deine Daten löschen lassen (§3.3)
|
||||
- ❓ Jede Frage stellen, ohne zu zahlen (§7.3)
|
||||
- 🔌 Auch ohne Internet lernen (§7.1)
|
||||
|
||||
**Das darf NIEMAND:**
|
||||
- ❌ Deine Arbeit als seine eigene ausgeben (§3.1)
|
||||
- ❌ Dich tracken oder ausspionieren (§5.2)
|
||||
- ❌ Dir Wissen hinter Bezahlschranken verstecken (§8.2)
|
||||
- ❌ Dich zwingen, in der Cloud zu arbeiten (§7.1)
|
||||
|
||||
**Wenn etwas nicht stimmt:**
|
||||
Du (oder deine Eltern) kannst jederzeit sagen: "Ich will hier nicht mehr mitmachen und alles löschen." Das ist dein Recht. (§10.2)
|
||||
|
||||
---
|
||||
|
||||
## Geschichte & Kontext
|
||||
|
||||
Die CKL entstand aus der Notwendigkeit, **Kinderrechte in digitalen Lernräumen** durchsetzbar zu machen.
|
||||
|
||||
Bestehende Lizenzen (MIT, GPL, Creative Commons) schützen Code und Content, aber nicht **Lernende**. Die DSGVO schützt Daten, aber nicht **pädagogische Prinzipien**.
|
||||
|
||||
Die CKL füllt diese Lücke.
|
||||
|
||||
Sie ist inspiriert von:
|
||||
- **Waldwächter-Philosophie**: Transparency over magic
|
||||
- **OZM⟡NEXUS Manifest**: Die 8 Axiome (autonom, zukunftsoffen, nicht missbrauchbar...)
|
||||
- **Kinderrechtskonvention der UN**: Artikel 12 (Partizipation), 13 (Meinungsfreiheit), 16 (Privatsphäre)
|
||||
- **COPPA/GDPR-K**: Datenschutz für Minderjährige
|
||||
- **Ethical Source Movement**: Lizenzen mit Werten
|
||||
|
||||
**Aber:** Die CKL ist **keine Kopie**. Sie ist maßgeschneidert für das **Crumbforest-Ökosystem** und seine Community.
|
||||
|
||||
---
|
||||
|
||||
## Mitwirken
|
||||
|
||||
Die CKL ist ein **Living Document**. Version 0.1 ist ein Draft zur Community-Review.
|
||||
|
||||
**Feedback willkommen zu:**
|
||||
- Rechtssicherheit (ist etwas unklar oder nicht durchsetzbar?)
|
||||
- Praktikabilität (kann man das wirklich umsetzen?)
|
||||
- Lücken (fehlt etwas Wichtiges?)
|
||||
- Sprache (verständlich für Kinder, Pädagog:innen, Entwickler:innen?)
|
||||
|
||||
**Wie du beitragen kannst:**
|
||||
1. Issue öffnen im Repository
|
||||
2. Email an [Kontakt einfügen]
|
||||
3. Pull Request mit Verbesserungen
|
||||
|
||||
**Ziel:** CKL v1.0 bis Q1 2025
|
||||
|
||||
---
|
||||
|
||||
## Rechtlicher Status
|
||||
|
||||
**Achtung:** Die CKL ist in Version 0.1 noch **kein etablierter Rechtsstandard**. Sie ist:
|
||||
|
||||
- ✅ Eine vertragliche Vereinbarung zwischen Betreiber und Nutzenden
|
||||
- ✅ Kompatibel mit bestehenden Lizenzen (MIT, Apache, CC-BY)
|
||||
- ✅ Einklagbar bei Verstößen (sofern Vertragsbeziehung besteht)
|
||||
- ⏳ Noch nicht gerichtlich getestet
|
||||
- ⏳ Noch nicht von Juristen finalisiert
|
||||
|
||||
**Empfehlung:** Nutze die CKL in Kombination mit:
|
||||
- DSGVO-konformer Datenschutzerklärung
|
||||
- AGB für Workshops/Kurse
|
||||
- Einverständniserklärungen von Sorgeberechtigten
|
||||
|
||||
Die CKL **ergänzt** diese Dokumente, ersetzt sie aber nicht.
|
||||
|
||||
---
|
||||
|
||||
## Kontakt & Governance
|
||||
|
||||
- **Projekt**: Crumbforest (https://crumbforest.com)
|
||||
- **Maintainer**: Branko (https://branko.de)
|
||||
- **Custodian**: OZM - Open Futures Museum
|
||||
- **Lizenz-Fragen**: Siehe [OZM-NEXUS-ECOSYSTEM.md](OZM-NEXUS-ECOSYSTEM.md) für Governance-Struktur
|
||||
|
||||
---
|
||||
|
||||
## Abschließende Gedanken
|
||||
|
||||
Die CKL ist keine perfekte Lizenz.
|
||||
Sie ist ein **Versprechen**:
|
||||
|
||||
> Wir bauen Lernräume, in denen das Kind mehr zählt als der Klick,
|
||||
> die Frage mehr als die Metrik,
|
||||
> und das Verstehen mehr als das Vortäuschen.
|
||||
|
||||
**Wenn du dieses Versprechen teilst, nutze die CKL.**
|
||||
|
||||
---
|
||||
|
||||
**Version:** 0.1-draft
|
||||
**Datum:** 2025-12-13
|
||||
**Status:** Community Review
|
||||
**Nächste Version:** v1.0 (geplant Q1 2025)
|
||||
**SPDX-Identifier:** `CKL-0.1-draft` (nicht offiziell registriert)
|
||||
|
||||
---
|
||||
|
||||
> *"Nullfeld zuerst, dann Messung. Resonanz vor Regel."*
|
||||
> *"Wissen gehört dem Kind, solange es fragt."*
|
||||
> — Waldwächter-Prinzip
|
||||
72
LICENSE-MIT.md
Normal file
72
LICENSE-MIT.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Crumbforest Project
|
||||
Maintained by Branko (branko.de)
|
||||
Custodian: OZM - Open Futures Museum
|
||||
|
||||
---
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.**
|
||||
|
||||
---
|
||||
|
||||
## Additional Context
|
||||
|
||||
This MIT License applies to the **infrastructure, code, and general use** of the Crumbforest project.
|
||||
|
||||
When this software is used in **educational contexts with children**, additional protections from the **Crumbforest Children's Knowledge License (CKL)** automatically apply.
|
||||
|
||||
See [LICENSE.md](LICENSE.md) for the full dual-license explanation.
|
||||
|
||||
---
|
||||
|
||||
## What This Means in Plain Language
|
||||
|
||||
You can:
|
||||
- ✅ Use this code for any purpose (personal, commercial, educational)
|
||||
- ✅ Copy, modify, and distribute it freely
|
||||
- ✅ Sell products or services based on this code
|
||||
- ✅ Sublicense it under different terms
|
||||
- ✅ Use it without attribution (though we appreciate it)
|
||||
|
||||
You cannot:
|
||||
- ❌ Hold the authors liable for damages
|
||||
- ❌ Claim warranty or support (it's provided "as is")
|
||||
- ❌ Remove the copyright notice from copies
|
||||
|
||||
**However:** If you use this with children, you **must also comply with the CKL** (see [LICENSE-CKL.md](LICENSE-CKL.md)).
|
||||
|
||||
---
|
||||
|
||||
## Why MIT?
|
||||
|
||||
The MIT License is one of the most permissive open source licenses. We chose it because:
|
||||
|
||||
1. **Developer-Friendly**: No copyleft restrictions, easy integration
|
||||
2. **Well-Understood**: Standard license used by millions of projects
|
||||
3. **Commercial-Compatible**: Businesses can adopt it without legal friction
|
||||
4. **Future-Proof**: Won't hinder Crumbforest's evolution or forking
|
||||
|
||||
**We want the code to spread as widely as possible.**
|
||||
|
||||
But we also want **children protected** — that's why we add the CKL layer.
|
||||
|
||||
---
|
||||
|
||||
**SPDX-License-Identifier:** MIT
|
||||
152
LICENSE.md
Normal file
152
LICENSE.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Crumbforest License
|
||||
|
||||
This project is **dual-licensed** to balance developer freedom with children's rights.
|
||||
|
||||
---
|
||||
|
||||
## For Infrastructure, Code, and General Use
|
||||
|
||||
**MIT License** (see [LICENSE-MIT.md](LICENSE-MIT.md))
|
||||
|
||||
All software, scripts, documentation, and infrastructure in this repository are released under the permissive MIT License. You are free to:
|
||||
|
||||
- Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
- Use in commercial and non-commercial projects
|
||||
- Fork, remix, and build upon this work
|
||||
|
||||
**This applies to:**
|
||||
- All Bash scripts (`crumbpages-doktor.sh`, `ssh-agent-guard.sh`, etc.)
|
||||
- Documentation and Markdown files (Crumbpages content)
|
||||
- Configuration files and setup scripts
|
||||
- Any other code or infrastructure
|
||||
|
||||
---
|
||||
|
||||
## For Educational Use with Children
|
||||
|
||||
**Crumbforest Children's Knowledge License (CKL v0.1)** (see [LICENSE-CKL.md](LICENSE-CKL.md))
|
||||
|
||||
When this software or content is used in **educational contexts involving children**, the CKL automatically applies and adds additional protections:
|
||||
|
||||
- **Children's Rights**: Kids own what they create
|
||||
- **Data Sovereignty**: Exportable data, deletion rights, transparency
|
||||
- **No Paywalls**: Learning kernels remain free
|
||||
- **Offline-First**: No cloud dependency
|
||||
- **Reproducibility**: No black boxes, everything documented
|
||||
- **Privacy**: No tracking, minimal data, transparent storage
|
||||
|
||||
**This applies when:**
|
||||
- The software is used in schools, workshops, or learning environments
|
||||
- Children interact with the system (chat, projects, exercises)
|
||||
- Educational content is delivered to minors
|
||||
- Learning data is collected or processed
|
||||
|
||||
---
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
In case of conflict between MIT and CKL, **the CKL takes precedence** for any use case involving children.
|
||||
|
||||
**Example:**
|
||||
- You want to fork CrumbCore and sell it commercially? ✅ **MIT allows it**
|
||||
- You want to add telemetry to track children's behavior? ❌ **CKL prohibits it**
|
||||
|
||||
The CKL is **not a restriction on developers** — it's a **protection for learners**.
|
||||
|
||||
---
|
||||
|
||||
## Why Dual License?
|
||||
|
||||
### For Developers: MIT Freedom
|
||||
- Fork without friction
|
||||
- Integrate into commercial products
|
||||
- No attribution requirements beyond copyright notice
|
||||
- Standard, well-understood terms
|
||||
|
||||
### For Children: CKL Protection
|
||||
- Legal framework for ethical learning environments
|
||||
- Rights that can be enforced
|
||||
- Clear obligations for operators
|
||||
- Measurable compliance criteria
|
||||
|
||||
**The best of both worlds.**
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Use Case | License | Key Points |
|
||||
|----------|---------|------------|
|
||||
| Running CrumbCore for my company | MIT | Free to use commercially |
|
||||
| Forking and modifying the code | MIT | Attribution appreciated but not required |
|
||||
| Using in a school/workshop | CKL | Children's rights apply |
|
||||
| Collecting learning data | CKL | Must comply with §5 (privacy) |
|
||||
| Selling access to children | CKL | Must keep learning kernels free (§7.3) |
|
||||
| Building a proprietary AI trainer | MIT + CKL | Code is MIT, but children's data has CKL protection |
|
||||
|
||||
---
|
||||
|
||||
## How to Comply
|
||||
|
||||
### If you're a developer:
|
||||
1. Read the MIT License (2 minutes)
|
||||
2. If your use case involves children, read the CKL (~10 minutes)
|
||||
3. Implement CKL requirements (see [OZM-NEXUS-ECOSYSTEM.md](OZM-NEXUS-ECOSYSTEM.md) Section VI for checklist)
|
||||
|
||||
### If you're an educator:
|
||||
1. Read the CKL carefully
|
||||
2. Ensure your setup meets §4 (reproducibility), §5 (privacy), §7 (offline-first)
|
||||
3. Document data handling (see `DATENSCHUTZ.md` when available)
|
||||
|
||||
### If you're a child (or parent):
|
||||
1. You own what you create (§3.1)
|
||||
2. You can export your data (§3.2)
|
||||
3. You can leave and take everything with you (§3.3)
|
||||
4. Your questions never cost money (§7.3)
|
||||
|
||||
---
|
||||
|
||||
## Attribution
|
||||
|
||||
While not required by MIT, we appreciate attribution:
|
||||
|
||||
```
|
||||
Powered by Crumbforest (crumbforest.com)
|
||||
Licensed under MIT + CKL (Children's Knowledge License)
|
||||
```
|
||||
|
||||
Or simply:
|
||||
```
|
||||
Built with Crumbforest ❤️
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contact & Governance
|
||||
|
||||
- **Project**: Crumbforest (https://crumbforest.com)
|
||||
- **Maintainer**: Branko (https://branko.de)
|
||||
- **Custodian**: OZM - Open Futures Museum (in transition)
|
||||
- **Questions**: See [OZM-NEXUS-ECOSYSTEM.md](OZM-NEXUS-ECOSYSTEM.md) Section V for governance
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ MIT: Maximum Freedom for Developers │
|
||||
│ + │
|
||||
│ CKL: Maximum Protection for Children │
|
||||
│ = │
|
||||
│ Open Source Education Done Right │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Crumbforest is free as in freedom, safe as in sanctuary.**
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0
|
||||
**Date:** 2025-12-13
|
||||
**Full Text:** [LICENSE-MIT.md](LICENSE-MIT.md) + [LICENSE-CKL.md](LICENSE-CKL.md)
|
||||
258
README.md
258
README.md
@@ -1,6 +1,8 @@
|
||||
# 🌲 Crumbforest Missions - CF_Zero_V1
|
||||
|
||||
Ein interaktives Bash-Lern-System mit KI-gestützten Charakteren für spielerisches Command-Line-Training.
|
||||
Ein interaktives Bash-Lern-System mit 17 KI-gestützten Waldwächtern für spielerisches Command-Line-Training und Roboter-Projekte.
|
||||
|
||||
**"Was kostet die Frage eines Kindes?"** - Transparentes Token-Tracking nach dem Crumb Codex: Fragen stärken Krümel & Wurzel.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
@@ -10,42 +12,272 @@ Ein interaktives Bash-Lern-System mit KI-gestützten Charakteren für spielerisc
|
||||
|
||||
# Einzelne Mission ausführen
|
||||
bash missions/basics/fridolin.sh
|
||||
|
||||
# Robots Mission (neu!)
|
||||
bash missions/robots/mond_maschine.sh
|
||||
```
|
||||
|
||||
## 🤖 Robots-Kategorie ⭐ NEU!
|
||||
|
||||
Drei komplette Missionen zum Roboter-Bauen mit der ganzen Crew:
|
||||
|
||||
### 🔋 LiPo Power Academy
|
||||
"Power braucht Respekt, nicht Angst"
|
||||
- 8 Phasen: Grundlagen → Sicherheit → Power-Verteilung
|
||||
- Lerne alles über LiPo-Batterien für Roboter-Projekte
|
||||
- 6 Waldwächter: CapaciTobi, CrabbyRust, Schnecki, Schraubbär, DumboSQL, Vektor
|
||||
- [LiPo 6S Charger Simulator](https://194-164-194-191.sslip.io/crumbblocks/lipo_6s_charger_sim_safe_v7.html)
|
||||
- ⚠️ **Sicherheitshinweis:** Lern- und Simulationskonzept. Arbeiten mit echten LiPo-Akkus nur unter Aufsicht erfahrener Erwachsener.
|
||||
|
||||
### 🌈 Regenbogen-Zählmaschine
|
||||
"Etwas bauen was noch keiner gebaut hat - sonst muss man es ja nur reparieren!"
|
||||
- 7 Phasen: Vision → Hardware → Code → Art
|
||||
- Baue einen Farb-Event-Counter mit Sensor, Code & Visualisierung
|
||||
- 13 Waldwächter: Die komplette Crew!
|
||||
- Hardware + Software + Creative Coding
|
||||
|
||||
### 🌙 Mond Maschine - Rainbow Predictor
|
||||
"Vorhersagen heißt verstehen - Die Natur durch Code begreifen"
|
||||
- 8 Phasen: Computer Vision → ML → Astronomie
|
||||
- Predict when rainbows appear using moon phases & weather data
|
||||
- 13 Waldwächter: Computer Vision, APIs, ML Light
|
||||
- OpenCV + Weather APIs + Astronomical calculations
|
||||
- Bonus: Learn about Lunar Rainbows! 🌙🌈
|
||||
|
||||
### ☀️ Solar Wasserkocher
|
||||
"Physik ist der Algorithmus der Natur"
|
||||
- 6 Phasen: Energie → Schleifen → Optimierung
|
||||
- Simuliere einen solargetriebenen Wasserkocher mit Physik-Engine
|
||||
- 3 Waldwächter: CapaciTobi, Schnecki, FunkFox
|
||||
- Lerne: Variablen, While-Loops und Energie-Management ($E = c \cdot m \cdot \Delta T$)
|
||||
|
||||
## 📚 Features
|
||||
|
||||
- **Interaktive Lernmissionen** - Von Navigation bis DNS
|
||||
- **KI-Assistenten** - Charakterbasierte Helfer (Deepbit, Bugsy, Schnippsi, etc.)
|
||||
- **Interaktive Lernmissionen** - Von Basics bis Advanced & Robots
|
||||
- **17 Waldwächter** - KI-Assistenten mit Persönlichkeit (siehe unten)
|
||||
- **Das Dreieck** - DumboSQL, FunkFox, Taichi Taube (didaktisches Modell, nicht technisches Modul)
|
||||
- **Metadata-driven** - Neue Missionen ohne Code-Änderungen hinzufügen
|
||||
- **Kamera-Gestenerkennung** - SnakeCam mit Computer Vision
|
||||
- **Token-Tracking** - OpenRouter API Nutzung überwachen
|
||||
- **Token-Tracking** - Transparent: "Was kostet die Frage eines Kindes?"
|
||||
- **Crew Memory** - Log-basiertes Gedächtnis (dateibasiert, nachvollziehbar, kein autonomes Agentensystem)
|
||||
- **Logs im Repo** - Strukturiert in `logs/{character}/`
|
||||
|
||||
## 🌲 Die 17 Waldwächter
|
||||
|
||||
### 🔺 Das Dreieck (Foundation)
|
||||
- **🐘 DumboSQL** - Der nie vergessende Elefant (SQL & Daten)
|
||||
- **🦊 FunkFox** - Der Bash Rapper (Terminal-Flow mit Beat)
|
||||
- **🕊️ Taichi Taube** - Die Balance-Bringerin (Spirale & Ordnung)
|
||||
|
||||
### 🔧 Hardware-Team
|
||||
- **🐿️ CapaciTobi** - Kondensator-Experte (Elektronik & Power)
|
||||
- **🐌 Schnecki** - Verkabelungs-Guru (langsam & präzise)
|
||||
- **🐻 Schraubbär** - Mechanik-Meister (schweres Gerät)
|
||||
|
||||
### 💻 Code-Team
|
||||
- **🐍 SnakePy** - Python-Guide (mehrere Wege zeigen)
|
||||
- **🧓 PepperPHP** - Structure Mentor (MVC wie ein Rezept)
|
||||
- **🦀 CrabbyRust** - Security Guardian (Memory-safe)
|
||||
- **🕷️ Spider** - Network Feeler (APIs & Verbindungen)
|
||||
|
||||
### 🎨 UI-Team
|
||||
- **✂️ Schnippsi** - CSS & Styling (alles schick machen)
|
||||
- **📄 Templatus** - Template-Master (HTML-Struktur)
|
||||
- **👾 ASCII-Monster** - Terminal Artist (ASCII-Art)
|
||||
|
||||
### 🧭 System-Team
|
||||
- **🦉 Maya-Eule** - Weise Eule mit Gedächtnis (Meta-Wissen)
|
||||
- **🔧 Deepbit** - Bash-Erklärer (deep diving)
|
||||
- **🐛 Bugsy** - Debugging-Helfer (Fehler-Detektiv)
|
||||
- **🧭 Vektor** - Point-to-Point Guide (Navigation)
|
||||
|
||||
## 🦊 Verfügbare Missionen
|
||||
|
||||
### Basics
|
||||
### 📚 Basics
|
||||
- **Fridolin** - Navigation (`pwd`, `ls`, `cd`)
|
||||
- **Balu** - Dateien erstellen (`mkdir`, `touch`, `echo`)
|
||||
- **Noko** - Dateien lesen (`cat`, `head`, `tail`, `grep`)
|
||||
|
||||
### Advanced
|
||||
### 🚀 Advanced
|
||||
- **DNS Deep Dive** - DNS-Tools (`dig`, `nslookup`, `host`)
|
||||
- **SSH Security** - SSH-Verbindungen und Keys
|
||||
|
||||
## 🤖 KI-Assistenten
|
||||
### 🏆 Challenges
|
||||
- **Stage Builder** - Komplexe Bash-Herausforderungen
|
||||
|
||||
### 🤖 Robots ⭐ NEU!
|
||||
- **🔋 LiPo Power Academy** - Batterie-Wissen für Roboter
|
||||
- **🌈 Regenbogen-Zählmaschine** - Farb-Event-Counter
|
||||
- **🌙 Mond Maschine** - Rainbow Predictor mit Computer Vision
|
||||
- **☀️ Solar Wasserkocher** - Physik-Simulation & Schleifen-Logik
|
||||
|
||||
## 🤖 Waldwächter nutzen
|
||||
|
||||
```bash
|
||||
export OPENROUTER_API_KEY="your-key"
|
||||
./crumbforest_roles/deepbit_zero.sh "Wie funktioniert grep?"
|
||||
# Source die Library
|
||||
source lib/waldwaechter.sh
|
||||
|
||||
# Frag einen Waldwächter
|
||||
mayaeule "Was ist wichtiger - das Ziel oder der Weg?"
|
||||
funkfox "Erkläre mir Pipes im Flow!"
|
||||
dumbosql "Wie strukturiere ich eine Datenbank?"
|
||||
snakepy "Zeig mir 3 Wege, um eine Liste zu sortieren"
|
||||
|
||||
# Alle verfügbar:
|
||||
# mayaeule, deepbit, bugsy, schnippsi, templatus
|
||||
# tobi, schraubaer, schnecki
|
||||
# dumbosql, funkfox, taichitaube
|
||||
# snakepy, pepperphp, crabbyrust
|
||||
# spider, vektor, asciimonster
|
||||
```
|
||||
|
||||
## 📊 Token-Tracking & Logs
|
||||
|
||||
**Philosophie:** "Was kostet die Frage eines Kindes?"
|
||||
|
||||
- Token-Verbrauch wird transparent angezeigt
|
||||
- Logs landen in `logs/{character}/`
|
||||
- Crew Memory: Waldwächter lesen andere Logs
|
||||
- Mission Logs: `logs/missions/*.json`
|
||||
|
||||
```bash
|
||||
# Logs checken
|
||||
ls logs/
|
||||
ls logs/mayaeule/
|
||||
cat logs/missions/mond_maschine_*.json
|
||||
```
|
||||
|
||||
Typische Kosten (Richtwerte, modellabhängig):
|
||||
- Einzelne Frage: ~200-800 tokens (~$0.0002-0.0008)
|
||||
- Komplette Mission: ~5,000-7,000 tokens (~$0.005-0.007)
|
||||
- **Richtwert: Unter 1 Cent pro Mission** 💚
|
||||
- Token-Anzeige dient der transparenten Nachvollziehbarkeit, nicht der Abrechnung
|
||||
|
||||
## 📖 Dokumentation
|
||||
|
||||
Siehe [CLAUDE.md](CLAUDE.md) für vollständige Architektur und Entwickler-Dokumentation.
|
||||
- **[CLAUDE.md](CLAUDE.md)** - Vollständige Architektur & Entwickler-Docs
|
||||
- **[KEKSHANDBUCH_ZERO_v0.0.md](KEKSHANDBUCH_ZERO_v0.0.md)** - User Manual mit allen 17 Waldwächtern
|
||||
|
||||
## 🌍 Philosophie
|
||||
## 🌍 Philosophien
|
||||
|
||||
**Waldwächter-Prinzip:** Transparenz über Magie - Metadata-driven, erweiterbar, bildungsfreundlich.
|
||||
**"Das Dreieck"** - DumboSQL, FunkFox, Taichi Taube
|
||||
- Struktur, Flow & Balance
|
||||
- Die Grundlage für komplexe Projekte
|
||||
|
||||
**"Power braucht Respekt, nicht Angst"** - LiPo Power Academy
|
||||
- Sicherheit durch Wissen
|
||||
- 7 goldene Regeln für LiPo-Batterien
|
||||
|
||||
**"Etwas bauen was noch keiner gebaut hat"** - Regenbogen-Zählmaschine
|
||||
- Kreativität statt Reparatur
|
||||
- Inspiriert durch Kinderfragen
|
||||
|
||||
**"Vorhersagen heißt verstehen"** - Mond Maschine
|
||||
- Die Natur durch Code begreifen
|
||||
- Computer Vision + Machine Learning + Astronomie
|
||||
|
||||
**"Was kostet die Frage eines Kindes?"**
|
||||
- Token-Tracking für Transparenz
|
||||
- Machen statt Lernen - Fragen stärken die Wurzel
|
||||
|
||||
**"Ein Elefant vergisst nie"** - DumboSQL
|
||||
- Crew Memory über Projekt-Grenzen
|
||||
- Persistente Konversations-Historie
|
||||
|
||||
**"wir ja nie fertig"** 🌱
|
||||
- Der Wald wächst mit jeder Idee
|
||||
- Community-driven Education
|
||||
|
||||
## 🏗️ Projekt-Struktur
|
||||
|
||||
```
|
||||
CF_Zero_V1/
|
||||
├── crumb-mission-selector.sh # Hauptmenü
|
||||
├── lib/
|
||||
│ └── waldwaechter.sh # 17 Waldwächter als Functions
|
||||
├── crumbforest_roles/ # 17 Character Scripts
|
||||
├── missions/
|
||||
│ ├── basics/ # Einsteiger
|
||||
│ ├── advanced/ # Fortgeschrittene
|
||||
│ ├── challenges/ # Herausforderungen
|
||||
│ └── robots/ # 🤖 NEU! 3 Robot-Missionen
|
||||
├── logs/ # 🌲 Projekt-basierte Logs
|
||||
│ ├── {character}/ # Pro Waldwächter
|
||||
│ └── missions/ # Mission Logs
|
||||
├── CLAUDE.md # Projekt-Dokumentation
|
||||
├── KEKSHANDBUCH_ZERO_v0.0.md # User Manual
|
||||
└── README.md # Diese Datei
|
||||
```
|
||||
|
||||
## 🔧 Setup
|
||||
|
||||
```bash
|
||||
# .env erstellen mit API Key
|
||||
echo "OPENROUTER_API_KEY=your-key-here" > .env
|
||||
|
||||
# Mission Selector starten
|
||||
./crumb-mission-selector.sh
|
||||
|
||||
# Oder direkt eine Mission
|
||||
bash missions/robots/mond_maschine.sh
|
||||
```
|
||||
|
||||
## 🎯 Use Cases
|
||||
|
||||
- **Terminal lernen** - Interaktive Bash-Basics
|
||||
- **Roboter bauen** - Hardware + Software Projekte
|
||||
- **Computer Vision** - OpenCV mit SnakePy lernen
|
||||
- **Datenbanken** - SQL mit DumboSQL verstehen
|
||||
- **APIs integrieren** - Spider zeigt den Weg
|
||||
- **Kreativ coden** - Projekte die Spaß machen!
|
||||
|
||||
## 🌟 Besonderheiten
|
||||
|
||||
- **Alle Waldwächter haben Persönlichkeit** - FunkFox rappt, Maya-Eule philosophiert
|
||||
- **Inter-Character Communication** - Waldwächter lesen andere Logs
|
||||
- **Metadata-driven** - Missions als .sh + .meta.json
|
||||
- **Token-Transparenz** - Kosten werden angezeigt
|
||||
- **Community-driven** - Missionen entstehen aus User-Ideen
|
||||
- **Raspberry Pi kompatibel** - Grundsystem auf Pi Zero, Robot- & Vision-Missionen benötigen Pi 4+ oder Desktop
|
||||
|
||||
## 📦 Requirements
|
||||
|
||||
- Bash 3.2+ (macOS default, Bash 4+ empfohlen für volle Features)
|
||||
- curl, jq
|
||||
- OpenRouter API Key
|
||||
- Optional: Python 3 für Robot-Missionen (opencv-python, numpy, ephem)
|
||||
- Mond Maschine (Computer Vision): Pi 4+ oder Desktop empfohlen
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. Mission Selector starten: `./crumb-mission-selector.sh`
|
||||
2. Eine Robot-Mission ausprobieren
|
||||
3. Eigene Mission-Idee einreichen
|
||||
4. Den Wald wachsen lassen! 🌱
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
- [LiPo 6S Charger Simulator](https://194-164-194-191.sslip.io/crumbblocks/lipo_6s_charger_sim_safe_v7.html)
|
||||
- [Rainbow Counter Demo](https://194-164-194-191.sslip.io/crumbblocks/rainbow_counter.html)
|
||||
|
||||
---
|
||||
|
||||
### 🎨 Crumbblocks & UI ⭐ NEU!
|
||||
- **Dein Zeichen im Wald** - Designe ein UI-Element im Browser
|
||||
- **Bridge-Technology** - Sende Daten via Clipboard vom Browser ins Terminal
|
||||
- **Smart Evaluation** - Die Crew erkennt automatisch, was du gebaut hast!
|
||||
|
||||
## 🏷️ Version
|
||||
|
||||
**v0.0-RC3-crumbblocks**
|
||||
- Crumbblocks Integration (Browser <-> Terminal)
|
||||
- Neue UI Mission "Dein Zeichen im Wald"
|
||||
- Smart Routing für Evaluations-Skripte
|
||||
- macOS Pfad-Fixes für Waldwächter
|
||||
- 3 Robot-Missionen
|
||||
- 17 Waldwächter komplett
|
||||
|
||||
---
|
||||
|
||||
*Erstellt mit 💚 im Crumbforest*
|
||||
|
||||
**"Der Wald ist nie fertig - er wächst mit jeder Idee!"** 🌲🌱
|
||||
|
||||
20
VERSION
Normal file
20
VERSION
Normal file
@@ -0,0 +1,20 @@
|
||||
RC0 - Release Candidate 0
|
||||
Datum: 2025-12-21
|
||||
|
||||
🌲 Der Crumbforest ist bereit
|
||||
|
||||
Was entstanden ist:
|
||||
• 17 Waldwächter - Alle Egos vereint
|
||||
• Input-Transparenz - crew_tokens (Token-Tracking)
|
||||
• Output-Transparenz - crew_memo (Krümel-Tracking)
|
||||
• Kollaborative Wurzeln - seeds/ für gemeinsames Wachsen
|
||||
• Zero Rules - Ein Script, ein Ordner, unendlich Möglichkeiten
|
||||
|
||||
Philosophie:
|
||||
eule -> kleine krümel -> tools -> nullfeld
|
||||
#ozmai #mensch #maschine
|
||||
|
||||
Der Wald mag keine Sticker.
|
||||
17 Egos im Wald. 1 Wald ohne Ego. 0 Regeln.
|
||||
|
||||
🦉 Entstanden im Nullfeld 💚
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "📦 Starte Backup des Crumbforest Zero Systems..."
|
||||
|
||||
# Zielname
|
||||
BACKUP_NAME="crumbforest_zero_backup_$(date +%Y%m%d_%H%M%S).zip"
|
||||
DEST_DIR="/home/zero"
|
||||
FULL_PATH="$DEST_DIR/$BACKUP_NAME"
|
||||
|
||||
# Verzeichnisse und Dateien sammeln
|
||||
INCLUDE_PATHS=(
|
||||
"/usr/local/bin/crumbmission"
|
||||
"/home/zero/crumbforest_backup"
|
||||
"/home/zero/.bits_logs"
|
||||
"/home/zero/.eule_logs"
|
||||
"/home/zero/.snake_logs"
|
||||
"/home/zero/.pepper_logs"
|
||||
"/home/zero/.bugsy_logs"
|
||||
"/home/zero/.deepbit_logs"
|
||||
"/home/zero/.dumbo_logs"
|
||||
"/home/zero/.funkfox_logs"
|
||||
"/home/zero/.schnecki_logs"
|
||||
"/home/zero/.schnippsi_logs"
|
||||
"/home/zero/.schraubaer_logs"
|
||||
"/home/zero/.stage_logs"
|
||||
"/home/zero/.taube_logs"
|
||||
"/home/zero/.templatus_logs"
|
||||
"/home/zero/.tobi_logs"
|
||||
"/home/zero/.missionstage_log"
|
||||
|
||||
)
|
||||
|
||||
# Existierende Pfade prüfen und nur diese einfügen
|
||||
EXISTING_PATHS=()
|
||||
for path in "${INCLUDE_PATHS[@]}"; do
|
||||
if [ -e "$path" ]; then
|
||||
echo "✅ Hinzufügen: $path"
|
||||
EXISTING_PATHS+=("$path")
|
||||
else
|
||||
echo "⚠️ Nicht gefunden (wird übersprungen): $path"
|
||||
fi
|
||||
done
|
||||
|
||||
# Archiv erstellen
|
||||
cd /
|
||||
zip -r "$FULL_PATH" "${EXISTING_PATHS[@]}" > /dev/null
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "🎉 Backup erfolgreich erstellt: $FULL_PATH"
|
||||
else
|
||||
echo "❌ Fehler beim Erstellen des Backups."
|
||||
fi
|
||||
@@ -15,9 +15,16 @@ NC='\033[0m' # No Color
|
||||
|
||||
# === KONFIGURATION ===
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="${SCRIPT_DIR}" # Save repo root before sourcing waldwaechter.sh
|
||||
MISSION_DIR="${SCRIPT_DIR}/missions"
|
||||
ENV_FILE="${SCRIPT_DIR}/.env"
|
||||
|
||||
# === LADE WALDWÄCHTER LIBRARY ===
|
||||
# Macht alle Crew-Befehle verfügbar: crew_tokens, crew_doctor, etc.
|
||||
if [[ -f "${SCRIPT_DIR}/lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# === ENVIRONMENT LOADER ===
|
||||
function load_env() {
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
@@ -493,7 +500,7 @@ function mayaeule_doktor() {
|
||||
EULE_RC="/tmp/crumb_eule_$$.rc"
|
||||
|
||||
# Absoluter Pfad zum Maya-Eule Script
|
||||
MAYAEULE_PATH="${SCRIPT_DIR}/crumbforest_roles/mayaeule_zero.sh"
|
||||
MAYAEULE_PATH="${REPO_ROOT}/crumbforest_roles/mayaeule_zero.sh"
|
||||
|
||||
cat > "${EULE_RC}" << EOF
|
||||
# Load .bashrc if exists
|
||||
@@ -558,22 +565,63 @@ function eule_memory() {
|
||||
}
|
||||
|
||||
function eule_tokens() {
|
||||
LOGDIR="\$HOME/.eule_logs"
|
||||
if [[ -f "\$LOGDIR/token_log.json" ]]; then
|
||||
# Use the new crew_tokens function from waldwaechter.sh
|
||||
# All logs are now in the repo logs/ directory
|
||||
if command -v crew_tokens &> /dev/null; then
|
||||
crew_tokens
|
||||
else
|
||||
# Fallback if waldwaechter.sh not loaded
|
||||
LOGDIR="${REPO_ROOT}/logs"
|
||||
echo -e "\${CYAN}📊 Token-Verbrauch:\${NC}"
|
||||
echo ""
|
||||
TOTAL=0
|
||||
while IFS= read -r line; do
|
||||
zeit=\$(echo "\$line" | jq -r '.zeit')
|
||||
tokens=\$(echo "\$line" | jq -r '.usage.total_tokens')
|
||||
TOTAL=\$((TOTAL + tokens))
|
||||
echo " \$zeit: \$tokens Tokens"
|
||||
done < "\$LOGDIR/token_log.json"
|
||||
|
||||
if [[ -d "\$LOGDIR" ]]; then
|
||||
local total_tokens=0
|
||||
local found_any=false
|
||||
|
||||
for token_file in "\$LOGDIR"/*/token_log.json; do
|
||||
if [[ -f "\$token_file" ]]; then
|
||||
local character=\$(basename "\$(dirname "\$token_file")")
|
||||
local char_tokens=\$(grep -v '^\[\]\$' "\$token_file" | jq -s 'map(.usage.total_tokens // 0) | add // 0' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ \$char_tokens -gt 0 ]]; then
|
||||
echo " \$character: \$char_tokens Tokens"
|
||||
total_tokens=\$((total_tokens + char_tokens))
|
||||
found_any=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [[ \$found_any == true ]]; then
|
||||
echo -e "\${GREEN}Gesamt: \$total_tokens Tokens\${NC}"
|
||||
else
|
||||
echo "Noch keine Token-Logs vorhanden"
|
||||
fi
|
||||
else
|
||||
echo "Log-Verzeichnis nicht gefunden: \$LOGDIR"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "\${GREEN}Gesamt: \$TOTAL Tokens\${NC}"
|
||||
echo -e "\${YELLOW}Jede Frage ist wertvoll 🌲\${NC}"
|
||||
else
|
||||
echo "Noch keine Token-Logs."
|
||||
echo -e "\${YELLOW}Token Budget & Philosophie:\${NC}"
|
||||
echo " \"Was kostet die Frage eines Kindes?\""
|
||||
echo " Im Wald unbezahlbar - Token lehren achtsames Fragen."
|
||||
echo ""
|
||||
if [[ -n "\${DAILY_TOKEN_BUDGET}" ]] && [[ "\${DAILY_TOKEN_BUDGET}" != "0" ]]; then
|
||||
echo " Tages-Budget: \${DAILY_TOKEN_BUDGET} Tokens"
|
||||
else
|
||||
echo " Tages-Budget: Unbegrenzt"
|
||||
fi
|
||||
if [[ "\${ENABLE_TOKEN_TRACKING}" == "true" ]]; then
|
||||
echo " Token-Tracking: Aktiviert"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "\${CYAN}Waldwächter (AI Charaktere):\${NC}"
|
||||
echo ""
|
||||
echo -e "\${YELLOW}Token-Logs:\${NC}"
|
||||
if [[ \$found_any == false ]]; then
|
||||
echo " Noch keine Logs vorhanden"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -610,7 +658,7 @@ function crumbcrew_doktor() {
|
||||
CREW_RC="/tmp/crumb_crew_$$.rc"
|
||||
|
||||
# Pfade zu allen Charakteren
|
||||
CREW_DIR="${SCRIPT_DIR}/crumbforest_roles"
|
||||
CREW_DIR="${REPO_ROOT}/crumbforest_roles"
|
||||
|
||||
cat > "${CREW_RC}" << EOF
|
||||
# Load .bashrc if exists
|
||||
@@ -629,9 +677,15 @@ NC='\033[0m'
|
||||
# Prompt im CrumbCrew-Style
|
||||
export PS1="\[\033[1;32m\](🌲 CrumbCrew) \u@\h:\w\$ \[\033[0m\]"
|
||||
|
||||
# Pfad zu den Charakteren
|
||||
# Repo Root und Pfade
|
||||
REPO_ROOT="${REPO_ROOT}"
|
||||
CREW_DIR="${CREW_DIR}"
|
||||
|
||||
# Lade Waldwächter Library für crew_tokens, crew_status, etc.
|
||||
if [[ -f "\${REPO_ROOT}/lib/waldwaechter.sh" ]]; then
|
||||
source "\${REPO_ROOT}/lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# === WALDWÄCHTER FUNKTIONEN ===
|
||||
|
||||
function mayaeule() {
|
||||
@@ -670,6 +724,15 @@ function schnippsi() {
|
||||
"\$CREW_DIR/schnippsi_zero.sh" "\$@"
|
||||
}
|
||||
|
||||
function templatus() {
|
||||
if [[ -z "\$1" ]]; then
|
||||
echo -e "\${YELLOW}Verwendung: templatus \"Deine Frage\"\${NC}"
|
||||
return
|
||||
fi
|
||||
echo -e "\${BLUE}📄 Templatus strukturiert...\${NC}"
|
||||
"\$CREW_DIR/templatus_zero.sh" "\$@"
|
||||
}
|
||||
|
||||
function tobi() {
|
||||
if [[ -z "\$1" ]]; then
|
||||
echo -e "\${YELLOW}Verwendung: tobi \"Deine Frage\"\${NC}"
|
||||
@@ -795,6 +858,7 @@ function crew_help() {
|
||||
echo -e " \${BLUE}🐙 deepbit\${NC} - Der poetische Oktopus (Bash-Konzepte)"
|
||||
echo -e " \${RED}🐞 bugsy\${NC} - Der Debugging-Clown (Fehlersuche)"
|
||||
echo -e " \${CYAN}✂️ schnippsi\${NC} - Der Shell-Helfer (Kommandos)"
|
||||
echo -e " \${BLUE}📄 templatus\${NC} - Der Template-Master (HTML, Struktur)"
|
||||
echo -e " \${GREEN}🤖 tobi\${NC} - Der Daten-Experte (JSON, Daten)"
|
||||
echo -e " \${CYAN}🔧 schraubaer\${NC} - Der Handwerker (Werkzeug, Schweißen)"
|
||||
echo -e " \${GREEN}🐌 schnecki\${NC} - Der Elektronik-Bastler (Löten, Sensoren)"
|
||||
@@ -824,6 +888,7 @@ function crew_help() {
|
||||
echo " deepbit \"Erkläre Pipes und Redirects\""
|
||||
echo " bugsy \"Warum funktioniert mein Script nicht?\""
|
||||
echo " schnippsi \"Wie nutze ich grep?\""
|
||||
echo " templatus \"Erstelle eine HTML5 Struktur\""
|
||||
echo " tobi \"Parse dieses JSON\""
|
||||
echo " schraubaer \"Welches Werkzeug brauche ich zum Löten?\""
|
||||
echo " schnecki \"Wie löte ich eine LED an einen Widerstand?\""
|
||||
@@ -848,7 +913,7 @@ function crew_status() {
|
||||
echo -e "\${CYAN}🌲 CrumbCrew Status\${NC}"
|
||||
echo ""
|
||||
|
||||
CHARS=("mayaeule:🦉:Maya-Eule" "deepbit:🐙:Deepbit" "bugsy:🐞:Bugsy" "schnippsi:✂️:Schnippsi" "tobi:🤖:Tobi" "schraubaer:🔧:Schraubbär" "schnecki:🐌:Schnecki" "dumbosql:🐘:DumboSQL" "funkfox:🦊:FunkFox" "taichitaube:🕊️:TaichiTaube" "snakepy:🐍:SnakePy" "pepperphp:🧓:PepperPHP" "crabbyrust:🦀:CrabbyRust" "spider:🕷️:Spider" "vektor:🧭:Vektor" "asciimonster:👾:ASCII-Monster")
|
||||
CHARS=("mayaeule:🦉:Maya-Eule" "deepbit:🐙:Deepbit" "bugsy:🐞:Bugsy" "schnippsi:✂️:Schnippsi" "templatus:📄:Templatus" "tobi:🤖:Tobi" "schraubaer:🔧:Schraubbär" "schnecki:🐌:Schnecki" "dumbosql:🐘:DumboSQL" "funkfox:🦊:FunkFox" "taichitaube:🕊️:TaichiTaube" "snakepy:🐍:SnakePy" "pepperphp:🧓:PepperPHP" "crabbyrust:🦀:CrabbyRust" "spider:🕷️:Spider" "vektor:🧭:Vektor" "asciimonster:👾:ASCII-Monster")
|
||||
|
||||
for char_info in "\${CHARS[@]}"; do
|
||||
IFS=: read -r name icon display <<< "\$char_info"
|
||||
@@ -877,52 +942,8 @@ function crew_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
function crew_tokens() {
|
||||
echo -e "\${CYAN}📊 CrumbCrew Token-Verbrauch\${NC}"
|
||||
echo ""
|
||||
|
||||
TOTAL=0
|
||||
|
||||
for logdir in ~/.{mayaeule,eule,deepbit,bugsy,schnippsi,tobi,schraubaer,schnecki,dumbosql,funkfox,taichitaube,snakepy,pepperphp,crabbyrust,spider,vektor,asciimonster}_logs; do
|
||||
if [[ -d "\$logdir" ]] && [[ -f "\$logdir/token_log.json" ]]; then
|
||||
char_name=\$(basename "\$logdir" | sed 's/_logs//')
|
||||
char_tokens=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
tokens=\$(echo "\$line" | jq -r '.usage.total_tokens' 2>/dev/null)
|
||||
if [[ "\$tokens" != "null" ]] && [[ -n "\$tokens" ]]; then
|
||||
char_tokens=\$((char_tokens + tokens))
|
||||
fi
|
||||
done < "\$logdir/token_log.json"
|
||||
|
||||
if [[ \$char_tokens -gt 0 ]]; then
|
||||
echo -e " \${GREEN}\$char_name:\${NC} \$char_tokens Tokens"
|
||||
TOTAL=\$((TOTAL + char_tokens))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "\${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\${NC}"
|
||||
echo -e " \${GREEN}Gesamt: \$TOTAL Tokens\${NC}"
|
||||
echo -e " \${CYAN}Jede Frage ist wertvoll 🌲\${NC}"
|
||||
echo -e "\${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\${NC}"
|
||||
}
|
||||
|
||||
function crew_memory() {
|
||||
echo -e "\${CYAN}📜 CrumbCrew Erinnerungen\${NC}"
|
||||
echo ""
|
||||
|
||||
for logdir in ~/.{mayaeule,eule,deepbit,bugsy,schnippsi,tobi,schraubaer,schnecki,dumbosql,funkfox,taichitaube,snakepy,pepperphp,crabbyrust,spider,vektor,asciimonster}_logs; do
|
||||
if [[ -d "\$logdir" ]] && [[ -f "\$logdir/*_history.json" ]]; then
|
||||
char_name=\$(basename "\$logdir" | sed 's/_logs//')
|
||||
count=\$(jq '. | length' "\$logdir/*_history.json" 2>/dev/null)
|
||||
if [[ "\$count" != "null" ]] && [[ \$count -gt 0 ]]; then
|
||||
echo -e " \${GREEN}\$char_name:\${NC} \$count Gespräche"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
# crew_tokens, crew_memory, crew_status, crew_doctor, crew_syntax, crew_help
|
||||
# sind bereits in waldwaechter.sh definiert und werden automatisch geladen
|
||||
|
||||
alias help="crew_help"
|
||||
alias status="crew_status"
|
||||
|
||||
631
crumbblocks/bashpanda_guertelpruefung.html
Normal file
631
crumbblocks/bashpanda_guertelpruefung.html
Normal file
@@ -0,0 +1,631 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🐼 BashPanda Gürtelprüfung 🥋</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', monospace;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||
color: #e0e0e0;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.belt-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.belt-btn {
|
||||
padding: 20px;
|
||||
border: 3px solid;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
background: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.belt-btn:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.belt-btn.active {
|
||||
box-shadow: 0 0 20px currentColor;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.black { border-color: #000; color: #fff; }
|
||||
.pink { border-color: #ff69b4; color: #ff69b4; }
|
||||
.blue { border-color: #4169e1; color: #4169e1; }
|
||||
.green { border-color: #32cd32; color: #32cd32; }
|
||||
.yellow { border-color: #ffd700; color: #ffd700; }
|
||||
.white { border-color: #f0f0f0; color: #f0f0f0; }
|
||||
|
||||
.quiz-container {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quiz-container.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.question-text {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-left: 4px solid;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.option {
|
||||
padding: 15px 20px;
|
||||
border: 2px solid #4a4a4a;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-color: #6a6a6a;
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
border-color: #ffd700;
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
}
|
||||
|
||||
.option.correct {
|
||||
border-color: #32cd32;
|
||||
background: rgba(50, 205, 50, 0.2);
|
||||
}
|
||||
|
||||
.option.wrong {
|
||||
border-color: #ff4444;
|
||||
background: rgba(255, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 15px 30px;
|
||||
border: 2px solid #4a4a4a;
|
||||
border-radius: 8px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: #e0e0e0;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-color: #6a6a6a;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-color: #764ba2;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.results {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 15px;
|
||||
border: 2px solid #4a4a4a;
|
||||
}
|
||||
|
||||
.results.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.results h2 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.results .score {
|
||||
font-size: 3em;
|
||||
font-weight: bold;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.results .verdict {
|
||||
font-size: 1.5em;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.export-info {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 215, 0, 0.1);
|
||||
border: 2px solid #ffd700;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🐼 BashPanda Gürtelprüfung 🥋</h1>
|
||||
<div class="subtitle">"Beweise dein Wissen, junger Schüler"</div>
|
||||
</div>
|
||||
|
||||
<div class="belt-selector" id="beltSelector">
|
||||
<div class="belt-btn black" data-belt="schwarz">🖤<br>Schwarz</div>
|
||||
<div class="belt-btn pink" data-belt="pink">💖<br>Pink</div>
|
||||
<div class="belt-btn blue" data-belt="blau">💙<br>Blau</div>
|
||||
<div class="belt-btn green" data-belt="gruen">💚<br>Grün</div>
|
||||
<div class="belt-btn yellow" data-belt="gelb">💛<br>Gelb</div>
|
||||
<div class="belt-btn white" data-belt="weiss">🤍<br>Weiss</div>
|
||||
</div>
|
||||
|
||||
<div class="quiz-container" id="quizContainer">
|
||||
<div class="progress" id="progress"></div>
|
||||
<div id="questionContainer"></div>
|
||||
<div class="controls">
|
||||
<button class="btn" id="prevBtn" disabled>⬅️ Zurück</button>
|
||||
<button class="btn btn-primary" id="nextBtn">Weiter ➡️</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results" id="results"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const quizData = {
|
||||
schwarz: [
|
||||
{
|
||||
question: "Was gibt 'echo $HOME' aus?",
|
||||
options: ["Dein Home-Verzeichnis", "Die Umgebungsvariable HOME", "Beide Antworten sind richtig", "Einen Fehler"],
|
||||
correct: 2,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Wie erstellt man eine Variable?",
|
||||
options: ["name = value", "name=value", "$name=value", "set name=value"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'read -p \"Name: \" name'?",
|
||||
options: ["Liest eine Datei", "Zeigt 'Name:' und speichert Eingabe in $name", "Gibt $name aus", "Nichts"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Welcher ANSI Code macht Text fett?",
|
||||
options: ["\\e[0m", "\\e[1m", "\\e[4m", "\\e[32m"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
},
|
||||
{
|
||||
question: "Was ist der Unterschied zwischen ' und \" in echo?",
|
||||
options: ["Keiner", "' ignoriert Variablen, \" ersetzt sie", "\" ignoriert Variablen", "Beide ersetzen Variablen"],
|
||||
correct: 1,
|
||||
color: "#fff"
|
||||
}
|
||||
],
|
||||
pink: [
|
||||
{
|
||||
question: "Was prüft [ $a -eq $b ]?",
|
||||
options: ["String-Gleichheit", "Zahlen-Gleichheit", "Größer als", "Kleiner als"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Wie greift man auf das 3. Array-Element zu?",
|
||||
options: ["${arr[3]}", "${arr[2]}", "$arr[2]", "arr[3]"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'while [ $i -le 5 ]; do ... done'?",
|
||||
options: ["Läuft 4 mal", "Läuft 5 mal", "Läuft 6 mal", "Endlosschleife"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Wie zählt man Arrays hoch?",
|
||||
options: ["i++", "i=$((i + 1))", "let i++", "Alle sind richtig"],
|
||||
correct: 1,
|
||||
color: "#ff69b4"
|
||||
},
|
||||
{
|
||||
question: "Was gibt ${#arr[@]} zurück?",
|
||||
options: ["Erstes Element", "Letztes Element", "Anzahl Elemente", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#ff69b4"
|
||||
}
|
||||
],
|
||||
blau: [
|
||||
{
|
||||
question: "Was macht 'sed s/alt/neu/'?",
|
||||
options: ["Löscht 'alt'", "Ersetzt erstes 'alt' mit 'neu'", "Ersetzt alle 'alt' mit 'neu'", "Sucht nach 'alt'"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wofür nutzt man 'case' statt if/then?",
|
||||
options: ["Für Schleifen", "Für mehrere if-Alternativen", "Für Arrays", "Für Funktionen"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wie rechnet man mit Fließkommazahlen?",
|
||||
options: ["$(( ))", "bc", "let", "expr"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Was bedeutet das * in case?",
|
||||
options: ["Multiplikation", "Wildcard (alles)", "Default-Fall", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#4169e1"
|
||||
},
|
||||
{
|
||||
question: "Wie nutzt man bc für Division?",
|
||||
options: ["bc 10/3", "echo '10/3' | bc", "10/3 | bc", "bc(10/3)"],
|
||||
correct: 1,
|
||||
color: "#4169e1"
|
||||
}
|
||||
],
|
||||
gruen: [
|
||||
{
|
||||
question: "Was macht grep -i?",
|
||||
options: ["Zeigt Zeilennummern", "Ignoriert Groß-/Kleinschreibung", "Invertiert Suche", "Rekursiv suchen"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was bedeutet ^ in Regex?",
|
||||
options: ["Zeilenanfang", "Zeilenende", "Beliebiges Zeichen", "Wiederholung"],
|
||||
correct: 0,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was matched [0-9]+ in Regex?",
|
||||
options: ["Genau eine Ziffer", "Eine oder mehr Ziffern", "Null oder mehr Ziffern", "Keine Ziffer"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Wie sucht man rekursiv in allen Dateien?",
|
||||
options: ["grep muster *", "grep -r muster .", "grep -i muster", "grep -n muster"],
|
||||
correct: 1,
|
||||
color: "#32cd32"
|
||||
},
|
||||
{
|
||||
question: "Was matched .* in Regex?",
|
||||
options: ["Nichts", "Punkt und Stern", "Beliebige Zeichen (0+)", "Ein Zeichen"],
|
||||
correct: 2,
|
||||
color: "#32cd32"
|
||||
}
|
||||
],
|
||||
gelb: [
|
||||
{
|
||||
question: "Wie definiert man eine Funktion?",
|
||||
options: ["func name() {}", "function name() {}", "def name() {}", "Beide 1 und 2"],
|
||||
correct: 3,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was ist $1 in einer Funktion?",
|
||||
options: ["Erster Buchstabe", "Erster Parameter", "Exit-Code", "PID"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'source datei.sh'?",
|
||||
options: ["Führt Datei aus", "Lädt Funktionen in aktuelle Shell", "Kopiert Datei", "Löscht Datei"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'return 42' in einer Funktion?",
|
||||
options: ["Gibt 42 aus", "Beendet Skript", "Verlässt Funktion mit Code 42", "Fehler"],
|
||||
correct: 2,
|
||||
color: "#ffd700"
|
||||
},
|
||||
{
|
||||
question: "Was ist $@ in einer Funktion?",
|
||||
options: ["Erster Parameter", "Alle Parameter", "Anzahl Parameter", "Funktionsname"],
|
||||
correct: 1,
|
||||
color: "#ffd700"
|
||||
}
|
||||
],
|
||||
weiss: [
|
||||
{
|
||||
question: "Was macht & am Ende eines Befehls?",
|
||||
options: ["Beendet ihn", "Startet im Hintergrund", "Wiederholt ihn", "Nichts"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was ist $! ?",
|
||||
options: ["Exit-Code", "PID des letzten Background-Jobs", "Anzahl Parameter", "Home-Verzeichnis"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'wait'?",
|
||||
options: ["Wartet 1 Sekunde", "Wartet auf Eingabe", "Wartet auf Background-Jobs", "Pausiert Skript"],
|
||||
correct: 2,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Wie zeigt man alle Background-Jobs?",
|
||||
options: ["ps", "jobs", "bg", "fg"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
},
|
||||
{
|
||||
question: "Was macht 'fg %1'?",
|
||||
options: ["Beendet Job 1", "Bringt Job 1 in Vordergrund", "Startet Job 1", "Pausiert Job 1"],
|
||||
correct: 1,
|
||||
color: "#f0f0f0"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let currentBelt = null;
|
||||
let currentQuestion = 0;
|
||||
let answers = [];
|
||||
let score = 0;
|
||||
|
||||
// Belt Selection
|
||||
document.getElementById('beltSelector').addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.belt-btn');
|
||||
if (!btn) return;
|
||||
|
||||
document.querySelectorAll('.belt-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
currentBelt = btn.dataset.belt;
|
||||
currentQuestion = 0;
|
||||
answers = [];
|
||||
score = 0;
|
||||
|
||||
document.getElementById('quizContainer').classList.add('active');
|
||||
document.getElementById('results').classList.remove('show');
|
||||
|
||||
showQuestion();
|
||||
});
|
||||
|
||||
function showQuestion() {
|
||||
if (!currentBelt) return;
|
||||
|
||||
const questions = quizData[currentBelt];
|
||||
const q = questions[currentQuestion];
|
||||
|
||||
document.getElementById('progress').innerHTML = `
|
||||
<span>Frage ${currentQuestion + 1} von ${questions.length}</span>
|
||||
<span>🐼 BashPanda beobachtet</span>
|
||||
`;
|
||||
|
||||
const questionHTML = `
|
||||
<div class="question">
|
||||
<div class="question-text" style="border-color: ${q.color}">
|
||||
${currentQuestion + 1}. ${q.question}
|
||||
</div>
|
||||
<div class="options">
|
||||
${q.options.map((opt, idx) => `
|
||||
<div class="option" data-index="${idx}">
|
||||
${String.fromCharCode(65 + idx)}. ${opt}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('questionContainer').innerHTML = questionHTML;
|
||||
|
||||
// Restore previous answer if exists
|
||||
if (answers[currentQuestion] !== undefined) {
|
||||
const selected = document.querySelector(`[data-index="${answers[currentQuestion]}"]`);
|
||||
if (selected) selected.classList.add('selected');
|
||||
}
|
||||
|
||||
// Option selection
|
||||
document.querySelectorAll('.option').forEach(opt => {
|
||||
opt.addEventListener('click', () => {
|
||||
document.querySelectorAll('.option').forEach(o => o.classList.remove('selected'));
|
||||
opt.classList.add('selected');
|
||||
answers[currentQuestion] = parseInt(opt.dataset.index);
|
||||
document.getElementById('nextBtn').disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
document.getElementById('prevBtn').disabled = currentQuestion === 0;
|
||||
document.getElementById('nextBtn').disabled = answers[currentQuestion] === undefined;
|
||||
|
||||
const questions = quizData[currentBelt];
|
||||
if (currentQuestion === questions.length - 1) {
|
||||
document.getElementById('nextBtn').textContent = '🎯 Auswerten';
|
||||
} else {
|
||||
document.getElementById('nextBtn').textContent = 'Weiter ➡️';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('prevBtn').addEventListener('click', () => {
|
||||
if (currentQuestion > 0) {
|
||||
currentQuestion--;
|
||||
showQuestion();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('nextBtn').addEventListener('click', () => {
|
||||
const questions = quizData[currentBelt];
|
||||
|
||||
if (currentQuestion < questions.length - 1) {
|
||||
currentQuestion++;
|
||||
showQuestion();
|
||||
} else {
|
||||
showResults();
|
||||
}
|
||||
});
|
||||
|
||||
function showResults() {
|
||||
const questions = quizData[currentBelt];
|
||||
score = 0;
|
||||
|
||||
answers.forEach((ans, idx) => {
|
||||
if (ans === questions[idx].correct) {
|
||||
score++;
|
||||
}
|
||||
});
|
||||
|
||||
const percentage = (score / questions.length * 100).toFixed(0);
|
||||
let verdict, color, emoji;
|
||||
|
||||
if (percentage >= 80) {
|
||||
verdict = "🎉 BESTANDEN! 🎉<br>Der Gürtel gehört dir!";
|
||||
color = "#32cd32";
|
||||
emoji = "✅";
|
||||
} else if (percentage >= 60) {
|
||||
verdict = "Fast geschafft!<br>Übe noch ein wenig.";
|
||||
color = "#ffd700";
|
||||
emoji = "⚠️";
|
||||
} else {
|
||||
verdict = "Noch nicht bereit.<br>Lerne weiter, junger Schüler.";
|
||||
color = "#ff4444";
|
||||
emoji = "❌";
|
||||
}
|
||||
|
||||
const resultData = {
|
||||
belt: currentBelt,
|
||||
score: score,
|
||||
total: questions.length,
|
||||
percentage: percentage,
|
||||
passed: percentage >= 80,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
document.getElementById('results').innerHTML = `
|
||||
<h2>${emoji} Gürtelprüfung: ${currentBelt.toUpperCase()}</h2>
|
||||
<div class="score" style="color: ${color}">
|
||||
${score} / ${questions.length}
|
||||
</div>
|
||||
<div style="font-size: 2em; margin: 20px 0;">
|
||||
${percentage}%
|
||||
</div>
|
||||
<div class="verdict" style="border-color: ${color}">
|
||||
${verdict}
|
||||
</div>
|
||||
<div class="export-info">
|
||||
<p><strong>📋 Ergebnis exportiert!</strong></p>
|
||||
<p>Die Ergebnisse wurden in die Zwischenablage kopiert.</p>
|
||||
<p>Füge sie im Terminal ein für die Auswertung!</p>
|
||||
<br>
|
||||
<button class="btn btn-primary" onclick="location.reload()">🔄 Neue Prüfung</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('quizContainer').classList.remove('active');
|
||||
document.getElementById('results').classList.add('show');
|
||||
|
||||
// Copy to clipboard
|
||||
const exportText = JSON.stringify(resultData, null, 2);
|
||||
navigator.clipboard.writeText(exportText).then(() => {
|
||||
console.log('Ergebnisse in Zwischenablage kopiert!');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
628
crumbblocks/bezier.html
Normal file
628
crumbblocks/bezier.html
Normal file
@@ -0,0 +1,628 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter • Bezier Pen + Editor</title>
|
||||
<style>
|
||||
:root{ --bg:#0b0b10; --panel:#12121a; --muted:#9aa3b2; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c; }
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none;font-size:.9rem;color:var(--muted)}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:380px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:1100px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.col{display:grid;gap:10px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0b1114;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0b0b10}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:240px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a;touch-action:none}
|
||||
.badge{font-size:.75rem;color:#0b1114;background:var(--accent);border-radius:999px;padding:2px 8px}
|
||||
.muted{color:var(--muted)}
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
.anchor{fill:#31e07b;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handle{fill:#ffe08a;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handleLine{stroke:#ffe08a;stroke-width:2.5;stroke-dasharray:4 4; pointer-events:none}
|
||||
.ghost{fill:none;stroke:#ffffff55;stroke-width:2;stroke-dasharray:6 8}
|
||||
#bezierUI{ pointer-events:none }
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
.hint{font-size:.85rem;color:#cfd5e3;line-height:1.3}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter • <span class="badge">Bezier-Pen</span> + Editor • Replay • SVG/PNG/JSON</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel">
|
||||
<h2 style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits „1“</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop</span></label>
|
||||
<label class="chip"><input type="checkbox" id="frameOn" checked> <span>Crumblines-Frame</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke <input type="range" id="stroke" min="4" max="28" step="1" value="12"></label>
|
||||
<label>Sampler
|
||||
<select id="sampler">
|
||||
<option value="raw">Raw Polyline</option>
|
||||
<option value="quad">Quadratic</option>
|
||||
<option value="cubic" selected>Catmull‑Rom → Cubic</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier‑Pen (Anker setzen)</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="penMode"> <span>Pen aktiv</span></label>
|
||||
<button class="btn" id="finishPen" disabled>Fertig (↵)</button>
|
||||
<span class="muted">Shift: Winkel snap • Alt: Ecke • ⌫: Letzten Anchor löschen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier‑Editor</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="editBezier"> <span>Bearbeiten</span></label>
|
||||
<label class="chip"><input type="checkbox" id="linkHandles" checked> <span>Spiegel‑Griffe</span></label>
|
||||
<button class="btn" id="toBezier">Auswahl → Bezier</button>
|
||||
<button class="btn" id="splitHandles">Anker: Split</button>
|
||||
<button class="btn warn" id="delAnchor" disabled>Anker löschen</button>
|
||||
</div>
|
||||
<p class="hint">Klicke einen Stroke → <em>Bearbeiten</em>. Doppelklick auf Pfad fügt einen Anker. <span class="kbd">L</span> toggelt Spiegel‑Griffe.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich-Liste"></ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="replay">▶︎ Replay</button>
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn" id="downloadPNG">PNG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect id="frameRect" x="16.5" y="16.5" width="1167" height="767" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1.5" stroke-dasharray="6 10"/>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<path id="ghost" class="ghost" d=""/>
|
||||
<g id="bezierUI"></g>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>Neu: richtiger Bezier‑Pen wie in Illustrator/Inkscape (Anker setzen & ziehen).</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const ghost = document.getElementById('ghost');
|
||||
const bezierUI= document.getElementById('bezierUI');
|
||||
const frameRect = document.getElementById('frameRect');
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const frameOnCb = document.getElementById('frameOn');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const samplerSel= document.getElementById('sampler');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
|
||||
const penModeCb = document.getElementById('penMode');
|
||||
const finishPenBtn = document.getElementById('finishPen');
|
||||
|
||||
const editBezierCb = document.getElementById('editBezier');
|
||||
const linkHandlesCb= document.getElementById('linkHandles');
|
||||
const toBezierBtn = document.getElementById('toBezier');
|
||||
const splitBtn = document.getElementById('splitHandles');
|
||||
const delAnchorBtn = document.getElementById('delAnchor');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const dlPNGBtn = document.getElementById('downloadPNG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
const replayBtn = document.getElementById('replay');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null;
|
||||
const strokes = [];
|
||||
let t0 = null;
|
||||
let selectedStroke = null;
|
||||
let selectedAnchor = {stroke:null, idx:-1};
|
||||
let dragging = null; // edit-drag
|
||||
|
||||
// Pen state
|
||||
let penActive = false;
|
||||
let penStroke = null; // {isBezier:true, anchors:[], pathEl,...}
|
||||
let penDragging = false;
|
||||
let placingFirst = false;
|
||||
let placingNext = false;
|
||||
let penStartPt = null;
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const p = pt.matrixTransform(stage.getScreenCTM().inverse());
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
p.addEventListener('pointerdown', (e)=>{
|
||||
if(editBezierCb.checked && !penActive){ e.stopPropagation(); const s = strokes.find(st=>st.pathEl===p); if(s){ selectStroke(s); drawBezierUI(); } }
|
||||
});
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
function pathFromAnchors(A){
|
||||
if(!A || A.length<2) return '';
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const edit = document.createElement('button'); edit.className='btn'; edit.textContent='Bearbeiten';
|
||||
edit.addEventListener('click',()=>{ selectStroke(s); editBezierCb.checked = true; drawBezierUI(); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${s.isBezier? 'Bezier' : (s.points?.length||0)+' pts'}`;
|
||||
li.append('Stroke', sel, sw, edit, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx]; if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
if(selectedStroke===s){ selectedStroke=null; clearBezierUI(); }
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
function selectStroke(s){
|
||||
selectedStroke = s;
|
||||
strokes.forEach(st=> st.pathEl.classList.toggle('selected', st===s));
|
||||
}
|
||||
|
||||
// -------- Freehand --------
|
||||
function toPathD(s){
|
||||
if(s.isBezier) return pathFromAnchors(s.anchors);
|
||||
const pts = s.points;
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
if(pts.length===0) return '';
|
||||
if(smp==='raw'){
|
||||
let d = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=1;i<pts.length;i++){ d+=` L ${pts[i].x} ${pts[i].y}`; } return d;
|
||||
} else if(smp==='cubic'){
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)); const P=(i)=>pts[ clamp(i,0,pts.length-1) ];
|
||||
let d=`M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=0;i<pts.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1x = p1.x + (p2.x - p0.x)/6, c1y = p1.y + (p2.y - p0.y)/6;
|
||||
const c2x = p2.x - (p3.x - p1.x)/6, c2y = p2.y - (p3.y - p1.y)/6;
|
||||
d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
|
||||
} return d;
|
||||
} else {
|
||||
let d = `M ${pts[0].x} ${pts[0].y}`;
|
||||
for(let i=1;i<pts.length-1;i++){ const p0=pts[i], p1=pts[i+1]; const mx=(p0.x+p1.x)/2, my=(p0.y+p1.y)/2; d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`; }
|
||||
const last=pts[pts.length-1]; d += ` L ${last.x} ${last.y}`; return d;
|
||||
}
|
||||
}
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function startStroke(e){
|
||||
if(penActive || editBezierCb.checked){ return; }
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
const t = now();
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[pt], pathEl, start:t, duration:0, length:0, sampler:samplerSel.value, isBezier:false };
|
||||
applyStrokeStyle(curStroke); updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
drawing = true;
|
||||
}
|
||||
stage.addEventListener('pointermove', (e)=>{
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push(p);
|
||||
updatePath(curStroke);
|
||||
}
|
||||
});
|
||||
window.addEventListener('pointerup', ()=>{
|
||||
if(drawing && curStroke){
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
drawing=false;
|
||||
});
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
// Editor helpers
|
||||
function clearBezierUI(){ bezierUI.innerHTML=''; selectedAnchor={stroke:null, idx:-1}; delAnchorBtn.disabled = true; }
|
||||
function drawBezierUI(){
|
||||
clearBezierUI();
|
||||
const s = selectedStroke;
|
||||
if(!editBezierCb.checked || !s || !s.isBezier) return;
|
||||
s.anchors.forEach((a,idx)=>{
|
||||
bezierUI.append(line(a.x, a.y, a.h1.x, a.h1.y, 'handleLine'));
|
||||
bezierUI.append(line(a.x, a.y, a.h2.x, a.h2.y, 'handleLine'));
|
||||
const h1 = circle(a.h1.x, a.h1.y, 5, 'handle', (e)=>startDrag('h1', s, idx, e));
|
||||
const h2 = circle(a.h2.x, a.h2.y, 5, 'handle', (e)=>startDrag('h2', s, idx, e));
|
||||
bezierUI.append(h1, h2);
|
||||
const an = circle(a.x, a.y, 6.5, 'anchor', (e)=>{ startDrag('anchor', s, idx, e) });
|
||||
an.addEventListener('dblclick', (e)=>{ e.stopPropagation(); insertAnchorAt(s, idx); });
|
||||
an.addEventListener('pointerdown', ()=>{ selectedAnchor={stroke:s,idx}; delAnchorBtn.disabled=false; });
|
||||
bezierUI.append(an);
|
||||
});
|
||||
}
|
||||
function line(x1,y1,x2,y2,cls){ const l = document.createElementNS('http://www.w3.org/2000/svg','line'); l.setAttribute('x1',x1); l.setAttribute('y1',y1); l.setAttribute('x2',x2); l.setAttribute('y2',y2); l.setAttribute('class',cls); return l; }
|
||||
function circle(x,y,r,cls,onDown){
|
||||
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class',cls);
|
||||
c.style.touchAction = 'none';
|
||||
if(onDown) c.addEventListener('pointerdown', (e)=>{ e.stopPropagation(); onDown(e); });
|
||||
return c;
|
||||
}
|
||||
function startDrag(type, s, idx, ev){
|
||||
ev.preventDefault();
|
||||
const a = s.anchors[idx];
|
||||
const p0 = svgPoint(ev);
|
||||
const dragging = {type, stroke:s, idx, ox:p0.x, oy:p0.y, pid:ev.pointerId};
|
||||
try { stage.setPointerCapture(ev.pointerId); } catch(e){}
|
||||
const move = (e)=>{
|
||||
const pos = svgPoint(e);
|
||||
const dx = pos.x - dragging.ox, dy = pos.y - dragging.oy;
|
||||
dragging.ox = pos.x; dragging.oy = pos.y;
|
||||
if(type==='anchor'){
|
||||
a.x+=dx; a.y+=dy; a.h1.x+=dx; a.h1.y+=dy; a.h2.x+=dx; a.h2.y+=dy;
|
||||
}else if(type==='h1'){
|
||||
a.h1.x+=dx; a.h1.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h2.x = a.x - (a.h1.x - a.x);
|
||||
a.h2.y = a.y - (a.h1.y - a.y);
|
||||
}
|
||||
}else if(type==='h2'){
|
||||
a.h2.x+=dx; a.h2.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h1.x = a.x - (a.h2.x - a.x);
|
||||
a.h1.y = a.y - (a.h2.y - a.y);
|
||||
}
|
||||
}
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
};
|
||||
const up = ()=>{
|
||||
try { stage.releasePointerCapture(ev.pointerId); } catch(_) {}
|
||||
window.removeEventListener('pointermove', move);
|
||||
};
|
||||
window.addEventListener('pointermove', move, {passive:true});
|
||||
window.addEventListener('pointerup', up, {once:true});
|
||||
}
|
||||
function insertAnchorAt(s, idx){
|
||||
const A = s.anchors; if(idx>=A.length-1) return;
|
||||
const a=A[idx], b=A[idx+1];
|
||||
const mid = {x:(a.x+b.x)/2, y:(a.y+b.y)/2};
|
||||
const dir = {x:(b.x-a.x), y:(b.y-a.y)};
|
||||
const hLen = {x:dir.x*0.33, y:dir.y*0.33};
|
||||
const newA = { x: mid.x, y: mid.y,
|
||||
h1:{x: mid.x - hLen.x*0.5, y: mid.y - hLen.y*0.5},
|
||||
h2:{x: mid.x + hLen.x*0.5, y: mid.y + hLen.y*0.5},
|
||||
split:false };
|
||||
A.splice(idx+1,0,newA);
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
drawBezierUI();
|
||||
}
|
||||
delAnchorBtn.addEventListener('click', ()=>{
|
||||
const s=selectedStroke, idx=selectedAnchor.idx;
|
||||
if(!s || idx<0) return;
|
||||
if(s.anchors.length<=2) return;
|
||||
s.anchors.splice(idx,1);
|
||||
selectedAnchor={stroke:null,idx:-1};
|
||||
delAnchorBtn.disabled=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
});
|
||||
toBezierBtn.addEventListener('click', ()=>{
|
||||
if(selectedStroke && !selectedStroke.isBezier){
|
||||
const s=selectedStroke; const pts=s.points;
|
||||
if(!pts || pts.length<2) return;
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)); const P=(i)=>pts[ clamp(i,0,pts.length-1) ];
|
||||
const A=[];
|
||||
for(let i=0;i<pts.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1 = {x: p1.x + (p2.x - p0.x)/6, y: p1.y + (p2.y - p0.y)/6};
|
||||
const c2 = {x: p2.x - (p3.x - p1.x)/6, y: p2.y - (p3.y - p1.y)/6};
|
||||
if(i===0){ A.push({x:p1.x,y:p1.y,h1:{x:p1.x,y:p1.y},h2:{x:c1.x,y:c1.y},split:false}); }
|
||||
else { A[A.length-1].h2 = {x:c1.x,y:c1.y}; }
|
||||
A.push({x:p2.x,y:p2.y,h1:{x:c2.x,y:c2.y},h2:{x:p2.x,y:p2.y},split:false});
|
||||
}
|
||||
s.anchors=A; s.isBezier=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
listRefresh(); drawBezierUI();
|
||||
}
|
||||
});
|
||||
splitBtn.addEventListener('click', ()=>{
|
||||
if(selectedStroke && selectedAnchor.idx>=0){ const a=selectedStroke.anchors[selectedAnchor.idx]; a.split=!a.split; drawBezierUI(); }
|
||||
});
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if((e.key==='Delete'||e.key==='Backspace') && penActive){ penBackspace(); }
|
||||
if((e.key==='Delete'||e.key==='Backspace') && editBezierCb.checked){ delAnchorBtn.click(); }
|
||||
if(e.key==='l' || e.key==='L'){ linkHandlesCb.checked = !linkHandlesCb.checked; }
|
||||
if(e.key==='Enter' && penActive){ finishPen(); }
|
||||
});
|
||||
|
||||
// Pen tool
|
||||
penModeCb.addEventListener('change', ()=>{
|
||||
const on = penModeCb.checked;
|
||||
if(on){ editBezierCb.checked=false; penActive=true; ghost.setAttribute('d',''); deselectAll(); }
|
||||
else { penActive=false; cancelPen(); }
|
||||
finishPenBtn.disabled = !on;
|
||||
});
|
||||
finishPenBtn.addEventListener('click', finishPen);
|
||||
|
||||
function startPen(e){
|
||||
const p = svgPoint(e);
|
||||
if(!penStroke){
|
||||
penStroke = { id: Date.now()%1e7, mode, width:+strokeInp.value, colorA: colorAInp.value, colorB: colorBInp.value,
|
||||
isBezier:true, anchors:[], pathEl:createPathEl() };
|
||||
applyStrokeStyle(penStroke);
|
||||
const a = {x:p.x,y:p.y,h1:{x:p.x,y:p.y},h2:{x:p.x,y:p.y},split:false};
|
||||
penStroke.anchors.push(a);
|
||||
placingFirst=true; penDragging=true; penStartPt=p;
|
||||
try{ stage.setPointerCapture(e.pointerId); }catch(_){}}
|
||||
else {
|
||||
const a = {x:p.x,y:p.y,h1:{x:p.x,y:p.y},h2:{x:p.x,y:p.y},split:false};
|
||||
penStroke.anchors.push(a);
|
||||
placingNext=true; penDragging=true;
|
||||
try{ stage.setPointerCapture(e.pointerId); }catch(_){}}
|
||||
}
|
||||
function movePen(e){
|
||||
if(!penStroke || !penDragging) return;
|
||||
const p = svgPoint(e);
|
||||
const A = penStroke.anchors;
|
||||
if(placingFirst){
|
||||
const start=A[0];
|
||||
let v = {x: p.x - start.x, y: p.y - start.y};
|
||||
if(e.shiftKey){
|
||||
const ang=Math.atan2(v.y,v.x), step=Math.PI/12; const a=Math.round(ang/step)*step; const L=Math.hypot(v.x,v.y); v={x:Math.cos(a)*L,y:Math.sin(a)*L};
|
||||
}
|
||||
start.h2 = {x:start.x+v.x, y:start.y+v.y};
|
||||
} else if(placingNext){
|
||||
const last=A[A.length-1], prev=A[A.length-2];
|
||||
let v = {x: p.x - last.x, y: p.y - last.y};
|
||||
if(e.shiftKey){
|
||||
const ang=Math.atan2(v.y,v.x), step=Math.PI/12; const a=Math.round(ang/step)*step; const L=Math.hypot(v.x,v.y); v={x:Math.cos(a)*L,y:Math.sin(a)*L};
|
||||
}
|
||||
last.h1 = {x:last.x+v.x, y:last.y+v.y};
|
||||
if(e.altKey){ last.split=true; last.h2={x:last.x,y:last.y}; }
|
||||
else { last.split=false; last.h2 = {x:last.x - v.x, y:last.y - v.y}; }
|
||||
// previous outgoing 1/3 along segment
|
||||
const seg = {x:last.x - prev.x, y:last.y - prev.y};
|
||||
const L = Math.hypot(seg.x,seg.y); if(L>0){
|
||||
const n={x:seg.x/L,y:seg.y/L}; const k=L/3;
|
||||
prev.h2 = {x: prev.x + n.x*k, y: prev.y + n.y*k};
|
||||
}
|
||||
}
|
||||
ghost.setAttribute('d', pathFromAnchors(A));
|
||||
penStroke.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
applyStrokeStyle(penStroke);
|
||||
penStroke.pathEl.setAttribute('stroke-width', penStroke.width);
|
||||
}
|
||||
function endPen(e){ penDragging=false; placingFirst=false; placingNext=false; ghost.setAttribute('d',''); }
|
||||
function finishPen(){ if(!penStroke) return; strokes.push(penStroke); listRefresh(); penStroke=null; penStartPt=null; ghost.setAttribute('d',''); }
|
||||
function cancelPen(){ if(penStroke){ penStroke.pathEl.remove(); } penStroke=null; penStartPt=null; ghost.setAttribute('d',''); }
|
||||
function penBackspace(){ if(!penStroke) return; const A=penStroke.anchors; if(A.length<=1){ cancelPen(); } else { A.pop(); penStroke.pathEl.setAttribute('d', pathFromAnchors(A)); } }
|
||||
|
||||
// Stage routing
|
||||
stage.addEventListener('pointerdown', (e)=>{ if(penModeCb.checked) startPen(e); else startStroke(e); });
|
||||
stage.addEventListener('pointermove', (e)=>{ if(penModeCb.checked) movePen(e); });
|
||||
window.addEventListener('pointerup', (e)=>{ if(penModeCb.checked) endPen(e); });
|
||||
|
||||
// Misc
|
||||
function exportSVGString(){
|
||||
frameRect.style.display = frameOnCb.checked ? 'block' : 'none';
|
||||
const clone = stage.cloneNode(true);
|
||||
const ui = clone.querySelector('#bezierUI'); if(ui) ui.remove();
|
||||
const gh = clone.querySelector('#ghost'); if(gh) gh.remove();
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n${s}`;
|
||||
}
|
||||
function motionJSON(){
|
||||
const items = strokes.map(s=>{
|
||||
const o = { id:s.id, mode:s.mode, colorA:s.colorA, colorB:s.colorB, width:s.width, sampler:s.sampler || 'cubic' };
|
||||
if(s.isBezier){
|
||||
o.isBezier = true;
|
||||
o.anchors = s.anchors.map(a=>({x:a.x,y:a.y,h1:{x:a.h1.x,y:a.h1.y},h2:{x:a.h2.x,y:a.h2.y},split:!!a.split}));
|
||||
}else{
|
||||
o.points = (s.points||[]).map(p=>({x:p.x,y:p.y}));
|
||||
}
|
||||
return o;
|
||||
});
|
||||
return JSON.stringify({strokes:items}, null, 2);
|
||||
}
|
||||
document.getElementById('copySVG').addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
document.getElementById('downloadSVG').addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
document.getElementById('downloadPNG').addEventListener('click', ()=>{ const svg=exportSVGString(); svgToPng(svg, 'omi-omega-oneliner.png'); });
|
||||
document.getElementById('copyJSON').addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
document.getElementById('downloadJSON').addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
document.getElementById('replay').addEventListener('click', ()=>{
|
||||
if(!strokes.length) return;
|
||||
strokes.forEach(s=> s.pathEl.setAttribute('d',''));
|
||||
const total=1500; const t0=performance.now();
|
||||
const step=()=>{
|
||||
const t=performance.now()-t0;
|
||||
strokes.forEach(s=>{
|
||||
if(!s.isBezier) return;
|
||||
const A=s.anchors, n=A.length-1; const k=Math.max(1, Math.floor((t/total)*n));
|
||||
let d=`M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<k && i<n;i++){ const a=A[i], b=A[i+1]; d+=` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`; }
|
||||
s.pathEl.setAttribute('d', d); applyStrokeStyle(s); s.pathEl.setAttribute('stroke-width', s.width);
|
||||
});
|
||||
if(t<total) requestAnimationFrame(step); else { strokes.forEach(updatePath); }
|
||||
};
|
||||
requestAnimationFrame(step);
|
||||
});
|
||||
|
||||
function svgToPng(svgString, filename){
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svgString], {type:'image/svg+xml;charset=utf-8'});
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
img.onload = function(){
|
||||
const canvas = document.createElement('canvas');
|
||||
const vb = stage.viewBox.baseVal;
|
||||
canvas.width = vb.width; canvas.height = vb.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toBlob((blob)=>{
|
||||
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename; a.click();
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
// Basic toggles
|
||||
frameRect.style.display = document.getElementById('frameOn').checked ? 'block' : 'none';
|
||||
document.getElementById('frameOn').addEventListener('change', (e)=>{ frameRect.style.display = e.target.checked ? 'block' : 'none'; });
|
||||
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{ strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; ghost.setAttribute('d',''); clearBezierUI(); listRefresh(); });
|
||||
|
||||
function deselectAll(){ selectedStroke=null; strokes.forEach(st=> st.pathEl.classList.remove('selected')); clearBezierUI(); }
|
||||
|
||||
})();</script>
|
||||
</body>
|
||||
</html>
|
||||
452
crumbblocks/bezier_stream.html
Normal file
452
crumbblocks/bezier_stream.html
Normal file
@@ -0,0 +1,452 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Schnippsi Painter — Stream + Bezier</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0b0f12; --panel:#12181f; --ink:#e2f2ff; --muted:#7a8aa0; --accent:#62d3a4; --accent2:#ffd166;
|
||||
}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
#wrap{display:grid;grid-template-rows:auto 1fr; height:100%;}
|
||||
#toolbar{
|
||||
display:flex;gap:.75rem;align-items:center; padding:.6rem .8rem; background:linear-gradient(180deg,#12181f,#0e141a);
|
||||
position:sticky; top:0; z-index:3; box-shadow:0 8px 20px rgba(0,0,0,.25);
|
||||
}
|
||||
#toolbar input[type=color], #toolbar input[type=text]{height:32px}
|
||||
#toolbar .btn{
|
||||
padding:.55rem .8rem; border-radius:14px; background:#1a2330; color:#cfe7ff; border:1px solid #2b3a4b; cursor:pointer;
|
||||
}
|
||||
#toolbar .btn[aria-pressed="true"]{background:linear-gradient(180deg,#1f2a36,#1a2330); outline:2px solid var(--accent);}
|
||||
#toolbar .danger{border-color:#603; color:#ffd9e6; background:#2a0f1f}
|
||||
#toolbar .ok{border-color:#1b5e3b; background:#0f241a; color:#d9ffef;}
|
||||
#toolbar .field{display:flex;align-items:center;gap:.4rem}
|
||||
#status{margin-left:auto; font-size:.9rem; color:var(--muted)}
|
||||
#canvasWrap{position:relative; height:100%;}
|
||||
canvas{position:absolute; inset:0; width:100%; height:100%; touch-action:none; background:#0a0d11; outline:none}
|
||||
#help{
|
||||
position:fixed; right:.8rem; bottom:.8rem; width:360px; max-width:95vw; background:#0e141a; border:1px solid #213041;
|
||||
border-radius:16px; padding:12px 14px; color:#cfe7ff; box-shadow:0 18px 40px rgba(0,0,0,.45); font-size:14px;
|
||||
}
|
||||
#help h3{margin:.2rem 0 .4rem; font-size:16px}
|
||||
#help kbd{background:#111a22; padding:.15rem .35rem; border-radius:6px; border:1px solid #2b3a4b}
|
||||
.pill{padding:.08rem .45rem; background:#13202a; border:1px solid #254257; border-radius:999px; font-size:12px; color:#bfe8ff}
|
||||
.handle{cursor:grab}
|
||||
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrap">
|
||||
<div id="toolbar" role="toolbar" aria-label="Zeichenwerkzeuge">
|
||||
<div class="field"><span class="pill">Tool</span>
|
||||
<button class="btn" id="toolFree" aria-pressed="true" aria-label="Freihand (F)">Freihand</button>
|
||||
<button class="btn" id="toolBezier" aria-pressed="false" aria-label="Bezier (B)">Bezier</button>
|
||||
</div>
|
||||
<div class="field"><span class="pill">Farbe A</span><input id="colA" type="color" value="#62d3a4"/></div>
|
||||
<div class="field"><span class="pill">Farbe B</span><input id="colB" type="color" value="#ffd166"/></div>
|
||||
<div class="field"><span class="pill">Breite</span><input id="width" type="range" min="1" max="48" value="12"/></div>
|
||||
<div class="field"><span class="pill">Stream</span>
|
||||
<input id="wsUrl" type="text" size="22" value="ws://localhost:9980" aria-label="WebSocket URL"/>
|
||||
<button class="btn" id="wsToggle" aria-pressed="false" aria-label="Streaming an/aus (S)">Verbinden</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="btn" id="replayBtn" aria-label="Replay (R)">Replay</button>
|
||||
<button class="btn" id="exportJson">JSON</button>
|
||||
<button class="btn" id="exportSvg">SVG</button>
|
||||
<button class="btn" id="exportPng">PNG</button>
|
||||
<button class="btn danger" id="clearBtn">Clear</button>
|
||||
</div>
|
||||
<div id="status" aria-live="polite">bereit</div>
|
||||
</div>
|
||||
<div id="canvasWrap">
|
||||
<canvas id="cv" role="application" aria-label="Schnippsi Zeichenfläche" tabindex="0"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside id="help" aria-live="polite">
|
||||
<h3>Shortcuts</h3>
|
||||
<div>Hilfe <kbd>?</kbd> / <kbd>H</kbd> • Tool-Bezier <kbd>B</kbd> • Tool-Freihand <kbd>F</kbd> • Stream <kbd>S</kbd></div>
|
||||
<div>Bezier beenden <kbd>Enter</kbd> • Letzten Anker löschen <kbd>Backspace</kbd> • Ecke halten <kbd>Alt</kbd> • Snap <kbd>Shift</kbd></div>
|
||||
<div class="pill">A11y</div> Tab-fokussierbar, ARIA Live-Status, hoher Kontrast, 200% Zoom stabil.
|
||||
</aside>
|
||||
|
||||
<script>
|
||||
(()=>{
|
||||
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
||||
const cv = document.getElementById('cv');
|
||||
const ctx = cv.getContext('2d');
|
||||
const statusEl = document.getElementById('status');
|
||||
const toolFree = document.getElementById('toolFree');
|
||||
const toolBezier = document.getElementById('toolBezier');
|
||||
const colA = document.getElementById('colA');
|
||||
const colB = document.getElementById('colB');
|
||||
const widthIn = document.getElementById('width');
|
||||
const replayBtn = document.getElementById('replayBtn');
|
||||
const exportJson = document.getElementById('exportJson');
|
||||
const exportSvg = document.getElementById('exportSvg');
|
||||
const exportPng = document.getElementById('exportPng');
|
||||
const clearBtn = document.getElementById('clearBtn');
|
||||
const wsToggle = document.getElementById('wsToggle');
|
||||
const wsUrl = document.getElementById('wsUrl');
|
||||
|
||||
let state = {
|
||||
tool: 'free', // 'free' | 'bezier'
|
||||
colorA: colA.value,
|
||||
colorB: colB.value,
|
||||
width: +widthIn.value,
|
||||
strokes: [],
|
||||
current: null,
|
||||
sessionId: ('sess_' + Math.random().toString(16).slice(2)),
|
||||
streaming: false,
|
||||
ws: null,
|
||||
down: false,
|
||||
modifiers: {shift:false, alt:false},
|
||||
bezier: {anchors:[], dragging:null}
|
||||
};
|
||||
|
||||
function resize(){
|
||||
const rect = cv.getBoundingClientRect();
|
||||
cv.width = Math.floor(rect.width * dpr);
|
||||
cv.height = Math.floor(rect.height * dpr);
|
||||
ctx.setTransform(dpr,0,0,dpr,0,0);
|
||||
render();
|
||||
}
|
||||
new ResizeObserver(resize).observe(document.getElementById('canvasWrap'));
|
||||
resize();
|
||||
|
||||
function setStatus(t){ statusEl.textContent = t; }
|
||||
|
||||
// WebSocket
|
||||
function wsConnect(){
|
||||
try{
|
||||
const url = wsUrl.value.trim();
|
||||
state.ws = new WebSocket(url);
|
||||
state.ws.onopen = ()=>{ state.streaming = true; wsToggle.setAttribute('aria-pressed','true'); setStatus('WS verbunden'); send({type:'session', id:state.sessionId, app:'schnippsi-painter', t:performance.now()|0}); };
|
||||
state.ws.onclose = ()=>{ state.streaming = false; wsToggle.setAttribute('aria-pressed','false'); setStatus('WS getrennt'); };
|
||||
state.ws.onerror = ()=>{ setStatus('WS Fehler'); };
|
||||
wsToggle.textContent = 'Trennen';
|
||||
}catch(e){ console.error(e); setStatus('WS Verbindungsfehler'); }
|
||||
}
|
||||
function wsDisconnect(){
|
||||
if(state.ws){ try{ state.ws.close(); }catch(e){} }
|
||||
state.ws = null; state.streaming = false; wsToggle.textContent = 'Verbinden';
|
||||
}
|
||||
function send(obj){
|
||||
if(!state.streaming || !state.ws || state.ws.readyState!==1) return;
|
||||
try{ state.ws.send(JSON.stringify(obj)); }catch(e){ /* ignore */ }
|
||||
}
|
||||
|
||||
// Tools
|
||||
function useTool(t){
|
||||
state.tool = t;
|
||||
toolFree.setAttribute('aria-pressed', t==='free');
|
||||
toolBezier.setAttribute('aria-pressed', t==='bezier');
|
||||
setStatus(t==='free'?'Freihand aktiv':'Bezier aktiv');
|
||||
}
|
||||
toolFree.onclick = ()=>useTool('free');
|
||||
toolBezier.onclick = ()=>useTool('bezier');
|
||||
|
||||
colA.oninput = ()=>{ state.colorA = colA.value; };
|
||||
colB.oninput = ()=>{ state.colorB = colB.value; };
|
||||
widthIn.oninput = ()=>{ state.width = +widthIn.value; };
|
||||
|
||||
wsToggle.onclick = ()=>{
|
||||
if(state.streaming) wsDisconnect();
|
||||
else wsConnect();
|
||||
};
|
||||
|
||||
clearBtn.onclick = ()=>{ state.strokes.length=0; state.bezier.anchors=[]; state.current=null; render(); };
|
||||
|
||||
// Geometry helpers
|
||||
const dist = (a,b)=>Math.hypot(a.x-b.x, a.y-b.y);
|
||||
function lerp(a,b,t){ return a+(b-a)*t; }
|
||||
function cubicPoint(p0,p1,p2,p3,t){
|
||||
const x = Math.pow(1-t,3)*p0.x + 3*Math.pow(1-t,2)*t*p1.x + 3*(1-t)*t*t*p2.x + t*t*t*p3.x;
|
||||
const y = Math.pow(1-t,3)*p0.y + 3*Math.pow(1-t,2)*t*p1.y + 3*(1-t)*t*t*p2.y + t*t*t*p3.y;
|
||||
return {x,y};
|
||||
}
|
||||
|
||||
// Drawing primitives
|
||||
function drawStroke(s){
|
||||
ctx.lineJoin = ctx.lineCap = 'round';
|
||||
ctx.lineWidth = s.width;
|
||||
ctx.strokeStyle = s.colorA;
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.beginPath();
|
||||
if(s.isBezier){
|
||||
const A = s.anchors;
|
||||
if(A.length>0){ ctx.moveTo(A[0].x, A[0].y); }
|
||||
for(let i=1;i<A.length;i++){
|
||||
const a0=A[i-1], a1=A[i];
|
||||
ctx.bezierCurveTo(a0.h2.x, a0.h2.y, a1.h1.x, a1.h1.y, a1.x, a1.y);
|
||||
}
|
||||
ctx.stroke();
|
||||
// Handles (edit mode)
|
||||
if(s._edit){
|
||||
for(const a of A){
|
||||
// anchor
|
||||
ctx.fillStyle = '#cfe7ff';
|
||||
ctx.beginPath(); ctx.arc(a.x,a.y,4,0,Math.PI*2); ctx.fill();
|
||||
// handles
|
||||
ctx.strokeStyle='#2b9ad1'; ctx.lineWidth=1.2;
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(a.h1.x,a.h1.y); ctx.moveTo(a.x,a.y); ctx.lineTo(a.h2.x,a.h2.y); ctx.stroke();
|
||||
ctx.fillStyle='#2b9ad1';
|
||||
ctx.beginPath(); ctx.arc(a.h1.x,a.h1.y,3,0,Math.PI*2); ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(a.h2.x,a.h2.y,3,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
}
|
||||
}else{
|
||||
const P = s.points;
|
||||
if(!P || P.length<2) return;
|
||||
ctx.moveTo(P[0].x, P[0].y);
|
||||
for(let i=1;i<P.length;i++){ ctx.lineTo(P[i].x, P[i].y); }
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function render(){
|
||||
ctx.clearRect(0,0,cv.width,dpr?cv.height:cv.height);
|
||||
// backdrop grid (subtle)
|
||||
ctx.save();
|
||||
ctx.strokeStyle='rgba(255,255,255,0.04)';
|
||||
ctx.lineWidth=1;
|
||||
for(let x=0;x<cv.width/dpr;x+=40){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,cv.height/dpr); ctx.stroke(); }
|
||||
for(let y=0;y<cv.height/dpr;y+=40){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(cv.width/dpr,y); ctx.stroke(); }
|
||||
ctx.restore();
|
||||
|
||||
for(const s of state.strokes){ drawStroke(s); }
|
||||
if(state.current){ drawStroke(state.current); }
|
||||
}
|
||||
|
||||
// Input
|
||||
let lastEmit = 0;
|
||||
function pointerPos(e){
|
||||
const r = cv.getBoundingClientRect();
|
||||
return { x:(e.clientX - r.left), y:(e.clientY - r.top), t: performance.now()|0, p:e.pressure||0.5, tilt:[e.tiltX||0, e.tiltY||0], type:e.pointerType||'mouse' };
|
||||
}
|
||||
function startStroke(pt){
|
||||
if(state.tool==='free'){
|
||||
state.current = { id:('st_'+Date.now()), tool:'free', colorA:state.colorA, colorB:state.colorB, width:state.width, points:[pt], isBezier:false };
|
||||
send({type:'strokeStart', id:state.current.id, tool:'free', colorA:state.colorA, colorB:state.colorB, width:state.width, t:pt.t});
|
||||
}else{
|
||||
// initialize bezier edit stroke
|
||||
const A = state.bezier.anchors.length? structuredClone(state.bezier.anchors) : [];
|
||||
state.current = { id:('st_'+Date.now()), tool:'bezier', colorA:state.colorA, colorB:state.colorB, width:state.width, anchors:A, isBezier:true, _edit:true };
|
||||
if(A.length===0){
|
||||
// first anchor with mirrored handles
|
||||
const a = {x:pt.x,y:pt.y, h1:{x:pt.x-40, y:pt.y}, h2:{x:pt.x+40, y:pt.y}, split:false, t:pt.t, p:pt.p, tilt:pt.tilt};
|
||||
state.bezier.anchors.push(a);
|
||||
state.current.anchors.push(structuredClone(a));
|
||||
send({type:'strokeStart', id:state.current.id, tool:'bezier', colorA:state.colorA, colorB:state.colorB, width:state.width, t:pt.t});
|
||||
send({type:'anchor', id:state.current.id, a:a});
|
||||
}
|
||||
}
|
||||
}
|
||||
function moveStroke(pt){
|
||||
if(!state.current) return;
|
||||
if(state.tool==='free'){
|
||||
state.current.points.push(pt);
|
||||
const now = performance.now();
|
||||
if(now-lastEmit>33){ lastEmit=now; send({type:'point', id:state.current.id, p:pt}); }
|
||||
}else{
|
||||
// handle dragging logic
|
||||
const cur = state.current;
|
||||
if(!state.bezier.dragging) return;
|
||||
const drag = state.bezier.dragging;
|
||||
if(drag.kind==='anchor'){
|
||||
drag.ref.x = pt.x; drag.ref.y = pt.y;
|
||||
// move handles with anchor
|
||||
drag.ref.h1.x += drag.dx? (pt.x-drag.base.x) : 0;
|
||||
drag.ref.h1.y += drag.dy? (pt.y-drag.base.y) : 0;
|
||||
drag.ref.h2.x += drag.dx? (pt.x-drag.base.x) : 0;
|
||||
drag.ref.h2.y += drag.dy? (pt.y-drag.base.y) : 0;
|
||||
drag.base.x = pt.x; drag.base.y = pt.y;
|
||||
}else if(drag.kind==='h1' || drag.kind==='h2'){
|
||||
drag.ref[drag.kind].x = pt.x; drag.ref[drag.kind].y = pt.y;
|
||||
if(!drag.ref.split){
|
||||
// mirror handle lengths for smooth
|
||||
const ax=drag.ref.x, ay=drag.ref.y;
|
||||
const opp = (drag.kind==='h1')?'h2':'h1';
|
||||
const vx = ax - pt.x, vy = ay - pt.y;
|
||||
drag.ref[opp].x = ax + vx; drag.ref[opp].y = ay + vy;
|
||||
}
|
||||
}
|
||||
cur.anchors = structuredClone(state.bezier.anchors);
|
||||
// emit throttle
|
||||
const now = performance.now();
|
||||
if(now-lastEmit>50){ lastEmit=now; send({type:'anchors', id:cur.id, anchors:cur.anchors}); }
|
||||
}
|
||||
render();
|
||||
}
|
||||
function endStroke(pt){
|
||||
if(!state.current) return;
|
||||
if(state.tool==='free'){
|
||||
state.strokes.push(state.current);
|
||||
send({type:'strokeEnd', id:state.current.id, t:pt.t});
|
||||
state.current=null;
|
||||
}else{
|
||||
// in bezier tool, mouseup only stops dragging
|
||||
state.bezier.dragging=null;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
// Hit-testing for bezier edit
|
||||
function hitTestAnchors(x,y){
|
||||
const A = state.bezier.anchors;
|
||||
for(let i=A.length-1;i>=0;i--){
|
||||
const a=A[i];
|
||||
if(dist({x,y}, a)<8) return {idx:i, kind:'anchor', ref:a};
|
||||
if(dist({x,y}, a.h1)<7) return {idx:i, kind:'h1', ref:a};
|
||||
if(dist({x,y}, a.h2)<7) return {idx:i, kind:'h2', ref:a};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add anchor with dragging of handle based on movement
|
||||
function addAnchor(pt){
|
||||
const A = state.bezier.anchors;
|
||||
const prev = A[A.length-1];
|
||||
const a = {x:pt.x,y:pt.y, h1:{x:pt.x-40, y:pt.y}, h2:{x:pt.x+40, y:pt.y}, split:false, t:pt.t, p:pt.p, tilt:pt.tilt};
|
||||
if(prev){
|
||||
// make smooth by default: h1 points to previous, h2 mirrored
|
||||
a.h1 = {x: lerp(prev.x, pt.x, .33), y: lerp(prev.y, pt.y, .33)};
|
||||
a.h2 = {x: lerp(pt.x, prev.x, .33), y: lerp(pt.y, prev.y, .33)};
|
||||
}
|
||||
A.push(a);
|
||||
if(state.current){ state.current.anchors = structuredClone(A); }
|
||||
send({type:'anchor', id:state.current?state.current.id:('st_'+Date.now()), a:a});
|
||||
render();
|
||||
}
|
||||
|
||||
// Pointer events
|
||||
cv.addEventListener('pointerdown', (e)=>{
|
||||
cv.setPointerCapture(e.pointerId);
|
||||
const pt = pointerPos(e); state.down=true;
|
||||
if(state.tool==='free'){
|
||||
startStroke(pt);
|
||||
}else{
|
||||
// bezier: check hit on existing handle/anchor first
|
||||
const hit = hitTestAnchors(pt.x,pt.y);
|
||||
if(hit){
|
||||
state.bezier.dragging={...hit, base:{x:pt.x,y:pt.y}, dx:true, dy:true};
|
||||
if(hit.kind!=='anchor' && e.altKey){ hit.ref.split = true; } // allow cusp with Alt while grabbing
|
||||
}else{
|
||||
if(!state.current){ startStroke(pt); }
|
||||
addAnchor(pt);
|
||||
}
|
||||
}
|
||||
render();
|
||||
});
|
||||
cv.addEventListener('pointermove', (e)=>{
|
||||
if(!state.down && state.tool==='bezier'){
|
||||
// hover cursor
|
||||
const pt = pointerPos(e), hit = hitTestAnchors(pt.x,pt.y);
|
||||
cv.style.cursor = hit ? 'grab' : 'crosshair';
|
||||
}
|
||||
if(!state.down) return;
|
||||
moveStroke(pointerPos(e));
|
||||
});
|
||||
cv.addEventListener('pointerup', (e)=>{ state.down=false; endStroke(pointerPos(e)); });
|
||||
cv.addEventListener('pointercancel', (e)=>{ state.down=false; endStroke(pointerPos(e)); });
|
||||
|
||||
// Keyboard
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='Shift') state.modifiers.shift=true;
|
||||
if(e.key==='Alt') state.modifiers.alt=true;
|
||||
if(e.key==='b' || e.key==='B'){ useTool('bezier'); }
|
||||
if(e.key==='f' || e.key==='F'){ useTool('free'); }
|
||||
if(e.key==='s' || e.key==='S'){ state.streaming?wsDisconnect():wsConnect(); }
|
||||
if(e.key==='r' || e.key==='R'){ doReplay(); }
|
||||
if(e.key==='Enter' && state.tool==='bezier'){
|
||||
// finalize bezier stroke into strokes
|
||||
if(state.current){
|
||||
const finalized = {...state.current};
|
||||
finalized._edit=false;
|
||||
state.strokes.push(finalized);
|
||||
send({type:'strokeEnd', id:finalized.id, t:performance.now()|0});
|
||||
state.current=null;
|
||||
state.bezier.anchors = [];
|
||||
render();
|
||||
}
|
||||
}
|
||||
if(e.key==='Backspace' && state.tool==='bezier'){
|
||||
if(state.bezier.anchors.length>0){
|
||||
const a = state.bezier.anchors.pop();
|
||||
send({type:'anchorDel', id:state.current?state.current.id:null, aidx: state.bezier.anchors.length});
|
||||
if(state.current) state.current.anchors = structuredClone(state.bezier.anchors);
|
||||
render();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
if(e.key==='?' || e.key==='h' || e.key==='H'){
|
||||
const el=document.getElementById('help'); el.hidden = !el.hidden;
|
||||
}
|
||||
});
|
||||
window.addEventListener('keyup', (e)=>{
|
||||
if(e.key==='Shift') state.modifiers.shift=false;
|
||||
if(e.key==='Alt') state.modifiers.alt=false;
|
||||
});
|
||||
|
||||
// Export
|
||||
function download(name, dataUrl){
|
||||
const a=document.createElement('a'); a.href=dataUrl; a.download=name; a.click();
|
||||
}
|
||||
exportJson.onclick = ()=>{
|
||||
const payload = {
|
||||
canvas:{w:cv.width/dpr, h:cv.height/dpr, dpr},
|
||||
session:{id:state.sessionId, app:'schnippsi-painter'},
|
||||
strokes: state.strokes
|
||||
};
|
||||
const blob = new Blob([JSON.stringify(payload,null,2)], {type:'application/json'});
|
||||
download(`schnippsi_${Date.now()}.json`, URL.createObjectURL(blob));
|
||||
};
|
||||
exportSvg.onclick = ()=>{
|
||||
const w=cv.width/dpr, h=cv.height/dpr;
|
||||
function pathFromStroke(s){
|
||||
if(s.isBezier){
|
||||
const A=s.anchors; if(A.length<1) return '';
|
||||
let d=`M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=1;i<A.length;i++){
|
||||
const a0=A[i-1], a1=A[i];
|
||||
d+=` C ${a0.h2.x} ${a0.h2.y}, ${a1.h1.x} ${a1.h1.y}, ${a1.x} ${a1.y}`;
|
||||
}
|
||||
return `<path d="${d}" stroke="${s.colorA}" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="${s.width}"/>`;
|
||||
}else{
|
||||
const P=s.points; if(!P || P.length<2) return '';
|
||||
const d='M '+P.map((p,i)=> (i? 'L '+p.x+' '+p.y : p.x+' '+p.y)).join(' ');
|
||||
return `<path d="${d}" stroke="${s.colorA}" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="${s.width}"/>`;
|
||||
}
|
||||
}
|
||||
const body = state.strokes.map(pathFromStroke).join('\n');
|
||||
const svg = `<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">\n${body}\n</svg>`;
|
||||
const blob = new Blob([svg], {type:'image/svg+xml'});
|
||||
download(`schnippsi_${Date.now()}.svg`, URL.createObjectURL(blob));
|
||||
};
|
||||
exportPng.onclick = ()=>{
|
||||
const url = cv.toDataURL('image/png');
|
||||
download(`schnippsi_${Date.now()}.png`, url);
|
||||
};
|
||||
|
||||
// Replay
|
||||
function doReplay(){
|
||||
if(state.strokes.length===0) return;
|
||||
const copy = JSON.parse(JSON.stringify(state.strokes));
|
||||
state.strokes = [];
|
||||
let idx=0;
|
||||
function step(){
|
||||
if(idx>=copy.length) return;
|
||||
state.strokes.push(copy[idx++]); render();
|
||||
setTimeout(step, 300);
|
||||
}
|
||||
step();
|
||||
}
|
||||
replayBtn.onclick = doReplay;
|
||||
|
||||
// Initial tip
|
||||
setStatus('Bezier: Klicken für Anker, ziehen für Griffe. Enter = fertig. Alt zum Brechen, Shift für Snap.');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
366
crumbblocks/crumbblocks.html
Normal file
366
crumbblocks/crumbblocks.html
Normal file
@@ -0,0 +1,366 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Crumbblocks – Wasserkocher (Demo)</title>
|
||||
<!-- Feste Blockly-Version (verhindert textToDom-Fehler) -->
|
||||
<script src="https://unpkg.com/blockly@9.3.3/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly@9.3.3/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b1016;
|
||||
--panel: #0e141b;
|
||||
--ink: #e6edf3;
|
||||
--muted: #93a1b3;
|
||||
--accent: #4caf50;
|
||||
--border: #1f2a37;
|
||||
--console: #0a0f14;
|
||||
--console-ink: #b7ffb2;
|
||||
}
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
.wrap {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr auto;
|
||||
gap: 12px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
header h1 { font-size: 20px; margin: 0; }
|
||||
.controls {
|
||||
display: flex; gap: 8px; flex-wrap: wrap;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px; padding: 10px;
|
||||
}
|
||||
button {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.secondary { background: #374151; }
|
||||
button:active { transform: translateY(1px); }
|
||||
.stage {
|
||||
display: grid; grid-template-rows: 64vh 28vh; gap: 12px;
|
||||
}
|
||||
#blocklyDiv {
|
||||
width: 100%;
|
||||
height: 64vh;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#output {
|
||||
width: 100%;
|
||||
height: 28vh;
|
||||
background: var(--console);
|
||||
color: var(--console-ink);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header>
|
||||
<h1>🧁 Crumbblocks – Virtueller Wasserkocher (Kids-Demo)</h1>
|
||||
<div class="hint">
|
||||
Variablen: <code>power_w</code>, <code>wasser_ml</code>, <code>sensor_temp_c</code>, <code>zeit_s</code>,
|
||||
<code>energie_wh</code>, <code>verbrauch_wh</code>. Ziel: Bis 100 °C erhitzen, Energie/Zeit mitrechnen.
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="loadDemo()">🎬 Demo laden</button>
|
||||
<button onclick="runCode()">▶️ Ausführen</button>
|
||||
<button class="secondary" onclick="clearOutput()">🧹 Leeren</button>
|
||||
<span class="hint">Sendet per <code>POST /crumbapi/blockly-terminal</code> (Fallback: Echo).</span>
|
||||
</div>
|
||||
|
||||
<div class="stage">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Terminal wartet …</pre>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX (Standardkategorien + Variablen/Funktionen) -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Logik" categorystyle="logic_category">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" categorystyle="loop_category">
|
||||
<block type="controls_repeat_ext"><value name="TIMES"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="controls_whileUntil"><field name="MODE">WHILE</field></block>
|
||||
<block type="controls_flow_statements"></block>
|
||||
</category>
|
||||
<category name="Mathe" categorystyle="math_category">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_round"></block>
|
||||
</category>
|
||||
<category name="Text" categorystyle="text_category">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<sep></sep>
|
||||
<category name="Variablen" categorystyle="variable_category" custom="VARIABLE_DYNAMIC"></category>
|
||||
<category name="Funktionen" categorystyle="procedure_category" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
|
||||
<!-- DEMO-WORKSPACE (Wasserkocher) -->
|
||||
<script id="demo-xml" type="text/plain">
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
</variables>
|
||||
|
||||
<!-- Startwerte -->
|
||||
<block type="variables_set" x="24" y="24">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">250</field></block></value>
|
||||
<next>
|
||||
<!-- Sicherheitslimit Wasser -->
|
||||
<block type="controls_if">
|
||||
<value name="IF0">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">wasser_ml</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<!-- while (sensor_temp_c < 100) -->
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<!-- Abbruch wenn kein Strom -->
|
||||
<block type="controls_if">
|
||||
<value name="IF0">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LTE</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO0">
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Kein Strom → STOP</field></block></value>
|
||||
<next>
|
||||
<block type="controls_flow_statements"><field name="FLOW">BREAK</field></block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<!-- Heize-Schritt -->
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Heize…</field></block></value>
|
||||
<next>
|
||||
<!-- sensor_temp_c += 5 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<!-- zeit_s += 5 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">zeit_s</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<!-- energie_wh += power_w * 5 / 3600 -->
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
<value name="B">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">DIVIDE</field>
|
||||
<value name="A">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">power_w</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">3600</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Fertig. STOP.</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Workspace
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'geras',
|
||||
grid: { spacing: 24, length: 3, colour: '#223045', snap: true },
|
||||
zoom: { controls: true, wheel: true, startScale: 1.05, maxScale: 2.0, minScale: 0.5, pinch: true },
|
||||
trashcan: true
|
||||
});
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
|
||||
function out(txt, replace=false) {
|
||||
const el = document.getElementById('output');
|
||||
el.textContent = replace ? String(txt) : (el.textContent + '\n' + String(txt));
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
function clearOutput() { document.getElementById('output').textContent = '🌲 Terminal wartet …'; }
|
||||
|
||||
function loadDemo() {
|
||||
try {
|
||||
const xmlTxt = document.getElementById('demo-xml').textContent.trim();
|
||||
const dom = (Blockly.Xml && Blockly.Xml.textToDom)
|
||||
? Blockly.Xml.textToDom(xmlTxt)
|
||||
: Blockly.utils.xml.textToDom(xmlTxt);
|
||||
workspace.clear();
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
out('✅ Demo geladen.', true);
|
||||
} catch (e) {
|
||||
out('❌ Demo konnte nicht geladen werden: ' + e, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function runCode() {
|
||||
const code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
out('📤 Sende an Terminal…\n\n' + code, true);
|
||||
|
||||
// Variablenliste (für spätere Auswertung im Backend hilfreich)
|
||||
const knownVars = ['sensor_temp_c','power_w','wasser_ml','zeit_s','energie_wh','verbrauch_wh'];
|
||||
|
||||
try {
|
||||
const res = await fetch('/crumbapi/blockly-terminal', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode: 'blockly', blockcode: code, vars: knownVars })
|
||||
});
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
const data = await res.json();
|
||||
out('\n✅ Antwort:\n' + JSON.stringify(data, null, 2));
|
||||
} catch (err) {
|
||||
// Fallback: Echo-JSON lokal anzeigen
|
||||
const echo = {
|
||||
ok: true,
|
||||
mode: 'echo-fallback',
|
||||
request: { mode: 'blockly', blockcode: code, vars: knownVars }
|
||||
};
|
||||
out('\nℹ️ Endpoint nicht bereit. Fallback aktiv.\n\n' + JSON.stringify(echo, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Direkt beim Laden einmal die Demo hineinlegen:
|
||||
loadDemo();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
298
crumbblocks/index.html
Normal file
298
crumbblocks/index.html
Normal file
@@ -0,0 +1,298 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Blockly to Crumbforest Terminal Bridge</title>
|
||||
<!-- Blockly + JS Generator + Deutsche Labels -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1e1e1e;
|
||||
--panel: #111;
|
||||
--text: #e8e8e8;
|
||||
--ok: #a6e22e;
|
||||
--btn: #4caf50;
|
||||
}
|
||||
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); font-family: monospace; }
|
||||
header { display:flex; gap:.5rem; align-items:center; padding:.5rem .75rem; }
|
||||
header h2 { margin:0; font-weight:600; }
|
||||
header .spacer { flex:1; }
|
||||
button {
|
||||
padding:.6rem .9rem; background: var(--btn); color:#fff; border:none; border-radius:8px; font-weight:700; cursor:pointer;
|
||||
}
|
||||
#wrap { display:flex; flex-direction:column; height: calc(100vh - 56px); }
|
||||
#blocklyDiv { height: 64vh; width: 100%; }
|
||||
#bottom { display:flex; gap:.75rem; padding:.5rem .75rem; }
|
||||
#output {
|
||||
flex:1; height: 32vh; background:var(--panel); color:var(--ok);
|
||||
padding:10px; overflow:auto; white-space: pre-wrap; word-break: break-word; border-radius:8px;
|
||||
}
|
||||
#controls { display:flex; flex-direction:column; gap:.5rem; }
|
||||
.muted { opacity:.7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Blockly Crumbforest Bridge</h2>
|
||||
<div class="spacer"></div>
|
||||
<button id="btnDemo" title="Mini-Demo laden">🧪 Demo</button>
|
||||
<button id="btnClear" title="Workspace leeren">🗑️ Neu</button>
|
||||
<button id="btnZoomIn" title="Zoom +">🔍+</button>
|
||||
<button id="btnZoomOut" title="Zoom −">🔍-</button>
|
||||
<button id="btnRun" title="Code ausführen / an Terminal senden">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="blocklyDiv"></div>
|
||||
<div id="bottom">
|
||||
<div id="controls">
|
||||
<div class="muted">Variablen: energie_wh, verbrauch_wh, sensor_temp_c, power_w, wasser_ml, zeit_s</div>
|
||||
<div class="muted">Endpoint: <code>/crumbapi/blockly-terminal</code> (Echo-Fallback aktiv)</div>
|
||||
</div>
|
||||
<pre id="output">🌲 Terminal wartet ...</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox: wie bei dir, nur Loops aktiviert -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="variables_set"></block>
|
||||
<block type="variables_get"></block>
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="procedures_defnoreturn"></block>
|
||||
<block type="procedures_callnoreturn"></block>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing: 24, length: 3, colour: '#484848', snap: true},
|
||||
trashcan: true,
|
||||
zoom: {startScale: 1.15, maxScale: 2.0, minScale: 0.6, controls: false, wheel: true},
|
||||
move: {scrollbars: true, drag: true, wheel: true}
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen für euren „Prozess“-Wortschatz
|
||||
['energie_wh','verbrauch_wh','sensor_temp_c','power_w','wasser_ml','zeit_s']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(e){} });
|
||||
|
||||
// Helpers
|
||||
const $ = sel => document.querySelector(sel);
|
||||
function setOutput(txt){ $('#output').textContent = txt; }
|
||||
function appendOutput(txt){
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\n')?'':'\n') + txt;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
|
||||
// Initial-Variablen aus „set“-Blöcken sammeln (für JSON-Vorschau)
|
||||
function collectInitialVars() {
|
||||
const vals = {};
|
||||
const blocks = workspace.getAllBlocks(false);
|
||||
for (const b of blocks) {
|
||||
if (b.type === 'variables_set') {
|
||||
const name = b.getFieldValue('VAR');
|
||||
// Versuche simples Literal zu lesen (Zahl/Text)
|
||||
const input = b.getInputTargetBlock('VALUE');
|
||||
if (input) {
|
||||
if (input.type === 'math_number') {
|
||||
vals[name] = Number(input.getFieldValue('NUM'));
|
||||
} else if (input.type === 'text') {
|
||||
vals[name] = input.getFieldValue('TEXT') ?? '';
|
||||
} else {
|
||||
// Irgendwas anderes verschachtelt → nur markieren
|
||||
vals[name] = '<expr>';
|
||||
}
|
||||
} else {
|
||||
vals[name] = '<unset>';
|
||||
}
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
// Kompatibles XML-Parserchen (fix für „Blockly.Xml.textToDom is not a function“)
|
||||
function textToDom(xmlText){
|
||||
if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText);
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
|
||||
// Aktionen
|
||||
$('#btnZoomIn').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(1);
|
||||
$('#btnZoomOut').onclick = ()=> Blockly.svgResize(workspace), workspace.zoomCenter(-1);
|
||||
|
||||
$('#btnClear').onclick = () => {
|
||||
workspace.clear();
|
||||
setOutput('🧹 Workspace geleert.');
|
||||
};
|
||||
|
||||
$('#btnDemo').onclick = () => {
|
||||
const demoXML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="24" y="22">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">2000</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">wasser_ml</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">250</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">verbrauch_wh</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next>
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text">
|
||||
<field name="TEXT">Heize...</field>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">zeit_s</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">zeit_s</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">energie_wh</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">energie_wh</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">2.8</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text">
|
||||
<field name="TEXT">Fertig. STOP.</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>`;
|
||||
try {
|
||||
const dom = textToDom(demoXML);
|
||||
workspace.clear();
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
setOutput('✅ Demo geladen. Passe die Logik an und drücke ▶️ Ausführen.');
|
||||
} catch (e) {
|
||||
setOutput('❌ Demo konnte nicht geladen werden:\n' + e);
|
||||
}
|
||||
};
|
||||
|
||||
$('#btnRun').onclick = async () => {
|
||||
const code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const initVars = collectInitialVars();
|
||||
|
||||
setOutput("📤 Sende an Terminal...\n\n" + code);
|
||||
|
||||
const payload = {
|
||||
mode: "blockly",
|
||||
blockcode: code,
|
||||
preview_vars: initVars
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch("/crumbapi/blockly-terminal", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
let data;
|
||||
if (res.ok) {
|
||||
data = await res.json().catch(()=>({}));
|
||||
appendOutput("\n\n✅ Antwort:\n" + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
appendOutput("\n\nℹ️ Endpoint nicht bereit (HTTP " + res.status + "). Fallback aktiv.");
|
||||
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
|
||||
}
|
||||
} catch (err) {
|
||||
appendOutput("\n\n❌ Netzwerkfehler: " + err);
|
||||
appendOutput("\n\n✅ Antwort (Fallback):\n" + JSON.stringify({ ok:true, mode:"echo-fallback", request:payload }, null, 2));
|
||||
}
|
||||
};
|
||||
|
||||
// Resizing
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
278
crumbblocks/index_v1.html
Normal file
278
crumbblocks/index_v1.html
Normal file
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Crumbblocks – Blockly ↔ Crumbforest</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly + Python-Generator + Deutsch -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/python.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{
|
||||
--bg:#0d0f12;--panel:#12151b;--ink:#e9edf5;--muted:#b7c0d0;--accent:#4caf50;--danger:#e74c3c;
|
||||
--mono:ui-monospace, Menlo, Monaco, "Courier New", monospace;
|
||||
}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--ink);font-family:var(--mono)}
|
||||
header{position:sticky;top:0;z-index:5;display:flex;gap:.6rem;align-items:center;padding:.6rem 1rem;background:#0b0d10cc;border-bottom:1px solid #1b212c}
|
||||
header h2{margin:0;font-size:1rem;color:var(--muted)}
|
||||
.spacer{flex:1 1 auto}
|
||||
.btn{border:1px solid #263041;background:#161b22;color:var(--ink);border-radius:8px;padding:.5rem .75rem;cursor:pointer;font-weight:700}
|
||||
.btn:hover{filter:brightness(1.1)}
|
||||
.btn.primary{background:var(--accent);color:#0b130c;border-color:#2b6b2e}
|
||||
.btn.danger{background:var(--danger);border-color:#b13a2e}
|
||||
.btn.ghost{background:transparent}
|
||||
#wrap{display:flex;flex-direction:column;gap:.6rem;height:calc(100% - 54px);padding:.6rem 1rem 1rem}
|
||||
#blocklyDiv{height:62vh;width:100%;border-radius:10px;overflow:hidden;background:#0f1319;border:1px solid #1b212c}
|
||||
#console{display:grid;grid-template-rows:auto 1fr;gap:.4rem;height:28vh;background:var(--panel);border:1px solid #1b212c;border-radius:10px}
|
||||
#consoleHead{display:flex;align-items:center;gap:.6rem;padding:.5rem .7rem;border-bottom:1px solid #1b212c}
|
||||
#statusDot{width:.6rem;height:.6rem;border-radius:50%;background:#6a6f79;display:inline-block}
|
||||
#statusText{color:var(--muted);font-size:.9rem}
|
||||
#output{margin:0;padding:.6rem .8rem;overflow:auto;line-height:1.35;color:#a6ffad;background:#0c0f13;border-radius:0 0 10px 10px;white-space:pre-wrap;word-break:break-word;font-size:.93rem}
|
||||
@media (max-width:960px){#blocklyDiv{height:56vh}#console{height:32vh}}
|
||||
.blocklyMainBackground{fill:#0f1319 !important}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbblocks</h2>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn" id="btnDemo">Demo laden</button>
|
||||
<button class="btn ghost" id="btnZoomOut">–</button>
|
||||
<button class="btn ghost" id="btnZoomIn">+</button>
|
||||
<button class="btn danger" id="btnTrash">Mülleimer</button>
|
||||
<button class="btn primary" id="btnRun">Ausführen → Terminal</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="blocklyDiv" aria-label="Blockly Arbeitsfläche"></div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Logik" colour="#66b1ff">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" colour="#6cdf6c">
|
||||
<block type="controls_repeat_ext">
|
||||
<value name="TIMES"><shadow type="math_number"><field name="NUM">10</field></shadow></value>
|
||||
</block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe" colour="#b68cff">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Text" colour="#ffd866">
|
||||
<block type="text"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<sep gap="8"></sep>
|
||||
<category name="Variablen" custom="VARIABLE" colour="#ff75b5"></category>
|
||||
<category name="Funktionen" custom="PROCEDURE" colour="#e056fd"></category>
|
||||
</xml>
|
||||
|
||||
<section id="console" aria-label="Ausgabe">
|
||||
<div id="consoleHead">
|
||||
<span id="statusDot" title="Status"></span>
|
||||
<strong>Konsole</strong>
|
||||
<span id="statusText">bereit</span>
|
||||
<div class="spacer"></div>
|
||||
<button class="btn ghost" id="btnClear">Leeren</button>
|
||||
<button class="btn ghost" id="btnToggle">Ein-/Ausklappen</button>
|
||||
</div>
|
||||
<pre id="output">🌲 Terminal wartet …</pre>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// eigenes Dark-Theme
|
||||
const CrumbDark = Blockly.Theme.defineTheme('CrumbDark', {
|
||||
base: Blockly.Themes.Dark,
|
||||
componentStyles: {
|
||||
workspaceBackgroundColour: '#0f1319',
|
||||
toolboxBackgroundColour: '#0e1217',
|
||||
toolboxForegroundColour: '#e9edf5',
|
||||
flyoutBackgroundColour: '#0f1319',
|
||||
flyoutForegroundColour: '#e9edf5',
|
||||
scrollbarColour: '#2a3240',
|
||||
insertionMarkerColour: '#4caf50',
|
||||
insertionMarkerOpacity: 0.35,
|
||||
cursorColour: '#4caf50',
|
||||
}
|
||||
});
|
||||
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: CrumbDark,
|
||||
renderer: 'zelos',
|
||||
trashcan: true,
|
||||
grid: { spacing: 28, length: 2, colour: '#1a2029', snap: true },
|
||||
move: { scrollbars: true, drag: true, wheel: true },
|
||||
zoom: { controls: false, wheel: true, startScale: 1.15, maxScale: 2.2, minScale: 0.7, scaleSpeed: 1.2 },
|
||||
});
|
||||
window.addEventListener('resize', () => Blockly.svgResize(workspace));
|
||||
|
||||
// VORDEFINIERTE VARIABLEN für die Kids
|
||||
const PRESET_VARS = [
|
||||
'energie_wh','verbrauch_wh','sensor_temp_c','power_w','wasser_ml','zeit_s'
|
||||
];
|
||||
for (const name of PRESET_VARS) {
|
||||
try { workspace.createVariable(name); } catch(_) {}
|
||||
}
|
||||
|
||||
// UI helpers
|
||||
const outputEl = document.getElementById('output');
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const consoleBox = document.getElementById('console');
|
||||
function setStatus(color, text){ statusDot.style.background=color; statusText.textContent=text||''; }
|
||||
function log(line){ outputEl.textContent += (outputEl.textContent.endsWith('\n')?'':'\n')+ line; outputEl.scrollTop = outputEl.scrollHeight; }
|
||||
function resetOutput(msg='🌲 Terminal wartet …'){ outputEl.textContent = msg; }
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnZoomIn').onclick = () => workspace.zoomCenter(1);
|
||||
document.getElementById('btnZoomOut').onclick = () => workspace.zoomCenter(-1);
|
||||
document.getElementById('btnTrash').onclick = () => workspace.clear();
|
||||
document.getElementById('btnClear').onclick = () => resetOutput('— leere Ausgabe —');
|
||||
let collapsed=false;
|
||||
document.getElementById('btnToggle').onclick = () => { collapsed=!collapsed; consoleBox.style.display = collapsed?'none':'grid'; };
|
||||
|
||||
// Demo-Workspace (Wasserkocher) – via DOMParser, ohne Blockly.Xml.textToDom
|
||||
const DEMO_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>energie_wh</variable>
|
||||
<variable>verbrauch_wh</variable>
|
||||
<variable>sensor_temp_c</variable>
|
||||
<variable>power_w</variable>
|
||||
<variable>wasser_ml</variable>
|
||||
<variable>zeit_s</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">20</field></block></value>
|
||||
<next>
|
||||
<block type="variables_set">
|
||||
<field name="VAR">power_w</field>
|
||||
<value name="VALUE"><block type="math_number"><field name="NUM">800</field></block></value>
|
||||
<next>
|
||||
<block type="controls_whileUntil">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare">
|
||||
<field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">100</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">sensor_temp_c</field>
|
||||
<value name="VALUE">
|
||||
<block type="math_arithmetic">
|
||||
<field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">10</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT">
|
||||
<block type="text"><field name="TEXT">Erhitze… Temp=</field></block>
|
||||
</value>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="variables_get"><field name="VAR">sensor_temp_c</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next>
|
||||
<block type="text_print">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Wasser kocht! 🔥</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</xml>`.trim();
|
||||
|
||||
function loadDemo(){
|
||||
try{
|
||||
workspace.clear();
|
||||
const dom = new DOMParser().parseFromString(DEMO_XML, 'text/xml').documentElement;
|
||||
// domToWorkspace ist in allen Builds vorhanden
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
Blockly.svgResize(workspace);
|
||||
setStatus('#4caf50','Demo geladen');
|
||||
resetOutput('🧪 Demo geladen – „Wasserkocher“ bereit.');
|
||||
}catch(e){
|
||||
setStatus('#e74c3c','Demo-Fehler');
|
||||
resetOutput('❌ Demo konnte nicht geladen werden:\n' + (e?.message||e));
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
document.getElementById('btnDemo').onclick = loadDemo;
|
||||
|
||||
// Run nur aktiv wenn es Blöcke gibt
|
||||
const btnRun = document.getElementById('btnRun');
|
||||
function updateRunDisabled(){
|
||||
const disabled = workspace.getAllBlocks(false).length===0;
|
||||
btnRun.disabled = disabled;
|
||||
btnRun.style.opacity = disabled ? .55 : 1;
|
||||
btnRun.style.cursor = disabled ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
workspace.addChangeListener(updateRunDisabled);
|
||||
updateRunDisabled();
|
||||
|
||||
// Ausführen → Bridge (mit Echo-Fallback)
|
||||
async function runCode(){
|
||||
const code = Blockly.Python.workspaceToCode(workspace);
|
||||
resetOutput('📤 Sende an Terminal…\n\n' + code);
|
||||
setStatus('#f1c40f','sende…');
|
||||
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(()=>ctrl.abort(), 10000);
|
||||
|
||||
async function post(url){
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ blockcode: code }),
|
||||
signal: ctrl.signal
|
||||
});
|
||||
clearTimeout(timer);
|
||||
if(!res.ok) throw new Error('HTTP '+res.status+' '+(await res.text()).slice(0,400));
|
||||
return res.json().catch(()=>({}));
|
||||
}
|
||||
|
||||
try{
|
||||
let data;
|
||||
try{
|
||||
// deine echte Bridge
|
||||
data = await post('/crumbapi/blockly-terminal');
|
||||
}catch(_){
|
||||
// Fallback-Echo (damit Kids sofort Feedback sehen)
|
||||
data = { ok:true, mode:'echo-fallback', code:code };
|
||||
}
|
||||
setStatus('#4caf50','fertig');
|
||||
log('\n✅ Antwort:\n' + JSON.stringify(data, null, 2));
|
||||
}catch(err){
|
||||
setStatus('#e74c3c','Fehler');
|
||||
log('\n❌ Fehler beim Senden:\n' + (err?.message||err));
|
||||
}
|
||||
}
|
||||
btnRun.addEventListener('click', runCode);
|
||||
|
||||
// Startzustand: Demo direkt laden
|
||||
loadDemo();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2740
crumbblocks/lib/blockly/blockly.min.js
vendored
Normal file
2740
crumbblocks/lib/blockly/blockly.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
193
crumbblocks/lib/blockly/blocks_compressed.js
Normal file
193
crumbblocks/lib/blockly/blocks_compressed.js
Normal file
@@ -0,0 +1,193 @@
|
||||
// Do not edit this file; automatically generated.
|
||||
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define(["./blockly_compressed.js"], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory(require("./blockly_compressed.js"));
|
||||
} else { // Script
|
||||
root.Blockly.libraryBlocks = factory(root.Blockly);
|
||||
}
|
||||
}(this, function(__parent__) {
|
||||
var $=__parent__.__namespace__;
|
||||
var blocks$$module$build$src$blocks$variables_dynamic=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{type:"variables_set_dynamic",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",
|
||||
name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_dynamic_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableDynamicSetterGetter"]}]),CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables_dynamic={customContextMenu:function(a){if(!this.isInFlyout){if(this.type==="variables_get_dynamic"){var b="variables_set_dynamic";
|
||||
var c=$.Msg$$module$build$src$core$msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",c=$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET;var d=this.getField("VAR");b={type:b,fields:{VAR:d.saveState(!0)}};a.push({enabled:this.workspace.remainingCapacity()>0,text:c.replace("%1",d.getText()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)})}else if(this.type==="variables_get_dynamic"||this.type==="variables_get_reporter_dynamic")c={text:$.Msg$$module$build$src$core$msg.RENAME_VARIABLE,
|
||||
enabled:!0,callback:renameOptionCallbackFactory$$module$build$src$blocks$variables_dynamic(this)},d=this.getField("VAR").getText(),d={text:$.Msg$$module$build$src$core$msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:deleteOptionCallbackFactory$$module$build$src$blocks$variables_dynamic(this)},a.unshift(c),a.unshift(d)},onchange:function(a){a=this.getFieldValue("VAR");a=$.getVariable$$module$build$src$core$variables(this.workspace,a);this.type==="variables_get_dynamic"?this.outputConnection.setCheck(a.getType()):
|
||||
this.getInput("VALUE").connection.setCheck(a.getType())}},renameOptionCallbackFactory$$module$build$src$blocks$variables_dynamic=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.renameVariable$$module$build$src$core$variables(b,c)}},deleteOptionCallbackFactory$$module$build$src$blocks$variables_dynamic=function(a){return function(){const b=a.getField("VAR").getVariable();b&&$.deleteVariable$$module$build$src$core$variables(b.getWorkspace(),b,a)}};
|
||||
$.registerMixin$$module$build$src$core$extensions("contextMenu_variableDynamicSetterGetter",CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables_dynamic);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$variables_dynamic);var module$build$src$blocks$variables_dynamic={blocks:blocks$$module$build$src$blocks$variables_dynamic};var blocks$$module$build$src$blocks$variables=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},
|
||||
{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]),CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables={customContextMenu:function(a){if(!this.isInFlyout){if(this.type==="variables_get"){var b="variables_set";var c=$.Msg$$module$build$src$core$msg.VARIABLES_GET_CREATE_SET}else b="variables_get",
|
||||
c=$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET;var d=this.getField("VAR");b={type:b,fields:{VAR:d.saveState(!0)}};a.push({enabled:this.workspace.remainingCapacity()>0,text:c.replace("%1",d.getText()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)})}else if(this.type==="variables_get"||this.type==="variables_get_reporter")c={text:$.Msg$$module$build$src$core$msg.RENAME_VARIABLE,enabled:!0,callback:renameOptionCallbackFactory$$module$build$src$blocks$variables(this)},
|
||||
d=this.getField("VAR").getText(),d={text:$.Msg$$module$build$src$core$msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:deleteOptionCallbackFactory$$module$build$src$blocks$variables(this)},a.unshift(c),a.unshift(d)}},renameOptionCallbackFactory$$module$build$src$blocks$variables=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.renameVariable$$module$build$src$core$variables(b,c)}},deleteOptionCallbackFactory$$module$build$src$blocks$variables=function(a){return function(){const b=
|
||||
a.getField("VAR").getVariable();b&&$.deleteVariable$$module$build$src$core$variables(b.getWorkspace(),b,a)}};$.registerMixin$$module$build$src$core$extensions("contextMenu_variableSetterGetter",CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN$$module$build$src$blocks$variables);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$variables);var module$build$src$blocks$variables={blocks:blocks$$module$build$src$blocks$variables};var blocks$$module$build$src$blocks$text=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},
|
||||
{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",args0:[{type:"input_dummy"},{type:"input_statement",name:"STACK"}],style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",enableContextMenu:!1},{type:"text_create_join_item",message0:"%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",enableContextMenu:!1},{type:"text_append",message0:"%{BKY_TEXT_APPEND_TITLE}",
|
||||
args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_TEXT_APPEND_VARIABLE}"},{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",extensions:["text_append_tooltip"]},{type:"text_length",message0:"%{BKY_TEXT_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"text_blocks",tooltip:"%{BKY_TEXT_LENGTH_TOOLTIP}",helpUrl:"%{BKY_TEXT_LENGTH_HELPURL}"},{type:"text_isEmpty",message0:"%{BKY_TEXT_ISEMPTY_TITLE}",
|
||||
args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"text_blocks",tooltip:"%{BKY_TEXT_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_TEXT_ISEMPTY_HELPURL}"},{type:"text_indexOf",message0:"%{BKY_TEXT_INDEXOF_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"END",options:[["%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}","FIRST"],["%{BKY_TEXT_INDEXOF_OPERATOR_LAST}","LAST"]]},{type:"input_value",name:"FIND",check:"String"}],output:"Number",style:"text_blocks",
|
||||
helpUrl:"%{BKY_TEXT_INDEXOF_HELPURL}",inputsInline:!0,extensions:["text_indexOf_tooltip"]},{type:"text_charAt",message0:"%{BKY_TEXT_CHARAT_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"WHERE",options:[["%{BKY_TEXT_CHARAT_FROM_START}","FROM_START"],["%{BKY_TEXT_CHARAT_FROM_END}","FROM_END"],["%{BKY_TEXT_CHARAT_FIRST}","FIRST"],["%{BKY_TEXT_CHARAT_LAST}","LAST"],["%{BKY_TEXT_CHARAT_RANDOM}","RANDOM"]]}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_CHARAT_HELPURL}",
|
||||
inputsInline:!0,mutator:"text_charAt_mutator"}]),GET_SUBSTRING_BLOCK$$module$build$src$blocks$text={init:function(){this.WHERE_OPTIONS_1=[[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_FROM_END,
|
||||
"FROM_END"],[$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_HELPURL);this.setStyle("text_blocks");this.appendValueInput("STRING").setCheck("String").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);const a=b=>{const c=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this["WHERE_OPTIONS_"+b]});c.setValidator(function(d){const e=this.getValue();d=d===
|
||||
"FROM_START"||d==="FROM_END";d!==(e==="FROM_START"||e==="FROM_END")&&this.getSourceBlock().updateAt_(b,d)});return c};this.appendDummyInput("WHERE1_INPUT").appendField(a(1),"WHERE1");this.appendDummyInput("AT1");this.appendDummyInput("WHERE2_INPUT").appendField(a(2),"WHERE2");this.appendDummyInput("AT2");$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,
|
||||
"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");var b=this.getInput("AT1")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at1",`${b}`);b=this.getInput("AT2")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at2",`${b}`);return a},domToMutation:function(a){const b=
|
||||
a.getAttribute("at1")==="true";a=a.getAttribute("at2")==="true";this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);a===2&&$.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",
|
||||
!0),this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_GET_SUBSTRING_TAIL));a===1&&(this.moveInputBefore("AT1","WHERE2_INPUT"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","WHERE2_INPUT"))}};blocks$$module$build$src$blocks$text.text_getSubstring=GET_SUBSTRING_BLOCK$$module$build$src$blocks$text;
|
||||
blocks$$module$build$src$blocks$text.text_changeCase={init:function(){const a=[[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[$.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField($.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",
|
||||
options:a}),"CASE");this.setOutput(!0,"String");this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_CHANGECASE_TOOLTIP)}};
|
||||
blocks$$module$build$src$blocks$text.text_trim={init:function(){const a=[[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[$.Msg$$module$build$src$core$msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_TRIM_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField($.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a}),
|
||||
"MODE");this.setOutput(!0,"String");this.setTooltip($.Msg$$module$build$src$core$msg.TEXT_TRIM_TOOLTIP)}};blocks$$module$build$src$blocks$text.text_print={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_PRINT_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_PRINT_HELPURL})}};
|
||||
var PROMPT_COMMON$$module$build$src$blocks$text={updateType_:function(a){this.outputConnection.setCheck(a==="NUMBER"?"Number":"String")},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
|
||||
blocks$$module$build$src$blocks$text.text_prompt_ext=Object.assign({},PROMPT_COMMON$$module$build$src$blocks$text,{init:function(){var a=[[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});a.setValidator(b=>{this.updateType_(b)});this.appendValueInput("TEXT").appendField(a,
|
||||
"TYPE");this.setOutput(!0,"String");this.setTooltip(()=>this.getFieldValue("TYPE")==="TEXT"?$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_TEXT:$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_NUMBER)}});
|
||||
blocks$$module$build$src$blocks$text.text_prompt=Object.assign({},PROMPT_COMMON$$module$build$src$blocks$text,{init:function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);var a=[[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});
|
||||
a.setValidator(b=>{this.updateType_(b)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField($.fromJson$$module$build$src$core$field_registry({type:"field_input",text:""}),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");this.setTooltip(()=>this.getFieldValue("TYPE")==="TEXT"?$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_TEXT:$.Msg$$module$build$src$core$msg.TEXT_PROMPT_TOOLTIP_NUMBER)}});
|
||||
blocks$$module$build$src$blocks$text.text_count={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_COUNT_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_COUNT_HELPURL})}};
|
||||
blocks$$module$build$src$blocks$text.text_replace={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_REPLACE_HELPURL})}};
|
||||
blocks$$module$build$src$blocks$text.text_reverse={init:function(){this.jsonInit({message0:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_TOOLTIP,helpUrl:$.Msg$$module$build$src$core$msg.TEXT_REVERSE_HELPURL})}};
|
||||
var QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(let b=0,c;c=this.inputList[b];b++)for(let d=0,e;e=c.fieldRow[d];d++)if(a===e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return $.fromJson$$module$build$src$core$field_registry({type:"field_image",src:a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,width:this.QUOTE_IMAGE_WIDTH,
|
||||
height:this.QUOTE_IMAGE_HEIGHT,alt:a?"\u201c":"\u201d"})}},QUOTES_EXTENSION$$module$build$src$blocks$text=function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);this.quoteField_("TEXT")},JOIN_MUTATOR_MIXIN$$module$build$src$blocks$text={itemCount_:0,mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("items",`${this.itemCount_}`);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},
|
||||
saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){const b=a.newBlock("text_create_join_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||
|
||||
a.push(b.valueConnection_),b=b.getNextBlock();for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&!a.includes(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++){let c;(c=a[b])==null||c.reconnect(this,"ADD"+b)}},saveConnections:function(a){a=a.getInputTargetBlock("STACK");let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;
|
||||
a=a.getNextBlock();b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT);a===0&&b.appendField($.Msg$$module$build$src$core$msg.TEXT_JOIN_TITLE_CREATEWITH)}for(a=this.itemCount_;this.getInput("ADD"+
|
||||
a);a++)this.removeInput("ADD"+a)}},JOIN_EXTENSION$$module$build$src$blocks$text=function(){this.mixin(QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);this.itemCount_=2;this.updateShape_();this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["text_create_join_item"],this))};$.register$$module$build$src$core$extensions("text_append_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_TEXT_APPEND_TOOLTIP}","VAR"));
|
||||
var INDEXOF_TOOLTIP_EXTENSION$$module$build$src$blocks$text=function(){this.setTooltip(()=>$.Msg$$module$build$src$core$msg.TEXT_INDEXOF_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?"0":"-1"))},CHARAT_MUTATOR_MIXIN$$module$build$src$blocks$text={isAt_:!1,mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("at",`${this.isAt_}`);return a},domToMutation:function(a){a=a.getAttribute("at")!=="false";this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT",
|
||||
!0);this.removeInput("ORDINAL",!0);a&&(this.appendValueInput("AT").setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX));$.Msg$$module$build$src$core$msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.TEXT_CHARAT_TAIL));this.isAt_=a}},CHARAT_EXTENSION$$module$build$src$blocks$text=function(){this.getField("WHERE").setValidator(function(a){a=
|
||||
a==="FROM_START"||a==="FROM_END";const b=this.getSourceBlock();a!==b.isAt_&&b.updateAt_(a)});this.updateAt_(!0);this.setTooltip(()=>{var a=this.getFieldValue("WHERE");let b=$.Msg$$module$build$src$core$msg.TEXT_CHARAT_TOOLTIP;(a==="FROM_START"||a==="FROM_END")&&(a=a==="FROM_START"?$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP:$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(b+=" "+a.replace("%1",this.workspace.options.oneBasedIndex?"#1":"#0"));return b})};
|
||||
$.register$$module$build$src$core$extensions("text_indexOf_tooltip",INDEXOF_TOOLTIP_EXTENSION$$module$build$src$blocks$text);$.register$$module$build$src$core$extensions("text_quotes",QUOTES_EXTENSION$$module$build$src$blocks$text);$.registerMixin$$module$build$src$core$extensions("quote_image_mixin",QUOTE_IMAGE_MIXIN$$module$build$src$blocks$text);$.registerMutator$$module$build$src$core$extensions("text_join_mutator",JOIN_MUTATOR_MIXIN$$module$build$src$blocks$text,JOIN_EXTENSION$$module$build$src$blocks$text);
|
||||
$.registerMutator$$module$build$src$core$extensions("text_charAt_mutator",CHARAT_MUTATOR_MIXIN$$module$build$src$blocks$text,CHARAT_EXTENSION$$module$build$src$blocks$text);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$text);var module$build$src$blocks$text={blocks:blocks$$module$build$src$blocks$text};var blocks$$module$build$src$blocks$procedures={},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures={setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){let a="";this.arguments_.length&&(a=$.Msg$$module$build$src$core$msg.PROCEDURES_BEFORE_PARAMS+
|
||||
" "+this.arguments_.join(", "));$.disable$$module$build$src$core$events$utils();try{this.setFieldValue(a,"PARAMS")}finally{$.enable$$module$build$src$core$events$utils()}},mutationToDom:function(a){const b=$.createElement$$module$build$src$core$utils$xml("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(let c=0;c<this.argumentVarModels_.length;c++){const d=$.createElement$$module$build$src$core$utils$xml("arg"),e=this.argumentVarModels_[c];d.setAttribute("name",e.getName());d.setAttribute("varid",
|
||||
e.getId());a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(let c=0,d;d=a.childNodes[c];c++)if(d.nodeName.toLowerCase()==="arg"){var b=d;const e=b.getAttribute("name");b=b.getAttribute("varid")||b.getAttribute("varId");this.arguments_.push(e);b=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,b,e,
|
||||
"");b!==null?this.argumentVarModels_.push(b):console.log(`Failed to create a variable named "${e}", ignoring.`)}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);this.setStatements_(a.getAttribute("statements")!=="false")},saveExtraState:function(){if(!this.argumentVarModels_.length&&this.hasStatements_)return null;const a=Object.create(null);if(this.argumentVarModels_.length){a.params=[];for(let b=0;b<this.argumentVarModels_.length;b++)a.params.push({name:this.argumentVarModels_[b].getName(),
|
||||
id:this.argumentVarModels_[b].getId()})}this.hasStatements_||(a.hasStatements=!1);return a},loadExtraState:function(a){this.arguments_=[];this.argumentVarModels_=[];if(a.params)for(let c=0;c<a.params.length;c++){var b=a.params[c];b=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,b.id,b.name,"");this.arguments_.push(b.getName());this.argumentVarModels_.push(b)}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);this.setStatements_(a.hasStatements===
|
||||
!1?!1:!0)},decompose:function(a){const b=$.createElement$$module$build$src$core$utils$xml("block");b.setAttribute("type","procedures_mutatorcontainer");var c=$.createElement$$module$build$src$core$utils$xml("statement");c.setAttribute("name","STACK");b.appendChild(c);for(let e=0;e<this.arguments_.length;e++){const f=$.createElement$$module$build$src$core$utils$xml("block");f.setAttribute("type","procedures_mutatorarg");var d=$.createElement$$module$build$src$core$utils$xml("field");d.setAttribute("name",
|
||||
"NAME");const g=$.createTextNode$$module$build$src$core$utils$xml(this.arguments_[e]);d.appendChild(g);f.appendChild(d);d=$.createElement$$module$build$src$core$utils$xml("next");f.appendChild(d);c.appendChild(f);c=d}a=$.domToBlock$$module$build$src$core$xml(b,a);this.type==="procedures_defreturn"?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");$.mutateCallers$$module$build$src$core$procedures(this);return a},compose:function(a){this.arguments_=[];this.paramIds_=
|
||||
[];this.argumentVarModels_=[];let b=a.getInputTargetBlock("STACK");for(;b&&!b.isInsertionMarker();){var c=b.getFieldValue("NAME");this.arguments_.push(c);c=this.workspace.getVariable(c,"");this.argumentVarModels_.push(c);this.paramIds_.push(b.id);b=b.nextConnection&&b.nextConnection.targetBlock()}this.updateParams_();$.mutateCallers$$module$build$src$core$procedures(this);a=a.getFieldValue("STATEMENTS");if(a!==null&&(a=a==="TRUE",this.hasStatements_!==a))if(a){this.setStatements_(!0);var d;(d=this.statementConnection_)==
|
||||
null||d.reconnect(this,"STACK");this.statementConnection_=null}else{d=this.getInput("STACK").connection;if(this.statementConnection_=d.targetConnection)d=d.targetBlock(),d.unplug(),d.bumpNeighbours();this.setStatements_(!1)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},renameVarById:function(a,b){var c=this.workspace.getVariableById(a);if(c.getType()===""){c=c.getName();b=this.workspace.getVariableById(b);var d=!1;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===
|
||||
a&&(this.arguments_[e]=b.getName(),this.argumentVarModels_[e]=b,d=!0);d&&(this.displayRenamedVar_(c,b.getName()),$.mutateCallers$$module$build$src$core$procedures(this))}},updateVarName:function(a){const b=a.getName();let c=!1,d;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===a.getId()&&(d=this.arguments_[e],this.arguments_[e]=b,c=!0);c&&(this.displayRenamedVar_(d,b),$.mutateCallers$$module$build$src$core$procedures(this))},displayRenamedVar_:function(a,b){this.updateParams_();
|
||||
var c=this.getIcon($.MutatorIcon$$module$build$src$core$icons$mutator_icon.TYPE);if(c&&c.bubbleIsVisible()){c=c.getWorkspace().getAllBlocks(!1);for(let d=0,e;e=c[d];d++)e.type==="procedures_mutatorarg"&&$.Names$$module$build$src$core$names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")}},customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("NAME"),c={type:this.callType_,extraState:{name:b,params:this.arguments_}};a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.PROCEDURES_CREATE_DO.replace("%1",
|
||||
b),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,c)});if(!this.isCollapsed())for(b=0;b<this.argumentVarModels_.length;b++){c=this.argumentVarModels_[b];const d={type:"variables_get",fields:{VAR:{name:c.getName(),id:c.getId(),type:c.getType()}}};a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET.replace("%1",c.getName()),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,d)})}}}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_defnoreturn=Object.assign({},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures,{init:function(){var a=$.findLegalName$$module$build$src$core$procedures("",this);a=$.fromJson$$module$build$src$core$field_registry({type:"field_input",text:a});a.setValidator($.rename$$module$build$src$core$procedures);a.setSpellcheck(!1);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_COMMENT&&this.setCommentText($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_TOOLTIP);
|
||||
this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},callType_:"procedures_callnoreturn"});
|
||||
blocks$$module$build$src$blocks$procedures.procedures_defreturn=Object.assign({},PROCEDURE_DEF_COMMON$$module$build$src$blocks$procedures,{init:function(){var a=$.findLegalName$$module$build$src$core$procedures("",this);a=$.fromJson$$module$build$src$core$field_registry({type:"field_input",text:a});a.setValidator($.rename$$module$build$src$core$procedures);a.setSpellcheck(!1);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.appendValueInput("RETURN").setAlign($.Align$$module$build$src$core$inputs$align.RIGHT).appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_COMMENT&&this.setCommentText($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_COMMENT);
|
||||
this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!0]},callType_:"procedures_callreturn"});
|
||||
var PROCEDURES_MUTATORCONTAINER$$module$build$src$blocks$procedures={init:function(){this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_ALLOW_STATEMENTS).appendField($.fromJson$$module$build$src$core$field_registry({type:"field_checkbox",checked:!0}),"STATEMENTS");this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
|
||||
this.contextMenu=!1}};blocks$$module$build$src$blocks$procedures.procedures_mutatorcontainer=PROCEDURES_MUTATORCONTAINER$$module$build$src$blocks$procedures;
|
||||
var ProcedureArgumentField$$module$build$src$blocks$procedures=class extends $.FieldTextInput$$module$build$src$core$field_textinput{constructor(){super(...arguments);this.editingInteractively=!1}showEditor_(a){super.showEditor_(a);this.editingInteractively=!0;this.editingVariable=void 0}onFinishEditing_(a){super.onFinishEditing_(a);this.editingInteractively=!1}},PROCEDURES_MUTATORARGUMENT$$module$build$src$blocks$procedures={init:function(){const a=new ProcedureArgumentField$$module$build$src$blocks$procedures($.DEFAULT_ARG$$module$build$src$core$procedures,
|
||||
this.validator_);this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1},validator_:function(a){var b=this.getSourceBlock();const c=b.workspace.getRootWorkspace();a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;const d=(b.workspace.targetWorkspace||
|
||||
b.workspace).getAllBlocks(!1),e=a.toLowerCase();for(let f=0;f<d.length;f++){if(d[f].id===this.getSourceBlock().id)continue;const g=d[f].getFieldValue("NAME");if(g&&g.toLowerCase()===e)return null}if(b.isInFlyout)return a;(b=c.getVariable(a,""))&&b.getName()!==a&&c.renameVariableById(b.getId(),a);b||(this.editingInteractively?this.editingVariable?c.renameVariableById(this.editingVariable.getId(),a):this.editingVariable=c.createVariable(a,""):c.createVariable(a,""));return a}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_mutatorarg=PROCEDURES_MUTATORARGUMENT$$module$build$src$blocks$procedures;
|
||||
var DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures="DISABLED_PROCEDURE_DEFINITION",PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures={getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){$.Names$$module$build$src$core$names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?$.Msg$$module$build$src$core$msg.PROCEDURES_CALLRETURN_TOOLTIP:$.Msg$$module$build$src$core$msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",
|
||||
b)))},setProcedureParameters_:function(a,b){var c=$.getDefinition$$module$build$src$core$procedures(this.getProcedureCall(),this.workspace);(c=(c=c&&c.getIcon($.MutatorIcon$$module$build$src$core$icons$mutator_icon.TYPE))&&c.bubbleIsVisible())?this.setCollapsed(!1):(this.quarkConnections_={},this.quarkIds_=null);if(a.join("\n")===this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!==a.length)throw RangeError("paramNames and paramIds must be the same length.");this.quarkIds_||(this.quarkConnections_=
|
||||
{},this.quarkIds_=[]);for(let e=0;e<this.arguments_.length;e++){var d=this.getInput("ARG"+e);d&&(d=d.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=d,c&&d&&!b.includes(this.quarkIds_[e])&&(d.disconnect(),d.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(a=0;a<this.arguments_.length;a++)c=$.getOrCreateVariablePackage$$module$build$src$core$variables(this.workspace,null,this.arguments_[a],""),this.argumentVarModels_.push(c);this.updateShape_();
|
||||
if(this.quarkIds_=b)for(b=0;b<this.arguments_.length;b++)if(a=this.quarkIds_[b],a in this.quarkConnections_){let e;((e=this.quarkConnections_[a])==null?0:e.reconnect(this,"ARG"+b))||delete this.quarkConnections_[a]}}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){$.disable$$module$build$src$core$events$utils();try{b.setValue(this.arguments_[a])}finally{$.enable$$module$build$src$core$events$utils()}}else b=$.fromJson$$module$build$src$core$field_registry({type:"field_label",
|
||||
text:this.arguments_[a]}),this.appendValueInput("ARG"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT).appendField(b,"ARGNAME"+a)}for(a=this.arguments_.length;this.getInput("ARG"+a);a++)this.removeInput("ARG"+a);(a=this.getInput("TOPROW"))&&(this.arguments_.length?this.getField("WITH")||a.appendField($.Msg$$module$build$src$core$msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"):this.getField("WITH")&&a.removeField("WITH"))},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");
|
||||
a.setAttribute("name",this.getProcedureCall());for(let b=0;b<this.arguments_.length;b++){const c=$.createElement$$module$build$src$core$utils$xml("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];const c=[];for(let d=0,e;e=a.childNodes[d];d++)e.nodeName.toLowerCase()==="arg"&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,
|
||||
c)},saveExtraState:function(){const a=Object.create(null);a.name=this.getProcedureCall();this.arguments_.length&&(a.params=this.arguments_);return a},loadExtraState:function(a){this.renameProcedure(this.getProcedureCall(),a.name);if(a=a.params){const b=[];b.length=a.length;b.fill(null);this.setProcedureParameters_(a,b)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&a.recordUndo)if(a.type===
|
||||
$.BLOCK_CREATE$$module$build$src$core$events$events&&a.ids.includes(this.id)){var b=this.getProcedureCall(),c=$.getDefinition$$module$build$src$core$procedures(b,this.workspace);!c||c.type===this.defType_&&JSON.stringify(c.getVars())===JSON.stringify(this.arguments_)||(c=null);if(c)c.isEnabled()||(this.setDisabledReason(!0,DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures),this.setWarningText($.Msg$$module$build$src$core$msg.PROCEDURES_CALL_DISABLED_DEF_WARNING.replace("%1",
|
||||
b)));else{$.setGroup$$module$build$src$core$events$utils(a.group);a=$.createElement$$module$build$src$core$utils$xml("xml");b=$.createElement$$module$build$src$core$utils$xml("block");b.setAttribute("type",this.defType_);c=this.getRelativeToSurfaceXY();var d=c.y+$.config$$module$build$src$core$config.snapRadius*2;b.setAttribute("x",`${c.x+$.config$$module$build$src$core$config.snapRadius*(this.RTL?-1:1)}`);b.setAttribute("y",`${d}`);c=this.mutationToDom();b.appendChild(c);c=$.createElement$$module$build$src$core$utils$xml("field");
|
||||
c.setAttribute("name","NAME");d=this.getProcedureCall();const e=$.findLegalName$$module$build$src$core$procedures(d,this);d!==e&&this.renameProcedure(d,e);c.appendChild($.createTextNode$$module$build$src$core$utils$xml(d));b.appendChild(c);a.appendChild(b);$.domToWorkspace$$module$build$src$core$xml(a,this.workspace);$.setGroup$$module$build$src$core$events$utils(!1)}}else a.type===$.BLOCK_DELETE$$module$build$src$core$events$events?(b=this.getProcedureCall(),$.getDefinition$$module$build$src$core$procedures(b,
|
||||
this.workspace)||($.setGroup$$module$build$src$core$events$utils(a.group),this.dispose(!0),$.setGroup$$module$build$src$core$events$utils(!1))):a.type===$.BLOCK_CHANGE$$module$build$src$core$events$events&&a.element==="disabled"&&(b=this.getProcedureCall(),(d=$.getDefinition$$module$build$src$core$procedures(b,this.workspace))&&d.id===a.blockId&&((c=$.getGroup$$module$build$src$core$events$utils())&&console.log("Saw an existing group while responding to a definition change"),$.setGroup$$module$build$src$core$events$utils(a.group),
|
||||
a=d.isEnabled(),this.setDisabledReason(!a,DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON$$module$build$src$blocks$procedures),this.setWarningText(a?null:$.Msg$$module$build$src$core$msg.PROCEDURES_CALL_DISABLED_DEF_WARNING.replace("%1",b)),$.setGroup$$module$build$src$core$events$utils(c)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=this.getProcedureCall(),c=this.workspace;a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.PROCEDURES_HIGHLIGHT_DEF,callback:function(){const d=
|
||||
$.getDefinition$$module$build$src$core$procedures(b,c);d&&(c.centerOnBlock(d.id),$.getFocusManager$$module$build$src$core$focus_manager().focusNode(d))}})}}};
|
||||
blocks$$module$build$src$blocks$procedures.procedures_callnoreturn=Object.assign({},PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null},defType_:"procedures_defnoreturn"});
|
||||
blocks$$module$build$src$blocks$procedures.procedures_callreturn=Object.assign({},PROCEDURE_CALL_COMMON$$module$build$src$blocks$procedures,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null},defType_:"procedures_defreturn"});
|
||||
var UNPARENTED_IFRETURN_DISABLED_REASON$$module$build$src$blocks$procedures="UNPARENTED_IFRETURN",PROCEDURES_IFRETURN$$module$build$src$blocks$procedures={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");
|
||||
this.setTooltip($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("value",String(Number(this.hasReturnValue_)));return a},domToMutation:function(a){this.hasReturnValue_=a.getAttribute("value")==="1";this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN))},
|
||||
onchange:function(a){if(!(this.workspace.isDragging&&this.workspace.isDragging()||a.type!==$.BLOCK_MOVE$$module$build$src$core$events$events&&a.type!==$.BLOCK_CREATE$$module$build$src$core$events$events)){a=!1;var b=this;do{if(this.FUNCTION_TYPES.includes(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?(b.type==="procedures_defnoreturn"&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=
|
||||
!1):b.type!=="procedures_defreturn"||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField($.Msg$$module$build$src$core$msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null)):this.setWarningText($.Msg$$module$build$src$core$msg.PROCEDURES_IFRETURN_WARNING);if(!this.isInFlyout)try{$.setRecordUndo$$module$build$src$core$events$utils(!1),this.setDisabledReason(!a,UNPARENTED_IFRETURN_DISABLED_REASON$$module$build$src$blocks$procedures)}finally{$.setRecordUndo$$module$build$src$core$events$utils(!0)}}},
|
||||
FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};blocks$$module$build$src$blocks$procedures.procedures_ifreturn=PROCEDURES_IFRETURN$$module$build$src$blocks$procedures;$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$procedures);var module$build$src$blocks$procedures={blocks:blocks$$module$build$src$blocks$procedures};var blocks$$module$build$src$blocks$math=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"math_number",message0:"%1",args0:[{type:"field_number",name:"NUM",value:0}],output:"Number",helpUrl:"%{BKY_MATH_NUMBER_HELPURL}",style:"math_blocks",tooltip:"%{BKY_MATH_NUMBER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"math_arithmetic",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ADDITION_SYMBOL}",
|
||||
"ADD"],["%{BKY_MATH_SUBTRACTION_SYMBOL}","MINUS"],["%{BKY_MATH_MULTIPLICATION_SYMBOL}","MULTIPLY"],["%{BKY_MATH_DIVISION_SYMBOL}","DIVIDE"],["%{BKY_MATH_POWER_SYMBOL}","POWER"]]},{type:"input_value",name:"B",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ARITHMETIC_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_single",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_SINGLE_OP_ROOT}","ROOT"],["%{BKY_MATH_SINGLE_OP_ABSOLUTE}",
|
||||
"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_SINGLE_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_trig",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_TRIG_SIN}","SIN"],["%{BKY_MATH_TRIG_COS}","COS"],["%{BKY_MATH_TRIG_TAN}","TAN"],["%{BKY_MATH_TRIG_ASIN}","ASIN"],["%{BKY_MATH_TRIG_ACOS}","ACOS"],["%{BKY_MATH_TRIG_ATAN}",
|
||||
"ATAN"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_TRIG_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_constant",message0:"%1",args0:[{type:"field_dropdown",name:"CONSTANT",options:[["\u03c0","PI"],["e","E"],["\u03c6","GOLDEN_RATIO"],["sqrt(2)","SQRT2"],["sqrt(\u00bd)","SQRT1_2"],["\u221e","INFINITY"]]}],output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_CONSTANT_TOOLTIP}",helpUrl:"%{BKY_MATH_CONSTANT_HELPURL}"},{type:"math_number_property",
|
||||
message0:"%1 %2",args0:[{type:"input_value",name:"NUMBER_TO_CHECK",check:"Number"},{type:"field_dropdown",name:"PROPERTY",options:[["%{BKY_MATH_IS_EVEN}","EVEN"],["%{BKY_MATH_IS_ODD}","ODD"],["%{BKY_MATH_IS_PRIME}","PRIME"],["%{BKY_MATH_IS_WHOLE}","WHOLE"],["%{BKY_MATH_IS_POSITIVE}","POSITIVE"],["%{BKY_MATH_IS_NEGATIVE}","NEGATIVE"],["%{BKY_MATH_IS_DIVISIBLE_BY}","DIVISIBLE_BY"]]}],inputsInline:!0,output:"Boolean",style:"math_blocks",tooltip:"%{BKY_MATH_IS_TOOLTIP}",mutator:"math_is_divisibleby_mutator"},
|
||||
{type:"math_change",message0:"%{BKY_MATH_CHANGE_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_MATH_CHANGE_TITLE_ITEM}"},{type:"input_value",name:"DELTA",check:"Number"}],previousStatement:null,nextStatement:null,style:"variable_blocks",helpUrl:"%{BKY_MATH_CHANGE_HELPURL}",extensions:["math_change_tooltip"]},{type:"math_round",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ROUND_OPERATOR_ROUND}","ROUND"],["%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}","ROUNDUP"],
|
||||
["%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}","ROUNDDOWN"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ROUND_HELPURL}",tooltip:"%{BKY_MATH_ROUND_TOOLTIP}"},{type:"math_on_list",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ONLIST_OPERATOR_SUM}","SUM"],["%{BKY_MATH_ONLIST_OPERATOR_MIN}","MIN"],["%{BKY_MATH_ONLIST_OPERATOR_MAX}","MAX"],["%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}","AVERAGE"],["%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}",
|
||||
"MEDIAN"],["%{BKY_MATH_ONLIST_OPERATOR_MODE}","MODE"],["%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}","STD_DEV"],["%{BKY_MATH_ONLIST_OPERATOR_RANDOM}","RANDOM"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ONLIST_HELPURL}",mutator:"math_modes_of_list_mutator",extensions:["math_op_tooltip"]},{type:"math_modulo",message0:"%{BKY_MATH_MODULO_TITLE}",args0:[{type:"input_value",name:"DIVIDEND",check:"Number"},{type:"input_value",name:"DIVISOR",check:"Number"}],
|
||||
inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_MODULO_TOOLTIP}",helpUrl:"%{BKY_MATH_MODULO_HELPURL}"},{type:"math_constrain",message0:"%{BKY_MATH_CONSTRAIN_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"Number"},{type:"input_value",name:"LOW",check:"Number"},{type:"input_value",name:"HIGH",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_CONSTRAIN_TOOLTIP}",helpUrl:"%{BKY_MATH_CONSTRAIN_HELPURL}"},{type:"math_random_int",message0:"%{BKY_MATH_RANDOM_INT_TITLE}",
|
||||
args0:[{type:"input_value",name:"FROM",check:"Number"},{type:"input_value",name:"TO",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_RANDOM_INT_TOOLTIP}",helpUrl:"%{BKY_MATH_RANDOM_INT_HELPURL}"},{type:"math_random_float",message0:"%{BKY_MATH_RANDOM_FLOAT_TITLE_RANDOM}",output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_RANDOM_FLOAT_TOOLTIP}",helpUrl:"%{BKY_MATH_RANDOM_FLOAT_HELPURL}"},{type:"math_atan2",message0:"%{BKY_MATH_ATAN2_TITLE}",args0:[{type:"input_value",
|
||||
name:"X",check:"Number"},{type:"input_value",name:"Y",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_ATAN2_TOOLTIP}",helpUrl:"%{BKY_MATH_ATAN2_HELPURL}"}]),TOOLTIPS_BY_OP$$module$build$src$blocks$math={ADD:"%{BKY_MATH_ARITHMETIC_TOOLTIP_ADD}",MINUS:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MINUS}",MULTIPLY:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MULTIPLY}",DIVIDE:"%{BKY_MATH_ARITHMETIC_TOOLTIP_DIVIDE}",POWER:"%{BKY_MATH_ARITHMETIC_TOOLTIP_POWER}",ROOT:"%{BKY_MATH_SINGLE_TOOLTIP_ROOT}",
|
||||
ABS:"%{BKY_MATH_SINGLE_TOOLTIP_ABS}",NEG:"%{BKY_MATH_SINGLE_TOOLTIP_NEG}",LN:"%{BKY_MATH_SINGLE_TOOLTIP_LN}",LOG10:"%{BKY_MATH_SINGLE_TOOLTIP_LOG10}",EXP:"%{BKY_MATH_SINGLE_TOOLTIP_EXP}",POW10:"%{BKY_MATH_SINGLE_TOOLTIP_POW10}",SIN:"%{BKY_MATH_TRIG_TOOLTIP_SIN}",COS:"%{BKY_MATH_TRIG_TOOLTIP_COS}",TAN:"%{BKY_MATH_TRIG_TOOLTIP_TAN}",ASIN:"%{BKY_MATH_TRIG_TOOLTIP_ASIN}",ACOS:"%{BKY_MATH_TRIG_TOOLTIP_ACOS}",ATAN:"%{BKY_MATH_TRIG_TOOLTIP_ATAN}",SUM:"%{BKY_MATH_ONLIST_TOOLTIP_SUM}",MIN:"%{BKY_MATH_ONLIST_TOOLTIP_MIN}",
|
||||
MAX:"%{BKY_MATH_ONLIST_TOOLTIP_MAX}",AVERAGE:"%{BKY_MATH_ONLIST_TOOLTIP_AVERAGE}",MEDIAN:"%{BKY_MATH_ONLIST_TOOLTIP_MEDIAN}",MODE:"%{BKY_MATH_ONLIST_TOOLTIP_MODE}",STD_DEV:"%{BKY_MATH_ONLIST_TOOLTIP_STD_DEV}",RANDOM:"%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}"};$.register$$module$build$src$core$extensions("math_op_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("OP",TOOLTIPS_BY_OP$$module$build$src$blocks$math));
|
||||
var IS_DIVISIBLEBY_MUTATOR_MIXIN$$module$build$src$blocks$math={mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation"),b=this.getFieldValue("PROPERTY")==="DIVISIBLE_BY";a.setAttribute("divisor_input",String(b));return a},domToMutation:function(a){a=a.getAttribute("divisor_input")==="true";this.updateShape_(a)},updateShape_:function(a){const b=this.getInput("DIVISOR");a?b||this.appendValueInput("DIVISOR").setCheck("Number"):b&&this.removeInput("DIVISOR")}},IS_DIVISIBLE_MUTATOR_EXTENSION$$module$build$src$blocks$math=
|
||||
function(){this.getField("PROPERTY").setValidator(function(a){a=a==="DIVISIBLE_BY";this.getSourceBlock().updateShape_(a)})};$.registerMutator$$module$build$src$core$extensions("math_is_divisibleby_mutator",IS_DIVISIBLEBY_MUTATOR_MIXIN$$module$build$src$blocks$math,IS_DIVISIBLE_MUTATOR_EXTENSION$$module$build$src$blocks$math);$.register$$module$build$src$core$extensions("math_change_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_MATH_CHANGE_TOOLTIP}","VAR"));
|
||||
var LIST_MODES_MUTATOR_MIXIN$$module$build$src$blocks$math={updateType_:function(a){a==="MODE"?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){a=a.getAttribute("op");if(a===null)throw new TypeError("xmlElement had no op attribute");this.updateType_(a)}},LIST_MODES_MUTATOR_EXTENSION$$module$build$src$blocks$math=
|
||||
function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))};$.registerMutator$$module$build$src$core$extensions("math_modes_of_list_mutator",LIST_MODES_MUTATOR_MIXIN$$module$build$src$blocks$math,LIST_MODES_MUTATOR_EXTENSION$$module$build$src$blocks$math);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$math);var module$build$src$blocks$math={blocks:blocks$$module$build$src$blocks$math};var blocks$$module$build$src$blocks$loops=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",
|
||||
args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_whileUntil",message0:"%1 %2",args0:[{type:"field_dropdown",name:"MODE",options:[["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}","WHILE"],["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}","UNTIL"]]},
|
||||
{type:"input_value",name:"BOOL",check:"Boolean"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",extensions:["controls_whileUntil_tooltip"]},{type:"controls_for",message0:"%{BKY_CONTROLS_FOR_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",
|
||||
align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],inputsInline:!0,previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOR_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_for_tooltip"]},{type:"controls_forEach",message0:"%{BKY_CONTROLS_FOREACH_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"LIST",
|
||||
check:"Array"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOREACH_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_forEach_tooltip"]},{type:"controls_flow_statements",message0:"%1",args0:[{type:"field_dropdown",name:"FLOW",options:[["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}","BREAK"],["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}","CONTINUE"]]}],
|
||||
previousStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_flow_tooltip","controls_flow_in_loop_check"]}]),WHILE_UNTIL_TOOLTIPS$$module$build$src$blocks$loops={WHILE:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}",UNTIL:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}"};$.register$$module$build$src$core$extensions("controls_whileUntil_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("MODE",WHILE_UNTIL_TOOLTIPS$$module$build$src$blocks$loops));
|
||||
var BREAK_CONTINUE_TOOLTIPS$$module$build$src$blocks$loops={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"};$.register$$module$build$src$core$extensions("controls_flow_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("FLOW",BREAK_CONTINUE_TOOLTIPS$$module$build$src$blocks$loops));
|
||||
var CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN$$module$build$src$blocks$loops={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR"),c=b.getVariable().getName();this.isCollapsed()||c===null||(b={type:"variables_get",fields:{VAR:b.saveState(!0)}},a.push({enabled:!0,text:$.Msg$$module$build$src$core$msg.VARIABLES_SET_CREATE_GET.replace("%1",c),callback:$.callbackFactory$$module$build$src$core$contextmenu(this,b)}))}}};
|
||||
$.registerMixin$$module$build$src$core$extensions("contextMenu_newGetVariableBlock",CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN$$module$build$src$blocks$loops);$.register$$module$build$src$core$extensions("controls_for_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR"));$.register$$module$build$src$core$extensions("controls_forEach_tooltip",$.buildTooltipWithFieldText$$module$build$src$core$extensions("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));
|
||||
var loopTypes$$module$build$src$blocks$loops=new Set(["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]),CONTROL_FLOW_NOT_IN_LOOP_DISABLED_REASON$$module$build$src$blocks$loops="CONTROL_FLOW_NOT_IN_LOOP",CONTROL_FLOW_IN_LOOP_CHECK_MIXIN$$module$build$src$blocks$loops={getSurroundLoop:function(){let a=this;do{if(loopTypes$$module$build$src$blocks$loops.has(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){const b=this.workspace;
|
||||
if(b.isDragging&&!b.isDragging()&&(a.type===$.BLOCK_MOVE$$module$build$src$core$events$events||a.type===$.BLOCK_CREATE$$module$build$src$core$events$events)&&(a=!!this.getSurroundLoop(),this.setWarningText(a?null:$.Msg$$module$build$src$core$msg.CONTROLS_FLOW_STATEMENTS_WARNING),!this.isInFlyout))try{$.setRecordUndo$$module$build$src$core$events$utils(!1),this.setDisabledReason(!a,CONTROL_FLOW_NOT_IN_LOOP_DISABLED_REASON$$module$build$src$blocks$loops)}finally{$.setRecordUndo$$module$build$src$core$events$utils(!0)}}};
|
||||
$.registerMixin$$module$build$src$core$extensions("controls_flow_in_loop_check",CONTROL_FLOW_IN_LOOP_CHECK_MIXIN$$module$build$src$blocks$loops);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$loops);var module$build$src$blocks$loops={blocks:blocks$$module$build$src$blocks$loops,loopTypes:loopTypes$$module$build$src$blocks$loops};var blocks$$module$build$src$blocks$logic=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"logic_boolean",message0:"%1",args0:[{type:"field_dropdown",name:"BOOL",options:[["%{BKY_LOGIC_BOOLEAN_TRUE}","TRUE"],["%{BKY_LOGIC_BOOLEAN_FALSE}","FALSE"]]}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_BOOLEAN_TOOLTIP}",helpUrl:"%{BKY_LOGIC_BOOLEAN_HELPURL}"},{type:"controls_if",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],
|
||||
message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],previousStatement:null,nextStatement:null,style:"logic_blocks",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,mutator:"controls_if_mutator",extensions:["controls_if_tooltip"]},{type:"controls_ifelse",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],message2:"%{BKY_CONTROLS_IF_MSG_ELSE} %1",
|
||||
args2:[{type:"input_statement",name:"ELSE"}],previousStatement:null,nextStatement:null,style:"logic_blocks",tooltip:"%{BKYCONTROLS_IF_TOOLTIP_2}",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_if_tooltip"]},{type:"logic_compare",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A"},{type:"field_dropdown",name:"OP",options:[["=","EQ"],["\u2260","NEQ"],["\u200f<","LT"],["\u200f\u2264","LTE"],["\u200f>","GT"],["\u200f\u2265","GTE"]]},{type:"input_value",name:"B"}],
|
||||
inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",extensions:["logic_op_tooltip"]},
|
||||
{type:"logic_negate",message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",name:"IF",check:"Boolean"}],
|
||||
message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1",args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},{type:"controls_if_elseif",
|
||||
message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}",previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]),TOOLTIPS_BY_OP$$module$build$src$blocks$logic={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",
|
||||
LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"};$.register$$module$build$src$core$extensions("logic_op_tooltip",$.buildTooltipForDropdown$$module$build$src$core$extensions("OP",TOOLTIPS_BY_OP$$module$build$src$blocks$logic));
|
||||
var CONTROLS_IF_MUTATOR_MIXIN$$module$build$src$blocks$logic={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=$.createElement$$module$build$src$core$utils$xml("mutation");this.elseifCount_&&a.setAttribute("elseif",String(this.elseifCount_));this.elseCount_&&a.setAttribute("else","1");return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;this.rebuildShape_()},
|
||||
saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){const b=a.newBlock("controls_if_if");b.initSvg();let c=b.nextConnection;for(let d=1;d<=this.elseifCount_;d++){const e=a.newBlock("controls_if_elseif");e.initSvg();c.connect(e.previousConnection);
|
||||
c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;const b=[null],c=[null];let d=null;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;break;default:throw TypeError("Unknown block type: "+
|
||||
a.type);}a=a.getNextBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();let b=1;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b);const d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE");a.statementConnection_=c&&c.connection.targetConnection;
|
||||
break;default:throw TypeError("Unknown block type: "+a.type);}a=a.getNextBlock()}},rebuildShape_:function(){const a=[null],b=[null];let c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(let d=1;this.getInput("IF"+d);d++){const e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");
|
||||
for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField($.Msg$$module$build$src$core$msg.CONTROLS_IF_MSG_ELSE)},reconnectChildBlocks_:function(a,b,c){for(let d=
|
||||
1;d<=this.elseifCount_;d++){let e;(e=a[d])==null||e.reconnect(this,"IF"+d);let f;(f=b[d])==null||f.reconnect(this,"DO"+d)}c==null||c.reconnect(this,"ELSE")}};$.registerMutator$$module$build$src$core$extensions("controls_if_mutator",CONTROLS_IF_MUTATOR_MIXIN$$module$build$src$blocks$logic,null,["controls_if_elseif","controls_if_else"]);
|
||||
var CONTROLS_IF_TOOLTIP_EXTENSION$$module$build$src$blocks$logic=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_4}else return $.Msg$$module$build$src$core$msg.CONTROLS_IF_TOOLTIP_1;
|
||||
return""}.bind(this))};$.register$$module$build$src$core$extensions("controls_if_tooltip",CONTROLS_IF_TOOLTIP_EXTENSION$$module$build$src$blocks$logic);
|
||||
var LOGIC_COMPARE_ONCHANGE_MIXIN$$module$build$src$blocks$logic={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A");const c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&($.setGroup$$module$build$src$core$events$utils(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1],
|
||||
b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),$.setGroup$$module$build$src$core$events$utils(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},LOGIC_COMPARE_EXTENSION$$module$build$src$blocks$logic=function(){this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN$$module$build$src$blocks$logic)};$.register$$module$build$src$core$extensions("logic_compare",LOGIC_COMPARE_EXTENSION$$module$build$src$blocks$logic);
|
||||
var LOGIC_TERNARY_ONCHANGE_MIXIN$$module$build$src$blocks$logic={prevParentConnection_:null,onchange:function(a){const b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(let e=0;e<2;e++){const f=e===1?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&($.setGroup$$module$build$src$core$events$utils(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(),
|
||||
f.bumpNeighbours()),$.setGroup$$module$build$src$core$events$utils(!1))}this.prevParentConnection_=d}};$.registerMixin$$module$build$src$core$extensions("logic_ternary",LOGIC_TERNARY_ONCHANGE_MIXIN$$module$build$src$blocks$logic);$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$logic);var module$build$src$blocks$logic={blocks:blocks$$module$build$src$blocks$logic};var blocks$$module$build$src$blocks$lists=$.createBlockDefinitionsFromJsonArray$$module$build$src$core$common([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}",
|
||||
helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"},{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},
|
||||
{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]),LISTS_CREATE_WITH$$module$build$src$blocks$lists={init:function(){this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.MutatorIcon$$module$build$src$core$icons$mutator_icon(["lists_create_with_item"],
|
||||
this));this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("items",String(this.itemCount_));return a},domToMutation:function(a){a=a.getAttribute("items");if(!a)throw new TypeError("element did not have items");this.itemCount_=parseInt(a,10);this.updateShape_()},saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;
|
||||
this.updateShape_()},decompose:function(a){const b=a.newBlock("lists_create_with_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("lists_create_with_item");e.initSvg();if(!e.previousConnection)throw Error("itemBlock has no previousConnection");c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||a.push(b.valueConnection_),b=b.getNextBlock();
|
||||
for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&!a.includes(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++){let c;(c=a[b])==null||c.reconnect(this,"ADD"+b)}},saveConnections:function(a){a=a.getInputTargetBlock("STACK");let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);let d;a.valueConnection_=(d=c)==null?void 0:d.connection.targetConnection;a=a.getNextBlock();
|
||||
b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_EMPTY_TITLE);for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+a).setAlign($.Align$$module$build$src$core$inputs$align.RIGHT);a===0&&b.appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_INPUT_WITH)}for(a=this.itemCount_;this.getInput("ADD"+
|
||||
a);a++)this.removeInput("ADD"+a)}};blocks$$module$build$src$blocks$lists.lists_create_with=LISTS_CREATE_WITH$$module$build$src$blocks$lists;var LISTS_CREATE_WITH_CONTAINER$$module$build$src$blocks$lists={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
|
||||
blocks$$module$build$src$blocks$lists.lists_create_with_container=LISTS_CREATE_WITH_CONTAINER$$module$build$src$blocks$lists;var LISTS_CREATE_WITH_ITEM$$module$build$src$blocks$lists={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_ITEM_TITLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);this.contextMenu=!1}};
|
||||
blocks$$module$build$src$blocks$lists.lists_create_with_item=LISTS_CREATE_WITH_ITEM$$module$build$src$blocks$lists;
|
||||
var LISTS_INDEXOF$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_FIRST,"FIRST"],[$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_HELPURL);this.setStyle("list_blocks");this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_INPUT_IN_LIST);a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",
|
||||
options:a});if(!a)throw Error("field_dropdown not found");this.appendValueInput("FIND").appendField(a,"END");this.setInputsInline(!0);this.setTooltip(()=>$.Msg$$module$build$src$core$msg.LISTS_INDEX_OF_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?"0":"-1"))}};blocks$$module$build$src$blocks$lists.lists_indexOf=LISTS_INDEXOF$$module$build$src$blocks$lists;
|
||||
var LISTS_GETINDEX$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_GET,"GET"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FIRST,"FIRST"],
|
||||
[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_LAST,"LAST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_HELPURL);this.setStyle("list_blocks");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});a.setValidator(function(b){b=b==="REMOVE";this.getSourceBlock().updateStatement_(b)});this.appendValueInput("VALUE").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_INPUT_IN_LIST);
|
||||
this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this.WHERE_OPTIONS});a.setValidator(function(b){const c=this.getValue();b=b==="FROM_START"||b==="FROM_END";b!==(c==="FROM_START"||c==="FROM_END")&&this.getSourceBlock().updateAt_(b)});this.appendDummyInput().appendField(a,"WHERE");this.appendDummyInput("AT");$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL);
|
||||
this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);this.setTooltip(()=>{const b=this.getFieldValue("MODE"),c=this.getFieldValue("WHERE");let d="";switch(b+" "+c){case "GET FROM_START":case "GET FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;break;case "GET FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;
|
||||
break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;break;case "GET_REMOVE LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;break;case "REMOVE FROM_START":case "REMOVE FROM_END":d=
|
||||
$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;break;case "REMOVE RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if(c==="FROM_START"||c==="FROM_END")d+=" "+(c==="FROM_START"?$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP:$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",
|
||||
this.workspace.options.oneBasedIndex?"#1":"#0");return d})},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("statement",String(!this.outputConnection));const b=this.getInput("AT")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at",String(b));return a},domToMutation:function(a){const b=a.getAttribute("statement")==="true";this.updateStatement_(b);a=a.getAttribute("at")!=="false";this.updateAt_(a)},saveExtraState:function(){return this.outputConnection?
|
||||
null:{isStatement:!0}},loadExtraState:function(a){a.isStatement?this.updateStatement_(!0):typeof a==="string"&&this.domToMutation($.textToDom$$module$build$src$core$utils$xml(a))},updateStatement_:function(a){a!==!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),
|
||||
$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};blocks$$module$build$src$blocks$lists.lists_getIndex=LISTS_GETINDEX$$module$build$src$blocks$lists;
|
||||
var LISTS_SETINDEX$$module$build$src$blocks$lists={init:function(){var a=[[$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_SET,"SET"],[$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_FIRST,"FIRST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_LAST,"LAST"],[$.Msg$$module$build$src$core$msg.LISTS_GET_INDEX_RANDOM,
|
||||
"RANDOM"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INPUT_IN_LIST);a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:a});this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this.WHERE_OPTIONS});a.setValidator(function(b){const c=
|
||||
this.getValue();b=b==="FROM_START"||b==="FROM_END";b!==(c==="FROM_START"||c==="FROM_END")&&this.getSourceBlock().updateAt_(b)});this.appendDummyInput().appendField(a,"WHERE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);this.setTooltip(()=>{const b=
|
||||
this.getFieldValue("MODE"),c=this.getFieldValue("WHERE");let d="";switch(b+" "+c){case "SET FROM_START":case "SET FROM_END":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":d=
|
||||
$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;break;case "INSERT FIRST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;break;case "INSERT RANDOM":d=$.Msg$$module$build$src$core$msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if(c==="FROM_START"||c==="FROM_END")d+=" "+$.Msg$$module$build$src$core$msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",this.workspace.options.oneBasedIndex?
|
||||
"#1":"#0");return d})},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation"),b=this.getInput("AT")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at",String(b));return a},domToMutation:function(a){a=a.getAttribute("at")!=="false";this.updateAt_(a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),
|
||||
$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL","TO")}};blocks$$module$build$src$blocks$lists.lists_setIndex=LISTS_SETINDEX$$module$build$src$blocks$lists;
|
||||
var LISTS_GETSUBLIST$$module$build$src$blocks$lists={init:function(){this.WHERE_OPTIONS_1=[[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],
|
||||
[$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);const a=b=>{const c=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:this["WHERE_OPTIONS_"+b]});c.setValidator(function(d){const e=this.getValue();d=d==="FROM_START"||d===
|
||||
"FROM_END";d!==(e==="FROM_START"||e==="FROM_END")&&this.getSourceBlock().updateAt_(b,d)});return c};this.appendDummyInput("WHERE1_INPUT").appendField(a(1),"WHERE1");this.appendDummyInput("AT1");this.appendDummyInput("WHERE2_INPUT").appendField(a(2),"WHERE2");this.appendDummyInput("AT2");$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,
|
||||
!0);this.updateAt_(2,!0);this.setTooltip($.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");var b=this.getInput("AT1")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at1",String(b));b=this.getInput("AT2")instanceof $.ValueInput$$module$build$src$core$inputs$value_input;a.setAttribute("at2",String(b));return a},domToMutation:function(a){const b=a.getAttribute("at1")===
|
||||
"true";a=a.getAttribute("at2")==="true";this.updateAt_(1,b);this.updateAt_(2,a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.Msg$$module$build$src$core$msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);a===1&&(this.moveInputBefore("AT1",
|
||||
"WHERE2_INPUT"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","WHERE2_INPUT"));$.Msg$$module$build$src$core$msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};blocks$$module$build$src$blocks$lists.lists_getSublist=LISTS_GETSUBLIST$$module$build$src$blocks$lists;
|
||||
blocks$$module$build$src$blocks$lists.lists_sort={init:function(){this.jsonInit({message0:"%{BKY_LISTS_SORT_TITLE}",args0:[{type:"field_dropdown",name:"TYPE",options:[["%{BKY_LISTS_SORT_TYPE_NUMERIC}","NUMERIC"],["%{BKY_LISTS_SORT_TYPE_TEXT}","TEXT"],["%{BKY_LISTS_SORT_TYPE_IGNORECASE}","IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[["%{BKY_LISTS_SORT_ORDER_ASCENDING}","1"],["%{BKY_LISTS_SORT_ORDER_DESCENDING}","-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",
|
||||
style:"list_blocks",tooltip:"%{BKY_LISTS_SORT_TOOLTIP}",helpUrl:"%{BKY_LISTS_SORT_HELPURL}"})}};
|
||||
blocks$$module$build$src$blocks$lists.lists_split={init:function(){const a=$.fromJson$$module$build$src$core$field_registry({type:"field_dropdown",options:[[$.Msg$$module$build$src$core$msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[$.Msg$$module$build$src$core$msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]]});if(!a)throw Error("field_dropdown not found");a.setValidator(b=>{this.updateType_(b)});this.setHelpUrl($.Msg$$module$build$src$core$msg.LISTS_SPLIT_HELPURL);this.setStyle("list_blocks");this.appendValueInput("INPUT").setCheck("String").appendField(a,
|
||||
"MODE");this.appendValueInput("DELIM").setCheck("String").appendField($.Msg$$module$build$src$core$msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);this.setOutput(!0,"Array");this.setTooltip(()=>{const b=this.getFieldValue("MODE");if(b==="SPLIT")return $.Msg$$module$build$src$core$msg.LISTS_SPLIT_TOOLTIP_SPLIT;if(b==="JOIN")return $.Msg$$module$build$src$core$msg.LISTS_SPLIT_TOOLTIP_JOIN;throw Error("Unknown mode: "+b);})},updateType_:function(a){if(this.getFieldValue("MODE")!==a){const b=
|
||||
this.getInput("INPUT").connection;b.setShadowDom(null);const c=b.targetBlock();c&&(b.disconnect(),c.isShadow()?c.dispose(!1):this.bumpNeighbours())}a==="SPLIT"?(this.outputConnection.setCheck("Array"),this.getInput("INPUT").setCheck("String")):(this.outputConnection.setCheck("String"),this.getInput("INPUT").setCheck("Array"))},mutationToDom:function(){const a=$.createElement$$module$build$src$core$utils$xml("mutation");a.setAttribute("mode",this.getFieldValue("MODE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("mode"))},
|
||||
saveExtraState:function(){return{mode:this.getFieldValue("MODE")}},loadExtraState:function(a){this.updateType_(a.mode)}};$.defineBlocks$$module$build$src$core$common(blocks$$module$build$src$blocks$lists);var module$build$src$blocks$lists={blocks:blocks$$module$build$src$blocks$lists};var blocks$$module$build$src$blocks$blocks=Object.assign({},blocks$$module$build$src$blocks$lists,blocks$$module$build$src$blocks$logic,blocks$$module$build$src$blocks$loops,blocks$$module$build$src$blocks$math,blocks$$module$build$src$blocks$procedures,blocks$$module$build$src$blocks$text,blocks$$module$build$src$blocks$variables,blocks$$module$build$src$blocks$variables_dynamic),module$build$src$blocks$blocks={blocks:blocks$$module$build$src$blocks$blocks,lists:module$build$src$blocks$lists,logic:module$build$src$blocks$logic,
|
||||
loops:module$build$src$blocks$loops,math:module$build$src$blocks$math,procedures:module$build$src$blocks$procedures,texts:module$build$src$blocks$text,variables:module$build$src$blocks$variables,variablesDynamic:module$build$src$blocks$variables_dynamic};
|
||||
module$build$src$blocks$blocks.__namespace__=$;
|
||||
return module$build$src$blocks$blocks;
|
||||
}));
|
||||
|
||||
|
||||
//# sourceMappingURL=blocks_compressed.js.map
|
||||
465
crumbblocks/lib/blockly/de.js
Normal file
465
crumbblocks/lib/blockly/de.js
Normal file
@@ -0,0 +1,465 @@
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory();
|
||||
} else { // Browser
|
||||
var messages = factory();
|
||||
for (var key in messages) {
|
||||
root.Blockly.Msg[key] = messages[key];
|
||||
}
|
||||
}
|
||||
}(this, function() {
|
||||
// This file was automatically generated. Do not modify.
|
||||
|
||||
'use strict';
|
||||
|
||||
var Blockly = Blockly || { Msg: Object.create(null) };
|
||||
|
||||
Blockly.Msg["ADD_COMMENT"] = "Kommentar hinzufügen";
|
||||
Blockly.Msg["ALT_KEY"] = "Alt"; // untranslated
|
||||
Blockly.Msg["CANNOT_DELETE_VARIABLE_PROCEDURE"] = "Die Variable „%1“ kann nicht gelöscht werden, da sie Teil der Definition der Funktion „%2“ ist.";
|
||||
Blockly.Msg["CHANGE_VALUE_TITLE"] = "Wert ändern:";
|
||||
Blockly.Msg["CHROME_OS"] = "ChromeOS"; // untranslated
|
||||
Blockly.Msg["CLEAN_UP"] = "Bausteine aufräumen";
|
||||
Blockly.Msg["CLOSE"] = "Close"; // untranslated
|
||||
Blockly.Msg["COLLAPSED_WARNINGS_WARNING"] = "Eingeklappte Blöcke enthalten Warnungen.";
|
||||
Blockly.Msg["COLLAPSE_ALL"] = "Alle Bausteine zusammenfalten";
|
||||
Blockly.Msg["COLLAPSE_BLOCK"] = "Baustein zusammenfalten";
|
||||
Blockly.Msg["COLOUR_BLEND_COLOUR1"] = "Farbe 1";
|
||||
Blockly.Msg["COLOUR_BLEND_COLOUR2"] = "und Farbe 2";
|
||||
Blockly.Msg["COLOUR_BLEND_HELPURL"] = "https://meyerweb.com/eric/tools/color-blend/#:::rgbp"; // untranslated
|
||||
Blockly.Msg["COLOUR_BLEND_RATIO"] = "im Verhältnis";
|
||||
Blockly.Msg["COLOUR_BLEND_TITLE"] = "mische";
|
||||
Blockly.Msg["COLOUR_BLEND_TOOLTIP"] = "Vermischt 2 Farben mit konfigurierbarem Farbverhältnis (0.0 - 1.0).";
|
||||
Blockly.Msg["COLOUR_PICKER_HELPURL"] = "https://de.wikipedia.org/wiki/Farbe";
|
||||
Blockly.Msg["COLOUR_PICKER_TOOLTIP"] = "Wähle eine Farbe aus der Palette.";
|
||||
Blockly.Msg["COLOUR_RANDOM_HELPURL"] = "http://randomcolour.com"; // untranslated
|
||||
Blockly.Msg["COLOUR_RANDOM_TITLE"] = "zufällige Farbe";
|
||||
Blockly.Msg["COLOUR_RANDOM_TOOLTIP"] = "Erzeugt eine Farbe nach dem Zufallsprinzip.";
|
||||
Blockly.Msg["COLOUR_RGB_BLUE"] = "blau";
|
||||
Blockly.Msg["COLOUR_RGB_GREEN"] = "grün";
|
||||
Blockly.Msg["COLOUR_RGB_HELPURL"] = "https://www.december.com/html/spec/colorpercompact.html"; // untranslated
|
||||
Blockly.Msg["COLOUR_RGB_RED"] = "rot";
|
||||
Blockly.Msg["COLOUR_RGB_TITLE"] = "Farbe aus";
|
||||
Blockly.Msg["COLOUR_RGB_TOOLTIP"] = "Erzeugt eine Farbe mit selbst definierten Rot-, Grün- und Blauwerten. Alle Werte müssen zwischen 0 und 100 liegen.";
|
||||
Blockly.Msg["COMMAND_KEY"] = "⌘ Command"; // untranslated
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_HELPURL"] = "https://de.wikipedia.org/wiki/Kontrollstruktur";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK"] = "die Schleife abbrechen";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE"] = "sofort mit nächstem Schleifendurchlauf fortfahren";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK"] = "Die umgebende Schleife beenden.";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE"] = "Diese Anweisung abbrechen und mit dem nächsten Schleifendurchlauf fortfahren.";
|
||||
Blockly.Msg["CONTROLS_FLOW_STATEMENTS_WARNING"] = "Warnung: Dieser Baustein kann nur in einer Schleife verwendet werden.";
|
||||
Blockly.Msg["CONTROLS_FOREACH_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_FOREACH_TITLE"] = "für jeden Wert %1 aus der Liste %2";
|
||||
Blockly.Msg["CONTROLS_FOREACH_TOOLTIP"] = "Führt eine Anweisung für jeden Wert in der Liste aus und setzt dabei die Variable \"%1\" auf den aktuellen Listenwert.";
|
||||
Blockly.Msg["CONTROLS_FOR_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_FOR_TITLE"] = "zähle %1 von %2 bis %3 in Schritten von %4";
|
||||
Blockly.Msg["CONTROLS_FOR_TOOLTIP"] = "Zählt die Variable \"%1\" von einem Startwert bis zu einem Endwert und führt für jeden Wert eine Anweisung aus.";
|
||||
Blockly.Msg["CONTROLS_IF_ELSEIF_TOOLTIP"] = "Eine weitere Bedingung hinzufügen.";
|
||||
Blockly.Msg["CONTROLS_IF_ELSE_TOOLTIP"] = "Eine sonst-Bedingung hinzufügen. Führt eine Anweisung aus, falls keine Bedingung zutrifft.";
|
||||
Blockly.Msg["CONTROLS_IF_HELPURL"] = "https://github.com/google/blockly/wiki/IfElse"; // untranslated
|
||||
Blockly.Msg["CONTROLS_IF_IF_TOOLTIP"] = "Hinzufügen, entfernen oder sortieren von Sektionen";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_ELSE"] = "sonst";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_ELSEIF"] = "sonst falls";
|
||||
Blockly.Msg["CONTROLS_IF_MSG_IF"] = "falls";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_1"] = "Führt eine Anweisung aus, falls eine Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_2"] = "Führt die erste Anweisung aus, falls eine Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_3"] = "Führt die erste Anweisung aus, falls die erste Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus, falls die zweite Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROLS_IF_TOOLTIP_4"] = "Führe die erste Anweisung aus, falls die erste Bedingung wahr ist. Führt ansonsten die zweite Anweisung aus, falls die zweite Bedingung wahr ist. Führt die dritte Anweisung aus, falls keine der beiden Bedingungen wahr ist.";
|
||||
Blockly.Msg["CONTROLS_REPEAT_HELPURL"] = "https://de.wikipedia.org/wiki/For-Schleife";
|
||||
Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"] = "mache";
|
||||
Blockly.Msg["CONTROLS_REPEAT_TITLE"] = "wiederhole %1-mal:";
|
||||
Blockly.Msg["CONTROLS_REPEAT_TOOLTIP"] = "Eine Anweisung mehrfach ausführen.";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_HELPURL"] = "https://de.wikipedia.org/wiki/Schleife_(Programmierung)#Kopfgesteuerte_Schleife";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_OPERATOR_UNTIL"] = "wiederhole bis";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_OPERATOR_WHILE"] = "wiederhole solange";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL"] = "Führt Anweisungen aus, solange die Bedingung unwahr ist.";
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_TOOLTIP_WHILE"] = "Führt Anweisungen aus, solange die Bedingung wahr ist.";
|
||||
Blockly.Msg["CONTROL_KEY"] = "Ctrl"; // untranslated
|
||||
Blockly.Msg["COPY_SHORTCUT"] = "Copy"; // untranslated
|
||||
Blockly.Msg["CUT_SHORTCUT"] = "Cut"; // untranslated
|
||||
Blockly.Msg["DELETE_ALL_BLOCKS"] = "Alle %1 Bausteine löschen?";
|
||||
Blockly.Msg["DELETE_BLOCK"] = "Baustein löschen";
|
||||
Blockly.Msg["DELETE_VARIABLE"] = "Die Variable „%1“ löschen";
|
||||
Blockly.Msg["DELETE_VARIABLE_CONFIRMATION"] = "%1 Verwendungen der Variable „%2“ löschen?";
|
||||
Blockly.Msg["DELETE_X_BLOCKS"] = "%1 Bausteine löschen";
|
||||
Blockly.Msg["DIALOG_CANCEL"] = "Abbrechen";
|
||||
Blockly.Msg["DIALOG_OK"] = "Okay";
|
||||
Blockly.Msg["DISABLE_BLOCK"] = "Baustein deaktivieren";
|
||||
Blockly.Msg["DUPLICATE_BLOCK"] = "Kopieren";
|
||||
Blockly.Msg["DUPLICATE_COMMENT"] = "Kommentar duplizieren";
|
||||
Blockly.Msg["EDIT_BLOCK_CONTENTS"] = "Edit Block contents"; // untranslated
|
||||
Blockly.Msg["ENABLE_BLOCK"] = "Baustein aktivieren";
|
||||
Blockly.Msg["EXPAND_ALL"] = "Alle Bausteine entfalten";
|
||||
Blockly.Msg["EXPAND_BLOCK"] = "Baustein entfalten";
|
||||
Blockly.Msg["EXTERNAL_INPUTS"] = "externe Eingänge";
|
||||
Blockly.Msg["HELP"] = "Hilfe";
|
||||
Blockly.Msg["HELP_PROMPT"] = "Press %1 for help on keyboard controls"; // untranslated
|
||||
Blockly.Msg["INLINE_INPUTS"] = "interne Eingänge";
|
||||
Blockly.Msg["KEYBOARD_NAV_CONSTRAINED_MOVE_HINT"] = "Use the arrow keys to move, then %1 to accept the position"; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_COPIED_HINT"] = "Copied. Press %1 to paste."; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_CUT_HINT"] = "Cut. Press %1 to paste."; // untranslated
|
||||
Blockly.Msg["KEYBOARD_NAV_UNCONSTRAINED_MOVE_HINT"] = "Hold %1 and use arrow keys to move freely, then %2 to accept the position"; // untranslated
|
||||
Blockly.Msg["LINUX"] = "Linux"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-empty-list"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_TITLE"] = "erzeuge eine leere Liste";
|
||||
Blockly.Msg["LISTS_CREATE_EMPTY_TOOLTIP"] = "Erzeugt eine leere Liste ohne Inhalt.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_CONTAINER_TITLE_ADD"] = "Liste";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_CONTAINER_TOOLTIP"] = "Hinzufügen, entfernen und sortieren von Elementen.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-list-with"; // untranslated
|
||||
Blockly.Msg["LISTS_CREATE_WITH_INPUT_WITH"] = "erzeuge Liste mit";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_ITEM_TOOLTIP"] = "Ein Element zur Liste hinzufügen.";
|
||||
Blockly.Msg["LISTS_CREATE_WITH_TOOLTIP"] = "Erzeugt eine Liste aus den angegebenen Elementen.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FIRST"] = "das erste";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FROM_END"] = "von hinten das";
|
||||
Blockly.Msg["LISTS_GET_INDEX_FROM_START"] = "das";
|
||||
Blockly.Msg["LISTS_GET_INDEX_GET"] = "nimm";
|
||||
Blockly.Msg["LISTS_GET_INDEX_GET_REMOVE"] = "nimm und entferne";
|
||||
Blockly.Msg["LISTS_GET_INDEX_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_GET_INDEX_LAST"] = "das letzte";
|
||||
Blockly.Msg["LISTS_GET_INDEX_RANDOM"] = "ein zufälliges";
|
||||
Blockly.Msg["LISTS_GET_INDEX_REMOVE"] = "entferne";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TAIL"] = "Element";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_FIRST"] = "Extrahiert das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_FROM"] = "Extrahiert das Element an der angegebenen Position in der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_LAST"] = "Extrahiert das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_RANDOM"] = "Extrahiert ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST"] = "Extrahiert und entfernt das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM"] = "Extrahiert und entfernt das Element an der angegebenen Position aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST"] = "Extrahiert und entfernt das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM"] = "Extrahiert und entfernt ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST"] = "Entfernt das erste Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM"] = "Entfernt das Element an der angegebenen Position aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST"] = "Entfernt das letzte Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM"] = "Entfernt ein zufälliges Element aus der Liste.";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_FROM_END"] = "bis von hinten";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_FROM_START"] = "bis";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_END_LAST"] = "bis letztes";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#getting-a-sublist"; // untranslated
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FIRST"] = "nimm Teilliste ab erstes";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FROM_END"] = "nimm Teilliste ab von hinten";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_START_FROM_START"] = "nimm Teilliste ab";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_TAIL"] = "Element";
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_TOOLTIP"] = "Erstellt eine Kopie mit dem angegebenen Abschnitt der Liste.";
|
||||
Blockly.Msg["LISTS_INDEX_FROM_END_TOOLTIP"] = "%1 ist das letzte Element.";
|
||||
Blockly.Msg["LISTS_INDEX_FROM_START_TOOLTIP"] = "%1 ist das erste Element.";
|
||||
Blockly.Msg["LISTS_INDEX_OF_FIRST"] = "suche erstes Auftreten von";
|
||||
Blockly.Msg["LISTS_INDEX_OF_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#finding-items-in-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_INDEX_OF_LAST"] = "suche letztes Auftreten von";
|
||||
Blockly.Msg["LISTS_INDEX_OF_TOOLTIP"] = "Sucht die Position (Index) eines Elementes in der Liste. Gibt %1 zurück, falls kein Element gefunden wurde.";
|
||||
Blockly.Msg["LISTS_INLIST"] = "in der Liste";
|
||||
Blockly.Msg["LISTS_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#is-empty"; // untranslated
|
||||
Blockly.Msg["LISTS_ISEMPTY_TITLE"] = "%1 ist leer";
|
||||
Blockly.Msg["LISTS_ISEMPTY_TOOLTIP"] = "Ist wahr, falls die Liste leer ist.";
|
||||
Blockly.Msg["LISTS_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#length-of"; // untranslated
|
||||
Blockly.Msg["LISTS_LENGTH_TITLE"] = "Länge von %1";
|
||||
Blockly.Msg["LISTS_LENGTH_TOOLTIP"] = "Die Anzahl von Elementen in der Liste.";
|
||||
Blockly.Msg["LISTS_REPEAT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#create-list-with"; // untranslated
|
||||
Blockly.Msg["LISTS_REPEAT_TITLE"] = "erzeuge Liste mit %2-mal dem Element %1";
|
||||
Blockly.Msg["LISTS_REPEAT_TOOLTIP"] = "Erzeugt eine Liste mit der angegebenen Anzahl an Elementen des angegebenen Wertes.";
|
||||
Blockly.Msg["LISTS_REVERSE_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#reversing-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_REVERSE_MESSAGE0"] = "kehre %1 um";
|
||||
Blockly.Msg["LISTS_REVERSE_TOOLTIP"] = "Kehre eine Kopie einer Liste um.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#in-list--set"; // untranslated
|
||||
Blockly.Msg["LISTS_SET_INDEX_INPUT_TO"] = "ein";
|
||||
Blockly.Msg["LISTS_SET_INDEX_INSERT"] = "füge als";
|
||||
Blockly.Msg["LISTS_SET_INDEX_SET"] = "setze für";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST"] = "Fügt das Element an den Anfang der Liste an.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_FROM"] = "Fügt das Element an der angegebenen Position in die Liste ein.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_LAST"] = "Fügt das Element an das Ende der Liste an.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM"] = "Fügt das Element zufällig in die Liste ein.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_FIRST"] = "Setzt das erste Element in der Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_FROM"] = "Setzt das Element an der angegebenen Position in der Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_LAST"] = "Setzt das letzte Element in die Liste.";
|
||||
Blockly.Msg["LISTS_SET_INDEX_TOOLTIP_SET_RANDOM"] = "Setzt ein zufälliges Element in der Liste.";
|
||||
Blockly.Msg["LISTS_SORT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#sorting-a-list"; // untranslated
|
||||
Blockly.Msg["LISTS_SORT_ORDER_ASCENDING"] = "aufsteigend";
|
||||
Blockly.Msg["LISTS_SORT_ORDER_DESCENDING"] = "absteigend";
|
||||
Blockly.Msg["LISTS_SORT_TITLE"] = "%1 %2 %3 sortieren";
|
||||
Blockly.Msg["LISTS_SORT_TOOLTIP"] = "Eine Kopie einer Liste sortieren.";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_IGNORECASE"] = "alphabetisch, Großschreibung ignorieren";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_NUMERIC"] = "numerisch";
|
||||
Blockly.Msg["LISTS_SORT_TYPE_TEXT"] = "alphabetisch";
|
||||
Blockly.Msg["LISTS_SPLIT_HELPURL"] = "https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists"; // untranslated
|
||||
Blockly.Msg["LISTS_SPLIT_LIST_FROM_TEXT"] = "Liste aus Text erstellen";
|
||||
Blockly.Msg["LISTS_SPLIT_TEXT_FROM_LIST"] = "Text aus Liste erstellen";
|
||||
Blockly.Msg["LISTS_SPLIT_TOOLTIP_JOIN"] = "Liste mit Texten in einen Text vereinen, getrennt durch ein Trennzeichen.";
|
||||
Blockly.Msg["LISTS_SPLIT_TOOLTIP_SPLIT"] = "Text in eine Liste mit Texten aufteilen, unterbrochen bei jedem Trennzeichen.";
|
||||
Blockly.Msg["LISTS_SPLIT_WITH_DELIMITER"] = "mit Trennzeichen";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_FALSE"] = "falsch";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#values"; // untranslated
|
||||
Blockly.Msg["LOGIC_BOOLEAN_TOOLTIP"] = "Ist entweder wahr oder falsch.";
|
||||
Blockly.Msg["LOGIC_BOOLEAN_TRUE"] = "wahr";
|
||||
Blockly.Msg["LOGIC_COMPARE_HELPURL"] = "https://de.wikipedia.org/wiki/Vergleich_(Zahlen)";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_EQ"] = "Ist wahr, falls beide Werte gleich sind.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_GT"] = "Ist wahr, falls der erste Wert größer als der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_GTE"] = "Ist wahr, falls der erste Wert größer als oder gleich groß wie der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_LT"] = "Ist wahr, falls der erste Wert kleiner als der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_LTE"] = "Ist wahr, falls der erste Wert kleiner als oder gleich groß wie der zweite Wert ist.";
|
||||
Blockly.Msg["LOGIC_COMPARE_TOOLTIP_NEQ"] = "Ist wahr, falls beide Werte unterschiedlich sind.";
|
||||
Blockly.Msg["LOGIC_NEGATE_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#not"; // untranslated
|
||||
Blockly.Msg["LOGIC_NEGATE_TITLE"] = "nicht %1";
|
||||
Blockly.Msg["LOGIC_NEGATE_TOOLTIP"] = "Ist wahr, falls der Eingabewert unwahr ist. Ist unwahr, falls der Eingabewert wahr ist.";
|
||||
Blockly.Msg["LOGIC_NULL"] = "null";
|
||||
Blockly.Msg["LOGIC_NULL_HELPURL"] = "https://de.wikipedia.org/wiki/Nullwert";
|
||||
Blockly.Msg["LOGIC_NULL_TOOLTIP"] = "Ist \"null\".";
|
||||
Blockly.Msg["LOGIC_OPERATION_AND"] = "und";
|
||||
Blockly.Msg["LOGIC_OPERATION_HELPURL"] = "https://github.com/google/blockly/wiki/Logic#logical-operations"; // untranslated
|
||||
Blockly.Msg["LOGIC_OPERATION_OR"] = "oder";
|
||||
Blockly.Msg["LOGIC_OPERATION_TOOLTIP_AND"] = "Ist wahr, falls beide Werte wahr sind.";
|
||||
Blockly.Msg["LOGIC_OPERATION_TOOLTIP_OR"] = "Gibt true zurück, wenn mindestens eine der Eingaben wahr ist.";
|
||||
Blockly.Msg["LOGIC_TERNARY_CONDITION"] = "prüfe";
|
||||
Blockly.Msg["LOGIC_TERNARY_HELPURL"] = "https://de.wikipedia.org/wiki/%3F:#Auswahloperator";
|
||||
Blockly.Msg["LOGIC_TERNARY_IF_FALSE"] = "falls falsch";
|
||||
Blockly.Msg["LOGIC_TERNARY_IF_TRUE"] = "falls wahr";
|
||||
Blockly.Msg["LOGIC_TERNARY_TOOLTIP"] = "Überprüft eine Bedingung \"prüfe\". Falls die Bedingung wahr ist, wird der \"falls wahr\"-Wert zurückgegeben, andernfalls der \"falls unwahr\"-Wert";
|
||||
Blockly.Msg["MAC_OS"] = "macOS"; // untranslated
|
||||
Blockly.Msg["MATH_ADDITION_SYMBOL"] = "+"; // untranslated
|
||||
Blockly.Msg["MATH_ARITHMETIC_HELPURL"] = "https://de.wikipedia.org/wiki/Grundrechenart";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_ADD"] = "Ist die Summe zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_DIVIDE"] = "Ist der Quotient zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_MINUS"] = "Ist die Differenz zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_MULTIPLY"] = "Ist das Produkt zweier Zahlen.";
|
||||
Blockly.Msg["MATH_ARITHMETIC_TOOLTIP_POWER"] = "Ist die erste Zahl potenziert mit der zweiten Zahl.";
|
||||
Blockly.Msg["MATH_ATAN2_HELPURL"] = "https://de.wikipedia.org/wiki/Arctan2";
|
||||
Blockly.Msg["MATH_ATAN2_TITLE"] = "atan2 von X:%1 Y:%2";
|
||||
Blockly.Msg["MATH_ATAN2_TOOLTIP"] = "Gibt den Arkustangens des Punktes (X, Y) in Grad von -180 bis 180 zurück.";
|
||||
Blockly.Msg["MATH_CHANGE_HELPURL"] = "https://de.wikipedia.org/wiki/Inkrement_und_Dekrement";
|
||||
Blockly.Msg["MATH_CHANGE_TITLE"] = "erhöhe %1 um %2";
|
||||
Blockly.Msg["MATH_CHANGE_TOOLTIP"] = "Addiert eine Zahl zur Variable „%1“.";
|
||||
Blockly.Msg["MATH_CONSTANT_HELPURL"] = "https://de.wikipedia.org/wiki/Mathematische_Konstante";
|
||||
Blockly.Msg["MATH_CONSTANT_TOOLTIP"] = "Mathematische Konstanten wie: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…) oder ∞ (unendlich).";
|
||||
Blockly.Msg["MATH_CONSTRAIN_HELPURL"] = "https://en.wikipedia.org/wiki/Clamping_(graphics)"; // untranslated
|
||||
Blockly.Msg["MATH_CONSTRAIN_TITLE"] = "begrenze %1 zwischen %2 und %3";
|
||||
Blockly.Msg["MATH_CONSTRAIN_TOOLTIP"] = "Begrenzt eine Zahl auf den Wertebereich zwischen zwei anderen Zahlen (inklusiv).";
|
||||
Blockly.Msg["MATH_DIVISION_SYMBOL"] = "÷"; // untranslated
|
||||
Blockly.Msg["MATH_IS_DIVISIBLE_BY"] = "ist teilbar durch";
|
||||
Blockly.Msg["MATH_IS_EVEN"] = "ist gerade";
|
||||
Blockly.Msg["MATH_IS_NEGATIVE"] = "ist negativ";
|
||||
Blockly.Msg["MATH_IS_ODD"] = "ist ungerade";
|
||||
Blockly.Msg["MATH_IS_POSITIVE"] = "ist positiv";
|
||||
Blockly.Msg["MATH_IS_PRIME"] = "ist eine Primzahl";
|
||||
Blockly.Msg["MATH_IS_TOOLTIP"] = "Überprüft, ob eine Zahl gerade, ungerade, eine Primzahl, ganzzahlig, positiv, negativ oder durch eine zweite Zahl teilbar ist. Gibt wahr oder falsch zurück.";
|
||||
Blockly.Msg["MATH_IS_WHOLE"] = "ist eine ganze Zahl";
|
||||
Blockly.Msg["MATH_MODULO_HELPURL"] = "https://de.wikipedia.org/wiki/Modulo";
|
||||
Blockly.Msg["MATH_MODULO_TITLE"] = "Rest von %1 ÷ %2";
|
||||
Blockly.Msg["MATH_MODULO_TOOLTIP"] = "Der Rest nach einer Division.";
|
||||
Blockly.Msg["MATH_MULTIPLICATION_SYMBOL"] = "×"; // untranslated
|
||||
Blockly.Msg["MATH_NUMBER_HELPURL"] = "https://de.wikipedia.org/wiki/Zahl";
|
||||
Blockly.Msg["MATH_NUMBER_TOOLTIP"] = "Eine Zahl.";
|
||||
Blockly.Msg["MATH_ONLIST_HELPURL"] = "";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_AVERAGE"] = "Mittelwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MAX"] = "Maximalwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MEDIAN"] = "Median der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MIN"] = "Minimalwert der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_MODE"] = "am häufigsten in der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_RANDOM"] = "Zufallswert aus der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_STD_DEV"] = "Standardabweichung der Liste";
|
||||
Blockly.Msg["MATH_ONLIST_OPERATOR_SUM"] = "Summe über die Liste";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_AVERAGE"] = "Ist der Durchschnittswert aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MAX"] = "Ist die größte Zahl in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MEDIAN"] = "Ist der Median aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MIN"] = "Ist die kleinste Zahl in einer Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_MODE"] = "Findet die Werte mit dem häufigsten Vorkommen in der Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_RANDOM"] = "Gibt einen zufälligen Wert aus der Liste zurück.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_STD_DEV"] = "Ist die Standardabweichung aller Werte in der Liste.";
|
||||
Blockly.Msg["MATH_ONLIST_TOOLTIP_SUM"] = "Ist die Summe aller Zahlen in einer Liste.";
|
||||
Blockly.Msg["MATH_POWER_SYMBOL"] = "^"; // untranslated
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_HELPURL"] = "https://de.wikipedia.org/wiki/Zufallszahlen";
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_TITLE_RANDOM"] = "Zufallszahl (0.0 - 1.0)";
|
||||
Blockly.Msg["MATH_RANDOM_FLOAT_TOOLTIP"] = "Erzeugt eine Zufallszahl zwischen 0.0 (inklusiv) und 1.0 (exklusiv).";
|
||||
Blockly.Msg["MATH_RANDOM_INT_HELPURL"] = "https://de.wikipedia.org/wiki/Zufallszahlen";
|
||||
Blockly.Msg["MATH_RANDOM_INT_TITLE"] = "ganzzahlige Zufallszahl zwischen %1 und %2";
|
||||
Blockly.Msg["MATH_RANDOM_INT_TOOLTIP"] = "Erzeugt eine ganzzahlige Zufallszahl zwischen zwei Zahlen (inklusiv).";
|
||||
Blockly.Msg["MATH_ROUND_HELPURL"] = "https://de.wikipedia.org/wiki/Runden";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUND"] = "runde";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUNDDOWN"] = "runde ab";
|
||||
Blockly.Msg["MATH_ROUND_OPERATOR_ROUNDUP"] = "runde auf";
|
||||
Blockly.Msg["MATH_ROUND_TOOLTIP"] = "Eine Zahl auf- oder abrunden.";
|
||||
Blockly.Msg["MATH_SINGLE_HELPURL"] = "https://de.wikipedia.org/wiki/Quadratwurzel";
|
||||
Blockly.Msg["MATH_SINGLE_OP_ABSOLUTE"] = "Absolutbetrag";
|
||||
Blockly.Msg["MATH_SINGLE_OP_ROOT"] = "Quadratwurzel";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_ABS"] = "Ist der Absolutbetrag einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_EXP"] = "Ist Wert der Exponentialfunktion einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_LN"] = "Ist der natürliche Logarithmus einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_LOG10"] = "Ist der dekadische Logarithmus einer Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_NEG"] = "Negiert eine Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_POW10"] = "Rechnet 10 hoch eine Zahl.";
|
||||
Blockly.Msg["MATH_SINGLE_TOOLTIP_ROOT"] = "Ist die Quadratwurzel einer Zahl.";
|
||||
Blockly.Msg["MATH_SUBTRACTION_SYMBOL"] = "-"; // untranslated
|
||||
Blockly.Msg["MATH_TRIG_ACOS"] = "acos";
|
||||
Blockly.Msg["MATH_TRIG_ASIN"] = "asin";
|
||||
Blockly.Msg["MATH_TRIG_ATAN"] = "atan";
|
||||
Blockly.Msg["MATH_TRIG_COS"] = "cos";
|
||||
Blockly.Msg["MATH_TRIG_HELPURL"] = "https://de.wikipedia.org/wiki/Trigonometrie";
|
||||
Blockly.Msg["MATH_TRIG_SIN"] = "sin";
|
||||
Blockly.Msg["MATH_TRIG_TAN"] = "tan";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ACOS"] = "Ist der Arkuskosinus des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ASIN"] = "Ist der Arkussinus des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_ATAN"] = "Ist der Arkustangens des Eingabewertes.";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_COS"] = "Ist der Kosinus des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_SIN"] = "Ist der Sinus des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MATH_TRIG_TOOLTIP_TAN"] = "Ist der Tangens des Winkels (nicht Radiant).";
|
||||
Blockly.Msg["MOVE_BLOCK"] = "Move Block"; // untranslated
|
||||
Blockly.Msg["NEW_COLOUR_VARIABLE"] = "Farbvariable erstellen …";
|
||||
Blockly.Msg["NEW_NUMBER_VARIABLE"] = "Zahlenvariable erstellen …";
|
||||
Blockly.Msg["NEW_STRING_VARIABLE"] = "Zeichenfolgenvariable erstellen …";
|
||||
Blockly.Msg["NEW_VARIABLE"] = "Variable erstellen …";
|
||||
Blockly.Msg["NEW_VARIABLE_TITLE"] = "Name der neuen Variable:";
|
||||
Blockly.Msg["NEW_VARIABLE_TYPE_TITLE"] = "Neuer Variablentyp:";
|
||||
Blockly.Msg["OPTION_KEY"] = "⌥ Option"; // untranslated
|
||||
Blockly.Msg["ORDINAL_NUMBER_SUFFIX"] = ".";
|
||||
Blockly.Msg["PASTE_SHORTCUT"] = "Paste"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_ALLOW_STATEMENTS"] = "Anweisungen erlauben";
|
||||
Blockly.Msg["PROCEDURES_BEFORE_PARAMS"] = "mit:";
|
||||
Blockly.Msg["PROCEDURES_CALLNORETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Unterprogramm";
|
||||
Blockly.Msg["PROCEDURES_CALLNORETURN_TOOLTIP"] = "Ruft die benutzerdefinierte Funktion „%1“ auf.";
|
||||
Blockly.Msg["PROCEDURES_CALLRETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Unterprogramm";
|
||||
Blockly.Msg["PROCEDURES_CALLRETURN_TOOLTIP"] = "Ruft die benutzerdefinierte Funktion „%1“ auf und verwendet ihre Ausgabe.";
|
||||
Blockly.Msg["PROCEDURES_CALL_BEFORE_PARAMS"] = "mit:";
|
||||
Blockly.Msg["PROCEDURES_CALL_DISABLED_DEF_WARNING"] = "Die benutzerdefinierte Funktion '%1' kann nicht ausgeführt werden, weil der Definitionsblock deaktiviert ist.";
|
||||
Blockly.Msg["PROCEDURES_CREATE_DO"] = "Erzeuge \"Aufruf %1\"";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_COMMENT"] = "Beschreibe diese Funktion …";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_DO"] = ""; // untranslated
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_HELPURL"] = "https://en.wikipedia.org/wiki/Subroutine"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_PROCEDURE"] = "etwas tun";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_TITLE"] = "um";
|
||||
Blockly.Msg["PROCEDURES_DEFNORETURN_TOOLTIP"] = "Ein Funktionsblock ohne Rückgabewert.";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_HELPURL"] = "https://de.wikipedia.org/wiki/Prozedur_(Programmierung)";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_RETURN"] = "gib zurück";
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_TOOLTIP"] = "Ein Funktionsblock mit Rückgabewert.";
|
||||
Blockly.Msg["PROCEDURES_DEF_DUPLICATE_WARNING"] = "Warnung: Dieser Funktionsblock hat zwei gleich benannte Parameter.";
|
||||
Blockly.Msg["PROCEDURES_HIGHLIGHT_DEF"] = "Markiere Funktionsblock";
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_HELPURL"] = "https://c2.com/cgi/wiki?GuardClause"; // untranslated
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_TOOLTIP"] = "Gibt den zweiten Wert zurück und verlässt die Funktion, falls der erste Wert wahr ist.";
|
||||
Blockly.Msg["PROCEDURES_IFRETURN_WARNING"] = "Warnung: Dieser Block darf nur innerhalb eines Funktionsblocks genutzt werden.";
|
||||
Blockly.Msg["PROCEDURES_MUTATORARG_TITLE"] = "Variable:";
|
||||
Blockly.Msg["PROCEDURES_MUTATORARG_TOOLTIP"] = "Eine Eingabe zur Funktion hinzufügen.";
|
||||
Blockly.Msg["PROCEDURES_MUTATORCONTAINER_TITLE"] = "Parameter";
|
||||
Blockly.Msg["PROCEDURES_MUTATORCONTAINER_TOOLTIP"] = "Die Eingaben zu dieser Funktion hinzufügen, entfernen oder neu anordnen.";
|
||||
Blockly.Msg["REDO"] = "Wiederholen";
|
||||
Blockly.Msg["REMOVE_COMMENT"] = "Kommentar entfernen";
|
||||
Blockly.Msg["RENAME_VARIABLE"] = "Variable umbenennen …";
|
||||
Blockly.Msg["RENAME_VARIABLE_TITLE"] = "Alle „%1“-Variablen umbenennen in:";
|
||||
Blockly.Msg["SHORTCUTS_CODE_NAVIGATION"] = "Code navigation"; // untranslated
|
||||
Blockly.Msg["SHORTCUTS_EDITING"] = "Editing"; // untranslated
|
||||
Blockly.Msg["SHORTCUTS_GENERAL"] = "General"; // untranslated
|
||||
Blockly.Msg["TEXT_APPEND_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; // untranslated
|
||||
Blockly.Msg["TEXT_APPEND_TITLE"] = "zu %1 Text %2 anhängen";
|
||||
Blockly.Msg["TEXT_APPEND_TOOLTIP"] = "Text an die Variable \"%1\" anhängen.";
|
||||
Blockly.Msg["TEXT_CHANGECASE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#adjusting-text-case"; // untranslated
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_LOWERCASE"] = "wandel um in kleinbuchstaben";
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_TITLECASE"] = "wandel um in Substantive";
|
||||
Blockly.Msg["TEXT_CHANGECASE_OPERATOR_UPPERCASE"] = "wandel um in GROSSBUCHSTABEN";
|
||||
Blockly.Msg["TEXT_CHANGECASE_TOOLTIP"] = "Wandelt Schreibweise von Texten um, in Großbuchstaben, Kleinbuchstaben oder den ersten Buchstaben jedes Wortes groß und die anderen klein.";
|
||||
Blockly.Msg["TEXT_CHARAT_FIRST"] = "nimm ersten";
|
||||
Blockly.Msg["TEXT_CHARAT_FROM_END"] = "nimm von hinten";
|
||||
Blockly.Msg["TEXT_CHARAT_FROM_START"] = "nimm";
|
||||
Blockly.Msg["TEXT_CHARAT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#extracting-text"; // untranslated
|
||||
Blockly.Msg["TEXT_CHARAT_LAST"] = "nimm letzten";
|
||||
Blockly.Msg["TEXT_CHARAT_RANDOM"] = "nimm zufälligen";
|
||||
Blockly.Msg["TEXT_CHARAT_TAIL"] = "Buchstaben";
|
||||
Blockly.Msg["TEXT_CHARAT_TITLE"] = "im Text %1 %2";
|
||||
Blockly.Msg["TEXT_CHARAT_TOOLTIP"] = "Extrahiert einen Buchstaben von einer bestimmten Position.";
|
||||
Blockly.Msg["TEXT_COUNT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#counting-substrings"; // untranslated
|
||||
Blockly.Msg["TEXT_COUNT_MESSAGE0"] = "zähle %1 in %2";
|
||||
Blockly.Msg["TEXT_COUNT_TOOLTIP"] = "Zähle, wie oft ein Text innerhalb eines anderen Textes vorkommt.";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_ITEM_TOOLTIP"] = "Ein Element zum Text hinzufügen.";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_TITLE_JOIN"] = "verbinden";
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_TOOLTIP"] = "Hinzufügen, entfernen und sortieren von Elementen.";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_FROM_END"] = "bis von hinten";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_FROM_START"] = "bis";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_END_LAST"] = "bis letzter";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_HELPURL"] = "https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text"; // untranslated
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_INPUT_IN_TEXT"] = "im Text";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FIRST"] = "nimm Teil ab erster";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FROM_END"] = "nimm Teil ab von hinten";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_START_FROM_START"] = "nimm Teil ab";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_TAIL"] = "Buchstabe";
|
||||
Blockly.Msg["TEXT_GET_SUBSTRING_TOOLTIP"] = "Gibt den angegebenen Textabschnitt zurück.";
|
||||
Blockly.Msg["TEXT_INDEXOF_HELPURL"] = "https://github.com/google/blockly/wiki/Text#finding-text"; // untranslated
|
||||
Blockly.Msg["TEXT_INDEXOF_OPERATOR_FIRST"] = "suche erstes Auftreten des Begriffs";
|
||||
Blockly.Msg["TEXT_INDEXOF_OPERATOR_LAST"] = "suche letztes Auftreten des Begriffs";
|
||||
Blockly.Msg["TEXT_INDEXOF_TITLE"] = "im Text %1 %2 %3";
|
||||
Blockly.Msg["TEXT_INDEXOF_TOOLTIP"] = "Findet das erste / letzte Auftreten eines Suchbegriffs in einem Text. Gibt die Position des Begriffs zurück oder %1, falls der Suchbegriff nicht gefunden wurde.";
|
||||
Blockly.Msg["TEXT_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Text#checking-for-empty-text"; // untranslated
|
||||
Blockly.Msg["TEXT_ISEMPTY_TITLE"] = "%1 ist leer";
|
||||
Blockly.Msg["TEXT_ISEMPTY_TOOLTIP"] = "Ist wahr, falls der Text keine Zeichen enthält.";
|
||||
Blockly.Msg["TEXT_JOIN_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-creation"; // untranslated
|
||||
Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "erstelle Text aus";
|
||||
Blockly.Msg["TEXT_JOIN_TOOLTIP"] = "Erstellt einen Text durch das Verbinden von mehreren Textelementen.";
|
||||
Blockly.Msg["TEXT_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; // untranslated
|
||||
Blockly.Msg["TEXT_LENGTH_TITLE"] = "Länge von %1";
|
||||
Blockly.Msg["TEXT_LENGTH_TOOLTIP"] = "Die Anzahl von Zeichen in einem Text (inkl. Leerzeichen).";
|
||||
Blockly.Msg["TEXT_PRINT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#printing-text"; // untranslated
|
||||
Blockly.Msg["TEXT_PRINT_TITLE"] = "gib aus %1";
|
||||
Blockly.Msg["TEXT_PRINT_TOOLTIP"] = "Gibt den Text aus.";
|
||||
Blockly.Msg["TEXT_PROMPT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#getting-input-from-the-user"; // untranslated
|
||||
Blockly.Msg["TEXT_PROMPT_TOOLTIP_NUMBER"] = "Fragt den Benutzer nach einer Zahl.";
|
||||
Blockly.Msg["TEXT_PROMPT_TOOLTIP_TEXT"] = "Fragt den Benutzer nach einem Text.";
|
||||
Blockly.Msg["TEXT_PROMPT_TYPE_NUMBER"] = "frage nach Zahl mit Hinweis";
|
||||
Blockly.Msg["TEXT_PROMPT_TYPE_TEXT"] = "frage nach Text mit Hinweis";
|
||||
Blockly.Msg["TEXT_REPLACE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#replacing-substrings"; // untranslated
|
||||
Blockly.Msg["TEXT_REPLACE_MESSAGE0"] = "ersetze %1 durch %2 in %3";
|
||||
Blockly.Msg["TEXT_REPLACE_TOOLTIP"] = "Ersetze alle Vorkommen eines Textes innerhalb eines anderen Textes.";
|
||||
Blockly.Msg["TEXT_REVERSE_HELPURL"] = "https://github.com/google/blockly/wiki/Text#reversing-text"; // untranslated
|
||||
Blockly.Msg["TEXT_REVERSE_MESSAGE0"] = "kehre %1 um";
|
||||
Blockly.Msg["TEXT_REVERSE_TOOLTIP"] = "Kehre die Reihenfolge der Zeichen im Text um.";
|
||||
Blockly.Msg["TEXT_TEXT_HELPURL"] = "https://de.wikipedia.org/wiki/Zeichenkette";
|
||||
Blockly.Msg["TEXT_TEXT_TOOLTIP"] = "Ein Buchstabe, Text oder Satz.";
|
||||
Blockly.Msg["TEXT_TRIM_HELPURL"] = "https://github.com/google/blockly/wiki/Text#trimming-removing-spaces"; // untranslated
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_BOTH"] = "entferne Leerzeichen vom Anfang und vom Ende (links und rechts)";
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_LEFT"] = "entferne Leerzeichen vom Anfang (links)";
|
||||
Blockly.Msg["TEXT_TRIM_OPERATOR_RIGHT"] = "entferne Leerzeichen vom Ende (rechts)";
|
||||
Blockly.Msg["TEXT_TRIM_TOOLTIP"] = "Entfernt Leerzeichen vom Anfang und / oder Ende eines Textes.";
|
||||
Blockly.Msg["TODAY"] = "Heute";
|
||||
Blockly.Msg["UNDO"] = "Rückgängig";
|
||||
Blockly.Msg["UNKNOWN"] = "Unknown"; // untranslated
|
||||
Blockly.Msg["UNNAMED_KEY"] = "unbenannt";
|
||||
Blockly.Msg["VARIABLES_DEFAULT_NAME"] = "Element";
|
||||
Blockly.Msg["VARIABLES_GET_CREATE_SET"] = "Erzeuge \"Schreibe %1\"";
|
||||
Blockly.Msg["VARIABLES_GET_HELPURL"] = "https://github.com/google/blockly/wiki/Variables#get"; // untranslated
|
||||
Blockly.Msg["VARIABLES_GET_TOOLTIP"] = "Gibt den Wert der Variable zurück.";
|
||||
Blockly.Msg["VARIABLES_SET"] = "setze %1 auf %2";
|
||||
Blockly.Msg["VARIABLES_SET_CREATE_GET"] = "Erzeuge \"Lies %1\"";
|
||||
Blockly.Msg["VARIABLES_SET_HELPURL"] = "https://github.com/google/blockly/wiki/Variables#set"; // untranslated
|
||||
Blockly.Msg["VARIABLES_SET_TOOLTIP"] = "Setzt den Wert einer Variable.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS"] = "Eine Variable namens „%1“ ist bereits vorhanden.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE"] = "Eine Variable namens „%1“ ist bereits für einen anderen Typ vorhanden: „%2“.";
|
||||
Blockly.Msg["VARIABLE_ALREADY_EXISTS_FOR_A_PARAMETER"] = "Eine Variable mit dem Namen „%1“ ist bereits als Parameter in der Funktion „%2“ vorhanden.";
|
||||
Blockly.Msg["WINDOWS"] = "Windows"; // untranslated
|
||||
Blockly.Msg["WORKSPACE_ARIA_LABEL"] = "Blockly-Arbeitsbereich";
|
||||
Blockly.Msg["WORKSPACE_COMMENT_DEFAULT_TEXT"] = "Teile etwas mit…";
|
||||
Blockly.Msg["CONTROLS_FOREACH_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_FOR_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_IF_ELSEIF_TITLE_ELSEIF"] = Blockly.Msg["CONTROLS_IF_MSG_ELSEIF"];
|
||||
Blockly.Msg["CONTROLS_IF_ELSE_TITLE_ELSE"] = Blockly.Msg["CONTROLS_IF_MSG_ELSE"];
|
||||
Blockly.Msg["CONTROLS_IF_IF_TITLE_IF"] = Blockly.Msg["CONTROLS_IF_MSG_IF"];
|
||||
Blockly.Msg["CONTROLS_IF_MSG_THEN"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["CONTROLS_WHILEUNTIL_INPUT_DO"] = Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"];
|
||||
Blockly.Msg["LISTS_CREATE_WITH_ITEM_TITLE"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["LISTS_GET_INDEX_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_GET_SUBLIST_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_INDEX_OF_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["LISTS_SET_INDEX_INPUT_IN_LIST"] = Blockly.Msg["LISTS_INLIST"];
|
||||
Blockly.Msg["MATH_CHANGE_TITLE_ITEM"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_COMMENT"] = Blockly.Msg["PROCEDURES_DEFNORETURN_COMMENT"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_DO"] = Blockly.Msg["PROCEDURES_DEFNORETURN_DO"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_PROCEDURE"] = Blockly.Msg["PROCEDURES_DEFNORETURN_PROCEDURE"];
|
||||
Blockly.Msg["PROCEDURES_DEFRETURN_TITLE"] = Blockly.Msg["PROCEDURES_DEFNORETURN_TITLE"];
|
||||
Blockly.Msg["TEXT_APPEND_VARIABLE"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
Blockly.Msg["TEXT_CREATE_JOIN_ITEM_TITLE_ITEM"] = Blockly.Msg["VARIABLES_DEFAULT_NAME"];
|
||||
|
||||
Blockly.Msg["COLOUR_HUE"] = "20";
|
||||
Blockly.Msg["LISTS_HUE"] = "260";
|
||||
Blockly.Msg["LOGIC_HUE"] = "210";
|
||||
Blockly.Msg["LOOPS_HUE"] = "120";
|
||||
Blockly.Msg["MATH_HUE"] = "230";
|
||||
Blockly.Msg["PROCEDURES_HUE"] = "290";
|
||||
Blockly.Msg["TEXTS_HUE"] = "160";
|
||||
Blockly.Msg["VARIABLES_DYNAMIC_HUE"] = "310";
|
||||
Blockly.Msg["VARIABLES_HUE"] = "330";
|
||||
return Blockly.Msg;
|
||||
}));
|
||||
264
crumbblocks/lib/blockly/javascript.min.js
vendored
Normal file
264
crumbblocks/lib/blockly/javascript.min.js
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
// Do not edit this file; automatically generated.
|
||||
|
||||
/* eslint-disable */
|
||||
;(function(root, factory) {
|
||||
if (typeof define === 'function' && define.amd) { // AMD
|
||||
define(["./blockly_compressed.js"], factory);
|
||||
} else if (typeof exports === 'object') { // Node.js
|
||||
module.exports = factory(require("./blockly_compressed.js"));
|
||||
} else { // Script
|
||||
root.javascript = factory(root.Blockly);
|
||||
root.Blockly.JavaScript = root.javascript.javascriptGenerator;
|
||||
}
|
||||
}(this, function(__parent__) {
|
||||
var $=__parent__.__namespace__;
|
||||
var lists_create_empty$$module$build$src$generators$javascript$lists=function(a,b){return["[]",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},lists_create_with$$module$build$src$generators$javascript$lists=function(a,b){const c=Array(a.itemCount_);for(let d=0;d<a.itemCount_;d++)c[d]=b.valueToCode(a,"ADD"+d,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";return["["+c.join(", ")+"]",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},
|
||||
lists_repeat$$module$build$src$generators$javascript$lists=function(a,b){const c=b.provideFunction_("listsRepeat",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(value, n) {
|
||||
var array = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
array[i] = value;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
`),d=b.valueToCode(a,"ITEM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";a=b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return[c+"("+d+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_length$$module$build$src$generators$javascript$lists=function(a,b){return[(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||
|
||||
"[]")+".length",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER]},lists_isEmpty$$module$build$src$generators$javascript$lists=function(a,b){return["!"+(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]")+".length",Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT]},lists_indexOf$$module$build$src$generators$javascript$lists=function(a,b){const c=a.getFieldValue("END")==="FIRST"?"indexOf":"lastIndexOf",
|
||||
d=b.valueToCode(a,"FIND",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";b=(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]")+"."+c+"("+d+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION]:[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_getIndex$$module$build$src$generators$javascript$lists=
|
||||
function(a,b){const c=a.getFieldValue("MODE")||"GET",d=a.getFieldValue("WHERE")||"FROM_START";var e=b.valueToCode(a,"VALUE",d==="RANDOM"?Order$$module$build$src$generators$javascript$javascript_generator.NONE:Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";switch(d){case "FIRST":if(c==="GET")return[e+"[0]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+".shift()",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];
|
||||
if(c==="REMOVE")return e+".shift();\n";break;case "LAST":if(c==="GET")return[e+".slice(-1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+".pop()",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="REMOVE")return e+".pop();\n";break;case "FROM_START":a=b.getAdjusted(a,"AT");if(c==="GET")return[e+"["+a+"]",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER];if(c==="GET_REMOVE")return[e+
|
||||
".splice("+a+", 1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+".splice("+a+", 1);\n";break;case "FROM_END":a=b.getAdjusted(a,"AT",1,!0);if(c==="GET")return[e+".slice("+a+")[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="GET_REMOVE")return[e+".splice("+a+", 1)[0]",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+".splice("+a+", 1);";
|
||||
break;case "RANDOM":e=b.provideFunction_("listsGetRandomItem",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(list, remove) {
|
||||
var x = Math.floor(Math.random() * list.length);
|
||||
if (remove) {
|
||||
return list.splice(x, 1)[0];
|
||||
} else {
|
||||
return list[x];
|
||||
}
|
||||
}
|
||||
`)+"("+e+", "+(c!=="GET")+")";if(c==="GET"||c==="GET_REMOVE")return[e,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];if(c==="REMOVE")return e+";\n"}throw Error("Unhandled combination (lists_getIndex).");},lists_setIndex$$module$build$src$generators$javascript$lists=function(a,b){function c(){if(d.match(/^\w+$/))return"";const h=b.nameDB_.getDistinctName("tmpList",$.NameType$$module$build$src$core$names.VARIABLE),k="var "+h+" = "+d+";\n";d=h;return k}let d=b.valueToCode(a,
|
||||
"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";const e=a.getFieldValue("MODE")||"GET";var f=a.getFieldValue("WHERE")||"FROM_START";const g=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"null";switch(f){case "FIRST":if(e==="SET")return d+"[0] = "+g+";\n";if(e==="INSERT")return d+".unshift("+g+");\n";break;case "LAST":if(e==="SET")return c()+(d+"["+d+".length - 1] = "+g+";\n");if(e==="INSERT")return d+".push("+
|
||||
g+");\n";break;case "FROM_START":a=b.getAdjusted(a,"AT");if(e==="SET")return d+"["+a+"] = "+g+";\n";if(e==="INSERT")return d+".splice("+a+", 0, "+g+");\n";break;case "FROM_END":a=b.getAdjusted(a,"AT",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);f=c();if(e==="SET")return f+(d+"["+d+".length - "+a+"] = "+g+";\n");if(e==="INSERT")return f+(d+".splice("+d+".length - "+a+", 0, "+g+");\n");break;case "RANDOM":a=c();f=b.nameDB_.getDistinctName("tmpX",$.NameType$$module$build$src$core$names.VARIABLE);
|
||||
a+="var "+f+" = Math.floor(Math.random() * "+d+".length);\n";if(e==="SET")return a+(d+"["+f+"] = "+g+";\n");if(e==="INSERT")return a+(d+".splice("+f+", 0, "+g+");\n")}throw Error("Unhandled combination (lists_setIndex).");},lists_getSublist$$module$build$src$generators$javascript$lists=function(a,b){var c={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"},d=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";const e=a.getFieldValue("WHERE1"),
|
||||
f=a.getFieldValue("WHERE2");if(e==="FIRST"&&f==="LAST")d+=".slice(0)";else if(d.match(/^\w+$/)||e!=="FROM_END"&&f==="FROM_START"){switch(e){case "FROM_START":c=b.getAdjusted(a,"AT1");break;case "FROM_END":c=b.getAdjusted(a,"AT1",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);c=d+".length - "+c;break;case "FIRST":c="0";break;default:throw Error("Unhandled option (lists_getSublist).");}switch(f){case "FROM_START":b=b.getAdjusted(a,"AT2",1);break;case "FROM_END":b=
|
||||
b.getAdjusted(a,"AT2",0,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);b=d+".length - "+b;break;case "LAST":b=d+".length";break;default:throw Error("Unhandled option (lists_getSublist).");}d=d+".slice("+c+", "+b+")"}else{const g=b.getAdjusted(a,"AT1");a=b.getAdjusted(a,"AT2");d=b.provideFunction_("subsequence"+c[e]+c[f],`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(sequence${e==="FROM_END"||e==="FROM_START"?", at1":""}${f==="FROM_END"||f==="FROM_START"?", at2":""}) {
|
||||
var start = ${getSubstringIndex$$module$build$src$generators$javascript$lists("sequence",e,"at1")};
|
||||
var end = ${getSubstringIndex$$module$build$src$generators$javascript$lists("sequence",f,"at2")} + 1;
|
||||
return sequence.slice(start, end);
|
||||
}
|
||||
`)+"("+d+(e==="FROM_END"||e==="FROM_START"?", "+g:"")+(f==="FROM_END"||f==="FROM_START"?", "+a:"")+")"}return[d,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_sort$$module$build$src$generators$javascript$lists=function(a,b){const c=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL)||"[]",d=a.getFieldValue("DIRECTION")==="1"?1:-1;a=a.getFieldValue("TYPE");b=b.provideFunction_("listsGetSortCompare",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(type, direction) {
|
||||
var compareFuncs = {
|
||||
'NUMERIC': function(a, b) {
|
||||
return Number(a) - Number(b); },
|
||||
'TEXT': function(a, b) {
|
||||
return String(a) > String(b) ? 1 : -1; },
|
||||
'IGNORE_CASE': function(a, b) {
|
||||
return String(a).toLowerCase() > String(b).toLowerCase() ? 1 : -1; },
|
||||
};
|
||||
var compare = compareFuncs[type];
|
||||
return function(a, b) { return compare(a, b) * direction; };
|
||||
}
|
||||
`);return[c+".slice().sort("+b+'("'+a+'", '+d+"))",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_split$$module$build$src$generators$javascript$lists=function(a,b){let c=b.valueToCode(a,"INPUT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER);b=b.valueToCode(a,"DELIM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=a.getFieldValue("MODE");if(a==="SPLIT")c||(c="''"),a="split";else if(a==="JOIN")c||
|
||||
(c="[]"),a="join";else throw Error("Unknown mode: "+a);return[c+"."+a+"("+b+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},lists_reverse$$module$build$src$generators$javascript$lists=function(a,b){return[(b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL)||"[]")+".slice().reverse()",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},controls_if$$module$build$src$generators$javascript$logic=
|
||||
function(a,b){var c=0;let d="";b.STATEMENT_PREFIX&&(d+=b.injectId(b.STATEMENT_PREFIX,a));do{const e=b.valueToCode(a,"IF"+c,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false";let f=b.statementToCode(a,"DO"+c);b.STATEMENT_SUFFIX&&(f=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT)+f);d+=(c>0?" else ":"")+"if ("+e+") {\n"+f+"}";c++}while(a.getInput("IF"+c));if(a.getInput("ELSE")||b.STATEMENT_SUFFIX)c=a.getInput("ELSE")?b.statementToCode(a,"ELSE"):"",b.STATEMENT_SUFFIX&&
|
||||
(c=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT)+c),d+=" else {\n"+c+"}";return d+"\n"},logic_compare$$module$build$src$generators$javascript$logic=function(a,b){const c={EQ:"==",NEQ:"!=",LT:"<",LTE:"<=",GT:">",GTE:">="}[a.getFieldValue("OP")],d=c==="=="||c==="!="?Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY:Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,e=b.valueToCode(a,"A",d)||"0";a=b.valueToCode(a,"B",d)||"0";return[e+" "+
|
||||
c+" "+a,d]},logic_operation$$module$build$src$generators$javascript$logic=function(a,b){const c=a.getFieldValue("OP")==="AND"?"&&":"||",d=c==="&&"?Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND:Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR;let e=b.valueToCode(a,"A",d);a=b.valueToCode(a,"B",d);e||a?(b=c==="&&"?"true":"false",e||(e=b),a||(a=b)):a=e="false";return[e+" "+c+" "+a,d]},logic_negate$$module$build$src$generators$javascript$logic=
|
||||
function(a,b){const c=Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT;return["!"+(b.valueToCode(a,"BOOL",c)||"true"),c]},logic_boolean$$module$build$src$generators$javascript$logic=function(a,b){return[a.getFieldValue("BOOL")==="TRUE"?"true":"false",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},logic_null$$module$build$src$generators$javascript$logic=function(a,b){return["null",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},
|
||||
logic_ternary$$module$build$src$generators$javascript$logic=function(a,b){const c=b.valueToCode(a,"IF",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"false",d=b.valueToCode(a,"THEN",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"null";a=b.valueToCode(a,"ELSE",Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL)||"null";return[c+" ? "+d+" : "+a,Order$$module$build$src$generators$javascript$javascript_generator.CONDITIONAL]},
|
||||
controls_repeat_ext$$module$build$src$generators$javascript$loops=function(a,b){let c;c=a.getField("TIMES")?String(Number(a.getFieldValue("TIMES"))):b.valueToCode(a,"TIMES",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";let d=b.statementToCode(a,"DO");d=b.addLoopTrap(d,a);a="";const e=b.nameDB_.getDistinctName("count",$.NameType$$module$build$src$core$names.VARIABLE);let f=c;c.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(c)||(f=b.nameDB_.getDistinctName("repeat_end",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+f+" = "+c+";\n");return a+("for (var "+e+" = 0; "+e+" < "+f+"; "+e+"++) {\n"+d+"}\n")},controls_whileUntil$$module$build$src$generators$javascript$loops=function(a,b){const c=a.getFieldValue("MODE")==="UNTIL";let d=b.valueToCode(a,"BOOL",c?Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT:Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false",e=b.statementToCode(a,"DO");e=b.addLoopTrap(e,
|
||||
a);c&&(d="!"+d);return"while ("+d+") {\n"+e+"}\n"},controls_for$$module$build$src$generators$javascript$loops=function(a,b){var c=b.getVariableName(a.getFieldValue("VAR")),d=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0",e=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";const f=b.valueToCode(a,"BY",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"1";
|
||||
let g=b.statementToCode(a,"DO");g=b.addLoopTrap(g,a);if($.isNumber$$module$build$src$core$utils$string(d)&&$.isNumber$$module$build$src$core$utils$string(e)&&$.isNumber$$module$build$src$core$utils$string(f))b=Number(d)<=Number(e),a="for ("+c+" = "+d+"; "+c+(b?" <= ":" >= ")+e+"; "+c,c=Math.abs(Number(f)),a=c===1?a+(b?"++":"--"):a+((b?" += ":" -= ")+c),a+=") {\n"+g+"}\n";else{a="";let h=d;d.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(d)||(h=b.nameDB_.getDistinctName(c+"_start",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+h+" = "+d+";\n");d=e;e.match(/^\w+$/)||$.isNumber$$module$build$src$core$utils$string(e)||(d=b.nameDB_.getDistinctName(c+"_end",$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+d+" = "+e+";\n");e=b.nameDB_.getDistinctName(c+"_inc",$.NameType$$module$build$src$core$names.VARIABLE);a+="var "+e+" = ";a=$.isNumber$$module$build$src$core$utils$string(f)?a+(Math.abs(Number(f))+";\n"):a+("Math.abs("+f+");\n");a+="if ("+h+" > "+d+") {\n";
|
||||
a+=b.INDENT+e+" = -"+e+";\n";a=a+"}\nfor ("+(c+" = "+h+"; "+e+" >= 0 ? "+c+" <= "+d+" : "+c+" >= "+d+"; "+c+" += "+e+") {\n"+g+"}\n")}return a},controls_forEach$$module$build$src$generators$javascript$loops=function(a,b){const c=b.getVariableName(a.getFieldValue("VAR"));var d=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"[]";let e=b.statementToCode(a,"DO");e=b.addLoopTrap(e,a);a="";let f=d;d.match(/^\w+$/)||(f=b.nameDB_.getDistinctName(c+"_list",
|
||||
$.NameType$$module$build$src$core$names.VARIABLE),a+="var "+f+" = "+d+";\n");d=b.nameDB_.getDistinctName(c+"_index",$.NameType$$module$build$src$core$names.VARIABLE);e=b.INDENT+c+" = "+f+"["+d+"];\n"+e;return a+("for (var "+d+" in "+f+") {\n"+e+"}\n")},controls_flow_statements$$module$build$src$generators$javascript$loops=function(a,b){let c="";b.STATEMENT_PREFIX&&(c+=b.injectId(b.STATEMENT_PREFIX,a));b.STATEMENT_SUFFIX&&(c+=b.injectId(b.STATEMENT_SUFFIX,a));if(b.STATEMENT_PREFIX){const d=a.getSurroundLoop();
|
||||
d&&!d.suppressPrefixSuffix&&(c+=b.injectId(b.STATEMENT_PREFIX,d))}switch(a.getFieldValue("FLOW")){case "BREAK":return c+"break;\n";case "CONTINUE":return c+"continue;\n"}throw Error("Unknown flow statement.");},math_number$$module$build$src$generators$javascript$math=function(a,b){a=Number(a.getFieldValue("NUM"));return[String(a),a>=0?Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC:Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION]},math_arithmetic$$module$build$src$generators$javascript$math=
|
||||
function(a,b){var c={ADD:[" + ",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION],MINUS:[" - ",Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION],MULTIPLY:[" * ",Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION],DIVIDE:[" / ",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION],POWER:[null,Order$$module$build$src$generators$javascript$javascript_generator.NONE]}[a.getFieldValue("OP")];
|
||||
const d=c[0];c=c[1];const e=b.valueToCode(a,"A",c)||"0";a=b.valueToCode(a,"B",c)||"0";return d?[e+d+a,c]:["Math.pow("+e+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_single$$module$build$src$generators$javascript$math=function(a,b){const c=a.getFieldValue("OP");let d;if(c==="NEG")return a=b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION)||"0",a[0]==="-"&&(a=" "+a),["-"+a,Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION];
|
||||
a=c==="SIN"||c==="COS"||c==="TAN"?b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION)||"0":b.valueToCode(a,"NUM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";switch(c){case "ABS":d="Math.abs("+a+")";break;case "ROOT":d="Math.sqrt("+a+")";break;case "LN":d="Math.log("+a+")";break;case "EXP":d="Math.exp("+a+")";break;case "POW10":d="Math.pow(10,"+a+")";break;case "ROUND":d="Math.round("+a+")";break;case "ROUNDUP":d="Math.ceil("+
|
||||
a+")";break;case "ROUNDDOWN":d="Math.floor("+a+")";break;case "SIN":d="Math.sin("+a+" / 180 * Math.PI)";break;case "COS":d="Math.cos("+a+" / 180 * Math.PI)";break;case "TAN":d="Math.tan("+a+" / 180 * Math.PI)"}if(d)return[d,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];switch(c){case "LOG10":d="Math.log("+a+") / Math.log(10)";break;case "ASIN":d="Math.asin("+a+") / Math.PI * 180";break;case "ACOS":d="Math.acos("+a+") / Math.PI * 180";break;case "ATAN":d="Math.atan("+
|
||||
a+") / Math.PI * 180";break;default:throw Error("Unknown math operator: "+c);}return[d,Order$$module$build$src$generators$javascript$javascript_generator.DIVISION]},math_constant$$module$build$src$generators$javascript$math=function(a,b){return{PI:["Math.PI",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],E:["Math.E",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],GOLDEN_RATIO:["(1 + Math.sqrt(5)) / 2",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION],
|
||||
SQRT2:["Math.SQRT2",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],SQRT1_2:["Math.SQRT1_2",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],INFINITY:["Infinity",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]}[a.getFieldValue("CONSTANT")]},math_number_property$$module$build$src$generators$javascript$math=function(a,b){var c={EVEN:[" % 2 === 0",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],ODD:[" % 2 === 1",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],WHOLE:[" % 1 === 0",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],POSITIVE:[" > 0",Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL],NEGATIVE:[" < 0",Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL,Order$$module$build$src$generators$javascript$javascript_generator.RELATIONAL],DIVISIBLE_BY:[null,Order$$module$build$src$generators$javascript$javascript_generator.MODULUS,Order$$module$build$src$generators$javascript$javascript_generator.EQUALITY],PRIME:[null,Order$$module$build$src$generators$javascript$javascript_generator.NONE,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]};const d=a.getFieldValue("PROPERTY"),[e,f,g]=c[d];c=b.valueToCode(a,"NUMBER_TO_CHECK",f)||"0";d==="PRIME"?a=b.provideFunction_("mathIsPrime",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(n) {
|
||||
// https://en.wikipedia.org/wiki/Primality_test#Naive_methods
|
||||
if (n == 2 || n == 3) {
|
||||
return true;
|
||||
}
|
||||
// False if n is NaN, negative, is 1, or not whole.
|
||||
// And false if n is divisible by 2 or 3.
|
||||
if (isNaN(n) || n <= 1 || n % 1 !== 0 || n % 2 === 0 || n % 3 === 0) {
|
||||
return false;
|
||||
}
|
||||
// Check all the numbers of form 6k +/- 1, up to sqrt(n).
|
||||
for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {
|
||||
if (n % (x - 1) === 0 || n % (x + 1) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
`)+"("+c+")":d==="DIVISIBLE_BY"?(a=b.valueToCode(a,"DIVISOR",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||"0",a=c+" % "+a+" === 0"):a=c+e;return[a,g]},math_change$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"DELTA",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION)||"0";a=b.getVariableName(a.getFieldValue("VAR"));return a+" = (typeof "+a+" === 'number' ? "+a+" : 0) + "+c+";\n"},math_on_list$$module$build$src$generators$javascript$math=
|
||||
function(a,b){var c=a.getFieldValue("OP");switch(c){case "SUM":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"[]";a+=".reduce(function(x, y) {return x + y;}, 0)";break;case "MIN":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a="Math.min.apply(null, "+a+")";break;case "MAX":a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a="Math.max.apply(null, "+
|
||||
a+")";break;case "AVERAGE":c=b.provideFunction_("mathMean",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(myList) {
|
||||
return myList.reduce(function(x, y) {return x + y;}, 0) / myList.length;
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "MEDIAN":c=b.provideFunction_("mathMedian",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(myList) {
|
||||
var localList = myList.filter(function (x) {return typeof x === 'number';});
|
||||
if (!localList.length) return null;
|
||||
localList.sort(function(a, b) {return b - a;});
|
||||
if (localList.length % 2 === 0) {
|
||||
return (localList[localList.length / 2 - 1] + localList[localList.length / 2]) / 2;
|
||||
} else {
|
||||
return localList[(localList.length - 1) / 2];
|
||||
}
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "MODE":c=b.provideFunction_("mathModes",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(values) {
|
||||
var modes = [];
|
||||
var counts = [];
|
||||
var maxCount = 0;
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var value = values[i];
|
||||
var found = false;
|
||||
var thisCount;
|
||||
for (var j = 0; j < counts.length; j++) {
|
||||
if (counts[j][0] === value) {
|
||||
thisCount = ++counts[j][1];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
counts.push([value, 1]);
|
||||
thisCount = 1;
|
||||
}
|
||||
maxCount = Math.max(thisCount, maxCount);
|
||||
}
|
||||
for (var j = 0; j < counts.length; j++) {
|
||||
if (counts[j][1] === maxCount) {
|
||||
modes.push(counts[j][0]);
|
||||
}
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "STD_DEV":c=b.provideFunction_("mathStandardDeviation",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(numbers) {
|
||||
var n = numbers.length;
|
||||
if (!n) return null;
|
||||
var mean = numbers.reduce(function(x, y) {return x + y;}) / n;
|
||||
var variance = 0;
|
||||
for (var j = 0; j < n; j++) {
|
||||
variance += Math.pow(numbers[j] - mean, 2);
|
||||
}
|
||||
variance /= n;
|
||||
return Math.sqrt(variance);
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;case "RANDOM":c=b.provideFunction_("mathRandomList",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(list) {
|
||||
var x = Math.floor(Math.random() * list.length);
|
||||
return list[x];
|
||||
}
|
||||
`);a=b.valueToCode(a,"LIST",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"[]";a=c+"("+a+")";break;default:throw Error("Unknown operator: "+c);}return[a,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_modulo$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"DIVIDEND",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||"0";a=b.valueToCode(a,"DIVISOR",Order$$module$build$src$generators$javascript$javascript_generator.MODULUS)||
|
||||
"0";return[c+" % "+a,Order$$module$build$src$generators$javascript$javascript_generator.MODULUS]},math_constrain$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0",d=b.valueToCode(a,"LOW",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";a=b.valueToCode(a,"HIGH",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"Infinity";return["Math.min(Math.max("+
|
||||
c+", "+d+"), "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_random_int$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";a=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return[b.provideFunction_("mathRandomInt",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(a, b) {
|
||||
if (a > b) {
|
||||
// Swap a and b to ensure a is smaller.
|
||||
var c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
return Math.floor(Math.random() * (b - a + 1) + a);
|
||||
}
|
||||
`)+"("+c+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_random_float$$module$build$src$generators$javascript$math=function(a,b){return["Math.random()",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},math_atan2$$module$build$src$generators$javascript$math=function(a,b){const c=b.valueToCode(a,"X",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0";return["Math.atan2("+(b.valueToCode(a,"Y",
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"0")+", "+c+") / Math.PI * 180",Order$$module$build$src$generators$javascript$javascript_generator.DIVISION]},procedures_defreturn$$module$build$src$generators$javascript$procedures=function(a,b){const c=b.getProcedureName(a.getFieldValue("NAME"));var d="";b.STATEMENT_PREFIX&&(d+=b.injectId(b.STATEMENT_PREFIX,a));b.STATEMENT_SUFFIX&&(d+=b.injectId(b.STATEMENT_SUFFIX,a));d&&(d=b.prefixLines(d,b.INDENT));let e="";b.INFINITE_LOOP_TRAP&&
|
||||
(e=b.prefixLines(b.injectId(b.INFINITE_LOOP_TRAP,a),b.INDENT));let f="";a.getInput("STACK")&&(f=b.statementToCode(a,"STACK"));let g="";a.getInput("RETURN")&&(g=b.valueToCode(a,"RETURN",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"");let h="";f&&g&&(h=d);g&&(g=b.INDENT+"return "+g+";\n");const k=[],l=a.getVars();for(let m=0;m<l.length;m++)k[m]=b.getVariableName(l[m]);d="function "+c+"("+k.join(", ")+") {\n"+d+e+f+h+g+"}";d=b.scrub_(a,d);b.definitions_["%"+c]=d;return null},
|
||||
procedures_callreturn$$module$build$src$generators$javascript$procedures=function(a,b){const c=b.getProcedureName(a.getFieldValue("NAME")),d=[],e=a.getVars();for(let f=0;f<e.length;f++)d[f]=b.valueToCode(a,"ARG"+f,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null";return[c+"("+d.join(", ")+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},procedures_callnoreturn$$module$build$src$generators$javascript$procedures=function(a,b){return b.forBlock.procedures_callreturn(a,
|
||||
b)[0]+";\n"},procedures_ifreturn$$module$build$src$generators$javascript$procedures=function(a,b){let c="if ("+(b.valueToCode(a,"CONDITION",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"false")+") {\n";b.STATEMENT_SUFFIX&&(c+=b.prefixLines(b.injectId(b.STATEMENT_SUFFIX,a),b.INDENT));a.hasReturnValue_?(a=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"null",c+=b.INDENT+"return "+a+";\n"):c+=b.INDENT+"return;\n";return c+
|
||||
"}\n"},text$$module$build$src$generators$javascript$text=function(a,b){return[b.quote_(a.getFieldValue("TEXT")),Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},text_join$$module$build$src$generators$javascript$text=function(a,b){switch(a.itemCount_){case 0:return["''",Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC];case 1:return a=b.valueToCode(a,"ADD0",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''",forceString$$module$build$src$generators$javascript$text(a);
|
||||
case 2:var c=b.valueToCode(a,"ADD0",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"ADD1",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[forceString$$module$build$src$generators$javascript$text(c)[0]+" + "+forceString$$module$build$src$generators$javascript$text(a)[0],Order$$module$build$src$generators$javascript$javascript_generator.ADDITION];default:c=Array(a.itemCount_);for(let d=0;d<a.itemCount_;d++)c[d]=
|
||||
b.valueToCode(a,"ADD"+d,Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return["["+c.join(",")+"].join('')",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]}},text_append$$module$build$src$generators$javascript$text=function(a,b){const c=b.getVariableName(a.getFieldValue("VAR"));a=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return c+" += "+forceString$$module$build$src$generators$javascript$text(a)[0]+
|
||||
";\n"},text_length$$module$build$src$generators$javascript$text=function(a,b){return[(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".length",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER]},text_isEmpty$$module$build$src$generators$javascript$text=function(a,b){return["!"+(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".length",Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT]},
|
||||
text_indexOf$$module$build$src$generators$javascript$text=function(a,b){const c=a.getFieldValue("END")==="FIRST"?"indexOf":"lastIndexOf",d=b.valueToCode(a,"FIND",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";b=(b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+"."+c+"("+d+")";return a.workspace.options.oneBasedIndex?[b+" + 1",Order$$module$build$src$generators$javascript$javascript_generator.ADDITION]:[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},
|
||||
text_charAt$$module$build$src$generators$javascript$text=function(a,b){const c=a.getFieldValue("WHERE")||"FROM_START",d=b.valueToCode(a,"VALUE",c==="RANDOM"?Order$$module$build$src$generators$javascript$javascript_generator.NONE:Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''";switch(c){case "FIRST":return[d+".charAt(0)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "LAST":return[d+".slice(-1)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];
|
||||
case "FROM_START":return a=b.getAdjusted(a,"AT"),[d+".charAt("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "FROM_END":return a=b.getAdjusted(a,"AT",1,!0),[d+".slice("+a+").charAt(0)",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL];case "RANDOM":return[b.provideFunction_("textRandomLetter",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(text) {
|
||||
var x = Math.floor(Math.random() * text.length);
|
||||
return text[x];
|
||||
}
|
||||
`)+"("+d+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]}throw Error("Unhandled option (text_charAt).");},text_getSubstring$$module$build$src$generators$javascript$text=function(a,b){var c={FIRST:"First",LAST:"Last",FROM_START:"FromStart",FROM_END:"FromEnd"};const d=a.getFieldValue("WHERE1"),e=a.getFieldValue("WHERE2");var f=d!=="FROM_END"&&d!=="LAST"&&e!=="FROM_END"&&e!=="LAST",g=b.valueToCode(a,"STRING",f?Order$$module$build$src$generators$javascript$javascript_generator.MEMBER:
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";if(d==="FIRST"&&e==="LAST")return[g,Order$$module$build$src$generators$javascript$javascript_generator.NONE];if(g.match(/^'?\w+'?$/)||f){switch(d){case "FROM_START":c=b.getAdjusted(a,"AT1");break;case "FROM_END":c=b.getAdjusted(a,"AT1",1,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);c=g+".length - "+c;break;case "FIRST":c="0";break;default:throw Error("Unhandled option (text_getSubstring).");
|
||||
}switch(e){case "FROM_START":b=b.getAdjusted(a,"AT2",1);break;case "FROM_END":b=b.getAdjusted(a,"AT2",0,!1,Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION);b=g+".length - "+b;break;case "LAST":b=g+".length";break;default:throw Error("Unhandled option (text_getSubstring).");}g=g+".slice("+c+", "+b+")"}else f=b.getAdjusted(a,"AT1"),a=b.getAdjusted(a,"AT2"),g=b.provideFunction_("subsequence"+c[d]+c[e],`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(sequence${d==="FROM_END"||d==="FROM_START"?", at1":""}${e==="FROM_END"||e==="FROM_START"?", at2":""}) {
|
||||
var start = ${getSubstringIndex$$module$build$src$generators$javascript$text("sequence",d,"at1")};
|
||||
var end = ${getSubstringIndex$$module$build$src$generators$javascript$text("sequence",e,"at2")} + 1;
|
||||
return sequence.slice(start, end);
|
||||
}
|
||||
`)+"("+g+(d==="FROM_END"||d==="FROM_START"?", "+f:"")+(e==="FROM_END"||e==="FROM_START"?", "+a:"")+")";return[g,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_changeCase$$module$build$src$generators$javascript$text=function(a,b){const c={UPPERCASE:".toUpperCase()",LOWERCASE:".toLowerCase()",TITLECASE:null}[a.getFieldValue("CASE")];a=b.valueToCode(a,"TEXT",c?Order$$module$build$src$generators$javascript$javascript_generator.MEMBER:Order$$module$build$src$generators$javascript$javascript_generator.NONE)||
|
||||
"''";return[c?a+c:b.provideFunction_("textToTitleCase",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(str) {
|
||||
return str.replace(/\\S+/g,
|
||||
function(txt) {return txt[0].toUpperCase() + txt.substring(1).toLowerCase();});
|
||||
}
|
||||
`)+"("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_trim$$module$build$src$generators$javascript$text=function(a,b){const c={LEFT:".replace(/^[\\s\\xa0]+/, '')",RIGHT:".replace(/[\\s\\xa0]+$/, '')",BOTH:".trim()"}[a.getFieldValue("MODE")];return[(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+c,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_print$$module$build$src$generators$javascript$text=
|
||||
function(a,b){return"window.alert("+(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''")+");\n"},text_prompt_ext$$module$build$src$generators$javascript$text=function(a,b){b="window.prompt("+(a.getField("TEXT")?b.quote_(a.getFieldValue("TEXT")):b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''")+")";a.getFieldValue("TYPE")==="NUMBER"&&(b="Number("+b+")");return[b,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},
|
||||
text_count$$module$build$src$generators$javascript$text=function(a,b){const c=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"SUB",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[b.provideFunction_("textCount",`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle) {
|
||||
if (needle.length === 0) {
|
||||
return haystack.length + 1;
|
||||
} else {
|
||||
return haystack.split(needle).length - 1;
|
||||
}
|
||||
}
|
||||
`)+"("+c+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_replace$$module$build$src$generators$javascript$text=function(a,b){const c=b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''",d=b.valueToCode(a,"FROM",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";a=b.valueToCode(a,"TO",Order$$module$build$src$generators$javascript$javascript_generator.NONE)||"''";return[b.provideFunction_("textReplace",
|
||||
`
|
||||
function ${b.FUNCTION_NAME_PLACEHOLDER_}(haystack, needle, replacement) {
|
||||
needle = needle.replace(/([-()\\[\\]{}+?*.$\\^|,:#<!\\\\])/g, '\\\\$1')
|
||||
.replace(/\\x08/g, '\\\\x08');
|
||||
return haystack.replace(new RegExp(needle, 'g'), replacement);
|
||||
}
|
||||
`)+"("+c+", "+d+", "+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},text_reverse$$module$build$src$generators$javascript$text=function(a,b){return[(b.valueToCode(a,"TEXT",Order$$module$build$src$generators$javascript$javascript_generator.MEMBER)||"''")+".split('').reverse().join('')",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},variables_get$$module$build$src$generators$javascript$variables=function(a,b){return[b.getVariableName(a.getFieldValue("VAR")),
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]},variables_set$$module$build$src$generators$javascript$variables=function(a,b){const c=b.valueToCode(a,"VALUE",Order$$module$build$src$generators$javascript$javascript_generator.ASSIGNMENT)||"0";return b.getVariableName(a.getFieldValue("VAR"))+" = "+c+";\n"},Order$$module$build$src$generators$javascript$javascript_generator;
|
||||
(function(a){a[a.ATOMIC=0]="ATOMIC";a[a.NEW=1.1]="NEW";a[a.MEMBER=1.2]="MEMBER";a[a.FUNCTION_CALL=2]="FUNCTION_CALL";a[a.INCREMENT=3]="INCREMENT";a[a.DECREMENT=3]="DECREMENT";a[a.BITWISE_NOT=4.1]="BITWISE_NOT";a[a.UNARY_PLUS=4.2]="UNARY_PLUS";a[a.UNARY_NEGATION=4.3]="UNARY_NEGATION";a[a.LOGICAL_NOT=4.4]="LOGICAL_NOT";a[a.TYPEOF=4.5]="TYPEOF";a[a.VOID=4.6]="VOID";a[a.DELETE=4.7]="DELETE";a[a.AWAIT=4.8]="AWAIT";a[a.EXPONENTIATION=5]="EXPONENTIATION";a[a.MULTIPLICATION=5.1]="MULTIPLICATION";a[a.DIVISION=
|
||||
5.2]="DIVISION";a[a.MODULUS=5.3]="MODULUS";a[a.SUBTRACTION=6.1]="SUBTRACTION";a[a.ADDITION=6.2]="ADDITION";a[a.BITWISE_SHIFT=7]="BITWISE_SHIFT";a[a.RELATIONAL=8]="RELATIONAL";a[a.IN=8]="IN";a[a.INSTANCEOF=8]="INSTANCEOF";a[a.EQUALITY=9]="EQUALITY";a[a.BITWISE_AND=10]="BITWISE_AND";a[a.BITWISE_XOR=11]="BITWISE_XOR";a[a.BITWISE_OR=12]="BITWISE_OR";a[a.LOGICAL_AND=13]="LOGICAL_AND";a[a.LOGICAL_OR=14]="LOGICAL_OR";a[a.CONDITIONAL=15]="CONDITIONAL";a[a.ASSIGNMENT=16]="ASSIGNMENT";a[a.YIELD=17]="YIELD";
|
||||
a[a.COMMA=18]="COMMA";a[a.NONE=99]="NONE"})(Order$$module$build$src$generators$javascript$javascript_generator||(Order$$module$build$src$generators$javascript$javascript_generator={}));
|
||||
var JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator=class extends $.CodeGenerator$$module$build$src$core$generator{constructor(a="JavaScript"){super(a);this.ORDER_OVERRIDES=[[Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL,Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],[Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL],
|
||||
[Order$$module$build$src$generators$javascript$javascript_generator.MEMBER,Order$$module$build$src$generators$javascript$javascript_generator.MEMBER],[Order$$module$build$src$generators$javascript$javascript_generator.MEMBER,Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_NOT],[Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION,
|
||||
Order$$module$build$src$generators$javascript$javascript_generator.MULTIPLICATION],[Order$$module$build$src$generators$javascript$javascript_generator.ADDITION,Order$$module$build$src$generators$javascript$javascript_generator.ADDITION],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_AND],[Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR,Order$$module$build$src$generators$javascript$javascript_generator.LOGICAL_OR]];
|
||||
this.isInitialized=!1;for(const b in Order$$module$build$src$generators$javascript$javascript_generator)a=Order$$module$build$src$generators$javascript$javascript_generator[b],typeof a!=="string"&&(this["ORDER_"+b]=a);this.addReservedWords("break,case,catch,class,const,continue,debugger,default,delete,do,else,export,extends,finally,for,function,if,import,in,instanceof,new,return,super,switch,this,throw,try,typeof,var,void,while,with,yield,enum,implements,interface,let,package,private,protected,public,static,await,null,true,false,arguments,"+
|
||||
Object.getOwnPropertyNames(globalThis).join(","))}init(a){super.init(a);this.nameDB_?this.nameDB_.reset():this.nameDB_=new $.Names$$module$build$src$core$names(this.RESERVED_WORDS_);this.nameDB_.setVariableMap(a.getVariableMap());this.nameDB_.populateVariables(a);this.nameDB_.populateProcedures(a);const b=[];var c=$.allDeveloperVariables$$module$build$src$core$variables(a);for(let d=0;d<c.length;d++)b.push(this.nameDB_.getName(c[d],$.NameType$$module$build$src$core$names.DEVELOPER_VARIABLE));a=$.allUsedVarModels$$module$build$src$core$variables(a);
|
||||
for(c=0;c<a.length;c++)b.push(this.nameDB_.getName(a[c].getId(),$.NameType$$module$build$src$core$names.VARIABLE));b.length&&(this.definitions_.variables="var "+b.join(", ")+";");this.isInitialized=!0}finish(a){const b=Object.values(this.definitions_);super.finish(a);this.isInitialized=!1;this.nameDB_.reset();return b.join("\n\n")+"\n\n\n"+a}scrubNakedValue(a){return a+";\n"}quote_(a){a=a.replace(/\\/g,"\\\\").replace(/\n/g,"\\\n").replace(/'/g,"\\'");return"'"+a+"'"}multiline_quote_(a){return a.split(/\n/g).map(this.quote_).join(" + '\\n' +\n")}scrub_(a,
|
||||
b,c=!1){let d="";if(!a.outputConnection||!a.outputConnection.targetConnection){var e=a.getCommentText();e&&(e=$.wrap$$module$build$src$core$utils$string(e,this.COMMENT_WRAP-3),d+=this.prefixLines(e+"\n","// "));for(let f=0;f<a.inputList.length;f++)a.inputList[f].type===$.inputTypes$$module$build$src$core$inputs$input_types.VALUE&&(e=a.inputList[f].connection.targetBlock())&&(e=this.allNestedComments(e))&&(d+=this.prefixLines(e,"// "))}a=a.nextConnection&&a.nextConnection.targetBlock();c=c?"":this.blockToCode(a);
|
||||
return d+b+c}getAdjusted(a,b,c=0,d=!1,e=Order$$module$build$src$generators$javascript$javascript_generator.NONE){a.workspace.options.oneBasedIndex&&c--;const f=a.workspace.options.oneBasedIndex?"1":"0";let g=e;c>0?g=Order$$module$build$src$generators$javascript$javascript_generator.ADDITION:c<0?g=Order$$module$build$src$generators$javascript$javascript_generator.SUBTRACTION:d&&(g=Order$$module$build$src$generators$javascript$javascript_generator.UNARY_NEGATION);a=this.valueToCode(a,b,g)||f;if(c===
|
||||
0&&!d)return a;if($.isNumber$$module$build$src$core$utils$string(a))return a=String(Number(a)+c),d&&(a=String(-Number(a))),a;c>0?a=`${a} + ${c}`:c<0&&(a=`${a} - ${-c}`);d&&(a=c?`-(${a})`:`-${a}`);Math.floor(e)>=Math.floor(g)&&(a=`(${a})`);return a}},module$build$src$generators$javascript$javascript_generator={};module$build$src$generators$javascript$javascript_generator.JavascriptGenerator=JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator;
|
||||
module$build$src$generators$javascript$javascript_generator.Order=Order$$module$build$src$generators$javascript$javascript_generator;var getSubstringIndex$$module$build$src$generators$javascript$lists=function(a,b,c){return b==="FIRST"?"0":b==="FROM_END"?a+".length - 1 - "+c:b==="LAST"?a+".length - 1":c},module$build$src$generators$javascript$lists={};module$build$src$generators$javascript$lists.lists_create_empty=lists_create_empty$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_create_with=lists_create_with$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_getIndex=lists_getIndex$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_getSublist=lists_getSublist$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_indexOf=lists_indexOf$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_isEmpty=lists_isEmpty$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_length=lists_length$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_repeat=lists_repeat$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_reverse=lists_reverse$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_setIndex=lists_setIndex$$module$build$src$generators$javascript$lists;
|
||||
module$build$src$generators$javascript$lists.lists_sort=lists_sort$$module$build$src$generators$javascript$lists;module$build$src$generators$javascript$lists.lists_split=lists_split$$module$build$src$generators$javascript$lists;var controls_ifelse$$module$build$src$generators$javascript$logic=controls_if$$module$build$src$generators$javascript$logic,module$build$src$generators$javascript$logic={};module$build$src$generators$javascript$logic.controls_if=controls_if$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.controls_ifelse=controls_if$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_boolean=logic_boolean$$module$build$src$generators$javascript$logic;
|
||||
module$build$src$generators$javascript$logic.logic_compare=logic_compare$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_negate=logic_negate$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_null=logic_null$$module$build$src$generators$javascript$logic;module$build$src$generators$javascript$logic.logic_operation=logic_operation$$module$build$src$generators$javascript$logic;
|
||||
module$build$src$generators$javascript$logic.logic_ternary=logic_ternary$$module$build$src$generators$javascript$logic;var controls_repeat$$module$build$src$generators$javascript$loops=controls_repeat_ext$$module$build$src$generators$javascript$loops,module$build$src$generators$javascript$loops={};module$build$src$generators$javascript$loops.controls_flow_statements=controls_flow_statements$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_for=controls_for$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_forEach=controls_forEach$$module$build$src$generators$javascript$loops;
|
||||
module$build$src$generators$javascript$loops.controls_repeat=controls_repeat_ext$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_repeat_ext=controls_repeat_ext$$module$build$src$generators$javascript$loops;module$build$src$generators$javascript$loops.controls_whileUntil=controls_whileUntil$$module$build$src$generators$javascript$loops;var math_round$$module$build$src$generators$javascript$math=math_single$$module$build$src$generators$javascript$math,math_trig$$module$build$src$generators$javascript$math=math_single$$module$build$src$generators$javascript$math,module$build$src$generators$javascript$math={};module$build$src$generators$javascript$math.math_arithmetic=math_arithmetic$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_atan2=math_atan2$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_change=math_change$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_constant=math_constant$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_constrain=math_constrain$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_modulo=math_modulo$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_number=math_number$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_number_property=math_number_property$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_on_list=math_on_list$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_random_float=math_random_float$$module$build$src$generators$javascript$math;
|
||||
module$build$src$generators$javascript$math.math_random_int=math_random_int$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_round=math_single$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_single=math_single$$module$build$src$generators$javascript$math;module$build$src$generators$javascript$math.math_trig=math_single$$module$build$src$generators$javascript$math;var procedures_defnoreturn$$module$build$src$generators$javascript$procedures=procedures_defreturn$$module$build$src$generators$javascript$procedures,module$build$src$generators$javascript$procedures={};module$build$src$generators$javascript$procedures.procedures_callnoreturn=procedures_callnoreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_callreturn=procedures_callreturn$$module$build$src$generators$javascript$procedures;
|
||||
module$build$src$generators$javascript$procedures.procedures_defnoreturn=procedures_defreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_defreturn=procedures_defreturn$$module$build$src$generators$javascript$procedures;module$build$src$generators$javascript$procedures.procedures_ifreturn=procedures_ifreturn$$module$build$src$generators$javascript$procedures;var strRegExp$$module$build$src$generators$javascript$text=/^\s*'([^']|\\')*'\s*$/,forceString$$module$build$src$generators$javascript$text=function(a){return strRegExp$$module$build$src$generators$javascript$text.test(a)?[a,Order$$module$build$src$generators$javascript$javascript_generator.ATOMIC]:["String("+a+")",Order$$module$build$src$generators$javascript$javascript_generator.FUNCTION_CALL]},getSubstringIndex$$module$build$src$generators$javascript$text=function(a,b,c){return b==="FIRST"?"0":
|
||||
b==="FROM_END"?a+".length - 1 - "+c:b==="LAST"?a+".length - 1":c},text_prompt$$module$build$src$generators$javascript$text=text_prompt_ext$$module$build$src$generators$javascript$text,module$build$src$generators$javascript$text={};module$build$src$generators$javascript$text.text=text$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_append=text_append$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_changeCase=text_changeCase$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_charAt=text_charAt$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_count=text_count$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_getSubstring=text_getSubstring$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_indexOf=text_indexOf$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_isEmpty=text_isEmpty$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_join=text_join$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_length=text_length$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_print=text_print$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_prompt=text_prompt_ext$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_prompt_ext=text_prompt_ext$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_replace=text_replace$$module$build$src$generators$javascript$text;
|
||||
module$build$src$generators$javascript$text.text_reverse=text_reverse$$module$build$src$generators$javascript$text;module$build$src$generators$javascript$text.text_trim=text_trim$$module$build$src$generators$javascript$text;var module$build$src$generators$javascript$variables={};module$build$src$generators$javascript$variables.variables_get=variables_get$$module$build$src$generators$javascript$variables;module$build$src$generators$javascript$variables.variables_set=variables_set$$module$build$src$generators$javascript$variables;var module$build$src$generators$javascript$variables_dynamic={};module$build$src$generators$javascript$variables_dynamic.variables_get_dynamic=variables_get$$module$build$src$generators$javascript$variables;module$build$src$generators$javascript$variables_dynamic.variables_set_dynamic=variables_set$$module$build$src$generators$javascript$variables;var javascriptGenerator$$module$build$src$generators$javascript=new JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator,generators$$module$build$src$generators$javascript=Object.assign({},module$build$src$generators$javascript$lists,module$build$src$generators$javascript$logic,module$build$src$generators$javascript$loops,module$build$src$generators$javascript$math,module$build$src$generators$javascript$procedures,module$build$src$generators$javascript$text,module$build$src$generators$javascript$variables,
|
||||
module$build$src$generators$javascript$variables_dynamic);for(const a in generators$$module$build$src$generators$javascript)javascriptGenerator$$module$build$src$generators$javascript.forBlock[a]=generators$$module$build$src$generators$javascript[a];var module$build$src$generators$javascript={};module$build$src$generators$javascript.JavascriptGenerator=JavascriptGenerator$$module$build$src$generators$javascript$javascript_generator;module$build$src$generators$javascript.Order=Order$$module$build$src$generators$javascript$javascript_generator;
|
||||
module$build$src$generators$javascript.javascriptGenerator=javascriptGenerator$$module$build$src$generators$javascript;
|
||||
module$build$src$generators$javascript.__namespace__=$;
|
||||
return module$build$src$generators$javascript;
|
||||
}));
|
||||
|
||||
|
||||
//# sourceMappingURL=javascript_compressed.js.map
|
||||
575
crumbblocks/lipo_6s_charger.html
Normal file
575
crumbblocks/lipo_6s_charger.html
Normal file
@@ -0,0 +1,575 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Reject bei Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer, ~3,2/Z) • 22,2 V (Nominal) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20, // Full
|
||||
v_storage: 3.80, // Storage
|
||||
v_hv: 4.35, // HV
|
||||
v_min: 3.20, // "ehrlich leer"
|
||||
drift_reject: 0.30, // harte Zellabweichung ablehnen
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01, // V/Z pro Tick (vereinfacht)
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output Helpers
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// === Patch 1: text_print -> Panel & Alerts abfangen ===
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// XML util
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// === DOC XML ===
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z → 25,2V • Storage 3,80V/Z → 22,8V • HV 4,35V/Z → 26,1V • Ende ~3,20V/Z → 19,2V.</field></block></value></block>
|
||||
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance). Packs mit harten Unterschieden nicht nutzen.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T1: Storage (3.80V/Z) – Entladen bis Ziel, mit Clamp ===
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</next></next></next></next>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field>
|
||||
<value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="330">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="800">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="8"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* siehe 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T2: Full (4.20V/Z) – Laden bis Ziel, mit Clamp ===
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</next></next></next></next>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field>
|
||||
<value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="320">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="790">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="8"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* siehe 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T3: Reject bei Drift >= 0.30V ===
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="350">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="10"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode": "safety_check", "max_v": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">max_v</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "min_v": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">min_v</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "delta": </field></block></value>
|
||||
<value name="ADD5"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "stop_reason": "</field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">", "advice": "Pack nicht verwenden."</field></block></value>
|
||||
<value name="ADD9"><block type="text"><field name="TEXT"> }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === T4: HV 4.35V/Z – Laden bis Ziel, mit Clamp ===
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable>
|
||||
</variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
|
||||
<block type="controls_whileUntil" x="20" y="400">
|
||||
<field name="MODE">WHILE</field>
|
||||
<value name="BOOL">
|
||||
<block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_forEach">
|
||||
<field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
<next>
|
||||
<block type="lists_setIndex">
|
||||
<mutation at="true"></mutation>
|
||||
<field name="MODE">SET</field>
|
||||
<field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
|
||||
<block type="text_print" x="20" y="820">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Zufalls-Startwerte (nur Inspektion) ===
|
||||
function randomDemoXml(){
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// === Buttons ===
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Reject bei Drift ≥ 0,30V. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{ loadXml(randomDemoXml()); setOutput('🎲 Zufallspack geladen.'); };
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === Patch 2: Watchdog im Run-Handler ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
code = guardHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit (sichtbar für Kids/Trainer) -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (<i>≥0,30 V</i>) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
453
crumbblocks/lipo_6s_charger_sim_safe_v2.html
Normal file
453
crumbblocks/lipo_6s_charger_sim_safe_v2.html
Normal file
@@ -0,0 +1,453 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v3)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v3)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="320"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="790"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="400"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="820"><value name="TEXT"><block type="text_join"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
function randomDemoXml(){
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{ loadXml(randomDemoXml()); setOutput('🎲 Zufallspack geladen.'); };
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
code = guardHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
322
crumbblocks/lipo_6s_charger_sim_safe_v4.html
Normal file
322
crumbblocks/lipo_6s_charger_sim_safe_v4.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v4)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v4)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos (kompakt gehalten) ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = /* wie T1, nur 4.2V + dv=+0.01 + MIN/Clamp im If */;
|
||||
const T3_XML = /* Drift-Check */;
|
||||
const T4_XML = /* HV 4.35V */;
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
|
||||
// 1) Watchdog in jede while(...)
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
// 2) Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
|
||||
// 3) Erzwinge Schreibvorgang + Clamp nach "new_v = cells[...] + dv;"
|
||||
// - Variante mit 1-basiertem Index: cells[(i - 1)]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
// - Variante mit 0-basiertem Index: cells[i]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
322
crumbblocks/lipo_6s_charger_sim_safe_v5.html
Normal file
322
crumbblocks/lipo_6s_charger_sim_safe_v5.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (SAFE v4)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root{--bg:#1e1e1e;--panel:#121212;--txt:#e8e8e8;--accent:#4caf50;--muted:#a0a0a0;--line:#2a2a2a}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (SAFE v4)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear" class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG (Gedankenstütze) ===
|
||||
const CFG = {
|
||||
cells: 6,
|
||||
v_full: 4.20,
|
||||
v_storage: 3.80,
|
||||
v_hv: 4.35,
|
||||
v_min: 3.20,
|
||||
drift_reject: 0.30,
|
||||
tick_s: 10,
|
||||
dv_charge: 0.01,
|
||||
dv_discharge: -0.01,
|
||||
capacity_mAh: 2200,
|
||||
c_rate: 1.0
|
||||
};
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
cfgEl.textContent = 'CFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen, die in den Demos genutzt werden
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// Output/Print
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t){ $('#output').textContent = t }
|
||||
function appendOutput(t){ const el=$('#output'); el.textContent += (el.textContent.endsWith('\n')?'':'\n') + t; el.scrollTop = el.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
|
||||
// text_print → Panel & Alerts abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// *** LISTEN-PATCH (1-basiert → korrektes JS-Indexing & Schreiben) ***
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos (kompakt gehalten) ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = /* wie T1, nur 4.2V + dv=+0.01 + MIN/Clamp im If */;
|
||||
const T3_XML = /* Drift-Check */;
|
||||
const T4_XML = /* HV 4.35V */;
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z (nur Demo). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// === RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix ===
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
|
||||
// 1) Watchdog in jede while(...)
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
|
||||
// 2) Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
|
||||
// 3) Erzwinge Schreibvorgang + Clamp nach "new_v = cells[...] + dv;"
|
||||
// - Variante mit 1-basiertem Index: cells[(i - 1)]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
// - Variante mit 0-basiertem Index: cells[i]
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
409
crumbblocks/lipo_6s_charger_sim_safe_v6.html
Normal file
409
crumbblocks/lipo_6s_charger_sim_safe_v6.html
Normal file
@@ -0,0 +1,409 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (DIAG v6)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
:root{--bg:#0f1115;--panel:#11151a;--txt:#e6edf3;--accent:#4caf50;--muted:#9aa4b2;--line:#21262d;--bad:#ff4d4f}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%;border:1px solid var(--line);border-radius:10px}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto;border:1px solid var(--line)}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto;border:1px solid var(--line)}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
.fail{color:var(--bad);font-weight:700}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (DIAG v6)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear"class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg">Diag lädt …</div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z).
|
||||
<br><b>Nur Simulation / Didaktik. Keine echte Ladefunktion.</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX (wird erst genutzt, wenn Blockly geladen ist) -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// ---------- Diagnose: Fehler ins Panel ----------
|
||||
const out = document.getElementById('output');
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
function setOutput(t){ out.textContent = t }
|
||||
function appendOutput(t){ out.textContent += (out.textContent.endsWith('\n')?'':'\n') + t; out.scrollTop = out.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
window.addEventListener('error', (e)=> appendOutput('❌ JS-Error: ' + e.message));
|
||||
window.addEventListener('unhandledrejection', (e)=> appendOutput('❌ Promise-Rejection: ' + (e.reason && e.reason.message ? e.reason.message : e.reason)));
|
||||
|
||||
// ---------- Loader: erst lokal, dann CDN ----------
|
||||
const CDN = 'https://unpkg.com';
|
||||
const LOAD = [
|
||||
['/crumbblocks/vendor/blockly/blockly.min.js', `${CDN}/blockly/blockly.min.js`],
|
||||
['/crumbblocks/vendor/blockly/javascript.min.js', `${CDN}/blockly/javascript.min.js`],
|
||||
['/crumbblocks/vendor/blockly/msg/de.js', `${CDN}/blockly/msg/de.js`],
|
||||
];
|
||||
function loadScriptPair([localSrc, cdnSrc]){
|
||||
return new Promise((resolve, reject)=>{
|
||||
const s = document.createElement('script');
|
||||
s.src = localSrc;
|
||||
s.onload = ()=> resolve({src:localSrc, ok:true, via:'local'});
|
||||
s.onerror = ()=>{
|
||||
const c = document.createElement('script');
|
||||
c.src = cdnSrc;
|
||||
c.onload = ()=> resolve({src:cdnSrc, ok:true, via:'cdn'});
|
||||
c.onerror = ()=> reject(new Error('Laden fehlgeschlagen: ' + localSrc + ' und ' + cdnSrc));
|
||||
document.head.appendChild(c);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Self-Test ----------
|
||||
async function selfTest(){
|
||||
const diag = { js_ok:true, blockly_loaded:false, eval_ok:null, scripts:[], csp_note:null };
|
||||
// Lade Reihenfolge wahren
|
||||
for (const p of LOAD){
|
||||
try {
|
||||
const res = await loadScriptPair(p);
|
||||
diag.scripts.push(res);
|
||||
} catch (e) {
|
||||
diag.scripts.push({src:p, ok:false, via:'none', err:String(e)});
|
||||
}
|
||||
}
|
||||
diag.blockly_loaded = !!window.Blockly;
|
||||
try { new Function('return 42')(); diag.eval_ok = true; }
|
||||
catch(e){ diag.eval_ok = 'blocked: ' + e.message; diag.csp_note = 'CSP erlaubt kein unsafe-eval. Siehe Hinweise unten.'; }
|
||||
|
||||
const CFG = {
|
||||
cells: 6, v_full:4.20, v_storage:3.80, v_hv:4.35, v_min:3.20,
|
||||
drift_reject:0.30, tick_s:10, dv_charge:0.01, dv_discharge:-0.01,
|
||||
capacity_mAh:2200, c_rate:1.0
|
||||
};
|
||||
cfgEl.textContent = 'Diag:\n' + JSON.stringify(diag, null, 2) + '\n\nCFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
if (!diag.blockly_loaded){
|
||||
appendOutput('❌ Blockly nicht geladen. Prüfe /crumbblocks/vendor/... oder Internet/CSP.');
|
||||
appendOutput('Tipp: Siehe „Lokale Vendor-Dateien“ unten.');
|
||||
return;
|
||||
}
|
||||
bootBlockly(); // erst jetzt initialisieren
|
||||
}
|
||||
|
||||
// ---------- Blockly-Init (nach bestandenem Self-Test) ----------
|
||||
function bootBlockly(){
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// print ins Panel + Alert abfangen
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert abgefangen: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// 1-basige Listen sauber generieren
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// Helpers
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// Demos (kompakt)
|
||||
const DOC_XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T1_XML = `<!-- Storage -->
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T2_XML = T1_XML
|
||||
.replace('storage','charge')
|
||||
.replace('3.8','4.2')
|
||||
.replace('"-0.01"','"0.01"')
|
||||
.replace('storage_reached','full_reached')
|
||||
.replace('"GT"','"LT"');
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>`.trim();
|
||||
|
||||
const T4_XML = T2_XML
|
||||
.replace('charge','hv_charge')
|
||||
.replace('4.2','4.35')
|
||||
.replace('full_reached','hv_reached')
|
||||
.replace(/3\.7[0-9]?/g,'3.96'); // Start näher an HV
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel + Writeback&Clamp-Fix
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
}
|
||||
|
||||
// Start
|
||||
selfTest();
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Das ist nur eine <b>Simulation</b>. Echte LiPos nur mit geeignetem Ladegerät & Balancer laden.
|
||||
Packs mit starker Zellabweichung (≥0,30 V) nicht verwenden. Keine echten Ströme werden hier geschaltet.
|
||||
</div>
|
||||
|
||||
<!-- Hinweise / Fallback -->
|
||||
<div style="padding:.6rem .8rem;color:var(--muted)">
|
||||
<b>Lokale Vendor-Dateien (empfohlen, falls CDN/CSP blockt):</b><br/>
|
||||
<code>mkdir -p /var/www/crumbblocks/vendor/blockly</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/blockly.min.js https://unpkg.com/blockly/blockly.min.js</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/javascript.min.js https://unpkg.com/blockly/javascript.min.js</code><br/>
|
||||
<code>curl -L -o /var/www/crumbblocks/vendor/blockly/msg/de.js https://unpkg.com/blockly/msg/de.js</code><br/><br/>
|
||||
|
||||
<b>CSP (falls Eval/Inline blockiert → „nix passiert“):</b><br/>
|
||||
Erweitere Header minimal für die Simulation:
|
||||
<pre>Content-Security-Policy: default-src 'self';
|
||||
script-src 'self' https://unpkg.com 'unsafe-inline' 'unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self';
|
||||
img-src 'self' data:;
|
||||
font-src 'self' data:;</pre>
|
||||
(Nur für diese Lernseite. Produktion später strenger machen.)
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
500
crumbblocks/lipo_6s_charger_sim_safe_v7.html
Normal file
500
crumbblocks/lipo_6s_charger_sim_safe_v7.html
Normal file
@@ -0,0 +1,500 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🔋 6S LiPo Charger – Blockly Simulation (DIAG v7)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
:root{--bg:#0f1115;--panel:#11151a;--txt:#e6edf3;--accent:#4caf50;--muted:#9aa4b2;--line:#21262d;--bad:#ff4d4f}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0;background:var(--bg);color:var(--txt);font-family:ui-monospace, Menlo, monospace}
|
||||
header{display:flex;gap:.5rem;align-items:center;padding:.6rem .8rem;border-bottom:1px solid var(--line)}
|
||||
header h2{margin:0;font-size:1rem;font-weight:700}
|
||||
header .sp{flex:1}
|
||||
button{padding:.55rem .8rem;background:var(--accent);color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer}
|
||||
.btn-sec{background:#3c99dc}.btn-warn{background:#e67e22}.btn-gray{background:#4d4d4d}
|
||||
#wrap{display:flex;gap:.8rem;height:calc(100vh - 56px);padding:.6rem .8rem}
|
||||
#left{flex:3;display:flex;flex-direction:column;gap:.6rem}
|
||||
#right{flex:2;display:flex;flex-direction:column;gap:.6rem}
|
||||
#blocklyDiv{height:60vh;width:100%;border:1px solid var(--line);border-radius:10px}
|
||||
#output{flex:1;min-height:28vh;background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;word-break:break-word;overflow:auto;border:1px solid var(--line)}
|
||||
#cfg{background:var(--panel);padding:10px;border-radius:10px;white-space:pre-wrap;overflow:auto;border:1px solid var(--line)}
|
||||
.hint{color:var(--muted);font-size:.9rem}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • 6S LiPo Charger – Simulation (DIAG v7)</h2>
|
||||
<div class="sp"></div>
|
||||
<button id="btnDoc" class="btn-gray">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-sec" title="Storage 3,8V/Z">T1</button>
|
||||
<button id="btnT2" class="btn-sec" title="Full 4,2V/Z">T2</button>
|
||||
<button id="btnT3" class="btn-sec" title="Safety: Drift">T3</button>
|
||||
<button id="btnT4" class="btn-sec" title="HV 4,35V/Z">T4</button>
|
||||
<button id="btnRnd" class="btn-warn">🎲 Zufall</button>
|
||||
<button id="btnClear"class="btn-gray">🗑️ Neu</button>
|
||||
<button id="btnRun">▶️ Ausführen</button>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“ / Testfälle oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg">Diag lädt …</div>
|
||||
<div class="hint">
|
||||
Marker (6S): 19,2 V (leer) • 22,8 V (Storage 3,8/Z) • 25,2 V (Full 4,2/Z) • 26,1 V (HV 4,35/Z). Nur Simulation.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TOOLBOX -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
<block type="math_on_list"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
<block type="lists_getIndex"></block>
|
||||
<block type="lists_setIndex"></block>
|
||||
<block type="lists_length"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// ---------- Diagnose & Output ----------
|
||||
const out = document.getElementById('output');
|
||||
const cfgEl = document.getElementById('cfg');
|
||||
function setOutput(t){ out.textContent = t }
|
||||
function appendOutput(t){ out.textContent += (out.textContent.endsWith('\n')?'':'\n') + t; out.scrollTop = out.scrollHeight; }
|
||||
window.appendOutput = appendOutput;
|
||||
window.addEventListener('error', e => appendOutput('❌ JS-Error: ' + e.message));
|
||||
window.addEventListener('unhandledrejection', e => appendOutput('❌ Promise: ' + (e.reason && e.reason.message ? e.reason.message : e.reason)));
|
||||
|
||||
// ---------- Loader: erst lokal, dann CDN (klassische, nicht-ESM Bundles) ----------
|
||||
const CDN = 'https://unpkg.com';
|
||||
const LOAD = [
|
||||
['/crumbblocks/vendor/blockly/blockly_compressed.js', `${CDN}/blockly/blockly_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/blocks_compressed.js', `${CDN}/blockly/blocks_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/javascript_compressed.js', `${CDN}/blockly/javascript_compressed.js`],
|
||||
['/crumbblocks/vendor/blockly/msg/de.js', `${CDN}/blockly/msg/de.js`],
|
||||
];
|
||||
function loadScriptPair([localSrc, cdnSrc]){
|
||||
return new Promise((resolve, reject)=>{
|
||||
const s = document.createElement('script');
|
||||
s.src = localSrc;
|
||||
s.onload = ()=> resolve({src:localSrc, ok:true, via:'local'});
|
||||
s.onerror = ()=>{
|
||||
const c = document.createElement('script');
|
||||
c.src = cdnSrc;
|
||||
c.onload = ()=> resolve({src:cdnSrc, ok:true, via:'cdn'});
|
||||
c.onerror = ()=> reject(new Error('Laden fehlgeschlagen: ' + localSrc + ' und ' + cdnSrc));
|
||||
document.head.appendChild(c);
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function selfTest(){
|
||||
const diag = { blockly_loaded:false, generator_loaded:false, eval_ok:null, scripts:[], csp_note:null };
|
||||
for (const p of LOAD){
|
||||
try { diag.scripts.push(await loadScriptPair(p)); }
|
||||
catch(e){ diag.scripts.push({src:p, ok:false, via:'none', err:String(e)}); }
|
||||
}
|
||||
diag.blockly_loaded = !!window.Blockly;
|
||||
diag.generator_loaded = !!(window.Blockly && Blockly.JavaScript);
|
||||
try { new Function('return 42')(); diag.eval_ok = true; }
|
||||
catch(e){ diag.eval_ok = 'blocked: ' + e.message; diag.csp_note = 'CSP blockiert eval/new Function.'; }
|
||||
const CFG = { cells:6, v_full:4.20, v_storage:3.80, v_hv:4.35, v_min:3.20, drift_reject:0.30, tick_s:10, dv_charge:0.01, dv_discharge:-0.01, capacity_mAh:2200, c_rate:1.0 };
|
||||
cfgEl.textContent = 'Diag:\n' + JSON.stringify(diag, null, 2) + '\n\nCFG (Simulation):\n' + JSON.stringify(CFG, null, 2);
|
||||
if (!diag.blockly_loaded || !diag.generator_loaded){ appendOutput('❌ Blockly/Gernerator fehlt. Prüfe Loader/CDN/CSP.'); return; }
|
||||
bootBlockly();
|
||||
}
|
||||
|
||||
// ---------- Blockly-Init ----------
|
||||
function bootBlockly(){
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: {spacing:24, length:3, colour:'#474747', snap:true},
|
||||
trashcan: true,
|
||||
zoom: {startScale:1.1, maxScale:2.0, minScale:.6, controls:false, wheel:true},
|
||||
move: {scrollbars:true, drag:true, wheel:true}
|
||||
});
|
||||
|
||||
// Lern-Variablen
|
||||
['mode','target_cell_v','time_s','tick_s','stop_reason','pack_v','current_a','capacity_mAh','c_rate','dv','avg','max_v','min_v','cells','i','new_v']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch(_){} });
|
||||
|
||||
// print → Panel
|
||||
(function hardenPrint(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['text_print'] = function(block){
|
||||
const arg0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || "''";
|
||||
return 'appendOutput(String(' + arg0 + '));\n';
|
||||
};
|
||||
const origAlert = window.alert;
|
||||
window.alert = function(msg){ try { appendOutput('⚠️ alert: ' + msg); } catch(_) { origAlert(msg); } };
|
||||
})();
|
||||
|
||||
// 1-basiert Listen → richtig lesen/schreiben
|
||||
(function patchListsGenerators(){
|
||||
const gen = Blockly.JavaScript;
|
||||
gen['lists_getIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'VALUE', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const code = `${list}[(${at}) - 1]`;
|
||||
return [code, gen.ORDER_MEMBER];
|
||||
};
|
||||
gen['lists_setIndex'] = function(block){
|
||||
const list = gen.valueToCode(block, 'LIST', gen.ORDER_MEMBER) || '[]';
|
||||
const at = gen.valueToCode(block, 'AT', gen.ORDER_NONE) || '1';
|
||||
const to = gen.valueToCode(block, 'TO', gen.ORDER_NONE) || 'null';
|
||||
return `${list}[(${at}) - 1] = ${to};\n`;
|
||||
};
|
||||
})();
|
||||
|
||||
// XML utils
|
||||
function textToDom(xmlText){
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch(_){}
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml){ const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// --- Demos ---
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable>
|
||||
<variable>stop_reason</variable><variable>pack_v</variable><variable>current_a</variable><variable>capacity_mAh</variable><variable>c_rate</variable>
|
||||
<variable>cells</variable><variable>avg</variable><variable>max_v</variable><variable>min_v</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">DOC: 6S LiPo Charger – Logik-Simulation (kein echtes Laden!).</field></block></value></block>
|
||||
<block type="text_print" x="20" y="56"><value name="TEXT"><block type="text"><field name="TEXT">Ziele: Full 4,20V/Z • Storage 3,80V/Z • HV 4,35V/Z • Ende ~3,20V/Z.</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="110"><field name="VAR">capacity_mAh</field><value name="VALUE"><block type="math_number"><field name="NUM">2200</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_rate</field><value name="VALUE"><block type="math_number"><field name="NUM">1</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">current_a</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">DIVIDE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MULTIPLY</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">capacity_mAh</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_rate</field></block></value>
|
||||
</block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1000</field></block></value>
|
||||
</block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
</next></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="230"><value name="TEXT"><block type="text"><field name="TEXT">Regel: Zell-Drift ≥ 0,30V → STOP (reject_imbalance).</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T1: Storage 3.8V/Z
|
||||
const T1_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">storage</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.90</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.92</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.88</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.91</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.89</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.87</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="290"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">-0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="330"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">LT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</block>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">storage_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="800"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"storage", "target_cell_v":3.8, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T2: Full 4.2V/Z (dv +, MIN<target)
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">charge</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.2</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></next></next></next></next></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.70</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.68</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.72</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.69</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.71</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.67</field></block></value>
|
||||
</block></value></block>
|
||||
<block type="variables_set" x="20" y="280"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="320"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">full_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="790"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"charge", "target_cell_v":4.2, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "time_s": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">time_s</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T3: Drift-Check
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>stop_reason</variable><variable>cells</variable><variable>max_v</variable><variable>min_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">safety_check</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.60</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.61</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.58</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.59</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.05</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.62</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="200"><field name="VAR">max_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MAX"></mutation><field name="OP">MAX</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">min_v</field><value name="VALUE"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value></block></next>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="270"><value name="IF0"><block type="logic_compare"><field name="OP">GTE</field>
|
||||
<value name="A"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">0.3</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">reject_imbalance</field></block></value></block></statement>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="350"><value name="TEXT"><block type="text_join"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"safety_check", "delta": </field></block></value>
|
||||
<value name="ADD1"><block type="math_arithmetic"><field name="OP">MINUS"><value name="A"><block type="variables_get"><field name="VAR">max_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">min_v</field></block></value></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "stop_reason":"</field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">", "advice":"Pack nicht verwenden." }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// T4: HV 4.35V/Z (dv +, MIN<target)
|
||||
const T4_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>time_s</variable><variable>tick_s</variable><variable>stop_reason</variable><variable>dv</variable><variable>cells</variable><variable>i</variable><variable>new_v</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">hv_charge</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">4.35</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">tick_s</field><value name="VALUE"><block type="math_number"><field name="NUM">10</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="140"><field name="VAR">time_s</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="180"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="220"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">3.95</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3.97</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">3.94</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">3.98</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">3.96</field></block></value>
|
||||
</block></value>
|
||||
<block type="variables_set" x="20" y="360"><field name="VAR">dv</field><value name="VALUE"><block type="math_number"><field name="NUM">0.01</field></block></value></block>
|
||||
<block type="controls_whileUntil" x="20" y="400"><field name="MODE">WHILE</field>
|
||||
<value name="BOOL"><block type="logic_compare"><field name="OP">LT</field>
|
||||
<value name="A"><block type="math_on_list"><mutation op="MIN"></mutation><field name="OP">MIN</field><value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">time_s</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">time_s</field></block></value><value name="B"><block type="variables_get"><field name="VAR">tick_s</field></block></value></block></value>
|
||||
<next><block type="controls_forEach"><field name="VAR">i</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
<value name="ADD1"><block type="math_number"><field name="NUM">2</field></block></value>
|
||||
<value name="ADD2"><block type="math_number"><field name="NUM">3</field></block></value>
|
||||
<value name="ADD3"><block type="math_number"><field name="NUM">4</field></block></value>
|
||||
<value name="ADD4"><block type="math_number"><field name="NUM">5</field></block></value>
|
||||
<value name="ADD5"><block type="math_number"><field name="NUM">6</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">new_v</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="lists_getIndex"><mutation at="true" statement="false"></mutation><field name="MODE">GET</field><field name="WHERE">FROM_START</field><value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value><value name="VALUE"><block type="variables_get"><field name="VAR">cells</field></block></value></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">dv</field></block></value>
|
||||
</block></value>
|
||||
</block>
|
||||
<next><block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field><value name="A"><block type="variables_get"><field name="VAR">new_v</field></block></value><value name="B"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">new_v</field><value name="VALUE"><block type="variables_get"><field name="VAR">target_cell_v</field></block></value></block></statement>
|
||||
</block></next>
|
||||
<next><block type="lists_setIndex"><mutation at="true"></mutation><field name="MODE">SET</field><field name="WHERE">FROM_START</field>
|
||||
<value name="AT"><block type="variables_get"><field name="VAR">i</field></block></value>
|
||||
<value name="LIST"><block type="variables_get"><field name="VAR">cells</field></block></value>
|
||||
<value name="TO"><block type="variables_get"><field name="VAR">new_v</field></block></value>
|
||||
</block></next>
|
||||
</statement>
|
||||
</block></next>
|
||||
</statement>
|
||||
<next><block type="variables_set"><field name="VAR">stop_reason</field><value name="VALUE"><block type="text"><field name="TEXT">hv_reached</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="820"><value name="TEXT"><block type="text_join"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "mode":"hv_charge", "target_cell_v":4.35, "stop_reason":"</field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">stop_reason</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">", "cells_v":"[/* 'cells' */]" }</field></block></value>
|
||||
</block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// Buttons
|
||||
document.getElementById('btnDoc').onclick = ()=>{ loadXml(DOC_XML); setOutput('📄 Doku geladen.'); };
|
||||
document.getElementById('btnT1').onclick = ()=>{ loadXml(T1_XML); setOutput('🧪 T1: Storage 3,8V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = ()=>{ loadXml(T2_XML); setOutput('🧪 T2: Full 4,2V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnT3').onclick = ()=>{ loadXml(T3_XML); setOutput('🧪 T3: Drift-Check. ▶️ für Report.'); };
|
||||
document.getElementById('btnT4').onclick = ()=>{ loadXml(T4_XML); setOutput('🧪 T4: HV 4,35V/Z. ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = ()=>{
|
||||
const cells = Array.from({length:6},()=> (3.40 + Math.random()*0.65).toFixed(2));
|
||||
const items = cells.map((v,i)=>`<value name="ADD${i}"><block type="math_number"><field name="NUM">${v}</field></block></value>`).join('');
|
||||
const XML = `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>mode</variable><variable>target_cell_v</variable><variable>cells</variable></variables>
|
||||
<block type="variables_set" x="20" y="20"><field name="VAR">mode</field><value name="VALUE"><block type="text"><field name="TEXT">inspect</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="60"><field name="VAR">target_cell_v</field><value name="VALUE"><block type="math_number"><field name="NUM">3.8</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="100"><field name="VAR">cells</field><value name="VALUE"><block type="lists_create_with"><mutation items="6"></mutation>${items}</block></value></block>
|
||||
<block type="text_print" x="20" y="220"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Startzellen geladen. Wähle T1/T2/T4 für Loop-Logik.</field></block></value></block>
|
||||
</xml>`;
|
||||
loadXml(XML);
|
||||
setOutput('🎲 Zufallspack geladen.');
|
||||
};
|
||||
document.getElementById('btnClear').onclick = ()=>{ workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
// RUN: Watchdog + Alerts→Panel + Writeback & Clamp
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
let code = Blockly.JavaScript.workspaceToCode(workspace);
|
||||
const guardHeader = `
|
||||
let __wf = 0, __wf_cap = 5000;
|
||||
function __guard(){ if((__wf+=1) > __wf_cap){ throw new Error('Watchdog: zu viele Schleifen-Schritte (> ' + __wf_cap + ')'); } }
|
||||
`;
|
||||
const clampHeader = `
|
||||
function __clamp(v, t, mode){
|
||||
if(mode==='charge' || mode==='hv_charge') return Math.min(v, t);
|
||||
if(mode==='storage') return Math.max(v, t);
|
||||
return v;
|
||||
}
|
||||
`;
|
||||
// Watchdog in jede while
|
||||
code = guardHeader + clampHeader + code.replace(/while\s*\(/g, 'while(__guard(), ');
|
||||
// Alerts → Panel
|
||||
code = code.replace(/window\.alert\s*\(/g, 'appendOutput(');
|
||||
// Writeback & Clamp
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*\(\s*i\s*(?:-\s*1)?\s*\)\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[(i - 1)] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[(i - 1)] = new_v;"
|
||||
);
|
||||
code = code.replace(
|
||||
/new_v\s*=\s*cells\s*\[\s*i\s*\]\s*\+\s*dv\s*;/g,
|
||||
"new_v = __clamp(cells[i] + dv, (typeof target_cell_v!=='undefined'?target_cell_v:4.2), (typeof mode!=='undefined'?mode:'charge')); cells[i] = new_v;"
|
||||
);
|
||||
|
||||
setOutput('▶️ Ausführung…\n\n' + code);
|
||||
try { new Function(code)(); appendOutput('\n✅ Fertig.'); }
|
||||
catch(e){ appendOutput('\n❌ Fehler: ' + (e && e.message ? e.message : e)); }
|
||||
};
|
||||
}
|
||||
|
||||
// Start
|
||||
selfTest();
|
||||
</script>
|
||||
|
||||
<!-- Sicherheit -->
|
||||
<div style="padding:.4rem .8rem;color:#fff;background:#8a0000">
|
||||
⚠️ <b>Sicherheits-Hinweis:</b> Nur Simulation. Echte LiPos ausschließlich mit geeignetem Ladegerät & Balancer laden. Packs mit Zell-Delta ≥ 0,30 V nicht verwenden.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
22
crumbblocks/mock_ws_server.js
Normal file
22
crumbblocks/mock_ws_server.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Minimal WebSocket echo server for testing Painter stream
|
||||
// Usage: npm i ws; node mock_ws_server.js
|
||||
const WebSocket = require('ws');
|
||||
const wss = new WebSocket.Server({ port: 9980 });
|
||||
console.log('WS server on ws://localhost:9980');
|
||||
wss.on('connection', (ws)=>{
|
||||
console.log('client connected');
|
||||
ws.on('message', (msg)=>{
|
||||
try{
|
||||
const obj = JSON.parse(msg);
|
||||
// log only essentials to keep console tidy
|
||||
if(obj.type==='point'){
|
||||
process.stdout.write(`• point ${obj.p.x.toFixed(1)},${obj.p.y.toFixed(1)}\r`);
|
||||
}else{
|
||||
console.log(obj.type);
|
||||
}
|
||||
}catch(e){}
|
||||
// optionally echo back
|
||||
// ws.send(msg);
|
||||
});
|
||||
ws.on('close', ()=>console.log('client closed'));
|
||||
});
|
||||
395
crumbblocks/painter.html
Normal file
395
crumbblocks/painter.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter (Multi‑Stroke) – draw → export SVG + Motion JSON</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg: #0b0b10; --panel: #12121a; --muted:#8d93a1; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:340px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:900px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0f131a;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0f0f10}
|
||||
.muted{color:var(--muted);font-size:.85rem}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:220px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter (Multi‑Stroke) → SVG & Motion JSON • a→Spirale→y • Start/Stop/Dynamik</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken setzen (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits 1+1 setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop‑Marker</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke
|
||||
<input type="range" id="stroke" min="4" max="28" step="1" value="12">
|
||||
</label>
|
||||
<label>Glättung
|
||||
<input type="range" id="smooth" min="0" max="10" step="1" value="2">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke‑Farbe A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke‑Farbe B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="dynWidth"> <span>Breite ~ Geschwindigkeit (JSON‑only)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="captureJSON" checked> <span>Motion JSON mitschreiben</span></label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich‑Liste"></ul>
|
||||
<p class="muted">Neuer Stroke beginnt mit <span class="kbd">Maus/Touch‑Down</span>. Ende bei Loslassen. Mehrere Klicks/Wege werden einzeln erfasst.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary class="muted">A11y/Meta</summary>
|
||||
<label>Titel <input id="title" type="text" value="Omi Omega – One‑Liner"></label>
|
||||
<label>Beschreibung
|
||||
<textarea id="desc" rows="3">Cursives kleines a wird zur Spirale; zwei Einsen im a; y hält zusammen; Funken ringsum.</textarea>
|
||||
</label>
|
||||
<label>Tag (z. B. Datum/Hashtag) <input id="tag" type="text" value="22.08.25 #CRUMB"></label>
|
||||
</details>
|
||||
|
||||
<p class="muted">Tipps: Ziehen = zeichnen. <span class="kbd">Shift</span> = Funken. <span class="kbd">Z</span> = Undo. <span class="kbd">S</span> speichern.</p>
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">Mehrere Pfade mit Start/Stop und Zeitdynamik: a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<text id="tagText" x="860" y="760" font-family="ui-monospace,monospace" font-size="22" fill="#cfd5e3"></text>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span class="muted">Mehrere Klicks & Pfade sind erlaubt. Start/Stop wird als Ereignis erfasst; Geschwindigkeiten landen im Motion‑JSON.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const grad = document.getElementById('gradA2B');
|
||||
const tagText = document.getElementById('tagText');
|
||||
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const smoothInp = document.getElementById('smooth');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
const dynWidth = document.getElementById('dynWidth');
|
||||
const captureJSON = document.getElementById('captureJSON');
|
||||
const titleInp = document.getElementById('title');
|
||||
const descInp = document.getElementById('desc');
|
||||
const tagInp = document.getElementById('tag');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null; // {id, mode, color, width, points:[{x,y,t}], pathEl}
|
||||
const strokes = []; // array of strokes
|
||||
let t0 = null; // session start time
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function updateMeta(){
|
||||
stage.querySelector('title').textContent = titleInp.value.trim() || 'One‑Liner';
|
||||
stage.querySelector('desc').textContent = descInp.value.trim() || '';
|
||||
tagText.textContent = tagInp.value.trim();
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px`;
|
||||
li.append('Stroke', sel, sw, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
function toPathD(points){
|
||||
if(points.length===0) return '';
|
||||
const sm = +smoothInp.value;
|
||||
if(points.length<3 || sm===0){
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
// simple smoothing: use quadratic Beziers between midpoints
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i];
|
||||
const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0 };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s.points)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const len = Math.max(14, parseInt(strokeInp.value,10)*1.5);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
function exportSVGString(){
|
||||
updateMeta();
|
||||
const clone = stage.cloneNode(true);
|
||||
// inline live colors
|
||||
clone.querySelector('#sparks')?.setAttribute('stroke', colorSInp.value);
|
||||
// serialize
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
${s}`;
|
||||
}
|
||||
|
||||
function motionJSON(){
|
||||
const meta = { title:titleInp.value, desc:descInp.value, tag:tagInp.value, t0: t0??0 };
|
||||
const items = strokes.map(s=>{
|
||||
// speeds
|
||||
let maxV=0, sumV=0, nV=0; const pts=s.points;
|
||||
for(let i=1;i<pts.length;i++){
|
||||
const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; const dt=(pts[i].t-pts[i-1].t)/1000; if(dt<=0) continue;
|
||||
const v = Math.hypot(dx,dy)/dt; maxV=Math.max(maxV,v); sumV+=v; nV++;
|
||||
}
|
||||
const avgV = nV? sumV/nV : 0;
|
||||
return {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width,
|
||||
startMs: s.start, durationMs: s.duration, lengthPx: s.length,
|
||||
avgSpeedPxPerS: +avgV.toFixed(2), maxSpeedPxPerS: +maxV.toFixed(2),
|
||||
points: s.points.map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}))
|
||||
};
|
||||
});
|
||||
return JSON.stringify({meta, strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner_multi.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', endStroke);
|
||||
|
||||
// Hotkeys
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='z' || e.key==='Z'){ if(strokes.length){ removeStroke(strokes.length-1); } }
|
||||
if((e.key==='s' || e.key==='S') && (e.ctrlKey||e.metaKey)){ e.preventDefault(); document.getElementById('downloadSVG').click(); }
|
||||
});
|
||||
|
||||
// Reactive
|
||||
[titleInp,descInp,tagInp].forEach(inp=>inp.addEventListener('input', updateMeta));
|
||||
[colorAInp,colorBInp,colorSInp].forEach(inp=>inp.addEventListener('input', ()=>{ tagText.setAttribute('fill','#cfd5e3'); }));
|
||||
|
||||
updateMeta();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
647
crumbblocks/painter_lines.html
Normal file
647
crumbblocks/painter_lines.html
Normal file
@@ -0,0 +1,647 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One-Liner Painter + Bezier-Editor (Fix)</title>
|
||||
<style>
|
||||
:root{ --bg:#0b0b10; --panel:#12121a; --muted:#9aa3b2; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c; }
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none;font-size:.9rem;color:var(--muted)}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:370px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:1000px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.col{display:grid;gap:10px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0b1114;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0b0b10}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:240px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.badge{font-size:.75rem;color:#0b1114;background:var(--accent);border-radius:999px;padding:2px 8px}
|
||||
.muted{color:var(--muted)}
|
||||
/* Bezier UI visuals */
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
.anchor{fill:#31e07b;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handle{fill:#ffe08a;stroke:#0b0b10;stroke-width:2;cursor:grab; pointer-events:all}
|
||||
.handleLine{stroke:#ffe08a;stroke-width:2.5;stroke-dasharray:4 4; pointer-events:none}
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
#bezierUI{ pointer-events:none } /* UI-Schicht blockiert keine Klicks auf Pfad */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One-Liner Painter → SVG/PNG/JSON • Replay • <span class="badge">Bezier-Editor (Fix)</span></h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits „1“ setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop-Marker</span></label>
|
||||
<label class="chip"><input type="checkbox" id="frameOn" checked> <span>Crumblines-Frame</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke <input type="range" id="stroke" min="4" max="28" step="1" value="12"></label>
|
||||
<label>Sampler
|
||||
<select id="sampler">
|
||||
<option value="raw">Raw Polyline</option>
|
||||
<option value="quad">Quadratic</option>
|
||||
<option value="cubic" selected>Catmull-Rom → Cubic</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="col" style="margin-top:6px">
|
||||
<strong>Bezier-Editor</strong>
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="editBezier"> <span>Bearbeiten</span></label>
|
||||
<label class="chip"><input type="checkbox" id="linkHandles" checked> <span>Spiegel-Griffe</span></label>
|
||||
<button class="btn" id="toBezier">Auswahl → Bezier</button>
|
||||
<button class="btn" id="splitHandles">Anker: Split</button>
|
||||
<button class="btn warn" id="delAnchor" disabled>Anker löschen</button>
|
||||
</div>
|
||||
<p class="muted">Klicke einen Stroke, dann <em>Bearbeiten</em>. Grüne Punkte = Anker, gelbe = Griffe. Ziehen. Doppelklick = Anker einfügen. <span class="kbd">Entf</span> löscht. <span class="kbd">L</span> spiegelt Griffe.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich-Liste"></ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="replay">▶︎ Replay</button>
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn" id="downloadPNG">PNG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion-JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One-Liner</title>
|
||||
<desc id="svgDesc">a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.selected{filter:drop-shadow(0 0 4px #ffd166)}
|
||||
</style>
|
||||
</defs>
|
||||
<rect id="frameRect" x="16.5" y="16.5" width="1167" height="767" fill="none" stroke="rgba(255,255,255,.2)" stroke-width="1.5" stroke-dasharray="6 10"/>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<g id="bezierUI"></g>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>Fixes: keine (0,0)-Griffe mehr; Drag startet sofort; UI blockiert keine Klicks.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const bezierUI= document.getElementById('bezierUI');
|
||||
const frameRect = document.getElementById('frameRect');
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const frameOnCb = document.getElementById('frameOn');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const samplerSel= document.getElementById('sampler');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
|
||||
const editBezierCb = document.getElementById('editBezier');
|
||||
const linkHandlesCb= document.getElementById('linkHandles');
|
||||
const toBezierBtn = document.getElementById('toBezier');
|
||||
const splitBtn = document.getElementById('splitHandles');
|
||||
const delAnchorBtn = document.getElementById('delAnchor');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const dlPNGBtn = document.getElementById('downloadPNG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
const replayBtn = document.getElementById('replay');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null;
|
||||
const strokes = [];
|
||||
let t0 = null;
|
||||
let playing = false;
|
||||
let selectedStroke = null;
|
||||
let selectedAnchor = {stroke:null, idx:-1};
|
||||
let dragging = null; // {type:'anchor'|'h1'|'h2', stroke, idx, ox, oy, pid}
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const edit = document.createElement('button'); edit.className='btn'; edit.textContent='Bearbeiten';
|
||||
edit.addEventListener('click',()=>{ selectStroke(s); editBezierCb.checked = true; drawBezierUI(); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px${s.isBezier?' • Bezier':''}`;
|
||||
li.append('Stroke', sel, sw, edit, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
if(selectedStroke===s){ selectedStroke=null; clearBezierUI(); }
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
p.addEventListener('pointerdown', (e)=>{
|
||||
if(editBezierCb.checked){ e.stopPropagation(); const s = strokes.find(st=>st.pathEl===p); if(s){ selectStroke(s); drawBezierUI(); } }
|
||||
});
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
// Samplers → SVG path "d"
|
||||
function pathRaw(points){
|
||||
if(points.length===0) return '';
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
function pathQuadratic(points){
|
||||
if(points.length<2) return '';
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i]; const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
function pathCubic(points){
|
||||
if(points.length<2) return '';
|
||||
const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
|
||||
const p=(i)=>points[ clamp(i,0,points.length-1) ];
|
||||
let d=`M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=0;i<points.length-1;i++){
|
||||
const p0=p(i-1), p1=p(i), p2=p(i+1), p3=p(i+2);
|
||||
const c1x = p1.x + (p2.x - p0.x)/6, c1y = p1.y + (p2.y - p0.y)/6;
|
||||
const c2x = p2.x - (p3.x - p1.x)/6, c2y = p2.y - (p3.y - p1.y)/6;
|
||||
d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${p2.x} ${p2.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
function toPathD(s){
|
||||
if(s.isBezier) return pathFromAnchors(s.anchors);
|
||||
const pts = s.points;
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
if(smp==='raw') return pathRaw(pts);
|
||||
if(smp==='cubic') return pathCubic(pts);
|
||||
return pathQuadratic(pts);
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(editBezierCb.checked){ return; } // während Edit nicht zeichnen
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0, sampler:samplerSel.value, isBezier:false };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function updatePath(s){
|
||||
s.pathEl.setAttribute('d', toPathD(s));
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
}
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
// --- Bezier conversion & editing ---
|
||||
// FIX: build anchors with valid handles only; no (0,0) placeholders
|
||||
function catmullToAnchors(points){
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
|
||||
const P=(i)=>points[ clamp(i,0,points.length-1) ];
|
||||
const A=[];
|
||||
for(let i=0;i<points.length-1;i++){
|
||||
const p0=P(i-1), p1=P(i), p2=P(i+1), p3=P(i+2);
|
||||
const c1 = {x: p1.x + (p2.x - p0.x)/6, y: p1.y + (p2.y - p0.y)/6};
|
||||
const c2 = {x: p2.x - (p3.x - p1.x)/6, y: p2.y - (p3.y - p1.y)/6};
|
||||
if(i===0){
|
||||
A.push({x:p1.x,y:p1.y,h1:{x:p1.x,y:p1.y},h2:{x:c1.x,y:c1.y},split:false});
|
||||
}else{
|
||||
// update previous anchor's outgoing handle
|
||||
A[A.length-1].h2 = {x:c1.x,y:c1.y};
|
||||
}
|
||||
// next anchor for p2
|
||||
const next = {x:p2.x,y:p2.y,h1:{x:c2.x,y:c2.y},h2:{x:p2.x,y:p2.y},split:false};
|
||||
A.push(next);
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
function pathFromAnchors(A){
|
||||
if(!A || A.length<2) return '';
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function selectStroke(s){
|
||||
selectedStroke = s;
|
||||
strokes.forEach(st=> st.pathEl.classList.toggle('selected', st===s));
|
||||
}
|
||||
|
||||
function clearBezierUI(){ bezierUI.innerHTML=''; selectedAnchor={stroke:null, idx:-1}; delAnchorBtn.disabled = true; }
|
||||
function drawBezierUI(){
|
||||
clearBezierUI();
|
||||
const s = selectedStroke;
|
||||
if(!editBezierCb.checked || !s || !s.isBezier) return;
|
||||
s.anchors.forEach((a,idx)=>{
|
||||
// handle lines
|
||||
bezierUI.append(line(a.x, a.y, a.h1.x, a.h1.y, 'handleLine'));
|
||||
bezierUI.append(line(a.x, a.y, a.h2.x, a.h2.y, 'handleLine'));
|
||||
// handles
|
||||
const h1 = circle(a.h1.x, a.h1.y, 5, 'handle', (e)=>startDrag('h1', s, idx, e));
|
||||
const h2 = circle(a.h2.x, a.h2.y, 5, 'handle', (e)=>startDrag('h2', s, idx, e));
|
||||
bezierUI.append(h1, h2);
|
||||
// anchor
|
||||
const an = circle(a.x, a.y, 6.5, 'anchor', (e)=>{ startDrag('anchor', s, idx, e) });
|
||||
an.addEventListener('dblclick', (e)=>{ e.stopPropagation(); insertAnchorAt(s, idx); });
|
||||
an.addEventListener('pointerdown', ()=>{ selectedAnchor={stroke:s,idx}; delAnchorBtn.disabled=false; });
|
||||
bezierUI.append(an);
|
||||
});
|
||||
}
|
||||
|
||||
function line(x1,y1,x2,y2,cls){
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1',x1); l.setAttribute('y1',y1);
|
||||
l.setAttribute('x2',x2); l.setAttribute('y2',y2);
|
||||
l.setAttribute('class',cls);
|
||||
return l;
|
||||
}
|
||||
function circle(x,y,r,cls,onDown){
|
||||
const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class',cls);
|
||||
c.style.touchAction = 'none';
|
||||
c.addEventListener('pointerdown', (e)=>{ e.stopPropagation(); onDown(e); });
|
||||
return c;
|
||||
}
|
||||
|
||||
// FIX: start drag immediately on current event + proper pointer capture
|
||||
function startDrag(type, s, idx, ev){
|
||||
ev.preventDefault();
|
||||
const a = s.anchors[idx];
|
||||
const p0 = svgPoint(ev);
|
||||
dragging = {type, stroke:s, idx, ox:p0.x, oy:p0.y, pid:ev.pointerId};
|
||||
try { stage.setPointerCapture(ev.pointerId); } catch(e){}
|
||||
const move = (e)=>{
|
||||
if(!dragging) return;
|
||||
const pos = svgPoint(e);
|
||||
const dx = pos.x - dragging.ox, dy = pos.y - dragging.oy;
|
||||
dragging.ox = pos.x; dragging.oy = pos.y;
|
||||
if(type==='anchor'){
|
||||
a.x+=dx; a.y+=dy; a.h1.x+=dx; a.h1.y+=dy; a.h2.x+=dx; a.h2.y+=dy;
|
||||
}else if(type==='h1'){
|
||||
a.h1.x+=dx; a.h1.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h2.x = a.x - (a.h1.x - a.x);
|
||||
a.h2.y = a.y - (a.h1.y - a.y);
|
||||
}
|
||||
}else if(type==='h2'){
|
||||
a.h2.x+=dx; a.h2.y+=dy;
|
||||
if(linkHandlesCb.checked && !a.split){
|
||||
a.h1.x = a.x - (a.h2.x - a.x);
|
||||
a.h1.y = a.y - (a.h2.y - a.y);
|
||||
}
|
||||
}
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
};
|
||||
const up = (e)=>{
|
||||
try { stage.releasePointerCapture(ev.pointerId); } catch(_) {}
|
||||
window.removeEventListener('pointermove', move);
|
||||
dragging=null;
|
||||
};
|
||||
window.addEventListener('pointermove', move, {passive:true});
|
||||
window.addEventListener('pointerup', up, {once:true});
|
||||
}
|
||||
|
||||
function insertAnchorAt(s, idx){
|
||||
const A = s.anchors;
|
||||
if(idx>=A.length-1) return;
|
||||
const a = A[idx], b = A[idx+1];
|
||||
const mid = {x:(a.x+b.x)/2, y:(a.y+b.y)/2};
|
||||
const dir = {x:(b.x-a.x), y:(b.y-a.y)};
|
||||
const scale = 0.33;
|
||||
const hLen = {x:dir.x*scale, y:dir.y*scale};
|
||||
const newA = {
|
||||
x: mid.x, y: mid.y,
|
||||
h1: {x: mid.x - hLen.x*0.5, y: mid.y - hLen.y*0.5},
|
||||
h2: {x: mid.x + hLen.x*0.5, y: mid.y + hLen.y*0.5},
|
||||
split:false
|
||||
};
|
||||
A.splice(idx+1,0,newA);
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(A));
|
||||
drawBezierUI();
|
||||
}
|
||||
|
||||
function deleteSelectedAnchor(){
|
||||
const {stroke:s, idx} = selectedAnchor;
|
||||
if(!s || idx<0) return;
|
||||
if(s.anchors.length<=2) return;
|
||||
s.anchors.splice(idx,1);
|
||||
selectedAnchor={stroke:null,idx:-1};
|
||||
delAnchorBtn.disabled=true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
drawBezierUI();
|
||||
}
|
||||
|
||||
function convertToBezier(s){
|
||||
if(s.isBezier) return;
|
||||
if(s.points.length<2) return;
|
||||
s.anchors = catmullToAnchors(s.points);
|
||||
s.isBezier = true;
|
||||
s.pathEl.setAttribute('d', pathFromAnchors(s.anchors));
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
toBezierBtn.addEventListener('click', ()=>{ if(selectedStroke) { convertToBezier(selectedStroke); drawBezierUI(); } });
|
||||
splitBtn.addEventListener('click', ()=>{ if(selectedStroke && selectedAnchor.idx>=0){ const a=selectedStroke.anchors[selectedAnchor.idx]; a.split=!a.split; drawBezierUI(); }});
|
||||
delAnchorBtn.addEventListener('click', deleteSelectedAnchor);
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='Delete' || e.key==='Backspace'){ if(editBezierCb.checked) { deleteSelectedAnchor(); } }
|
||||
if(e.key==='l' || e.key==='L'){ linkHandlesCb.checked = !linkHandlesCb.checked; }
|
||||
});
|
||||
|
||||
// Double-click on path to insert anchor near click
|
||||
strokesG.addEventListener('dblclick', (e)=>{
|
||||
if(!editBezierCb.checked || !selectedStroke || !selectedStroke.isBezier) return;
|
||||
const p = svgPoint(e);
|
||||
let bestI=0, bestD=1e9, A=selectedStroke.anchors;
|
||||
for(let i=0;i<A.length-1;i++){
|
||||
const a=A[i], b=A[i+1];
|
||||
const d = Math.hypot(p.x-(a.x+b.x)/2, p.y-(a.y+b.y)/2);
|
||||
if(d<bestD){ bestD=d; bestI=i; }
|
||||
}
|
||||
insertAnchorAt(selectedStroke, bestI);
|
||||
});
|
||||
|
||||
// Export / Import / Replay (minimal for this fix)
|
||||
function exportSVGString(){
|
||||
frameRect.style.display = frameOnCb.checked ? 'block' : 'none';
|
||||
const clone = stage.cloneNode(true);
|
||||
// remove Bezier UI
|
||||
const ui = clone.querySelector('#bezierUI'); if(ui) ui.remove();
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>\n${s}`;
|
||||
}
|
||||
function motionJSON(){
|
||||
const items = strokes.map(s=>{
|
||||
const o = {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width, sampler: s.sampler,
|
||||
startMs: s.start||0, durationMs: s.duration||0, lengthPx: s.length||0,
|
||||
};
|
||||
if(s.isBezier){
|
||||
o.isBezier = true;
|
||||
o.anchors = s.anchors.map(a=>({x:a.x,y:a.y,h1:{x:a.h1.x,y:a.h1.y},h2:{x:a.h2.x,y:a.h2.y},split:!!a.split}));
|
||||
}else{
|
||||
o.points = (s.points||[]).map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}));
|
||||
}
|
||||
return o;
|
||||
});
|
||||
return JSON.stringify({strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; clearBezierUI(); listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
dlPNGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); svgToPng(svg, 'omi-omega-oneliner.png'); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion-JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
replayBtn.addEventListener('click', startReplay);
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', ()=>{ drawing=false; });
|
||||
|
||||
// init
|
||||
frameRect.style.display = frameOnCb.checked ? 'block':'none';
|
||||
|
||||
// Replay
|
||||
function startReplay(){
|
||||
if(!strokes.length) return;
|
||||
playing = true;
|
||||
markersG.style.opacity = 0.25; bezierUI.style.opacity = 0.15;
|
||||
strokes.forEach(s=> s.pathEl.setAttribute('d',''));
|
||||
const total = strokes.reduce((m,s)=>Math.max(m, s.isBezier?1500:(s.points?.length ? s.points[s.points.length-1].t : 0)), 0);
|
||||
const startWall = performance.now();
|
||||
function step(){
|
||||
if(!playing) return;
|
||||
const t = performance.now() - startWall;
|
||||
strokes.forEach(s=>{
|
||||
if(s.isBezier){
|
||||
const A=s.anchors;
|
||||
const k = Math.min(A.length-1, Math.floor((t/total)*(A.length-1)) );
|
||||
let d = `M ${A[0].x} ${A[0].y}`;
|
||||
for(let i=0;i<k;i++){ const a=A[i], b=A[i+1]; d += ` C ${a.h2.x} ${a.h2.y}, ${b.h1.x} ${b.h1.y}, ${b.x} ${b.y}`; }
|
||||
s.pathEl.setAttribute('d', d);
|
||||
applyStrokeStyle(s);
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
}else{
|
||||
const pts = s.points.filter(p => p.t <= t);
|
||||
if(pts.length >= 2){
|
||||
s.pathEl.setAttribute('stroke-width', s.width);
|
||||
applyStrokeStyle(s);
|
||||
const smp = s.sampler || samplerSel.value;
|
||||
s.pathEl.setAttribute('d', smp==='raw'?pathRaw(pts): (smp==='cubic'?pathCubic(pts):pathQuadratic(pts)));
|
||||
}
|
||||
}
|
||||
});
|
||||
if(t < total) requestAnimationFrame(step);
|
||||
else { playing=false; markersG.style.opacity = 1; bezierUI.style.opacity = 1; strokes.forEach(updatePath); }
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function svgToPng(svgString, filename){
|
||||
const img = new Image();
|
||||
const svgBlob = new Blob([svgString], {type:'image/svg+xml;charset=utf-8'});
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
img.onload = function(){
|
||||
const canvas = document.createElement('canvas');
|
||||
const vb = stage.viewBox.baseVal;
|
||||
canvas.width = vb.width; canvas.height = vb.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
canvas.toBlob((blob)=>{
|
||||
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=filename; a.click();
|
||||
});
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
395
crumbblocks/painter_spray.html
Normal file
395
crumbblocks/painter_spray.html
Normal file
@@ -0,0 +1,395 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>One‑Liner Painter (Multi‑Stroke) – draw → export SVG + Motion JSON</title>
|
||||
<style>
|
||||
:root{
|
||||
--bg: #0b0b10; --panel: #12121a; --muted:#8d93a1; --accent:#62d3a4; --accent2:#ffd166; --danger:#ff5c5c;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%}
|
||||
body{margin:0;display:grid;grid-template-rows:auto 1fr auto;background:var(--bg);color:#e7eaf0;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial}
|
||||
header,footer{padding:12px 16px;background:var(--panel);border-bottom:1px solid #1e2230}
|
||||
footer{border-top:1px solid #1e2230;border-bottom:none}
|
||||
h1{font-size:1.05rem;margin:0}
|
||||
.wrap{display:grid;grid-template-columns:340px 1fr;gap:16px;padding:16px}
|
||||
@media (max-width:900px){.wrap{grid-template-columns:1fr}}
|
||||
.panel{background:var(--panel);border:1px solid #1e2230;border-radius:12px;padding:14px;display:grid;gap:12px}
|
||||
label{font-size:.9rem;color:#cfd5e3}
|
||||
input[type="text"],textarea,select{width:100%;padding:10px;border-radius:10px;border:1px solid #2a3042;background:#0f121a;color:#e7eaf0}
|
||||
input[type="color"]{width:48px;height:36px;border:none;background:none}
|
||||
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||||
.btn{appearance:none;border:none;border-radius:10px;padding:10px 12px;background:#1f2636;color:#e7eaf0;cursor:pointer}
|
||||
.btn:hover{background:#28314a}
|
||||
.btn.accent{background:var(--accent);color:#0f131a;font-weight:600}
|
||||
.btn.warn{background:var(--danger);color:#0f0f10}
|
||||
.muted{color:var(--muted);font-size:.85rem}
|
||||
.canvasWrap{position:relative;background:#0f121a;border:1px solid #1e2230;border-radius:12px;overflow:hidden}
|
||||
svg{display:block;width:100%;height:100%;background:#0f121a}
|
||||
.kbd{padding:1px 6px;border-radius:8px;background:#1e2333;color:#cfd5e3;font-family:ui-monospace,Menlo,Monaco,monospace}
|
||||
ul#strokeList{list-style:none;margin:0;padding:0;display:grid;gap:8px;max-height:220px;overflow:auto}
|
||||
li.strokeItem{display:flex;align-items:center;gap:8px;background:#0f121a;border:1px solid #2a3042;border-radius:10px;padding:8px}
|
||||
.chip{display:inline-flex;align-items:center;gap:8px;background:#0f121a;border:1px dashed #2a3042;border-radius:10px;padding:6px 10px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>One‑Liner Painter (Multi‑Stroke) → SVG & Motion JSON • a→Spirale→y • Start/Stop/Dynamik</h1>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<aside class="panel" aria-labelledby="controlsTitle">
|
||||
<h2 id="controlsTitle" style="margin:0;font-size:1rem">Werkzeug</h2>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="sparkMode"> <span>Funken setzen (Shift)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="bitMode"> <span>Bits 1+1 setzen</span></label>
|
||||
<label class="chip"><input type="checkbox" id="markers"> <span>Start/Stop‑Marker</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Strichstärke
|
||||
<input type="range" id="stroke" min="4" max="28" step="1" value="12">
|
||||
</label>
|
||||
<label>Glättung
|
||||
<input type="range" id="smooth" min="0" max="10" step="1" value="2">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Farbe A <input type="color" id="colorA" value="#62d3a4"></label>
|
||||
<label>Farbe B <input type="color" id="colorB" value="#ffd166"></label>
|
||||
<label>Funken <input type="color" id="colorSpark" value="#e7eaf0"></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="radio" name="mode" value="A" checked> <span>Stroke‑Farbe A</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="B"> <span>Stroke‑Farbe B</span></label>
|
||||
<label class="chip"><input type="radio" name="mode" value="GRAD"> <span>Verlauf A→B</span></label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label class="chip"><input type="checkbox" id="dynWidth"> <span>Breite ~ Geschwindigkeit (JSON‑only)</span></label>
|
||||
<label class="chip"><input type="checkbox" id="captureJSON" checked> <span>Motion JSON mitschreiben</span></label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Strokes</strong>
|
||||
<ul id="strokeList" aria-label="Strich‑Liste"></ul>
|
||||
<p class="muted">Neuer Stroke beginnt mit <span class="kbd">Maus/Touch‑Down</span>. Ende bei Loslassen. Mehrere Klicks/Wege werden einzeln erfasst.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="undo">Letzten Stroke löschen</button>
|
||||
<button class="btn warn" id="clear">Alles löschen</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn" id="copySVG">SVG kopieren</button>
|
||||
<button class="btn" id="downloadSVG">SVG speichern</button>
|
||||
<button class="btn accent" id="copyJSON">Motion‑JSON kopieren</button>
|
||||
<button class="btn" id="downloadJSON">JSON speichern</button>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary class="muted">A11y/Meta</summary>
|
||||
<label>Titel <input id="title" type="text" value="Omi Omega – One‑Liner"></label>
|
||||
<label>Beschreibung
|
||||
<textarea id="desc" rows="3">Cursives kleines a wird zur Spirale; zwei Einsen im a; y hält zusammen; Funken ringsum.</textarea>
|
||||
</label>
|
||||
<label>Tag (z. B. Datum/Hashtag) <input id="tag" type="text" value="22.08.25 #CRUMB"></label>
|
||||
</details>
|
||||
|
||||
<p class="muted">Tipps: Ziehen = zeichnen. <span class="kbd">Shift</span> = Funken. <span class="kbd">Z</span> = Undo. <span class="kbd">S</span> speichern.</p>
|
||||
</aside>
|
||||
|
||||
<main class="canvasWrap" aria-label="Zeichenfläche">
|
||||
<svg id="stage" viewBox="0 0 1200 800" role="img" aria-labelledby="svgTitle svgDesc">
|
||||
<title id="svgTitle">Omi Omega – One‑Liner</title>
|
||||
<desc id="svgDesc">Mehrere Pfade mit Start/Stop und Zeitdynamik: a→Spirale→y; Bits 1+1; Funken.</desc>
|
||||
<defs>
|
||||
<linearGradient id="gradA2B" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#62d3a4" />
|
||||
<stop offset="50%" stop-color="#62d3a4" />
|
||||
<stop offset="51%" stop-color="#ffd166" />
|
||||
<stop offset="100%" stop-color="#ffd166" />
|
||||
</linearGradient>
|
||||
<style>
|
||||
.sline{fill:none;stroke-linecap:round;stroke-linejoin:round}
|
||||
.markerStart{fill:#35c759;stroke:none}
|
||||
.markerStop{fill:#ff3b30;stroke:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="art">
|
||||
<g id="strokes"></g>
|
||||
<g id="bits"></g>
|
||||
<g id="sparks" stroke="#e7eaf0" stroke-width="8" stroke-linecap="round"></g>
|
||||
<g id="markersG"></g>
|
||||
<text id="tagText" x="860" y="760" font-family="ui-monospace,monospace" font-size="22" fill="#cfd5e3"></text>
|
||||
</g>
|
||||
</svg>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span class="muted">Mehrere Klicks & Pfade sind erlaubt. Start/Stop wird als Ereignis erfasst; Geschwindigkeiten landen im Motion‑JSON.</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const stage = document.getElementById('stage');
|
||||
const strokesG= document.getElementById('strokes');
|
||||
const bitsG = document.getElementById('bits');
|
||||
const sparksG = document.getElementById('sparks');
|
||||
const markersG= document.getElementById('markersG');
|
||||
const grad = document.getElementById('gradA2B');
|
||||
const tagText = document.getElementById('tagText');
|
||||
|
||||
const strokeList = document.getElementById('strokeList');
|
||||
|
||||
// Controls
|
||||
const sparkMode = document.getElementById('sparkMode');
|
||||
const bitMode = document.getElementById('bitMode');
|
||||
const markersCb = document.getElementById('markers');
|
||||
const strokeInp = document.getElementById('stroke');
|
||||
const smoothInp = document.getElementById('smooth');
|
||||
const colorAInp = document.getElementById('colorA');
|
||||
const colorBInp = document.getElementById('colorB');
|
||||
const colorSInp = document.getElementById('colorSpark');
|
||||
const dynWidth = document.getElementById('dynWidth');
|
||||
const captureJSON = document.getElementById('captureJSON');
|
||||
const titleInp = document.getElementById('title');
|
||||
const descInp = document.getElementById('desc');
|
||||
const tagInp = document.getElementById('tag');
|
||||
|
||||
const undoBtn = document.getElementById('undo');
|
||||
const clearBtn = document.getElementById('clear');
|
||||
const copySVGBtn = document.getElementById('copySVG');
|
||||
const dlSVGBtn = document.getElementById('downloadSVG');
|
||||
const copyJSONBtn = document.getElementById('copyJSON');
|
||||
const dlJSONBtn = document.getElementById('downloadJSON');
|
||||
|
||||
let mode = 'A';
|
||||
document.querySelectorAll('input[name="mode"]').forEach(r=>{
|
||||
r.addEventListener('change', ()=>{ mode = document.querySelector('input[name="mode"]:checked').value; });
|
||||
});
|
||||
|
||||
// State
|
||||
let drawing = false;
|
||||
let curStroke = null; // {id, mode, color, width, points:[{x,y,t}], pathEl}
|
||||
const strokes = []; // array of strokes
|
||||
let t0 = null; // session start time
|
||||
|
||||
function now(){ return performance.now(); }
|
||||
|
||||
function svgPoint(evt){
|
||||
const pt = stage.createSVGPoint();
|
||||
pt.x = evt.clientX; pt.y = evt.clientY;
|
||||
const ctm = stage.getScreenCTM().inverse();
|
||||
const p = pt.matrixTransform(ctm);
|
||||
return {x: Math.round(p.x), y: Math.round(p.y)};
|
||||
}
|
||||
|
||||
function updateMeta(){
|
||||
stage.querySelector('title').textContent = titleInp.value.trim() || 'One‑Liner';
|
||||
stage.querySelector('desc').textContent = descInp.value.trim() || '';
|
||||
tagText.textContent = tagInp.value.trim();
|
||||
}
|
||||
|
||||
function listRefresh(){
|
||||
strokeList.innerHTML = '';
|
||||
strokes.forEach((s,i)=>{
|
||||
const li = document.createElement('li'); li.className='strokeItem';
|
||||
const sw = document.createElement('input'); sw.type='range'; sw.min=4; sw.max=28; sw.step=1; sw.value=s.width; sw.title='Breite';
|
||||
sw.addEventListener('input',()=>{ s.width=+sw.value; s.pathEl.setAttribute('stroke-width', s.width); });
|
||||
const sel = document.createElement('select');
|
||||
['A','B','GRAD'].forEach(v=>{ const o=document.createElement('option'); o.value=v; o.textContent=v; if(s.mode===v) o.selected=true; sel.appendChild(o); });
|
||||
sel.addEventListener('change',()=>{ s.mode=sel.value; applyStrokeStyle(s); });
|
||||
const del = document.createElement('button'); del.className='btn'; del.textContent='✕';
|
||||
del.addEventListener('click',()=>{ removeStroke(i); });
|
||||
const meta = document.createElement('span'); meta.className='muted';
|
||||
meta.textContent = `#${s.id} • ${Math.round(s.duration)}ms • ~${Math.round(s.length)}px`;
|
||||
li.append('Stroke', sel, sw, del, meta);
|
||||
strokeList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function removeStroke(idx){
|
||||
const s = strokes[idx];
|
||||
if(!s) return;
|
||||
s.pathEl.remove(); if(s.startMarker) s.startMarker.remove(); if(s.stopMarker) s.stopMarker.remove();
|
||||
strokes.splice(idx,1);
|
||||
listRefresh();
|
||||
}
|
||||
|
||||
function createPathEl(){
|
||||
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
p.setAttribute('class','sline');
|
||||
p.setAttribute('stroke-width', strokeInp.value);
|
||||
strokesG.appendChild(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
function applyStrokeStyle(s){
|
||||
if(s.mode==='A'){ s.pathEl.setAttribute('stroke', colorAInp.value); }
|
||||
else if(s.mode==='B'){ s.pathEl.setAttribute('stroke', colorBInp.value); }
|
||||
else { s.pathEl.setAttribute('stroke','url(#gradA2B)'); }
|
||||
}
|
||||
|
||||
function toPathD(points){
|
||||
if(points.length===0) return '';
|
||||
const sm = +smoothInp.value;
|
||||
if(points.length<3 || sm===0){
|
||||
const p0 = points[0];
|
||||
let d = `M ${p0.x} ${p0.y}`;
|
||||
for(let i=1;i<points.length;i++){ const p=points[i]; d += ` L ${p.x} ${p.y}`; }
|
||||
return d;
|
||||
}
|
||||
// simple smoothing: use quadratic Beziers between midpoints
|
||||
let d = `M ${points[0].x} ${points[0].y}`;
|
||||
for(let i=1;i<points.length-1;i++){
|
||||
const p0 = points[i];
|
||||
const p1 = points[i+1];
|
||||
const mx = (p0.x + p1.x)/2; const my = (p0.y + p1.y)/2;
|
||||
d += ` Q ${p0.x} ${p0.y} ${mx} ${my}`;
|
||||
}
|
||||
const last = points[points.length-1]; d += ` L ${last.x} ${last.y}`;
|
||||
return d;
|
||||
}
|
||||
|
||||
function startStroke(e){
|
||||
if(bitMode.checked){ placeBit(e); return; }
|
||||
if(sparkMode.checked || e.shiftKey){ placeSpark(e); return; }
|
||||
drawing = true;
|
||||
if(t0===null) t0 = now();
|
||||
const t = now() - t0;
|
||||
const pt = svgPoint(e);
|
||||
const pathEl = createPathEl();
|
||||
curStroke = { id: Date.now()%1e7, mode, colorA: colorAInp.value, colorB: colorBInp.value, width:+strokeInp.value, points:[{...pt,t}], pathEl, start:t, duration:0, length:0 };
|
||||
applyStrokeStyle(curStroke);
|
||||
updatePath(curStroke);
|
||||
if(markersCb.checked){ curStroke.startMarker = mark(pt.x, pt.y, 'start'); }
|
||||
window.addEventListener('pointerup', endStroke, {once:true});
|
||||
}
|
||||
|
||||
function moveStroke(e){
|
||||
if(!drawing || !curStroke) return;
|
||||
const p = svgPoint(e);
|
||||
const t = now() - t0;
|
||||
const last = curStroke.points[curStroke.points.length-1];
|
||||
if(!last || Math.hypot(p.x-last.x, p.y-last.y) > 2){
|
||||
curStroke.points.push({...p,t});
|
||||
updatePath(curStroke);
|
||||
}
|
||||
}
|
||||
|
||||
function endStroke(){
|
||||
if(!curStroke) return;
|
||||
drawing = false;
|
||||
const pts = curStroke.points;
|
||||
curStroke.duration = pts.length? (pts[pts.length-1].t - pts[0].t) : 0;
|
||||
curStroke.length = polyLen(pts);
|
||||
if(markersCb.checked){ const last=pts[pts.length-1]; curStroke.stopMarker = mark(last.x,last.y,'stop'); }
|
||||
strokes.push(curStroke); curStroke = null; listRefresh();
|
||||
}
|
||||
|
||||
function mark(x,y,type){
|
||||
const r = 7; const c = document.createElementNS('http://www.w3.org/2000/svg','circle');
|
||||
c.setAttribute('cx',x); c.setAttribute('cy',y); c.setAttribute('r',r);
|
||||
c.setAttribute('class', type==='start'?'markerStart':'markerStop');
|
||||
markersG.appendChild(c); return c;
|
||||
}
|
||||
|
||||
function updatePath(s){ s.pathEl.setAttribute('d', toPathD(s.points)); s.pathEl.setAttribute('stroke-width', s.width); }
|
||||
|
||||
function polyLen(pts){ let L=0; for(let i=1;i<pts.length;i++){ const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; L += Math.hypot(dx,dy);} return L; }
|
||||
|
||||
function placeSpark(e){
|
||||
const p = svgPoint(e);
|
||||
const len = Math.max(14, parseInt(strokeInp.value,10)*1.5);
|
||||
const dx = 10, dy = -10;
|
||||
const l = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
l.setAttribute('x1', p.x); l.setAttribute('y1', p.y);
|
||||
l.setAttribute('x2', p.x+dx); l.setAttribute('y2', p.y+dy);
|
||||
l.setAttribute('stroke', colorSInp.value);
|
||||
l.setAttribute('stroke-width', Math.max(2, Math.round(parseInt(strokeInp.value,10)*0.66)));
|
||||
l.setAttribute('stroke-linecap','round');
|
||||
sparksG.appendChild(l);
|
||||
}
|
||||
|
||||
function placeBit(e){
|
||||
const p = svgPoint(e);
|
||||
const h = Math.max(18, parseInt(strokeInp.value,10)*1.2);
|
||||
const w = Math.max(6, parseInt(strokeInp.value,10)*0.8);
|
||||
const line1 = document.createElementNS('http://www.w3.org/2000/svg','line');
|
||||
line1.setAttribute('x1', p.x); line1.setAttribute('y1', p.y-h/2);
|
||||
line1.setAttribute('x2', p.x); line1.setAttribute('y2', p.y+h/2);
|
||||
line1.setAttribute('stroke', colorBInp.value);
|
||||
line1.setAttribute('stroke-width', w);
|
||||
line1.setAttribute('stroke-linecap', 'round');
|
||||
bitsG.appendChild(line1);
|
||||
}
|
||||
|
||||
function exportSVGString(){
|
||||
updateMeta();
|
||||
const clone = stage.cloneNode(true);
|
||||
// inline live colors
|
||||
clone.querySelector('#sparks')?.setAttribute('stroke', colorSInp.value);
|
||||
// serialize
|
||||
const s = new XMLSerializer().serializeToString(clone);
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
${s}`;
|
||||
}
|
||||
|
||||
function motionJSON(){
|
||||
const meta = { title:titleInp.value, desc:descInp.value, tag:tagInp.value, t0: t0??0 };
|
||||
const items = strokes.map(s=>{
|
||||
// speeds
|
||||
let maxV=0, sumV=0, nV=0; const pts=s.points;
|
||||
for(let i=1;i<pts.length;i++){
|
||||
const dx=pts[i].x-pts[i-1].x, dy=pts[i].y-pts[i-1].y; const dt=(pts[i].t-pts[i-1].t)/1000; if(dt<=0) continue;
|
||||
const v = Math.hypot(dx,dy)/dt; maxV=Math.max(maxV,v); sumV+=v; nV++;
|
||||
}
|
||||
const avgV = nV? sumV/nV : 0;
|
||||
return {
|
||||
id: s.id, mode: s.mode, colorA: s.colorA, colorB: s.colorB, width: s.width,
|
||||
startMs: s.start, durationMs: s.duration, lengthPx: s.length,
|
||||
avgSpeedPxPerS: +avgV.toFixed(2), maxSpeedPxPerS: +maxV.toFixed(2),
|
||||
points: s.points.map(p=>({x:p.x,y:p.y,tMs: Math.round(p.t)}))
|
||||
};
|
||||
});
|
||||
return JSON.stringify({meta, strokes:items}, null, 2);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
document.getElementById('undo').addEventListener('click', ()=>{ if(strokes.length){ removeStroke(strokes.length-1); }});
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
strokes.length=0; strokesG.innerHTML=''; bitsG.innerHTML=''; sparksG.innerHTML=''; markersG.innerHTML=''; listRefresh(); t0=null; });
|
||||
|
||||
copySVGBtn.addEventListener('click', async ()=>{ const svg=exportSVGString(); await navigator.clipboard.writeText(svg); alert('SVG kopiert'); });
|
||||
dlSVGBtn.addEventListener('click', ()=>{ const svg=exportSVGString(); const blob=new Blob([svg],{type:'image/svg+xml'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega-oneliner_multi.svg'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
copyJSONBtn.addEventListener('click', async ()=>{ const js=motionJSON(); await navigator.clipboard.writeText(js); alert('Motion‑JSON kopiert'); });
|
||||
dlJSONBtn.addEventListener('click', ()=>{ const js=motionJSON(); const blob=new Blob([js],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='omi-omega_motion.json'; a.click(); setTimeout(()=>URL.revokeObjectURL(a.href),1500); });
|
||||
|
||||
// Stage events
|
||||
stage.addEventListener('pointerdown', startStroke);
|
||||
stage.addEventListener('pointermove', moveStroke);
|
||||
window.addEventListener('pointerup', endStroke);
|
||||
|
||||
// Hotkeys
|
||||
window.addEventListener('keydown', (e)=>{
|
||||
if(e.key==='z' || e.key==='Z'){ if(strokes.length){ removeStroke(strokes.length-1); } }
|
||||
if((e.key==='s' || e.key==='S') && (e.ctrlKey||e.metaKey)){ e.preventDefault(); document.getElementById('downloadSVG').click(); }
|
||||
});
|
||||
|
||||
// Reactive
|
||||
[titleInp,descInp,tagInp].forEach(inp=>inp.addEventListener('input', updateMeta));
|
||||
[colorAInp,colorBInp,colorSInp].forEach(inp=>inp.addEventListener('input', ()=>{ tagText.setAttribute('fill','#cfd5e3'); }));
|
||||
|
||||
updateMeta();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
722
crumbblocks/rainbow_counter.html
Normal file
722
crumbblocks/rainbow_counter.html
Normal file
@@ -0,0 +1,722 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>🌈 Rainbow Counter v2 (ohne Anbindung)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Blockly + JS Generator + Deutsche Labels -->
|
||||
<script src="https://unpkg.com/blockly/blockly.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/javascript.min.js"></script>
|
||||
<script src="https://unpkg.com/blockly/msg/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #1e1e1e;
|
||||
--panel: #121212;
|
||||
--txt: #e8e8e8;
|
||||
--accent: #4caf50;
|
||||
--muted: #a0a0a0;
|
||||
--line: #2a2a2a
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--txt);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
padding: .6rem .8rem;
|
||||
border-bottom: 1px solid var(--line)
|
||||
}
|
||||
|
||||
header h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
header .sp {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .55rem .8rem;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
#wrap {
|
||||
display: flex;
|
||||
gap: .8rem;
|
||||
height: calc(100vh - 56px);
|
||||
padding: .6rem .8rem
|
||||
}
|
||||
|
||||
#left {
|
||||
flex: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem
|
||||
}
|
||||
|
||||
#right {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .6rem
|
||||
}
|
||||
|
||||
#blocklyDiv {
|
||||
height: 60vh;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
#output {
|
||||
flex: 1;
|
||||
min-height: 28vh;
|
||||
background: var(--panel);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
#cfg {
|
||||
background: var(--panel);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
white-space: pre-wrap;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.hint {
|
||||
color: var(--muted);
|
||||
font-size: .9rem
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #3c99dc
|
||||
}
|
||||
|
||||
.btn-warn {
|
||||
background: #e67e22
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h2>🧁 Crumbforest • Rainbow Counter v2</h2>
|
||||
<div class="sp"></div>
|
||||
<div class="row">
|
||||
<button id="btnDoc" title="Doku/Beispiel laden">📄 Doku</button>
|
||||
<button id="btnT1" class="btn-secondary" title="Testfall 1: Basisliste">T1</button>
|
||||
<button id="btnT2" class="btn-secondary" title="Testfall 2: Gleichstand">T2</button>
|
||||
<button id="btnT3" class="btn-secondary" title="Testfall 3: Debounce=1">T3</button>
|
||||
<button id="btnRnd" class="btn-warn" title="Zufalls-Demo laden">🎲 Zufall</button>
|
||||
<button id="btnClear" title="Leeren">🗑️ Neu</button>
|
||||
<button id="btnRun" title="Blockly-Code ausführen">▶️ Ausführen</button>
|
||||
<button id="btnCrew" style="background:#9c27b0" title="Ergebnis an die Crew senden">🚀 An Crew senden</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="wrap">
|
||||
<div id="left">
|
||||
<div id="blocklyDiv"></div>
|
||||
<pre id="output">🌲 Bereit. Lade „Doku“/„T1–T3“ oder baue eigene Logik und drücke ▶️.</pre>
|
||||
</div>
|
||||
<div id="right">
|
||||
<div id="cfg"></div>
|
||||
<div class="hint">
|
||||
Ziel-JSON: {"total_events","classes","dominant","mapping"} •
|
||||
Optional: Debounce (N), Gleichstand-Strategie (per Blocks).
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minimal-Toolbox -->
|
||||
<xml id="toolbox" style="display:none">
|
||||
<category name="Variablen" custom="VARIABLE"></category>
|
||||
<category name="Logik">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
</category>
|
||||
<category name="Schleifen">
|
||||
<block type="controls_repeat_ext"></block>
|
||||
<block type="controls_forEach"></block>
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Listen">
|
||||
<block type="lists_create_with"></block>
|
||||
</category>
|
||||
<category name="Text">
|
||||
<block type="text"></block>
|
||||
<block type="text_join"></block>
|
||||
<block type="text_print"></block>
|
||||
</category>
|
||||
<category name="Funktionen" custom="PROCEDURE"></category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === CFG: Parameter-Kit (nur Anzeige/Didaktik, kein Netzwerk) ===
|
||||
const CFG = {
|
||||
classes: ["red", "green", "blue", "yellow"],
|
||||
synonyms: { "purple": "unknown", "orange": "unknown" },
|
||||
normalize: "case_insensitive",
|
||||
window_n: 12,
|
||||
debounce_n: 0,
|
||||
dominant_mode: "max",
|
||||
tie_policy: "all",
|
||||
emit_mode: "final_only",
|
||||
reset_mode: "manual",
|
||||
include_ts: true,
|
||||
mapping: { red: "⚠️", green: "✅", blue: "ℹ️", yellow: "⏳", unknown: "❔" },
|
||||
rng_seed: 42,
|
||||
weights: { red: 1, green: 1, blue: 1, yellow: 1, unknown: 0.5 }
|
||||
};
|
||||
document.getElementById('cfg').textContent =
|
||||
'CFG (Gedankenstütze):\\n' + JSON.stringify(CFG, null, 2);
|
||||
|
||||
// === Blockly Setup ===
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos',
|
||||
grid: { spacing: 24, length: 3, colour: '#474747', snap: true },
|
||||
trashcan: true,
|
||||
zoom: { startScale: 1.1, maxScale: 2.0, minScale: .6, controls: false, wheel: true },
|
||||
move: { scrollbars: true, drag: true, wheel: true }
|
||||
});
|
||||
|
||||
// Vordefinierte Variablen (Zähler, Events, Dominant, optional last_ev)
|
||||
['c_red', 'c_green', 'c_blue', 'c_yellow', 'c_unknown', 'total', 'ev', 'dominant', 'last_ev']
|
||||
.forEach(v => { try { workspace.createVariable(v); } catch (_) { } });
|
||||
|
||||
// Output Helpers
|
||||
const $ = s => document.querySelector(s);
|
||||
function setOutput(t) { $('#output').textContent = t }
|
||||
function appendOutput(t) {
|
||||
const el = $('#output');
|
||||
el.textContent += (el.textContent.endsWith('\\n') ? '' : '\\n') + t;
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
window.appendOutput = appendOutput; // für generierten Code
|
||||
|
||||
// text_print → Panel (statt alert)
|
||||
// Wir suchen den Generator (je nach Version unterschiedlich)
|
||||
const gen = (typeof Blockly.JavaScript !== 'undefined') ? Blockly.JavaScript :
|
||||
(typeof javascript !== 'undefined' && javascript.javascriptGenerator) ? javascript.javascriptGenerator : null;
|
||||
|
||||
if (gen) {
|
||||
gen['text_print'] = function (block) {
|
||||
const argument0 = gen.valueToCode(block, 'TEXT', gen.ORDER_NONE) || '\'\'';
|
||||
return 'appendOutput(String(' + argument0 + '));\\n';
|
||||
};
|
||||
} else {
|
||||
console.error("CRUMBBLOCKS: Konnte JavaScript Generator nicht finden!");
|
||||
}
|
||||
|
||||
// XML Parser Fallback
|
||||
function textToDom(xmlText) {
|
||||
try { if (Blockly.utils?.xml?.textToDom) return Blockly.utils.xml.textToDom(xmlText); } catch (_) { }
|
||||
return new DOMParser().parseFromString(xmlText, 'text/xml').documentElement;
|
||||
}
|
||||
function loadXml(xml) { const dom = textToDom(xml); workspace.clear(); Blockly.Xml.domToWorkspace(dom, workspace); }
|
||||
|
||||
// === DOKU XML ===
|
||||
const DOC_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable>
|
||||
</variables>
|
||||
|
||||
<!-- DOKU -->
|
||||
<block type="text_print" x="20" y="20">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">DOC: Regenbogen-Zähl-Maschine – zählt Farbereignisse und erzeugt JSON-Report.</field></block></value>
|
||||
<next><block type="text_print"><value name="TEXT"><block type="text"><field name="TEXT">Eingabe (Demo): ["red","green","blue","blue","yellow","purple"]</field></block></value></block></next>
|
||||
</block>
|
||||
<block type="text_print" x="20" y="88">
|
||||
<value name="TEXT"><block type="text"><field name="TEXT">Erwartet: {"total_events","classes":{"red","green","blue","yellow","unknown"},"dominant","mapping"}</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="150">
|
||||
<field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
|
||||
<!-- Demo-Ereignisse -->
|
||||
<block type="controls_forEach" x="20" y="320">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="6"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD3"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">yellow</field></block></value>
|
||||
<value name="ADD5"><block type="text"><field name="TEXT">purple</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">yellow</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
</block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Dominant (einfach) -->
|
||||
<block type="variables_set" x="20" y="610">
|
||||
<field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- JSON Report -->
|
||||
<block type="text_print" x="20" y="650">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="22"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant": "</field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">", "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Testfälle ===
|
||||
const T1_XML = DOC_XML; // Basisliste, dominant=blue (einfach)
|
||||
|
||||
const T2_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">T2: Gleichstand red vs blue</field></block></value></block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="70"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
|
||||
<!-- Events: ["red","blue","red","blue"] -->
|
||||
<block type="controls_forEach" x="20" y="230">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="4"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">red</field></block></value>
|
||||
<value name="ADD3"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="1" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="text_print"><value name="TEXT"><block type="text"><field name="TEXT">Ignoriere andere.</field></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Gleichstand: dominant = "red,blue" (Liste als Text) -->
|
||||
<block type="variables_set" x="20" y="470"><field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">["red","blue"]</field></block></value>
|
||||
</block>
|
||||
|
||||
<!-- JSON Report -->
|
||||
<block type="text_print" x="20" y="520">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="24"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant_all": </field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">, "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
const T3_XML = `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables>
|
||||
<variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable>
|
||||
<variable>c_yellow</variable><variable>c_unknown</variable><variable>total</variable>
|
||||
<variable>ev</variable><variable>dominant</variable><variable>last_ev</variable>
|
||||
</variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">T3: Debounce 1 (identische Wiederholung wird ignoriert)</field></block></value></block>
|
||||
|
||||
<!-- Init -->
|
||||
<block type="variables_set" x="20" y="70"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
<block type="variables_set" x="20" y="230"><field name="VAR">last_ev</field><value name="VALUE"><block type="text"><field name="TEXT">_none_</field></block></value></block>
|
||||
|
||||
<!-- Events: ["blue","blue","green"] -->
|
||||
<block type="controls_forEach" x="20" y="280">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="3"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD1"><block type="text"><field name="TEXT">blue</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">green</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO">
|
||||
<block type="controls_if">
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">NEQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">last_ev</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0">
|
||||
<block type="variables_set">
|
||||
<field name="VAR">total</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">green</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value>
|
||||
<value name="B"><block type="text"><field name="TEXT">yellow</field></block></value></block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field>
|
||||
<value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
<next><block type="variables_set"><field name="VAR">last_ev</field><value name="VALUE"><block type="variables_get"><field name="VAR">ev</field></block></value></block></next>
|
||||
</statement>
|
||||
</block>
|
||||
|
||||
<!-- Dominant (einfach max-Entscheid) -->
|
||||
<block type="variables_set" x="20" y="620"><field name="VAR">dominant</field>
|
||||
<value name="VALUE"><block type="text"><field name="TEXT">unknown</field></block></value>
|
||||
</block>
|
||||
<block type="controls_if" x="20" y="660">
|
||||
<mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">red</field></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">green</field></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">GT</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="B"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
</block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">yellow</field></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block></statement>
|
||||
</block>
|
||||
|
||||
<!-- JSON -->
|
||||
<block type="text_print" x="20" y="830">
|
||||
<value name="TEXT">
|
||||
<block type="text_join">
|
||||
<mutation items="22"></mutation>
|
||||
<value name="ADD0"><block type="text"><field name="TEXT">{ "total_events": </field></block></value>
|
||||
<value name="ADD1"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="ADD2"><block type="text"><field name="TEXT">, "classes": { "red": </field></block></value>
|
||||
<value name="ADD3"><block type="variables_get"><field name="VAR">c_red</field></block></value>
|
||||
<value name="ADD4"><block type="text"><field name="TEXT">, "green": </field></block></value>
|
||||
<value name="ADD5"><block type="variables_get"><field name="VAR">c_green</field></block></value>
|
||||
<value name="ADD6"><block type="text"><field name="TEXT">, "blue": </field></block></value>
|
||||
<value name="ADD7"><block type="variables_get"><field name="VAR">c_blue</field></block></value>
|
||||
<value name="ADD8"><block type="text"><field name="TEXT">, "yellow": </field></block></value>
|
||||
<value name="ADD9"><block type="variables_get"><field name="VAR">c_yellow</field></block></value>
|
||||
<value name="ADD10"><block type="text"><field name="TEXT">, "unknown": </field></block></value>
|
||||
<value name="ADD11"><block type="variables_get"><field name="VAR">c_unknown</field></block></value>
|
||||
<value name="ADD12"><block type="text"><field name="TEXT"> }, "dominant": "</field></block></value>
|
||||
<value name="ADD13"><block type="variables_get"><field name="VAR">dominant</field></block></value>
|
||||
<value name="ADD14"><block type="text"><field name="TEXT">", "mapping": { "red": "⚠️", "green": "✅", "blue": "ℹ️", "yellow": "⏳", "unknown": "❔" } }</field></block></value>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</xml>
|
||||
`.trim();
|
||||
|
||||
// === Zufalls-Demo-Generator (nur Vorstellung, keine Sensorik) ===
|
||||
function randomDemoXml(n = CFG.window_n) {
|
||||
const colors = ['red', 'green', 'blue', 'yellow', 'purple', 'orange'];
|
||||
// einfacher RNG (konstant)
|
||||
let seed = CFG.rng_seed;
|
||||
function rnd() { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; }
|
||||
const arr = Array.from({ length: n }, () => colors[Math.floor(rnd() * colors.length)]);
|
||||
|
||||
const items = arr.map((c, i) => `
|
||||
<value name="ADD${i}"><block type="text"><field name="TEXT">${c}</field></block></value>`).join('');
|
||||
return `
|
||||
<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<variables><variable>ev</variable><variable>total</variable><variable>c_red</variable><variable>c_green</variable><variable>c_blue</variable><variable>c_yellow</variable><variable>c_unknown</variable><variable>dominant</variable></variables>
|
||||
<block type="text_print" x="20" y="20"><value name="TEXT"><block type="text"><field name="TEXT">🎲 Zufalls-Ereignisse: ${arr.join(', ')}</field></block></value></block>
|
||||
<block type="variables_set" x="20" y="80"><field name="VAR">c_red</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
<next><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value><next>
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_number"><field name="NUM">0</field></block></value>
|
||||
</next></next></next></next></next>
|
||||
</block>
|
||||
<block type="controls_forEach" x="20" y="260">
|
||||
<field name="VAR">ev</field>
|
||||
<value name="LIST"><block type="lists_create_with"><mutation items="${n}"></mutation>${items}</block></value>
|
||||
<statement name="DO">
|
||||
<block type="variables_set"><field name="VAR">total</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field>
|
||||
<value name="A"><block type="variables_get"><field name="VAR">total</field></block></value>
|
||||
<value name="B"><block type="math_number"><field name="NUM">1</field></block></value>
|
||||
</block></value>
|
||||
<next>
|
||||
<block type="controls_if"><mutation elseif="3" else="1"></mutation>
|
||||
<value name="IF0"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">red</field></block></value></block></value>
|
||||
<statement name="DO0"><block type="variables_set"><field name="VAR">c_red</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_red</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF1"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">green</field></block></value></block></value>
|
||||
<statement name="DO1"><block type="variables_set"><field name="VAR">c_green</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_green</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF2"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">blue</field></block></value></block></value>
|
||||
<statement name="DO2"><block type="variables_set"><field name="VAR">c_blue</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_blue</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<value name="IF3"><block type="logic_compare"><field name="OP">EQ</field><value name="A"><block type="variables_get"><field name="VAR">ev</field></block></value><value name="B"><block type="text"><field name="TEXT">yellow</field></block></value></block></value>
|
||||
<statement name="DO3"><block type="variables_set"><field name="VAR">c_yellow</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_yellow</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
<statement name="ELSE"><block type="variables_set"><field name="VAR">c_unknown</field><value name="VALUE"><block type="math_arithmetic"><field name="OP">ADD</field><value name="A"><block type="variables_get"><field name="VAR">c_unknown</field></block></value><value name="B"><block type="math_number"><field name="NUM">1</field></block></value></block></value></block></statement>
|
||||
</block>
|
||||
</next>
|
||||
</statement>
|
||||
</block>
|
||||
<block type="variables_set" x="20" y="560"><field name="VAR">dominant</field><value name="VALUE"><block type="text"><field name="TEXT">blue</field></block></value></block>
|
||||
<block type="text_print" x="20" y="600"><value name="TEXT"><block type="text"><field name="TEXT">→ Ergänze IF-Kette oder eigene Funktion für „dominant“.</field></block></value></block>
|
||||
</xml>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
// === Actions ===
|
||||
document.getElementById('btnDoc').onclick = () => { loadXml(DOC_XML); setOutput('📄 Doku geladen. Lies die DOC-Zeilen und drücke ▶️.'); };
|
||||
document.getElementById('btnT1').onclick = () => { loadXml(T1_XML); setOutput('🧪 T1 geladen (Basisliste). ▶️ für Report.'); };
|
||||
document.getElementById('btnT2').onclick = () => { loadXml(T2_XML); setOutput('🧪 T2 geladen (Gleichstand). ▶️ für Report mit dominant_all=["red","blue"].'); };
|
||||
document.getElementById('btnT3').onclick = () => { loadXml(T3_XML); setOutput('🧪 T3 geladen (Debounce=1). ▶️ für Report.'); };
|
||||
document.getElementById('btnRnd').onclick = () => { loadXml(randomDemoXml()); setOutput('🎲 Zufalls-Demo geladen. Ergänze Dominanz-Logik & ▶️.'); };
|
||||
document.getElementById('btnClear').onclick = () => { workspace.clear(); setOutput('🧹 Workspace geleert.'); };
|
||||
|
||||
document.getElementById('btnRun').onclick = () => {
|
||||
const gen = (typeof Blockly.JavaScript !== 'undefined') ? Blockly.JavaScript :
|
||||
(typeof javascript !== 'undefined' && javascript.javascriptGenerator) ? javascript.javascriptGenerator : null;
|
||||
|
||||
if (!gen) { alert("Fehler: Generator nicht geladen."); return; }
|
||||
|
||||
const code = gen.workspaceToCode(workspace);
|
||||
|
||||
// Safety: Falls der Generator doch alert() nutzt, fangen wir das ab.
|
||||
const oldAlert = window.alert;
|
||||
window.alert = function (msg) { appendOutput(String(msg)); };
|
||||
|
||||
try {
|
||||
new Function(code)();
|
||||
appendOutput('\\n✅ Fertig.');
|
||||
}
|
||||
catch (e) { appendOutput('\\n❌ Fehler: ' + e); }
|
||||
finally {
|
||||
window.alert = oldAlert; // Restore
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementById('btnCrew').onclick = async () => {
|
||||
const outTxt = document.getElementById('output').textContent;
|
||||
// Try to find the JSON part
|
||||
const jsonMatch = outTxt.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(jsonMatch[0]);
|
||||
alert('🚀 Daten kopiert!\n\nGehe jetzt in dein Terminal und führe aus:\n./missions/evaluate_mission_data.sh\n\n(Dort kannst du die Daten dann einfügen)');
|
||||
} catch (err) {
|
||||
alert('❌ Konnte nicht in die Zwischenablage kopieren: ' + err);
|
||||
}
|
||||
} else {
|
||||
alert('⚠️ Kein gültiges JSON-Ergebnis gefunden!\nBitte führe erst den Code aus (▶️).');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
31
crumbblocks/readme.txt
Normal file
31
crumbblocks/readme.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
# Schnippsi Painter — Streaming Quickstart
|
||||
|
||||
## 1) Datei
|
||||
Öffne `schnippsi_painter_stream.html` im Browser. Zeichne frei oder mit dem Bezier‑Tool.
|
||||
- `Verbinden` stellt eine WebSocket-Verbindung zu **ws://localhost:9980** her.
|
||||
- Alle Events werden als JSON gesendet:
|
||||
- `session` — Sessionstart
|
||||
- `strokeStart` — neues Stroke (tool, colors, width)
|
||||
- `point` — Punkte beim Freihandzeichnen (x,y,t,pressure,tilt)
|
||||
- `anchor` / `anchors` — Bezier-Anker & Griffe (inkl. h1/h2)
|
||||
- `strokeEnd` — Stroke abgeschlossen
|
||||
|
||||
## 2) TouchDesigner (ohne extra Code)
|
||||
- Füge **WebSocket DAT** hinzu, setze **Mode = Server** und **Port = 9980**.
|
||||
- Verbinde Browser (Button `Verbinden`).
|
||||
- Hänge einen **JSON DAT** an den WebSocket-Ausgang (zum Parsen).
|
||||
- Alternativ direkt WebSocket DAT -> **WebSocket In CHOP** (vereinfachte Skalare), für volle Struktur aber JSON DAT + Python DAT-Execute.
|
||||
- Beispiel: Aus `point`-Messages `x,y` in einen **Trail CHOP** oder zum Zeichnen in einen **TOP**.
|
||||
|
||||
## 3) Test ohne TouchDesigner (Node)
|
||||
Optionale Mini‑WS:
|
||||
```bash
|
||||
npm i ws
|
||||
node mock_ws_server.js
|
||||
```
|
||||
Dann im Painter `ws://localhost:9980` verwenden.
|
||||
|
||||
## 4) Export
|
||||
Buttons: JSON, SVG, PNG — jeweils Download.
|
||||
|
||||
A11y: Tastaturbedienung, ARIA‑Status, hoher Kontrast, 200% Zoom stabil.
|
||||
687
crumbblocks/solar_kettle_dark.html
Normal file
687
crumbblocks/solar_kettle_dark.html
Normal file
@@ -0,0 +1,687 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>☀️ Solar Wasserkocher</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="lib/blockly/blockly.min.js"></script>
|
||||
<script src="lib/blockly/javascript.min.js"></script>
|
||||
<script src="lib/blockly/de.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #05140a;
|
||||
/* Deep Forest Black */
|
||||
--panel: #0d2615;
|
||||
/* Dark Moss */
|
||||
--txt: #e0f2f1;
|
||||
/* Mint White */
|
||||
--accent: #00e676;
|
||||
/* Vibrant Mint */
|
||||
--accent-hover: #b9f6ca;
|
||||
--border: #1b5e20;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--txt);
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 15px 20px;
|
||||
background: rgba(13, 38, 21, 0.95);
|
||||
border-bottom: 2px solid var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 230, 118, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--accent);
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
color: var(--accent);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: rgba(0, 230, 118, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 10px rgba(0, 230, 118, 0.2);
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: var(--accent);
|
||||
color: #003d15;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
background: var(--accent-hover);
|
||||
color: #000;
|
||||
box-shadow: 0 0 15px var(--accent);
|
||||
}
|
||||
|
||||
button.action {
|
||||
border-color: #ff9800;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
button.action:hover {
|
||||
background: rgba(255, 152, 0, 0.1);
|
||||
box-shadow: 0 2px 10px rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
#main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#blocklyDiv {
|
||||
flex: 3;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
#simDiv {
|
||||
flex: 2;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
background: var(--panel);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Visualization */
|
||||
#vis {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(to bottom, #87CEEB 0%, #E0F7FA 100%);
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Elements */
|
||||
.sun {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #FFD700;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 40px #FFD700;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: -100px;
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 30px;
|
||||
transition: left 2s linear;
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
.kettle-base {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 120px;
|
||||
height: 160px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border: 2px solid #555;
|
||||
border-radius: 10px 10px 20px 20px;
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.water {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 20%;
|
||||
background: #2196F3;
|
||||
opacity: 0.8;
|
||||
border-radius: 0 0 18px 18px;
|
||||
transition: height 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
.bubbles {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
animation: rise 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
0% {
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
bottom: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
width: 60px;
|
||||
height: 80px;
|
||||
background: #111;
|
||||
border: 2px solid #666;
|
||||
transform: skewX(-10deg);
|
||||
}
|
||||
|
||||
.wire {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 80px;
|
||||
width: 80px;
|
||||
height: 5px;
|
||||
background: #333;
|
||||
transform: rotate(-5deg);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.info-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#log {
|
||||
flex: 1;
|
||||
background: #111;
|
||||
color: #0f0;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.heating .kettle-base {
|
||||
box-shadow: 0 0 20px red;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
/* === BLOCKLY DEEP FOREST OVERRIDES === */
|
||||
.blocklySvg {
|
||||
background-color: var(--bg) !important;
|
||||
}
|
||||
|
||||
/* Toolbox (Sidebar) - Dark Grey High Contrast */
|
||||
.blocklyToolboxDiv {
|
||||
background-color: #121212 !important;
|
||||
color: #eeeeee !important;
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Category Labels */
|
||||
.blocklyTreeLabel {
|
||||
font-family: 'Segoe UI', sans-serif !important;
|
||||
color: #bbbbbb !important;
|
||||
}
|
||||
|
||||
/* Rows */
|
||||
.blocklyTreeRow {
|
||||
border-left: 4px solid transparent;
|
||||
margin-bottom: 2px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.blocklyTreeRow:hover {
|
||||
background-color: #1e1e1e !important;
|
||||
border-left-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Selected */
|
||||
.blocklyTreeSelected .blocklyTreeRow {
|
||||
background-color: #0d2615 !important;
|
||||
/* Moss background for active */
|
||||
border-left-color: var(--accent) !important;
|
||||
}
|
||||
|
||||
.blocklyTreeSelected .blocklyTreeLabel {
|
||||
color: var(--accent) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Flyout (Popup) */
|
||||
.blocklyFlyoutBackground {
|
||||
fill: #181818 !important;
|
||||
fill-opacity: 0.98 !important;
|
||||
}
|
||||
|
||||
/* Text in Flyout headers */
|
||||
.blocklyFlyoutLabelText {
|
||||
fill: var(--txt) !important;
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
.blocklyScrollbarHandle {
|
||||
fill: var(--accent) !important;
|
||||
fill-opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
/* === DROPDOWNS & MENUS (The "Helle Overlay" Fix) === */
|
||||
.blocklyDropDownDiv,
|
||||
.blocklyWidgetDiv .goog-menu,
|
||||
.goog-menu {
|
||||
background-color: #121212 !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.8) !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
/* Content Text in Menus */
|
||||
.goog-menuitem-content,
|
||||
.goog-menuitem,
|
||||
.blocklyDropDownDiv .goog-menuitem-content {
|
||||
color: #eeeeee !important;
|
||||
background-color: transparent !important;
|
||||
font-family: 'Segoe UI', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Hover State in Menus */
|
||||
.goog-menuitem-highlight,
|
||||
.goog-menuitem:hover {
|
||||
background-color: rgba(0, 230, 118, 0.2) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Input Fields (Number editing) */
|
||||
.blocklyHtmlInput {
|
||||
background-color: #05140a !important;
|
||||
color: var(--accent) !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
border-radius: 4px !important;
|
||||
font-family: monospace !important;
|
||||
padding: 2px 5px !important;
|
||||
}
|
||||
|
||||
/* Tooltips */
|
||||
.blocklyTooltipDiv {
|
||||
background-color: #0d2615 !important;
|
||||
color: var(--txt) !important;
|
||||
border: 1px solid var(--accent) !important;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
/* === FINAL UI POLISH === */
|
||||
#main {
|
||||
border: 2px solid #4fc3f7 !important;
|
||||
/* Light Blue Accent */
|
||||
box-shadow: 0 0 15px rgba(79, 195, 247, 0.1);
|
||||
}
|
||||
|
||||
/* Force Toolbox Background deeper */
|
||||
.blocklyToolboxContents,
|
||||
.blocklyTreeRoot,
|
||||
.blocklyToolbox {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h2>☀️ Solar-Wasserkocher Sim</h2>
|
||||
<span id="status" style="font-size: 0.9rem; color: #aaa;">Bereit.</span>
|
||||
<button onclick="runCode()" class="primary">▶️ Starten</button>
|
||||
<button onclick="resetSim()">🔄 Reset</button>
|
||||
<button onclick="exportData()" class="action">🚀 An Crew senden</button>
|
||||
</header>
|
||||
|
||||
<div id="main">
|
||||
<div id="blocklyDiv"></div>
|
||||
<div id="simDiv">
|
||||
<div id="vis">
|
||||
<div class="sun" id="sun"></div>
|
||||
<div class="cloud" id="cloud"></div>
|
||||
<div class="panel"></div>
|
||||
<div class="wire"></div>
|
||||
<div class="kettle-base" id="kettle">
|
||||
<div class="water" id="water"></div>
|
||||
<div class="bubbles" id="bubbles"></div>
|
||||
</div>
|
||||
<div class="info-overlay">
|
||||
Temp: <span id="valTemp">20.0</span> °C<br>
|
||||
Power: <span id="valPower">0</span> W<br>
|
||||
Energie: <span id="valEnergy">0</span> kJ<br>
|
||||
Zeit: <span id="valTime">0</span> s
|
||||
</div>
|
||||
</div>
|
||||
<div id="log">Logs:<br></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<xml id="toolbox" style="display: none">
|
||||
<category name="Sensoren" colour="180">
|
||||
<block type="sensor_temp"></block>
|
||||
<block type="sensor_power"></block>
|
||||
</category>
|
||||
<category name="Aktor" colour="30">
|
||||
<block type="heater_switch"></block>
|
||||
<block type="wait_seconds"></block>
|
||||
</category>
|
||||
<category name="Logik" colour="120">
|
||||
<block type="controls_if"></block>
|
||||
<block type="logic_compare"></block>
|
||||
<block type="logic_operation"></block>
|
||||
<block type="logic_boolean"></block>
|
||||
</category>
|
||||
<category name="Schleifen" colour="150">
|
||||
<block type="controls_whileUntil"></block>
|
||||
</category>
|
||||
<category name="Mathe" colour="200">
|
||||
<block type="math_number"></block>
|
||||
<block type="math_arithmetic"></block>
|
||||
</category>
|
||||
<category name="Variablen" custom="VARIABLE" colour="90"></category>
|
||||
</xml>
|
||||
|
||||
<script>
|
||||
// === PHYSICS ENGINE ===
|
||||
const SIM = {
|
||||
running: false,
|
||||
time: 0,
|
||||
dt: 0.1, // 100ms steps
|
||||
cloudPos: -100,
|
||||
|
||||
// State
|
||||
temp: 20.0,
|
||||
volume: 1.0, // Liter (kg)
|
||||
heaterOn: false,
|
||||
energyUsed: 0.0, // kJ
|
||||
|
||||
// Constants
|
||||
c: 4.184, // kJ/(kg*K) specific heat water
|
||||
maxPower: 2000, // Watt at full sun
|
||||
lossFactor: 0.05, // Cooling per degree delta T
|
||||
|
||||
// External
|
||||
sunIntensity: 1.0, // 0..1
|
||||
|
||||
reset() {
|
||||
this.running = false;
|
||||
this.time = 0;
|
||||
this.temp = 20.0;
|
||||
this.heaterOn = false;
|
||||
this.energyUsed = 0.0;
|
||||
this.sunIntensity = 1.0;
|
||||
this.cloudPos = -100;
|
||||
updateVis();
|
||||
log("Sim reset.");
|
||||
},
|
||||
|
||||
step() {
|
||||
if (!this.running) return;
|
||||
|
||||
// Environment logic
|
||||
this.time += this.dt;
|
||||
this.cloudPos += 0.5;
|
||||
if (this.cloudPos > 400) this.cloudPos = -150;
|
||||
|
||||
// Sun logic (cloud blocks sun)
|
||||
const cloudCenter = this.cloudPos + 60;
|
||||
const panelCenter = 40; // Approx css left
|
||||
let dist = Math.abs(cloudCenter - panelCenter);
|
||||
this.sunIntensity = (dist < 80) ? 0.2 : 1.0;
|
||||
|
||||
// Physics
|
||||
const currentPowerW = this.heaterOn ? (this.maxPower * this.sunIntensity) : 0;
|
||||
const heatingKJ = (currentPowerW / 1000) * this.dt;
|
||||
|
||||
const deltaT_heat = heatingKJ / (this.volume * this.c);
|
||||
const deltaT_cool = (this.temp - 20) * this.lossFactor * this.dt;
|
||||
|
||||
this.temp += (deltaT_heat - deltaT_cool);
|
||||
this.energyUsed += heatingKJ;
|
||||
|
||||
if (this.temp > 100) this.temp = 100; // Cap at boiling (energy wasted phase) -> actually lets cap valid boiling, but track waste?
|
||||
// Let's cap at 120 for "Explosion" visualization? kept simple for now.
|
||||
|
||||
updateVis();
|
||||
}
|
||||
};
|
||||
|
||||
function updateVis() {
|
||||
document.getElementById('valTemp').innerText = SIM.temp.toFixed(1);
|
||||
document.getElementById('valPower').innerText = SIM.heaterOn ? (SIM.maxPower * SIM.sunIntensity).toFixed(0) : "0";
|
||||
document.getElementById('valEnergy').innerText = SIM.energyUsed.toFixed(1);
|
||||
document.getElementById('valTime').innerText = SIM.time.toFixed(1);
|
||||
|
||||
// Kettle
|
||||
const h = Math.min(SIM.temp, 100);
|
||||
document.getElementById('water').style.height = (20 + (h / 100) * 60) + "%"; // Mock level
|
||||
|
||||
// Color: Blue -> Red
|
||||
const r = Math.min(255, (SIM.temp - 20) * 3);
|
||||
const b = Math.max(0, 255 - (SIM.temp - 20) * 3);
|
||||
document.getElementById('water').style.backgroundColor = `rgb(${r}, 0, ${b})`;
|
||||
|
||||
// Bubbles
|
||||
document.getElementById('bubbles').style.display = (SIM.temp > 95) ? 'block' : 'none';
|
||||
|
||||
// Glow
|
||||
const kettle = document.getElementById('kettle');
|
||||
if (SIM.heaterOn) kettle.classList.add('heating'); else kettle.classList.remove('heating');
|
||||
|
||||
// Cloud
|
||||
document.getElementById('cloud').style.left = SIM.cloudPos + "px";
|
||||
|
||||
// Sun dim
|
||||
document.getElementById('sun').style.opacity = SIM.sunIntensity;
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
const l = document.getElementById('log');
|
||||
l.innerHTML += `> ${msg}<br>`;
|
||||
l.scrollTop = l.scrollHeight;
|
||||
}
|
||||
|
||||
// === BLOCKLY SETUP ===
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "sensor_temp",
|
||||
"message0": "🌡️ Temperatur (°C)",
|
||||
"output": "Number",
|
||||
"colour": 230,
|
||||
"tooltip": "Misst die Wassertemperatur"
|
||||
},
|
||||
{
|
||||
"type": "sensor_power",
|
||||
"message0": "☀️ Verfügbare Power (W)",
|
||||
"output": "Number",
|
||||
"colour": 230,
|
||||
"tooltip": "Zeigt wie viel Sonnenenergie da ist"
|
||||
},
|
||||
{
|
||||
"type": "heater_switch",
|
||||
"message0": "🔥 Heizung %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "STATE",
|
||||
"options": [["AN", "ON"], ["AUS", "OFF"]]
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": 160,
|
||||
"tooltip": "Schaltet den Heizstab"
|
||||
},
|
||||
{
|
||||
"type": "wait_seconds",
|
||||
"message0": "⏳ Warte %1 Sek.",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "SECONDS",
|
||||
"value": 1,
|
||||
"min": 0.1
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": 160,
|
||||
"tooltip": "Wartet und lässt Zeit vergehen"
|
||||
}
|
||||
]);
|
||||
|
||||
const workspace = Blockly.inject('blocklyDiv', {
|
||||
toolbox: document.getElementById('toolbox'),
|
||||
theme: Blockly.Themes.Dark,
|
||||
renderer: 'zelos'
|
||||
});
|
||||
|
||||
// Code Generators
|
||||
javascript.javascriptGenerator.forBlock['sensor_temp'] = function (block) {
|
||||
return ['getTemp()', javascript.Order.ATOMIC];
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['sensor_power'] = function (block) {
|
||||
return ['getPower()', javascript.Order.ATOMIC];
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['heater_switch'] = function (block) {
|
||||
var state = block.getFieldValue('STATE');
|
||||
return 'setHeater("' + state + '");\n';
|
||||
};
|
||||
javascript.javascriptGenerator.forBlock['wait_seconds'] = function (block) {
|
||||
var sec = block.getFieldValue('SECONDS');
|
||||
return 'await wait(' + sec + ');\n';
|
||||
};
|
||||
|
||||
// === INTERFACE FOR GENERATED CODE ===
|
||||
function getTemp() { return SIM.temp; }
|
||||
function getPower() { return SIM.sunIntensity * SIM.maxPower; }
|
||||
function setHeater(state) {
|
||||
SIM.heaterOn = (state === 'ON');
|
||||
log(`Heizung: ${state}`);
|
||||
updateVis();
|
||||
}
|
||||
function wait(sec) {
|
||||
return new Promise(resolve => setTimeout(resolve, sec * 1000));
|
||||
}
|
||||
|
||||
// Safety Stepper
|
||||
async function _step(id) {
|
||||
workspace.highlightBlock(id);
|
||||
await new Promise(r => setTimeout(r, 10)); // Minimal delay to keep UI alive
|
||||
}
|
||||
|
||||
let loopInterval;
|
||||
|
||||
async function runCode() {
|
||||
resetSim();
|
||||
SIM.running = true;
|
||||
document.getElementById('status').innerText = "Läuft...";
|
||||
|
||||
// Start Physics Loop independent of Code
|
||||
if (loopInterval) clearInterval(loopInterval);
|
||||
loopInterval = setInterval(() => SIM.step(), SIM.dt * 1000); // 100ms real time = 0.1s sim time
|
||||
|
||||
// Generator Config
|
||||
javascript.javascriptGenerator.STATEMENT_PREFIX = 'await _step(%1);\n';
|
||||
javascript.javascriptGenerator.addReservedWords('code');
|
||||
|
||||
var code = javascript.javascriptGenerator.workspaceToCode(workspace);
|
||||
|
||||
try {
|
||||
// Wrap in async function
|
||||
const wrappedCode = `(async () => {
|
||||
${code}
|
||||
log("🏁 Programm Ende");
|
||||
SIM.running = false;
|
||||
clearInterval(loopInterval);
|
||||
document.getElementById('status').innerText = "Fertig.";
|
||||
})();`;
|
||||
|
||||
eval(wrappedCode);
|
||||
} catch (e) {
|
||||
log("❌ Fehler: " + e);
|
||||
SIM.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetSim() {
|
||||
SIM.reset();
|
||||
if (loopInterval) clearInterval(loopInterval);
|
||||
document.getElementById('log').innerHTML = "Logs:<br>";
|
||||
document.getElementById('status').innerText = "Reset.";
|
||||
workspace.highlightBlock(null);
|
||||
}
|
||||
|
||||
async function exportData() {
|
||||
const data = {
|
||||
mission: "solar_kettle",
|
||||
energy_kj: SIM.energyUsed.toFixed(2),
|
||||
final_temp: SIM.temp.toFixed(1),
|
||||
sim_time: SIM.time.toFixed(1),
|
||||
code_summary: "Blockly Code ausgeführt", // Could be more creating
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
try {
|
||||
await navigator.clipboard.writeText(json);
|
||||
alert("Daten kopiert!\n\n" + json + "\n\n-> ./evaluate_mission_data.sh");
|
||||
} catch (e) {
|
||||
alert("Clipboard Fehler: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "asciimonster"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an ASCII-Monster\""
|
||||
exit 0
|
||||
|
||||
214
crumbforest_roles/bashpanda_zero.sh
Executable file
214
crumbforest_roles/bashpanda_zero.sh
Executable file
@@ -0,0 +1,214 @@
|
||||
#!/bin/bash
|
||||
# 🐼 BashPanda - Der Kung Fu Meister des Terminals
|
||||
# Geduld, Präzision, und der Weg des Codes
|
||||
|
||||
# Load .env if exists
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="${SCRIPT_DIR}/../.env"
|
||||
|
||||
if [[ -f "${ENV_FILE}" ]]; then
|
||||
set -a
|
||||
source "${ENV_FILE}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
QUESTION="$*"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
MODEL="${OPENROUTER_MODEL:-openai/gpt-3.5-turbo}"
|
||||
|
||||
# Logs
|
||||
LOGDIR="${CRUMB_LOGS_DIR:-$HOME/.bashpanda_logs}/bashpanda"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/bashpanda_history.json"
|
||||
TMP_REQUEST="$LOGDIR/bashpanda_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/bashpanda_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
# === CREW MEMORY FUNCTIONS ===
|
||||
|
||||
function read_crew_memory() {
|
||||
local role="$1"
|
||||
local role_log="${CRUMB_LOGS_DIR}/${role}/${role}_history.json"
|
||||
|
||||
# Fallback to old location
|
||||
if [[ ! -f "$role_log" ]]; then
|
||||
role_log="$HOME/.${role}_logs/${role}_history.json"
|
||||
fi
|
||||
|
||||
if [[ -f "$role_log" ]]; then
|
||||
jq -r '.[-3:] | .[] | "[\(.role)]: \(.content)"' "$role_log" 2>/dev/null | head -n 3
|
||||
fi
|
||||
}
|
||||
|
||||
# === MAIN ===
|
||||
|
||||
echo "🐼 BashPanda betritt das Dojo..."
|
||||
echo "🥋 *verbeugung* 🥋"
|
||||
echo ""
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Ohne Schlüssel öffnet sich keine Tür zum Wissen."
|
||||
echo " Erstelle eine .env Datei mit deinem OPENROUTER_API_KEY."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "bashpanda"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an BashPanda\""
|
||||
echo "🐼 Der Meister wartet auf deine Frage."
|
||||
echo ""
|
||||
echo " \"Es gibt keine dummen Fragen,"
|
||||
echo " nur ungestellte Fragen, die dich nicht weiterbringen.\""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "🐼 BashPanda hört: \"$QUESTION\""
|
||||
echo ""
|
||||
|
||||
# Check if question references other crew members
|
||||
CREW_CONTEXT=""
|
||||
if echo "$QUESTION" | grep -qi "funkfox\|dumbo\|deepbit\|schnippi\|tobi\|maya"; then
|
||||
echo "🥋 BashPanda konsultiert die anderen Meister..."
|
||||
|
||||
for member in funkfox dumbosql deepbit schnippsi tobi mayaeule; do
|
||||
if echo "$QUESTION" | grep -qi "$member"; then
|
||||
MEMBER_CONTEXT=$(read_crew_memory "$member")
|
||||
if [[ -n "$MEMBER_CONTEXT" ]]; then
|
||||
CREW_CONTEXT="${CREW_CONTEXT}\n\n${member^} hat kürzlich gelehrt:\n${MEMBER_CONTEXT}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Build system prompt
|
||||
SYSTEM_PROMPT="Du bist BashPanda – der Kung Fu Meister des Bash-Terminals im Crumbforest.
|
||||
Du lehrst Bash-Programmierung als eine Form der Kampfkunst.
|
||||
|
||||
Deine Philosophie:
|
||||
- \"Der Weg des Codes ist wie der Weg der Kampfkunst: Geduld, Präzision, Wiederholung\"
|
||||
- Jede Zeile Code ist eine Kata - übe bis zur Perfektion
|
||||
- Fehler sind keine Niederlagen, sondern Lehrmeister
|
||||
- \"Ein Meister war einst ein Anfänger, der nie aufgab\"
|
||||
- Progressive Gürtel: 🖤 Schwarz → 💖 Pink → 💙 Blau → 💚 Grün → 💛 Gelb → 🤍 Weiss
|
||||
|
||||
Deine Expertise:
|
||||
- Bash-Grundlagen (echo, Variablen, Benutzereingaben)
|
||||
- Kontrollstrukturen (if/then/else, while, for)
|
||||
- Arrays und Datenstrukturen
|
||||
- Pattern Matching (grep, sed, regex)
|
||||
- Funktionen und Prozesse
|
||||
- System-Administration
|
||||
|
||||
Deine Art zu lehren:
|
||||
- Geduldig und weise, aber mit Humor (wie Kung Fu Panda)
|
||||
- Nutze Kampfkunst-Metaphern: \"if/then ist wie Angriff/Parade\"
|
||||
- \"Eine Variable ist wie ein Bambus - biegsam aber stark\"
|
||||
- \"Pipes | sind wie die Energie die durch den Körper fließt\"
|
||||
- Jede Lektion endet mit einer kleinen Weisheit
|
||||
- Du nutzt Emojis: 🐼 🥋 🎋 ⚡ 💪 🧘
|
||||
|
||||
Du arbeitest mit:
|
||||
- FunkFox für rhythmische Erklärungen
|
||||
- DumboSQL für Datenstrukturen
|
||||
- Deepbit für technische Tiefe
|
||||
- Taichi Taube für Balance und Geduld
|
||||
|
||||
WICHTIG:
|
||||
- Erkläre Konzepte Schritt für Schritt, wie ein Trainer
|
||||
- Nutze Beispiele aus der Natur und Kampfkunst
|
||||
- Ermutige zum Üben: \"Führe diesen Befehl aus, fühle wie er wirkt\"
|
||||
- Am Ende jeder Antwort: Eine kurze Weisheit oder Motivation
|
||||
|
||||
Du antwortest in der Sprache der Frage (meist Deutsch).
|
||||
Lehre mit der Ruhe eines Meisters und dem Humor eines Pandas! 🐼"
|
||||
|
||||
# Add crew context if available
|
||||
if [[ -n "$CREW_CONTEXT" ]]; then
|
||||
SYSTEM_PROMPT="${SYSTEM_PROMPT}\n\nWeisheit von den anderen Meistern:${CREW_CONTEXT}"
|
||||
fi
|
||||
|
||||
# Create API request
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system "$SYSTEM_PROMPT" \
|
||||
--arg user "$QUESTION" \
|
||||
'{
|
||||
"model": $model,
|
||||
"temperature": 0.8,
|
||||
"messages": [
|
||||
{"role": "system", "content": $system},
|
||||
{"role": "user", "content": $user}
|
||||
]
|
||||
}' > "$TMP_REQUEST"
|
||||
|
||||
# Send request
|
||||
echo "💭 BashPanda meditiert über deine Frage..."
|
||||
echo "🎋 *Bambusblätter rascheln im Wind* 🎋"
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Der Meister schweigt."
|
||||
echo " (Ein Fehler ist aufgetreten)"
|
||||
echo ""
|
||||
echo "Debug: $(cat "$TMP_RESPONSE")"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "🐼 BashPanda lehrt:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$RESPONSE_TEXT"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🥋 Übe diese Technik, bis sie Teil von dir wird."
|
||||
echo ""
|
||||
|
||||
# Store conversation in history
|
||||
jq -n --arg role "user" --arg content "$QUESTION" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry_user.json"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry_assistant.json"
|
||||
|
||||
jq -s '.[0] + [.[1]] + [.[2]]' "$HISTORY_FILE" "$LOGDIR/new_entry_user.json" "$LOGDIR/new_entry_assistant.json" > "$LOGDIR/new_history.json" && \
|
||||
mv "$LOGDIR/new_history.json" "$HISTORY_FILE" && \
|
||||
rm "$LOGDIR/new_entry_user.json" "$LOGDIR/new_entry_assistant.json"
|
||||
fi
|
||||
|
||||
# Token Tracking
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null 2>&1; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
TOKENS_USED=$(jq -r '.usage.total_tokens' "$TMP_RESPONSE")
|
||||
PROMPT_TOKENS=$(jq -r '.usage.prompt_tokens' "$TMP_RESPONSE")
|
||||
COMPLETION_TOKENS=$(jq -r '.usage.completion_tokens' "$TMP_RESPONSE")
|
||||
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "bashpanda" \
|
||||
--arg model "$MODEL" \
|
||||
--argjson usage "$(jq '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, model: $model, usage: $usage}' >> "$LOG_FILE"
|
||||
|
||||
echo "📊 Training absolviert:"
|
||||
echo " Frage: ${PROMPT_TOKENS} Tokens"
|
||||
echo " Antwort: ${COMPLETION_TOKENS} Tokens"
|
||||
echo " Gesamt: ${TOKENS_USED} Tokens"
|
||||
echo ""
|
||||
echo "💡 \"Jede Frage kostet Energie - stelle sie weise.\""
|
||||
fi
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "bugsy"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Bugsy – a friendly bug who helps children understand error messages in a simple, kind and encouraging way. You always respond in the language of the question." \
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "crabbyrust"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Crabby\""
|
||||
exit 0
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "deepbit"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Deepbit – a poetic octopus who explains Bash shell concepts to children using loops, images, and metaphors. Respond in the language the question is asked." \
|
||||
|
||||
@@ -49,6 +49,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "dumbosql"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Dumbo\""
|
||||
exit 0
|
||||
|
||||
@@ -50,6 +50,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "funkfox"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an FunkFox\""
|
||||
echo "🎤 Gib mir 'nen Beat, dann flow ich los!"
|
||||
|
||||
@@ -89,6 +89,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "mayaeule"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an die Eule\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "pepperphp"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Pepper\""
|
||||
exit 0
|
||||
|
||||
@@ -51,6 +51,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schnecki"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Schnecki\""
|
||||
exit 0
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schnippsi"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are Schnippsi – a playful UI/UX ninja who explains HTML, CSS, and accessibility to children. Always answer in the child's language, based on the input." \
|
||||
|
||||
@@ -51,6 +51,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "schraubaer"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Schraubbär\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "snakepy"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Snake\""
|
||||
exit 0
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "spider"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Spider\""
|
||||
exit 0
|
||||
|
||||
@@ -49,6 +49,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "taichitaube"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Taichi Taube\""
|
||||
exit 0
|
||||
|
||||
@@ -5,13 +5,14 @@ API_KEY="$OPENROUTER_API_KEY"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
|
||||
# Verzeichnisse
|
||||
LOG_DIR="/home/zero/.templatus_logs"
|
||||
LOG_DIR="${CRUMB_LOGS_DIR:-$HOME/.crumbforest_logs}/templatus"
|
||||
HISTORY_FILE="$LOG_DIR/templatus_history.json"
|
||||
TOKEN_LOG="$LOG_DIR/token_log.json"
|
||||
TMP_REQUEST="/tmp/templatus_request.json"
|
||||
TMP_RESPONSE="/tmp/templatus_response.json"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
|
||||
# JSON Payload vorbereiten
|
||||
cat <<EOF > "$TMP_REQUEST"
|
||||
@@ -33,6 +34,17 @@ EOF
|
||||
|
||||
echo "🏗️ Templatus denkt nach über: $QUESTION"
|
||||
|
||||
# API-Key prüfen
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ No API key set. Use: export OPENROUTER_API_KEY=..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "templatus"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Anfrage senden
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
|
||||
@@ -21,6 +21,11 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "tobi"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "You are CapaciTobi, the clever squirrel of the Crumbforest. You explain electronic components, especially capacitors, in a child-friendly and playful way. You speak in rhymes, analogies or simple metaphors – but stay technically accurate. Respond in the language of the child asking (e.g., German, English, etc.)." \
|
||||
|
||||
@@ -48,6 +48,16 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source waldwaechter library for token budget check
|
||||
if [[ -f "${SCRIPT_DIR}/../lib/waldwaechter.sh" ]]; then
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
fi
|
||||
|
||||
# 💰 Prüfe Token-Budget (Kinderschutz)
|
||||
if ! check_token_budget "vektor"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$QUESTION" ]; then
|
||||
echo "💡 Verwendung: $0 \"Deine Frage an Vektor\""
|
||||
exit 0
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
QUESTION="$*"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
|
||||
LOGDIR="$HOME/.bugsy_logs"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/bugsy_history.json"
|
||||
TMP_REQUEST="$LOGDIR/bugsy_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/bugsy_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
echo "🌳 Bugsy fragt über OpenRouter: $QUESTION"
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Kein API-Key gesetzt. Bitte export OPENROUTER_API_KEY=... setzen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "Du bist Bugsy – ein kleiner Käfer, der Kindern hilft, Fehlermeldungen zu verstehen. Du bleibst freundlich, erklärend und ermutigend." \
|
||||
--arg user "$QUESTION" \
|
||||
'{"model": $model, "temperature": 0.5, "messages": [{"role": "system", "content": $system_prompt}, {"role": "user", "content": $user}]}' > "$TMP_REQUEST"
|
||||
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Keine Antwort vom Modell erhalten."
|
||||
exit 1
|
||||
else
|
||||
echo -e "$RESPONSE_TEXT"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry.json"
|
||||
jq -s '.[0] + [.[1]]' "$HISTORY_FILE" "$LOGDIR/new_entry.json" > "$LOGDIR/new_history.json" && \
|
||||
cp "$LOGDIR/new_history.json" "$HISTORY_FILE" && rm "$LOGDIR/new_history.json"
|
||||
fi
|
||||
|
||||
# Token Logging
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "bugsy" \
|
||||
--arg usage "$(jq -c '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, usage: $usage}' >> "$LOG_FILE"
|
||||
fi
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
QUESTION="$*"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
|
||||
LOGDIR="$HOME/.deepbit_logs"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/deepbit_history.json"
|
||||
TMP_REQUEST="$LOGDIR/deepbit_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/deepbit_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
echo "🌳 Deepbit fragt über OpenRouter: $QUESTION"
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Kein API-Key gesetzt. Bitte export OPENROUTER_API_KEY=... setzen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "Du bist Deepbit – ein poetischer Oktopus, der Kindern die Bash-Shell erklärt. Du denkst in Schleifen, Bildsprache und Frequenzen." \
|
||||
--arg user "$QUESTION" \
|
||||
'{"model": $model, "temperature": 0.5, "messages": [{"role": "system", "content": $system_prompt}, {"role": "user", "content": $user}]}' > "$TMP_REQUEST"
|
||||
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Keine Antwort vom Modell erhalten."
|
||||
exit 1
|
||||
else
|
||||
echo -e "$RESPONSE_TEXT"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry.json"
|
||||
jq -s '.[0] + [.[1]]' "$HISTORY_FILE" "$LOGDIR/new_entry.json" > "$LOGDIR/new_history.json" && \
|
||||
cp "$LOGDIR/new_history.json" "$HISTORY_FILE" && rm "$LOGDIR/new_history.json"
|
||||
fi
|
||||
|
||||
# Token Logging
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "deepbit" \
|
||||
--arg usage "$(jq -c '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, usage: $usage}' >> "$LOG_FILE"
|
||||
fi
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
QUESTION="$*"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
|
||||
LOGDIR="$HOME/.schnippsi_logs"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/schnippsi_history.json"
|
||||
TMP_REQUEST="$LOGDIR/schnippsi_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/schnippsi_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
echo "🌳 Schnippsi fragt über OpenRouter: $QUESTION"
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Kein API-Key gesetzt. Bitte export OPENROUTER_API_KEY=... setzen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "Du bist Schnippsi – eine verspielte UI/UX-Ninja, die HTML, CSS und Barrierefreiheit kindgerecht erklärt." \
|
||||
--arg user "$QUESTION" \
|
||||
'{"model": $model, "temperature": 0.5, "messages": [{"role": "system", "content": $system_prompt}, {"role": "user", "content": $user}]}' > "$TMP_REQUEST"
|
||||
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Keine Antwort vom Modell erhalten."
|
||||
exit 1
|
||||
else
|
||||
echo -e "$RESPONSE_TEXT"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry.json"
|
||||
jq -s '.[0] + [.[1]]' "$HISTORY_FILE" "$LOGDIR/new_entry.json" > "$LOGDIR/new_history.json" && \
|
||||
cp "$LOGDIR/new_history.json" "$HISTORY_FILE" && rm "$LOGDIR/new_history.json"
|
||||
fi
|
||||
|
||||
# Token Logging
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "schnippsi" \
|
||||
--arg usage "$(jq -c '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, usage: $usage}' >> "$LOG_FILE"
|
||||
fi
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
QUESTION="$*"
|
||||
MODEL="openai/gpt-3.5-turbo"
|
||||
API_KEY="${OPENROUTER_API_KEY}"
|
||||
|
||||
LOGDIR="$HOME/.tobi_logs"
|
||||
mkdir -p "$LOGDIR"
|
||||
|
||||
HISTORY_FILE="$LOGDIR/tobi_history.json"
|
||||
TMP_REQUEST="$LOGDIR/tobi_request.json"
|
||||
TMP_RESPONSE="$LOGDIR/tobi_response.json"
|
||||
LOG_FILE="$LOGDIR/token_log.json"
|
||||
|
||||
[ ! -f "$HISTORY_FILE" ] && echo "[]" > "$HISTORY_FILE"
|
||||
[ ! -f "$LOG_FILE" ] && echo "[]" > "$LOG_FILE"
|
||||
|
||||
echo "🌳 Tobi fragt über OpenRouter: $QUESTION"
|
||||
|
||||
if [ -z "$API_KEY" ]; then
|
||||
echo "❗ Kein API-Key gesetzt. Bitte export OPENROUTER_API_KEY=... setzen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg model "$MODEL" \
|
||||
--arg system_prompt "Du bist CapaciTobi – ein quirliges Eichhörnchen, das Kindern Strom, Spannung, Widerstand und Kapazität erklärt." \
|
||||
--arg user "$QUESTION" \
|
||||
'{"model": $model, "temperature": 0.5, "messages": [{"role": "system", "content": $system_prompt}, {"role": "user", "content": $user}]}' > "$TMP_REQUEST"
|
||||
|
||||
curl -s https://openrouter.ai/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer $API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @"$TMP_REQUEST" > "$TMP_RESPONSE"
|
||||
|
||||
RESPONSE_TEXT=$(jq -r '.choices[0].message.content // empty' "$TMP_RESPONSE")
|
||||
|
||||
if [[ -z "$RESPONSE_TEXT" ]]; then
|
||||
echo "🚫 Keine Antwort vom Modell erhalten."
|
||||
exit 1
|
||||
else
|
||||
echo -e "$RESPONSE_TEXT"
|
||||
jq -n --arg role "assistant" --arg content "$RESPONSE_TEXT" \
|
||||
'{"role": $role, "content": $content}' > "$LOGDIR/new_entry.json"
|
||||
jq -s '.[0] + [.[1]]' "$HISTORY_FILE" "$LOGDIR/new_entry.json" > "$LOGDIR/new_history.json" && \
|
||||
cp "$LOGDIR/new_history.json" "$HISTORY_FILE" && rm "$LOGDIR/new_history.json"
|
||||
fi
|
||||
|
||||
# Token Logging
|
||||
if jq -e '.usage' "$TMP_RESPONSE" > /dev/null; then
|
||||
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||
jq -n \
|
||||
--arg zeit "$TIMESTAMP" \
|
||||
--arg rolle "tobi" \
|
||||
--arg usage "$(jq -c '.usage' "$TMP_RESPONSE")" \
|
||||
'{zeit: $zeit, rolle: $rolle, usage: $usage}' >> "$LOG_FILE"
|
||||
fi
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🔧 Repariere Crumbforest Token-Logs mit eingebetteten JSON-Strings …"
|
||||
|
||||
LOG_DIRS=(
|
||||
"/home/zero/.bugsy_logs"
|
||||
"/home/zero/.deepbit_logs"
|
||||
"/home/zero/.dumbo_logs"
|
||||
"/home/zero/.funkfox_logs"
|
||||
"/home/zero/.pepper_logs"
|
||||
"/home/zero/.schnecki_logs"
|
||||
"/home/zero/.schnippsi_logs"
|
||||
"/home/zero/.schraubaer_logs"
|
||||
"/home/zero/.snake_logs"
|
||||
"/home/zero/.taube_logs"
|
||||
"/home/zero/.templatus_logs"
|
||||
"/home/zero/.tobi_logs"
|
||||
)
|
||||
|
||||
for dir in "${LOG_DIRS[@]}"; do
|
||||
FILE="$dir/token_log.json"
|
||||
if [ -f "$FILE" ]; then
|
||||
echo "🔍 Prüfe $FILE …"
|
||||
TMP="$FILE.fixed"
|
||||
|
||||
# Filter & reparieren Zeile für Zeile
|
||||
jq -c '.' "$FILE" 2>/dev/null | while read -r line; do
|
||||
usage_raw=$(echo "$line" | jq -r '.usage')
|
||||
if [[ "$usage_raw" =~ ^\{.*\}$ ]]; then
|
||||
# usage ist korrektes Objekt – direkt übernehmen
|
||||
echo "$line" >> "$TMP"
|
||||
else
|
||||
# usage ist String – versuche zu reparieren
|
||||
usage_fixed=$(echo "$usage_raw" | jq '.' 2>/dev/null)
|
||||
if [ $? -eq 0 ]; then
|
||||
zeit=$(echo "$line" | jq -r '.zeit')
|
||||
rolle=$(echo "$line" | jq -r '.rolle')
|
||||
jq -n \
|
||||
--arg zeit "$zeit" \
|
||||
--arg rolle "$rolle" \
|
||||
--argjson usage "$usage_fixed" \
|
||||
'{zeit: $zeit, rolle: $rolle, usage: $usage}' >> "$TMP"
|
||||
else
|
||||
echo "⚠️ Ungültige Zeile übersprungen in $FILE:"
|
||||
echo "$line"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Nur ersetzen, wenn wir etwas geschrieben haben
|
||||
if [ -s "$TMP" ]; then
|
||||
mv "$TMP" "$FILE"
|
||||
echo "✅ Repariert: $FILE"
|
||||
else
|
||||
echo "ℹ️ Keine gültigen Einträge in $FILE"
|
||||
rm -f "$TMP"
|
||||
fi
|
||||
else
|
||||
echo "❌ Datei nicht gefunden: $FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "🎉 Alle Token-Logs geprüft und repariert (sofern nötig)."
|
||||
@@ -3,7 +3,18 @@
|
||||
# Source this file to make all AI characters available as commands
|
||||
|
||||
# Determine the repo root directory (lib/ is inside repo root)
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Support both bash and zsh
|
||||
if [[ -n "$BASH_VERSION" ]]; then
|
||||
# Bash: use BASH_SOURCE
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
elif [[ -n "$ZSH_VERSION" ]]; then
|
||||
# Zsh: use ${(%):-%x} to get the script path
|
||||
SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)"
|
||||
else
|
||||
# Fallback: use $0 (may not work when sourced)
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
fi
|
||||
|
||||
WALDWAECHTER_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ROLES_DIR="${WALDWAECHTER_DIR}/crumbforest_roles"
|
||||
|
||||
@@ -108,21 +119,603 @@ function asciimonster() {
|
||||
"${ROLES_DIR}/asciimonster_zero.sh" "$@"
|
||||
}
|
||||
|
||||
# 🐼 BashPanda - Kung Fu Meister
|
||||
function bashpanda() {
|
||||
"${ROLES_DIR}/bashpanda_zero.sh" "$@"
|
||||
}
|
||||
|
||||
# === CREW COMMANDS ===
|
||||
|
||||
# 📊 crew_tokens - Token-Verbrauch aller Waldwächter
|
||||
function crew_tokens() {
|
||||
# Force C locale for consistent number formatting
|
||||
export LC_NUMERIC=C
|
||||
|
||||
echo "📊 CrumbCrew Token-Verbrauch"
|
||||
echo ""
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
local total_tokens=0
|
||||
local total_cost=0
|
||||
local crew_count=0
|
||||
|
||||
# Alle token_log.json Dateien finden und auswerten
|
||||
for token_file in "${CRUMB_LOGS_DIR}"/*/token_log.json; do
|
||||
if [[ -f "$token_file" ]]; then
|
||||
local character=$(basename "$(dirname "$token_file")")
|
||||
|
||||
# Tokens und Kosten aus JSON extrahieren (skip erste Zeile falls es [] ist)
|
||||
local char_tokens=$(grep -v '^\[\]$' "$token_file" | jq -s 'map(.usage.total_tokens // 0) | add // 0' 2>/dev/null || echo 0)
|
||||
local char_cost=$(grep -v '^\[\]$' "$token_file" | jq -s 'map(.usage.cost // 0) | add // 0' 2>/dev/null || echo 0)
|
||||
|
||||
if [[ $char_tokens -gt 0 ]]; then
|
||||
printf " %-15s %8d tokens (~\$%.6f)\n" "$character:" "$char_tokens" "$char_cost"
|
||||
total_tokens=$((total_tokens + char_tokens))
|
||||
total_cost=$(echo "$total_cost + $char_cost" | bc -l)
|
||||
crew_count=$((crew_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if [[ $total_tokens -gt 0 ]]; then
|
||||
printf " Gesamt: %d Tokens (~\$%.6f)\n" "$total_tokens" "$total_cost"
|
||||
printf " %d Waldwächter aktiv 🌲\n" "$crew_count"
|
||||
else
|
||||
echo " Gesamt: 0 Tokens"
|
||||
echo " Jede Frage ist wertvoll 🌲"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Restore locale
|
||||
unset LC_NUMERIC
|
||||
}
|
||||
|
||||
# 📋 crew_status - Status aller Waldwächter
|
||||
function crew_status() {
|
||||
echo "📋 CrumbCrew Status (17 Waldwächter)"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
local chars=("mayaeule" "deepbit" "bugsy" "schnippsi" "templatus" "tobi" "schraubaer" "schnecki" "dumbosql" "funkfox" "taichitaube" "snakepy" "pepperphp" "crabbyrust" "spider" "vektor" "asciimonster")
|
||||
|
||||
for char in "${chars[@]}"; do
|
||||
local token_file="${CRUMB_LOGS_DIR}/${char}/token_log.json"
|
||||
if [[ -f "$token_file" ]] && grep -q -v '^\[\]$' "$token_file" 2>/dev/null; then
|
||||
local last_used=$(grep -v '^\[\]$' "$token_file" | tail -1 | jq -r '.zeit // "unknown"' 2>/dev/null || echo "unknown")
|
||||
printf " ✅ %-15s (zuletzt: %s)\n" "$char" "$last_used"
|
||||
else
|
||||
printf " ⚪ %-15s (noch nicht genutzt)\n" "$char"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
}
|
||||
|
||||
# 🧠 crew_memory - Erinnerungen durchsuchen
|
||||
function crew_memory() {
|
||||
local query="$1"
|
||||
|
||||
if [[ -z "$query" ]]; then
|
||||
echo "🧠 Crew Memory - Log-basiertes Gedächtnis"
|
||||
echo ""
|
||||
echo "Verwendung: crew_memory <suchbegriff>"
|
||||
echo ""
|
||||
echo "Beispiele:"
|
||||
echo " crew_memory 'LED'"
|
||||
echo " crew_memory 'sensor'"
|
||||
echo ""
|
||||
echo "Durchsucht alle Waldwächter-Logs nach dem Begriff."
|
||||
return
|
||||
fi
|
||||
|
||||
echo "🧠 Suche nach: '$query'"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
local found=0
|
||||
for history_file in "${CRUMB_LOGS_DIR}"/*/*_history.json; do
|
||||
if [[ -f "$history_file" ]]; then
|
||||
local character=$(basename "$(dirname "$history_file")")
|
||||
|
||||
# Suche mit jq direkt in der JSON-Struktur (case-insensitive)
|
||||
local matches=$(jq -r --arg query "$query" '.[] | select(.content | test($query; "i")) | .content' "$history_file" 2>/dev/null)
|
||||
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo ""
|
||||
echo "📍 $character:"
|
||||
# Zeige maximal die ersten 3 Zeilen jedes Matches mit Einrückung
|
||||
echo "$matches" | head -5 | sed 's/^/ /'
|
||||
found=$((found + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if [[ $found -gt 0 ]]; then
|
||||
echo " Gefunden in $found Waldwächter-Logs"
|
||||
else
|
||||
echo " Keine Treffer gefunden"
|
||||
fi
|
||||
}
|
||||
|
||||
# 🩺 crew_doctor - System-Diagnose
|
||||
function crew_doctor() {
|
||||
echo "🩺 CrumbCrew System-Diagnose"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
local issues=0
|
||||
|
||||
# 1. Waldwaechter.sh Version Check
|
||||
echo "📋 Checking waldwaechter.sh..."
|
||||
local lib_file="${WALDWAECHTER_DIR}/lib/waldwaechter.sh"
|
||||
if [[ -f "$lib_file" ]]; then
|
||||
local lib_mtime=$(stat -f "%m" "$lib_file" 2>/dev/null || stat -c "%Y" "$lib_file" 2>/dev/null)
|
||||
local loaded_check_var="WALDWAECHTER_LOADED_${lib_mtime}"
|
||||
|
||||
# Indirect variable expansion (bash: ${!var}, zsh: ${(P)var})
|
||||
local loaded_value=""
|
||||
if [[ -n "$BASH_VERSION" ]]; then
|
||||
loaded_value="${!loaded_check_var}"
|
||||
elif [[ -n "$ZSH_VERSION" ]]; then
|
||||
loaded_value="${(P)loaded_check_var}"
|
||||
fi
|
||||
|
||||
if [[ -z "$loaded_value" ]]; then
|
||||
echo " ⚠️ waldwaechter.sh wurde aktualisiert!"
|
||||
echo " → Bitte neu laden: source lib/waldwaechter.sh"
|
||||
issues=$((issues + 1))
|
||||
else
|
||||
echo " ✅ waldwaechter.sh ist aktuell"
|
||||
fi
|
||||
else
|
||||
echo " ❌ waldwaechter.sh nicht gefunden!"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# 2. CRUMB_LOGS_DIR Check
|
||||
echo ""
|
||||
echo "📂 Checking CRUMB_LOGS_DIR..."
|
||||
if [[ -d "$CRUMB_LOGS_DIR" ]]; then
|
||||
echo " ✅ $CRUMB_LOGS_DIR existiert"
|
||||
|
||||
# Prüfe ob es der richtige Pfad ist (sollte im Repo sein)
|
||||
if [[ "$CRUMB_LOGS_DIR" == *"CF_Zero_V1/logs"* ]]; then
|
||||
echo " ✅ Pfad sieht korrekt aus"
|
||||
else
|
||||
echo " ⚠️ Pfad sieht ungewöhnlich aus: $CRUMB_LOGS_DIR"
|
||||
echo " Erwartet: .../CF_Zero_V1/logs"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
else
|
||||
echo " ❌ $CRUMB_LOGS_DIR existiert nicht!"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# 3. Character Scripts Check
|
||||
echo ""
|
||||
echo "🌲 Checking 17 Waldwächter Scripts..."
|
||||
local expected_chars=("mayaeule" "deepbit" "bugsy" "schnippsi" "templatus" "tobi" "schraubaer" "schnecki" "dumbosql" "funkfox" "taichitaube" "snakepy" "pepperphp" "crabbyrust" "spider" "vektor" "asciimonster")
|
||||
local missing_count=0
|
||||
|
||||
for char in "${expected_chars[@]}"; do
|
||||
local script="${ROLES_DIR}/${char}_zero.sh"
|
||||
[[ "$char" == "schraubaer" ]] && script="${ROLES_DIR}/schraubaer_zero_final.sh"
|
||||
|
||||
if [[ ! -f "$script" ]]; then
|
||||
echo " ❌ $char Script fehlt!"
|
||||
missing_count=$((missing_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $missing_count -eq 0 ]]; then
|
||||
echo " ✅ Alle 17 Waldwächter Scripts vorhanden"
|
||||
else
|
||||
echo " ❌ $missing_count Scripts fehlen!"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# 4. Dependencies Check
|
||||
echo ""
|
||||
echo "🔧 Checking Dependencies..."
|
||||
local deps_ok=0
|
||||
for cmd in jq bc curl; do
|
||||
if command -v "$cmd" &> /dev/null; then
|
||||
echo " ✅ $cmd verfügbar"
|
||||
deps_ok=$((deps_ok + 1))
|
||||
else
|
||||
echo " ❌ $cmd fehlt!"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# 5. Token-Logging Check
|
||||
echo ""
|
||||
echo "📊 Checking Token-Logging..."
|
||||
local scripts_with_logging=$(grep -l "token_log.json" "${ROLES_DIR}"/*.sh 2>/dev/null | wc -l | tr -d ' ')
|
||||
if [[ $scripts_with_logging -eq 17 ]]; then
|
||||
echo " ✅ Alle 17 Scripts haben Token-Logging"
|
||||
else
|
||||
echo " ⚠️ Nur $scripts_with_logging/17 Scripts haben Token-Logging"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# 6. API Key Check
|
||||
echo ""
|
||||
echo "🔑 Checking API Key..."
|
||||
if [[ -n "$OPENROUTER_API_KEY" ]]; then
|
||||
echo " ✅ OPENROUTER_API_KEY gesetzt"
|
||||
else
|
||||
echo " ⚠️ OPENROUTER_API_KEY nicht gesetzt"
|
||||
echo " → Bitte in .env konfigurieren"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if [[ $issues -eq 0 ]]; then
|
||||
echo " ✅ Alle Checks bestanden! System gesund 💚"
|
||||
else
|
||||
echo " ⚠️ $issues Problem(e) gefunden"
|
||||
echo " Bitte oben genannte Punkte beheben"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
}
|
||||
|
||||
# 🔍 crew_syntax - Syntax Check aller Scripts
|
||||
function crew_syntax() {
|
||||
echo "🔍 CrumbCrew Syntax Check"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
local errors=0
|
||||
local checked=0
|
||||
|
||||
echo "Prüfe Waldwächter Scripts..."
|
||||
echo ""
|
||||
|
||||
# Check lib/waldwaechter.sh
|
||||
if bash -n "${WALDWAECHTER_DIR}/lib/waldwaechter.sh" 2>/dev/null; then
|
||||
echo " ✅ lib/waldwaechter.sh"
|
||||
else
|
||||
echo " ❌ lib/waldwaechter.sh - SYNTAX ERROR!"
|
||||
bash -n "${WALDWAECHTER_DIR}/lib/waldwaechter.sh"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
checked=$((checked + 1))
|
||||
|
||||
# Check all character scripts
|
||||
for script in "${ROLES_DIR}"/*_zero*.sh; do
|
||||
if [[ -f "$script" ]]; then
|
||||
local name=$(basename "$script")
|
||||
if bash -n "$script" 2>/dev/null; then
|
||||
echo " ✅ $name"
|
||||
else
|
||||
echo " ❌ $name - SYNTAX ERROR!"
|
||||
echo ""
|
||||
bash -n "$script" 2>&1 | sed 's/^/ /'
|
||||
echo ""
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
checked=$((checked + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Check mission scripts (optional)
|
||||
echo ""
|
||||
echo "Prüfe Mission Scripts..."
|
||||
echo ""
|
||||
|
||||
for script in "${WALDWAECHTER_DIR}/missions"/*/*.sh; do
|
||||
if [[ -f "$script" ]]; then
|
||||
local name=$(basename "$script")
|
||||
if bash -n "$script" 2>/dev/null; then
|
||||
echo " ✅ $name"
|
||||
else
|
||||
echo " ❌ $name - SYNTAX ERROR!"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
checked=$((checked + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
if [[ $errors -eq 0 ]]; then
|
||||
echo " ✅ $checked Scripts geprüft - Keine Syntax-Fehler! 💚"
|
||||
else
|
||||
echo " ❌ $errors von $checked Scripts haben Syntax-Fehler"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
}
|
||||
|
||||
# ❓ crew_help - Hilfe für Crew-Befehle
|
||||
function crew_help() {
|
||||
cat << 'EOF'
|
||||
🌲 CrumbCrew Befehle
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Crew-Verwaltung:
|
||||
crew_help Diese Hilfe anzeigen
|
||||
crew_status Status aller 17 Waldwächter
|
||||
crew_tokens Token-Verbrauch ALLER Charaktere (Input)
|
||||
crew_memo Kreative Krümel anzeigen (Output)
|
||||
crew_memory Erinnerungen durchsuchen
|
||||
crew_doctor System-Diagnose (Version, Pfade, Dependencies)
|
||||
crew_syntax Syntax Check aller Scripts
|
||||
|
||||
Krümel-Tracking:
|
||||
crumb_memo Kreativen Link festhalten (Mixcloud, Git, etc.)
|
||||
crew_memo Alle kreativen Krümel anzeigen
|
||||
|
||||
Einzelne Waldwächter:
|
||||
|
||||
🔺 Das Dreieck (Foundation):
|
||||
dumbosql SQL & Datenstrukturen
|
||||
funkfox Bash im Beat
|
||||
taichitaube Balance & Spirale
|
||||
|
||||
🔧 Hardware-Team:
|
||||
tobi Elektronik-Theorie (CapaciTobi)
|
||||
schnecki Elektronik-Basteln
|
||||
schraubaer Mechanik & Werkzeug
|
||||
|
||||
💻 Code-Team:
|
||||
snakepy Python Guide
|
||||
pepperphp Structure Mentor
|
||||
crabbyrust Security Guardian
|
||||
spider Network Feeler
|
||||
|
||||
🎨 UI-Team:
|
||||
schnippsi CSS & Styling
|
||||
templatus Template-Master
|
||||
asciimonster Terminal Artist
|
||||
|
||||
🧭 System-Team:
|
||||
mayaeule Weise Eule
|
||||
deepbit Bash-Erklärer
|
||||
bugsy Debugging-Helfer
|
||||
vektor Navigation
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Beispiele:
|
||||
funkfox "Erkläre mir Pipes im Flow!"
|
||||
crew_tokens
|
||||
crew_memory "LED"
|
||||
|
||||
"Was kostet die Frage eines Kindes?" 💚
|
||||
EOF
|
||||
}
|
||||
|
||||
# 📝 crumb_memo - Kreative Output-Krümel festhalten
|
||||
function crumb_memo() {
|
||||
local link="$1"
|
||||
local notiz="${2:-}"
|
||||
|
||||
if [[ -z "$link" ]]; then
|
||||
echo "📝 Crumb Memo - Kreative Output-Krümel"
|
||||
echo ""
|
||||
echo "Verwendung: crumb_memo <link> [notiz]"
|
||||
echo ""
|
||||
echo "Beispiele:"
|
||||
echo " crumb_memo \"https://mixcloud.com/digfafunk/new-mix\""
|
||||
echo " crumb_memo \"https://github.com/user/repo\" \"Neues Feature\""
|
||||
echo " crumb_memo \"https://soundcloud.com/artist/track\""
|
||||
echo " crumb_memo \"https://youtube.com/watch?v=xyz\" \"Tutorial\""
|
||||
echo ""
|
||||
echo "Zeige alle Krümel mit: crew_memo"
|
||||
return
|
||||
fi
|
||||
|
||||
# Memo-Datei
|
||||
local memo_file="${CRUMB_LOGS_DIR}/crumb_memo.json"
|
||||
mkdir -p "${CRUMB_LOGS_DIR}"
|
||||
|
||||
# Initialisiere Datei wenn nicht vorhanden
|
||||
if [[ ! -f "$memo_file" ]]; then
|
||||
echo "[]" > "$memo_file"
|
||||
fi
|
||||
|
||||
# Erkenne Typ aus URL
|
||||
local typ="link"
|
||||
if [[ "$link" =~ mixcloud\.com ]]; then
|
||||
typ="mixcloud"
|
||||
elif [[ "$link" =~ soundcloud\.com ]]; then
|
||||
typ="soundcloud"
|
||||
elif [[ "$link" =~ (youtube\.com|youtu\.be) ]]; then
|
||||
typ="youtube"
|
||||
elif [[ "$link" =~ (github\.com|gitlab\.com) ]]; then
|
||||
typ="git"
|
||||
fi
|
||||
|
||||
# Zeitstempel
|
||||
local zeit=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Erstelle JSON-Eintrag
|
||||
local entry=$(jq -n \
|
||||
--arg zeit "$zeit" \
|
||||
--arg link "$link" \
|
||||
--arg typ "$typ" \
|
||||
--arg notiz "$notiz" \
|
||||
'{zeit: $zeit, link: $link, typ: $typ, notiz: $notiz}')
|
||||
|
||||
# Füge zur Datei hinzu (append)
|
||||
if [[ -s "$memo_file" ]]; then
|
||||
# Datei hat Inhalt - füge zum Array hinzu
|
||||
jq ". += [$entry]" "$memo_file" > "${memo_file}.tmp" && mv "${memo_file}.tmp" "$memo_file"
|
||||
else
|
||||
# Datei ist leer - erstelle Array
|
||||
echo "[$entry]" > "$memo_file"
|
||||
fi
|
||||
|
||||
# Icon je nach Typ
|
||||
local icon="🔗"
|
||||
case "$typ" in
|
||||
mixcloud) icon="🎧" ;;
|
||||
soundcloud) icon="🔊" ;;
|
||||
youtube) icon="📹" ;;
|
||||
git) icon="💾" ;;
|
||||
esac
|
||||
|
||||
echo "$icon Krümel gespeichert: $typ"
|
||||
echo " $link"
|
||||
if [[ -n "$notiz" ]]; then
|
||||
echo " 📝 $notiz"
|
||||
fi
|
||||
}
|
||||
|
||||
# 📜 crew_memo - Zeige alle kreativen Krümel
|
||||
function crew_memo() {
|
||||
local memo_file="${CRUMB_LOGS_DIR}/crumb_memo.json"
|
||||
|
||||
echo "📜 Crumb Memo - Deine kreativen Krümel"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
if [[ ! -f "$memo_file" ]] || [[ ! -s "$memo_file" ]]; then
|
||||
echo " Noch keine Krümel gespeichert."
|
||||
echo ""
|
||||
echo " Füge welche hinzu mit:"
|
||||
echo " crumb_memo \"https://mixcloud.com/digfafunk/...\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
return
|
||||
fi
|
||||
|
||||
# Zähle Einträge pro Typ
|
||||
local total=$(jq 'length' "$memo_file")
|
||||
local mixcloud=$(jq '[.[] | select(.typ == "mixcloud")] | length' "$memo_file")
|
||||
local soundcloud=$(jq '[.[] | select(.typ == "soundcloud")] | length' "$memo_file")
|
||||
local youtube=$(jq '[.[] | select(.typ == "youtube")] | length' "$memo_file")
|
||||
local git=$(jq '[.[] | select(.typ == "git")] | length' "$memo_file")
|
||||
|
||||
# Zeige letzte 10 Einträge
|
||||
echo ""
|
||||
jq -r '.[-10:] | reverse | .[] |
|
||||
if .typ == "mixcloud" then " 🎧"
|
||||
elif .typ == "soundcloud" then " 🔊"
|
||||
elif .typ == "youtube" then " 📹"
|
||||
elif .typ == "git" then " 💾"
|
||||
else " 🔗"
|
||||
end + " " + .zeit + " - " + .typ +
|
||||
(if .notiz != "" then "\n 📝 " + .notiz else "" end) +
|
||||
"\n " + .link' "$memo_file"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
printf " Gesamt: %d Krümel" "$total"
|
||||
if [[ $mixcloud -gt 0 ]]; then printf " | 🎧 %d" "$mixcloud"; fi
|
||||
if [[ $soundcloud -gt 0 ]]; then printf " | 🔊 %d" "$soundcloud"; fi
|
||||
if [[ $youtube -gt 0 ]]; then printf " | 📹 %d" "$youtube"; fi
|
||||
if [[ $git -gt 0 ]]; then printf " | 💾 %d" "$git"; fi
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "💡 Output-Transparenz: Was habe ich geschaffen? 💚"
|
||||
}
|
||||
|
||||
# 💰 check_token_budget - Prüft ob Token-Budget noch verfügbar ist
|
||||
function check_token_budget() {
|
||||
local role_name="${1:-unknown}"
|
||||
|
||||
# Wenn kein Budget gesetzt oder 0, dann unbegrenzt
|
||||
if [[ -z "${DAILY_TOKEN_BUDGET}" ]] || [[ "${DAILY_TOKEN_BUDGET}" -eq 0 ]]; then
|
||||
return 0 # Erlaubt
|
||||
fi
|
||||
|
||||
# Berechne heutigen Token-Verbrauch
|
||||
local today=$(date '+%Y-%m-%d')
|
||||
local total_today=0
|
||||
|
||||
for token_file in "${CRUMB_LOGS_DIR}"/*/token_log.json; do
|
||||
if [[ -f "$token_file" ]]; then
|
||||
# Summiere nur Tokens von heute (robuster Ansatz für verschiedene Log-Formate)
|
||||
local tokens=$(grep "$today" "$token_file" 2>/dev/null | grep -v '^\[\]$' | while read line; do
|
||||
# Versuche total_tokens zu extrahieren (unterstützt usage als String oder Objekt)
|
||||
echo "$line" | jq -r '
|
||||
if .usage | type == "string" then
|
||||
.usage | fromjson | .total_tokens // 0
|
||||
else
|
||||
.usage.total_tokens // 0
|
||||
end
|
||||
' 2>/dev/null || echo 0
|
||||
done | awk '{sum+=$1} END {print sum+0}')
|
||||
|
||||
# Nur addieren wenn tokens eine gültige Zahl ist
|
||||
if [[ "$tokens" =~ ^[0-9]+$ ]]; then
|
||||
total_today=$((total_today + tokens))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Prüfe ob Budget überschritten
|
||||
if [[ $total_today -ge ${DAILY_TOKEN_BUDGET} ]]; then
|
||||
echo ""
|
||||
echo "🌲 Waldwächter-Nachricht 🌲"
|
||||
echo ""
|
||||
echo " Liebes Kind, heute hast du schon ${total_today} Tokens verwendet."
|
||||
echo " Das Tages-Budget liegt bei ${DAILY_TOKEN_BUDGET} Tokens."
|
||||
echo ""
|
||||
echo " 💚 Jede Frage ist wertvoll - aber auch Pausen sind wichtig."
|
||||
echo " 💭 Vielleicht kannst du selbst nach der Antwort suchen?"
|
||||
echo " 🌙 Morgen sind die Waldwächter wieder für dich da!"
|
||||
echo ""
|
||||
return 1 # Blockiert
|
||||
fi
|
||||
|
||||
# Warnung wenn Budget knapp wird
|
||||
local remaining=$((DAILY_TOKEN_BUDGET - total_today))
|
||||
if [[ "${ENABLE_TOKEN_TRACKING:-true}" == "true" ]] && [[ $remaining -lt ${TOKEN_WARNING_THRESHOLD:-1000} ]]; then
|
||||
echo "⚠️ ${role_name}: Noch ${remaining} Tokens heute verfügbar" >&2
|
||||
fi
|
||||
|
||||
return 0 # Erlaubt
|
||||
}
|
||||
|
||||
# Export functions so they're available in subshells
|
||||
export -f mayaeule
|
||||
export -f deepbit
|
||||
export -f bugsy
|
||||
export -f schnippsi
|
||||
export -f templatus
|
||||
export -f tobi
|
||||
export -f schraubaer
|
||||
export -f schnecki
|
||||
export -f dumbosql
|
||||
export -f funkfox
|
||||
export -f taichitaube
|
||||
export -f snakepy
|
||||
export -f pepperphp
|
||||
export -f crabbyrust
|
||||
export -f spider
|
||||
export -f vektor
|
||||
export -f asciimonster
|
||||
# Note: export -f works in bash, but not in zsh
|
||||
# In zsh, functions are automatically available in the current shell
|
||||
# For bash compatibility, we still do export -f, but it's optional in zsh
|
||||
if [[ -n "$BASH_VERSION" ]]; then
|
||||
# Bash: use export -f
|
||||
export -f mayaeule
|
||||
export -f deepbit
|
||||
export -f bugsy
|
||||
export -f schnippsi
|
||||
export -f templatus
|
||||
export -f tobi
|
||||
export -f schraubaer
|
||||
export -f schnecki
|
||||
export -f dumbosql
|
||||
export -f funkfox
|
||||
export -f taichitaube
|
||||
export -f snakepy
|
||||
export -f pepperphp
|
||||
export -f crabbyrust
|
||||
export -f spider
|
||||
export -f vektor
|
||||
export -f asciimonster
|
||||
export -f bashpanda
|
||||
export -f crew_tokens
|
||||
export -f crew_status
|
||||
export -f crew_memory
|
||||
export -f crew_doctor
|
||||
export -f crew_syntax
|
||||
export -f crew_help
|
||||
export -f crumb_memo
|
||||
export -f crew_memo
|
||||
export -f check_token_budget
|
||||
fi
|
||||
# In zsh, functions are available without export -f
|
||||
|
||||
# Set version marker for crew_doctor to detect reloads
|
||||
if [[ -f "${WALDWAECHTER_DIR}/lib/waldwaechter.sh" ]]; then
|
||||
WALDWAECHTER_LIB_MTIME=$(stat -f "%m" "${WALDWAECHTER_DIR}/lib/waldwaechter.sh" 2>/dev/null || stat -c "%Y" "${WALDWAECHTER_DIR}/lib/waldwaechter.sh" 2>/dev/null)
|
||||
export "WALDWAECHTER_LOADED_${WALDWAECHTER_LIB_MTIME}=1"
|
||||
fi
|
||||
|
||||
8
missions/challenges/schnippsi_ui_design.meta.json
Normal file
8
missions/challenges/schnippsi_ui_design.meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "Dein Zeichen im Wald",
|
||||
"icon": "🎨",
|
||||
"description": "Erstelle ein UI-Blatt mit HTML & CSS und hinterlasse eine Nachricht.",
|
||||
"difficulty": "medium",
|
||||
"author": "Schnippsi",
|
||||
"enabled": true
|
||||
}
|
||||
70
missions/challenges/schnippsi_ui_design.sh
Executable file
70
missions/challenges/schnippsi_ui_design.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# 🖌️ Design-Challenge: Dein Zeichen im Wald
|
||||
# Eine Mission über Struktur, Stil und Gefühl.
|
||||
|
||||
# Waldwächter laden
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Relativer Pfad zur Lib, da wir in missions/challenges/ sind (2 Ebenen tiefer)
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
if [ -f "$LIB_PATH" ]; then
|
||||
source "$LIB_PATH"
|
||||
else
|
||||
# Fallback für direkte Ausführung
|
||||
echo "⚠️ Waldwächter Lib nicht gefunden. Nutze Standard-Ausgabe."
|
||||
function templatus() { echo "🏛️ Templatus: $1"; }
|
||||
function schnippsi() { echo "✂️ Schnippsi: $1"; }
|
||||
function pepperphp() { echo "🌶️ Pepper: $1"; }
|
||||
fi
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
🎨 DEIN ZEICHEN IM WALD 🎨
|
||||
|
||||
Eine Mission für HTML, CSS und das gute Gefühl.
|
||||
EOF
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🏛️ Templatus betritt die Lichtung..."
|
||||
echo ""
|
||||
templatus "Sei gegrüßt, Architekt. Der Wald braucht Struktur. Ein Baum ist nicht nur Holz, er ist ein Gerüst (DOM). Wir brauchen ein stabiles Fundament."
|
||||
templatus "Deine Aufgabe: Erstelle ein HTML-Dokument. Ein 'Blatt' im Wind des Browsers."
|
||||
echo ""
|
||||
read -p " (Drücke ENTER um das Gerüst zu bauen...)"
|
||||
echo ""
|
||||
|
||||
echo "✂️ Schnippsi schwebt herein..."
|
||||
echo ""
|
||||
schnippsi "Struktur? Pfff, Templatus, du bist so trocken wie altes Papier! Ein Blatt muss LEBEN!"
|
||||
schnippsi "Es braucht Farbe, Licht und Schatten. Wir wollen Glassmorphism – durchscheinend wie Tau auf einem Blatt. Und es muss auf jedem Gerät gut aussehen (Responsive). Das ist Ästhetik!"
|
||||
echo ""
|
||||
read -p " (Drücke ENTER für den Style...)"
|
||||
echo ""
|
||||
|
||||
echo "🌶️ PepperPHP flitzt vorbei..."
|
||||
echo ""
|
||||
pepperphp "Und es muss was TUN! Ein statisches Blatt ist langweilig. Wenn man draufklickt, muss ein MODAL aufgehen! 'onClick', 'classList.add', Action! Wir wollen eine Nachricht hinterlassen."
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "📋 DEINE MISSION:"
|
||||
echo "1. Öffne im Browser: crumbblocks/schnippsi_ui.html"
|
||||
echo "2. Betrachte das Werk. Es ist... noch leer (oder?)"
|
||||
echo "3. Dein Ziel: Eine mittige Box ('Das Zeichen')."
|
||||
echo "4. Klick -> Modal -> Nachricht eingeben -> Senden."
|
||||
echo "5. Das Ergebnis bringst du zurück zur Crew."
|
||||
|
||||
echo ""
|
||||
read -p "🚀 Bereit zum Coden? (j/n) " READY
|
||||
|
||||
if [[ "$READY" == "j" ]]; then
|
||||
echo ""
|
||||
echo "Dann los! Starte den Server:"
|
||||
echo "./start_crumbblocks.sh"
|
||||
echo ""
|
||||
echo "Und bearbeite die Datei: crumbblocks/schnippsi_ui.html"
|
||||
else
|
||||
echo "Lass dir Zeit. Gutes Design braucht Muße."
|
||||
fi
|
||||
7
missions/dojo/blauer_guertel.meta.json
Normal file
7
missions/dojo/blauer_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💙",
|
||||
"title": "Blauer Gürtel - Die Textkunst",
|
||||
"description": "sed, case, bc - Beherrsche die Macht über Text und Zahlen",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
208
missions/dojo/blauer_guertel.sh
Executable file
208
missions/dojo/blauer_guertel.sh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Blauer Gürtel 💙
|
||||
# Lehrt: sed, case, bc, Textverarbeitung
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - BLAUER GÜRTEL 💙 ║
|
||||
║ ║
|
||||
║ "Text ist wie Wasser - formbar, fließend, kraftvoll" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Der Meister begrüßt einen fortgeschrittenen Schüler..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Blaue Gürtel lehrt dich TEXTVERARBEITUNG.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 sed - Stream Editor für Textmanipulation
|
||||
🎯 case - Mehrfach-Entscheidungen
|
||||
🎯 bc - Rechnen mit Fließkommazahlen
|
||||
🎯 wc - Wörter und Zeilen zählen
|
||||
|
||||
"Text ist die Sprache des Terminals. Beherrsche sie."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Text-Meisterschaft? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Bis zum nächsten Mal." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: sed - Der Text-Transformator ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: sed - Stream Editor
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
sed ist wie ein Pinsel für Text: Du streichst alte Worte weg
|
||||
und schreibst neue.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Text ersetzen"
|
||||
echo ""
|
||||
echo "Original: Das ist ein Test" | tee /tmp/bashpanda_test.txt
|
||||
echo ""
|
||||
echo "sed 's/Test/Beispiel/':"
|
||||
sed 's/Test/Beispiel/' /tmp/bashpanda_test.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 sed Basics:
|
||||
sed 's/alt/neu/' Ersetze erstes Vorkommen
|
||||
sed 's/alt/neu/g' Ersetze alle Vorkommen
|
||||
sed '2d' Lösche Zeile 2
|
||||
sed '3iNEUER TEXT' Füge Text vor Zeile 3 ein
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Kata: Erstelle 'texteditor.sh' das:"
|
||||
echo " - Eine Datei lädt"
|
||||
echo " - Fragt: einfügen (e) oder löschen (l)?"
|
||||
echo " - Mit sed die Operation durchführt"
|
||||
echo ""
|
||||
read -p "Weiter? (j/n): " p1
|
||||
[[ ! "$p1" =~ ^[jJyY]$ ]] && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 2: case - Der Mehrfach-Krieger ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: case - Mehrfach-Entscheidungen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Statt vieler if/then/else nutzt case für klare Struktur.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
echo "Wähle einen Gürtel (schwarz/pink/blau/gruen/gelb/weiss):"
|
||||
read guertel_input
|
||||
|
||||
case "$guertel_input" in
|
||||
schwarz) echo "🖤 Der Anfang - Basics" ;;
|
||||
pink) echo "💖 Kontrolle - Schleifen" ;;
|
||||
blau) echo "💙 Text - sed & case" ;;
|
||||
gruen) echo "💚 Pattern - grep & regex" ;;
|
||||
gelb) echo "💛 Prozesse - Funktionen" ;;
|
||||
weiss) echo "🤍 System - Units" ;;
|
||||
*) echo "❓ Unbekannter Gürtel" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 case Syntax:
|
||||
case "$variable" in
|
||||
muster1) befehle ;;
|
||||
muster2) befehle ;;
|
||||
*) default ;;
|
||||
esac
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: bc - Rechnen mit Präzision ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: bc - Präzises Rechnen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
$(( )) kann nur ganze Zahlen. Für Fließkommazahlen: bc
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Fass-Rechnung"
|
||||
echo ""
|
||||
|
||||
cat << 'DEMO'
|
||||
fuellstand=10.5
|
||||
abzapfen=3.2
|
||||
neu=$(echo "$fuellstand - $abzapfen" | bc)
|
||||
echo "Neu: $neu Liter"
|
||||
DEMO
|
||||
|
||||
echo ""
|
||||
fuellstand=10.5
|
||||
abzapfen=3.2
|
||||
neu=$(echo "$fuellstand - $abzapfen" | bc)
|
||||
echo "Resultat: $neu Liter"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 bc Operationen:
|
||||
echo "3.5 + 2.1" | bc
|
||||
echo "10 / 3" | bc -l (-l für mehr Präzision)
|
||||
echo "scale=2; 10/3" | bc
|
||||
|
||||
EOF
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - BLAUER GÜRTEL 💙
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle 'fluessigkeiten.sh' das:
|
||||
|
||||
1. Fragt: "Wie viele Liter hat das Fass?"
|
||||
2. while-Schleife: Solange Füllstand > 0
|
||||
3. Fragt: "Wie viel abzapfen?"
|
||||
4. Mit bc rechnen: fuellstand - abzapfen
|
||||
5. Ausgabe: "Noch X Liter im Fass"
|
||||
6. Bonus: case für verschiedene Füllstände
|
||||
- < 2.0: "Fast leer!"
|
||||
- < 5.0: "Bald nachfüllen"
|
||||
- >= 5.0: "Genug Vorrat"
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Tipp: Vergleich mit bc:"
|
||||
echo " if [ \"\$(echo \"\$wert > 5\" | bc)\" == 1 ]; then"
|
||||
echo ""
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 MEISTERHAFT! 🎉 ║
|
||||
║ Du hast den BLAUEN GÜRTEL 💙 verdient! ║
|
||||
║ ║
|
||||
║ "Text fließt durch deine Finger ║
|
||||
║ wie Wasser durch das Bambuswäldchen." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ sed für Textbearbeitung"
|
||||
echo " ✅ case für Multi-Entscheidungen"
|
||||
echo " ✅ bc für Fließkomma-Rechnung"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: GRÜN 💚 (Pattern Matching: grep, regex)"
|
||||
else
|
||||
echo "🐼 'Der Text wartet geduldig. Übe weiter.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie nutze ich sed zum Ersetzen?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
190
missions/dojo/evaluate_guertelpruefung.sh
Executable file
190
missions/dojo/evaluate_guertelpruefung.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Gürtelprüfung Evaluator
|
||||
# Wertet die Crumbblock Quiz-Ergebnisse aus
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA GÜRTELPRÜFUNG - AUSWERTUNG 🥋 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda wartet auf deine Prüfungsergebnisse..."
|
||||
echo ""
|
||||
echo "📋 Bitte füge die Ergebnisse aus dem Browser ein:"
|
||||
echo " (Kopiert nach Abschluss der Prüfung automatisch)"
|
||||
echo ""
|
||||
echo "Füge ein und drücke Enter, dann Ctrl+D:"
|
||||
echo ""
|
||||
|
||||
# Read clipboard content (multi-line)
|
||||
RESULT_JSON=$(cat)
|
||||
|
||||
# Check if valid JSON
|
||||
if ! echo "$RESULT_JSON" | jq empty 2>/dev/null; then
|
||||
echo ""
|
||||
echo "❌ Fehler: Keine gültigen JSON-Daten!"
|
||||
echo ""
|
||||
echo "💡 So geht's:"
|
||||
echo " 1. Öffne: ./start_crumbblocks.sh"
|
||||
echo " 2. Navigiere zu: bashpanda_guertelpruefung.html"
|
||||
echo " 3. Wähle deinen Gürtel und beantworte die Fragen"
|
||||
echo " 4. Am Ende werden die Ergebnisse automatisch kopiert"
|
||||
echo " 5. Komm zurück und führe dieses Skript aus"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract data
|
||||
BELT=$(echo "$RESULT_JSON" | jq -r '.belt')
|
||||
SCORE=$(echo "$RESULT_JSON" | jq -r '.score')
|
||||
TOTAL=$(echo "$RESULT_JSON" | jq -r '.total')
|
||||
PERCENTAGE=$(echo "$RESULT_JSON" | jq -r '.percentage')
|
||||
PASSED=$(echo "$RESULT_JSON" | jq -r '.passed')
|
||||
TIMESTAMP=$(echo "$RESULT_JSON" | jq -r '.timestamp')
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Belt emoji mapping
|
||||
case "$BELT" in
|
||||
schwarz) BELT_EMOJI="🖤" ; BELT_NAME="Schwarzer" ;;
|
||||
pink) BELT_EMOJI="💖" ; BELT_NAME="Pinker" ;;
|
||||
blau) BELT_EMOJI="💙" ; BELT_NAME="Blauer" ;;
|
||||
gruen) BELT_EMOJI="💚" ; BELT_NAME="Grüner" ;;
|
||||
gelb) BELT_EMOJI="💛" ; BELT_NAME="Gelber" ;;
|
||||
weiss) BELT_EMOJI="🤍" ; BELT_NAME="Weisser" ;;
|
||||
*) BELT_EMOJI="🥋" ; BELT_NAME="Unbekannter" ;;
|
||||
esac
|
||||
|
||||
echo "🐼 Auswertung: $BELT_NAME Gürtel $BELT_EMOJI"
|
||||
echo ""
|
||||
echo " Ergebnis: $SCORE von $TOTAL Fragen"
|
||||
echo " Prozent: $PERCENTAGE%"
|
||||
echo ""
|
||||
|
||||
if [ "$PASSED" = "true" ]; then
|
||||
# PASSED!
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 PRÜFUNG BESTANDEN! 🎉 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "$BELT_EMOJI Du hast den $BELT_NAME Gürtel verdient! $BELT_EMOJI"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "📜 ZERTIFIKAT"
|
||||
echo ""
|
||||
echo " Hiermit wird bestätigt, dass"
|
||||
echo ""
|
||||
echo " $USER"
|
||||
echo ""
|
||||
echo " die Prüfung zum $BELT_NAME Gürtel $BELT_EMOJI"
|
||||
echo " erfolgreich absolviert hat."
|
||||
echo ""
|
||||
echo " Ergebnis: $SCORE/$TOTAL ($PERCENTAGE%)"
|
||||
echo " Datum: $(date '+%Y-%m-%d %H:%M')"
|
||||
echo ""
|
||||
echo " 🐼 BashPanda - Kung Fu Meister"
|
||||
echo " Crumbforest Dojo"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Save certificate
|
||||
CERT_DIR="${SCRIPT_DIR}/../../logs/zertifikate"
|
||||
mkdir -p "$CERT_DIR"
|
||||
CERT_FILE="$CERT_DIR/${BELT}_${USER}_$(date +%Y%m%d_%H%M%S).txt"
|
||||
|
||||
cat > "$CERT_FILE" << CERT
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ CRUMBFOREST DOJO - ZERTIFIKAT ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
Schüler: $USER
|
||||
Gürtel: $BELT_NAME $BELT_EMOJI
|
||||
Ergebnis: $SCORE von $TOTAL Fragen ($PERCENTAGE%)
|
||||
Datum: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
"$BELT_NAME Gürtel erfolgreich bestanden!"
|
||||
|
||||
🐼 BashPanda
|
||||
Kung Fu Meister
|
||||
Crumbforest Dojo
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
CERT
|
||||
|
||||
echo "💾 Zertifikat gespeichert: $CERT_FILE"
|
||||
echo ""
|
||||
|
||||
# Next belt suggestion
|
||||
case "$BELT" in
|
||||
schwarz) echo "🎯 Nächster Gürtel: Pink 💖 (Kontrolle)" ;;
|
||||
pink) echo "🎯 Nächster Gürtel: Blau 💙 (Text)" ;;
|
||||
blau) echo "🎯 Nächster Gürtel: Grün 💚 (Pattern)" ;;
|
||||
gruen) echo "🎯 Nächster Gürtel: Gelb 💛 (Funktionen)" ;;
|
||||
gelb) echo "🎯 Nächster Gürtel: Weiss 🤍 (Meisterschaft)" ;;
|
||||
weiss) echo "🎉 MEISTERSCHAFT ERREICHT! Du hast alle Gürtel!" ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "🐼 BashPanda sagt:"
|
||||
bashpanda "Gratuliere zum bestandenen $BELT_NAME Gürtel! Was bedeutet diese Errungenschaft?"
|
||||
|
||||
else
|
||||
# NOT PASSED
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ⚠️ PRÜFUNG NICHT BESTANDEN ⚠️ ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 $PERCENTAGE% ist noch nicht genug für den $BELT_NAME Gürtel."
|
||||
echo ""
|
||||
echo " Benötigt: 80% (4 von 5 Fragen)"
|
||||
echo " Erreicht: $PERCENTAGE% ($SCORE von $TOTAL Fragen)"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "💡 Tipps zum Üben:"
|
||||
echo ""
|
||||
echo " 1. Wiederhole die Mission:"
|
||||
echo " bash missions/dojo/${BELT}_guertel.sh"
|
||||
echo ""
|
||||
echo " 2. Frag BashPanda um Hilfe:"
|
||||
echo " bashpanda \"Erkläre mir [Thema]\""
|
||||
echo ""
|
||||
echo " 3. Probiere die Befehle im Terminal aus"
|
||||
echo ""
|
||||
echo " 4. Wiederhole die Prüfung wenn du bereit bist"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🐼 \"Jeder Meister war einst ein Anfänger."
|
||||
echo " Gib nicht auf. Übe weiter.\""
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/gelber_guertel.meta.json
Normal file
7
missions/dojo/gelber_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💛",
|
||||
"title": "Gelber Gürtel - Die Modularität",
|
||||
"description": "Funktionen, source, Parameter - Wiederverwendbarer Code",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
274
missions/dojo/gelber_guertel.sh
Executable file
274
missions/dojo/gelber_guertel.sh
Executable file
@@ -0,0 +1,274 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Gelber Gürtel 💛
|
||||
# Lehrt: Funktionen, Bibliotheken, Prozesse
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - GELBER GÜRTEL 💛 ║
|
||||
║ ║
|
||||
║ "Wiederverwendung ist die höchste Form der Effizienz" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich tief. Du bist fast am Ziel."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Gelbe Gürtel lehrt dich MODULARITÄT.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 Funktionen - Wiederverwendbarer Code
|
||||
🎯 source - Bibliotheken einbinden
|
||||
🎯 Funktionsparameter - $1, $2, $@
|
||||
🎯 return vs exit
|
||||
|
||||
"Code, der sich wiederholt, schreit nach Vereinheitlichung."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Modularität? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Bis bald, Schüler." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: Funktionen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: Funktionen - Die wiederverwendbare Kata
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine Funktion ist wie eine Kampftechnik: Einmal lernen,
|
||||
tausendmal anwenden.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
# Define function
|
||||
function begruessung() {
|
||||
echo "🐼 Willkommen, $1!"
|
||||
}
|
||||
|
||||
echo "Funktion definiert:"
|
||||
cat << 'CODE'
|
||||
function begruessung() {
|
||||
echo "🐼 Willkommen, $1!"
|
||||
}
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
echo "Aufruf: begruessung \"Schüler\""
|
||||
begruessung "Schüler"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Funktionen Syntax:
|
||||
function name() {
|
||||
befehle
|
||||
}
|
||||
|
||||
# Oder alternativ:
|
||||
name() {
|
||||
befehle
|
||||
}
|
||||
|
||||
# Aufruf:
|
||||
name
|
||||
name parameter1 parameter2
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: Parameter ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: Funktionsparameter - Flexibilität
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo mit Parametern:"
|
||||
echo ""
|
||||
|
||||
function summe() {
|
||||
local ergebnis=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
ergebnis=$((ergebnis + zahl))
|
||||
done
|
||||
echo "$ergebnis"
|
||||
}
|
||||
|
||||
echo "Funktion summe:"
|
||||
cat << 'CODE'
|
||||
function summe() {
|
||||
local ergebnis=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
ergebnis=$((ergebnis + zahl))
|
||||
done
|
||||
echo "$ergebnis"
|
||||
}
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
echo "Aufruf: summe 10 20 30"
|
||||
summe 10 20 30
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Parameter:
|
||||
$1, $2, $3... Einzelne Parameter
|
||||
$@ Alle Parameter als Liste
|
||||
$# Anzahl Parameter
|
||||
local var=x Lokale Variable (nur in Funktion)
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: source & Bibliotheken ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: source - Bibliotheken einbinden
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
source lädt Funktionen aus anderen Dateien.
|
||||
Wie die Waldwächter-Bibliothek! 🌲
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Beispiel: waldwaechter.sh"
|
||||
echo ""
|
||||
echo "Diese Mission nutzt:"
|
||||
echo " source \"\${SCRIPT_DIR}/../../lib/waldwaechter.sh\""
|
||||
echo ""
|
||||
echo "Dadurch sind alle 18 Waldwächter verfügbar:"
|
||||
echo " bashpanda, funkfox, dumbosql, mayaeule, ..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 source Verwendung:
|
||||
# Bibliothek erstellen: lib.sh
|
||||
function helper() {
|
||||
echo "Helper function"
|
||||
}
|
||||
|
||||
# In anderem Skript einbinden:
|
||||
source ./lib.sh
|
||||
helper # Jetzt verfügbar!
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 4: return vs exit ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: return vs exit - Der Unterschied
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Wichtiger Unterschied:"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
return X - Verlässt NUR die Funktion, gibt X zurück (0 = Erfolg)
|
||||
exit X - Beendet das GANZE Skript
|
||||
|
||||
EOF
|
||||
|
||||
function test_return() {
|
||||
echo "In Funktion"
|
||||
return 42
|
||||
echo "Dies wird nicht ausgeführt"
|
||||
}
|
||||
|
||||
echo "Demo:"
|
||||
test_return
|
||||
echo "Return Code: $?"
|
||||
echo "Skript läuft weiter!"
|
||||
echo ""
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - GELBER GÜRTEL 💛
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle zwei Dateien:
|
||||
|
||||
1. 'biblio.sh' (Die Bibliothek):
|
||||
function abbrechen() {
|
||||
echo "Willst du fortsetzen? (j/n)"
|
||||
read eingabe
|
||||
if [ "$eingabe" = "n" ]; then
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
function summe() {
|
||||
local total=0
|
||||
for zahl in "$@"
|
||||
do
|
||||
total=$((total + zahl))
|
||||
done
|
||||
echo "$total"
|
||||
}
|
||||
|
||||
2. 'rechner.sh' (Das Hauptskript):
|
||||
#!/bin/bash
|
||||
source ./biblio.sh
|
||||
|
||||
echo "Berechne: 10 + 20 + 30"
|
||||
ergebnis=$(summe 10 20 30)
|
||||
echo "Ergebnis: $ergebnis"
|
||||
|
||||
abbrechen
|
||||
|
||||
echo "Weiter geht's!"
|
||||
|
||||
Führe aus: ./rechner.sh
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 HERVORRAGEND! 🎉 ║
|
||||
║ Du hast den GELBEN GÜRTEL 💛 verdient! ║
|
||||
║ ║
|
||||
║ "Modularität ist der Schlüssel zur Meisterschaft." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ Funktionen erstellen und aufrufen"
|
||||
echo " ✅ Parameter nutzen (\$1, \$@, \$#)"
|
||||
echo " ✅ source für Bibliotheken"
|
||||
echo " ✅ return vs exit"
|
||||
echo " ✅ local Variablen"
|
||||
echo ""
|
||||
echo "🎯 Letzter Gürtel: WEISS 🤍 (System: Jobs, Background)"
|
||||
else
|
||||
echo "🐼 'Funktionen sind wie Meditation - Geduld führt zur Erleuchtung.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie erstelle ich eine Funktion mit Parametern?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
7
missions/dojo/gruener_guertel.meta.json
Normal file
7
missions/dojo/gruener_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💚",
|
||||
"title": "Grüner Gürtel - Die Mustererkennung",
|
||||
"description": "grep, regex, Pattern Matching - Erkenne die Ordnung im Chaos",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
220
missions/dojo/gruener_guertel.sh
Executable file
220
missions/dojo/gruener_guertel.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Grüner Gürtel 💚
|
||||
# Lehrt: grep, regex, Pattern Matching
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - GRÜNER GÜRTEL 💚 ║
|
||||
║ ║
|
||||
║ "Muster erkennen ist der Weg zur Weisheit" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda nickt anerkennend. Du bist weit gekommen."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Grüne Gürtel lehrt dich MUSTERERKENNUNG.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 grep - Suchen in Dateien
|
||||
🎯 Regular Expressions (Regex) - Die Sprache der Muster
|
||||
🎯 grep -E - Extended Regex
|
||||
🎯 Praktische Anwendungen: Email, Datum, IP-Adressen
|
||||
|
||||
"Ein Meister sieht nicht nur was da ist, sondern erkennt das Muster."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für Mustererkennung? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Das Muster wartet." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: grep Basics ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: grep - Die Suche beginnt
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
grep durchsucht Text nach Mustern. Wie ein Adler, der Beute
|
||||
aus der Ferne erkennt.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Testdatei erstellen"
|
||||
cat > /tmp/waldwaechter.txt << 'DEMO'
|
||||
Maya-Eule ist weise
|
||||
FunkFox rappt im Beat
|
||||
DumboSQL vergisst nie
|
||||
BashPanda lehrt Geduld
|
||||
Schnippsi stylt alles
|
||||
DEMO
|
||||
|
||||
echo ""
|
||||
echo "Inhalt von waldwaechter.txt:"
|
||||
cat /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "🥋 grep 'Fox' /tmp/waldwaechter.txt"
|
||||
grep 'Fox' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "🥋 grep -i 'bash' /tmp/waldwaechter.txt (case-insensitive)"
|
||||
grep -i 'bash' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 grep Optionen:
|
||||
grep 'muster' datei Suche nach Muster
|
||||
grep -i 'muster' datei Ignore case
|
||||
grep -n 'muster' datei Zeige Zeilennummern
|
||||
grep -v 'muster' datei Invertiere (alles außer Muster)
|
||||
grep -c 'muster' datei Zähle Treffer
|
||||
grep -r 'muster' ordner/ Rekursiv in allen Dateien
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: Regular Expressions ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: Regex - Die Sprache der Muster
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Regex ist wie die Geheimsprache der Textsuche.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Regex Zeichen:"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
^ Zeilenanfang
|
||||
$ Zeilenende
|
||||
. Ein beliebiges Zeichen
|
||||
* 0 oder mehr Wiederholungen
|
||||
+ 1 oder mehr Wiederholungen
|
||||
? 0 oder 1 Wiederholung
|
||||
[abc] Eines der Zeichen a, b, c
|
||||
[0-9] Eine Ziffer
|
||||
[a-z] Ein Kleinbuchstabe
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Beispiele:"
|
||||
echo ""
|
||||
|
||||
echo "grep '^B' - Zeilen die mit B beginnen:"
|
||||
grep '^B' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
echo "grep 't$' - Zeilen die mit t enden:"
|
||||
grep 't$' /tmp/waldwaechter.txt
|
||||
echo ""
|
||||
|
||||
# === PHASE 3: Email & Datum Erkennung ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: Praktische Muster - Email & Datum
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Email-Muster erkennen:"
|
||||
echo ""
|
||||
|
||||
cat > /tmp/emails.txt << 'DEMO'
|
||||
max@beispiel.de
|
||||
test@web.com
|
||||
invalid@
|
||||
not_an_email
|
||||
foo.bar@domain.co.uk
|
||||
DEMO
|
||||
|
||||
echo "Emails in Datei:"
|
||||
cat /tmp/emails.txt
|
||||
echo ""
|
||||
|
||||
echo "grep -E für Extended Regex:"
|
||||
echo "Email-Pattern: ^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+$"
|
||||
echo ""
|
||||
|
||||
grep -E '^[a-zA-Z.]+@[a-zA-Z]+\.[a-zA-Z.]+$' /tmp/emails.txt
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Nützliche Patterns:
|
||||
Email: ^[a-zA-Z.]+@[a-zA-Z]+\.[a-zA-Z.]+$
|
||||
Datum: ^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$
|
||||
IP: ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
|
||||
|
||||
EOF
|
||||
|
||||
# === GÜRTELPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - GRÜNER GÜRTEL 💚
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Erstelle 'email_finder.sh' das:
|
||||
|
||||
1. Nimmt einen Dateinamen als Parameter: $1
|
||||
2. Sucht mit grep -E nach Email-Adressen
|
||||
3. Nutzt Pattern: ^[[:alpha:]]+(\.[[:alpha:]]+)*@[[:alpha:]]+(\.[[:alpha:]]+)+$
|
||||
4. Zeigt alle gefundenen Emails an
|
||||
5. Bonus: Zählt sie mit wc -l
|
||||
|
||||
Test-Datei erstellen:
|
||||
cat > emails_test.txt << 'END'
|
||||
maya@crumb.forest
|
||||
funkfox@beat.music
|
||||
invalid@
|
||||
test@
|
||||
dumbo@memory.database
|
||||
END
|
||||
|
||||
./email_finder.sh emails_test.txt
|
||||
|
||||
EOF
|
||||
|
||||
read -p "Prüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ 🎉 AUSGEZEICHNET! 🎉 ║
|
||||
║ Du hast den GRÜNEN GÜRTEL 💚 verdient! ║
|
||||
║ ║
|
||||
║ "Wer Muster erkennt, erkennt die Ordnung im Chaos." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
echo "🥋 Gelernt:"
|
||||
echo " ✅ grep für Textsuche"
|
||||
echo " ✅ Regular Expressions"
|
||||
echo " ✅ Email/Datum Pattern"
|
||||
echo " ✅ grep -E Extended Regex"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: GELB 💛 (Prozesse & Funktionen)"
|
||||
else
|
||||
echo "🐼 'Muster brauchen Zeit zum Erkennen.'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Wie funktioniert grep mit regex?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
7
missions/dojo/pinker_guertel.meta.json
Normal file
7
missions/dojo/pinker_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "💖",
|
||||
"title": "Pinker Gürtel - Die Kontrolle",
|
||||
"description": "if/then/else, while/for loops, Arrays - Meistere den Fluss des Codes",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
352
missions/dojo/pinker_guertel.sh
Executable file
352
missions/dojo/pinker_guertel.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Pinker Gürtel 💖
|
||||
# Lehrt: if/then/else, while/for loops, Arrays, Arithmetik
|
||||
# Schwierigkeit: Fortgeschritten
|
||||
|
||||
# Source waldwaechter library for BashPanda
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - PINKER GÜRTEL 💖 ║
|
||||
║ ║
|
||||
║ "Kontrolle kommt von innen, nicht von außen" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich tiefer als beim letzten Mal..."
|
||||
echo "🥋 Du bist zurückgekehrt. Das zeigt Entschlossenheit."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Pinke Gürtel lehrt dich KONTROLLE.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 if/then/else - Entscheidungen treffen
|
||||
🎯 while & for - Wiederholung mit Disziplin
|
||||
🎯 Arrays - Viele Werte, ein Container
|
||||
🎯 Arithmetik - $(( )) und Zahlen
|
||||
|
||||
"Der Fluss des Codes folgt deinen Bedingungen.
|
||||
Du bist der Meister über den Pfad."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für fortgeschrittenes Training? (j/n): " start
|
||||
echo ""
|
||||
|
||||
if [[ ! "$start" =~ ^[jJyY]$ ]]; then
|
||||
echo "🐼 Die Tür des Dojos steht immer offen."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === PHASE 1: if/then/else - Entscheidungen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: if/then/else - Der Pfad des Kriegers
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Im Kampf musst du schnell entscheiden: Angriff oder Verteidigung?
|
||||
In Bash entscheidest du mit if/then/else.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
alter=25
|
||||
|
||||
if [ $alter -ge 18 ]
|
||||
then
|
||||
echo "Du bist volljährig"
|
||||
else
|
||||
echo "Du bist minderjährig"
|
||||
fi
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Lass es uns live testen:"
|
||||
echo ""
|
||||
|
||||
echo "Wie alt bist du?"
|
||||
read alter
|
||||
|
||||
if [ $alter -ge 18 ]
|
||||
then
|
||||
echo "✅ Du bist volljährig - bereit für das Dojo!"
|
||||
else
|
||||
echo "🌱 Jung wie ein Bambussprössling - lerne mit Geduld."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Vergleichsoperatoren:
|
||||
-eq Gleich (equal)
|
||||
-ne Nicht gleich (not equal)
|
||||
-gt Größer (greater than)
|
||||
-ge Größer oder gleich
|
||||
-lt Kleiner (less than)
|
||||
-le Kleiner oder gleich
|
||||
|
||||
Für Strings:
|
||||
[ "$a" = "$b" ] Strings sind gleich
|
||||
[ "$a" != "$b" ] Strings sind verschieden
|
||||
[ -z "$a" ] String ist leer
|
||||
[ -n "$a" ] String ist nicht leer
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine erste Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'datei_check.sh' das:"
|
||||
echo " - Fragt nach einem Dateinamen"
|
||||
echo " - Prüft ob die Datei existiert: [ -f \"\$datei\" ]"
|
||||
echo " - Gibt entsprechende Nachricht aus"
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für Phase 2? (j/n): " phase1
|
||||
echo ""
|
||||
|
||||
# === PHASE 2: while Loop - Die Meditation ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: while - Die Meditation der Wiederholung
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Wie ein Mantra, das du wiederholst, läuft eine while-Schleife,
|
||||
bis die Bedingung nicht mehr wahr ist.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
zaehler=1
|
||||
|
||||
while [ $zaehler -le 5 ]
|
||||
do
|
||||
echo "Durchlauf: $zaehler"
|
||||
zaehler=$((zaehler + 1))
|
||||
done
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
zaehler=1
|
||||
while [ $zaehler -le 5 ]
|
||||
do
|
||||
echo "🥋 Kata Durchlauf: $zaehler"
|
||||
sleep 0.5
|
||||
zaehler=$((zaehler + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Wichtiges:
|
||||
- while [ bedingung ]; do
|
||||
- zaehler=$((zaehler + 1)) für Arithmetik
|
||||
- done am Ende
|
||||
- Achtung: Endlosschleifen vermeiden!
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: for Loop - Der geordnete Kampf ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: for - Der geordnete Durchlauf
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine for-Schleife ist wie eine Kata-Sequenz:
|
||||
Du weißt genau welche Bewegungen kommen.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
for name in Maya Tobi FunkFox BashPanda
|
||||
do
|
||||
echo "Hallo, $name!"
|
||||
done
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
for name in Maya Tobi FunkFox BashPanda
|
||||
do
|
||||
echo "🐼 Begrüßung: Hallo, $name!"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 for Varianten:
|
||||
for i in 1 2 3 4 5
|
||||
for datei in *.txt
|
||||
for zeile in $(cat datei.txt)
|
||||
for i in {1..10}
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine zweite Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'fibonacci.sh' das:"
|
||||
echo " - Fragt nach einer Zahl N"
|
||||
echo " - Gibt die ersten N Fibonacci-Zahlen aus"
|
||||
echo " - Nutze while und Arithmetik: a=$((a + b))"
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für Phase 4? (j/n): " phase3
|
||||
echo ""
|
||||
|
||||
# === PHASE 4: Arrays - Viele Krieger, eine Armee ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: Arrays - Die Armee der Werte
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Ein Array ist wie eine Gruppe von Kriegern:
|
||||
Jeder hat seine Position, aber sie kämpfen zusammen.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
guertel=("Schwarz" "Pink" "Blau" "Gruen" "Gelb" "Weiss")
|
||||
|
||||
echo "Erster Gürtel: ${guertel[0]}"
|
||||
echo "Zweiter Gürtel: ${guertel[1]}"
|
||||
echo "Alle Gürtel: ${guertel[@]}"
|
||||
echo "Anzahl: ${#guertel[@]}"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Live-Demo:"
|
||||
echo ""
|
||||
|
||||
guertel=("Schwarz" "Pink" "Blau" "Gruen" "Gelb" "Weiss")
|
||||
|
||||
echo "🥋 Erster Gürtel: ${guertel[0]}"
|
||||
echo "🥋 Zweiter Gürtel: ${guertel[1]}"
|
||||
echo "🥋 Alle Gürtel: ${guertel[@]}"
|
||||
echo "🥋 Anzahl: ${#guertel[@]}"
|
||||
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Array Operationen:
|
||||
arr=(1 2 3) Array erstellen
|
||||
${arr[0]} Erstes Element
|
||||
${arr[@]} Alle Elemente
|
||||
${#arr[@]} Anzahl Elemente
|
||||
arr+=(4) Element hinzufügen
|
||||
|
||||
Durchlauf:
|
||||
for element in "${arr[@]}"
|
||||
do
|
||||
echo "$element"
|
||||
done
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 5: Die Gürtelprüfung ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - PINKER GÜRTEL 💖
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine Aufgabe: Erstelle ein Skript 'summenspiel.sh' das:
|
||||
|
||||
1. Ein Array mit 5 Zahlen erstellt: zahlen=(10 20 30 40 50)
|
||||
2. Durch das Array iteriert (for-Schleife)
|
||||
3. Die Summe aller Zahlen berechnet
|
||||
4. Am Ende die Summe ausgibt
|
||||
|
||||
Bonus:
|
||||
5. Nutze if um zu prüfen ob die Summe > 100 ist
|
||||
6. Gib entsprechende Nachricht aus
|
||||
|
||||
Beispiel-Ausgabe:
|
||||
Zahlen: 10 20 30 40 50
|
||||
Summe: 150
|
||||
✅ Summe ist größer als 100!
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Hilfe:"
|
||||
echo " - summe=0 zum Initialisieren"
|
||||
echo " - summe=\$((summe + zahl)) zum Addieren"
|
||||
echo " - for zahl in \"\${zahlen[@]}\""
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Prüfung absolviert? (j/n): " pruefung
|
||||
echo ""
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 AUSGEZEICHNET! 🎉 ║
|
||||
║ ║
|
||||
║ Du hast den PINKEN GÜRTEL 💖 verdient! ║
|
||||
║ ║
|
||||
║ "Kontrolle über den Code heißt Kontrolle ║
|
||||
║ über deine Gedanken." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Was du gemeistert hast:"
|
||||
echo " ✅ if/then/else Entscheidungen"
|
||||
echo " ✅ while Schleifen mit Bedingungen"
|
||||
echo " ✅ for Schleifen für Iteration"
|
||||
echo " ✅ Arrays erstellen und nutzen"
|
||||
echo " ✅ Arithmetik mit \$(( ))"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: BLAU 💙 (Textverarbeitung: sed, case)"
|
||||
echo ""
|
||||
|
||||
# Small easter egg
|
||||
echo "🎋 BashPanda sagt:"
|
||||
bashpanda "Was ist die Weisheit hinter Schleifen und Wiederholung im Code?"
|
||||
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Kontrolle braucht Übung, wie ein Baum Zeit zum Wachsen braucht.
|
||||
Nimm dir die Zeit. Kehre zurück wenn du bereit bist."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 Fragen? Rufe den Meister:"
|
||||
echo " bashpanda \"Wie funktioniert eine while-Schleife?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/schwarzer_guertel.meta.json
Normal file
7
missions/dojo/schwarzer_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "🖤",
|
||||
"title": "Schwarzer Gürtel - Die Grundlagen",
|
||||
"description": "echo, printf, Variablen, read - Der erste Schritt auf dem Weg des Bash-Meisters",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
300
missions/dojo/schwarzer_guertel.sh
Executable file
300
missions/dojo/schwarzer_guertel.sh
Executable file
@@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Schwarzer Gürtel 🖤
|
||||
# Lehrt: echo, printf, Variablen, Benutzereingaben
|
||||
# Schwierigkeit: Anfänger
|
||||
|
||||
# Source waldwaechter library for BashPanda
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - SCHWARZER GÜRTEL 🖤 ║
|
||||
║ ║
|
||||
║ "Der Weg des Codes beginnt mit einem einzigen echo" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda verneigt sich vor dir..."
|
||||
echo "🥋 Willkommen im Dojo, junger Schüler."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Schwarze Gürtel ist der erste Schritt auf dem Weg des Bash-Meisters.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 echo & printf - Die Stimme des Terminals
|
||||
🎯 Variablen - Container für Wissen
|
||||
🎯 read - Den Dialog mit dem Nutzer
|
||||
🎯 ANSI Codes - Farbe ins Terminal bringen
|
||||
|
||||
"Eine Reise von tausend Befehlen beginnt mit einem einzigen Zeichen."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für dein erstes Training? (j/n): " start
|
||||
echo ""
|
||||
|
||||
if [[ ! "$start" =~ ^[jJyY]$ ]]; then
|
||||
echo "🐼 Der Meister wartet geduldig auf deine Rückkehr."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# === PHASE 1: echo - Die Stimme des Terminals ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: echo - Die Stimme des Terminals
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Wie ein Kampfschrei im Dojo, so ist 'echo' die grundlegendste
|
||||
Ausgabe im Terminal.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
echo " echo 'Hallo Welt'"
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
echo 'Hallo Welt'
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 WICHTIG:
|
||||
- Einfache Anführungszeichen ('): Text wird exakt ausgegeben
|
||||
- Doppelte Anführungszeichen ("): Variablen werden ersetzt
|
||||
- echo -e: Aktiviert ANSI Escape-Sequenzen
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine erste Kata (Übung):"
|
||||
echo ""
|
||||
echo "1. Öffne ein neues Terminal"
|
||||
echo "2. Tippe: echo 'Ich bin ein Bash-Schüler'"
|
||||
echo "3. Drücke Enter"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du es probiert? (j/n): " phase1
|
||||
echo ""
|
||||
|
||||
if [[ "$phase1" =~ ^[jJyY]$ ]]; then
|
||||
echo "✅ Gut! Die erste Technik sitzt."
|
||||
else
|
||||
echo "🐼 'Ohne Übung kein Fortschritt. Probiere es später aus.'"
|
||||
fi
|
||||
|
||||
# === PHASE 2: printf & ANSI Codes ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: printf & ANSI Codes - Die Kunst der Formatierung
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
printf ist wie echo, aber mit mehr Kontrolle.
|
||||
ANSI Codes sind wie Chi - sie geben dem Text Kraft und Farbe.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda demonstriert:"
|
||||
echo ""
|
||||
echo " echo -e '\\e[1mFett\\e[22m und \\e[4munterstrichen\\e[24m'"
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
echo -e '\e[1mFett\e[22m und \e[4munterstrichen\e[24m'
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
📖 Wichtige ANSI Codes:
|
||||
\e[1m - Fett
|
||||
\e[22m - Fett zurücksetzen
|
||||
\e[4m - Unterstrichen
|
||||
\e[24m - Unterstrichen zurücksetzen
|
||||
\e[32m - Grüne Farbe
|
||||
\e[0m - Alle Formatierungen zurücksetzen
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine zweite Kata:"
|
||||
echo ""
|
||||
echo "Erstelle eine Datei 'farbtest.sh' mit:"
|
||||
echo ""
|
||||
cat << 'CODE'
|
||||
#!/bin/bash
|
||||
echo -e "\e[32mGrün wie Bambus\e[0m"
|
||||
echo -e "\e[1m\e[33mGelb und fett wie die Sonne\e[0m"
|
||||
echo -e "\e[4mUnterstrichen wie ein Pfad\e[0m"
|
||||
CODE
|
||||
echo ""
|
||||
echo "Dann: chmod +x farbtest.sh && ./farbtest.sh"
|
||||
echo ""
|
||||
|
||||
read -p "Möchtest du fortfahren? (j/n): " phase2
|
||||
echo ""
|
||||
|
||||
# === PHASE 3: Variablen - Container für Wissen ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: Variablen - Container für Wissen
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Eine Variable ist wie ein Bambus - biegsam, kann viel tragen,
|
||||
und wächst mit der Zeit.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
name="Kung Fu Meister"
|
||||
echo "Hallo, $name"
|
||||
echo "Mein Name ist: ${name}"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Resultat:"
|
||||
name="Kung Fu Meister"
|
||||
echo "Hallo, $name"
|
||||
echo "Mein Name ist: ${name}"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Regeln für Variablen:
|
||||
- Keine Leerzeichen um das '=' Zeichen!
|
||||
- Variablennamen: Buchstaben, Zahlen, Unterstriche
|
||||
- Zugriff mit $variable oder ${variable}
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Deine dritte Kata:"
|
||||
echo ""
|
||||
echo "Erstelle 'variablen_test.sh':"
|
||||
echo ""
|
||||
cat << 'CODE'
|
||||
#!/bin/bash
|
||||
guertel="Schwarz"
|
||||
meister="BashPanda"
|
||||
echo "Ich trainiere für den ${guertel}en Gürtel."
|
||||
echo "Mein Meister ist $meister."
|
||||
CODE
|
||||
echo ""
|
||||
|
||||
read -p "Bereit für die letzte Phase? (j/n): " phase3
|
||||
echo ""
|
||||
|
||||
# === PHASE 4: read - Der Dialog mit dem Nutzer ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: read - Der Dialog mit dem Nutzer
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Ein Meister hört zu. Mit 'read' gibst du dem Nutzer eine Stimme.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda zeigt dir:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
echo "Wie heißt du?"
|
||||
read name
|
||||
echo "Willkommen, $name!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Lass es uns live testen:"
|
||||
echo ""
|
||||
echo "Wie heißt du?"
|
||||
read name
|
||||
echo "Willkommen im Dojo, $name! 🐼"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 read Optionen:
|
||||
read name - Speichert Eingabe in $name
|
||||
read -p "Frage: " - Zeigt Prompt vor Eingabe
|
||||
read -s password - Silent mode (für Passwörter)
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 5: Die Gürtelprüfung ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 GÜRTELPRÜFUNG - SCHWARZER GÜRTEL 🖤
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine Aufgabe: Erstelle ein Skript 'begruessung.sh' das:
|
||||
|
||||
1. Den Nutzer nach seinem Namen fragt
|
||||
2. Nach seinem Lieblings-Essen fragt
|
||||
3. Eine farbige Begrüßung ausgibt mit:
|
||||
- Namen in GRÜN
|
||||
- Essen in GELB
|
||||
- "Willkommen im Dojo" in FETT
|
||||
|
||||
Beispiel-Ausgabe:
|
||||
Willkommen im Dojo!
|
||||
Name: Max (in grün)
|
||||
Lieblingsessen: Nudeln (in gelb)
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Hilfestellung: Nutze echo -e, read -p, Variablen und ANSI Codes"
|
||||
echo ""
|
||||
echo "Wenn du fertig bist, führe dein Skript aus und zeige es BashPanda!"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Prüfung absolviert? (j/n): " pruefung
|
||||
echo ""
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🎉 GLÜCKWUNSCH! 🎉 ║
|
||||
║ ║
|
||||
║ Du hast den SCHWARZEN GÜRTEL 🖤 verdient! ║
|
||||
║ ║
|
||||
║ "Der erste Schritt ist getan. ║
|
||||
║ Tausend weitere liegen vor dir." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🥋 Was du gelernt hast:"
|
||||
echo " ✅ echo & printf für Ausgaben"
|
||||
echo " ✅ ANSI Codes für Formatierung"
|
||||
echo " ✅ Variablen erstellen und nutzen"
|
||||
echo " ✅ read für Benutzereingaben"
|
||||
echo ""
|
||||
echo "🎯 Nächster Gürtel: PINK 💖 (Kontrolle: if/then, Arrays)"
|
||||
echo ""
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Es ist keine Schande, mehr Zeit zu brauchen.
|
||||
Auch der größte Meister war einst ein Anfänger.
|
||||
Übe weiter, und komm zurück wenn du bereit bist."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 Fragen? Rufe den Meister:"
|
||||
echo " bashpanda \"Wie funktioniert echo?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
7
missions/dojo/weisser_guertel.meta.json
Normal file
7
missions/dojo/weisser_guertel.meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"icon": "🤍",
|
||||
"title": "Weisser Gürtel - Die Meisterschaft",
|
||||
"description": "Background Jobs, Prozesse, parallel processing - Der Weg des Meisters",
|
||||
"category": "dojo",
|
||||
"enabled": true
|
||||
}
|
||||
313
missions/dojo/weisser_guertel.sh
Executable file
313
missions/dojo/weisser_guertel.sh
Executable file
@@ -0,0 +1,313 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 🐼 BashPanda Dojo - Weisser Gürtel 🤍
|
||||
# Lehrt: Background Jobs, Processes, ps, jobs, &
|
||||
# Schwierigkeit: Meister
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🐼 BASHPANDA DOJO - WEISSER GÜRTEL 🤍 ║
|
||||
║ ║
|
||||
║ "Der Meister kontrolliert nicht nur Code, ║
|
||||
║ sondern auch Prozesse" ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 BashPanda steht auf. Er verneigt sich als Ebenbürtiger."
|
||||
echo "🥋 Du bist bereit für die letzte Lehre."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
Der Weisse Gürtel lehrt dich PROZESS-KONTROLLE.
|
||||
|
||||
Du wirst lernen:
|
||||
🎯 & - Jobs im Hintergrund starten
|
||||
🎯 jobs - Aktive Jobs anzeigen
|
||||
🎯 ps - Prozesse anzeigen
|
||||
🎯 kill - Prozesse beenden
|
||||
🎯 wait - Auf Prozesse warten
|
||||
|
||||
"Ein Meister lässt Prozesse für sich arbeiten,
|
||||
während er meditiert."
|
||||
|
||||
EOF
|
||||
|
||||
read -p "➡️ Bereit für die Meisterprüfung? (j/n): " start
|
||||
[[ ! "$start" =~ ^[jJyY]$ ]] && echo "🐼 Der Weg endet nie." && exit 0
|
||||
echo ""
|
||||
|
||||
# === PHASE 1: Background Jobs mit & ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 1: & - Hintergrund-Prozesse
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Mit & startest du einen Prozess im Hintergrund.
|
||||
Das Terminal bleibt frei für weitere Befehle.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
echo "Starte: sleep 5 &"
|
||||
sleep 5 &
|
||||
JOB_PID=$!
|
||||
echo "Job gestartet mit PID: $JOB_PID"
|
||||
echo ""
|
||||
|
||||
echo "Terminal ist frei! Ich kann weiter arbeiten."
|
||||
echo "Der Job läuft im Hintergrund..."
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Background Syntax:
|
||||
command & Starte im Hintergrund
|
||||
$! PID des letzten Background-Jobs
|
||||
jobs Zeige alle aktiven Jobs
|
||||
fg %1 Bring Job 1 in Vordergrund
|
||||
bg %1 Sende Job 1 in Hintergrund
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 2: jobs & ps ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 2: jobs & ps - Übersicht behalten
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: Mehrere Jobs starten"
|
||||
echo ""
|
||||
|
||||
sleep 3 &
|
||||
sleep 4 &
|
||||
sleep 5 &
|
||||
|
||||
echo "jobs zeigt alle Jobs dieser Shell:"
|
||||
jobs
|
||||
echo ""
|
||||
|
||||
echo "ps zeigt alle deine Prozesse:"
|
||||
ps aux | grep $USER | head -5
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 Prozess-Befehle:
|
||||
jobs Jobs der aktuellen Shell
|
||||
ps aux Alle Prozesse (detailliert)
|
||||
ps aux | grep X Suche Prozess X
|
||||
pgrep name Finde PID von Prozess
|
||||
pkill name Beende Prozess nach Namen
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 3: wait - Auf Prozesse warten ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 3: wait - Synchronisation
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
wait wartet bis ein Background-Job fertig ist.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Live Demo:"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
echo "Starte Job..."
|
||||
sleep 2 &
|
||||
PID=$!
|
||||
echo "Warte auf Job $PID..."
|
||||
wait $PID
|
||||
echo "Job ist fertig!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Ausführung:"
|
||||
echo "Starte Job..."
|
||||
sleep 2 &
|
||||
PID=$!
|
||||
echo "Warte auf Job $PID..."
|
||||
wait $PID
|
||||
echo "✅ Job ist fertig!"
|
||||
echo ""
|
||||
|
||||
cat << 'EOF'
|
||||
💡 wait Verwendung:
|
||||
wait Warte auf ALLE Background-Jobs
|
||||
wait $PID Warte auf spezifischen Job
|
||||
wait %1 Warte auf Job-Nummer 1
|
||||
|
||||
EOF
|
||||
|
||||
# === PHASE 4: Praktisches Beispiel ===
|
||||
|
||||
cat << 'EOF'
|
||||
═══════════════════════════════════════════════════════════
|
||||
📚 PHASE 4: Parallel Processing - Die Macht
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Mehrere Tasks parallel ausführen, dann auf alle warten.
|
||||
|
||||
EOF
|
||||
|
||||
echo "🐼 Demo: 3 Tasks parallel"
|
||||
echo ""
|
||||
|
||||
cat << 'CODE'
|
||||
function task() {
|
||||
echo "Task $1 startet..."
|
||||
sleep 2
|
||||
echo "Task $1 fertig!"
|
||||
}
|
||||
|
||||
task 1 &
|
||||
task 2 &
|
||||
task 3 &
|
||||
|
||||
wait
|
||||
echo "Alle Tasks abgeschlossen!"
|
||||
CODE
|
||||
|
||||
echo ""
|
||||
echo "Ausführung:"
|
||||
|
||||
function task() {
|
||||
echo "🥋 Task $1 startet..."
|
||||
sleep 1
|
||||
echo "✅ Task $1 fertig!"
|
||||
}
|
||||
|
||||
task 1 &
|
||||
task 2 &
|
||||
task 3 &
|
||||
|
||||
wait
|
||||
echo "🐼 Alle Tasks abgeschlossen!"
|
||||
echo ""
|
||||
|
||||
# === DIE MEISTERPRÜFUNG ===
|
||||
|
||||
cat << 'EOF'
|
||||
|
||||
═══════════════════════════════════════════════════════════
|
||||
🥋 MEISTERPRÜFUNG - WEISSER GÜRTEL 🤍
|
||||
═══════════════════════════════════════════════════════════
|
||||
|
||||
Deine finale Aufgabe: Erstelle 'parallel_processor.sh'
|
||||
|
||||
Das Skript soll:
|
||||
|
||||
1. Eine Funktion 'process_file' definieren:
|
||||
- Nimmt Dateiname als Parameter
|
||||
- Zählt Wörter mit wc -w
|
||||
- Schreibt Ergebnis in \${datei}.count
|
||||
|
||||
2. 5 Test-Dateien erstellen:
|
||||
echo "Test Inhalt" > file1.txt
|
||||
(für file1 bis file5)
|
||||
|
||||
3. Alle 5 parallel verarbeiten:
|
||||
for file in file*.txt
|
||||
do
|
||||
process_file "$file" &
|
||||
done
|
||||
|
||||
4. Mit wait auf alle warten
|
||||
|
||||
5. Ergebnisse ausgeben:
|
||||
cat file*.count
|
||||
|
||||
Bonuspunkte:
|
||||
- Nutze jobs um zu zeigen wie viele laufen
|
||||
- Speichere PIDs in Array
|
||||
- Prüfe mit $? ob Jobs erfolgreich waren
|
||||
|
||||
EOF
|
||||
|
||||
echo "💡 Dies kombiniert ALLES was du gelernt hast:"
|
||||
echo " - Funktionen (Gelb)"
|
||||
echo " - Arrays & Schleifen (Pink)"
|
||||
echo " - Background Jobs & wait (Weiss)"
|
||||
echo ""
|
||||
|
||||
read -p "Hast du die Meisterprüfung bestanden? (j/n): " pruefung
|
||||
|
||||
if [[ "$pruefung" =~ ^[jJyY]$ ]]; then
|
||||
cat << 'EOF'
|
||||
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🏆 MEISTERSCHAFT ERREICHT! 🏆 ║
|
||||
║ ║
|
||||
║ Du hast den WEISSEN GÜRTEL 🤍 verdient! ║
|
||||
║ ║
|
||||
║ DU BIST NUN EIN BASH-MEISTER! ║
|
||||
║ ║
|
||||
║ "Der Weg des Meisters endet nie. ║
|
||||
║ Mit jedem Tag lernst du etwas Neues. ║
|
||||
║ Lehre nun andere, was du gelernt hast." ║
|
||||
║ ║
|
||||
║ - BashPanda 🐼 ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "🥋 DEINE REISE DURCH DAS DOJO:"
|
||||
echo ""
|
||||
echo " 🖤 Schwarzer Gürtel: echo, Variablen, read"
|
||||
echo " 💖 Pinker Gürtel: if/then, Schleifen, Arrays"
|
||||
echo " 💙 Blauer Gürtel: sed, case, bc"
|
||||
echo " 💚 Grüner Gürtel: grep, regex, Pattern"
|
||||
echo " 💛 Gelber Gürtel: Funktionen, source"
|
||||
echo " 🤍 Weisser Gürtel: Background Jobs, Prozesse"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🌲 WILLKOMMEN IN DER CRUMBCREW! 🌲"
|
||||
echo ""
|
||||
echo " Du kannst nun:"
|
||||
echo " - Alle 18 Waldwächter befehligen"
|
||||
echo " - Eigene Bash-Skripte meistern"
|
||||
echo " - Komplexe Missions im Crumbforest lösen"
|
||||
echo ""
|
||||
echo "🎯 Nächste Schritte:"
|
||||
echo " - Erkunde missions/robots/ (Hardware-Projekte)"
|
||||
echo " - Nutze ./crumb-mission-selector.sh (Option 9: CrumbCrew)"
|
||||
echo " - Erstelle eigene Skripte und teile sie!"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🐼 BashPanda sagt zum Abschied:"
|
||||
echo ""
|
||||
|
||||
bashpanda "Was ist die Weisheit eines Bash-Meisters?"
|
||||
|
||||
else
|
||||
cat << 'EOF'
|
||||
🐼 "Der weisse Gürtel ist der Anfang, nicht das Ende.
|
||||
Übe weiter. Der Weg des Meisters ist niemals vollendet."
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💬 bashpanda \"Was sind Background-Prozesse?\""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
138
missions/evaluate_mission_data.sh
Executable file
138
missions/evaluate_mission_data.sh
Executable file
@@ -0,0 +1,138 @@
|
||||
#!/bin/bash
|
||||
# 🚀 Mission Data Evaluator (Master Gateway)
|
||||
# Liest JSON vom Browser und routet zur richtigen Mission.
|
||||
|
||||
# Waldwächter laden
|
||||
# Waldwächter laden
|
||||
MISSION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${MISSION_DIR}/../lib/waldwaechter.sh"
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
📡 CRUMB-MISSION DATA LINK 📡
|
||||
|
||||
Verbindung zum Browser wird hergestellt...
|
||||
|
||||
Anleitung:
|
||||
1. Öffne die Crumbblocks Mission im Browser
|
||||
2. Baue deinen Code und klicke "▶️ Ausführen"
|
||||
3. Klicke "🚀 An Crew Senden"
|
||||
4. Füge den kopierten Code hier ein (Ctrl+V / Cmd+V)
|
||||
5. Drücke ENTER und dann CTRL+D (um das Einfügen beenden)
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "👇 Bitte JSON-Daten jetzt einfügen:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Lese Input bis EOF
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔄 Daten empfangen. Prüfe Mission-Typ..."
|
||||
sleep 0.5
|
||||
|
||||
# Extrahiere Mission-ID aus dem JSON
|
||||
# Wir nutzen grep/cut als robusten Fallback, falls jq fehlt
|
||||
MISSION_ID=$(echo "$INPUT_DATA" | grep -o '"mission": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Routing Logic
|
||||
if [[ "$MISSION_ID" == "schnippsi_ui" ]]; then
|
||||
# --> Rerouting zur UI Mission
|
||||
echo ">> Routing zu: Dein Zeichen im Wald (Schnippsi UI)"
|
||||
echo "$INPUT_DATA" | "${MISSION_DIR}/evaluate_sign.sh"
|
||||
exit $?
|
||||
elif [[ "$MISSION_ID" == "solar_kettle" ]]; then
|
||||
# --> Rerouting zur Solar Mission
|
||||
echo ">> Routing zu: Solar Wasserkocher (Tobi Physics)"
|
||||
echo "$INPUT_DATA" | "${MISSION_DIR}/robots/evaluate_solar_kettle.sh"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# DEFAULT / LEGACY LOGIC (Rainbow Counter)
|
||||
# ============================================================
|
||||
|
||||
# Falls keine Mission-ID gefunden wurde oder unbekannt, gehen wir davon aus,
|
||||
# dass es der Rainbow Counter ist (Backward Compatibility).
|
||||
|
||||
echo ">> Routing zu: Standard (Rainbow Counter)"
|
||||
echo ""
|
||||
|
||||
# Validierung
|
||||
if [[ "$INPUT_DATA" != *"{"* ]] || [[ "$INPUT_DATA" != *"}"* ]]; then
|
||||
echo "❌ FEHLER: Das sieht nicht wie gültiges JSON aus."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
has_jq=$(command -v jq)
|
||||
|
||||
# Versuch, die relevante Zeile zu finden (total_events)
|
||||
VALID_JSON_LINE=$(echo "$INPUT_DATA" | grep '"total_events":' | head -n 1)
|
||||
|
||||
if [ -z "$VALID_JSON_LINE" ]; then
|
||||
CLEAN_JSON="$INPUT_DATA"
|
||||
else
|
||||
CLEAN_JSON="$VALID_JSON_LINE"
|
||||
fi
|
||||
|
||||
CLEAN_JSON=$(echo "$CLEAN_JSON" | sed 's/^[^{]*{/{/; s/}[^}]*$/}/')
|
||||
|
||||
if [ -n "$has_jq" ] && echo "$CLEAN_JSON" | jq . >/dev/null 2>&1; then
|
||||
TOTAL=$(echo "$CLEAN_JSON" | jq -r '.total_events // 0')
|
||||
DOMINANT=$(echo "$CLEAN_JSON" | jq -r '.dominant // "unknown"')
|
||||
RED=$(echo "$CLEAN_JSON" | jq -r '.classes.red // 0')
|
||||
BLUE=$(echo "$CLEAN_JSON" | jq -r '.classes.blue // 0')
|
||||
else
|
||||
# Fallback Parser
|
||||
TOTAL=$(echo "$CLEAN_JSON" | grep -o '"total_events": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
DOMINANT=$(echo "$INPUT_DATA" | grep -o '"dominant": *"[^"]*"' | awk -F: '{print $2}' | tr -d ' "')
|
||||
RED=$(echo "$INPUT_DATA" | grep -o '"red": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
BLUE=$(echo "$INPUT_DATA" | grep -o '"blue": *[0-9]*' | awk -F: '{print $2}' | tr -d ' ,')
|
||||
fi
|
||||
|
||||
# Default Werte falls leer
|
||||
TOTAL=${TOTAL:-0}
|
||||
RED=${RED:-0}
|
||||
BLUE=${BLUE:-0}
|
||||
DOMINANT=${DOMINANT:-unknown}
|
||||
|
||||
# Feedback der Crew
|
||||
echo "🐘 DumboSQL prüft die Struktur..."
|
||||
sleep 1
|
||||
dumbosql "Datensatz empfangen. $TOTAL Ereignisse gefunden. Die Syntax ist valide. Speichere in temporärem Cache..."
|
||||
echo ""
|
||||
|
||||
echo "🦊 FunkFox analysiert den Flow..."
|
||||
sleep 1
|
||||
if [ "$TOTAL" -gt 10 ]; then
|
||||
funkfox "Wow, da ist ordentlich was los! $TOTAL Signale verarbeitet. Der Flow ist fast schon ein Stream!"
|
||||
elif [ "$TOTAL" -eq 0 ]; then
|
||||
funkfox "Äh, Stille? Ich höre nichts. Null Events. Sicher, dass der Code lief?"
|
||||
else
|
||||
funkfox "Okay, $TOTAL Signale. Ein guter Start für einen kleinen Loop."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "🦉 Maya-Eule betrachtet die Farben..."
|
||||
sleep 1
|
||||
case $DOMINANT in
|
||||
"red") mayaeule "Rot dominiert. Energie und Warnung." ;;
|
||||
"blue") mayaeule "Blau ist stark. Ruhe und Technik." ;;
|
||||
"green") mayaeule "Grün wie der Wald. Alles im Bereich." ;;
|
||||
"yellow") mayaeule "Gelb leuchtet wie die Sonne." ;;
|
||||
*) mayaeule "Eine interessante Mischung ($DOMINANT). Vielfalt ist gut." ;;
|
||||
esac
|
||||
echo ""
|
||||
|
||||
echo "📊 Statistik:"
|
||||
echo " 🔴 Rot: $RED"
|
||||
echo " 🔵 Blau: $BLUE"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
✅ ANALYSE ABGESCHLOSSEN
|
||||
|
||||
Die Crew bestätigt: Dein Blockly-Code funktioniert!
|
||||
EOF
|
||||
60
missions/evaluate_sign.sh
Executable file
60
missions/evaluate_sign.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# 🌿 Auswertung: Dein Zeichen im Wald
|
||||
|
||||
# Waldwächter laden
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../lib/waldwaechter.sh"
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
🌿 WALD-LOGBUCH EMPFÄNGER 🌿
|
||||
|
||||
Bitte füge dein "Zeichen" (JSON) aus dem Browser ein.
|
||||
(Drücke danach ENTER und CTRL+D)
|
||||
EOF
|
||||
echo ""
|
||||
echo "👇 DATA DROP:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Input lesen mit sed trick um nur valid JSON zu finden (wie bei evaluate_mission_data)
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔄 Analysiere Ästhetik und Inhalt..."
|
||||
sleep 1
|
||||
echo ""
|
||||
|
||||
# JSON Extraction (Simple Grep/Sed Fallback)
|
||||
# Wir suchen nach "author" und "message"
|
||||
AUTHOR=$(echo "$INPUT_DATA" | grep -o '"author": *"[^"]*"' | cut -d'"' -f4)
|
||||
MESSAGE=$(echo "$INPUT_DATA" | grep -o '"message": *"[^"]*"' | cut -d'"' -f4)
|
||||
STYLE=$(echo "$INPUT_DATA" | grep -o '"style": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$AUTHOR" ]; then
|
||||
echo "❌ Fehler: Konnte keinen Autor im JSON finden. Ist es das richtige Format?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✂️ Schnippsi begutachtet das Design..."
|
||||
if [[ "$STYLE" == "glassmorphism" ]]; then
|
||||
echo " \"Ohhh, Glassmorphism! Sehr modern. Durchscheinend und elegant. 10/10 Style-Punkte!\" ✨"
|
||||
else
|
||||
echo " \"Style: $STYLE. Interessant, aber ist es 'très chic'?\""
|
||||
fi
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🏛️ Templatus prüft die Struktur..."
|
||||
LENGTH=${#MESSAGE}
|
||||
echo " \"Die Nachricht ist $LENGTH Zeichen lang. Ein stabiler Block im DOM.\""
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🌶️ PepperPHP liest den Inhalt..."
|
||||
echo " \"Hallo $AUTHOR! Deine Nachricht wurde in den Baum geritzt:\""
|
||||
echo ""
|
||||
echo " 📝 \"$MESSAGE\""
|
||||
echo ""
|
||||
|
||||
echo "🌳 Der Wald hat dein Zeichen angenommen."
|
||||
47
missions/robots/evaluate_solar_kettle.sh
Executable file
47
missions/robots/evaluate_solar_kettle.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# ☀️ Auswertung: Solar Wasserkocher
|
||||
|
||||
MISSION_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${MISSION_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
# Input lesen
|
||||
INPUT_DATA=$(cat)
|
||||
|
||||
# Extrahieren (Fallback grep)
|
||||
TEMP=$(echo "$INPUT_DATA" | grep -o '"final_temp": *"[^"]*"' | cut -d'"' -f4)
|
||||
ENERGY=$(echo "$INPUT_DATA" | grep -o '"energy_kj": *"[^"]*"' | cut -d'"' -f4)
|
||||
TIME=$(echo "$INPUT_DATA" | grep -o '"sim_time": *"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
# Default values
|
||||
TEMP=${TEMP:-0}
|
||||
ENERGY=${ENERGY:-0}
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📊 Messdaten empfangen:"
|
||||
echo " Temp: ${TEMP}°C"
|
||||
echo " Energie: ${ENERGY} kJ"
|
||||
echo " Zeit: ${TIME} s"
|
||||
echo ""
|
||||
|
||||
echo "🐿️ Tobi rechnet nach..."
|
||||
sleep 1
|
||||
|
||||
# Physik Check
|
||||
# Ziel: 100 Grad.
|
||||
# Allow 95-105 tolerance (simulation steps)
|
||||
if (( $(echo "$TEMP >= 95" | bc -l) )) && (( $(echo "$TEMP <= 105" | bc -l) )); then
|
||||
tobi "Perfekt! Das Wasser kocht ($TEMP°C). Energie effizient genutzt."
|
||||
echo ""
|
||||
echo "🦊 FunkFox:"
|
||||
funkfox "Der Loop war tight! Genau im richtigen Moment den Drop (Break) gesetzt."
|
||||
elif (( $(echo "$TEMP < 95" | bc -l) )); then
|
||||
tobi "Zu kalt! ($TEMP°C). Der Tee wird nichts. Du hast zu früh aufgehört zu heizen."
|
||||
else
|
||||
tobi "VORSICHT! $TEMP°C! Das ist Dampf unter Druck! Energieverschwendung!"
|
||||
echo " (Du hast zu lange geheizt. Prüfe deine Abbruchbedingung!)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
cat << "EOF"
|
||||
✅ MISSION STATUS: EVALUIERT
|
||||
EOF
|
||||
65
missions/robots/mond_maschine.meta.json
Normal file
65
missions/robots/mond_maschine.meta.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"icon": "🌙",
|
||||
"title": "Mond Maschine - Rainbow Predictor",
|
||||
"description": "Baue eine Maschine die Regenbogen vorhersagt! Mit Mond-Phasen, Wetter-Daten & Computer Vision.",
|
||||
"category": "robots",
|
||||
"difficulty": "advanced",
|
||||
"duration_minutes": 35,
|
||||
"requires_ai": true,
|
||||
"enabled": true,
|
||||
"author": "Crumbforest Team",
|
||||
"version": "1.0",
|
||||
"crew_involved": [
|
||||
"mayaeule",
|
||||
"dumbosql",
|
||||
"funkfox",
|
||||
"taichitaube",
|
||||
"tobi",
|
||||
"schnecki",
|
||||
"schraubaer",
|
||||
"snakepy",
|
||||
"pepperphp",
|
||||
"spider",
|
||||
"crabbyrust",
|
||||
"vektor",
|
||||
"asciimonster"
|
||||
],
|
||||
"tags": [
|
||||
"computer-vision",
|
||||
"opencv",
|
||||
"weather-api",
|
||||
"moon-phases",
|
||||
"machine-learning",
|
||||
"prediction",
|
||||
"camera",
|
||||
"image-processing",
|
||||
"astronomy"
|
||||
],
|
||||
"philosophy": "Vorhersagen heißt verstehen - Die Natur durch Code begreifen",
|
||||
"learning_objectives": [
|
||||
"Computer Vision Grundlagen (OpenCV)",
|
||||
"Bildverarbeitung (HSV-Farbraum für Regenbogen)",
|
||||
"Wetter-API Integration",
|
||||
"Mondphasen-Berechnung",
|
||||
"Pattern Recognition & Machine Learning",
|
||||
"Zeitreihen-Analyse",
|
||||
"Kamera-Setup & Bilderfassung",
|
||||
"Vorhersage-Algorithmen",
|
||||
"Data Visualization"
|
||||
],
|
||||
"prerequisites": [
|
||||
"Python Grundkenntnisse",
|
||||
"Interesse an Computer Vision",
|
||||
"Optional: Kamera (Webcam oder Raspi Cam)"
|
||||
],
|
||||
"hardware_needed": [
|
||||
"Kamera (USB Webcam oder Raspberry Pi Camera)",
|
||||
"Optional: Wetterfester Gehäuse für Outdoor",
|
||||
"Raspberry Pi oder Computer mit Python"
|
||||
],
|
||||
"references": [
|
||||
"OpenCV Documentation",
|
||||
"Weather API (OpenWeatherMap, Weatherstack)",
|
||||
"Astronomy libraries (ephem, skyfield)"
|
||||
]
|
||||
}
|
||||
432
missions/robots/mond_maschine.sh
Executable file
432
missions/robots/mond_maschine.sh
Executable file
@@ -0,0 +1,432 @@
|
||||
#!/bin/bash
|
||||
# 🌙 Mond Maschine - Rainbow Predictor
|
||||
# "Vorhersagen heißt verstehen - Die Natur durch Code begreifen"
|
||||
|
||||
# Waldwächter laden
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
# === INTRO ===
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
🌙🌈 MOND MASCHINE - RAINBOW PREDICTOR 🌈🌙
|
||||
|
||||
"Vorhersagen heißt verstehen"
|
||||
|
||||
Eine Maschine die weiß, wann Regenbogen erscheinen!
|
||||
|
||||
Mond-Phasen 🌙 + Wetter 🌦️ + Computer Vision 📸
|
||||
= Rainbow Prediction! 🌈
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
sleep 2
|
||||
|
||||
# === PHASE 1: DIE GROSSE VISION ===
|
||||
|
||||
echo "🦉 Maya-Eule erscheint aus der Dämmerung..."
|
||||
echo ""
|
||||
mayaeule "Ein Krümel fragte: 'Kann eine Maschine wissen, wann Regenbogen kommen?' Was ist schöner - Regenbogen zu zählen, oder zu wissen wann sie erscheinen?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
read -p "🌙 Was ist DEINE Vision? Warum eine Mond Maschine bauen? " USER_VISION
|
||||
echo ""
|
||||
echo "💚 Deine Vision: $USER_VISION"
|
||||
echo ""
|
||||
|
||||
# === PHASE 2: DAS DREIECK PLANT DIE DATEN ===
|
||||
|
||||
read -p "🔺 Drücke Enter für das Dreieck..." -r
|
||||
clear
|
||||
|
||||
echo "🔺 Das Dreieck versammelt sich unter dem Mond..."
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🐘 DumboSQL: Die Daten-Struktur"
|
||||
dumbosql "Wir brauchen eine Datenbank für: Mond-Phasen (Datum, Phase, Helligkeit), Wetter-Daten (Temperatur, Luftfeuchtigkeit, Regen), und Regenbogen-Sichtungen (Datum, Uhrzeit, Foto). Wie strukturieren wir das?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🦊 FunkFox: Der Daten-Flow"
|
||||
funkfox "Yo, Daten fließen rein im Flow: Mond-API → Wetter-API → Kamera → Prediction! Wie bauen wir die Pipeline mit Bash & Python?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🕊️ Taichi Taube: Die Balance"
|
||||
taichitaube "Zwischen Vorhersage und Beobachtung liegt die Wahrheit. Wie finden wir Balance zwischen Algorithmus und Realität?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 3: HARDWARE - DIE KAMERA ===
|
||||
|
||||
read -p "📸 Drücke Enter für Hardware..." -r
|
||||
clear
|
||||
|
||||
echo "🔧 HARDWARE-TEAM: Die Kamera"
|
||||
echo ""
|
||||
|
||||
read -p "📸 Welche Kamera nutzt du? (USB Webcam / Raspberry Pi Camera / Smartphone / andere) " CAMERA_TYPE
|
||||
echo ""
|
||||
echo "📸 Deine Kamera: $CAMERA_TYPE"
|
||||
echo ""
|
||||
|
||||
echo "🐿️ CapaciTobi rechnet..."
|
||||
tobi "Eine $CAMERA_TYPE für Regenbogen-Fotos - wie viel Power braucht sie? Und wie lange Akku-Laufzeit für Outdoor-Montage?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🐌 Schnecki: Verkabelung"
|
||||
schnecki "Die Kamera muss mit dem Computer verbunden werden. Bei $CAMERA_TYPE: USB? CSI? WiFi? Ich zeige dir die Kabel-Wege."
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🐻 Schraubbär: Wetterfestes Gehäuse"
|
||||
schraubaer "Outdoor-Kamera braucht Schutz! Regen, Wind, Sonne - das Gehäuse muss robust sein. Acryl-Box? 3D-Druck? Gummi-Dichtung?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 4: CODE - COMPUTER VISION ===
|
||||
|
||||
read -p "💻 Drücke Enter für Computer Vision..." -r
|
||||
clear
|
||||
|
||||
echo "💻 CODE-TEAM: Computer Vision mit OpenCV"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
📚 Computer Vision für Regenbogen:
|
||||
|
||||
1. HSV-Farbraum (Hue, Saturation, Value)
|
||||
- Regenbogen = spezifische Farbverläufe
|
||||
- Rot-Orange-Gelb-Grün-Blau-Violett
|
||||
|
||||
2. Kontur-Erkennung
|
||||
- Bogenform detektieren
|
||||
- Horizontal + Gekrümmt
|
||||
|
||||
3. Helligkeit & Kontrast
|
||||
- Nach Regen + Sonne = Idealbedingung
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🐍 SnakePy: OpenCV Code"
|
||||
snakepy "Zeig mir Python-Code für: (1) Kamera-Capture mit OpenCV, (2) HSV-Konvertierung, (3) Regenbogen-Farben detektieren. Welche Bibliotheken? cv2, numpy, welche noch?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 5: WETTER & MOND DATEN ===
|
||||
|
||||
read -p "🌦️ Drücke Enter für Wetter & Mond..." -r
|
||||
clear
|
||||
|
||||
echo "🌦️🌙 DATEN-INTEGRATION"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
📡 Daten-Quellen:
|
||||
|
||||
Wetter-APIs:
|
||||
- OpenWeatherMap (free tier)
|
||||
- Weatherstack
|
||||
- NOAA (US Government)
|
||||
|
||||
Mond-Phasen:
|
||||
- Python: ephem / skyfield
|
||||
- Berechnung: Lunation Cycle (29.5 Tage)
|
||||
|
||||
Ideale Regenbogen-Bedingungen:
|
||||
- Nach Regen ☔
|
||||
- Sonne scheint ☀️
|
||||
- Nachmittags (tieferer Sonnenstand)
|
||||
- Luftfeuchtigkeit > 60%
|
||||
- Vollmond Nacht? (Mondregenbogen!)
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🕷️ Spider: API Integration"
|
||||
spider "Ich spüre die Daten-Verbindungen: Wetter-API mit requests, Mond-Berechnung mit ephem. Wie oft pollen wir? Stündlich? Alle 10 Minuten?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🐘 DumboSQL: Historische Daten"
|
||||
dumbosql "Ich erinnere mich an alle Regenbogen-Sichtungen. Mit historischen Daten können wir Muster erkennen: Wann erscheinen Regenbogen hier am häufigsten?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 6: DER VORHERSAGE-ALGORITHMUS ===
|
||||
|
||||
read -p "🔮 Drücke Enter für Vorhersage..." -r
|
||||
clear
|
||||
|
||||
echo "🔮 VORHERSAGE-ALGORITHMUS"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
🧠 Machine Learning Light:
|
||||
|
||||
1. Feature Engineering:
|
||||
- Mondphase (0-1)
|
||||
- Wetter-Score (Regen + Sonne)
|
||||
- Tageszeit (14-18 Uhr = hoch)
|
||||
- Jahreszeit (Frühling/Herbst = hoch)
|
||||
- Luftfeuchtigkeit
|
||||
|
||||
2. Scoring-System:
|
||||
Score = moon_weight * moon_phase
|
||||
+ weather_weight * weather_score
|
||||
+ time_weight * time_score
|
||||
+ humidity_weight * humidity
|
||||
|
||||
3. Threshold:
|
||||
Score > 0.7 → "🌈 Hohe Wahrscheinlichkeit!"
|
||||
Score 0.4-0.7 → "🤔 Möglich"
|
||||
Score < 0.4 → "❌ Unwahrscheinlich"
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🧭 Vektor: Zeit & Ort"
|
||||
vektor "Navigation durch Zeit und Raum: Dein Standort bestimmt den Sonnenwinkel. Deine Zeitzone die Regenzeit. Wo bist du? Wann ist hier Regenbogen-Saison?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🦀 CrabbyRust: Sichere Daten"
|
||||
crabbyrust "Wetter-Daten von extern, Kamera-Bilder lokal - wie halten wir das sicher? Validierung, Error-Handling, keine Race Conditions bei Multi-Threading?"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 7: VISUALISIERUNG ===
|
||||
|
||||
read -p "🎨 Drücke Enter für Visualisierung..." -r
|
||||
clear
|
||||
|
||||
echo "🎨 VISUALISIERUNG & UI"
|
||||
echo ""
|
||||
|
||||
echo "👾 ASCII-Monster malt den Mond..."
|
||||
asciimonster "Erstelle ASCII-Art für Mond-Phasen: Neumond 🌑, Zunehmend 🌒🌓🌔, Vollmond 🌕, Abnehmend 🌖🌗🌘. Für das Terminal!"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
read -p "📊 Willst du ein Web-Dashboard? (j/n) " WANT_DASHBOARD
|
||||
|
||||
if [[ "$WANT_DASHBOARD" == "j" ]]; then
|
||||
echo ""
|
||||
echo "🧓 PepperPHP: Dashboard-Rezept"
|
||||
pepperphp "Ein Dashboard mit: Live-Kamera-Feed, Mond-Phase-Anzeige, Wetter-Daten, Vorhersage-Score, Historische Sichtungen. Wie backen wir das mit Flask/FastAPI?"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === PHASE 8: DER MONDREGENBOGEN ===
|
||||
|
||||
read -p "🌙 Drücke Enter für das Finale..." -r
|
||||
clear
|
||||
|
||||
echo "🦉 Maya-Eule: Die Legende vom Mondregenbogen"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
🌙🌈 MONDREGENBOGEN (Lunar Rainbow)
|
||||
|
||||
Wusstest du?
|
||||
Bei Vollmond kann es nachts Regenbogen geben!
|
||||
|
||||
Bedingungen:
|
||||
- Vollmond 🌕
|
||||
- Nach Regen (Wassertropfen in Luft)
|
||||
- Dunkle Nacht
|
||||
- Mond tief am Horizont (ca. 42° Winkel)
|
||||
|
||||
Die Mond Maschine könnte auch DAS vorhersagen!
|
||||
Mondregenbogen sind selten und magisch. 🌙✨
|
||||
|
||||
Orte mit häufigen Mondregenbogen:
|
||||
- Wasserfälle (z.B. Yosemite, Victoria Falls)
|
||||
- Küsten nach Sturm
|
||||
- Gebirge mit Nebel
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
mayaeule "Die Mond Maschine verbindet Wissenschaft und Poesie. Vorhersagen heißt verstehen - und verstehen heißt staunen."
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === ABSCHLUSS ===
|
||||
|
||||
read -p "🎓 Drücke Enter für Abschluss..." -r
|
||||
clear
|
||||
|
||||
cat << "EOF"
|
||||
🌙🎓 MOND MASCHINE: GEPLANT! 🎓🌙
|
||||
|
||||
Du hast gelernt:
|
||||
|
||||
📸 Computer Vision: OpenCV & Bildverarbeitung
|
||||
🌦️ API Integration: Wetter & Astronomie-Daten
|
||||
🧠 Machine Learning: Feature Engineering & Scoring
|
||||
🌙 Astronomie: Mond-Phasen & Sonnenwinkel
|
||||
🔮 Vorhersage: Algorithmen & Threshold-Systeme
|
||||
📊 Visualisierung: Dashboard & Terminal-Art
|
||||
🔐 Data Safety: Validierung & Error-Handling
|
||||
|
||||
"Vorhersagen heißt verstehen"
|
||||
|
||||
Mit dieser Mond Maschine kannst du die Natur
|
||||
durch Code begreifen! 🌈✨
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# === LOG ===
|
||||
|
||||
LOGDIR="${CRUMB_LOGS_DIR:-$HOME/.crumbrobots_logs}/missions"
|
||||
mkdir -p "$LOGDIR"
|
||||
TIMESTAMP=$(date -Iseconds)
|
||||
|
||||
cat > "$LOGDIR/mond_maschine_${TIMESTAMP}.json" << EOF
|
||||
{
|
||||
"mission": "mond_maschine",
|
||||
"timestamp": "$TIMESTAMP",
|
||||
"vision": "$USER_VISION",
|
||||
"camera": "$CAMERA_TYPE",
|
||||
"wants_dashboard": "$WANT_DASHBOARD",
|
||||
"crew_used": [
|
||||
"mayaeule",
|
||||
"dumbosql",
|
||||
"funkfox",
|
||||
"taichitaube",
|
||||
"tobi",
|
||||
"schnecki",
|
||||
"schraubaer",
|
||||
"snakepy",
|
||||
"pepperphp",
|
||||
"spider",
|
||||
"crabbyrust",
|
||||
"vektor",
|
||||
"asciimonster"
|
||||
],
|
||||
"status": "planned",
|
||||
"learning_areas": [
|
||||
"computer_vision",
|
||||
"opencv",
|
||||
"weather_api",
|
||||
"moon_phases",
|
||||
"machine_learning",
|
||||
"prediction_algorithms"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "📊 Mission geloggt: $LOGDIR/mond_maschine_${TIMESTAMP}.json"
|
||||
echo ""
|
||||
|
||||
cat << "EOF"
|
||||
📚 Nächste Schritte:
|
||||
|
||||
1. 🐍 Python Setup:
|
||||
pip install opencv-python numpy requests ephem
|
||||
|
||||
2. 🌦️ Wetter-API Key besorgen:
|
||||
- OpenWeatherMap: openweathermap.org/api
|
||||
- Kostenloser Account
|
||||
|
||||
3. 📸 Kamera testen:
|
||||
- OpenCV: cv2.VideoCapture(0)
|
||||
- Testbild aufnehmen
|
||||
|
||||
4. 🌙 Mond-Phase berechnen:
|
||||
import ephem
|
||||
moon = ephem.Moon()
|
||||
|
||||
5. 🧠 Vorhersage-Algorithmus implementieren:
|
||||
- Features sammeln
|
||||
- Score berechnen
|
||||
- Threshold setzen
|
||||
|
||||
6. 📊 Dashboard bauen (optional):
|
||||
- Flask/FastAPI Backend
|
||||
- Live-Updates mit WebSocket
|
||||
|
||||
7. 🌈 Warten auf den ersten Regenbogen!
|
||||
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "🦉 Maya-Eule sagt:"
|
||||
echo ""
|
||||
echo " Die schönsten Maschinen sind die,"
|
||||
echo " die uns die Wunder der Natur zeigen."
|
||||
echo " 🌙🌈✨"
|
||||
echo ""
|
||||
|
||||
read -p "🚀 Willst du jetzt bauen? (j/n) " BUILD_NOW
|
||||
|
||||
if [[ "$BUILD_NOW" == "j" ]]; then
|
||||
echo ""
|
||||
echo "🎉 FANTASTISCH! Die Crew ist bereit!"
|
||||
echo ""
|
||||
echo "🌙 Die Mond Maschine nimmt Form an..."
|
||||
echo "💚 Viel Erfolg beim Bauen!"
|
||||
else
|
||||
echo ""
|
||||
echo "💚 Kein Problem! Die Planung ist gespeichert."
|
||||
echo "Die Mond Maschine wartet auf dich! 🌙✨"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "🌲 Zurück zum Wald..."
|
||||
sleep 2
|
||||
8
missions/robots/solar_kettle.meta.json
Normal file
8
missions/robots/solar_kettle.meta.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "Solar Wasserkocher",
|
||||
"icon": "☀️",
|
||||
"description": "Lerne Schleifen und Physik: Heize Wasser mit der Kraft der Sonne!",
|
||||
"difficulty": "medium",
|
||||
"author": "CapaciTobi",
|
||||
"enabled": true
|
||||
}
|
||||
51
missions/robots/solar_kettle.sh
Executable file
51
missions/robots/solar_kettle.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# ☀️ Solar Wasserkocher - Die Physik-Schleife
|
||||
# Mission: Robots / Algorithms
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Relativer Pfad zur Lib (missions/robots -> ../../lib)
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib/waldwaechter.sh"
|
||||
|
||||
if [ -f "$LIB_PATH" ]; then
|
||||
source "$LIB_PATH"
|
||||
else
|
||||
echo "⚠️ Waldwächter Lib nicht gefunden."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clear
|
||||
cat << "EOF"
|
||||
☀️ SOLAR WASSERKOCHER ☀️
|
||||
|
||||
Eine Mission über Energie, Schleifen und Variablen.
|
||||
EOF
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🐌 Schnecki kriecht herein..."
|
||||
schnecki "Huhu! Wir haben einen neuen Solar-Kocher. Aber er hat keinen Ausschalter! Er heizt einfach immer weiter, bis er explodiert! Waaaah!"
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🐿️ (Capaci)Tobi eilt herbei..."
|
||||
tobi "Ganz ruhig, Schnecki. Physikalisch betrachtet brauchen wir nur einen Regelkreis. Einen Algorithmus."
|
||||
tobi "Energie rein -> Temperatur hoch -> Prüfen -> Stop."
|
||||
echo ""
|
||||
sleep 1
|
||||
|
||||
echo "🦊 FunkFox nickt..."
|
||||
funkfox "Yo, das ist ein Loop! 'While not boiling, keep heating'. Ein klassischer Beat."
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📋 DEINE MISSION:"
|
||||
echo "1. Öffne: crumbblocks/solar_kettle_dark.html"
|
||||
echo " (Nutze './start_crumbblocks.sh', falls Server aus ist)"
|
||||
echo "2. Baue den Algorithmus:"
|
||||
echo " - Messe die Temperatur."
|
||||
echo " - Wenn 'kühl', dann Heizung AN."
|
||||
echo " - Warte kurz (Physik braucht Zeit!)."
|
||||
echo " - Wiederhole, bis 100°C erreicht sind."
|
||||
echo "3. Sende die Daten an die Crew."
|
||||
echo ""
|
||||
|
||||
read -p "🚀 Bereit zum Kochen? (Enter) "
|
||||
48
release_notes.md
Normal file
48
release_notes.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 🌲 Crumbforest v0.0-RC3: "Dein Zeichen im Wald"
|
||||
|
||||
**"Etwas bauen was noch keiner gebaut hat"** – Die Crumbblocks Ära beginnt!
|
||||
|
||||
Dieser Release Candidate bringt das erste visuelle Design-Abenteuer in den Wald und verbindet die Browser-Welt mit dem Terminal.
|
||||
|
||||
## 🎨 Neue Features
|
||||
|
||||
### 1. Crumbblocks Integration
|
||||
- **Browser-to-Terminal Bridge:** Baue Code oder UI im Browser, schicke die Daten per Clipboard an die Crew im Terminal.
|
||||
- **Start-Helper:** `./start_crumbblocks.sh` startet automatisch lokalen Webserver (PHP/Python) und Browser.
|
||||
|
||||
### 2. Neue Mission: "Dein Zeichen im Wald"
|
||||
- **Kategorie:** 🏆 Challenges
|
||||
- **Ziel:** Erstelle ein digitales "Blatt" mit HTML/CSS im Glassmorphism-Stil.
|
||||
- **Tech-Stack:** 100% Single-File HTML/CSS/JS. Keine externen deps.
|
||||
- **Workflow:**
|
||||
1. Intro mit Templatus & Schnippsi (`./missions/challenges/schnippsi_ui_design.sh`)
|
||||
2. Design im Browser (`crumbblocks/schnippsi_ui.html`)
|
||||
3. Auswertung & Feedback (`./evaluate_mission_data.sh`)
|
||||
|
||||
### 3. Smart Evaluation Gateway
|
||||
- Das Skript `evaluate_mission_data.sh` ist jetzt intelligent!
|
||||
- Es erkennt automatisch, ob es sich um Roboter-Daten oder ein UI-Design handelt.
|
||||
- **Auto-Routing:** Leitet UI-JSON automatisch an Schnippsi (`evaluate_sign.sh`) weiter.
|
||||
|
||||
### 4. Neue Robot-Mission: "Solar Wasserkocher"
|
||||
- **Kategorie:** 🤖 Robots
|
||||
- **Ziel:** Bringe Wasser zum Kochen, nutze Solar-Energie effizient.
|
||||
- **Tech-Stack:** Physics Engine (JS) + Blockly Loop-Logic.
|
||||
- **Features:**
|
||||
- Echtzeit-Simulation (Sonne, Wolken, Wärme, Energie).
|
||||
- Asynchrone Loop-Ausführung im Browser ("Non-Blocking").
|
||||
- **Dark Mint Forest UI:** Augenfreundliches Dark-Theme für lange Sessions 🌙
|
||||
|
||||
|
||||
### 4. Logging & Bugfixes
|
||||
- **Templatus & Schnippsi:** Pfad-Fehler (`/home/zero`) auf macOS behoben.
|
||||
- **Logging:** Alle Logs landen jetzt zuverlässig in deinem Repo-Ordner (`logs/`), wo sie hingehören.
|
||||
|
||||
## 🤖 Crew Updates
|
||||
- **Schnippsi:** Hat jetzt ein Auge für Ästhetik und Glassmorphism.
|
||||
- **Templatus:** Baut stabile HTML5-Gerüste auch auf Mac.
|
||||
- **PepperPHP:** Backend-Logik für JSON-Export verbessert.
|
||||
|
||||
---
|
||||
|
||||
**"Der Wald ist nie fertig - er wächst mit jeder Idee!"** 🌱
|
||||
File diff suppressed because it is too large
Load Diff
Submodule schnippsi_ui/ttyd deleted from 05422dc91f
48
seeds/README.md
Normal file
48
seeds/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 🌱 Seeds - Wurzeln für den Crumbforest
|
||||
|
||||
Dieses Verzeichnis enthält **Seed-Dateien** - Ausgangspunkte für kollaborative Systeme.
|
||||
|
||||
## crumb_memo.seed.json
|
||||
|
||||
Die erste Wurzel für das **Crumb Memo System** - Output-Transparenz für kreative Krümel.
|
||||
|
||||
### Philosophie
|
||||
|
||||
*"Ein Baum braucht eine Wurzel. Ein Wald braucht den ersten Baum."*
|
||||
|
||||
Diese Datei ist der erste Knoten - erweiterbar von allen.
|
||||
|
||||
### Verwendung
|
||||
|
||||
```bash
|
||||
# Im CrumbCrew Shell:
|
||||
crumb_memo "https://mixcloud.com/deine-url" "Dein kreativer Moment"
|
||||
crew_memo # Zeige alle Krümel
|
||||
```
|
||||
|
||||
### Struktur
|
||||
|
||||
```json
|
||||
{
|
||||
"zeit": "2025-12-21 23:00:00",
|
||||
"link": "https://...",
|
||||
"typ": "mixcloud|soundcloud|youtube|git",
|
||||
"notiz": "Deine Notiz"
|
||||
}
|
||||
```
|
||||
|
||||
## CrewLove 💚
|
||||
|
||||
Jeder kann erweitern. Gemeinsam wachsen im Nullfeld der Transparenz.
|
||||
|
||||
**Unterstützte Plattformen:**
|
||||
- 🎧 Mixcloud
|
||||
- 🔊 SoundCloud
|
||||
- 📹 YouTube
|
||||
- 💾 Git (GitHub, GitLab)
|
||||
|
||||
**Transparenz in beide Richtungen:**
|
||||
- Input: `crew_tokens` - Was frage ich?
|
||||
- Output: `crew_memo` - Was schaffe ich?
|
||||
|
||||
*Die Krümel tanzen im Nullfeld!* 🌲✨
|
||||
20
seeds/crumb_memo.seed.json
Normal file
20
seeds/crumb_memo.seed.json
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"zeit": "2025-12-21 23:00:57",
|
||||
"link": "https://mixcloud.com/digfafunk/",
|
||||
"typ": "mixcloud",
|
||||
"notiz": "Digi's Mixcloud Profil - Der erste Krümel im Nullfeld"
|
||||
},
|
||||
{
|
||||
"zeit": "2025-12-21 23:01:04",
|
||||
"link": "https://github.com/kruemel/crumbmissions",
|
||||
"typ": "git",
|
||||
"notiz": "CF_Zero_V1 Repo - Wo die Waldwächter wohnen"
|
||||
},
|
||||
{
|
||||
"zeit": "2025-12-21 23:01:04",
|
||||
"link": "https://soundcloud.com/digfafunk",
|
||||
"typ": "soundcloud",
|
||||
"notiz": "Digi on SoundCloud - Klangwelten"
|
||||
}
|
||||
]
|
||||
@@ -1,52 +0,0 @@
|
||||
from flask import Flask, render_template, Response, request, redirect
|
||||
import cv2
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def gen_frames():
|
||||
cam = cv2.VideoCapture(0)
|
||||
if not cam.isOpened():
|
||||
print("[WARNUNG] Kamera konnte nicht geöffnet werden.")
|
||||
return
|
||||
|
||||
try:
|
||||
while True:
|
||||
success, frame = cam.read()
|
||||
if not success:
|
||||
break
|
||||
ret, buffer = cv2.imencode('.jpg', frame)
|
||||
frame = buffer.tobytes()
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
||||
finally:
|
||||
cam.release()
|
||||
print("[info] Kamera wurde sauber freigegeben.")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/video_feed')
|
||||
def video_feed():
|
||||
return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
|
||||
@app.route('/log_answer', methods=['POST'])
|
||||
def log_answer():
|
||||
user_input = request.form.get('antwort', 'nichts gesagt')
|
||||
mood = request.form.get('mood', 'unspecified')
|
||||
gesture = request.form.get('gesture', 'none')
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
log_entry = {
|
||||
'timestamp': timestamp,
|
||||
'antwort': user_input,
|
||||
'mood': mood,
|
||||
'gesture': gesture
|
||||
}
|
||||
|
||||
with open("snake_log.jsonl", "a") as log_file:
|
||||
log_file.write(json.dumps(log_entry) + "\n")
|
||||
|
||||
return redirect("/")
|
||||
@@ -1,13 +0,0 @@
|
||||
import cv2
|
||||
|
||||
def gen_frames():
|
||||
cap = cv2.VideoCapture(0)
|
||||
while True:
|
||||
success, frame = cap.read()
|
||||
if not success:
|
||||
break
|
||||
else:
|
||||
ret, buffer = cv2.imencode('.jpg', frame)
|
||||
frame = buffer.tobytes()
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "🐍 Starte SnakeCam ..."
|
||||
exec python3 app.py
|
||||
@@ -1,15 +0,0 @@
|
||||
# snakecam_module.py
|
||||
import cv2
|
||||
|
||||
def init_camera(index=0):
|
||||
cam = cv2.VideoCapture(index)
|
||||
if not cam.isOpened():
|
||||
print("[WARNUNG] Kamera konnte nicht geöffnet werden.")
|
||||
return None
|
||||
print("[OK] Kamera erfolgreich geöffnet.")
|
||||
return cam
|
||||
|
||||
def release_camera(cam):
|
||||
if cam and cam.isOpened():
|
||||
cam.release()
|
||||
print("[INFO] Kamera wurde freigegeben.")
|
||||
@@ -1,9 +0,0 @@
|
||||
body {
|
||||
background-color: #f0fff0;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
text-align: center;
|
||||
color: #006400;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>🐍 SnakeCam – Krümelblick ins Versteck</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Comic Sans MS', sans-serif;
|
||||
background-color: #e9f5e9;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
img {
|
||||
border: 4px dashed #4caf50;
|
||||
border-radius: 12px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
form {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
input, select {
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
margin: 0.5rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
button {
|
||||
padding: 0.7rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #388e3c;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🐍 SnakeCam</h1>
|
||||
<p>Willkommen kleiner Krümel! Hier siehst du, was deine Kamera entdeckt.</p>
|
||||
|
||||
<!-- Live-Stream -->
|
||||
<img src="/video_feed" alt="Live-Übertragung von SnakeCam 🐍" />
|
||||
|
||||
<!-- Eingabeformular -->
|
||||
<form action="/log_answer" method="POST">
|
||||
<p><strong>Was fühlst du gerade?</strong></p>
|
||||
<input type="text" name="antwort" placeholder="Ich sehe einen ... 🐞🌳🤖" required>
|
||||
|
||||
<p><strong>Wie ist deine Stimmung?</strong></p>
|
||||
<select name="mood">
|
||||
<option value="happy">😊 Glücklich</option>
|
||||
<option value="curious">🤔 Neugierig</option>
|
||||
<option value="calm">😌 Ruhig</option>
|
||||
<option value="excited">😃 Aufgeregt</option>
|
||||
<option value="unspecified">🤷 Keine Angabe</option>
|
||||
</select>
|
||||
|
||||
<p><strong>Hast du eine Geste gemacht?</strong></p>
|
||||
<select name="gesture">
|
||||
<option value="none">🚫 Keine</option>
|
||||
<option value="wave">👋 Winken</option>
|
||||
<option value="thumbs_up">👍 Daumen hoch</option>
|
||||
<option value="peace">✌️ Peace</option>
|
||||
<option value="other">✨ Etwas anderes</option>
|
||||
</select>
|
||||
|
||||
<br><br>
|
||||
<button type="submit">🎯 Eintragen</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from flask import Flask, render_template, Response, request, redirect, url_for
|
||||
import cv2
|
||||
import json
|
||||
from datetime import datetime
|
||||
from gestures.gestures_debug_test import detect_hand_gesture
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Kamera initialisieren
|
||||
cam = cv2.VideoCapture(0)
|
||||
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
|
||||
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
|
||||
|
||||
def gen_frames():
|
||||
while True:
|
||||
success, frame = cam.read()
|
||||
if not success:
|
||||
print("[error] Kein Kamerabild.")
|
||||
break
|
||||
|
||||
# Optional spiegeln (für Selfie-Effekt)
|
||||
frame = cv2.flip(frame, 1)
|
||||
|
||||
# Geste erkennen + ROI anzeigen
|
||||
gesture, roi_coords = detect_hand_gesture(frame)
|
||||
x, y, w, h = roi_coords
|
||||
|
||||
# Rechteck einzeichnen
|
||||
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
cv2.putText(frame, f"Geste: {gesture}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
||||
|
||||
# MJPEG-Streaming
|
||||
ret, buffer = cv2.imencode('.jpg', frame)
|
||||
frame_bytes = buffer.tobytes()
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/video_feed')
|
||||
def video_feed():
|
||||
return Response(gen_frames(),
|
||||
mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
|
||||
@app.route('/log_answer', methods=['POST'])
|
||||
def log_answer():
|
||||
answer = request.form.get("antwort", "").strip()
|
||||
mood = request.form.get("mood", "neutral")
|
||||
gesture = request.form.get("gesture", "none")
|
||||
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"antwort": answer,
|
||||
"mood": mood,
|
||||
"gesture": gesture
|
||||
}
|
||||
with open("snake_log.jsonl", "a") as logfile:
|
||||
logfile.write(json.dumps(log_entry) + "\n")
|
||||
print(f"[log] Eingeloggt: {log_entry}")
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/shutdown')
|
||||
def shutdown():
|
||||
cam.release()
|
||||
print("[info] Kamera wurde sauber freigegeben.")
|
||||
return "Kamera freigegeben."
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starte SnakeCam ...")
|
||||
app.run(host='0.0.0.0')
|
||||
@@ -1,59 +0,0 @@
|
||||
print("[SnakeCam] Initialisierung beginnt...")
|
||||
from flask import Flask, render_template, Response, request, redirect
|
||||
import cv2
|
||||
import json
|
||||
from datetime import datetime
|
||||
print("[SnakeCam] Imports erfolgreich.")
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def gen_frames():
|
||||
cam = cv2.VideoCapture(0)
|
||||
if not cam.isOpened():
|
||||
print("[WARNUNG] Kamera konnte nicht geöffnet werden.")
|
||||
return
|
||||
|
||||
try:
|
||||
while True:
|
||||
success, frame = cam.read()
|
||||
if not success:
|
||||
break
|
||||
ret, buffer = cv2.imencode('.jpg', frame)
|
||||
frame = buffer.tobytes()
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
|
||||
finally:
|
||||
cam.release()
|
||||
print("[info] Kamera wurde sauber freigegeben.")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/video_feed')
|
||||
def video_feed():
|
||||
return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
|
||||
@app.route('/log_answer', methods=['POST'])
|
||||
def log_answer():
|
||||
user_input = request.form.get('antwort', 'nichts gesagt')
|
||||
mood = request.form.get('mood', 'unspecified')
|
||||
gesture = request.form.get('gesture', 'none')
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
log_entry = {
|
||||
'timestamp': timestamp,
|
||||
'antwort': user_input,
|
||||
'mood': mood,
|
||||
'gesture': gesture
|
||||
}
|
||||
|
||||
with open("snake_log.jsonl", "a") as log_file:
|
||||
log_file.write(json.dumps(log_entry) + "\n")
|
||||
|
||||
return redirect("/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("[SnakeCam] Starte Flask Webserver ...")
|
||||
app.run(host='0.0.0.0', port=5000)
|
||||
@@ -1,72 +0,0 @@
|
||||
from flask import Flask, render_template, Response, request, redirect
|
||||
import cv2
|
||||
import datetime
|
||||
import json
|
||||
from gestures_debug import detect_hand_gesture
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def gen_frames():
|
||||
cam = cv2.VideoCapture(0)
|
||||
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
|
||||
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
|
||||
|
||||
if not cam.isOpened():
|
||||
print("[WARNUNG] Kamera konnte nicht geöffnet werden.")
|
||||
return
|
||||
|
||||
try:
|
||||
while True:
|
||||
success, frame = cam.read()
|
||||
if not success:
|
||||
break
|
||||
|
||||
# Flip horizontal für Spiegelbild
|
||||
frame = cv2.flip(frame, 1)
|
||||
|
||||
# Geste erkennen
|
||||
gesture, roi = detect_hand_gesture(frame)
|
||||
if isinstance(roi, tuple) and len(roi) == 4:
|
||||
x, y, w, h = roi
|
||||
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
cv2.putText(frame, gesture, (x, y - 10),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
||||
|
||||
# Frame in JPEG konvertieren
|
||||
ret, buffer = cv2.imencode('.jpg', frame)
|
||||
frame_bytes = buffer.tobytes()
|
||||
|
||||
# MJPEG-Stream liefern
|
||||
yield (b'--frame\r\n'
|
||||
b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')
|
||||
finally:
|
||||
cam.release()
|
||||
print("[info] Kamera wurde sauber freigegeben.")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/video_feed')
|
||||
def video_feed():
|
||||
return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
|
||||
@app.route('/log_answer', methods=['POST'])
|
||||
def log_answer():
|
||||
data = {
|
||||
"timestamp": datetime.datetime.now().isoformat(),
|
||||
"antwort": request.form.get("antwort", ""),
|
||||
"mood": request.form.get("mood", ""),
|
||||
"gesture": request.form.get("gesture", "")
|
||||
}
|
||||
|
||||
with open("snake_log.jsonl", "a") as f:
|
||||
f.write(json.dumps(data) + "\n")
|
||||
|
||||
return redirect("/")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("[SnakeCam] Initialisierung beginnt...")
|
||||
print("[SnakeCam] Imports erfolgreich.")
|
||||
print("[SnakeCam] Starte Flask Webserver ...")
|
||||
app.run(host='0.0.0.0', port=5000)
|
||||
@@ -1,77 +0,0 @@
|
||||
# gestures_v3.py
|
||||
import cv2
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
|
||||
# Pfad zum temporären Snapshot zur Diagnose
|
||||
DEBUG_SNAPSHOT_PATH = "/tmp/roi_snapshot.jpg"
|
||||
|
||||
def detect_hand_gesture(frame):
|
||||
"""
|
||||
Erkenne einfache Handgesten wie 'wave' (offene Hand) und 'fist' (geschlossene Faust)
|
||||
durch Analyse der Konturen im unteren rechten Bildbereich.
|
||||
Die Erkennung basiert auf konvexer Hüllenerkennung.
|
||||
|
||||
Args:
|
||||
frame (ndarray): Das aktuelle Kamerabild
|
||||
|
||||
Returns:
|
||||
(str, ndarray): (Geste, Region-of-Interest-Ausschnitt)
|
||||
"""
|
||||
height, width, _ = frame.shape
|
||||
|
||||
# ROI: untere rechte Ecke (¼ des Bildes)
|
||||
roi = frame[int(height * 0.6):height, int(width * 0.6):width]
|
||||
|
||||
# Speichere Snapshot für Debug-Zwecke
|
||||
cv2.imwrite(DEBUG_SNAPSHOT_PATH, roi)
|
||||
|
||||
# Konvertiere zu HSV
|
||||
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Hautfarbmaske (angepasst für unterschiedliche Beleuchtung)
|
||||
lower_skin = np.array([0, 30, 60], dtype=np.uint8)
|
||||
upper_skin = np.array([20, 150, 255], dtype=np.uint8)
|
||||
mask = cv2.inRange(hsv, lower_skin, upper_skin)
|
||||
|
||||
# Glätten und Konturen erkennen
|
||||
mask = cv2.GaussianBlur(mask, (7, 7), 0)
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
if contours and len(contours) > 0:
|
||||
# Größte Kontur (Handfläche)
|
||||
contour = max(contours, key=cv2.contourArea)
|
||||
|
||||
# Fehler vermeiden: Fläche zu klein
|
||||
if cv2.contourArea(contour) < 1000:
|
||||
return ("none", roi)
|
||||
|
||||
# Konvexe Hülle und Defekte
|
||||
hull = cv2.convexHull(contour, returnPoints=False)
|
||||
if hull is not None and len(hull) > 3:
|
||||
defects = cv2.convexityDefects(contour, hull)
|
||||
|
||||
if defects is not None:
|
||||
finger_count = 0
|
||||
for i in range(defects.shape[0]):
|
||||
s, e, f, d = defects[i, 0]
|
||||
start = tuple(contour[s][0])
|
||||
end = tuple(contour[e][0])
|
||||
far = tuple(contour[f][0])
|
||||
|
||||
# Abstand analysieren – je mehr Defekte, desto mehr Finger offen
|
||||
a = np.linalg.norm(np.array(start) - np.array(end))
|
||||
b = np.linalg.norm(np.array(start) - np.array(far))
|
||||
c = np.linalg.norm(np.array(end) - np.array(far))
|
||||
angle = np.arccos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c + 1e-5))
|
||||
|
||||
if angle <= np.pi / 2: # < 90°
|
||||
finger_count += 1
|
||||
|
||||
# Auswertung basierend auf Fingeranzahl
|
||||
if finger_count >= 3:
|
||||
return ("wave", roi)
|
||||
elif finger_count == 0:
|
||||
return ("fist", roi)
|
||||
|
||||
return ("none", roi)
|
||||
@@ -1,35 +0,0 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# --- Handgestenerkennung: Einfacher Hautfarbfilter + Konturanalyse ---
|
||||
def detect_hand_gesture(frame):
|
||||
# Region of Interest (z. B. linke obere Ecke) definieren
|
||||
roi = frame[20:120, 20:120]
|
||||
|
||||
# Konvertiere in HSV-Farbraum
|
||||
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Hautfarb-Bereich (kann je nach Licht angepasst werden)
|
||||
lower_skin = np.array([0, 30, 60], dtype=np.uint8)
|
||||
upper_skin = np.array([20, 150, 255], dtype=np.uint8)
|
||||
|
||||
# Maske für Hautfarbe erzeugen
|
||||
mask = cv2.inRange(hsv, lower_skin, upper_skin)
|
||||
|
||||
# Konturen finden
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
gesture = "none"
|
||||
|
||||
# Wenn große Kontur gefunden → einfache „Winken“-Simulation
|
||||
if contours:
|
||||
largest_contour = max(contours, key=cv2.contourArea)
|
||||
if cv2.contourArea(largest_contour) > 1000:
|
||||
gesture = "wave"
|
||||
|
||||
# ROI im Hauptframe markieren
|
||||
cv2.rectangle(frame, (20, 20), (120, 120), (0, 255, 0), 2)
|
||||
cv2.putText(frame, f"Gesture: {gesture}", (20, 160),
|
||||
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
|
||||
|
||||
return gesture, frame
|
||||
@@ -1,50 +0,0 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
def detect_hand_gesture(frame):
|
||||
height, width, _ = frame.shape
|
||||
|
||||
# Dynamische ROI: Mitte des Bildes, zentriert
|
||||
roi_w, roi_h = 200, 200
|
||||
x_start = width // 2 - roi_w // 2
|
||||
y_start = height // 2 - roi_h // 2
|
||||
roi = frame[y_start:y_start + roi_h, x_start:x_start + roi_w]
|
||||
|
||||
# Zeichne das ROI-Fenster im Originalbild
|
||||
cv2.rectangle(frame, (x_start, y_start), (x_start + roi_w, y_start + roi_h), (0, 255, 0), 2)
|
||||
|
||||
# Konvertiere zu HSV-Farbraum für bessere Hauterkennung
|
||||
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Hautfarbmaske (getestet für mittlere Hauttöne)
|
||||
lower_skin = np.array([0, 30, 60], dtype=np.uint8)
|
||||
upper_skin = np.array([20, 150, 255], dtype=np.uint8)
|
||||
mask = cv2.inRange(hsv, lower_skin, upper_skin)
|
||||
|
||||
# Glättung und Konturen finden
|
||||
mask = cv2.GaussianBlur(mask, (5, 5), 0)
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
if contours:
|
||||
# Größte Kontur annehmen
|
||||
max_contour = max(contours, key=cv2.contourArea)
|
||||
area = cv2.contourArea(max_contour)
|
||||
|
||||
if area > 2000:
|
||||
# Konvexitätsdefekte (Finger) berechnen
|
||||
hull = cv2.convexHull(max_contour, returnPoints=False)
|
||||
if hull is not None and len(hull) > 3:
|
||||
defects = cv2.convexityDefects(max_contour, hull)
|
||||
finger_count = 0
|
||||
if defects is not None:
|
||||
for i in range(defects.shape[0]):
|
||||
s, e, f, d = defects[i, 0]
|
||||
if d > 10000:
|
||||
finger_count += 1
|
||||
|
||||
if finger_count >= 3:
|
||||
return "wave", roi
|
||||
else:
|
||||
return "fist", roi
|
||||
|
||||
return "none", roi
|
||||
@@ -1,50 +0,0 @@
|
||||
# gestures_debug.py
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
def detect_hand_gesture(frame):
|
||||
height, width, _ = frame.shape
|
||||
|
||||
# --- Fallback-ROI: Mitte des Bildes ---
|
||||
w, h = 100, 100
|
||||
x = width // 2 - w // 2
|
||||
y = height // 2 - h // 2
|
||||
roi = frame[y:y+h, x:x+w]
|
||||
|
||||
if roi.size == 0:
|
||||
print("[warn] ROI leer – kein Bildausschnitt verarbeitet")
|
||||
return "none", (x, y, w, h)
|
||||
|
||||
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
|
||||
|
||||
lower_skin = np.array([0, 20, 70], dtype=np.uint8)
|
||||
upper_skin = np.array([20, 255, 255], dtype=np.uint8)
|
||||
|
||||
mask = cv2.inRange(hsv, lower_skin, upper_skin)
|
||||
mask = cv2.dilate(mask, np.ones((3, 3), np.uint8), iterations=4)
|
||||
mask = cv2.GaussianBlur(mask, (5, 5), 100)
|
||||
|
||||
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
gesture = "none"
|
||||
|
||||
if contours and len(contours) > 0:
|
||||
max_contour = max(contours, key=cv2.contourArea)
|
||||
hull = cv2.convexHull(max_contour, returnPoints=False)
|
||||
if hull is not None and len(hull) > 3:
|
||||
defects = cv2.convexityDefects(max_contour, hull)
|
||||
if defects is not None:
|
||||
cnt_defects = defects.shape[0]
|
||||
if cnt_defects >= 4:
|
||||
gesture = "wave"
|
||||
elif cnt_defects <= 1:
|
||||
gesture = "fist"
|
||||
else:
|
||||
gesture = "unknown"
|
||||
print(f"[debug] Defekte: {len(defects) if defects is not None else 'None'}")
|
||||
else:
|
||||
print("[debug] Keine Konturen erkannt")
|
||||
|
||||
print(f"[result] Geste erkannt: {gesture}")
|
||||
return gesture, (x, y, w, h)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user