Hallo Besucher, der Thread wurde 3,3k mal aufgerufen und enthält 27 Antworten

letzter Beitrag von atomcode am

Float in ASM - BASIC-Routinen nutzen?

  • Hallo an alle!


    Ich schreibe gerade zur Übung einen kleinen Krackout-Clone (seinerseits ein Breakout-Clone), bei dem ich die Geschwindigkeit des Balls konstant halten möchte - egal, in welche Richtung er fliegt. Dafür brauche ich Floats, die ich dann runden möchte, um ganzzahlige Werte für (x,y) zu haben. Dazu erstelle ich gerade neue Routinen und nutze dazu die freien Zero-Page-Register FB, FC, FD im Format Lo Hi Fr (Low-Byte, High-Byte für einen 16-Bit-Integer und Fr für den Fractional Part). Es ist allerdings etwas mühselig, diese zu schreiben. Und da habe ich gedacht, dass man ja die eingebauten Funktionen aus dem BASIC-ROM benutzen könnte. Hierbei stellt dich die Frage, wie Floats im BASIC-Interpreter gespeichert werden und wo die Routinen zu z.B. Addition und Subtraktion zu finden sind. Hat jemand davon Ahnung und damit schon einmal gearbeitet? Lohnt sich das überhaupt?

  • In den meisten Spielen wird mit Pseudo-Floats gearbeitet, d.h. 16-Bit-Werte, bei denen ein Byte quasi den Nachkommawert enthält. Bei diversen Projektil-in-freie-Richtung-beweg-Code verwende ich so etwas wie Bresenham. Da wird mit reiner Integer-Arithmetik gearbeitet und erhält trotzdem fast frei drehbare Linien.

  • Hier habe ich es mal vor langer Zeit in cc65 probiert.

    Funktioniert wunderbar.

    Brauchst nur diese asm("ldy #36") Zeilen umsetzen in ASM "ldy #36" für den ACME , mehr nicht. Dies Poke brauchst du nicht.

    Das andere ist nur für den cc65.


    Probier mal die prg aus.


    Meine Werte für die JSR habe ich aus dieser Tabelle:

    https://www.c64-wiki.de/wiki/Flie%C3%9Fkommaarithmetik


    -------------------------------------------



    1. #include
      <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <conio.h>
    5. #include <ctype.h>
    6. #include <peekpoke.h>
    7. unsigned char wert[5];
    8. unsigned char wert1[5];
    9. void real(){ //Die Zahl 0.25 aus dem ROM (58090 bzw. 54024) kommt in den FACI und auf dem Screen
    10. POKE(1,55);
    11. asm("lda #234");
    12. asm("ldy #226");
    13. asm("jsr 48034");
    14. asm("jsr 43708");
    15. asm("rts");
    16. POKE(1,54);
    17. }
    18. void real1(){
    19. POKE(1,55);
    20. asm("lda #2"); //Ausgabe der Integerzahl 548 als Real auf dem Screen über INTFLP > FACMEM > FLOUT
    21. asm("ldy #36");
    22. asm("jsr 45969");
    23. asm("nop");
    24. asm(" ldx #<(_wert)");
    25. asm(" ldy #>(_wert)");
    26. asm("jsr 48078");
    27. asm("nop");
    28. asm("jsr 43708");
    29. asm("rts");
    30. POKE(1,54);
    31. }
    32. void real2(){
    33. POKE(1,55);
    34. asm(" lda #3"); // Die Zahl 1000 nach FAC1 mit INTFLP, Logarithmus von 1000 auf dem Screen
    35. asm(" ldy #232");
    36. asm("jsr 45969");
    37. asm("jsr 47594");
    38. asm(" ldx #<(_wert)");
    39. asm(" ldy #>(_wert)");
    40. asm("jsr 48087");
    41. asm("jsr 43708");
    42. asm(" lda #<(_wert)"); // Logarithmus von 1000 nach FAC1 , EHOCHF rechnet wieder um auf 1000 auf dem Screen
    43. asm(" ldy #>(_wert)");
    44. asm("jsr 48034");
    45. asm("jsr 49133");
    46. asm(" ldx #<(_wert1)");
    47. asm(" ldy #>(_wert1)");
    48. asm("jsr 48087");
    49. asm("jsr 43708");
    50. asm("rts");
    51. POKE(1,54);
    52. }
    53. int main (void)
    54. {
    55. real();
    56. real1();
    57. real2();
    58. cgetc ();
    59. return EXIT_SUCCESS;
    60. }

    ---------------------------------------------------



    Es macht spaß damit zu spielen.

  • Kannste mit dem ACME compilieren. Vorher die Zeilennummern entfernen.

    -------------------------------------------------

    1. !to "real.prg",cbm
    2. *= $c000
    3. ; starten mit sys 49152
    4. start
    5. lda #<-30000
    6. sta 679
    7. ldy #>-30000
    8. sty 680
    9. jsr intflp
    10. jsr fac_mem1
    11. jsr flpout
    12. jsr mem1_fac
    13. jsr adr_pi
    14. jsr m_mult
    15. jsr flpout
    16. rts
    17. ;-----------------------------
    18. adr_1
    19. lda #$bc
    20. ldy #$b9
    21. rts
    22. adr_05
    23. lda #$11
    24. ldy #$bf
    25. rts
    26. adr_025
    27. lda #$ea
    28. ldy #$e2
    29. rts
    30. adr_pi
    31. lda #$a8;
    32. ldy #$ae
    33. rts
    34. adr_pi12
    35. lda #$e0
    36. ldy #$e2
    37. rts
    38. adr_2pi
    39. lda #$09
    40. ldy #$e3
    41. rts
    42. adr_sqr2
    43. lda #$db
    44. ldy #$b9
    45. rts
    46. adr_m05
    47. lda #$e0
    48. ldy #$b9
    49. rts
    50. adr_10
    51. lda #$f9
    52. ldy #$ba
    53. rts
    54. ;---------------------------------
    55. fac_add05
    56. jsr 47177
    57. rts
    58. fac_fmal10
    59. jsr 47842
    60. rts
    61. fac_fdiv10
    62. jsr 47870
    63. rts
    64. fac_fminus
    65. jsr 49076
    66. rts
    67. fac_ab
    68. jsr 48216
    69. rts
    70. fac_add
    71. jsr 47210
    72. rts
    73. m_add
    74. jsr 47207
    75. rts
    76. fac_sub
    77. jsr 47187
    78. rts
    79. m_sub
    80. jsr 47184
    81. rts
    82. fac_mult
    83. jsr 47659
    84. rts
    85. m_mult
    86. jsr 47656
    87. rts
    88. fac_div
    89. jsr 47884
    90. rts
    91. m_div
    92. jsr 47887
    93. rts
    94. fac_sqr
    95. jsr 47887
    96. rts
    97. fac_pot
    98. jsr 49019
    99. rts
    100. m_pot
    101. jsr 49016
    102. rts
    103. fac_logn
    104. jsr 47594
    105. rts
    106. fac_ehoch
    107. jsr 49133
    108. rts
    109. fac_sin
    110. jsr 57963
    111. rts
    112. fac_cosin
    113. jsr 57956
    114. rts
    115. fac_tan
    116. jsr 58036
    117. rts
    118. fac_arct
    119. jsr 58126
    120. rts
    121. facmem_cmp
    122. jsr 48219
    123. rts
    124. fac_sgn
    125. jsr 48171
    126. rts
    127. fac_zufall
    128. jsr 57495
    129. rts
    130. ;---------------------------------------
    131. mem_fac
    132. jsr 48034
    133. rts
    134. mem_fac2
    135. jsr 47756
    136. rts
    137. ;---------------------------------------
    138. mem1_fac
    139. lda #<mem_1
    140. ldy #>mem_1
    141. jsr 48034
    142. rts
    143. mem2_fac
    144. lda #<mem_2
    145. ldy #>mem_2
    146. jsr 48034
    147. rts
    148. mem3_fac
    149. lda #<mem_3
    150. ldy #>mem_3
    151. jsr 48034
    152. rts
    153. mem1_fac2
    154. lda #<mem_1
    155. ldy #>mem_1
    156. jsr 47756
    157. rts
    158. mem2_fac2
    159. lda #<mem_2
    160. ldy #>mem_2
    161. jsr 47756
    162. rts
    163. mem3_fac2
    164. lda #<mem_3
    165. ldy #>mem_3
    166. jsr 47756
    167. rts
    168. ;----------------------------
    169. fac_mem1
    170. ldx #<mem_1
    171. ldy #>mem_1
    172. jsr 48087
    173. rts
    174. fac_mem2
    175. ldx #<mem_2
    176. ldy #>mem_2
    177. jsr 48087
    178. rts
    179. fac_mem3
    180. ldx #<mem_3
    181. ldy #>mem_3
    182. jsr 48087
    183. rts
    184. fac12
    185. jsr 48143
    186. rts
    187. fac21
    188. jsr 48124
    189. rts
    190. ;-------------------------------
    191. memory1
    192. lda #<mem_1
    193. ldy #>mem_1
    194. rts
    195. memory2
    196. lda #<mem_2
    197. ldy #>mem_2
    198. rts
    199. memory3
    200. lda #<mem_3
    201. ldy #>mem_3
    202. rts
    203. ;------------------------------
    204. flpout
    205. jsr 43708
    206. rts
    207. flpoutx
    208. jsr 48599
    209. rts
    210. ;---------------------------------
    211. flpint
    212. jsr 47095
    213. sty 679
    214. sta 680
    215. rts
    216. intflp
    217. lda 680
    218. ldy 679
    219. jsr 45969
    220. rts
    221. mem_1: !byte 0,0,0,0,0
    222. mem_2: !byte 0,0,0,0,0
    223. mem_3: !byte 0,0,0,0,0

    ----------------------------------------------------

  • Hierbei stellt dich die Frage, wie Floats im BASIC-Interpreter gespeichert werden und wo die Routinen zu z.B. Addition und Subtraktion zu finden sind.

    Kuckstu hier:

    https://sourceforge.net/p/acme…trunk/ACME_Lib/cbm/flpt.a

    https://sourceforge.net/p/acme…runk/ACME_Lib/cbm/mflpt.a

    https://sourceforge.net/p/acme…/ACME_Lib/cbm/c64/float.a


    Das geschilderte Problem würde ich allerdings eher mit einer vorberechneten Sinustabelle zu lösen versuchen, nicht mit echten Floats. Also man unterteilt den Vollkreis in 256 mögliche Richtungen für den Ball und holt für jede Richtung einfach Delta-X und Delta-Y aus der Tabelle, gerne auch als Fixedpoint...

  • Ich habe vor kurzem mal was aehnliches programmiert (mit sich beschleunigenden und abbremsenden Figuren) und da habe ich es so gemacht wie Endurion beschreibt. Also einen 16-Bit-Wert nehmen fuer die Positionen und die Berechnungen, aber beim Anzeigen usw. wird dann durch 16 geteilt, und bei der Kollision mit Tiles, die in diesem Falle aus 2x2 Char-Bloecken bestehen, also 16x16 Pixel, muss ja dann nochmal durch 16 geteilt werden, was im Endeffekt einer Division durch 256 entspricht und somit kann man sich diesen Schritt dann einfach sparen indem man direkt das High-Byte verwendet und das Low-Byte ignoriert. Je nachdem wie hoch die Nachkomma-Aufloesung sein muss waere natuerlich dieser Schritt auch direkt denkbar fuer die Anzeige, aber mir hat da eine Aufloesung von 16 Sub-Schritten gereicht.

  • Dein Spiel mit den Float lohnt sich schon.

    Da lernst du viel kennen. Und macht auch Spaß wenn es funktioniert.


    Lass dich nicht durch die einfachen Schemas hier durcheinander bringen.

    Die Billigschema sind was für Progger , die die ungenauigkeiten des Spiels nicht im Kauf nehmen.


    ACME bietet auch ein schöne Float-lib.




    Damit werden die Floatergebnisse auf den Screen ausgedruck

    flpout = 43708


    jsr 43708

  • Lass dich nicht durch die einfachen Schemas hier durcheinander bringen.

    Die Billigschema sind was für Progger , die die ungenauigkeiten des Spiels nicht im Kauf nehmen.

    Hast Du schonmal drüber nachgedacht, Dich von "spacer" in "sülzer" umzubenennen?

  • In den meisten Spielen wird mit Pseudo-Floats gearbeitet, d.h. 16-Bit-Werte, bei denen ein Byte quasi den Nachkommawert enthält. Bei diversen Projektil-in-freie-Richtung-beweg-Code verwende ich so etwas wie Bresenham. Da wird mit reiner Integer-Arithmetik gearbeitet und erhält trotzdem fast frei drehbare Linien.

    Dem kann ich nur beipflichten. Mit Fließkommazahlen für solche Zwecke zu rechnen ist schlichtweg Overkill. In BASIC macht man man das nur, weil es der Interpreter implizit immer so macht. Von dieser Vorstellung, dass man nur mit Fließkommazahlen rechnen kann, sollte man sich - speziell bei Spielprogrammierung - schnell verabschieden. Ja, im wissenschaftlichen Bereich braucht man, wenn man nicht abschätzen kann, in welcher Größenordnung die Werte haben werden bzw. halbwegs genau mit diesen Werten weiter rechnen möchte. Aber bei Spielen weiß man ziemlich genau, in welchem Bereich sich die Werte bewegen und kennt die Randbedingungen (etwas von der Grafik), die ein Weiterrechnen nicht mehr sinnvoll machen. Man hat in der Regel einen sehr kontrollierten Rechenpfad umd Wertebereich.
    Die Mühen der anderen Poster nicht schmälern zu wollen, aber für Spiele (und viel Anwendungen) zahlt sich der Aufwand in vielerlei Hinsicht nicht aus. ;)

  • -----------------------------

    Lohnt sich das überhaupt?

    ----------------------------

    Diese Frage zu stellen für den C64/VIC20 ist ein Killer für den Spaßfaktor etwas zu Programmieren.


    Es Lohnt sich überhaupt nicht etwas für diese Art Computer etwas zu programmieren , wenn einer meint er muss etwas schaffen was Punktuell

    präzise funktionieren soll. Dafür ist die Kaffeemühle C64/VIC20 nicht geeignet und soll sich schnell vom C64/VIC20 verabschieben.

    Und von Grafik kann man nicht reden von diesen 40x25 oder 320x200 die da ausgespuckt werden.


    Verabschiedet euch mal von diesen Wissenschaftlichen kram und Messungen , Randbedingungen , Overkill usw. bei diesem Gerät.


    Es ist schlichtweg ein einfacher müder Computer aus den 8chzigern , der für uns eine Spielekiste ist und nur Freude macht.

  • Lohnt sich das überhaupt?

    Das soll jeder für sich selbst entscheiden. So wie du vielleicht Float als einen Riesenspaß ansiehst, musst du auch die Meinung akzeptieren können, dass andere - nicht gerade unerfahrene Programmierer - eine Empfehlung abgeben, die auf Floats verzichtet. Da muss keiner den anderen zu überzeugen versuchen. Die Penetranz gewisser Postings lassen zwar daran zweifeln, aber was soll's. Ich kann damit leben und muss hier nicht um den Kaisers Bart herumstreiten.

    Wenn jemand, wie man in Österreich sagt, mit der Kirche ums Kreuz gehen will, dann bitte. So lustig und spaßig ist das mit den Floats auch wieder nicht, das kann man einem vielleicht am Anfang stehenden Programmierer ruhig sagen. Nur etwas Vorgefertigtes aus dem Forum abzukopieren, wird bei weitem nicht so erfüllend sein, wenn sich jemand selbst daran macht das zu erforschen. Dazu gehört aber auch einmal Floats generell zu verstehen ... :D

  • In den meisten Spielen wird mit Pseudo-Floats gearbeitet, d.h. 16-Bit-Werte, bei denen ein Byte quasi den Nachkommawert enthält.

    Ich frage mich immer, wo der Begriff "Pseudo-Floatingpoint" herkommt. Ist das nicht naheliegenderweise einfach "Fixpoint"? Aber ansonsten: ja, das ist für nahezu alle Anwendungen der richtige Ansatz. Ich arbeite beruflich an Signalprocessing-Software, und wenn diese auf Prozessoren ohne Floatingpoint-Hardwareunterstützung laufen soll, wird auch heute Fixpoint verwendet. Ist schneller und man kann sich ja selber aussuchen, wie genau es sein soll (16 Bit sollten für die Mehrzahl an Anwendungsfällen auf dem Cevi ausreichen).

  • Wo ist das Problem, zusaetzlich zur Erklaerung einen passenden Begriff zu verwenden? Muss man Anfaenger pauschal als "zu doof" einordnen, als dass man ihnen gleich auch den Namen des Verfahrens nennen koennte?

  • Z.B. in Wikipedia

  • Diese beiden Begriffe oben erscheinen nirgends im Duden oder in einem anderen Nachschlagewerk als anerkannter Begriff.


    Wo soll er als Anfänger nachlesen...?

    Z.B. in Wikipedia

    ...und schon wieder hat spacer es geschafft, jemand anderen für sich googeln zu lassen. :D

  • bei dem ich die Geschwindigkeit des Balls konstant halten möchte - egal, in welche Richtung er fliegt. Dafür brauche ich Floats

    Ich arbeite zurzeit hauptsächlich waagerecht und senkrecht, aber aus Interesse hatte ich mich gestern mal damit befasst und mit Hilfe von BASIC V2 experimentiert, bewusst, ohne irgendwo nachzuschlagen. Gab's damals ja schließlich auch nicht! :alt:


    Das einzige Problem, wenn man heute noch gern, wie früher, selbst hinter diversen Zusammenhängen steigen möchte, ist, dass man bei etwaiger Unterbreitung seiner Erkenntnisse im Indernet Gefahr läuft, dass man zur Sau gemacht wird, weil ja alles schon bekannt sei, und zwar viel genauer und besser, und man es nur noch nachschlagen müsste.

    Zum Teil ist das ja auch richtig, aber wenn man erst dann davon Gebrauch macht, wenn man selbst nicht auf eine optimale Lösung gekommen ist, hat es eben den Vorteil, den JeeK bereits ansprach, nämlich das positive Gefühl, das Erfolgserlebnis, die elementaren Erkenntnisse, auf die man aufbauen kann, die Sicherheit, den Durchblick, der Lerneffekt, den Spaß an der Freude. Also ...


    1. Wenn ich die Fließkommazahlen benutze, ist eine Routine, die einen Ball (ein Char) mit ständigem Richtungswechsel, aber ohne Geschwindigkeitskorrektur, per Zufall über den Bildschirm sausen lässt, sehr einfach aufgebaut (4 Zeilen). Als Steigung hatte ich immer Werte <=1, indem die Matrix bei größeren Steigungen gedreht behandelt wird.


    2. Im nächsten Schritt hatte ich echte Fließkommazahlen ausgeschlossen. Die Berechnung beschränkt sich dann neben den unvermeidbaren Int-Additionen (während der Bewegung) auf nur eine Int-Division pro Flugrichtung (bzw. pro Linie), und zwar 16bit (mit low byte=0) dividiert durch 8bit = 8bit Ergebnis, und das geht so schnell, dass ich dafür keine Delta-Tabellen investieren würde. Das ist die Zahl die ständig zu einem 8bit-Counter addiert werden muss, damit bei Überlauf die entsprechende Richtungskomponente inkrementiert wird, während die andere Richtungskomponente bei jedem Schritt inkrementiert wird. Man könnte sagen, das Summanden-Byte stellt den Nachkommaanteil der Steigung dar, die stets kleiner als 0 ist. Deswegen braucht man auch kein Vorkomma-Byte. Das Counter-Byte stellt den Nachkommaanteil der theoretischen Position in X- oder Y-Richtung dar. Die Linien sehen genauso gut aus wie mit der echten Fließkommaberechnung (Linker Screenshot).


    3. Das pythagoräische Geschwindigkeitsproblem. Bei einer schrägen Bewegung ist der Ball schneller als waagerecht oder senkrecht, bis zu 41%. Das kann man zwar leicht ausgleichen durch zwei Transformationstabellen, denen eine feste Hypotenuse von 1 bzw. 256 zugrunde liegt, aber damit stellt sich leider eine unschöne Eigenschaft ein, die ich schon in manchen Spielen beobachtet hatte: Eine unruhige oder zitternde Flugbewegung bei bestimmten Winkeln so zwischen 30 und 45°. Der Grund ist die Stauchung der Ankathete, die vorher 1 war. Dadurch entstehen hässliche Ecken in der Linie (Rechter Screenshot). Das kann man beseitigen, indem man nicht die Katheten bei größerem Winkel staucht, sondern bei kleinerem Winkel dehnt. Allerdings kommt es dann bei kleineren Winkeln zu Lücken in der Linie, die sich für den Ball wie Micro-Sprünge bemerkbar machen.


    Fazit: Ich denke, dass ich die Geschwindigkeitskorrektur vielleicht nicht in die Linien-Berechnung einfließen lassen würde, sondern in den Zeitpunkt des Plots. Deswegen kann man weiterhin wohl einen festen Raster-Interrupt nutzen. Man muss dann nur berechnen, ob der nächste Plot schon ausgeführt werden soll. Verpasste Plots nachholen braucht man natürlich nicht; die werden übersprungen. Auf diese Weise kann man auch leichter die absolute Ballgeschwindigkeit einfließen lassen.