Posts by JeeK

    Am Freitag 20:15 auf NDR ist es soweit - "Unser Schatz fuer Niedersachsen". im Trailer fuer die Sendung (eben zufaellig gesehen) ist auch der goldene C64 zu sehen.

    Naja, der Unternehmer, Hr. Streiff weiß nur von 3 Goldenen C64, die es noch gäbe ... hmm, da fehlt in bisschen der Szeneneinblick, würd ich sagen.

    Und als Bezeichnung soll "Brottrommel" geläufig gewesen sein ... echt? Ich dachte Brotkasten? (in Wien war mir damals eigentlich beides nicht geläufig).


    Aber den "Trailer", der ist doch schon von März 2021 ...?

    - Wie erstellt man einen Loop indem bei Sprites sich bewegen und dabei das Sprites wechseln.
    - Die Schleife sollte das Hauptprogramm nicht blockiert wird.

    Die Loop zur Bewegung sieht schon fast richtig aus.

    Eine Schleife, die das Hauptprogramm nicht blockiert, gibt es in diesem Basic nicht. Es gibt nur genau einen Programmfluss, nur eine Reihenfolge, in der alle Befehle nacheinander ausgeführt werden.

    Aber Du kannst die Aufgabe in kleine Schritte zerlegen, eine große Schleife drumrum, und die einzelnen Schritte dann in richtiger Reihenfolge nacheinander in die Schleife reintun. "Kleiner Schritt" wären hier wirklich kleine Schritte: Schritt 1 Player1, Schritt1 Player2, Pause, Schritt2 Player1...
    Ungefähr so dürfte das aussehen

    Ich würde eine einzige Schleife machen, die in 3 (Animationsphasen) läuft und abhängig von der Bewegung.



    Man könnte Player und Gegner auch asynchron laufen lassen, wenn man den Zugriff auf PS() und CS() im 2. Parameter über ein Zwischen-Array macht, das die Phasenverschiebung berücksichtigt. Dann ist P der fixe Zyklus der von 0 bis 2 immer wieder durchläuft und das Zwischen-Array liefert dann die tatsächliche Phase (der Bewegung).

    z.B. PV(Offset, Zyklus):

    0,0 = 0

    0,1 = 1

    0,2 = 2

    1,0 = 1

    1,1 = 2

    1,2 = 0

    2,0 = 2

    2,1 = 0

    2,2 = 1

    Die Joystick-Routinen liefern dann auch noch den Offset, z.B. PO und CO. Befinden wir uns in Zyklus P=1 und wird eine Kickbewegung erkannt, setzen wir den Offset auf 2, weil hier die mit Phase 0 begonnen wird, also auch das Sprite am Anfang der Bewegung ist. Das Sprite wird dann wie folgt gewählt (für den Player):

    POKE 2040,PS(BP, PV(PO, P))


    Dazu gehören noch alle möglichen Optimierungen hinsichtlich frühes und geordnetes Anlegen von Variablen, Vermeidung von Rückwärts-GOTOs, Konstanten in einfache Variablen ablegen und und und ...

    Wenn dann auch noch 1 bis 7 Gegner durchzuwälzen sind, muss man noch mal für die mit Arrays arbeiten. Einfach Variablen und entsprechend aufgerollter Code könnten da bald an Limits geraden und unübersichtlich werden ...

    Statt $fb/$fc zwei andere Zeropage-Locations verwenden (z. B. $50/$51, wird sonst nur für die Stringverarbeitung temporär gebraucht). Dann kann man mal ausschließen, dass der Interrupt in $fb/$fc reinspielt.


    Anmerkung zur Routine selbst. Wenn man nicht wirklich im laufenden Programm den Bildschirm an unterschiedlichen Positionen löschen muss, ist die Lösung ein ziemlicher Overkill (in Zeit und Platz).

    Reicht hier nicht die Standardlösung?

    Wozu, hab ichi schon gesagt: Um einzugrenzen, wo Mathe-Fehler auftauchen und sie zu vermeiden.

    Vermeiden?
    Primär den ROM-Patch verwenden, wenn man Bug vermeiden möchte.
    Sonst ist es ein generelles Problem, dass die Dezimaldarstellung und Binärdarstellung grundsätzlich nicht fehlerfrei konvertieren kann (also nicht in allen Fällen). Was soll dann hier in dem Thread passieren? Alle 2^40 theoretisch möglichen Werte einer FP-Darstellung für Dezimalzahlen auflisten und dann gegenüberstellen, ob die eine Dezimaldarstellungsentsprechung haben und ob und wie groß die Abweichung aus der Konvertierung ist ... na, dann viel Spaß damit. ;)
    Natürlich kann man, hier den Stoff aus dem Fachgebiet aus der Numerik der Informatik bzw. numerische Mathematik hier aufarbeiten. Oder für den 0815-BASIC-V2-Programmierer in appetitliche Häppchen bringen. Auch gut. Auch wenn das im Kontext des BASIC V2 passieren sollte, ich weiß nicht welche neuen Erkenntnisse hieraus erwachsen sollen. Wie oder was auch immer, ich bin mal gespannt, was uns hier nun erwarten wird ...

    Die Anwendung ist I/O-bound, die paar Millisekunden können's jetzt auch nicht reißen.

    Schrieb ich ja, bei GET-basiertem aber noch eher, als bei INPUT. Soweit schon klar. Aber in BASIC wirkt sich halt die Summe alle Optimierungen auf die Gesamtgeschwindigkeit aus, auch wenn die genannte nur einen kleinen Beitrag darstellt. Aber wenn ich einmal weiß, dass X schneller als Y, warum soll man es dann nicht auch als Kochrezept für solche Schleifen kontinuierlich verwenden? Und so unverständlicher ist das bei dieser Konstruktion mit FOR-NEXT auch nicht mehr ... ;)

    In der Vorschau für die NTV-Sendung heute, 16.8. um 20:15 ist schon mal ein C64 vorgekommen ... ob es noch mehr zu sehen gibt?


    Laut https://www.n-tv.de/mediathek/tvprogramm/


    20:15 Drei Jahrzehnte - Ein Wir Neue Deutsche Welle, Schulterpolster oder Minipli: Musik und Mode erfinden sich in den 80er Jahren quasi über Nacht neu. Politisch ist die Dekade ebenso vom Wandel geprägt. Das Jahrzehnt beginnt in der BRD mit Friedensmärschen und Protesten der Anti-Atom-Bewegung sowie dem erstmaligen Einzug der Grünen in verschiedene Parlamente. Große Aufmerksamkeit erregt auch die Affäre um die gefälschten Hitler-Tagebücher.

    1 OPEN15,8,15:FORP=-1TO0:GET#15,A$:PRINTA$;:P=ST=0:NEXT:CLOSE15

    Ich tendiere folgendes zu verwenden:


    FORP=0TO1:GET#15,A$:PRINTA$;:P=ABS(ST):NEXT:CLOSE15


    Das ABS() sorgt dafür, dass (mögliche) negative ST-Werte positiv werden, damit FOR/NEXT auch wirklich endet.

    Ist soweit ich gemessen habe ist das schneller. Für den Fehlerkanal egal, aber generell bei Dateien, Verzeichnis brauchbar. ;)

    Ja, aber das haben wir doch schon alles durchgekaut. Wenn es neue Fehler geht, dann einfach in lDie Fehler des Basic V2 posten und gut ist es.


    Was soll man da "untersuchen"? Es gibt die Fehler, die sind bekannt, dokumentiert und beschrieben. Jetzt sollen noch unzählige Beispiele dazu diesen Thread füllen? Wozu?

    Die Freunde von obfuscated code mögen bitte um Himmels Willen eigene Threads machen und nicht ausgerechnet Threads mit klar verständlichen, hilfreichen Tipps unterwandern. (-;

    Ja, das ist leider das bittere Los der Interpretersprachen, wo Eleganz und Ästhetik durch mangelnde Effizienz gestraft wird ... X/
    Die Grenze, was "obfuscated code" ist, mag hier sehr verschwimmen. Manchen sind der Meinung, das BASIC (speziell aus der 8-Bit-Ära) generell zu Obfuscated Coding zählt. :D Aber das kann ja jeder für sich entscheiden, ob es nun um "Geschwindigkeit" oder Code-Lesbarkeit geht. Jedenfalls konkurriert das hier miteinander und kann somit nicht so einfach losgelöst betrachtet werden.

    Das Codebeispiel geht meines Erachtens (im Denkmodell) davon aus, dass die Spriteposition SPRITEX, SPRITEY und ENEMYSPRITEX. ENEMYSPRITEY in der fiktiven Mitte der jeweiligen Sprites befinden. Deswegen wird der linke und obere Rand durch Subtraktion der halben Sprite-Breite/Höhe ermittelt und mit XSIZE2, YSIZE2 hat man dann die Breite bzw. Höhe des gesamten Sprites. Für die Kollisionsberechnung ist es eigentlich egal, ob der Bezugspunkt für das Sprite im in der fiktiven Mitte oder oben links ist, solange alle Sprites und Berechnungen gleich ausgeführt werden. Der absolute Bezug zum Schirm stimmt dann womöglich nicht.bzw. muss berücksichtigt werden.

    Aber den Kollisionsrahmen mit Sub. und Addition abzustecken ist vielleicht etwas zu umständlich. Wenn man als Bezugspunkt ohnehin schon die linke obere Ecke eines Sprites hat, dann braucht man doch nur die Sprite-Ausdehnung in jede Richtung einmal dazu zu addieren und hat dann absolut die linken/rechten bzw. oberen/unteren Randpositionen für den Vergleich mit einem anderen Sprite ...

    Ich sehe hier mehrere Probleme. Die C64 Wiki wird nur von wenigen Leuten betreut und man kann nicht alles wissen. Häufig spielt einen das Gedächtnis auch einen Streich, gerade bei Daten passiert das sehr schnell. Ich selbst bin häufig überrascht wenn es um Daten geht weil ich hätte schwören können das diese oder jene Serie, Film, Spiel etc deutlich früher erschienen sein müssten. Bei vielem ist man einfach auf die Angaben anderer angewiesen und so lange einem nichts gegenteiliges bekannt ist muss man es erstmal so hinnehmen, das geht einem Wiki Moderator nicht anders als uns allen. Eine Wiki ist auch kein allwissendes Lexikon mit Anspruch absoluter Korrektheit sonder das Ergebnis dessen was man selbst und andere an Informationen zufügen.

    Das kann ich nur absolut unterstreichen. Das ist ja die Kernidee eines Wikis, dass man unkompliziert Informationen sammelt, auch wenn hier die Informationen in der Regel nicht garantiert richtig oder vollständig ist oder sein kann. Durch die Kollaboration aller mitwirkenden Autoren und Admins haben wir somit einen ständig laufender Prozess der Verbesserung. Es gibt so gesehen keinen "Endzustand". Ich glaub, die Wortklauberei bezüglich Fakten etc. bringt uns dahingehend nicht sonderlich weiter. ;)


    BTW, den Status für Änderungen im Wiki kann man verbessern, indem man sich beim Wiki mit einem Account anmeldet (wenn da ein Problem auftritt, wegen zu vielen Änderungen).

    Gleich was sagen sollen? Ich wollte an den Paar Seiten, wo ich das beobachtet habe keinen Edit-War auslösen, wenn ich das direkt auf den Seiten ändere bzw. war mir nicht sicher, ob was anderes dahintersteckt und hab es daher nicht auf den Seiten andiskutiert. Deswegen wollte ich mich in der C64-Wiki-Community nur rückversichern, ob es dazu vielleicht einen Entschluss gab, da ich hier im Forum bezüglich Wiki auch nicht alles mitbekommen haben könnte. ;)


    Also fein, dass das geklärt ist, wir bleiben somit bei "Links" ("Weblinks" im dt. Wiki).


    Danke euch allen fürs Feedback. :)

    Kann man sich nicht zusätzlich noch ein paar Kopieraktionen sparen, wenn Strings mit den Längen 1 und 2 (und 0 natürlich ohnehin) erst gar nicht im String-Heap abgelegt werden sondern nur im Deskriptor-Pointer 'leben'?


    Ab String-Länge 3 geht dann der Tausch "Descriptor-Pointer gegen zwei End-Bytes im String" problemlos, wird ein String gelöscht geht immer die Markierung mit $FFxx an dessen Ende, und die Sonderfallbehandlung für Strings der Länge 1 fällt weg.


    Oder hab' ich da was übersehen?

    Gemeint ist hier ständig und nicht nur zum Zeitpunkt der Collection, oder? Das Konzept ist aber dann ein gänzlich anderes und habe ich zumindest schon länger verworfen (daher kam wohl zumindest der Grundgedanke, den Descriptor zweckzuentfremden), da hierbei im Interpreter mehr umgebaut werden müsste, wenn es um das String-Handling geht, ist das bei weitem - so finde ich - keine praktikable Lösung.

    Im Interpreter müssten dann alle Stellen, die mit einem String-Descriptor hantieren, die Fälle für Strings mit Descriptor (Länge >2) und für Strings der Länge 1 und 2 ohne Descriptor unterscheiden. Dann sollte es auch keine Fälle geben, wo es doch einen Descriptor Länge 1 oder 2 gibt, also auch nicht für Strings, die im BASIC-Text sind. Auch da müsste der String in den Descriptor kopiert werden. So weit so gut. Der Schritt Descriptor-Pointer-zu-String-Adresse und das Schreiben des Descriptors ist mit der Fallunterscheidung zu behandeln, also keine uniforme Behandlung von Descriptoren mehr (die zu einer Union-Struktur in C-Nomenklatur mutiert sind). Nicht sehr elegant wie ich finde. Für eine Konzeptstudie mag es ja interessant sein, aber nicht wirklich für die Praxis, wo der Interpreter noch umfangreicher umgebaut werden müssten. Das geht über ein "Plugin-Replacement" etwas hinaus.


    Abgesehen davon, das "Ersparen" von Kopieraktionen wird hier vielleicht überbewertet: Das macht bei einem linearen Algorithmus im Endeffekt und in der Praxis kaum mehr etwas aus. Selbst wenn man sich Stage 3 mit dem Aufbau der Strings der Länge 1 am Heap ersparen würde (also einen Durchlauf durch alle Discriptoren), ist selbst im praxisfremden "Worst Case" mit ca. 9600 Strings die Routine faktisch genauso schnell wie alle anderen linearen Implementierungen (die in der CBM-Variante sogar nur einen "Pass" haben). Vergleicht man mit Garbage Coll 64, die auch 2 Passes hat und man das als Maßstab nimmt, dann ist bei 5000 Strings mit einer Laufzeitersparnis in der Gegend von ca. 12 % zur rechnen (1,5 s statt 1,7 s). Ist eigentlich auch schon egal, wenn man ehrlich ist.

    Nur im Vergleich dazu der Aufwand, der in der originalen CBM-Variante von BASIC 4.0 und folgend gemacht wird: Hier wird mit einem Flag und deutlichem Mehraufwand in der Logik getrachtet, dass bei einem bereits "intakter" Heap soweit es geht *nicht* extra kopiert wird in der guten Hoffnung, sich das "Kopieren" zu ersparen. Der Gewinn ist in der Praxis minimal. Die Implementierung Garbage Coll wie auch BLOD ersparen sich das komplett und kopieren selbst bei eine nahezu vollständig "in Ordnung" befindlichen Heap die Strings an die gleiche Stelle. Egal, sie sind unterm Strich in den Benchmarks dennoch schneller.
    Natürlich, wenn ich in einer Schleife 1000 mal FRE(0) aufrufe, dann sieht/merkt man den Unterschied - hat dann aber auch nichts mit der Praxis zu tun. ;)

    Die hier am 25. März begonnene, sich dann ausweitende Diskussion und die Überlegungen zu den hier gestellten Fragen (die ja eigentlich schon bereits vor 35 Jahren entstanden sind) hat zwar insofern keine neuen Erkenntnis gebracht, abgesehen für jene, die sich bislang mit dem Thema noch nicht auseinander gesetzt hatten. Aber die Überlegungen rund um diesen Komplex sind auch bei mir wieder intensiver entfacht worden.

    Aber am 26. März wachte ich irgendwie unruhig auf, eine Unruhe, die wohl irgendwie die Diskussion hier ausgelöst hatte. Und plötzlich war da der "Gedanke": Wie wäre es, bei einem linearen Back-Link-Algorithmus ohne dabei für jeden String am String-Heap zwei Bytes für den Back-Link zu "verschwenden"? Die Back-Link kann man ja auch erst zum Zeitpunkt der GC erstellen (das macht die GarbColl-Implementierung für den C64 auch, die aber die 2 Bytes reserviert hat). Aber wo tut man den Back-Link hin? Natürlich, in den String selbst, beispielsweise ans Ende! Nun, der String würde ja dadurch zerstört, oder? Wir haben die Fälle ein String hat die

    1. Länge >= 2, wo prinzipiell im String für den Back-Link Platz hat. Die beiden überschriebenen Bytes speichert man dann einfach in den Descriptor (wo normalerweise die String-Adresse ist, aber zu diesem Zeitpunkt nicht gebraucht wird), auf den ja der Back-Link zeigt.
    2. Länge = 1, wo der Back-Link keinen Platz hat. Auch kein Problem: Der String-Inhalt von einem Byte wird eben so in den Descriptor verlagert. Das eine Byte am Heap wird als "Lücke" markiert. Den Back-Link ersparen wir uns überhaupt!

    Gut, damit hätten wir am Heap alle "verwendeten" Strings behandelt. Aber was ist mit den verworfenen Strings, dem Müll, der zwischen den verwendeten herumliegt? Im Zuge der "Collection" muss der Heap mit den gebrauchten Strings und den Lücken zusammenhängend durchwandert werden können. Der gängige Algorithmus von BASIC 4.0 und folgende markiert schon bei der Anforderung eines Strings diesen grundsätzlich als Lücke, wobei der Back-Link keine Adresse darstellt, sondern im High-Byte $FF enthält (was eine String-Adresse nicht haben kann/darf) und im Low-Byte die Länge der Lücke aufweist. Nun, das kann ein Problem werden ... hier haben wir ja keine fest reservierten Bytes für diese Markierung. D. h. wir müssen die Strings zu dem Zeitpunkt als Müll markieren, zu dem er anfällt. Nach einer Analyse der BASIC-Interpreterroutinen kristallisieren sich drei Stellen heraus:

    1. Die Zuweisung (LET, GET, INPUT, READ): Eine String-Variable, die bereits einen String (der am String-Heap liegt) zugewiesen hat, bekommt den Descriptor eines neuen Strings hineingeschrieben. -> Der "alte" String muss also als Lücke markiert werden.
    2. Die String-Zusammenfügung (Concatenation bzw. String-Addition): Hier entstehen aus 2 Strings ein neuer, wo bei der neue String bereits angelegt wird, bevor die "Argument"-Strings freigegeben werden können, weshalb die Argumente mitunter als Lücken zu markieren sind.
    3. Die String-Teilermittlung der Funktionen MID$, LEFT$, RIGHT$. Auch hier entsteht ein neuer String, noch bevor der Argument-String verworfen werden kann.

    In all diesen Fällen geht es nur um Strings, die temporär am String-Heap angelegt sind. Solche, die als Konstanten im BASIC-Code eingebettet sind oder im Interpreter-ROM liegen oder Variablen zugeordnet sind, sind nicht betroffen. Wie erkennt man diese? Diese werden stets am String-Descriptor-Stack angelegt. Der Interpreter hat eine zentrale String-Housekeeping-Routine, die sich zwar um das Entfernen von solchen Strings kümmert, aber in den obigen 3 Fällen, wirkt das nicht, weil ein neu produzierter String entsteht, der verhindert, dass etwaige unmittelbar zuvor angelegt Strings gleich wieder vom Heap entfernt werden können. Zudem wird ein String bei Markieren als Lücke "zerstört", kann also dann nicht mehr (etwas für ein Kopieren) verwendet werden. Der Interpreter geht nämlich davon aus, dass auf den Inhalt eines freigegebenen String zugegriffen werden kann! Aber mit den drei für die obigen Stellen entsprechend platzierten Patches kommt man aus, um den entstandenen Müll zu markieren, wobei die Verwendung des Strings und dessen Freigabe entsprechend umgeordnet werden.
    Die Markierung erfolgt nach dem Schema

    1. Lückenlänge 1: Es wird als der Wert 1 hinterlegt. Als High-Byte eines etwaige Back-Links kann es keinen Back-Link darstellen, da ein String-Descriptor nicht in der Stack-Page ($0100 bis $01FF) zu liegen kommen kann. Damit haben wir eine spezielle Markierung für Einserlücken.
    2. Lückenlänge >1: Es wird im High-Byte $FF hinterlegt, im Byte davor dann die Länge der Lücke.

    In allen anderen Fällen, wo temporär Strings entstehen, sorgt die Housekeeping-Routine dafür, dass Strings sofort vom Heap verschwinden. Diese erzeugen erst gar keinen String-Müll.

    Damit hat man die Voraussetzung geschaffen, dass der String-Heap in einem Zug, vom Anfang bis zum Ende durchgegangen werden kann.


    Die Implementierung dafür habe ich Back-Link On-Demand Garbage Collection (BLOD-GC) genannt. Wer mag, hat auch Github-Repository dazu.

    Die Beschreibung findet sich auch auf der genannten Seite. Zur Illustration der Arbeitsweise hilft das folgende Bild, das von eben dort kommt:

    Es zeigt die Arbeitsweise an exemplarischen Befehlen. Im oberen Teil sieht man die Arbeitsweise der Lückenmarkierung, hier im Fall der String-Addition und mit dem FRE(0)-Aufruf und damit dem Auslösen der Garbage-Collection kommt dann der in 3 Stufen ablaufende Collection-Vorgang:

    1. Alle aktiven Strings am Heap erhalten den Back-Link eingepflanzt, im Falle von Länge-1-String, wird das Byte am Heap als "Lücke" deklariert und das Byte im Descriptor des Strings "gesichert".
    2. Es wird der Heap durchgegangen, wobei Lücken übersprungen werden und Strings nach "oben" kopiert (also der neue Heap ohne die Lücken aufgebaut). Nach dem Kopieren, wird der Back-Link wieder entfernt und der String restauriert (aus dem Descriptor) und dabei bekommt der Descriptor die neue String-Adresse.
    3. Alle Strings mit Länge 1, die ursprünglich am String-Heap waren (und derzeit nur im String-Descriptor zwischen gespeichert sind), werden am String-Heap neu angelegt und der Descriptor mit der neuen String-Adresse versehen.

    Damit befinden sich nur noch die "aktiven" Strings am Heap und alle Lücken sind eliminiert, der maximal möglich freie Platz am Heap ist wieder vorhanden.


    Hinsichtlich der Laufzeit ist das Verfahren gleich schnell wie die pufferbasierte SuperGC und liegt gleichauf mit den anderen linearen Varianten. Das variiert dann nur noch hinsichtlich des Interpreter-Overheads der jeweiligen BASIC-Version und der jeweiligen CPU-Geschwindigkeit (wenn sie denn vom C64 abweicht). Im Grunde spielen die Abweichungen aber keine Rolle, da die Laufzeit eines solchen linearen Verfahrens im Vergleich zur Standard-Routine ja um Größenordnungen besser ist. :D

    As far as I could see the most common section name for the link section ist "Links". Currently some pages has been changed to "Weblinks" (as per convention for the german Wiki site). Is this the "new" writing convention for such a section in case of new or changed articles?