% --------------------------------------------------------------------------- % robotkarol.sty — Robot-Karol-Welten und Blockprogramme für Unterrichtsmaterial % % Copyright (C) 2026 Anselm Wagner % % This work may be distributed and/or modified under the conditions of % the LaTeX Project Public License, either version 1.3c of this license % or (at your option) any later version. The latest version of this % license is in https://www.latex-project.org/lppl.txt and version % 1.3c or later is part of all distributions of LaTeX version % 2008-05-04 or later. % % This work has the LPPL maintenance status `maintained'. % The Current Maintainer of this work is Anselm Wagner. % % This work consists of the files robotkarol.sty, robotkarol-doc.tex, % robotkarol-beispiel.tex, robotkarol.cwl, robotkarol-cheatsheet.md, % README.md and the derived files robotkarol-doc.pdf and % robotkarol-beispiel.pdf. % --------------------------------------------------------------------------- % Das Paket zeichnet Welten und Blockprogramme im Stil von Robot Karol % Online (https://karol.arrrg.de/) für Arbeitsblätter und Prüfungen. % Es benötigt LuaLaTeX. % % Bereitgestellt werden: % * karolwelt-Umgebung — volle Welten (breite x laenge), wahlweise % 3D-Schrägbild oder 2D-Draufsicht; Objekte: \karol, \ziegel, % \marke, \quader (= \wand) % * \karolzeile / \karolpaar / \karolgitter / \karollegende % — kompakte einzeilige Welten über Zeichenketten. Sie zeichnen % mit denselben Bausteinen wie die karolwelt-Umgebung: eine % Zeilen-Welt ist optisch eine karolwelt mit laenge=1. % * karolbloecke-Umgebung mit \bbefehl, \bwiederholemal, % \bwiederholesolange, \bwiederholeimmer, \bwenndann, % \bwenndannsonst, \bluecke, \bkommentar, \baufruf, \banweisung, % \bhauptprogramm, \bbedingung, \bist, \bnichtist % — Blockprogrammierung % * Inline-Blöcke für den Fließtext: \ibefehl, \iaufruf, % \ianweisung, \ikommentar, \ihauptprogramm, \iwiederholemal, % \iwiederholesolange, \iwiederholeimmer, \iwenndann, \isonst, % \ibedingung, \iist, \inichtist, \iluecke. Sie setzen genau % einen Block in die % laufende Zeile: Textgrundlinie und Schriftgröße wie der umgebende % Text, flachere Verzierungen. Mehrzeilige Blöcke erscheinen % inline nur mit ihrem Kopf. % * karolcode-Umgebung + \kb, \kw — Karol-Programme als Text % * struktex-Wrapper + \leeranweisung — Struktogramme % * \karolsetup — globale Einstellungen (Farben, Feldbreite, Figur) % * \karolfigur{karol|emma} — Aussehen der Figur in den Welten % (Vorgabe: karol; emma hat langes Haar und eine Blume) % * \karolschrift / \karolcodeschrift — die Schriften der Zeichnungen % auch für eigenen Text (siehe Abschnitt 0) % % NOTATION DER ZEILEN-WELTEN (\karolzeile): % > < ^ v Karol mit Blickrichtung; . leeres Feld; Z Ziegel; % 2…9 Ziegelstapel; M Marke; Q Quader; % {…} mehrere Objekte auf einem Feld, z. B. {M>}, {2M}, {3>}. % Leerzeichen sind erlaubt: \karolzeile{> Z . {2M} . Q} % --------------------------------------------------------------------------- % ÄNDERUNGEN % v3.6 Vorgabefigur ist wieder „karol"; „emma" holt man sich mit % \karolfigur{emma} bzw. \karolsetup{figur=emma}. % Schriften wie in der Oberfläche von Robot Karol Online: % Beschriftungen in Noto Sans, Programmtext in Hack (ersatz- % weise DejaVu Sans Mono). Dafür wird fontspec geladen. Mit % \usepackage[schrift=dokument]{robotkarol} bleibt es bei den % Schriften des Dokuments (Ausgabe wie bis v3.5); zur Laufzeit % umschaltbar mit \karolsetup{schrift=…, textschrift=…, % codeschrift=…}. % v3.5 Zweite Figur „emma" (langes Haar, Blume), umschaltbar mit % \karolfigur bzw. \karolsetup{figur=…}; die bisherige Figur % heißt „karol". % v3.4 Anschliff physikalisch statt durch Zerlegen der Kontur: auf % die Fläche beschneiden, Rand hell, Rand dunkel nach links % oben versetzt darüber. Jede nach oben oder links zeigende % Kante wird dadurch hell, jede nach unten oder rechts % zeigende dunkel — Ecken folgen der Blockform exakt und die % Striche bleiben im Block, sodass Blöcke ineinanderpassen. % v3.3 Anschliff der Blöcke aufgeteilt: Oberseite und linke Seite % hell, alles übrige dunkel. % v3.2 Blockoptik näher an der Oberfläche: jeder Block ist umrandet % (Kontur farbe!80!black, helle Oberkante farbe!70!white), damit % auch gleichfarbige Blöcke voneinander abgesetzt sind; % Beschriftungen normal statt fett, Pillentext reines Schwarz, % der Stecker der Bedingungen ist eine Puzzlenase. % v3.1 Himmelsrichtungs-Bedingungen \bist / \bnichtist mit % Auswahlmenü (acht Blöcke wie in Robot Karol Online). Der % Name in \baufruf steht jetzt wie im Original in einer % Auswahlpille mit Dreieck. % v3.0 karolwelt-Umgebung (volle Welten, 3D und 2D). \karolzeile % zeichnet im selben Design und teilt sich die Bausteine mit % ihr; Feldnummern stehen vor der vordersten Gitterkante, % Hervorhebungen sind magenta (neue Schlüssel: rahmen, hoehe, % hervorfarbe, gitterfarbe). Neu: Inline-Blöcke. Das Kommando % \karolwelt aus v2.x heißt jetzt \karolzeile. % --------------------------------------------------------------------------- % Der LaTeX-Kern von 2022-06-01 wird für \ProcessKeyOptions gebraucht % (Schlüssel-Wert-Paketoptionen, siehe Abschnitt 0). \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesPackage{robotkarol}% [2026/08/23 v3.6 Robot-Karol-Welten und Blockprogramme (LuaLaTeX)] \ifdefined\directlua\else \PackageWarning{robotkarol}{Dieses Paket ist fuer LuaLaTeX gedacht}% \fi \RequirePackage{expl3} \RequirePackage{listings} \RequirePackage{struktex} \RequirePackage{tikz} \RequirePackage{graphicx} % =========================================================================== % 0. Schriften % =========================================================================== % Vorgabe sind die Schriften der Oberfläche von Robot Karol Online: % Beschriftungen in Noto Sans, Programmtext in Hack. Beide sind aus den % Referenzaufnahmen bestimmt (Noto Sans deckt sich in % `Anweisungen.png` pixelgenau mit dem Blocktext; Hack ist die Schrift % des Online-Editors, siehe dessen `public/fonts/hack.css`). % % Paketoption schrift = karol | dokument % karol (Vorgabe) Das Paket lädt fontspec und benutzt Noto Sans % (liegt in TeX Live) sowie Hack, falls es im % System installiert ist — sonst DejaVu Sans % Mono, aus dem Hack hervorgegangen ist. % dokument Es wird nichts geladen; das Paket setzt seine % Beschriftungen mit \sffamily und den % Programmtext mit \ttfamily, übernimmt also die % Schriften des Dokuments (Verhalten bis v3.5). % % Zur Laufzeit umschaltbar: \karolsetup{schrift=karol|dokument}. Eigene % Schriften über \karolsetup{textschrift=…} und \karolsetup{codeschrift=…}. % Titel und Hinweis einer Welt bleiben immer in der Dokumentschrift — % sie sind Text des Dokuments, keine Beschriftung der Zeichnung. % --------------------------------------------------------------------------- \ExplSyntaxOn \bool_new:N \g__karol_fontladen_bool % Paketoption: Schriften laden? \bool_gset_true:N \g__karol_fontladen_bool \bool_new:N \g__karol_fontda_bool % Karol-Schriften verfügbar? \tl_new:N \l__karol_schrift_tl % Umschaltcode für Beschriftungen \tl_new:N \l__karol_codeschrift_tl % Umschaltcode für Programmtext \keys_define:nn { karol / paket } { schrift .choice: , schrift / karol .code:n = { \bool_gset_true:N \g__karol_fontladen_bool }, schrift / dokument .code:n = { \bool_gset_false:N \g__karol_fontladen_bool }, } \ProcessKeyOptions [ karol / paket ] % fontspec braucht LuaTeX oder XeTeX und bricht sonst hart ab. Ohne % \directlua wird deshalb gar nichts geladen — das Paket setzt dann mit % den Schriften des Dokuments und bleibt wenigstens übersetzbar. \cs_if_exist:NF \directlua { \bool_gset_false:N \g__karol_fontladen_bool } \bool_if:NT \g__karol_fontladen_bool { \RequirePackage { fontspec } \newfontfamily \__karol_fonttext: [ Extension = .ttf , UprightFont = *-Regular , ItalicFont = *-Italic , BoldFont = *-Bold , BoldItalicFont = *-BoldItalic , Ligatures = TeX , Numbers = { Monospaced , Lining } , ] { NotoSans } % Hack liegt nicht in TeX Live. Ist es im System installiert, wird es % benutzt — sonst DejaVu Sans Mono, dessen Vorläufer (Bitstream Vera % Sans Mono) die Grundlage von Hack ist. \IfFontExistsTF { Hack } { \newfontfamily \__karol_fontmono: [ Ligatures = TeX ] { Hack } \PackageInfo { robotkarol } { Programmtext ~ in ~ Hack } } { \newfontfamily \__karol_fontmono: [ Extension = .ttf , UprightFont = * , ItalicFont = *-Oblique , BoldFont = *-Bold , BoldItalicFont = *-BoldOblique , Ligatures = TeX , ] { DejaVuSansMono } \PackageInfo { robotkarol } { Hack ~ nicht ~ gefunden ~ - ~ Programmtext ~ in ~ DejaVu ~ Sans ~ Mono } } \bool_gset_true:N \g__karol_fontda_bool } % Die beiden Umschalter, die überall im Paket benutzt werden. \cs_new_protected:Npn \__karol_textfont: { \tl_use:N \l__karol_schrift_tl } \cs_new_protected:Npn \__karol_monofont: { \tl_use:N \l__karol_codeschrift_tl } % Auswahl setzen (auch von \karolsetup{schrift=…} aus aufgerufen) \cs_new_protected:Npn \__karol_schrift_dokument: { \tl_set:Nn \l__karol_schrift_tl { \sffamily } \tl_set:Nn \l__karol_codeschrift_tl { \ttfamily } } \cs_new_protected:Npn \__karol_schrift_karol: { \bool_if:NTF \g__karol_fontda_bool { \tl_set:Nn \l__karol_schrift_tl { \__karol_fonttext: } \tl_set:Nn \l__karol_codeschrift_tl { \__karol_fontmono: } } { \PackageWarning { robotkarol } { Die ~ Schriften ~ von ~ Robot ~ Karol ~ Online ~ sind ~ nicht ~ geladen \MessageBreak (Paketoption ~ schrift=dokument ~ oder ~ keine ~ LuaTeX-Engine). \MessageBreak Es ~ werden ~ die ~ Schriften ~ des ~ Dokuments ~ benutzt } \__karol_schrift_dokument: } } \bool_if:NTF \g__karol_fontda_bool { \__karol_schrift_karol: } { \__karol_schrift_dokument: } % Öffentliche Umschalter — damit eigener Text zur Zeichnung passt. \NewDocumentCommand { \karolschrift } { } { \__karol_textfont: } \NewDocumentCommand { \karolcodeschrift } { } { \__karol_monofont: } \ExplSyntaxOff % =========================================================================== % 1. Karol-Programme (Monospace, Schlüsselwörter fett) % =========================================================================== \lstdefinelanguage{Karol}{% morekeywords={wiederhole,mal,endewiederhole,solange,immer,wenn,dann,sonst,endewenn}, sensitive=true, } \lstdefinestyle{karolstil}{% language = Karol, basicstyle = \karolcodeschrift\small, keywordstyle = \bfseries, columns = fullflexible, keepspaces = true, tabsize = 2, xleftmargin = 1em, aboveskip = .7em, belowskip = .7em, extendedchars = true, literate = {ö}{{\"o}}1 {ä}{{\"a}}1 {ü}{{\"u}}1 {Ö}{{\"O}}1 {Ä}{{\"A}}1 {Ü}{{\"U}}1 {ß}{{\ss}}1, } \lstnewenvironment{karolcode}[1][]{\lstset{style=karolstil,#1}}{} % Karol-Fragmente im Fließtext: \kb{IstZiegel}, Schlüsselwort: \kw{wenn} \newcommand*{\kb}[1]{{\karolcodeschrift #1}} \newcommand*{\kw}[1]{{\karolcodeschrift\bfseries #1}} % =========================================================================== % 2. Struktogramme (struktex) % =========================================================================== \newcommand*{\leeranweisung}{\ensuremath{\emptyset}} \newcommand*{\stklucke}[1][2.4cm]{\rule[-0.4ex]{#1}{0.4pt}} \setlength{\unitlength}{1mm} % =========================================================================== % 3. Farbschema (zentral, per \karolsetup änderbar) % =========================================================================== % --- Welt --- % Gezeichnet wird mit den karolW…-Farben (in Abschnitt 4, pixelgenau aus % Robot Karol Online gesampelt). Die vier Namen hier bleiben als % öffentliche Kurzformen bestehen: \karolsetup{ziegelfarbe=…} setzt sie % zusammen mit den zugehörigen karolW…-Farben. \definecolor{karolBoden}{RGB}{45,45,215} % Bodengitter \definecolor{karolZiegel}{RGB}{255,0,0} % Ziegel \definecolor{karolMarke}{RGB}{250,205,60} % Marke \definecolor{karolQuader}{RGB}{128,128,128} % Quader / Wand % --- Blöcke (Voreinstellung; an Karol Online anpassbar) --- % Blockfarben: exakt aus der Karol-Online-Oberfläche gesampelt % (systematische 91/165-Palette) \definecolor{karolBlockBefehl}{RGB}{116,91,165} % Anweisungen (violett) \definecolor{karolBlockSchleife}{RGB}{91,165,91} % Schleifen (grün) \definecolor{karolBlockWenn}{RGB}{91,165,91} % Verzweigungen (grün) \definecolor{karolBlockBedingung}{RGB}{91,128,165} % Bedingungen (stahlblau) \definecolor{karolBlockAnweisung}{RGB}{165,91,98} % eigene Anweisungen (rot) \definecolor{karolBlockKommentar}{RGB}{165,150,91} % Kommentare (khaki) \definecolor{karolBlockHut}{RGB}{130,123,117} % Hauptprogramm-Hut \ExplSyntaxOn % --------------------------------------------------------------------------- % \karolsetup{feldbreite=8mm, ziegelfarbe=red!70!black, …} % --------------------------------------------------------------------------- \dim_new:N \g__karol_feld_dim % Kantenlänge eines Feldes \dim_gset:Nn \g__karol_feld_dim { 8.4mm } \bool_new:N \l__karol_wemma_bool \tl_new:N \l__karol_wfigur_tl % gewählte Figur (vor keys_define: .initial % wird schon beim Definieren ausgeführt) \keys_define:nn { karol / setup } { feldbreite .dim_gset:N = \g__karol_feld_dim, % Weltfarben. Sie wirken auf \karolzeile UND die karolwelt-Umgebung, % die dieselben Bausteine zeichnen. bodenfarbe .code:n = { \colorlet{karolBoden}{#1} \colorlet{karolWGitter}{#1} }, gitterfarbe .meta:n = { bodenfarbe = #1 }, % ziegelfarbe setzt die leuchtende Lagenkante; der flachere Körper wird % daraus abgeleitet — dasselbe Verhältnis wie im Original (59 %). ziegelfarbe .code:n = { \colorlet{karolZiegel}{#1} \colorlet{karolWZiegelKante}{#1} \colorlet{karolWZiegelDrei}{#1!59!black} }, markenfarbe .code:n = { \colorlet{karolMarke}{#1} \colorlet{karolWMarke}{#1} }, quaderfarbe .code:n = { \colorlet{karolQuader}{#1} \colorlet{karolWZelleQuader}{#1} }, hervorfarbe .code:n = { \colorlet{karolWHervor}{#1} }, % Aussehen der Figur in \karolzeile und der karolwelt-Umgebung figur .choices:nn = { karol, emma } { \tl_set:Nx \l__karol_wfigur_tl { \l_keys_choice_tl } }, figur .initial:n = { karol }, % Schriften: karol = Noto Sans / Hack wie in Robot Karol Online, % dokument = \sffamily / \ttfamily des Dokuments (siehe Abschnitt 0). schrift .choices:nn = { karol, dokument } { \use:c { __karol_schrift_ \l_keys_choice_tl : } }, % Freie Wahl: der Wert ist der Umschaltbefehl selbst, % z. B. textschrift = { \fontspec{Fira~Sans} }. textschrift .tl_set:N = \l__karol_schrift_tl, codeschrift .tl_set:N = \l__karol_codeschrift_tl, % Feinsteuerung der Ziegel (nach ziegelfarbe zu setzen) ziegelkoerperfarbe .code:n = { \colorlet{karolWZiegelDrei}{#1} }, ziegelkantenfarbe .code:n = { \colorlet{karolWZiegelKante}{#1} }, befehlfarbe .code:n = { \colorlet{karolBlockBefehl}{#1} }, schleifenfarbe .code:n = { \colorlet{karolBlockSchleife}{#1} }, verzweigungfarbe .code:n = { \colorlet{karolBlockWenn}{#1} }, bedingungfarbe .code:n = { \colorlet{karolBlockBedingung}{#1} }, anweisungfarbe .code:n = { \colorlet{karolBlockAnweisung}{#1} }, kommentarfarbe .code:n = { \colorlet{karolBlockKommentar}{#1} }, hutfarbe .code:n = { \colorlet{karolBlockHut}{#1} }, } \NewDocumentCommand { \karolsetup } { m } { \keys_set:nn { karol / setup } { #1 } } % \karolfigur{karol} (Vorgabe) bzw. \karolfigur{emma} — Kurzform für % \karolsetup{figur=…}. Wirkt auf \karolzeile UND die karolwelt-Umgebung; % in einer Gruppe gesetzt gilt es nur dort. \NewDocumentCommand { \karolfigur } { m } { \keys_set:nn { karol / setup } { figur = #1 } } % =========================================================================== % 4. Karol-Welten im Schrägbild: Bausteine und karolwelt-Umgebung % =========================================================================== % \begin{karolwelt}[breite=5, laenge=3, ansicht=3d] % \karol[x=1, y=2, richtung=osten] % \ziegel[x=2, y=2, anzahl=1, bisx=4] % \marke[x=5, y=1] % \quader[x=4, y=3, bisy=3] (\wand = Synonym) % \end{karolwelt} % % Koordinaten wie in Robot Karol Online: x = Spalte (1 .. breite, von % links), y = Zeile (1 .. laenge, von HINTEN); hoehe = Raumhöhe (nur % 3D-Rahmen). In der 2D-Ansicht ist Zeile y=1 die oberste Zeile. % --------------------------------------------------------------------------- % Farben der Online-Oberfläche (gesampelt) \definecolor{karolWZiegel}{RGB}{224,48,48} % Ziegel in Zeilen-Welten \definecolor{karolWZiegelDrei}{RGB}{150,8,8} % Ziegelkörper 3D-Welt \definecolor{karolWZiegelKante}{RGB}{255,0,0} % Ziegel-Lagenkanten 3D \definecolor{karolWFuge}{RGB}{153,20,40} % Ziegelfugen (Zeilen-Welten) \definecolor{karolWMarke}{RGB}{250,205,60} % Marke 3D \definecolor{karolWMarkeRand}{RGB}{214,143,0} % Marke 3D, Kreuz/Rand \definecolor{karolWGitter}{RGB}{45,45,215} % Bodengitter % Hervorhebung: bewusst magenta statt rot — die Ziegelkanten sind reines % Rot, ein roter Rahmen wäre auf einem Ziegelfeld nicht zu erkennen. \definecolor{karolWHervor}{RGB}{226,0,122} % hervorgehobene Felder \definecolor{karolWZelleZiegel}{RGB}{255,205,205}% 2D: Ziegelzelle \definecolor{karolWZelleMarke}{RGB}{210,212,39} % 2D: Markenzelle \definecolor{karolWZelleQuader}{RGB}{128,128,128}% 2D: Quaderzelle % Avatar \definecolor{karolAvatarKappe}{RGB}{198,32,44} \definecolor{karolAvatarShirt}{RGB}{186,44,54} \definecolor{karolAvatarHose}{RGB}{62,92,168} \definecolor{karolAvatarHaut}{RGB}{244,204,158} \definecolor{karolAvatarHaar}{RGB}{122,74,34} % Zweite Figur „Emma": dieselbe Figur mit langem Haar und einer Blume. % Die Farben sind aus der Paketpalette abgeleitet, nicht aus der % Oberfläche übernommen — nachgebaut, nicht kopiert. \definecolor{karolEmmaShirt}{RGB}{212,124,130} % karolAvatarShirt aufgehellt \definecolor{karolEmmaHaar}{RGB}{138,86,48} % karolAvatarHaar aufgehellt \definecolor{karolEmmaBluete}{RGB}{250,205,60} % wie die Marke \definecolor{karolEmmaStiel}{RGB}{60,150,70} % Projektion (aus den Online-Screenshots vermessen), in Feldbreiten: \fp_const:Nn \c__karol_wdx_fp { 0.47 } % Tiefenversatz je Reihe, x \fp_const:Nn \c__karol_wdy_fp { 0.69 } % Tiefenversatz je Reihe, y \fp_const:Nn \c__karol_wzh_fp { 0.46 } % Höhe eines Ziegels \fp_const:Nn \c__karol_wqh_fp { 0.80 } % Höhe eines Quaders \tl_new:N \l__karol_tmp_tl % Streuvariable beider Renderer \int_new:N \l__karol_wbreite_int \int_new:N \l__karol_wlaenge_int \int_new:N \l__karol_whoehe_int \tl_new:N \l__karol_wansicht_tl \tl_new:N \l__karol_wskal_tl \tl_new:N \l__karol_wtitel_tl \tl_new:N \l__karol_whinweis_tl \prop_new:N \l__karol_wziegel_prop % "x,y" -> Stapelhöhe \prop_new:N \l__karol_wmarke_prop % "x,y" -> Farbe \prop_new:N \l__karol_wquader_prop % "x,y" -> 1 \bool_new:N \l__karol_wkarol_bool \int_new:N \l__karol_wkx_int \int_new:N \l__karol_wky_int \tl_new:N \l__karol_wkdir_tl % o | w | s | n \keys_define:nn { karol / weltumg } { breite .int_set:N = \l__karol_wbreite_int, breite .initial:n = { 5 }, laenge .int_set:N = \l__karol_wlaenge_int, laenge .initial:n = { 5 }, hoehe .int_set:N = \l__karol_whoehe_int, hoehe .initial:n = { 6 }, ansicht .choices:nn = { 3d, 2d } { \tl_set:Nx \l__karol_wansicht_tl { \l_keys_choice_tl } }, ansicht .initial:n = { 3d }, skalierung .tl_set:N = \l__karol_wskal_tl, skalierung .initial:n = { 1 }, titel .tl_set:N = \l__karol_wtitel_tl, titel .initial:n = { }, hinweis .tl_set:N = \l__karol_whinweis_tl, hinweis .initial:n = { }, } % Dieselben Vorgaben noch einmal als Liste: die Umgebung setzt sie bei % jedem Aufruf, denn .initial:n wirkt nur einmal beim Laden des Pakets. \tl_const:Nn \c__karol_wvorgabe_tl { breite = 5, laenge = 5, hoehe = 6, ansicht = 3d, skalierung = 1, titel = { }, hinweis = { }, } % --- Objekt-Schlüssel ------------------------------------------------------ \int_new:N \l__karol_wox_int \int_new:N \l__karol_woy_int \int_new:N \l__karol_wobisx_int \int_new:N \l__karol_wobisy_int \int_new:N \l__karol_woanz_int \tl_new:N \l__karol_wofarbe_tl \keys_define:nn { karol / objekt } { x .int_set:N = \l__karol_wox_int, y .int_set:N = \l__karol_woy_int, bisx .int_set:N = \l__karol_wobisx_int, bisy .int_set:N = \l__karol_wobisy_int, bisX .meta:n = { bisx = #1 }, bisY .meta:n = { bisy = #1 }, anzahl .int_set:N = \l__karol_woanz_int, farbe .tl_set:N = \l__karol_wofarbe_tl, richtung .choice: , richtung / osten .code:n = { \tl_set:Nn \l__karol_wkdir_tl { o } }, richtung / westen .code:n = { \tl_set:Nn \l__karol_wkdir_tl { w } }, richtung / sueden .code:n = { \tl_set:Nn \l__karol_wkdir_tl { s } }, richtung / norden .code:n = { \tl_set:Nn \l__karol_wkdir_tl { n } }, richtung / rechts .meta:n = { richtung = osten }, richtung / links .meta:n = { richtung = westen }, richtung / vorn .meta:n = { richtung = sueden }, richtung / vorne .meta:n = { richtung = sueden }, richtung / unten .meta:n = { richtung = sueden }, richtung / hinten .meta:n = { richtung = norden }, richtung / oben .meta:n = { richtung = norden }, } \cs_new_protected:Npn \__karol_wobjekt_reset: { \int_set:Nn \l__karol_wox_int { 1 } \int_set:Nn \l__karol_woy_int { 1 } \int_zero:N \l__karol_wobisx_int \int_zero:N \l__karol_wobisy_int \int_set:Nn \l__karol_woanz_int { 1 } \tl_set:Nn \l__karol_wofarbe_tl { karolWMarke } } % Bereich (x..bisx) x (y..bisy) auf ein Prop-Feld anwenden: #1 Prop, #2 Wert \cs_new_protected:Npn \__karol_wbereich:Nn #1#2 { \int_compare:nT { \l__karol_wobisx_int < \l__karol_wox_int } { \int_set_eq:NN \l__karol_wobisx_int \l__karol_wox_int } \int_compare:nT { \l__karol_wobisy_int < \l__karol_woy_int } { \int_set_eq:NN \l__karol_wobisy_int \l__karol_woy_int } \int_step_inline:nnn { \l__karol_wox_int } { \l__karol_wobisx_int } { \int_step_inline:nnn { \l__karol_woy_int } { \l__karol_wobisy_int } { \prop_put:Nnx #1 { ##1 , ####1 } {#2} } } } % Die Objektkommandos heißen \karol, \ziegel, \marke, \quader und \wand. % Das sind gewöhnliche deutsche Wörter, die ein Dokument selbst belegt % haben kann — global definiert würde das Paket dort nicht mehr laden. % Sie werden deshalb unter privaten Namen angelegt und erst in der % Umgebung karolwelt sichtbar gemacht (\__karol_wobjekte:). Da die % Umgebung eine Gruppe bildet, gilt eine etwaige eigene Bedeutung % außerhalb unverändert weiter. \NewDocumentCommand { \__karol_wobj_karol:w } { O{} } { \__karol_wobjekt_reset: \keys_set:nn { karol / objekt } { richtung = sueden, #1 } \bool_set_true:N \l__karol_wkarol_bool \int_set_eq:NN \l__karol_wkx_int \l__karol_wox_int \int_set_eq:NN \l__karol_wky_int \l__karol_woy_int } \NewDocumentCommand { \__karol_wobj_ziegel:w } { O{} } { \__karol_wobjekt_reset: \keys_set:nn { karol / objekt } { #1 } \__karol_wbereich:Nn \l__karol_wziegel_prop { \int_use:N \l__karol_woanz_int } } \NewDocumentCommand { \__karol_wobj_marke:w } { O{} } { \__karol_wobjekt_reset: \keys_set:nn { karol / objekt } { #1 } \exp_args:NNx \__karol_wbereich:Nn \l__karol_wmarke_prop { \tl_use:N \l__karol_wofarbe_tl } } \NewDocumentCommand { \__karol_wobj_quader:w } { O{} } { \__karol_wobjekt_reset: \keys_set:nn { karol / objekt } { #1 } \__karol_wbereich:Nn \l__karol_wquader_prop { 1 } } \cs_new_protected:Npn \__karol_wobjekte: { \cs_set_eq:NN \karol \__karol_wobj_karol:w \cs_set_eq:NN \ziegel \__karol_wobj_ziegel:w \cs_set_eq:NN \marke \__karol_wobj_marke:w \cs_set_eq:NN \quader \__karol_wobj_quader:w \cs_set_eq:NN \wand \__karol_wobj_quader:w } % --- 2D-Ansicht (Draufsicht wie in Karol Online) --------------------------- \cs_new_protected:Npn \__karol_wrender_iid: { \begin{tikzpicture} [ x=\dim_use:N \g__karol_feld_dim , y=-\dim_use:N \g__karol_feld_dim ] % Zellfüllungen \int_step_inline:nn { \l__karol_wbreite_int } { \int_step_inline:nn { \l__karol_wlaenge_int } { \__karol_wzelle_iid:nn { ##1 } { ####1 } } } % Gitter (explizit: „grid" nutzt xstep/ystep als Canvas-Maß und % versagt bei negativem y-Einheitsvektor) \int_step_inline:nnn { 0 } { \l__karol_wbreite_int } { \draw [ karolWGitter, line~width=.55pt ] ( ##1 , 0 ) -- ( ##1 , \int_use:N \l__karol_wlaenge_int ); } \int_step_inline:nnn { 0 } { \l__karol_wlaenge_int } { \draw [ karolWGitter, line~width=.55pt ] ( 0 , ##1 ) -- ( \int_use:N \l__karol_wbreite_int , ##1 ); } % Karol \bool_if:NT \l__karol_wkarol_bool { \begin{scope} [ shift={ ( { \l__karol_wkx_int - 0.5 }, { \l__karol_wky_int - 0.5 } ) }, rotate=\str_case:Vn \l__karol_wkdir_tl { { o } { 0 } { n } { 90 } { w } { 180 } { s } { -90 } } ] \fill [ black ] (-0.34, -0.26) -- (0.38, 0) -- (-0.34, 0.26) -- (-0.18, 0) -- cycle; \end{scope} } \end{tikzpicture} } \cs_new_protected:Npn \__karol_wzelle_iid:nn #1#2 { \prop_if_in:NnT \l__karol_wquader_prop { #1 , #2 } { \fill [ karolWZelleQuader ] ( {#1 - 1}, {#2 - 1} ) rectangle ( {#1}, {#2} ); } % Marke: Standardfarbe = X-Kreuz (wie Karol Online), sonst Vollfüllung \prop_get:NnNT \l__karol_wmarke_prop { #1 , #2 } \l__karol_tmp_tl { \str_if_eq:VnTF \l__karol_tmp_tl { karolWMarke } { \draw [ karolWZelleMarke , line~width = \dim_eval:n { 0.10 \g__karol_feld_dim } ] ( {#1 - 0.79}, {#2 - 0.79} ) -- ( {#1 - 0.21}, {#2 - 0.21} ) ( {#1 - 0.79}, {#2 - 0.21} ) -- ( {#1 - 0.21}, {#2 - 0.79} ); } { \fill [ \l__karol_tmp_tl ] ( {#1 - 1}, {#2 - 1} ) rectangle ( {#1}, {#2} ); } } \prop_get:NnNT \l__karol_wziegel_prop { #1 , #2 } \l__karol_tmp_tl { \prop_if_in:NnF \l__karol_wmarke_prop { #1 , #2 } { \fill [ karolWZelleZiegel ] ( {#1 - 1}, {#2 - 1} ) rectangle ( {#1}, {#2} ); } \node at ( {#1 - 0.5}, {#2 - 0.5} ) { \normalfont \__karol_textfont: \small \l__karol_tmp_tl }; } } % --- 3D-Ansicht (Schrägbild wie in Karol Online) --------------------------- % Bildkoordinaten einer Bodenecke: x-Spalte #1 (0-basiert), Tiefe #2 % (0 = vorderste Reihe), Höhe #3 (in Ziegellagen ausgedrückt als fp-Wert). % Reihe y (1 = hinten) hat Tiefe laenge - y. \cs_new:Npn \__karol_wpx:nn #1#2 { \fp_eval:n { (#1) + \c__karol_wdx_fp * (#2) } } \cs_new:Npn \__karol_wpy:nnn #1#2#3 { \fp_eval:n { \c__karol_wdy_fp * (#2) + \c__karol_wzh_fp * (#3) } } \cs_new_protected:Npn \__karol_wrender_iiid: { \begin{tikzpicture} [ x=\dim_use:N \g__karol_feld_dim , y=\dim_use:N \g__karol_feld_dim ] \__karol_wraum: % Objekte, Reihe für Reihe von hinten (y=1) nach vorn \int_step_inline:nn { \l__karol_wlaenge_int } { \int_step_inline:nn { \l__karol_wbreite_int } { \__karol_wzelle_iiid:nn { ####1 } { ##1 } } } \end{tikzpicture} } % Raum: Bodengitter, Wandlinien, oberer Rahmen. % In zwei Teile zerlegt, weil \karolzeile das Gitter immer, den Rahmen % aber nur auf Wunsch (Schlüssel rahmen) zeichnet. \cs_new_protected:Npn \__karol_wraum: { \__karol_wboden_gitter: \__karol_wrahmen: } % Bodengitter (blau, in beide Richtungen) \cs_new_protected:Npn \__karol_wboden_gitter: { \int_step_inline:nnn { 0 } { \l__karol_wbreite_int } { % Tiefenlinien \draw [ karolWGitter, line~width=.5pt ] ( \__karol_wpx:nn { ##1 } { 0 }, \__karol_wpy:nnn { ##1 } { 0 } { 0 } ) -- ( \__karol_wpx:nn { ##1 } { \l__karol_wlaenge_int }, \__karol_wpy:nnn { ##1 } { \l__karol_wlaenge_int } { 0 } ); } \int_step_inline:nnn { 0 } { \l__karol_wlaenge_int } { % Querlinien \draw [ karolWGitter, line~width=.5pt ] ( \__karol_wpx:nn { 0 } { ##1 }, \__karol_wpy:nnn { 0 } { ##1 } { 0 } ) -- ( \__karol_wpx:nn { \l__karol_wbreite_int } { ##1 }, \__karol_wpy:nnn { \l__karol_wbreite_int } { ##1 } { 0 } ); } } % Raumrahmen: Rückwand, linke Wand, oberer Rahmen (Raumhöhe = hoehe Lagen) \cs_new_protected:Npn \__karol_wrahmen: { % Raumhöhe in Bildeinheiten \fp_set:Nn \l__karol_wtmp_fp { \c__karol_wzh_fp * \l__karol_whoehe_int } % Rückwand: senkrechte gestrichelte Linien an jeder Gitterlinie \int_step_inline:nnn { 0 } { \l__karol_wbreite_int } { \draw [ black!55, line~width=.4pt, dashed ] ( \__karol_wpx:nn { ##1 } { \l__karol_wlaenge_int }, \__karol_wpy:nnn { ##1 } { \l__karol_wlaenge_int } { 0 } ) -- ++ ( 0, \fp_use:N \l__karol_wtmp_fp ); } % Linke Wand \int_step_inline:nnn { 0 } { \l__karol_wlaenge_int } { \draw [ karolWGitter!70!white, line~width=.4pt, dashed ] ( \__karol_wpx:nn { 0 } { ##1 }, \__karol_wpy:nnn { 0 } { ##1 } { 0 } ) -- ++ ( 0, \fp_use:N \l__karol_wtmp_fp ); } % Oberer Rahmen (strichpunktiert): hinten und links \draw [ black!70, line~width=.5pt, dash~pattern=on~4pt~off~2pt~on~.6pt~off~2pt ] ( \__karol_wpx:nn { 0 } { 0 }, \fp_eval:n { \l__karol_wtmp_fp } ) -- ( \__karol_wpx:nn { 0 } { \l__karol_wlaenge_int }, \fp_eval:n { \c__karol_wdy_fp * \l__karol_wlaenge_int + \l__karol_wtmp_fp } ) -- ( \__karol_wpx:nn { \l__karol_wbreite_int } { \l__karol_wlaenge_int }, \fp_eval:n { \c__karol_wdy_fp * \l__karol_wlaenge_int + \l__karol_wtmp_fp } ); } % Eine Zelle in 3D: Quader / Ziegelstapel / Marke / Karol \fp_new:N \l__karol_wtmp_fp \fp_new:N \l__karol_wz_fp \cs_new_protected:Npn \__karol_wzelle_iiid:nn #1#2 % #1 = x, #2 = y { \fp_zero:N \l__karol_wz_fp % Oberkante des Zellinhalts, in Lagen \prop_if_in:NnT \l__karol_wquader_prop { #1 , #2 } { \__karol_wquader_iiid:nnn { #1 } { #2 } { 0 } } \prop_get:NnNT \l__karol_wziegel_prop { #1 , #2 } \l__karol_tmp_tl { \int_step_inline:nn { \l__karol_tmp_tl } { \__karol_wziegel_iiid:nnn { #1 } { #2 } { ##1 - 1 } } \fp_set:Nn \l__karol_wz_fp { \l__karol_tmp_tl } } \prop_get:NnNT \l__karol_wmarke_prop { #1 , #2 } \l__karol_tmp_tl { \exp_args:Nnnx \__karol_wmarke_iiid:nnn { #1 } { #2 } { \tl_use:N \l__karol_tmp_tl } } \bool_if:NT \l__karol_wkarol_bool { \int_compare:nT { \l__karol_wkx_int = #1 } { \int_compare:nT { \l__karol_wky_int = #2 } { \__karol_wavatar_iiid:nn { #1 } { #2 } } } } } % Hilfsgrößen einer Zelle (#1 x, #2 y): Tiefe t = laenge - y \cs_new:Npn \__karol_wtiefe:n #1 { \int_eval:n { \l__karol_wlaenge_int - (#1) } } % Ziegel: Würfelsegment auf Lage #3 (0-basiert) \cs_new_protected:Npn \__karol_wziegel_iiid:nnn #1#2#3 { % Wie Karol Online: flacher dunkelroter Körper, jede Lage auf allen % Sichtflächen leuchtend rot umrandet (Stapelhöhe seitlich ablesbar) \__karol_wkubus:nnnnnnnn { #1 } { #2 } { #3 } { 1 } { karolWZiegelDrei } { karolWZiegelDrei } { karolWZiegelDrei } { karolWZiegelKante } } % Quader (grau, fester Höhenwert), ab Lage #3 \cs_new_protected:Npn \__karol_wquader_iiid:nnn #1#2#3 { \group_begin: \fp_set:Nn \l__karol_wtmp_fp { \c__karol_wqh_fp / \c__karol_wzh_fp } \__karol_wkubus:nnnnnnnn { #1 } { #2 } { #3 } { \fp_use:N \l__karol_wtmp_fp } { karolWZelleQuader } { karolWZelleQuader!88!black } { karolWZelleQuader!94!white } { black!75 } \group_end: } % Allgemeiner Kubus: Zelle (#1,#2), ab Lage #3, Höhe #4 Lagen, % Farben: #5 Front, #6 Seite, #7 Deckel, #8 Kanten \cs_new_protected:Npn \__karol_wkubus:nnnnnnnn #1#2#3#4#5#6#7#8 { \begin{scope} [ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } }, { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} } { #3 } } ) } ] % rechte Seitenfläche \fill [ #6 ] ( 1, 0 ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp } ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } ) -- cycle; % Deckfläche \fill [ #7 ] ( 0, { (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( { \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ) -- cycle; % Frontfläche \fill [ #5 ] ( 0, 0 ) rectangle ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } ); % Kanten aller Sichtflächen \draw [ #8, line~width=.5pt, line~join=round ] ( 0, 0 ) rectangle ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } ); \draw [ #8, line~width=.5pt, line~join=round ] ( 1, 0 ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp } ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( 1, { (#4) * \fp_use:N \c__karol_wzh_fp } ); \draw [ #8, line~width=.5pt, line~join=round ] ( 0, { (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( { \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ) -- ( { 1 + \fp_use:N \c__karol_wdx_fp }, { \fp_use:N \c__karol_wdy_fp + (#4) * \fp_use:N \c__karol_wzh_fp } ); \end{scope} } % Marke: flache Platte mit Diagonalkreuz auf Höhe \l__karol_wz_fp \cs_new_protected:Npn \__karol_wmarke_iiid:nnn #1#2#3 { \colorlet{karolWMtmp}{#3} \begin{scope} [ shift={ ( { \__karol_wpx:nn { #1 - 1 } { \__karol_wtiefe:n {#2} } }, { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} } { \fp_use:N \l__karol_wz_fp } } ) } ] \fill [ karolWMtmp ] ( 0.07, 0.05 ) -- ( 0.93, 0.05 ) -- ( { 0.93 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ) -- ( { 0.07 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ) -- cycle; \draw [ karolWMtmp!62!black, line~width=.6pt ] ( 0.07, 0.05 ) -- ( 0.93, 0.05 ) -- ( { 0.93 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ) -- ( { 0.07 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ) -- cycle ( 0.07, 0.05 ) -- ( { 0.93 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ) ( 0.93, 0.05 ) -- ( { 0.07 + 0.86 * \fp_use:N \c__karol_wdx_fp }, { 0.05 + 0.86 * \fp_use:N \c__karol_wdy_fp } ); \end{scope} } % Avatar (Vektorfassung des Karol-Online-Avatars), Blick per wkdir \cs_new_protected:Npn \__karol_wavatar_iiid:nn #1#2 { \begin{scope} [ shift={ ( { \__karol_wpx:nn { #1 - 0.5 } { \__karol_wtiefe:n {#2} } + 0.5 * \fp_use:N \c__karol_wdx_fp }, { \__karol_wpy:nnn { 0 } { \__karol_wtiefe:n {#2} } { \fp_use:N \l__karol_wz_fp } + 0.5 * \fp_use:N \c__karol_wdy_fp } ) }, scale=1.45, xscale={ \str_if_eq:VnTF \l__karol_wkdir_tl { w } { -1 } { 1 } } ] \__karol_wavatar_zeichnen:V \l__karol_wkdir_tl \end{scope} } % Zeichnet die Figur. #1 = Blickrichtung (o/w/n/s). % Beide Figuren teilen sich denselben Körper; „emma" bekommt zusätzlich % langes Haar, eine Blume in der Hand und ein helleres Hemd. \cs_new_protected:Npn \__karol_wavatar_zeichnen:n #1 { \bool_set_false:N \l__karol_wemma_bool \str_if_eq:VnT \l__karol_wfigur_tl { emma } { \bool_set_true:N \l__karol_wemma_bool } \bool_if:NTF \l__karol_wemma_bool { \colorlet { karolAvatarHemd } { karolEmmaShirt } } { \colorlet { karolAvatarHemd } { karolAvatarShirt } } % Beine \fill [ karolAvatarHose, rounded~corners=.6pt ] (-0.17, 0) rectangle (-0.03, 0.34) ( 0.03, 0) rectangle ( 0.17, 0.34); \fill [ black!75 ] (-0.19, 0) rectangle (-0.01, 0.055) ( 0.01, 0) rectangle ( 0.19, 0.055); % Rumpf \fill [ karolAvatarHemd, rounded~corners=1pt ] (-0.21, 0.32) rectangle (0.21, 0.72); % Arme \fill [ karolAvatarHemd, rounded~corners=.8pt ] (-0.29, 0.36) rectangle (-0.19, 0.70) ( 0.19, 0.36) rectangle ( 0.29, 0.70); \fill [ karolAvatarHaut ] (-0.285, 0.335) rectangle (-0.195, 0.42) ( 0.195, 0.335) rectangle ( 0.285, 0.42); % Kopf \str_if_eq:nnTF {#1} { n } { % Rückenansicht: Haar statt Gesicht \fill [ karolAvatarHaar, rounded~corners=1.2pt ] (-0.185, 0.70) rectangle (0.185, 0.97); } { \fill [ karolAvatarHaut, rounded~corners=1.2pt ] (-0.185, 0.70) rectangle (0.185, 0.97); \str_case:nn {#1} { { s } { % zugewandt: zwei Augen, Mund \fill [ black!80 ] (-0.075, 0.865) circle (0.022) ( 0.075, 0.865) circle (0.022); \draw [ black!60, line~width=.5pt ] (-0.05, 0.775) -- (0.05, 0.775); } { o } { % Profil (Osten; Westen via xscale=-1) \fill [ black!80 ] (0.10, 0.865) circle (0.022); \draw [ black!60, line~width=.5pt ] (0.06, 0.775) -- (0.15, 0.775); \__karol_wavatar_haar_kurz: } { w } { \fill [ black!80 ] (0.10, 0.865) circle (0.022); \draw [ black!60, line~width=.5pt ] (0.06, 0.775) -- (0.15, 0.775); \__karol_wavatar_haar_kurz: } } } % Emma: langes Haar. Nach dem Kopf gezeichnet, damit es das Gesicht % rahmt statt dahinter zu verschwinden — aber schmal genug, um Augen % und Mund frei zu lassen. % % Alle Strähnen bleiben innerhalb der Mütze (x von -0.20 bis 0.20) % und enden oben unter deren Unterkante: die Mütze wird danach % gezeichnet und deckt sie ab. Ragt das Haar seitlich darüber % hinaus, sieht es aus, als läge es auf der Mütze. \bool_if:NT \l__karol_wemma_bool { \str_case:nn {#1} { { n } { % abgewandt: das Haar bedeckt Kopf und Rücken \fill [ karolEmmaHaar, rounded~corners=1.6pt ] (-0.20, 0.38) rectangle (0.20, 1.00); } { s } { % zugewandt: lange Strähne links, kurze rechts \fill [ karolEmmaHaar, rounded~corners=1.2pt ] (-0.195, 0.44) rectangle (-0.125, 0.98); \fill [ karolEmmaHaar, rounded~corners=1pt ] ( 0.135, 0.73) rectangle ( 0.195, 0.98); } { o } { % Profil: das Haar fällt hinten (links) herab \fill [ karolEmmaHaar, rounded~corners=1.2pt ] (-0.195, 0.40) rectangle (-0.105, 0.99); } { w } { \fill [ karolEmmaHaar, rounded~corners=1.2pt ] (-0.195, 0.40) rectangle (-0.105, 0.99); } } } % Kappe: Deckel + Schirm in Blickrichtung \fill [ karolAvatarKappe, rounded~corners=1.4pt ] (-0.20, 0.93) rectangle (0.20, 1.08); \str_case:nn {#1} { { o } { \fill [ karolAvatarKappe, rounded~corners=.5pt ] (0.17, 0.93) rectangle (0.40, 0.985); } { w } { \fill [ karolAvatarKappe, rounded~corners=.5pt ] (0.17, 0.93) rectangle (0.40, 0.985); } { s } { \fill [ karolAvatarKappe!85!black, rounded~corners=.5pt ] (-0.21, 0.905) rectangle (0.21, 0.955); } { n } { } } % Emma: Blume in der Hand — zuletzt, damit sie vor allem anderen liegt \bool_if:NT \l__karol_wemma_bool { \fill [ karolEmmaStiel, rounded~corners=.3pt ] (0.222, 0.38) rectangle (0.258, 0.62); \fill [ karolEmmaBluete ] (0.24, 0.655) circle (0.072); \fill [ karolEmmaBluete!62!black ] (0.24, 0.655) circle (0.026); } } % Kurzes Haar am Hinterkopf (nur die Figur „karol"; bei „emma" verdeckt % es das lange Haar ohnehin) \cs_new_protected:Npn \__karol_wavatar_haar_kurz: { \bool_if:NF \l__karol_wemma_bool { \fill [ karolAvatarHaar ] (-0.185, 0.70) rectangle (-0.10, 0.95); } } \cs_generate_variant:Nn \__karol_wavatar_zeichnen:n { V } % --- Umgebung -------------------------------------------------------------- \NewDocumentEnvironment { karolwelt } { O{} } { \__karol_wobjekte: \keys_set:nV { karol / weltumg } \c__karol_wvorgabe_tl \keys_set:nn { karol / weltumg } { #1 } \prop_clear:N \l__karol_wziegel_prop \prop_clear:N \l__karol_wmarke_prop \prop_clear:N \l__karol_wquader_prop \bool_set_false:N \l__karol_wkarol_bool \ignorespaces } { \par \mode_leave_vertical: \begin{tabular} [b] {@{}c@{}} \tl_if_empty:NF \l__karol_wtitel_tl { { \footnotesize \l__karol_wtitel_tl } \\ [2pt] } \exp_args:NV \scalebox \l__karol_wskal_tl { \str_if_eq:VnTF \l__karol_wansicht_tl { 2d } { \__karol_wrender_iid: } { \__karol_wrender_iiid: } } \tl_if_empty:NF \l__karol_whinweis_tl { \\ [3pt] { \footnotesize \itshape \l__karol_whinweis_tl } } \end{tabular} } % =========================================================================== % 4b. Zeilen-Welten: \karolzeile % =========================================================================== % \karolzeile{> Z 2 {2M} M Q} zeichnet eine EINZELNE Feldreihe aus einer % Zeichenkette. Sie ist der schnelle Weg für Arbeitsblätter, bei denen % die Tiefe der Welt keine Rolle spielt. % % Gezeichnet wird mit denselben Bausteinen wie die karolwelt-Umgebung: % gleiche Projektion, gleiches Bodengitter, gleiche Ziegel, Marken, % Quader und derselbe Avatar. Eine Zeilen-Welt ist damit optisch nichts % anderes als eine karolwelt mit laenge=1 — nur die Eingabe ist kürzer. % % Zeichen: . leeres Feld > Karol nach Osten % Z ein Ziegel < Karol nach Westen % 1…9 Ziegelstapel ^ Karol nach Norden (abgewandt) % M Marke v Karol nach Süden (zugewandt) % Q Quader / Wand % Mehrere Zeichen auf einem Feld werden geklammert: {2M} = zwei Ziegel % mit einer Marke obenauf. Die Zeichen eines Feldes werden von unten % nach oben abgearbeitet. % --------------------------------------------------------------------------- \int_new:N \l__karol_i_int % aktueller Feldindex (0-basiert) \int_new:N \l__karol_n_int % Feldanzahl \tl_new:N \l__karol_skalierung_tl \tl_new:N \l__karol_hinweis_tl \tl_new:N \l__karol_titel_tl \bool_new:N \l__karol_nummern_bool \bool_new:N \l__karol_rahmen_bool \int_new:N \l__karol_zhoehe_int % Raumhöhe des optionalen Rahmens \clist_new:N \l__karol_hervor_clist \keys_define:nn { karol / welt } { skalierung .tl_set:N = \l__karol_skalierung_tl, skalierung .initial:n = { 1 }, nummern .bool_set:N = \l__karol_nummern_bool, nummern .default:n = true, nummern .initial:n = true, hinweis .tl_set:N = \l__karol_hinweis_tl, hinweis .initial:n = { }, titel .tl_set:N = \l__karol_titel_tl, titel .initial:n = { }, hervorheben .clist_set:N = \l__karol_hervor_clist, hervorheben .initial:n = { }, % Raumrahmen wie in der karolwelt-Umgebung. Für eine einzelne Reihe % ist er meist zu wuchtig (und in Legenden störend), deshalb aus. rahmen .bool_set:N = \l__karol_rahmen_bool, rahmen .default:n = true, rahmen .initial:n = false, hoehe .int_set:N = \l__karol_zhoehe_int, hoehe .initial:n = { 3 }, } % --- Feldnummern ------------------------------------------------------------ % Die Nummern stehen VOR der vordersten Gitterkante, nicht auf dem Feld: % so verdeckt sie kein Ziegelstapel und die Zuordnung Zahl <-> Feld bleibt % auch in vollen Welten eindeutig. % % Abstand der Ziffernoberkante von der Gitterkante, in Feldbreiten. Er % muss den Hervorhebungsrahmen mit einschließen: der liegt 0.06 unterhalb % der Zelle (\__karol_zhervor:n) und ist 1.2pt stark, reicht also gut % 0.066 Feldbreiten unter die Kante. Sonst berührt die Ziffer ihn. \fp_const:Nn \c__karol_znummer_fp { 0.15 } \tl_new:N \l__karol_znfarbe_tl \cs_new_protected:Npn \__karol_znummer:n #1 % #1 = Feldindex { % Hervorgehobene Felder bekommen auch eine rote Nummer — der rote % Bodenrahmen kann von hohen Stapeln verdeckt sein. \clist_if_in:NnTF \l__karol_hervor_clist {#1} { \tl_set:Nn \l__karol_znfarbe_tl { karolWHervor } } { \tl_set:Nn \l__karol_znfarbe_tl { karolWGitter } } \node [ font=\__karol_textfont:\bfseries\tiny, inner~sep=0pt, anchor=north, text=\l__karol_znfarbe_tl ] at ( { (#1) + 0.5 }, { -\fp_use:N \c__karol_znummer_fp } ) { \int_eval:n {#1} }; } % --- Hervorhebung einzelner Felder ----------------------------------------- % Rotes Feldparallelogramm auf dem Boden (wird vor den Objekten gezeichnet, % die Objekte stehen also darauf). \cs_new_protected:Npn \__karol_zhervor:n #1 { % Der Rahmen liegt 0.06 Feldbreiten außerhalb der Zelle: so bleibt er % auch dann sichtbar, wenn ein Ziegelstapel oder Quader auf dem Feld % steht (die Objekte füllen die Zelle randlos aus). \draw [ karolWHervor, line~width=1.2pt, line~join=round ] ( \__karol_wpx:nn { #1 - 0.06 } { -0.06 } , \__karol_wpy:nnn { 0 } { -0.06 } { 0 } ) -- ( \__karol_wpx:nn { #1 + 1.06 } { -0.06 } , \__karol_wpy:nnn { 0 } { -0.06 } { 0 } ) -- ( \__karol_wpx:nn { #1 + 1.06 } { 1.06 } , \__karol_wpy:nnn { 0 } { 1.06 } { 0 } ) -- ( \__karol_wpx:nn { #1 - 0.06 } { 1.06 } , \__karol_wpy:nnn { 0 } { 1.06 } { 0 } ) -- cycle; } % --- Zellinhalt: ein Element ------------------------------------------------ % Alle Elemente sitzen auf dem Höhencursor \l__karol_wz_fp (in Ziegellagen) % und schieben ihn — genau wie bisher — um ihre eigene Bauhöhe weiter. \cs_new_protected:Npn \__karol_zziegel: { \__karol_wziegel_iiid:nnn { \l__karol_i_int + 1 } { 1 } { \fp_use:N \l__karol_wz_fp } \fp_add:Nn \l__karol_wz_fp { 1 } } \cs_new_protected:Npn \__karol_zstapel:n #1 { \prg_replicate:nn {#1} { \__karol_zziegel: } } \cs_new_protected:Npn \__karol_zquader: { \__karol_wquader_iiid:nnn { \l__karol_i_int + 1 } { 1 } { \fp_use:N \l__karol_wz_fp } \fp_add:Nn \l__karol_wz_fp { \c__karol_wqh_fp / \c__karol_wzh_fp } } % Die Marke ist — wie in der karolwelt-Umgebung — eine flache Platte ohne % Bauhöhe; sie schiebt den Cursor daher nicht weiter. Karol steht auf ihr. \cs_new_protected:Npn \__karol_zmarke: { \__karol_wmarke_iiid:nnn { \l__karol_i_int + 1 } { 1 } { karolWMarke } } \cs_new_protected:Npn \__karol_zfigur:n #1 { \tl_set:Nn \l__karol_wkdir_tl {#1} \__karol_wavatar_iiid:nn { \l__karol_i_int + 1 } { 1 } } \cs_new_protected:Npn \__karol_element:n #1 { \str_case:nnF {#1} { { . } { } { > } { \__karol_zfigur:n { o } } { < } { \__karol_zfigur:n { w } } { ^ } { \__karol_zfigur:n { n } } { v } { \__karol_zfigur:n { s } } { Z } { \__karol_zziegel: } { 1 } { \__karol_zstapel:n { 1 } } { 2 } { \__karol_zstapel:n { 2 } } { 3 } { \__karol_zstapel:n { 3 } } { 4 } { \__karol_zstapel:n { 4 } } { 5 } { \__karol_zstapel:n { 5 } } { 6 } { \__karol_zstapel:n { 6 } } { 7 } { \__karol_zstapel:n { 7 } } { 8 } { \__karol_zstapel:n { 8 } } { 9 } { \__karol_zstapel:n { 9 } } { M } { \__karol_zmarke: } { Q } { \__karol_zquader: } } { \msg_warning:nnn { karol } { unbekanntes-zeichen } {#1} } } \msg_new:nnn { karol } { unbekanntes-zeichen } { Unbekanntes~Weltzeichen~"#1"~-~wird~ignoriert. } % --- Zelle: Inhaltsstapel über dem Bodenfeld ------------------------------- \cs_new_protected:Npn \__karol_zelle:n #1 { \fp_zero:N \l__karol_wz_fp \tl_map_function:nN {#1} \__karol_element:n \int_incr:N \l__karol_i_int } % --------------------------------------------------------------------------- % \karolzeile[keys]{Zeichenkette} % --------------------------------------------------------------------------- \NewDocumentCommand { \karolzeile } { O{} m } { \group_begin: \keys_set:nn { karol / welt } { #1 } \int_set:Nn \l__karol_n_int { \tl_count:n { #2 } } \int_zero:N \l__karol_i_int % Eine Zeilen-Welt ist eine karolwelt mit genau einer Reihe: % damit rechnen die gemeinsamen Bausteine korrekt. \int_set_eq:NN \l__karol_wbreite_int \l__karol_n_int \int_set:Nn \l__karol_wlaenge_int { 1 } \int_set_eq:NN \l__karol_whoehe_int \l__karol_zhoehe_int \mode_leave_vertical: \begin{tabular} [b] {@{}c@{}} \tl_if_empty:NF \l__karol_titel_tl { { \footnotesize \l__karol_titel_tl } \\[2pt] } \scalebox { \l__karol_skalierung_tl } { \begin{tikzpicture} [ x = \g__karol_feld_dim, y = \g__karol_feld_dim, baseline = 0pt ] % unsichtbare Mindesthöhe, damit flache Welten im Paar % nicht „springen": \path (0, 0) -- ( 0, { \fp_eval:n { \c__karol_wdy_fp + 1.5 * \c__karol_wzh_fp } } ); \__karol_wboden_gitter: \bool_if:NT \l__karol_rahmen_bool { \__karol_wrahmen: } \clist_map_function:NN \l__karol_hervor_clist \__karol_zhervor:n \tl_map_function:nN { #2 } \__karol_zelle:n \bool_if:NT \l__karol_nummern_bool { \int_step_inline:nnn { 0 } { \l__karol_n_int - 1 } { \__karol_znummer:n { ##1 } } } \end{tikzpicture} } \tl_if_empty:NF \l__karol_hinweis_tl { \\[3pt] { \footnotesize \itshape \l__karol_hinweis_tl } } \end{tabular} \group_end: } % --------------------------------------------------------------------------- % \karolgitter[keys]{Anzahl Felder} — leere Felder zum Eintragen % --------------------------------------------------------------------------- \tl_new:N \l__karol_opt_tl \NewDocumentCommand { \karolgitter } { O{} m } { \group_begin: \tl_set:Nn \l__karol_opt_tl { #1 } \tl_set:Nx \l__karol_tmp_tl { \prg_replicate:nn { #2 } { . } } \use:x { \exp_not:N \karolzeile [ \exp_not:V \l__karol_opt_tl ] { \exp_not:V \l__karol_tmp_tl } } \group_end: } \ExplSyntaxOff % --------------------------------------------------------------------------- % \karolpaar[gemeinsame keys][keys nur für „nachher"]{vorher}{nachher} % --------------------------------------------------------------------------- \providecommand{\faArrowRight}{\ensuremath{\rightarrow}} \NewDocumentCommand{\karolpaar}{ O{} O{} m m }{% \begin{center} \begin{tabular}[b]{@{}c@{\hspace{1.1em}}c@{\hspace{1.1em}}c@{}} \karolzeile[#1, titel={\textbf{vorher}}]{#3} & \raisebox{1.4em}{\large\faArrowRight} & \karolzeile[#1, titel={\textbf{nachher}}, #2]{#4} \\ \end{tabular} \end{center}% } % --------------------------------------------------------------------------- % Legende der Weltsymbole % --------------------------------------------------------------------------- % Ein Legendeneintrag: \karollegendenitem{Weltstring}{Beschriftung} % Die Einträge sind bewusst klein skaliert: im Schrägbild der karolwelt % braucht ein Feld mehr Fläche als in der früheren flachen Darstellung. \newcommand{\karollegendenitem}[2]{% \mbox{\karolzeile[nummern=false, skalierung=0.42]{#1}\,#2}% } % Trenner mit viel Dehnung und leichtem Umbruchanreiz: die Legende ist % breit und soll notfalls über mehrere Zeilen laufen statt überzustehen. \newcommand{\karollegendentrenner}{% \penalty0\hskip 0.9em plus 2.5em minus .25em\relax} \newcommand{\karollegende}{% \karollegendenitem{>}{Karol (Blickrichtung)}\karollegendentrenner \karollegendenitem{Z}{Ziegel}\karollegendentrenner \karollegendenitem{3}{Ziegelstapel}\karollegendentrenner \karollegendenitem{M}{Marke}\karollegendentrenner \karollegendenitem{{M>}}{Karol auf Marke}\karollegendentrenner \karollegendenitem{Q}{Quader\,/\,Wand}% } % =========================================================================== % 5. Karol-Blöcke (Blockprogrammierung wie in Robot Karol Online) % =========================================================================== % Verwendung: % \begin{karolbloecke}[skalierung=1] % \bwiederholemal{5}{% % \bbefehl{Schritt} % \bbefehl{MarkeSetzen}} % \end{karolbloecke} % % \bwiederholesolange{NichtIstWand}{…} % \bwenndann{IstZiegel}{…} % \bwenndannsonst{IstZiegel}{…}{…} % \bwiederholeimmer{…} % \bluecke[7em] — leerer „Geisterblock" zum Ausfüllen % \bbedingung{IstZiegel} — Sechseck, auch im Fließtext nutzbar % --------------------------------------------------------------------------- \ExplSyntaxOn % --- Maße (bei jedem Rendern aus dem aktuellen em berechnet) --------------- \dim_new:N \l__karol_bnd_dim % Nasen-/Kerbentiefe \dim_new:N \l__karol_bnx_dim % Kerbe: Beginn \dim_new:N \l__karol_bns_dim % Kerbe: Schräge \dim_new:N \l__karol_bnf_dim % Kerbe: gerades Stück \dim_new:N \l__karol_bpx_dim % Innenabstand links/rechts \dim_new:N \l__karol_bpt_dim % Innenabstand oben (unter der Kerbe) \dim_new:N \l__karol_bpb_dim % Innenabstand unten \dim_new:N \l__karol_bmw_dim % Mindestbreite \dim_new:N \l__karol_barm_dim % Armbreite der C-Blöcke \dim_new:N \l__karol_bfut_dim % Fußhöhe der C-Blöcke \dim_new:N \l__karol_bmm_dim % Mindesthöhe eines Mauls \dim_new:N \l__karol_bbase_dim % Absenkung für die Grundlinienausrichtung \tl_new:N \l__karol_brc_tl % Eckenradius (für TikZ-Schlüssel) \dim_new:N \l__karol_brc_dim % derselbe Wert zum Rechnen \dim_new:N \l__karol_bkw_dim % Strichstärke der Blockumrandung % Inline-Modus: Blöcke im Fließtext. Sie werden in der Schriftgröße des % umgebenden Textes gesetzt, auf dessen Grundlinie ausgerichtet und mit % kleineren „Verzierungen" (Kerbe, Nase, Innenabstände) versehen, damit % ihre Höhe der Zeilenhöhe nahekommt. Mehrzeilige Blöcke (C-/E-Blöcke) % erscheinen inline nur mit ihrem Kopf. \bool_new:N \l__karol_binline_bool % aktuelle Ausgabe ist ein Inline-Block \bool_new:N \l__karol_bimbau_bool % wir stecken bereits in einem Block \cs_new_protected:Npn \__karol_bdims: { \bool_if:NTF \l__karol_binline_bool { % --- Inline: flachere Kerbe, knappe Innenabstände, keine Mindestbreite \dim_set:Nn \l__karol_bnd_dim { 0.12em } \dim_set:Nn \l__karol_bnx_dim { 0.50em } \dim_set:Nn \l__karol_bns_dim { 0.14em } \dim_set:Nn \l__karol_bnf_dim { 0.45em } \dim_set:Nn \l__karol_bpx_dim { 0.40em } \dim_set:Nn \l__karol_bpt_dim { 0.08em } \dim_set:Nn \l__karol_bpb_dim { 0.10em } \dim_zero:N \l__karol_bmw_dim \dim_set:Nn \l__karol_barm_dim { 0.70em } \dim_set:Nn \l__karol_bfut_dim { 0.40em } \dim_set:Nn \l__karol_bmm_dim { 0.90em } \dim_set:Nn \l__karol_brc_dim { 1.6pt } } { \dim_set:Nn \l__karol_bnd_dim { 0.28em } \dim_set:Nn \l__karol_bnx_dim { 0.75em } \dim_set:Nn \l__karol_bns_dim { 0.26em } \dim_set:Nn \l__karol_bnf_dim { 0.80em } \dim_set:Nn \l__karol_bpx_dim { 0.60em } \dim_set:Nn \l__karol_bpt_dim { 0.26em } \dim_set:Nn \l__karol_bpb_dim { 0.30em } \dim_set:Nn \l__karol_bmw_dim { 5.4em } \dim_set:Nn \l__karol_barm_dim { 1.05em } \dim_set:Nn \l__karol_bfut_dim { 0.62em } \dim_set:Nn \l__karol_bmm_dim { 1.30em } \dim_set:Nn \l__karol_brc_dim { 2pt } } \tl_set:Nx \l__karol_brc_tl { \dim_use:N \l__karol_brc_dim } % Umrandung: in der Oberfläche 3 px bei 110 px Blockhöhe, also gut % 2.7 % — bei den Maßen dieses Pakets rund ein halbes Punkt. In em, % damit sie inline mitschrumpft. \dim_set:Nn \l__karol_bkw_dim { 0.045em } % Alle Blöcke werden jetzt umrandet, also brauchen ALLE die scharfen % Ecken an Kerbe und Nase: dort sind die Segmente kürzer als zwei % Eckradien, und pgf macht daraus sonst Spitzen (siehe \bluecke). \tl_set:Nn \l__karol_bkerbe_tl { [ sharp~corners ] } \tl_set:Nx \l__karol_bkante_tl { [ rounded~corners = \l__karol_brc_tl ] } } % Blockschrift: im Stapel \footnotesize, inline die Größe des Fließtextes % Beschriftungen sind in Robot Karol Online normal gesetzt, nicht fett. \cs_new_protected:Npn \__karol_blockfont: { \__karol_textfont: \bool_if:NF \l__karol_binline_bool { \footnotesize } } % Höhenphantom der Beschriftung. Im Stapel großzügig (Umlaut + Klammer), % damit alle Blöcke exakt gleich hoch werden; inline knapp (Versalhöhe + % Unterlänge), damit der Block die Zeilenhöhe nicht unnötig aufbläht. \cs_new_protected:Npn \__karol_bphantom: { \bool_if:NTF \l__karol_binline_bool { \vphantom { Ag } } { \vphantom { Äg( } } } % Flachere Fassung für Bedingungsplättchen und Pillen: sie sitzen INNERHALB % eines Blocks und dürfen ihn nicht unnötig aufblähen (keine Klammertiefe). \cs_new_protected:Npn \__karol_bphantom_flach: { \bool_if:NTF \l__karol_binline_bool { \vphantom { Ag } } { \vphantom { Äg } } } % Beginn eines Nutzerblocks: Inline-Modus bestimmen und merken, dass wir % ab jetzt im Blockbau sind (verschachtelte \bbedingung/Pillen erben ihn). \cs_new_protected:Npn \__karol_bstart: { \bool_if:NF \l__karol_bimbau_bool { \mode_if_horizontal:TF { \bool_set_true:N \l__karol_binline_bool } { \bool_set_false:N \l__karol_binline_bool } } \bool_set_true:N \l__karol_bimbau_bool } % Wie \__karol_bstart:, aber erzwingt den Inline-Modus (\i…-Kommandos) \cs_new_protected:Npn \__karol_bstart_inline: { \bool_if:NF \l__karol_bimbau_bool { \bool_set_true:N \l__karol_binline_bool } \bool_set_true:N \l__karol_bimbau_bool \mode_leave_vertical: } % Block ausgeben: im Stapel als bündige Zeile; inline so abgesenkt, dass % die Textgrundlinie des Blocks auf der Grundlinie des Fließtextes liegt. % (Die Bild-Grundlinie liegt an der Blockunterkante, ht = #2, dp = 0; die % Beschriftung sitzt um Textunterlänge + Fußabstand darüber.) % #1 = Inhalt, #2 = Gesamthöhen-Variable \cs_new_protected:Npn \__karol_bemit:nN #1#2 { \bool_if:NTF \l__karol_binline_bool { \mode_leave_vertical: \raisebox { -\l__karol_bbase_dim } { \hbox:n { #1 } } } { \hbox:n { #1 } } } % Arbeitsregister \box_new:N \l__karol_btxt_box \box_new:N \l__karol_bbody_box \box_new:N \l__karol_belse_box \box_new:N \l__karol_bsonst_box \dim_new:N \l__karol_bw_dim % Blockbreite \dim_new:N \l__karol_bh_dim % Kopfhöhe \dim_new:N \l__karol_bm_dim % Maulhöhe \dim_new:N \l__karol_bmzwei_dim % Maulhöhe (sonst-Zweig) \dim_new:N \l__karol_bs_dim % Höhe des sonst-Balkens \dim_new:N \l__karol_bt_dim % Gesamthöhe \dim_new:N \l__karol_bbw_dim % Breite der Gesamt-Boundingbox % --- Stapel-Umgebung ------------------------------------------------------- % Innerhalb des Stapels: Kästen bündig ohne Zwischenraum übereinander. \cs_new_protected:Npn \__karol_stapelmodus: { % Im Stapel wird nie inline gesetzt — auch nicht, wenn der umgebende % Block inline war. Damit erben Rümpfe stets die Stapel-Optik. \bool_set_false:N \l__karol_binline_bool \bool_set_false:N \l__karol_bimbau_bool \skip_zero:N \parskip \dim_zero:N \parindent \skip_set:Nn \baselineskip { 0pt } \dim_zero:N \lineskip \dim_set:Nn \lineskiplimit { \maxdimen } } \tl_new:N \l__karol_bskal_tl \keys_define:nn { karol / bloecke } { skalierung .tl_set:N = \l__karol_bskal_tl, skalierung .initial:n = { 1 }, } \box_new:N \l__karol_bstapel_box \NewDocumentEnvironment { karolbloecke } { O{} } { \keys_set:nn { karol / bloecke } { #1 } \vbox_set:Nw \l__karol_bstapel_box \__karol_stapelmodus: } { \vbox_set_end: \mode_leave_vertical: \hbox:n { \exp_args:NV \scalebox \l__karol_bskal_tl { \box_use:N \l__karol_bstapel_box } } } % --- Kerbenpfade ----------------------------------------------------------- % Eckenmodus an Kerbe und Nase. Gefüllte Blöcke zeichnen die Kontur nicht % (draw=none), dort bleiben beide Marken leer und der Pfad ist unverändert. % Der Geisterblock \bluecke STRICHELT die Kontur — und dort sind die % Kerbensegmente kürzer als zwei Eckradien: inline ist die Schräge rund % 2.0pt lang, zwei Radien à 1.6pt brauchen 3.2pt. pgf erzeugt daraus einen % entarteten Bogen, der als Spitze aus der Kontur schießt. Deshalb setzt % \bluecke an Kerbe und Nase auf scharfe Ecken und rundet nur die vier % Außenecken. \tl_new:N \l__karol_bkerbe_tl % vor der Kerbe/Nase einzufügen \tl_new:N \l__karol_bkante_tl % danach wieder einzufügen \dim_new:N \l__karol_bstr_dim % Geisterblock: Strichlänge \dim_new:N \l__karol_bluk_dim % Geisterblock: Lückenlänge % Obere Kante mit Kerbe, von (0,0) bis (W,0). Der Rumpf ohne den % Startpunkt wird auch von der hellen Kante gebraucht, die schon an der % linken Seite beginnt. \cs_new:Npn \__karol_pfad_oben: { (0pt, 0pt) \__karol_pfad_oben_ab: } \cs_new:Npn \__karol_pfad_oben_ab: { \l__karol_bkerbe_tl -- ( \dim_use:N \l__karol_bnx_dim , 0pt ) -- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim }, -\dim_use:N \l__karol_bnd_dim ) -- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim }, -\dim_use:N \l__karol_bnd_dim ) -- ( \dim_eval:n { \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim }, 0pt ) \l__karol_bkante_tl -- ( \dim_use:N \l__karol_bw_dim , 0pt ) } % Untere Kante mit Nase, von (W,-#1) zurück bis (0,-#1): \cs_new:Npn \__karol_pfad_unten:n #1 { -- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } ) \l__karol_bkerbe_tl -- ( \dim_eval:n { \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) } ) -- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_eval:n { \l__karol_bnx_dim + \l__karol_bns_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_use:N \l__karol_bnx_dim , \dim_eval:n { 0pt-(#1) } ) \l__karol_bkante_tl -- ( 0pt , \dim_eval:n { 0pt-(#1) } ) -- cycle } % Maulöffnung an der Kopf-/Balkenunterkante bei y = -#1 % (von rechts nach links; Nase zeigt ins Maul): \cs_new:Npn \__karol_pfad_maul_auf:n #1 { -- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } ) \l__karol_bkerbe_tl -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + \l__karol_bns_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim }, \dim_eval:n { 0pt-(#1) } ) \l__karol_bkante_tl -- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { 0pt-(#1) } ) } % Maulschluss an der Fuß-/Balkenoberkante bei y = -#1 % (von links nach rechts; Kerbe nimmt die Nase des letzten Blocks auf): \cs_new:Npn \__karol_pfad_maul_zu:n #1 { -- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { 0pt-(#1) } ) \l__karol_bkerbe_tl -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim }, \dim_eval:n { 0pt-(#1) } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + \l__karol_bns_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + \l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim } ) -- ( \dim_eval:n { \l__karol_barm_dim + \l__karol_bnx_dim + 2\l__karol_bns_dim + \l__karol_bnf_dim }, \dim_eval:n { 0pt-(#1) } ) \l__karol_bkante_tl -- ( \dim_use:N \l__karol_bw_dim , \dim_eval:n { 0pt-(#1) } ) } % --- Kopftext ausmessen, Breite/Kopfhöhe setzen ---------------------------- \cs_new_protected:Npn \__karol_bmess:n #1 { \__karol_bdims: \hbox_set:Nn \l__karol_btxt_box { \__karol_blockfont: \color{white} \__karol_bphantom: #1 } \dim_set:Nn \l__karol_bw_dim { \box_wd:N \l__karol_btxt_box + 2 \l__karol_bpx_dim } \dim_compare:nT { \l__karol_bw_dim < \l__karol_bmw_dim } { \dim_set_eq:NN \l__karol_bw_dim \l__karol_bmw_dim } \dim_set:Nn \l__karol_bh_dim { \l__karol_bnd_dim + \l__karol_bpt_dim + \box_ht:N \l__karol_btxt_box + \box_dp:N \l__karol_btxt_box + \l__karol_bpb_dim } \__karol_bbase_setzen: } % Abstand von der Unterkante der Boundingbox (= Bild-Grundlinie) hinauf zur % Textgrundlinie im Block. \raisebox{-\l__karol_bbase_dim} legt beide % Grundlinien im Fließtext exakt übereinander. Inline zählt die Nase zur % Boundingbox (siehe \__karol_bbbox:N) und damit auch hier mit. \cs_new_protected:Npn \__karol_bbase_setzen: { \dim_set:Nn \l__karol_bbase_dim { \box_dp:N \l__karol_btxt_box + \l__karol_bpb_dim } \bool_if:NT \l__karol_binline_bool { \dim_add:Nn \l__karol_bbase_dim { \l__karol_bnd_dim } } } % Untere Kante der Boundingbox eines Blocks der Höhe #1. Im Stapel endet % sie an der Blockunterkante (die Nase ragt heraus und greift in den % Folgeblock); inline zählt die Nase mit, damit TeX den Zeilenabstand % richtig bemisst und nichts in die nächste Zeile ragt. \cs_new:Npn \__karol_bbbox:N #1 { \bool_if:NTF \l__karol_binline_bool { \dim_eval:n { 0pt - #1 - \l__karol_bnd_dim } } { \dim_eval:n { 0pt - #1 } } } % Kopftext in den Block setzen (Grundlinie unterhalb der Kerbe) % Achtung: die Box MUSS vor dem tikzpicture gefüllt werden (nullfont!) \cs_new_protected:Npn \__karol_btxt_setzen:n #1 { \__karol_btext_at:nN {#1} \l__karol_btxt_box } \cs_new_protected:Npn \__karol_btext_at:nN #1#2 % #1 = y-Offset, #2 = Box { \node [ anchor=base~west, inner~sep=0pt ] at ( \dim_use:N \l__karol_bpx_dim , \dim_eval:n { 0pt-(#1) - \l__karol_bnd_dim - \l__karol_bpt_dim - \box_ht:N #2 } ) { \box_use:N #2 }; } % --- Blockfläche mit Anschliff --------------------------------------------- % Robot Karol Online setzt einen gewöhnlichen 3D-Anschliff ein: jede nach % oben oder links zeigende Kante ist aufgehellt, jede nach unten oder % rechts zeigende abgedunkelt. Wichtig daran ist vor allem, dass % gleichfarbige Blöcke voneinander abgesetzt sind — ein grüner wenn-Block % in einer grünen Schleife wäre sonst nicht zu erkennen. % % Statt die Kontur in helle und dunkle Teilstücke zu zerlegen (fehler- % anfällig an Ecken, Kerbe, Nase und Maul), wird der Effekt so erzeugt, % wie er physikalisch entsteht: % % 1. Auf die Fläche BESCHNEIDEN. Dadurch liegt jeder Strich vollständig % INNERHALB des Blocks — Blöcke können sich nicht mehr überlagern und % passen exakt ineinander. % 2. Den kompletten Rand hell zeichnen. % 3. Denselben Rand dunkel darüber, um eine Strichstärke nach links oben % versetzt: an nach unten und rechts zeigenden Kanten rutscht er ins % Innere und deckt das Helle ab, an nach oben und links zeigenden aus % der Fläche heraus, wo der Beschnitt ihn entfernt. Schräge Kanten % bekommen automatisch den passenden Zwischenwert. % % Weil beide Striche denselben Pfad benutzen, folgen sie der Blockform in % jedem Detail — Rundungen, Kerbe, Nase und Maulöffnungen inbegriffen. % Die doppelte Strichstärke ist Absicht: der Beschnitt halbiert sie. % #1 = Blockfarbe, #2 = geschlossener Pfad der Blockform \cs_new_protected:Npn \__karol_brand:nn #1#2 { % Die Fläche einen Hauch überzeichnen (0.1pt nach außen). Zwei exakt % aneinanderstoßende Flächen hinterlassen sonst einen hellen % Antialiasing-Saum — sichtbar dort, wo die Nase eines Blocks in der % Kerbe des nächsten sitzt. Der Anschliff bleibt davon unberührt, er % wird auf den ungewachsenen Pfad beschnitten. \draw [ fill = #1, draw = #1, line~width = 0.2pt, line~join = round, rounded~corners = \l__karol_brc_tl ] #2 ; \begin{scope} \clip [ rounded~corners = \l__karol_brc_tl ] #2 ; \draw [ fill = #1, draw = #1!70!white, line~width = \dim_eval:n { 2 \l__karol_bkw_dim }, line~join = round, rounded~corners = \l__karol_brc_tl ] #2 ; \draw [ draw = #1!80!black, line~width = \dim_eval:n { 2 \l__karol_bkw_dim }, line~join = round, rounded~corners = \l__karol_brc_tl, xshift = -\dim_use:N \l__karol_bkw_dim, yshift = \dim_use:N \l__karol_bkw_dim ] #2 ; \end{scope} } % --- Einfacher Anweisungsblock --------------------------------------------- % #1 = Farbname #2 = Beschriftung \cs_new_protected:Npn \__karol_bblock:nn #1#2 { \tl_set:Nn \l__karol_bfarbe_tl {#1} \__karol_bmess:n {#2} \__karol_bemit:nN { \begin{tikzpicture} \useasboundingbox (0pt, 0pt) rectangle ( \dim_use:N \l__karol_bw_dim , \__karol_bbbox:N \l__karol_bh_dim ); \__karol_brand:nn {#1} { \__karol_pfad_oben: \__karol_pfad_unten:n \l__karol_bh_dim } \__karol_btxt_setzen:n { 0pt } \end{tikzpicture} } \l__karol_bh_dim } % --- C-Block (eine Maulöffnung) -------------------------------------------- % #1 = Farbname #2 = Kopfbeschriftung #3 = Rumpf \cs_new_protected:Npn \__karol_bcblock:nnn #1#2#3 { % Inline gibt es keine Maulöffnung: nur der Kopf wird gesetzt. \bool_if:NTF \l__karol_binline_bool { \__karol_bblock:nn {#1} {#2} } { \__karol_bcblock_voll:nnn {#1} {#2} {#3} } } \cs_new_protected:Npn \__karol_bcblock_voll:nnn #1#2#3 { \tl_set:Nn \l__karol_bfarbe_tl {#1} \vbox_set:Nn \l__karol_bbody_box { \__karol_stapelmodus: #3 } \__karol_bmess:n {#2} \dim_set:Nn \l__karol_bm_dim { \box_ht:N \l__karol_bbody_box + \box_dp:N \l__karol_bbody_box } \dim_compare:nT { \l__karol_bm_dim < \l__karol_bmm_dim } { \dim_set_eq:NN \l__karol_bm_dim \l__karol_bmm_dim } \dim_set:Nn \l__karol_bt_dim { \l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bfut_dim } \dim_set:Nn \l__karol_bbw_dim { \l__karol_barm_dim + \box_wd:N \l__karol_bbody_box + 0.4em } \dim_compare:nT { \l__karol_bbw_dim < \l__karol_bw_dim } { \dim_set_eq:NN \l__karol_bbw_dim \l__karol_bw_dim } \__karol_bemit:nN { \begin{tikzpicture} \useasboundingbox (0pt, 0pt) rectangle ( \dim_use:N \l__karol_bbw_dim , -\dim_use:N \l__karol_bt_dim ); \__karol_brand:nn {#1} { \__karol_pfad_oben: \__karol_pfad_maul_auf:n \l__karol_bh_dim -- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim } ) \__karol_pfad_maul_zu:n { \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } } \__karol_pfad_unten:n \l__karol_bt_dim } \__karol_btxt_setzen:n { 0pt } \node [ anchor=north~west, inner~sep=0pt ] at ( \dim_use:N \l__karol_barm_dim , -\dim_use:N \l__karol_bh_dim ) { \box_use:N \l__karol_bbody_box }; \end{tikzpicture} } \l__karol_bt_dim } % Mittelteil des E-Blocks (beide Mäuler und der sonst-Balken). Fläche und % dunkle Kante brauchen ihn gleichermaßen. \cs_new:Npn \__karol_beblock_rumpf: { \__karol_pfad_maul_auf:n \l__karol_bh_dim -- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim } ) \__karol_pfad_maul_zu:n { \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } } \__karol_pfad_maul_auf:n { \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bs_dim } } -- ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim - \l__karol_bs_dim - \l__karol_bmzwei_dim } ) \__karol_pfad_maul_zu:n { \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bs_dim + \l__karol_bmzwei_dim } } } % --- E-Block (wenn … dann … sonst …) --------------------------------------- % #1 = Farbname #2 = Kopf #3 = dann-Rumpf #4 = sonst-Rumpf \cs_new_protected:Npn \__karol_beblock:nnnn #1#2#3#4 { % Inline gibt es keine Maulöffnungen: nur der Kopf wird gesetzt. \bool_if:NTF \l__karol_binline_bool { \__karol_bblock:nn {#1} {#2} } { \__karol_beblock_voll:nnnn {#1} {#2} {#3} {#4} } } \cs_new_protected:Npn \__karol_beblock_voll:nnnn #1#2#3#4 { \tl_set:Nn \l__karol_bfarbe_tl {#1} \vbox_set:Nn \l__karol_bbody_box { \__karol_stapelmodus: #3 } \vbox_set:Nn \l__karol_belse_box { \__karol_stapelmodus: #4 } \__karol_bmess:n {#2} % Höhe des sonst-Balkens aus seiner Beschriftung \hbox_set:Nn \l__karol_bsonst_box { \__karol_blockfont: \color{white} \__karol_bphantom: sonst } \dim_set:Nn \l__karol_bs_dim { \l__karol_bnd_dim + \l__karol_bpt_dim + \box_ht:N \l__karol_bsonst_box + \box_dp:N \l__karol_bsonst_box + \l__karol_bpb_dim } \dim_set:Nn \l__karol_bm_dim { \box_ht:N \l__karol_bbody_box + \box_dp:N \l__karol_bbody_box } \dim_compare:nT { \l__karol_bm_dim < \l__karol_bmm_dim } { \dim_set_eq:NN \l__karol_bm_dim \l__karol_bmm_dim } \dim_set:Nn \l__karol_bmzwei_dim { \box_ht:N \l__karol_belse_box + \box_dp:N \l__karol_belse_box } \dim_compare:nT { \l__karol_bmzwei_dim < \l__karol_bmm_dim } { \dim_set_eq:NN \l__karol_bmzwei_dim \l__karol_bmm_dim } \dim_set:Nn \l__karol_bt_dim { \l__karol_bh_dim + \l__karol_bm_dim + \l__karol_bs_dim + \l__karol_bmzwei_dim + \l__karol_bfut_dim } \dim_set:Nn \l__karol_bbw_dim { \l__karol_barm_dim + \box_wd:N \l__karol_bbody_box + 0.4em } \dim_compare:nT { \l__karol_bbw_dim < \l__karol_bw_dim } { \dim_set_eq:NN \l__karol_bbw_dim \l__karol_bw_dim } \dim_compare:nT { \l__karol_bbw_dim < \l__karol_barm_dim + \box_wd:N \l__karol_belse_box } { \dim_set:Nn \l__karol_bbw_dim { \l__karol_barm_dim + \box_wd:N \l__karol_belse_box + 0.4em } } \__karol_bemit:nN { \begin{tikzpicture} \useasboundingbox (0pt, 0pt) rectangle ( \dim_use:N \l__karol_bbw_dim , -\dim_use:N \l__karol_bt_dim ); \__karol_brand:nn {#1} { \__karol_pfad_oben: \__karol_beblock_rumpf: \__karol_pfad_unten:n \l__karol_bt_dim } \__karol_btxt_setzen:n { 0pt } % sonst-Beschriftung (Box wurde VOR dem Bild gefüllt) \__karol_btext_at:nN { \dim_eval:n { \l__karol_bh_dim + \l__karol_bm_dim } } \l__karol_bsonst_box % Rümpfe \node [ anchor=north~west, inner~sep=0pt ] at ( \dim_use:N \l__karol_barm_dim , -\dim_use:N \l__karol_bh_dim ) { \box_use:N \l__karol_bbody_box }; \node [ anchor=north~west, inner~sep=0pt ] at ( \dim_use:N \l__karol_barm_dim , \dim_eval:n { - \l__karol_bh_dim - \l__karol_bm_dim - \l__karol_bs_dim } ) { \box_use:N \l__karol_belse_box }; \end{tikzpicture} } \l__karol_bt_dim } % --- Eingabefelder: Zahl (Pille) und Bedingung (Sechseck) ------------------ % Aktuelle Blockfarbe (für Pillen-Tönung), von den Buildern gesetzt \tl_new:N \l__karol_bfarbe_tl \tl_set:Nn \l__karol_bfarbe_tl { karolBlockBefehl } % Pille: blasse Tönung der Blockfarbe, dunkle Schrift (wie Karol Online) \dim_new:N \l__karol_bpxs_dim % Pille: Innenabstand waagerecht \dim_new:N \l__karol_bpys_dim % Pille: Innenabstand senkrecht \dim_new:N \l__karol_bpmw_dim % Pille: Mindestbreite \box_new:N \l__karol_bptxt_box % Pillenbeschriftung \box_new:N \l__karol_bpinh_box % Pilleninhalt (Beschriftung + Platz fürs Dreieck) \dim_new:N \l__karol_bph_dim % Pillenhöhe \dim_new:N \l__karol_bptri_dim % Auswahldreieck: Seitenlänge \dim_new:N \l__karol_bpgap_dim % Auswahldreieck: Lücke zum Text \dim_new:N \l__karol_bpby_dim % Auswahldreieck: Spitze über der Grundlinie % Verhältnisse des Auswahldreiecks, gemessen an der Oberfläche von Robot % Karol Online (Pillenhöhe H = 83 px): Seitenlänge 27 px = 0.325 H, % Lücke Text->Dreieck 22 px = 0.265 H, und die Spitze steht praktisch auf % der Textgrundlinie (2 px darüber = 0.024 H). Das Dreieck hat die VOLLE % Blockfarbe, nicht die aufgehellte der Pille. \fp_const:Nn \c__karol_btri_fp { 0.325 } % 0.265 ist im Original der Abstand von der TINTE des letzten Buchstabens; % hier wird ab der Textbox gemessen, deren rechte Kante die Seitenbreite % des Glyphen schon enthält (~0.09 H). Deshalb der kleinere Wert. \fp_const:Nn \c__karol_btrigap_fp { 0.19 } \fp_const:Nn \c__karol_btribas_fp { 0.024 } % #1 = Auswahldreieck ja/nein (Boolean-Ausdruck), #2 = Beschriftung \cs_new_protected:Npn \__karol_bpille_aux:nn #1#2 { \bool_if:NTF \l__karol_binline_bool { \dim_set:Nn \l__karol_bpxs_dim { 0.22em } \dim_set:Nn \l__karol_bpys_dim { 0.08em } \dim_set:Nn \l__karol_bpmw_dim { 0.85em } } { \dim_set:Nn \l__karol_bpxs_dim { 0.26em } \dim_set:Nn \l__karol_bpys_dim { 0.14em } \dim_set:Nn \l__karol_bpmw_dim { 1.05em } } % Beschriftung VOR dem Bild setzen (im tikzpicture gilt \nullfont) und % daraus die Pillenhöhe bestimmen; em-Maße müssen ohnehin vorher in % Register, im Bild sind sie 0pt. \hbox_set:Nn \l__karol_bptxt_box { \normalfont \__karol_textfont: \color{black} \bool_if:NF \l__karol_binline_bool { \footnotesize } \__karol_bphantom_flach: #2 } \dim_set:Nn \l__karol_bph_dim { \box_ht:N \l__karol_bptxt_box + \box_dp:N \l__karol_bptxt_box + 2 \l__karol_bpys_dim } \bool_if:nTF { #1 } { \dim_set:Nn \l__karol_bptri_dim { \fp_to_dim:n { \c__karol_btri_fp * \l__karol_bph_dim } } \dim_set:Nn \l__karol_bpgap_dim { \fp_to_dim:n { \c__karol_btrigap_fp * \l__karol_bph_dim } } \dim_set:Nn \l__karol_bpby_dim { \fp_to_dim:n { \c__karol_btribas_fp * \l__karol_bph_dim } } } { \dim_zero:N \l__karol_bptri_dim \dim_zero:N \l__karol_bpgap_dim \dim_zero:N \l__karol_bpby_dim } % Platz für Lücke und Dreieck FEST in eine Box backen. Als Leerraum % am Ende des Knotentextes geht er verloren — TikZ verwirft ihn, die % Pille wäre dann genau um die Dreieckbreite zu schmal. Die Breite % einer gesetzten Box kann TikZ dagegen nicht mehr ändern. \hbox_set:Nn \l__karol_bpinh_box { \box_use:N \l__karol_bptxt_box \skip_horizontal:n { \l__karol_bpgap_dim + \l__karol_bptri_dim } } \hbox:n { \begin{tikzpicture} [ baseline=0pt ] \node (karolpille) [ fill=\l__karol_bfarbe_tl!40!white, draw=none, rounded~corners=\l__karol_brc_tl, inner~xsep=\l__karol_bpxs_dim, inner~ysep=\l__karol_bpys_dim, minimum~width=\l__karol_bpmw_dim, anchor=base ] at (0pt, 0pt) { \box_use:N \l__karol_bpinh_box }; % Auswahldreieck, rechtsbündig im Innenraum der Pille. % Bezugspunkt ist der Anker "base east": rechte Knotenkante auf % Höhe der Textgrundlinie — damit stimmt die Lage auch dann, % wenn die Mindestbreite die Pille breiter macht als ihr Inhalt. \dim_compare:nT { \l__karol_bptri_dim > 0pt } { \begin{scope} [ shift={ (karolpille.base~east) } ] \fill [ \l__karol_bfarbe_tl ] ( \dim_eval:n { 0pt - \l__karol_bpxs_dim - \l__karol_bptri_dim }, \dim_eval:n { \l__karol_bpby_dim + \l__karol_bptri_dim } ) -- ( \dim_eval:n { 0pt - \l__karol_bpxs_dim }, \dim_eval:n { \l__karol_bpby_dim + \l__karol_bptri_dim } ) -- ( \dim_eval:n { 0pt - \l__karol_bpxs_dim - 0.5 \l__karol_bptri_dim }, \dim_use:N \l__karol_bpby_dim ) -- cycle; \end{scope} } \end{tikzpicture} } } % Pille ohne Auswahl (Zahlen, Namen von Anweisungsdefinitionen, Kommentare) \cs_new_protected:Npn \__karol_bpille:n #1 { \__karol_bpille_aux:nn { \c_false_bool } {#1} } % Pille mit Auswahldreieck (Aufrufe, Himmelsrichtungen) \cs_new_protected:Npn \__karol_bpille_menue:n #1 { \__karol_bpille_aux:nn { \c_true_bool } {#1} } \box_new:N \l__karol_bhex_box \dim_new:N \l__karol_bhw_dim \dim_new:N \l__karol_bho_dim \dim_new:N \l__karol_bhu_dim \dim_new:N \l__karol_bhp_dim \dim_new:N \l__karol_bhn_dim \dim_new:N \l__karol_bhm_dim \dim_new:N \l__karol_bhk_dim \NewDocumentCommand { \bbedingung } { m } { \group_begin: \__karol_bstart: \__karol_bdims: \tl_if_empty:nTF {#1} { % Leere Fassung: weißer Sockel (für Lückenaufgaben) \hbox_set:Nn \l__karol_bhex_box { \__karol_blockfont: \__karol_bphantom_flach: \hspace{1.1em} } \__karol_bbedform:n { white } } { \hbox_set:Nn \l__karol_bhex_box { \__karol_blockfont: \color{white} \__karol_bphantom_flach: #1 } \__karol_bbedform:n { karolBlockBedingung } } \group_end: } % Form: abgerundetes Rechteck, Stecker an der linken Kante (Karol Online) \cs_new_protected:Npn \__karol_bbedform:n #1 { \bool_if:NTF \l__karol_binline_bool { \dim_set:Nn \l__karol_bhw_dim { \box_wd:N \l__karol_bhex_box + 0.34em } \dim_set:Nn \l__karol_bho_dim { \box_ht:N \l__karol_bhex_box + 0.10em } \dim_set:Nn \l__karol_bhu_dim { \box_dp:N \l__karol_bhex_box + 0.10em } \dim_set:Nn \l__karol_bhp_dim { 0.20em } % Steckertiefe \dim_set:Nn \l__karol_bhn_dim { 0.17em } % halbe Steckerhöhe } { \dim_set:Nn \l__karol_bhw_dim { \box_wd:N \l__karol_bhex_box + 0.52em } \dim_set:Nn \l__karol_bho_dim { \box_ht:N \l__karol_bhex_box + 0.17em } \dim_set:Nn \l__karol_bhu_dim { \box_dp:N \l__karol_bhex_box + 0.17em } \dim_set:Nn \l__karol_bhp_dim { 0.30em } % Steckertiefe \dim_set:Nn \l__karol_bhn_dim { 0.26em } % halbe Steckerhöhe } % Senkrechte Mitte und Halshöhe des Steckers. In der Oberfläche ist % der Hals schmaler als das Steckerende (Puzzlenase, keine Lasche): % Hals ~0.27, Ende ~0.42 der Blockhöhe. \dim_set:Nn \l__karol_bhm_dim { 0.5\l__karol_bho_dim - 0.5\l__karol_bhu_dim } \dim_set:Nn \l__karol_bhk_dim { 0.62\l__karol_bhn_dim } % (em-Werte MÜSSEN vor dem Bild in Register: im tikzpicture ist em=0!) \mode_leave_vertical: \hbox:n { \begin{tikzpicture} [ baseline=0pt ] % Körper und Stecker in EINEM Pfad, damit die Umrandung außen % herumläuft statt quer durch den Stecker. \__karol_brand:nn {#1} { ( \dim_use:N \l__karol_bhp_dim , \dim_use:N \l__karol_bho_dim ) -- ( \dim_eval:n { \l__karol_bhp_dim + \l__karol_bhw_dim }, \dim_use:N \l__karol_bho_dim ) -- ( \dim_eval:n { \l__karol_bhp_dim + \l__karol_bhw_dim }, -\dim_use:N \l__karol_bhu_dim ) -- ( \dim_use:N \l__karol_bhp_dim , -\dim_use:N \l__karol_bhu_dim ) \__karol_bbed_links: -- cycle } \node [ anchor=base, inner~sep=0pt ] at ( \dim_eval:n { \l__karol_bhp_dim + 0.5\l__karol_bhw_dim }, 0pt ) { \box_use:N \l__karol_bhex_box }; \end{tikzpicture} } } % Linke Seite des Bedingungsplättchens: Hals und Puzzlenase, von der % unteren zur oberen Halskante. Scharfe Ecken, weil an den Übergängen % Gerade->Kurve die Segmente kürzer als zwei Eckradien sind — pgf machte % daraus sonst Häkchen (dieselbe Falle wie bei der Kerbe). \cs_new:Npn \__karol_bbed_links: { \l__karol_bkerbe_tl -- ( \dim_use:N \l__karol_bhp_dim , \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhk_dim } ) % Puzzlenase: ausschwingen, runde Außenkante, einschwingen .. controls ( \dim_eval:n { 0.72\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhk_dim } ) and ( \dim_eval:n { 0.55\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } ) .. ( \dim_eval:n { 0.22\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } ) .. controls ( \dim_eval:n { 0.02\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim - \l__karol_bhn_dim } ) and ( \dim_eval:n { 0.02\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } ) .. ( \dim_eval:n { 0.22\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } ) .. controls ( \dim_eval:n { 0.55\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhn_dim } ) and ( \dim_eval:n { 0.72\l__karol_bhp_dim }, \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhk_dim } ) .. ( \dim_use:N \l__karol_bhp_dim , \dim_eval:n { \l__karol_bhm_dim + \l__karol_bhk_dim } ) \l__karol_bkante_tl } % --- Himmelsrichtungen: Ist / NichtIst mit Auswahlmenü --------------------- % In Robot Karol Online zeigen diese acht Blöcke die Richtung nicht als % Text, sondern als Auswahlmenü: eine helle Pille mit Auswahldreieck. % % \bist{Norden} \bnichtist{Süden} % % Beide liefern für sich ein vollständiges Bedingungsplättchen (wie im % Original abgebildet). Als Argument eines Kontrollblocks steuern sie % dagegen nur dessen Beschriftung bei — dort baut \bbedingung das % Plättchen, ein zweites wäre falsch: % % \bwenndann{\bist{Norden}}{…} -> wenn dann % % Unterschieden wird über \l__karol_bimbau_bool: innerhalb eines Blocks % ist es wahr, auf oberster Ebene falsch. \cs_new_protected:Npn \__karol_bist_text:nn #1#2 { % Die Pille gehört zum Bedingungsplättchen, nicht zum umgebenden % Block — sonst bekäme sie in \bwenndann dessen Grün. \tl_set:Nn \l__karol_bfarbe_tl { karolBlockBedingung } #1 ~ \__karol_bpille_menue:n {#2} } \cs_new_protected:Npn \__karol_bist:nn #1#2 { \bool_if:NTF \l__karol_bimbau_bool { \__karol_bist_text:nn {#1} {#2} } { \bbedingung { \__karol_bist_text:nn {#1} {#2} } } } \NewDocumentCommand { \bist } { m } { \__karol_bist:nn { Ist } {#1} } \NewDocumentCommand { \bnichtist } { m } { \__karol_bist:nn { NichtIst } {#1} } % --- Benutzerkommandos ----------------------------------------------------- % Jeder Nutzerblock läuft in einer eigenen Gruppe und bestimmt zu Beginn % über \__karol_bstart:, ob er als Stapelblock oder inline gesetzt wird. % \__karol_bwrap:n kapselt das für alle \b…-Kommandos. \cs_new_protected:Npn \__karol_bwrap:n #1 { \group_begin: \__karol_bstart: #1 \group_end: } % dito, aber mit erzwungenem Inline-Modus (\i…-Kommandos) \cs_new_protected:Npn \__karol_iwrap:n #1 { \group_begin: \__karol_bstart_inline: #1 \group_end: } % \bbefehl[Anzahl]{Name} — optionale Zahl erscheint als Pille \NewDocumentCommand { \bbefehl } { O{} m } { \__karol_bwrap:n { \__karol_bblock:nn { karolBlockBefehl } { #2 \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } } } } % \baufruf[Anzahl]{Name} — Aufruf einer eigenen Anweisung (rot). % Der Name steht wie im Original in einer Auswahlpille mit Dreieck (dort % wählt man daraus die aufzurufende Anweisung); die Definition % \banweisung zeigt ihn dagegen in einer Pille OHNE Dreieck. \NewDocumentCommand { \baufruf } { O{} m } { \__karol_bwrap:n { \__karol_bblock:nn { karolBlockAnweisung } { \__karol_bpille_menue:n {#2} \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } } } } % \banweisung{Name}{Blöcke} — Definition einer eigenen Anweisung (C-Block) \NewDocumentCommand { \banweisung } { m m } { \__karol_bwrap:n { \__karol_bcblock:nnn { karolBlockAnweisung } { Anweisung ~ \__karol_bpille:n {#1} } { #2 } } } % \bkommentar{Text} — Kommentarblock (khaki) mit //-Präfix \NewDocumentCommand { \bkommentar } { m } { \__karol_bwrap:n { \__karol_bblock:nn { karolBlockKommentar } { { \normalfont \__karol_textfont: \color{black!70} // } ~ \__karol_bpille:n {#1} } } } % \bhauptprogramm — Hut-Block (ohne obere Kerbe), startet den Stapel \cs_new_protected:Npn \__karol_bhut: { \__karol_bmess:n { Hauptprogramm } \__karol_bemit:nN { \begin{tikzpicture} \useasboundingbox (0pt, 0pt) rectangle ( \dim_use:N \l__karol_bw_dim , \__karol_bbbox:N \l__karol_bh_dim ); \__karol_brand:nn { karolBlockHut } { (0pt, 0pt) -- ( \dim_use:N \l__karol_bw_dim , 0pt ) \__karol_pfad_unten:n \l__karol_bh_dim } \__karol_btxt_setzen:n { 0pt } \end{tikzpicture} } \l__karol_bh_dim } \NewDocumentCommand { \bhauptprogramm } { } { \__karol_bwrap:n { \__karol_bhut: } } \NewDocumentCommand { \bwiederholemal } { m m } { \__karol_bwrap:n { \__karol_bcblock:nnn { karolBlockSchleife } { wiederhole ~ \__karol_bpille:n {#1} ~ mal } { #2 } } } \NewDocumentCommand { \bwiederholesolange } { m m } { \__karol_bwrap:n { \__karol_bcblock:nnn { karolBlockSchleife } { wiederhole ~ solange ~ \bbedingung {#1} } { #2 } } } \NewDocumentCommand { \bwiederholeimmer } { m } { \__karol_bwrap:n { \__karol_bcblock:nnn { karolBlockSchleife } { wiederhole ~ immer } { #1 } } } \NewDocumentCommand { \bwenndann } { m m } { \__karol_bwrap:n { \__karol_bcblock:nnn { karolBlockWenn } { wenn ~ \bbedingung {#1} ~ dann } { #2 } } } \NewDocumentCommand { \bwenndannsonst } { m m m } { \__karol_bwrap:n { \__karol_beblock:nnnn { karolBlockWenn } { wenn ~ \bbedingung {#1} ~ dann } { #2 } { #3 } } } % Leerer „Geisterblock" zum handschriftlichen Ausfüllen \cs_new_protected:Npn \__karol_bluecke:n #1 { \__karol_bdims: \dim_set:Nn \l__karol_bw_dim { #1 } \dim_set:Nn \l__karol_bh_dim { \bool_if:NTF \l__karol_binline_bool { 1.20em } { 1.70em } } % Grundlinie wie bei einem beschrifteten Block: die Lücke sitzt mit % ihrer gedachten Textgrundlinie auf der Grundlinie des Fließtextes. \hbox_set:Nn \l__karol_btxt_box { \__karol_blockfont: \__karol_bphantom: } \__karol_bbase_setzen: % Strichmuster in em rechnen, damit es mit der Blockgröße mitwächst — % ein festes 3pt-Muster ist für die knapp 2pt langen Kerbensegmente des % Inline-Blocks zu grob und zerreißt die Kontur. em MUSS vorher ins % Register: im tikzpicture ist es 0pt. \dim_set:Nn \l__karol_bstr_dim { 0.17em } \dim_set:Nn \l__karol_bluk_dim { 0.13em } \__karol_bemit:nN { \begin{tikzpicture} \useasboundingbox (0pt, 0pt) rectangle ( \dim_use:N \l__karol_bw_dim , \__karol_bbbox:N \l__karol_bh_dim ); \draw [ draw=black!55, line~width=.7pt, dash~pattern=on~\l__karol_bstr_dim~off~\l__karol_bluk_dim, line~join=round, line~cap=round, fill=black!6, rounded~corners=1.6pt ] \__karol_pfad_oben: \__karol_pfad_unten:n \l__karol_bh_dim ; \end{tikzpicture} } \l__karol_bh_dim } \NewDocumentCommand { \bluecke } { O{7em} } { \__karol_bwrap:n { \__karol_bluecke:n {#1} } } % --------------------------------------------------------------------------- % Inline-Blöcke für den Fließtext % --------------------------------------------------------------------------- % Die \i…-Kommandos setzen genau einen Block in die laufende Zeile: % in der Schriftgröße des umgebenden Textes, mit dessen Grundlinie % bündig und mit knapperen Verzierungen als im Stapel. Blöcke, die im % Stapel mehrzeilig wären (Schleifen, Verzweigungen, eigene Anweisungen), % erscheinen inline nur mit ihrem Kopf — \iwenndann{IstZiegel} ergibt % also den Block „wenn ⟨IstZiegel⟩ dann". Für echte Rümpfe bleibt die % Umgebung karolbloecke zuständig. % % Auch die \b…-Kommandos erkennen den Fließtext selbst und setzen dort % automatisch inline; die \i…-Fassungen ersparen nur die leeren % Rumpf-Argumente und erzwingen den Modus auch im Stapel. % --------------------------------------------------------------------------- \NewDocumentCommand { \ibefehl } { O{} m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockBefehl } { #2 \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } } } } \NewDocumentCommand { \iaufruf } { O{} m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockAnweisung } { \__karol_bpille_menue:n {#2} \tl_if_empty:nF {#1} { ~ \__karol_bpille:n {#1} } } } } \NewDocumentCommand { \ianweisung } { m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockAnweisung } { Anweisung ~ \__karol_bpille:n {#1} } } } \NewDocumentCommand { \ikommentar } { m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockKommentar } { { \normalfont \__karol_textfont: \color{black!70} // } ~ \__karol_bpille:n {#1} } } } \NewDocumentCommand { \ihauptprogramm } { } { \__karol_iwrap:n { \__karol_bhut: } } \NewDocumentCommand { \iwiederholemal } { m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockSchleife } { wiederhole ~ \__karol_bpille:n {#1} ~ mal } } } \NewDocumentCommand { \iwiederholesolange } { m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockSchleife } { wiederhole ~ solange ~ \bbedingung {#1} } } } \NewDocumentCommand { \iwiederholeimmer } { } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockSchleife } { wiederhole ~ immer } } } \NewDocumentCommand { \iwenndann } { m } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockWenn } { wenn ~ \bbedingung {#1} ~ dann } } } % \isonst — der „sonst"-Balken einer Verzweigung als eigener Inline-Block \NewDocumentCommand { \isonst } { } { \__karol_iwrap:n { \__karol_bblock:nn { karolBlockWenn } { sonst } } } % \ibedingung{…} — Bedingungsplättchen im Fließtext (erzwungen inline) \NewDocumentCommand { \ibedingung } { m } { \__karol_iwrap:n { \bbedingung {#1} } } % Himmelsrichtungen inline \NewDocumentCommand { \iist } { m } { \__karol_iwrap:n { \bbedingung { \__karol_bist_text:nn { Ist } {#1} } } } \NewDocumentCommand { \inichtist } { m } { \__karol_iwrap:n { \bbedingung { \__karol_bist_text:nn { NichtIst } {#1} } } } \NewDocumentCommand { \iluecke } { O{4em} } { \__karol_iwrap:n { \__karol_bluecke:n {#1} } } \ExplSyntaxOff \endinput