Hallo Besucher, der Thread wurde 12k mal aufgerufen und enthält 97 Antworten

letzter Beitrag von Bytebreaker am

Yet Another Pong

  • Hallo zusammen.


    Ich hatte zwar angekündigt, Assembler zu lernen um Old School Intros machen zu können, aber V2 Basic ließ mich dann doch nicht los.
    Anbei findet ihr meinen neuen Pong-Klon. Ich denke mir immer, "das muss doch auch in Basic gehen" - auch wenn hier ein Raster Interrupt bestimmt beim Timing geholfen hätte.


    Features:
    - Verwendet Get anstelle von Input. Habe aus Euren Tips gelernt. Die Mühe, Backspace-Taste für Eingabekorrekturen abzufangen habe ich mir aber nicht gemacht. Jetzt weiß ich auch, warum String-Felder in Fenstern so eine sinvolle Erfindung sind.


    - Man hat im Optionsmenu nur die Wahl zwischen einer Runde und 3 Runden pro Spiel. Ich wollte bis zu 9 Runden möglich machen, aber mehr als 3 Runden (d.h. 5 Durchläufe bis ein Sieger feststeht) klappen nicht ohne Out of Memory Meldung. Default-Rundenzahl ist 3.


    - Ich hatte die Wahl zwischen einer Super-KI und einer Dumpfnuss-KI. Wegen der Langeweile hab ich mich für die Super-KI entschieden, auch wenn sie momentan eher schwer zu besiegen ist.


    - Dafür gibt es noch einen 2 Spieler Modus, Steuerung erfolgt per Joystick in Port 1 und 2.


    - Die Ballkalkulation ist vergleichsweise primitiv. In meinem Basic-Konstrukt habe ich aber keine Möglichkeit gesehen, zufallsgesteuerte, unterschiedlich "flache" Bälle zu verwenden ohne dass das Spiel noch langsamer geworden wäre oder Out of Memory Meldungen gekommen wären. Mein erster Gedanke war ja, beim Aufprall auf den Schläger einen unterschiedlich flachen Streckenverlauf auszuwürfeln, diesen in ein Array zu schreiben, der KI zu sagen wo der Ball landen wird und dann den Ball das Array abfahren lassen. Pustekuchen. Ich habe es in Basic nicht hinbekommen.


    - Die einzige Variation die man anstelle von Flachheitsgraden hat: Zu Beginn jeder Runde klebt der Ball am betreffenen Schläger, während man ihn bewegt. Bei Feuerknopf wird der Ball weggeschossen. So kann man den Streckenverlauf zumindest durch unterschiedliche Startpunkte beeinflussen.


    Wer etwas besser weiß, kann, oder sonstige Tips für mich hat: Immer her damit. :-)


    Jetzt muss ich aber Assembler lernen. Es sei denn ich mache noch ein BASIC-Kartenspiel oder einen BASIC-Scrolltext der sich anhand von Sinusdaten bewegt. Muss doch auch in BASIC gehen.. argh! C64 BASIC ist deshalb so fesselnd, weil das alles so ein gut durchdachter Baukasten ist. Ich denke so oft "Ach, auch daran haben sie also gedacht". Das Zusammenspiel ist gut von dem, was man mit BASIC, Kernel-ROM, Sprites, SID, Registern und Petscii-Bausteinen alles kombinieren kann. BASIC macht auch eigentlich nur Sinn ohne Grafikmodus finde ich.

  • C64 BASIC ist deshalb so fesselnd, weil das alles so ein gut durchdachter Baukasten ist. Ich denke so oft "Auch, auch daran haben sie also gedacht". Das Zusammenspiel ist gut von dem, was man mit BASIC, Kernel-ROM, Sprites, SID, Registern und Petscii-Bausteinen alles kombinieren kann. BASIC macht auch eigentlich nur Sinn ohne Grafikmodus finde ich.


    Glückwunsch zum Spiel. :thumbsup:


    So mal eben :popkorn: aufmach und vom Sessel aus beobachte was jetzt noch alles kommt. :bgdev


    Mal schauen ob den Schläger bewegt bekomme... :gruebel


    Ah... bissel langsaam, macht aber Spaß. :thumbup:


  • Die KI ist übrigens doch zu schlagen. Hab ich grad festgestellt.
    Tja und die Langsamkeit ist eben das was durchs BASIC kommt. Es sei denn jemand zeigt mir wie das ohne Assembler schneller geht.


    Da wart mal bis die Freaks Feierabend haben. Die werde das bestimmt noch tunen.


    Ich werde das mal ins 64er Studio reinziehen. Die Ballbewegung und KI interessiert mich.

  • Ich habe leider aus Performance-Gründen alle REMS entfernt und das Programm so weit es geht gestrafft. Nur manuelles Renumber hab ich sein lassen und Leerzeichen im Befehlscode drin gelassen (dafür aus den Print-Befehlen mit SPC herausgenommen). Daher dürfte sich die Lesbarkeit in Grenzen halten.
    Die KI besteht eigentlich nur aus einer einzelnen Formel in Zeile 564.

  • Basic Compiler bringt mehr Speed ^^


    Das Programm ist ja nicht groß. Eine prima Gelegenheit für mich demnächst, wenn ich mit dem 64er mal programmieren will.
    Kann ich mich mit dem Gerät auseinander setzen, wenn ich das Spiel nach Loco und/oder Z80 übertrage.
    Oder auch selber später ne Version in 6510 Versuchen. Mal schauen.

  • Die größte Schwäche im Spiel ist momentan das Timing. Ein Spieler kann den anderen "lähmen" durch ständiges Bewegen. Die Abfragegeschwindigkeit ist so langsam, dass das möglich ist.
    Ich werde mal schauen was der Basic Compiler an Speed-Zuwachs bietet und ob das Problem dann noch besteht.


    Wobei sich das im 2 Spieler Modus aktuell ausgleicht. Man ist abwechselnd an der Reihe und so könnte jeder den anderen "blocken". Daher würden sich zwei menschliche Spieler also einigen, nicht "asozial" zu spielen. Die KI dagegen putzt man mit diesem "Bug" leicht weg wenn man will.

  • Ich werde mal schauen was der Basic Compiler an Speed-Zuwachs bietet und ob das Problem dann noch besteht.


    Kannst ja mal BASIC 64 von Data Becker ausprobieren. Steht bei mir im Regal und erspart mir den Test. :whistling:


    Vieleicht bekommt das Programm ja noch ein B*f-Tuning. :weg:

  • Ich wollte bis zu 9 Runden möglich machen, aber mehr als 3 Runden (d.h. 5 Durchläufe bis ein Sieger feststeht) klappen nicht ohne Out of Memory Meldung.
    [...]
    ohne dass das Spiel noch langsamer geworden wäre oder Out of Memory Meldungen gekommen wären.

    Bei einem Programm dieser Größe ist "Out of memory" eigentlich ein Ding der Unmöglichkeit. Ich vermute, dass Du irgendwie mit den Unterprogramm-Aufrufen durcheinander gekommen bist und versehentlich eine Rekursion gebastelt hast. Beispiel:

    Code
    1. 10 print i;:i=i+1:gosub 10
    2. run

    25 Ebenen sind nicht viel, reichen aber normalerweise dicke aus.
    Um so etwas zu verhindern, kann man sich z.B. damit behelfen, dass immer nur Unterprogramme mit kleinerer Zeilennummer aufgerufen werden dürfen. Somit sortiert man sich die Unterprogramme automatisch nach Aufruftiefe, und wenn das an einer Stelle nicht möglich ist, hat man eine Rekursion (und somit vermutlich einen Fehler) gefunden. Wildes Umherspringen mit GOTO ist dann natürlich verboten, aber das sollte man ja eh minimieren.


    Ich hab den Source nur kurz überflogen, besonders aufgefallen sind mir nur die Joystickabfragen. Statt mehrerer wiederholter Abfragen des Typs "IF PEEK(joy)=this THEN that" würde ich immer nur ein einziges Mal PEEKen und das Ergebnis in einer Variable aufbewahren. Evtl. kann man auch mehrere Wert-Abfragen durch eine einzige mit entsprechenden AND/OR-Operationen erschlagen.

    C64 BASIC ist deshalb so fesselnd, weil das alles so ein gut durchdachter Baukasten ist. Ich denke so oft "Ach, auch daran haben sie also gedacht".

    :gruebel

  • müsste im Option-Screen nicht "Zahl <4" stehen ?
    Weil "3" lässt sich noch eingeben.


    An sonsten cooles Teil :thumbup:
    Sollte man echt mal durch einen Compiler laufen lassen ....

  • @ Mac Bacon


    Danke für dir Tips.
    Bzgl Joytick-Abfragen: Er peekt ja pro Schleife immer nur einmal, fragt dabei aber alles ab was möglich ist:


    In der "Vorrunde":


    - Linker Spieler fire?
    - Linker Spieler fire+up?
    - Linker Spieler Fire+down?


    - Rechter Spieler fire?
    - Rechter Spieler fire+up?
    - Rechter Spieler Fire+down?


    Nach dem Startschuss per Feuerknopf:


    - Linker Spieler up?
    - Linke rspieler down?


    - Rechter Spieler up?
    - Rechter Spieler down?


    Natürliche habe ich es auch mit Verunden und Verodern versucht, gerade bei den Firepress-Abfragen aber es gelang mir nicht.
    Benutzt habe ich Abfragen nach dem Schema if peek(register) and maske = 111
    Dass da 111 rauskommen muss stimmt sogar, aber es passiert dann nichts. Ich hab es nicht geschafft und dann resigniert Einzelabfragen von Zahlen gemacht.


    Das Out of Memory hast Du gut entlarvt:


    Ich mache goto Sprünge aus Unterprogrammen, die zuvor mit Gosub aufgerufen wurden. Das ist wohl die Schwäche an der es liegt. Mehr als 6 Durchläufe packt er dadurch nicht.


    Zum Thema Basic-Baukasten:
    Ja ich weiß, es stammt von Evil Microsoft und wer wirklich was vom Coden versteht sieht auch die vielen Schwächen.
    Trotzdem ist das Baukasten-Prinzip hochmotivierend für einfache Spiele und kleine Erfolgserlebnisse, finde ich.

  • Mac meinte wohl das hier:

    Code
    1. 213 if peek(56320) = 111 then joy=1:gosub 206
    2. 214 if peek(56320) = 110 then joy=1:gosub 206
    3. 215 if peek(56320) = 109 then joy=1:gosub 206


    Peek(magische Zahl) ist im Vergleich zu einer Variablensuche eher langsam, also sollte eine Konstruktion wie

    Code
    1. 213 j = peek(56320):if j = 111 then joy=1:gosub 206
    2. 214 if j = 110 then joy=1:gosub 206
    3. 215 j = 109 then joy=1:gosub 206


    schon eine leichte Geschwindigkeitssteigerung bringen.


    Des weiteren ist jedem Schalter des Joysticks ein Bit im Controlportregister zugeordnet, dass bei geschlossenem Schalter gelöscht und bei geöffnetem Schalter gesetzt ist.


    In obigem Codesnippet scheinst du verschiedene Schalterkonstellationen mit gedrücktem Feuerknopf abzufragen, einzig um den Feuerknopfzustand ermitteln zu wollen. Dessen Schalterzustand ist in Bit#4, und du kannst das ganze verkürzen indem du nur dieses eine Bit abfragst:

    Code
    1. 213 j = 255-peek(56320):if jand16 then joy=1:gosub 206
  • Das Out of Memory hast Du gut entlarvt:


    Ich mache goto Sprünge aus Unterprogrammen, die zuvor mit Gosub aufgerufen wurden. Das ist wohl die Schwäche an der es liegt. Mehr als 6 Durchläufe packt er dadurch nicht.

    Ich hab es mir jetzt mal näher angesehen und aus dem Programm aus Post #11 alles entfernt, was nicht den Programmfluss betrifft. Dann hab ich versucht, die Einzelteile nach Aufruftiefe zu sortieren. Heraus kommt dies:


    Hauptprogramm (Schachteltiefe 0):

    Aufgrund der Umsortierung sieht man jetzt, dass z.B. Zeile 183 überflüssig ist, denn damit werden lediglich ein paar Zeilen mit einem Unterprogramm übersprungen. Unterprogramme sollten niemals "an Ort und Stelle" in den Code eingebaut werden - das mag beim Schreiben bequemer sein, macht das Programm aber sofort sehr unübersichtlich. Lieber alle Unterprogramme an den Anfang des Programms stellen und beim Start einmal per GOTO überspringen! :prof:


    Schachteltiefe 1:

    Hier sieht man, dass die Zeilen 206/207 und die ab 400 eigentlich zusammengehören und somit auch direkt hintereinander stehen sollten. Durch eine Invertierung der IF-Bedingung fällt dann auch das GOTO weg.
    Die letzte Zeile (410) enthält eine Endlosschleife. Praktisch ist diese nicht ganz so endlos (siehe unten), aber das sieht bereits nach einem deutlichen Fehler aus.


    Schachteltiefe 2:

    Statt der vielen "goto 324" würde ich jeweils ein "return" bevorzugen, dann weiß man beim Lesen sofort, dass das Unterprogramm verlassen wird.


    Schachteltiefe 3:

    In Zeile 744 verbirgt sich der Fehler; hier wird einfach ins Hauptprogramm zurückgesprungen und es verbleiben bis zu drei Rücksprungadressen auf dem Stack. Wenn Du diese Stelle entsprechend aufräumst, dürfte das Spiel beliebig viele Runden schaffen.


    In Zeile 765 wird mit CLR die "Du kommst aus dem Gefängnis frei"-Karte eingesetzt: CLR löscht nicht nur die Variablen, sondern auch die GOSUB/RETURN-Rücksprungadressen. Damit kann man auch die oben erwähnte Endlosschleife verlassen. Das ist natürlich ein ziemlich dreckiger Hack, in diesem Fall (Neustart des Programms) aber gerade noch in Ordnung. Ein simples RUN hätte es allerdings auch getan. ;)

    Zum Thema Basic-Baukasten:
    Ja ich weiß, es stammt von Evil Microsoft und wer wirklich was vom Coden versteht sieht auch die vielen Schwächen.
    Trotzdem ist das Baukasten-Prinzip hochmotivierend für einfache Spiele und kleine Erfolgserlebnisse, finde ich.

    Es ging mir nicht darum, nur mal wieder etwas gegen das Basic2 sagen zu können ;)
    Ich hab bloß nicht verstanden (und tue es immer noch nicht), was Du in diesem Fall mit "Baukasten-Prinzip" überhaupt meinst...

    Mac meinte wohl das hier:

    Genau.

  • N'Abend Bytebreaker.


    Erstmal Glückwunsch zum Programm :dafuer: .


    War aber schon etwas schwierig durchzusteigen... :whistling: .


    Ein paar Vorschläge:


    Code
    1. 564 bpbak=int((bx-1024)/40)*40+1024+39-160


    würde ich zusammenfassen zu:


    Code
    1. 564 bpbak=int((bx-1024)/40)*40+903


    Hab's mal in einer Schleife getestet: Da ist das ca. 25% schneller.


    und hier:


    Code
    1. 540 if peek(bx+40)=67 then cy=-cy
    2. 555 if peek(bx-40)=67 then cy=abs(cy)
    3. 560 if peek(bx+1)=66 then cx=-cx
    4. 561 if peek(bx-1)=66 then cx=abs(cx)
    5. 562 if peek(bx+1)=96 then goto 730
    6. 563 if peek(bx-1)=96 then goto 735


    stattdessen:


    Code
    1. 535 bb = peek (bx+cx+cy)
    2. 540 if bb=67 then cy=-cy
    3. 560 if bb=66 then cx=-cx
    4. 562 if bb=96 then goto 730/735


    Dann müsste in 730/735 nur noch eine Abfrage (über cx) gemacht werden, in welche Richtung der Ball gerade ging.


    Was mir noch so auffiel:


    - das mit SHIFT-SPACE (96) ist gut :zustimm:
    - du kannst IF-Abfragen auch mit AND/OR etc. verbinden (z. B. Zeilen 283/310: IF PL+160=1944 AND N=1 GOTO 324)
    - statt Variable BPBAK nur BP -> schneller
    - Leerzeichen weg -> schneller
    - statt THEN GOTO nur THEN oder GOTO -> schneller
    - die ganzen PRINT"" könnte man durch PRINT ersetzen bzw. mit den Steuerzeichen machen (überhaupt: Steuerzeichen...)
    - negative Variable (z. B. cx) kann man auch mit cx=-cx wieder umkehren statt mit ABS(cx). ABS macht hauptsächlich Sinn, wenn man das Vorzeichen nicht kennt und nicht haben möchte (Differenz zwischen zwei Zahlen o. ä.). Macht aber geschwindigkeitstechnisch keinen nennenswerten Unterschied.


    :winke:

  • Schönes Spiel. Aber leider kapiere ich den Code nicht so ganz. Irgendwie stehe ich da auf dem Schlauch...
    Was z. B. ist der Unterschied zwischen Zeile 64 und 65?

    Code
    1. 64 IF LEN(B$) <= 5 THEN LN=LEN(B$) - 1 : B$ = LEFT$(B$, LN)
    2. 65 IF LEN(B$) = 6 THEN LN=LEN(B$) - 1 : B$ = LEFT$(B$, LN)

    Und was ist der Sinn von Zeile 74?

    Code
    1. 74 IF INT(VAL(A$)) - INT( INT( VAL(A$) ) / INT(2) ) * INT(2) = 0 THEN 72

    Ich hatte angenommen, daß es in der Routine nur darum geht, die Tastatur auf Werte zwischen '1' und '3' abzutesten, also so etwas wie

    Code
    1. 72 GET A$:IF A$ < "1" OR A$ > "3" THEN 72

    Jetzt raucht mein Kopf. BASIC ist wohl doch nichts für mich...