Metropoli BBS
VIEWER: anivga.pas MODE: TEXT (CP437)
{$A+,B-,D+,E-,O-,R-,S-,V-}
{$M 16384,0,655360}

{Programmtool zur Realisierung schneller Animationen auf der VGA-Grafik-    }
{karte von: Kai Rohrbacher, 1988-1991, Turbo-Pascal 6.0                     }

{ Features:                                                                 }

{ - flickerfreie Animation durch page-flipping, Auswertung des vertikalen   }
{   Retracesignals und Verwendung eines speziellen VGA-256-Farbengrafikmodus}
{ - Spritebewegung pixelweise (und nicht nur byteweise) moeglich            }
{ - beliebiges Hintergrundbild, vor der die Animation geschieht             }
{ - volle Unterstuetzung der 256-Farbmoeglichkeiten der VGA-Karte           }
{ - verschiedene Spritedarstellungsmoeglichkeiten:                          }
{   - Sprites koennen pixelweise als durchsichtig gegenueber dem Hintergrund}
{     deklariert werden                                                     }
{   - Sprites koennen ihre Farbe in Abhaengigkeit ihres momentanen Hinter-  }
{     grundes veraendern ("Schattenfunktion")                               }
{ - Routine zur exakten Feststellung der Kollision zweier Sprites           }
{ - Sprites werden beim Verschwinden an einer der Bildschirmgrenzen korrekt }
{   abgeschnitten                                                           }
{ - Verwaltung von bis zu 1000 verschiedenen Sprites                        }
{ - gleichzeitige Darstellung von bis zu 500 Sprites                        }
{ - maximale Spritegroesse 64k                                              }
{ - maximaler Umfang aller Sprites zusammen nur durch Hauptspeicher begrenzt}
{ - arbeitet mit virtuellen Koordinaten im Bereich -16000..+16000, daher    }
{   einfaches horizontales und vertikales Scrolling moeglich                }
{ - Scrollbares Hintergrundbild ebenfalls unterstuetzt                      }
{ - viele unterstuetzende Routinen: zeichnen von Linien (mit eingebautem    }
{   Clipping-Algorithmus), Punkten und Grafik-Text (dto.), automatische Ver-}
{   waltungs des Heaps zum Speichern/Laden von Sprites, Hintergrundbildern, }
{   Aendern von Spritedarstellungsmodi waehrend der Laufzeit, Geschwindig-  }
{   keitsanpassung an unterschiedlich schnelle Rechner, ...                 }


UNIT ANIVGA;
INTERFACE

USES CRT,DOS;

CONST NMAX=499;
      XMAX=319;
      YMAX=199;
      LoadMAX=1000;  {max. Anzahl an gleichzeitig geladenenen Sprites}
      LINESIZE=(XMAX+1) DIV 4;    {Groesse einer Zeile=80 Bytes}
      PAGESIZE=(YMAX+1)*LINESIZE; {200 Zeilen zu je 320/4 Bytes}
      BACKGNDPAGE=2;
      SCROLLPAGE=3;

      STATIC=0;      {Konstanten fuer Hintergrundart}
      SCROLLING=1;

      MaxTiles=10000;  {max. Anzahl an Hintergrund-Kacheln}
      StartVirtualX:INTEGER=0;  {obere linke Bildschirmecke}
      StartVirtualY:INTEGER=0;

      {unterstuetzte Darstellungsmodi der Sprites: }
      Display_NORMAL=0;   {normal  : durchsichtig fuer Farbe 0}
      Display_FAST  =1;   {schnell : keine Hintergrundberuecksichtigung}
      Display_SHADOW=2;   {Schatten: Farbumsetzung anhand des Hintergrundes}
      Display_UNKNOWN=255;{Fehlerwert}

      {Fehlercodes des Animationspaketes: }
      Err_None=0;
      Err_NotEnoughMemory=1;
      Err_FileIO=2;
      Err_InvalidSpriteNumber=3;
      Err_NoSprite=4;
      Err_InvalidPageNumber=5;
      Err_NoVGA=6;
      Err_NoPicture=7;
      Err_InvalidPercentage=8;
      Err_NoTile=9;
      Err_InvalidTileNumber=10;
      Err_InvalidCoordinates=11;
      Err_BackgroundToBig=12;
      Err_InvalidMode=13;
      Err_InvalidSpriteLoadNumber=3;

TYPE  FontChar=ARRAY[0..5] OF BYTE;
      Font=ARRAY[0..255] OF Fontchar;
      FontOrient=(horizontal,vertical);    {moegliche Textausgaberichtungen}
CONST GraphTextOrientation:FontOrient=horizontal; {aktuelle Ausgaberichtung}
      GraphTextColor:BYTE=white;           {aktuelle Textausgabefarben}
      GraphTextBackground:BYTE=white;
      FontHeight=6;
      FontWidth=6;

TYPE Table=ARRAY[0..NMAX] OF INTEGER;
     ColorTable=ARRAY[0..255] OF BYTE;


VAR Error:BYTE; {globale Fehlervariable}
    SpriteN:Table;
    SpriteX:Table;
    SpriteY:Table;
    NextSprite:ARRAY[0..LoadMAX] OF WORD;
    SPRITEAD:ARRAY[0..LoadMAX] OF WORD;
    PAGE,PAGEADR,SCROLLADR,BACKGNDADR:WORD;
    Color:BYTE;           {Zeichenfarbe fuer Linien}
    was_cut:BOOLEAN;      {TRUE/FALSE, falls "GetImage" clippen musste  }
    left_cut,             {Var., die durch "GetImage" gesetzt werden und}
    right_cut,            {bei "was_cut"=TRUE darueber Auskunft geben,  }
    top_cut,              {wo und wieviel des Bildes abgeschnitten wer- }
    bottom_cut:WORD;      {den musste                                   }

    BackgroundMode:BYTE;
    BackTile:ARRAY[0..MaxTiles-1] OF BYTE;  {Kachelnspeicher}
    XTiles,YTiles:INTEGER;                  {Breite,Hoehe des def. Bereiches}
    BackX1,BackY1,BackX2,BackY2:INTEGER;    {Koordinaten des def. Bereiches }


 PROCEDURE ShadowTab;
 PROCEDURE SetShadowTab(brightness:BYTE);
 PROCEDURE SetCycleTime(milliseconds:WORD);
 PROCEDURE SetSpriteCycle(nr,len:WORD);
 FUNCTION GetImage(x1,y1,x2,y2:INTEGER; pa:BYTE):POINTER;
 PROCEDURE PutImage(x,y:INTEGER; p:POINTER; pa:BYTE);
 PROCEDURE InitGraph;
 PROCEDURE Screen(pa:BYTE);
 PROCEDURE Line(x1,y1,x2,y2:INTEGER; pa:BYTE);
 PROCEDURE BackgroundLine(x1,y1,x2,y2:INTEGER);
 FUNCTION GetPixel(x,y:INTEGER):BYTE;
 FUNCTION BackgroundGetPixel(x,y:INTEGER):BYTE;
 FUNCTION PageGetPixel(x,y:INTEGER; pa:BYTE):BYTE;
 PROCEDURE PutPixel(x,y:INTEGER; color:Byte);
 PROCEDURE BackgroundPutPixel(x,y:INTEGER; color:Byte);
 PROCEDURE PagePutPixel(x,y:INTEGER; color,pa:Byte);
 PROCEDURE OutTextXY(x,y:INTEGER; pa:BYTE; s:STRING);
 PROCEDURE BackgroundOutTextXY(x,y:INTEGER; s:STRING);
 FUNCTION Hitdetect(s1,s2:INTEGER):BOOLEAN;
 PROCEDURE Animate;
 FUNCTION LoadSprite(name:String; number:WORD):WORD;
 FUNCTION LoadTile(name:STRING; number:BYTE):WORD;
 PROCEDURE SetBackgroundScrollRange(x1,y1,x2,y2:INTEGER);
 PROCEDURE SetBackgroundMode(mode:BYTE);
 PROCEDURE PutTile(x,y:INTEGER; TileNr:BYTE);
 PROCEDURE SetModeByte(Sp:WORD; M:BYTE);
 FUNCTION GetModeByte(Sp:WORD):BYTE;
 PROCEDURE FillPage(pa,color:Byte);
 PROCEDURE FillBackground(color:BYTE);
 PROCEDURE GetBackgroundFromPage(pa:Byte);
 PROCEDURE WritePage(name:STRING; pa:BYTE);
 PROCEDURE LoadPage(name:STRING; pa:BYTE);
 PROCEDURE WriteBackgroundPage(name:STRING);
 PROCEDURE LoadBackgroundPage(name:STRING);
 PROCEDURE InitRoutines;
 PROCEDURE CloseRoutines;
 FUNCTION GetErrorMessage:STRING;


{--------------------------------------------------------------------------}

IMPLEMENTATION

CONST StartIndex=0;
      EndIndex=StartIndex+3;
      {Offsetadressen der Grafikseiten (in Segment $A000):}
      Offset_Adr:Array[StartIndex..EndIndex] OF WORD=($0000,$3E80,$7D00,$BB80);
      {Segmentadressen der Grafikseiten (bei Offset = 0) :}
      Segment_Adr:ARRAY[StartIndex..EndIndex] OF WORD=($A000,$A3E8,$A7D0,$ABB8);

      {Sprite(header)aufbau:

      0..1   DW Plane_0_Daten
      2..3   DW Plane_1_Daten
      4..5   DW Plane_2_Daten
      6..7   DW Plane_3_Daten
      8..9   DW Breite (in 4er-Gruppen)
      10..11 DW Hoehe in Zeilen
      12..15 DB 1,2,4,8      ; Translate-Tabelle fuer Port-Ansteuerung
      16..17 DW SpriteLength ; Laenge der Spritedatei
                             ; jetzt fuer temporaere Variablen reservierter
                             ; Bereich:
      18..19 DW ?            ; licutoff_      | hit1xfirst
      20..21 DW ?            ; zeilenadr      | hit1yfirst
      22..23 DW ?            ; bildx          | hit2xfirst
      24..25 DW ?            ; yoffset_       | hit2yfirst
      26..27 DW ?            ; end_min_start  | ueberlappx_1
      28..29 DW ?            ;                | ueberlappy_1
      30..31 DW ?            ;                | x1
      32..33 DW ?            ;                | x2
      34..35 DW ?            ;                | y1
      36..37 DW ?            ;                | y2
      38..39 DB 'K','R'      ; Kennung als Sprite
      40     DB 1            ; Versionsnummer
      41     DB 0            ; Modusnummer fuer Sprite
      42..43 DW linke_Begrenzungen
      44..45 DW rechte_Begrenzungen
      46..47 DW obere_Begrenzungen
      48..49 DW untere_Begrenzungen
      50..?? DB Daten

      zum Bsp.:  xxrxxxxx, mit: r=rot=40, g=gruen=45, b=blau=35, x=weiss=30
                 xrgrxxxx
                 rbgbrxxx
      }

      {Adressen von wichtigen Werten innerhalb des Spriteheaders:}
      Left=42;
      Right=44;
      Top=46;
      Bottom=48;
      Breite=8;
      Hoehe=10;
      Translate=12;
      SpriteLength=16;
      Kennung=38;
      Version=40;
      Modus=41;

      {Adressen der temporaeren Variablen fuer die Zeichenroutinen:}
      licutoff_=18;
      zeilenadr=20;
      bildx=22;
      yoffset_=24;
      end_min_start=26;

      {Adressen der temporaeren Variablen fuer die Zeichenroutinen:}
      hit1xfirst=18;
      hit1yfirst=20;
      hit2xfirst=22;
      hit2yfirst=24;
      ueberlappx_1=26;
      ueberlappy_1=28;
      x1=30;
      x2=32;
      y1=34;
      y2=36;

      TranslateTab:ARRAY[0..3] OF BYTE=(1,2,4,8); {Fuer Maskenadressierung}
      PICHeader:STRING[3]='PIC'; {Kennung in Bilderdateien}

TYPE SpriteHeader= RECORD
                    Zeiger_auf_Plane:Array[0..3] OF Word;
                    Breite_in_4er_Gruppen:WORD;
                    Hoehe_in_Zeilen:WORD;
                    Translate:Array[1..4] OF Byte;
                    SpriteLength:WORD;
                    Dummy:Array[1..10] OF Word;
                    Kennung:ARRAY[1..2] OF CHAR;
                    Version:BYTE;
                    Modus:BYTE;
                    ZeigerL,ZeigerR,ZeigerO,ZeigerU:Word;
                   END;

CONST FontMask:ARRAY[0..7] OF BYTE=($80,$40,$20,$10,8,4,2,1);
      FontData: Font=(                   {verwendeter Zeichensatz:  }
      (  0,  0,  0,  0,  0,  0), {#0}    {selbstgestrickter 6x6 Font}
      (  0,216,  0,248,112,  0), {#1}
      (248,168,248,136,216,248), {#2}
      (  0, 80,248,248,112, 32), {#3}
      (  0, 32,112,248,112, 32), {#4}
      ( 32,112,216,248, 32,112), {#5}
      ( 32,112,248,248, 32,112), {#6}
      (  0, 32,216,216, 32,  0), {#7}
      (248,216,168,168,216,248), {#8}
      (  0,112,200,152,112,  0), {#9}
      (248,136,168,168,136,248), {#10}
      ( 56, 24, 32,112,136,112), {#11}
      (112,136,112, 32,248, 32), {#12}
      ( 56, 40, 32, 32,224,224), {#13}
      (  0,120, 72,120, 72,216), {#14}
      (  0, 32,168, 80,168, 32), {#15}
      (  0,128,224,248,224,128), {#16}
      (  0,  8, 56,248, 56,  8), {#17}
      ( 32,112,168,168,112, 32), {#18}
      (  0,216,216,216,  0,216), {#19}
      (  0,120,168,104, 40, 40), {#20}
      ( 24, 32, 16, 40,144, 96), {#21}
      (  0,  0,  0,  0,248,248), {#22}
      ( 32,112, 32,112, 32,248), {#23}
      (  0, 32,112,248, 32, 32), {#24}
      (  0, 32, 32,248,112, 32), {#25}
      (  0, 32, 16,248, 16, 32), {#26}
      (  0, 32, 64,248, 64, 32), {#27}
      (  0,  0,192,248,  0,  0), {#28}
      (  0,  0, 80,248, 80,  0), {#29}
      (  0,  0,  0, 32,112,248), {#30}
      (  0,  0,248,112, 32,  0), {#31}
      (  0,  0,  0,  0,  0,  0), { }
      (  0, 48, 48, 48,  0, 48), {!}
      (  0, 80, 80,  0,  0,  0), {"}
      (  0, 80,248, 80,248, 80), {#}
      ( 32,120,160,112, 40,240), { $}
      (  0,200, 16, 32, 64,152), {%}
      (  0,112,216,112,152,104), {&}
      (  0, 16, 32,  0,  0,  0), {'}
      (  0,112,192,192,192,112), {(}
      (  0,224, 48, 48, 48,224), {)}
      (  0, 80, 32,248, 32, 80), {*}
      (  0,  0, 32,248, 32,  0), {+}
      (  0,  0,  0, 32, 32, 64), {,}
      (  0,  0,  0,248,  0,  0), {-}
      (  0,  0,  0,  0, 48,  0), {.}
      (  4,  8, 16, 32, 64,128), {/}
      (  0,112,152,168,200,112), {0}
      (  0, 48,112, 48, 48,120), {1}
      (  0,240, 24,112,192,248), {2}
      (  0,240, 24,112, 24,240), {3}
      (  0,192,208,248, 48, 48), {4}
      (  0,248,192,240, 24,240), {5}
      (  0,248,128,248,136,248), {6}
      (  0,248, 24, 48, 96, 96), {7}
      (  0,112,216,112,216,112), {8}
      (  0,112,136,120,  8,112), {9}
      (  0,  0, 32,  0, 32,  0), {:}
      (  0,  0, 32,  0, 32, 64), {;}
      (  0, 24, 48, 96, 48, 24), {<}
      (  0,  0,248,  0,248,  0), {=}
      (  0, 96, 48, 24, 48, 96), {>}
      (112,136, 16, 32,  0, 32), {?}
      (  0,112,136,184,128,120), {@}
      (  0,112,200,248,200,200), {A}
      (  0,240,200,240,200,240), {B}
      (  0,120,192,192,192,120), {C}
      (  0,240,216,200,216,240), {D}
      (  0,248,192,240,192,248), {E}
      (  0,248,192,240,192,192), {F}
      (  0,120,192,216,200,120), {G}
      (  0,200,200,248,200,200), {H}
      (  0,120, 48, 48, 48,120), {I}
      (  0,248,  8,  8,200,112), {J}
      (  0,200,208,224,208,200), {K}
      (  0,192,192,192,192,248), {L}
      (  0,136,216,168,136,136), {M}
      (  0,136,200,168,152,136), {N}
      (  0,112,200,200,200,112), {O}
      (  0,240,200,240,192,192), {P}
      (  0, 96,208,208,208,104), {Q}
      (  0,240,136,240,208,200), {R}
      (  0,248,192,248, 24,248), {S}
      (  0,248, 96, 96, 96, 96), {T}
      (  0,200,200,200,200,248), {U}
      (  0,200,200,200,200, 48), {V}
      (  0,136,136,168,248, 80), {W}
      (  0,136,216,112,216,136), {X}
      (  0,200,200,112, 48, 48), {Y}
      (  0,248, 24,112,192,248), {Z}
      (  0,120, 96, 96, 96,120), {[}
      (128, 64, 32, 16,  8,  4), {\}
      (  0,120, 24, 24, 24,120), {]}
      ( 32, 80,136,  0,  0,  0), {^}
      (  0,  0,  0,  0,  0,248), {_}
      ( 64, 32,  0,  0,  0,  0), {`}
      (  0,  0,112,200,200,120), {a}
      (  0,128,240,136,136,240), {b}
      (  0,  0,120,192,192,120), {c}
      (  0,  8,120,136,136,120), {d}
      (  0,  0,112,248,128,112), {e}
      (  0, 24, 32,120, 32, 32), {f}
      (  0,112,136,120,  8,112), {g}
      (  0,192,240,200,200,200), {h}
      ( 48,  0, 48, 48, 48, 48), {i}
      ( 24,  0, 24, 24,216,112), {j}
      (  0,192,208,224,216,216), {k}
      (  0, 96, 96, 96, 96, 56), {l}
      (  0,  0,208,248,168,136), {m}
      (  0,  0,240,200,200,200), {n}
      (  0,  0,112,200,200,112), {o}
      (  0,  0,240,200,240,192), {p}
      (  0,  0,112,152,120, 24), {q}
      (  0,  0,176,104, 96, 96), {r}
      (  0, 56, 64, 48,136,112), {s}
      (  0, 96,248, 96,104, 48), {t}
      (  0,  0,200,200,200,120), {u}
      (  0,  0,200,200,200,112), {v}
      (  0,  0,136,168,168,112), {w}
      (  0,  0,216, 96, 48,216), {x}
      (  0,  0,200,248,  8,112), {y}
      (  0,  0,240, 48,192,248), {z}
      (  0, 56, 96,192, 96, 56),(*{*)
      (  0, 16, 16,  0, 16, 16), {|}
      (  0,224, 48, 24, 48,224),(*}*)
      (  0,104,144,  0,  0,  0), {~}
      (  0, 32, 80,136,248,  0), {#127}
      (112,200,128,200,112,192), {#128}
      (  0,200,  0,200,200,120), {#129}
      ( 24, 32,112,248,128,112), {#130}
      ( 16, 40,  0,120,196,124), {#131}
      (104,  0,112,200,200,120), {#132}
      ( 48,  8,112,136,136,120), {#133}
      ( 16, 40, 16,112,200,120), {#134}
      (  0,120,192,120, 16, 96), {#135}
      (112,  0,112,248,192,112), {#136}
      ( 80,  0,112,248,128,112), {#137}
      ( 48,  8,112,248,192,112), {#138}
      (104,  0, 48, 48, 48, 48), {#139}
      ( 48, 72,  0, 48, 48, 48), {#140}
      ( 96, 16,  0, 48, 48, 48), {#141}
      (200,  0,112,200,248,200), {#142}
      ( 48,  0,112,200,248,200), {#143}
      (112,248,192,240,192,248), {#144}
      (  0,208, 40,112,160, 88), {#145}
      (  0, 56, 80,248,144,152), {#146}
      ( 32, 80,  0,112,200,112), {#147}
      ( 80,  0,112,200,200,112), {#148}
      ( 96, 16,  0,112,200,112), {#149}
      ( 32, 80,  0,200,200,120), {#150}
      ( 96, 16,  0,200,200,120), {#151}
      ( 80,  0,200,248,  8,112), {#152}
      ( 80,  0,112,200,200,112), {#153}
      (200,  0,200,200,200,248), {#154}
      ( 16,120,128,128,120, 16), {#155}
      ( 48, 72,224, 64,136,248), {#156}
      (216, 32,248, 32,248, 32), {#157}
      (192,160,208,184,144,152), {#158}
      ( 48, 40, 96, 48,160, 96), {#159}
      ( 48, 64,  0,112,136,120), {#160}
      ( 48, 64,  0, 32, 32, 32), {#161}
      ( 48, 64,  0,112,200,112), {#162}
      ( 48, 64,  0,200,200,120), {#163}
      (104,144,  0,176, 72, 72), {#164}
      (104,144,  0,200,168,152), {#165}
      (112,144,104,  0,248,  0), {#166}
      (112,136,112,  0,248,  0), {#167}
      ( 32,  0, 32, 64,136,112), {#168}
      (  0,  0,252,192,  0,  0), {#169}
      (  0,  0,252, 12,  0,  0), {#170}
      ( 72, 80, 32, 64,168, 40), {#171}
      ( 72, 80, 32, 80,152,  8), {#172}
      ( 48,  0, 48, 48, 48,  0), {#173}
      ( 40, 80,160, 80, 40,  0), {#174}
      (160, 80, 40, 80,160,  0), {#175}
      ( 84,168, 84,168, 84,168), {#176}
      (252,252,252,252,252,252), {#177}
      (168, 84,168, 84,168, 84), {#178}
      ( 16, 16, 16, 16, 16, 16), {#179}
      ( 16, 16, 16,240, 16, 16), {#180}
      ( 16, 16,240, 16,240, 16), {#181}
      ( 40, 40, 40,232, 40, 40), {#182}
      (  0,  0,  0,248, 40, 40), {#183}
      (  0,  0,240, 16,240, 16), {#184}
      ( 40, 40,232,  8,232, 40), {#185}
      ( 40, 40, 40, 40, 40, 40), {#186}
      (  0,  0,248,  8,232, 40), {#187}
      ( 40, 40,232,  8,248,  0), {#188}
      ( 40, 40, 40,248,  0,  0), {#189}
      ( 16, 16,240, 16,240,  0), {#190}
      (  0,  0,  0,240, 16, 16), {#191}
      ( 16, 16, 16, 28,  0,  0), {#192}
      ( 16, 16, 16,252,  0,  0), {#193}
      (  0,  0,  0,252, 16, 16), {#194}
      ( 16, 16, 16, 28, 16, 16), {#195}
      (  0,  0,  0,252,  0,  0), {#196}
      ( 16, 16, 16,252, 16, 16), {#197}
      ( 16, 16, 28, 16, 28, 16), {#198}
      ( 40, 40, 40, 44, 40, 40), {#199}
      ( 40, 40, 44, 32, 60,  0), {#200}
      (  0,  0, 60, 32, 44, 40), {#201}
      ( 40, 40,236,  0,252,  0), {#202}
      (  0,  0,252,  0,236, 40), {#203}
      ( 40, 40, 44, 32, 44, 40), {#204}
      (  0,  0,252,  0,252,  0), {#205}
      ( 40, 40,236,  0,236, 40), {#206}
      ( 16, 16,252,  0,252,  0), {#207}
      ( 40, 40, 40,252,  0,  0), {#208}
      (  0,  0,252,  0,252, 16), {#209}
      (  0,  0,  0,252, 40, 40), {#210}
      ( 40, 40, 40, 60,  0,  0), {#211}
      ( 16, 16, 28, 16, 28,  0), {#212}
      (  0,  0, 28, 16, 28, 16), {#213}
      (  0,  0,  0, 60, 40, 40), {#214}
      ( 40, 40, 40,252, 40, 40), {#215}
      ( 16, 16,252, 16,252, 16), {#216}
      ( 16, 16, 16,240,  0,  0), {#217}
      (  0,  0, 28, 16, 16, 16), {#218}
      (252,252,252,252,252,252), {#219}
      (  0,  0,  0,252,252,252), {#220}
      (192,192,192,192,192,192), {#221}
      ( 12, 12, 12, 12, 12, 12), {#222}
      (252,252,252,  0,  0,  0), {#223}
      (  0,  0,104,144,144,104), {#224}
      (  0,112,152,176,136,176), {#225}
      (  0,248,136,128,128,128), {#226}
      (  0,  0,248, 80, 80, 80), {#227}
      (248, 72, 32, 64,136,248), {#228}
      (  0,  0,120,144,144, 96), {#229}
      (  0, 72, 72,120, 64,192), {#230}
      (  0,  0,104,176, 32, 32), {#231}
      (  0,248, 32, 80, 32,248), {#232}
      (  0,112,136,248,136,112), {#233}
      (  0,112,136,136, 80,216), {#234}
      ( 56, 64, 32,112,136,112), {#235}
      (  0,  0, 80,168, 80,  0), {#236}
      (  0,  8, 80,168, 80,128), {#237}
      (  0,120,128,248,128,120), {#238}
      (  0,  0,112,136,136,136), {#239}
      (  0,248,  0,248,  0,248), {#240}
      (  0, 32,112, 32,  0,248), {#241}
      ( 64, 32, 16, 32, 64,248), {#242}
      ( 16, 32, 64, 32, 16,248), {#243}
      ( 16, 40, 32, 32, 32, 32), {#244}
      ( 32, 32, 32, 32,160, 64), {#245}
      (  0, 32,  0,248,  0, 32), {#246}
      (104,144,  0,104,144,  0), {#247}
      ( 96,144, 96,  0,  0,  0), {#248}
      (  0,  0,  0, 48,  0,  0), {#249}
      (  0,  0,  0, 16,  0,  0), {#250}
      ( 60, 32, 32,160, 96, 32), {#251}
      (176, 72, 72,  0,  0,  0), {#252}
      (224, 16, 96,128,240,  0), {#253}
      (  0,  0,112,112,  0,  0), {#254}
      (  0,  0,  0,  0,  0,  0));{#255}

VAR Steigung:BYTE;        {entscheidet, welcher Alg. Anwendung findet}
    DY_mal2,DY_m_DX_mal2:INTEGER;
    oldMode:byte;
    regs:registers;

    IsAT:BYTE;
    TimeFlag:BYTE;
    CycleTime:LONGINT;


{-----------------------------------------------------}

PROCEDURE ShadowTab; ASSEMBLER;
{Pseudoprozedur, um Daten der Farbumsetztabelle im Codesegment unterzu-}
{bringen, AUF KEINEN FALL AUFRUFEN!!!                                  }
{Defaultwerte entsprechen Abdunkelung auf 70% des Farb-Helligkeitswerts}
ASM
   DB 254,104,120,124,112,108,114, 24, 20,128,144,  3,136,  5,140,  7
   DB 254,254, 17, 17, 18, 19, 20, 20, 21,  8, 23, 24, 24, 25, 26,  7
   DB   1,  1,107,108,  5,108,109,  4,  4,  4,  6,  6,116,116,117,  2
   DB   2,  2,123,124,  3,124,125,  1,152,155,156,156,  5,156,156,157
   DB 160,163,164,164,164,164,164,165,168,171,172,172,  3,172,172,173
   DB  24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
   DB  24, 24, 24, 24, 24, 24, 24, 24,176,177,178,179,180,181,182,183
   DB 184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199
   DB 200,201,203,204,204,204,205,207,208,209,211,212,212,212,213,215
   DB 216,217,219,220,220,220,221,223,246,227,228,228,228,228,228,229
   DB 234,235,236,236,236,236,236,237,242,243,244,244,244,244,244,245
   DB 254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254
   DB 254,254,254,254,254,254,254,254, 17, 17, 17, 17, 17, 17, 17, 17
   DB  17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17
   DB  17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17
   DB  17, 17, 17, 17, 17, 17, 17, 17,254,254,254,254,254,254,254,  7
END;

PROCEDURE CS_TranslateTab; ASSEMBLER;
{kleine Pseudoprozedur, um die Umsetztabelle fuer die Bitmaske auch im}
{Codesegment zu haben}
ASM
 DB 1,2,4,8
END;

PROCEDURE SetShadowTab(brightness:BYTE);
{ in: brightness = gewuenschte Helligkeit der Farben im Schattenbereich,}
{                  in Prozent zu der Helligkeit ihrer Originalfarben    }
{out: ShadowTab  = (Naeherungs-)Farbtabelle fuer gewuenschte Abdunkelung}
{rem: Defaultwert in ShadowTab ist 70% Helligkeit der Ursprungsfarben!  }
{     Diese Routine dauert ihre Zeit (ca. 4 sec auf 8MHz-AT!)           }
TYPE PaletteEntry=RECORD red,green,blue:BYTE END;
     Palette=ARRAY[0..255] OF PaletteEntry;
CONST default_Farben:Palette=      {Defaultfarben-Palette des 256-Farbmodus}
 (                                 {ausgelesen mithilfe des BIOS-Aufrufs:  }
  (red:  0; green:  0; blue:  0),  { MOV AX,1017h ;lese Palettenregister}
  (red:  0; green:  0; blue: 42),  { XOR BX,BX    ;von Farbe 0 an }
  (red:  0; green: 42; blue:  0),  { MOV CX,100h  ;alle 256 Farben}
  (red:  0; green: 42; blue: 42),  { LES DX,Ziel  ;nach ES:DX }
  (red: 42; green:  0; blue:  0),  { INT 10h }
  (red: 42; green:  0; blue: 42),  {Achtung! Die Werte koenn(t)en nur dann }
  (red: 42; green: 21; blue:  0),  {ausgelesen werden, wenn der Grafikmodus}
  (red: 42; green: 42; blue: 42),  {bereits aktiv ist, deshalb wurden sie  }
  (red: 21; green: 21; blue: 21),  {hier "statisch" aufgenommen!}
  (red: 21; green: 21; blue: 63),
  (red: 21; green: 63; blue: 21),
  (red: 21; green: 63; blue: 63),
  (red: 63; green: 21; blue: 21),
  (red: 63; green: 21; blue: 63),
  (red: 63; green: 63; blue: 21),
  (red: 63; green: 63; blue: 63),
  (red:  0; green:  0; blue:  0),
  (red:  5; green:  5; blue:  5),
  (red:  8; green:  8; blue:  8),
  (red: 11; green: 11; blue: 11),
  (red: 14; green: 14; blue: 14),
  (red: 17; green: 17; blue: 17),
  (red: 20; green: 20; blue: 20),
  (red: 24; green: 24; blue: 24),
  (red: 28; green: 28; blue: 28),
  (red: 32; green: 32; blue: 32),
  (red: 36; green: 36; blue: 36),
  (red: 40; green: 40; blue: 40),
  (red: 45; green: 45; blue: 45),
  (red: 50; green: 50; blue: 50),
  (red: 56; green: 56; blue: 56),
  (red: 63; green: 63; blue: 63),
  (red:  0; green:  0; blue: 63),
  (red: 16; green:  0; blue: 63),
  (red: 31; green:  0; blue: 63),
  (red: 47; green:  0; blue: 63),
  (red: 63; green:  0; blue: 63),
  (red: 63; green:  0; blue: 47),
  (red: 63; green:  0; blue: 31),
  (red: 63; green:  0; blue: 16),
  (red: 63; green:  0; blue:  0),
  (red: 63; green: 16; blue:  0),
  (red: 63; green: 31; blue:  0),
  (red: 63; green: 47; blue:  0),
  (red: 63; green: 63; blue:  0),
  (red: 47; green: 63; blue:  0),
  (red: 31; green: 63; blue:  0),
  (red: 16; green: 63; blue:  0),
  (red:  0; green: 63; blue:  0),
  (red:  0; green: 63; blue: 16),
  (red:  0; green: 63; blue: 31),
  (red:  0; green: 63; blue: 47),
  (red:  0; green: 63; blue: 63),
  (red:  0; green: 47; blue: 63),
  (red:  0; green: 31; blue: 63),
  (red:  0; green: 16; blue: 63),
  (red: 31; green: 31; blue: 63),
  (red: 39; green: 31; blue: 63),
  (red: 47; green: 31; blue: 63),
  (red: 55; green: 31; blue: 63),
  (red: 63; green: 31; blue: 63),
  (red: 63; green: 31; blue: 55),
  (red: 63; green: 31; blue: 47),
  (red: 63; green: 31; blue: 39),
  (red: 63; green: 31; blue: 31),
  (red: 63; green: 39; blue: 31),
  (red: 63; green: 47; blue: 31),
  (red: 63; green: 55; blue: 31),
  (red: 63; green: 63; blue: 31),
  (red: 55; green: 63; blue: 31),
  (red: 47; green: 63; blue: 31),
  (red: 39; green: 63; blue: 31),
  (red: 31; green: 63; blue: 31),
  (red: 31; green: 63; blue: 39),
  (red: 31; green: 63; blue: 47),
  (red: 31; green: 63; blue: 55),
  (red: 31; green: 63; blue: 63),
  (red: 31; green: 55; blue: 63),
  (red: 31; green: 47; blue: 63),
  (red: 31; green: 39; blue: 63),
  (red: 45; green: 45; blue: 63),
  (red: 49; green: 45; blue: 63),
  (red: 54; green: 45; blue: 63),
  (red: 58; green: 45; blue: 63),
  (red: 63; green: 45; blue: 63),
  (red: 63; green: 45; blue: 58),
  (red: 63; green: 45; blue: 54),
  (red: 63; green: 45; blue: 49),
  (red: 63; green: 45; blue: 45),
  (red: 63; green: 49; blue: 45),
  (red: 63; green: 54; blue: 45),
  (red: 63; green: 58; blue: 45),
  (red: 63; green: 63; blue: 45),
  (red: 58; green: 63; blue: 45),
  (red: 54; green: 63; blue: 45),
  (red: 49; green: 63; blue: 45),
  (red: 45; green: 63; blue: 45),
  (red: 45; green: 63; blue: 49),
  (red: 45; green: 63; blue: 54),
  (red: 45; green: 63; blue: 58),
  (red: 45; green: 63; blue: 63),
  (red: 45; green: 58; blue: 63),
  (red: 45; green: 54; blue: 63),
  (red: 45; green: 49; blue: 63),
  (red:  0; green:  0; blue: 28),
  (red:  7; green:  0; blue: 28),
  (red: 14; green:  0; blue: 28),
  (red: 21; green:  0; blue: 28),
  (red: 28; green:  0; blue: 28),
  (red: 28; green:  0; blue: 21),
  (red: 28; green:  0; blue: 14),
  (red: 28; green:  0; blue:  7),
  (red: 28; green:  0; blue:  0),
  (red: 28; green:  7; blue:  0),
  (red: 28; green: 14; blue:  0),
  (red: 28; green: 21; blue:  0),
  (red: 28; green: 28; blue:  0),
  (red: 21; green: 28; blue:  0),
  (red: 14; green: 28; blue:  0),
  (red:  7; green: 28; blue:  0),
  (red:  0; green: 28; blue:  0),
  (red:  0; green: 28; blue:  7),
  (red:  0; green: 28; blue: 14),
  (red:  0; green: 28; blue: 21),
  (red:  0; green: 28; blue: 28),
  (red:  0; green: 21; blue: 28),
  (red:  0; green: 14; blue: 28),
  (red:  0; green:  7; blue: 28),
  (red: 14; green: 14; blue: 28),
  (red: 17; green: 14; blue: 28),
  (red: 21; green: 14; blue: 28),
  (red: 24; green: 14; blue: 28),
  (red: 28; green: 14; blue: 28),
  (red: 28; green: 14; blue: 24),
  (red: 28; green: 14; blue: 21),
  (red: 28; green: 14; blue: 17),
  (red: 28; green: 14; blue: 14),
  (red: 28; green: 17; blue: 14),
  (red: 28; green: 21; blue: 14),
  (red: 28; green: 24; blue: 14),
  (red: 28; green: 28; blue: 14),
  (red: 24; green: 28; blue: 14),
  (red: 21; green: 28; blue: 14),
  (red: 17; green: 28; blue: 14),
  (red: 14; green: 28; blue: 14),
  (red: 14; green: 28; blue: 17),
  (red: 14; green: 28; blue: 21),
  (red: 14; green: 28; blue: 24),
  (red: 14; green: 28; blue: 28),
  (red: 14; green: 24; blue: 28),
  (red: 14; green: 21; blue: 28),
  (red: 14; green: 17; blue: 28),
  (red: 20; green: 20; blue: 28),
  (red: 22; green: 20; blue: 28),
  (red: 24; green: 20; blue: 28),
  (red: 26; green: 20; blue: 28),
  (red: 28; green: 20; blue: 28),
  (red: 28; green: 20; blue: 26),
  (red: 28; green: 20; blue: 24),
  (red: 28; green: 20; blue: 22),
  (red: 28; green: 20; blue: 20),
  (red: 28; green: 22; blue: 20),
  (red: 28; green: 24; blue: 20),
  (red: 28; green: 26; blue: 20),
  (red: 28; green: 28; blue: 20),
  (red: 26; green: 28; blue: 20),
  (red: 24; green: 28; blue: 20),
  (red: 22; green: 28; blue: 20),
  (red: 20; green: 28; blue: 20),
  (red: 20; green: 28; blue: 22),
  (red: 20; green: 28; blue: 24),
  (red: 20; green: 28; blue: 26),
  (red: 20; green: 28; blue: 28),
  (red: 20; green: 26; blue: 28),
  (red: 20; green: 24; blue: 28),
  (red: 20; green: 22; blue: 28),
  (red:  0; green:  0; blue: 16),
  (red:  4; green:  0; blue: 16),
  (red:  8; green:  0; blue: 16),
  (red: 12; green:  0; blue: 16),
  (red: 16; green:  0; blue: 16),
  (red: 16; green:  0; blue: 12),
  (red: 16; green:  0; blue:  8),
  (red: 16; green:  0; blue:  4),
  (red: 16; green:  0; blue:  0),
  (red: 16; green:  4; blue:  0),
  (red: 16; green:  8; blue:  0),
  (red: 16; green: 12; blue:  0),
  (red: 16; green: 16; blue:  0),
  (red: 12; green: 16; blue:  0),
  (red:  8; green: 16; blue:  0),
  (red:  4; green: 16; blue:  0),
  (red:  0; green: 16; blue:  0),
  (red:  0; green: 16; blue:  4),
  (red:  0; green: 16; blue:  8),
  (red:  0; green: 16; blue: 12),
  (red:  0; green: 16; blue: 16),
  (red:  0; green: 12; blue: 16),
  (red:  0; green:  8; blue: 16),
  (red:  0; green:  4; blue: 16),
  (red:  8; green:  8; blue: 16),
  (red: 10; green:  8; blue: 16),
  (red: 12; green:  8; blue: 16),
  (red: 14; green:  8; blue: 16),
  (red: 16; green:  8; blue: 16),
  (red: 16; green:  8; blue: 14),
  (red: 16; green:  8; blue: 12),
  (red: 16; green:  8; blue: 10),
  (red: 16; green:  8; blue:  8),
  (red: 16; green: 10; blue:  8),
  (red: 16; green: 12; blue:  8),
  (red: 16; green: 14; blue:  8),
  (red: 16; green: 16; blue:  8),
  (red: 14; green: 16; blue:  8),
  (red: 12; green: 16; blue:  8),
  (red: 10; green: 16; blue:  8),
  (red:  8; green: 16; blue:  8),
  (red:  8; green: 16; blue: 10),
  (red:  8; green: 16; blue: 12),
  (red:  8; green: 16; blue: 14),
  (red:  8; green: 16; blue: 16),
  (red:  8; green: 14; blue: 16),
  (red:  8; green: 12; blue: 16),
  (red:  8; green: 10; blue: 16),
  (red: 11; green: 11; blue: 16),
  (red: 12; green: 11; blue: 16),
  (red: 13; green: 11; blue: 16),
  (red: 15; green: 11; blue: 16),
  (red: 16; green: 11; blue: 16),
  (red: 16; green: 11; blue: 15),
  (red: 16; green: 11; blue: 13),
  (red: 16; green: 11; blue: 12),
  (red: 16; green: 11; blue: 11),
  (red: 16; green: 12; blue: 11),
  (red: 16; green: 13; blue: 11),
  (red: 16; green: 15; blue: 11),
  (red: 16; green: 16; blue: 11),
  (red: 15; green: 16; blue: 11),
  (red: 13; green: 16; blue: 11),
  (red: 12; green: 16; blue: 11),
  (red: 11; green: 16; blue: 11),
  (red: 11; green: 16; blue: 12),
  (red: 11; green: 16; blue: 13),
  (red: 11; green: 16; blue: 15),
  (red: 11; green: 16; blue: 16),
  (red: 11; green: 15; blue: 16),
  (red: 11; green: 13; blue: 16),
  (red: 11; green: 12; blue: 16),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red:  0; green:  0; blue:  0),
  (red: 63; green: 63; blue: 63)
 );

VAR neue_Tabelle:ColorTable;
    oldColor,newColor:PaletteEntry;
    i,j,p:BYTE;
    min:WORD;

BEGIN
 IF (brightness<0) OR (brightness>100)
  THEN BEGIN
        Error:=Err_InvalidPercentage;
        exit
       END;
 FOR i:=0 TO 255 DO 
  BEGIN
   {neue Farbe=alte, abgedunkelt auf "brightness" Prozent:}
   newColor.red  :=default_Farben[i].red  * brightness DIV 100;
   newColor.green:=default_Farben[i].green* brightness DIV 100;
   newColor.blue :=default_Farben[i].blue * brightness DIV 100;

   {nun fuer diese Farbe eine moeglichst gute Naeherung finden:}
   min:=65535;
   FOR j:=255 DOWNTO 0 DO
    BEGIN
     oldColor:=default_Farben[j];
     ASM
        MOV AL,newColor.red  {Farbdifferenz im Rotanteil berechnen:}
        SUB AL,oldColor.red
        JL @heller           {<0 bedeutet: neue Farbe waere heller!}
        MOV DL,AL            {>=0, quadrieren (um Differenzquadrat }
        MUL DL               {zu minmieren)                        }
        MOV BX,AX            {Dieses Fehlerquadrat in BX speichern }
        MOV AL,newColor.green  {dto. fuer gruenen Farbanteil...    }
        SUB AL,oldColor.green
        JL @heller
        MOV DL,AL
        MUL DL
        ADD BX,AX
        JC @heller           {(Uebertrag bedeutet sofortiges Ende) }
        MOV AL,newColor.blue {...und fuer den blauen Anteil auch   }
        SUB AL,oldColor.blue
        JL @heller
        MOV DL,AL
        MUL DL
        ADD AX,BX
        JNC @fertig
      @heller:               {hierher, wenn heller: maximale Abwei-}
        MOV AX,0FFFFh        {ung zurueckgeben, da nur dunklere    }
      @fertig:               {Farben gewuenscht sind!              }
                             {Abweichungsmass steht nun in AX!     }

        CMP AX,min           {falls Abweichung kleiner als bisheri-}
        JAE @noNewMin        {ges Minimum ist die Farbe j der neue }
        MOV min,AX           {beste Naeherungswert: ihn selbst (in }
        MOV AL,j             {min) und diese Farbe (in p) merken   }
        MOV p,AL
      @noNewMin:
     END;

    END; {of FOR j}
    neue_Tabelle[i]:=p  {p = bester Naeherungswert fuer "newColor" }
  END;
 MOVE(neue_Tabelle,@ShadowTab^,256); {Farbtabelle uebernehmen}
END;


{Nun folgen die Codestuecke, die Verwendung finden, um ein Sprite auf den}
{Schirm zu bringen; die Schnittstelle ist fuer alle gleich:              }
{ in: CX    = Anzahl Bytes, die von...                                   }
{     DS:SI = (Zeiger auf Quelladresse) nach...                          }
{     ES:BX = (Zeiger auf Zieladresses) zu bringen sind;                 }
{     DI    = Bitplane (0..3) (=X-Koordinate AND 3)                      }
{     Die Bitmaske fuer den richtigen Schreibe-Planezugriff wurde bereits}
{     gesetzt, eine evtl. noetige Leseplane dagegen nicht!               }
{     Die Routinen koennen sicher sein, dass CX<>0 ist                   }
{rem: Jede dieser Routinen MUSS EXAKT 16 Bytes lang und VOLL RELOKATIBEL }
{     sein, sowie die Register BP,DS,ES unveraendert lassen!!!!!!!!!!!!! }
{     Ausserdem muessen die einzelnen Routinen zur Unterscheidbarkeit in }
{     ihren ersten zwei Bytes paarweise verschieden sein!                }

PROCEDURE Modus0; ASSEMBLER;
{Modus 0 betrachtet die Farbe 0 als durchsichtig fuer den Hintergrund}
ASM
   INC CX
   STC
   SBB BX,SI
 @L1:
   LODSB
   OR AL,AL
   LOOPZ @L1
   JCXZ @L2
   MOV ES:[BX+SI],AL
   JMP @L1 {short}
 @L2:
END;

PROCEDURE Modus1; ASSEMBLER;
{Modus 1 schreibt die Daten sofort ohne weitere Untersuchung auf den Schirm}
ASM
   MOV DI,BX
   REP MOVSB
   JMP @L3
   NOP          {10 Bytes Platzhalter}
   NOP
   NOP
   NOP
   NOP
   NOP
   NOP
   NOP
   NOP
   NOP
 @L3:
END;

PROCEDURE Modus2Work; ASSEMBLER;
{Fortsetzung von Modus2 - all das, was nicht mehr in 16 Bytes unterzubringen}
{war, kommt hierher}
ASM
   OUT DX,AX          {Lesezugriff auf passende Plane ermoeglichen}

   PUSH DS            {DS zeigt noch auf Spritedaten, muss aber auf}
                      {Hintergrund zeigen!                         }
   MOV AX,ES          {DS:SI := ES:DI  (Quellzeiger:=Zielzeiger)   }
   MOV DS,AX
   MOV SI,DI
   MOV BX,OFFSET ShadowTab   {Zeiger auf Farbumsetztabelle setzen  }

 @L4:
   LODSB              {Hintergrundfarbe holen...  }
   SEGCS XLAT         {...mit Farbtabelle umsetzen}
   STOSB              {...und auf aktueller Seite darstellen}
   LOOP @L4

   POP DS
END;

PROCEDURE Modus2; ASSEMBLER;
{Modus 2 ist fuer "Schatten" und aehnliches gedacht: hierbei werden die }
{eigentlichen Spritedaten ignoriert und stattdessen die Hintergrunddaten}
{gelesen, die sich an der Spriteposition befinden und deren Farbwerte   }
{gegen die der Farbtabelle "ShadowTab" ersetzt (um bspw. Schatten zu er-}
{zeugen, muesste diese Tabelle zu jeder Farbe eine dunklere enthalten)  }
ASM
   MOV AX,DI          {Bitplane fuer Lesezugriff nach AX bringen}
   MOV DI,BX          {fuer Stringbefehle die Zieladresse nach DI bringen}
   MOV AH,AL          {Bitplane ins Highbyte bringen}
   MOV AL,4
   MOV DX,3CEh
   MOV SI,OFFSET Modus2Work  {fieser Trick: "CALL Modus2Work" wuerde (da re-}
   CALL SI                   {lativ codiert) zu falscher Adresse verzweigen!}
END;


PROCEDURE Adressen; ASSEMBLER;
{Tabelle der Startadressen der 3 Routinen im Codesegment}
ASM
   DW OFFSET Modus0
   DW OFFSET Modus1
   DW OFFSET Modus2
END;


PROCEDURE GADR; ASSEMBLER;
{Tabelle der Grafikzeilen-Startadressen (Offset-Anteil)}
ASM
   DW $0000,$0050,$00A0,$00F0,$0140,$0190,$01E0,$0230
   DW $0280,$02D0,$0320,$0370,$03C0,$0410,$0460,$04B0
   DW $0500,$0550,$05A0,$05F0,$0640,$0690,$06E0,$0730
   DW $0780,$07D0,$0820,$0870,$08C0,$0910,$0960,$09B0
   DW $0A00,$0A50,$0AA0,$0AF0,$0B40,$0B90,$0BE0,$0C30
   DW $0C80,$0CD0,$0D20,$0D70,$0DC0,$0E10,$0E60,$0EB0
   DW $0F00,$0F50,$0FA0,$0FF0,$1040,$1090,$10E0,$1130
   DW $1180,$11D0,$1220,$1270,$12C0,$1310,$1360,$13B0
   DW $1400,$1450,$14A0,$14F0,$1540,$1590,$15E0,$1630
   DW $1680,$16D0,$1720,$1770,$17C0,$1810,$1860,$18B0
   DW $1900,$1950,$19A0,$19F0,$1A40,$1A90,$1AE0,$1B30
   DW $1B80,$1BD0,$1C20,$1C70,$1CC0,$1D10,$1D60,$1DB0
   DW $1E00,$1E50,$1EA0,$1EF0,$1F40,$1F90,$1FE0,$2030
   DW $2080,$20D0,$2120,$2170,$21C0,$2210,$2260,$22B0
   DW $2300,$2350,$23A0,$23F0,$2440,$2490,$24E0,$2530
   DW $2580,$25D0,$2620,$2670,$26C0,$2710,$2760,$27B0
   DW $2800,$2850,$28A0,$28F0,$2940,$2990,$29E0,$2A30
   DW $2A80,$2AD0,$2B20,$2B70,$2BC0,$2C10,$2C60,$2CB0
   DW $2D00,$2D50,$2DA0,$2DF0,$2E40,$2E90,$2EE0,$2F30
   DW $2F80,$2FD0,$3020,$3070,$30C0,$3110,$3160,$31B0
   DW $3200,$3250,$32A0,$32F0,$3340,$3390,$33E0,$3430
   DW $3480,$34D0,$3520,$3570,$35C0,$3610,$3660,$36B0
   DW $3700,$3750,$37A0,$37F0,$3840,$3890,$38E0,$3930
   DW $3980,$39D0,$3A20,$3A70,$3AC0,$3B10,$3B60,$3BB0
   DW $3C00,$3C50,$3CA0,$3CF0,$3D40,$3D90,$3DE0,$3E30
END;


FUNCTION AT:BOOLEAN;
{ in: - }
{out: TRUE/FALSE, wenn die Maschine (mindestens) ein AT ist}
BEGIN
 AT:=MEM[$F000:$FFFE]=$FC
END;


PROCEDURE SetCycleTime(milliseconds:WORD);
{ in: Mindestzeit eines Animationszyklus in Millisekunden}
{out: CycleTime := dieser Wert in Mikrosekunden}
{     TimeFlag  := $80}
{rem: Fuer den ersten Animationszyklus nach Aufruf dieser Routine}
{     gilt wg. TimeFlag:=$80 die Zeitbedingung noch nicht!       }
{     Schaltet der Benutzer (durch Angabe von milliseconds=0) die}
{     Zeitueberwachung explizit ab, so wird das durch IsAT:=$80, }
{     d.h.: "Rechner ist ein PC" vorgetaeuscht. Sonst ist IsAT=0 }
BEGIN
 TimeFlag:=$80;
 CycleTime:=LONGINT(milliseconds)*LONGINT(1000);
 IF (milliseconds<>0) AND AT
  THEN IsAT:=0     {ja, Zeitueberwachung soll benutzt werden  }
  ELSE IsAT:=$80   {nein, keine moeglich oder nicht gewuenscht}
END;

PROCEDURE SetSpriteCycle(nr,len:WORD);
{ in: nr  = Spriteladenummer des ersten Sprites des Zyklus      }
{     len = Laenge des zu definierenden Spritezyklus            }
{out: NextSprite[nr] bis NextSprite[nr+len-1] wurden so gesetzt,}
{     dass sie im Ring aufeinander zeigen, d.h.: sie bilden     }
{     einen Spritezyklus}
{rem: Soll der Zyklus aus nicht direkt aufeinanderfolgenden     }
{     (physikalischen) Sprites gebildet werden, so muessen die  }
{     entsprechenden Eintraege in NextSprite[] manuell gemacht  }
{     werden }
{     Diese Routine verwendet SpriteLADEnummern!}
VAR i:WORD;
BEGIN
 IF (nr<1) OR (nr+len-1>LoadMAX)
  THEN Error:=Err_InvalidSpriteLoadNumber
  ELSE BEGIN
        FOR i:=nr TO nr+len-2 DO NextSprite[i]:=SUCC(i);
        NextSprite[PRED(nr+len)]:=nr  {letztes Sprite zeigt auf erstes}
       END;
END;


FUNCTION GetImage(x1,y1,x2,y2:INTEGER; pa:BYTE):POINTER;
{ in: (x1,y1) = linke obere Ecke des zu sichernden Bildausschnittes        }
{     (x2,y2) = rechte untere Ecke dazu (alles virtuelle Koordinaten!)     }
{     pa      = Grafikseite, von der der Ausschnitt zu sichern ist (0..2)  }
{     StartVirtualX,StartVirtualY = linke obere Bildschirmecke             }
{out: Zeiger auf Heapbereich, der den kopierten Bildausschnitt enthaelt    }
{     left_cut= evtl. noetiger linker Cutoff des Bildausschnittes (gibt an,}
{               um wieviel Punkte der Ausschnitt links ausserhalb des Bild-}
{               schirm ragte)                                              }
{     righ_cut,top_cut,bottom_cut = dto., fuer andere Raender              }
{     was_cut = TRUE/FALSE, falls ein zurechtstutzen des Bildausschnittes  }
{               noetig war/nicht noetig war                                }
{rem: Der benoetigte Speicher wird von der Routine automatisch reserviert  }
{     Sollte dies nicht moeglich sein (oder liegt der Bildausschnitt gaenz-}
{     lich ausserhalb des sichtbaren Bereichs), so wird NIL zurueckgegeben!}
{     Nur wenn "was_cut" TRUE ist, sind die Werte der globalen "..._cut"   }
{     Variablen <>0 gesetzt worden, d.h.: ist der Ausschnitt _ganz_ ausser-}
{     halb des Bildes (also zurueckgegebener Zeiger=NIL), dann liefert die }
{     Routine trotzdem "was_cut"=FALSE!}
VAR len,breite,hoehe,StartAdr,actualAdr,SegmAdr:WORD;
    p:POINTER;
BEGIN
 was_cut:=FALSE; left_cut:=0; right_cut:=0; top_cut:=0; bottom_cut:=0;
 dec(x1,StartVirtualX);   {Bildschirmkoordinaten berechnen}
 dec(y1,StartVirtualY);
 IF (x1>XMAX) or (y1>YMAX) or (x2<0) or (y2<0) or (x1>x2) or (y1>y2)
  THEN BEGIN  {Bildausschnitt nicht auf dem Bildschirm}
        GetImage:=NIL;
        exit
       END;
 {Ausschnitt auf Bildschirm zurechtklippen:}
 IF x1<0 THEN BEGIN left_cut :=-x1; x1:=0; was_cut:=TRUE END;
 IF y1<0 THEN BEGIN top_cut:=-y1; y1:=0; was_cut:=TRUE END;
 IF x2>XMAX THEN BEGIN right_cut :=x2-XMAX; x2:=XMAX; was_cut:=TRUE END;
 IF y2>YMAX THEN BEGIN bottom_cut:=y2-YMAX; y2:=YMAX; was_cut:=TRUE END;

 breite:=SUCC(x2-x1); hoehe:=SUCC(y2-y1);
 len:=breite*hoehe+2*2; {1 Pixel=1 Byte; dazu: 2 Woerter fuer breite & hoehe}
 IF len>MaxAvail
  THEN BEGIN
        Error:=Err_NotEnoughMemory;
        GetImage:=NIL;
        exit
       END;
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)  {Seitennummer muss 0..2 sein}
  THEN BEGIN
        Error:=Err_InvalidPageNumber;
        GetImage:=NIL;
        exit
       END
  ELSE SegmAdr:=Segment_Adr[pa];
 GetMem(p,len);         {Speicher auf dem Heap besorgen}
 ASM
    CLD
    LES DI,p        {ES:DI = Zeiger auf den besorgten Speicher}
    MOV AX,breite
    STOSW           {Breite zuerst ablegen...}
    MOV AX,hoehe
    STOSW           {...gefolgt von der Hoehe, danach dann die Daten}

    MOV BX,AX       {BX:=hoehe (fuer spaeter) }
    MOV SI,y1
    SHL SI,1
    MOV SI,CS:[OFFSET gadr + SI]   {SI:=y1*LINESIZE}
    MOV AX,x1
    MOV DL,AL
    SHR AX,1
    SHR AX,1
    ADD SI,AX       {SI:=Offsetanteil der Startadresse}
    MOV StartAdr,SI
    MOV actualAdr,SI
    AND DL,3
    MOV AH,DL
    MOV AL,4
    MOV DX,3CEh
    OUT DX,AX       {Startplane anwaehlen}
    MOV DS,SegmAdr

    {DS:SI = Zeiger auf erstes zu speicherndes Byte; ES:DI = Zieladr. dafuer}
    {AH = Startplane, AL = 4, BX = abzuarbeitende Zeilenanzahl}

    MOV DX,breite
    ADD DX,3
    SHR DX,1
    SHR DX,1        {DX = Anzahl zu sichernde Bytes je Zeile}

  @L1:
    MOV CX,DX       {Daten einer Zeile abspeichern}
    REP MOVSB
    MOV SI,actualAdr  {Quellzeiger um 1 Grafikzeile weitersetzen}
    ADD SI,LINESIZE
    MOV actualAdr,SI
    DEC BX          {Zeilenzaehler verringern}
    JNE @L1

    INC AH          {naechste Plane anwaehlen}
    CMP AH,4
    JNE @nowrap1    {wrap in den Bitplanes bedeutet: Startadresse}
    MOV AH,0        {um 1 Adresse weitersetzen! }
    INC StartAdr
  @nowrap1:
    MOV DX,3CEh
    OUT DX,AX
    MOV BX,hoehe
    MOV DX,breite
    INC DX
    INC DX
    SHR DX,1
    SHR DX,1
    MOV SI,StartAdr
    MOV actualAdr,SI

  @L2:
    MOV CX,DX
    REP MOVSB
    MOV SI,actualAdr
    ADD SI,LINESIZE
    MOV actualAdr,SI
    DEC BX
    JNE @L2

    INC AH
    CMP AH,4
    JNE @nowrap2
    MOV AH,0
    INC StartAdr
  @nowrap2:
    MOV DX,3CEh
    OUT DX,AX
    MOV BX,hoehe
    MOV DX,breite
    INC DX
    SHR DX,1
    SHR DX,1
    MOV SI,StartAdr
    MOV actualAdr,SI

  @L3:
    MOV CX,DX
    REP MOVSB
    MOV SI,actualAdr
    ADD SI,LINESIZE
    MOV actualAdr,SI
    DEC BX
    JNE @L3

    INC AH
    CMP AH,4
    JNE @nowrap3
    MOV AH,0
    INC StartAdr
  @nowrap3:
    MOV DX,3CEh
    OUT DX,AX
    MOV BX,hoehe
    MOV DX,breite
    SHR DX,1
    SHR DX,1
    MOV SI,StartAdr
    MOV actualAdr,SI

  @L4:
    MOV CX,DX
    REP MOVSB
    MOV SI,actualAdr
    ADD SI,LINESIZE
    MOV actualAdr,SI
    DEC BX
    JNE @L4

    MOV AX,SEG @DATA
    MOV DS,AX
 END;
 GetImage:=p
END;

PROCEDURE PutImage(x,y:INTEGER; p:POINTER; pa:BYTE);
{ in: (x,y) = linke obere Ecke des Zieles (in virtuellen Koordinaten)   }
{     p     = Zeiger auf (durch GetImage erstellten) Bildausschnitt     }
{     pa    = Grafikseite, in die der Bildausschnitt kopiert werden soll}
{     StartVirtualX,StartVirtualY = linke obere Bildschirmecke          }
{out: - }
{rem: Der Bildausschnitt wurde zur Bildschirmdarstellung zurechtgeklippt}
{     Bei Uebergabe von NIL als Zeiger stellt die Routine gar nicht dar;}
{     Dies hilft fuer eine direkte Uebernahme der von GetImage gelie-   }
{     ferten Werte!                                                     }
VAR breite,hoehe,SegmAdr,actualAdr,StartAdr,breite1,breite2,breite3,breite4,
    licut_div4,topcut,pl_adr1,pl_adr2,pl_adr3,pl_adr4:WORD;
    licutoff,temp:INTEGER;
BEGIN
 IF p=NIL THEN exit;
 dec(x,StartVirtualX);   {Bildschirmkoordinaten berechnen}
 dec(y,StartVirtualY);
 IF (x>XMAX) or (y>YMAX) THEN exit;
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)
  THEN BEGIN
        Error:=Err_InvalidPageNumber;
        exit
       END
  ELSE SegmAdr:=Segment_Adr[pa];
 breite:=MEMW[SEG(p^):OFS(p^)];
 hoehe :=MEMW[SEG(p^):OFS(p^)+2];
 IF (x+breite<=0) or (y+hoehe<=0) THEN exit;
 IF x<0 THEN BEGIN licutoff:=-x; x:=0 END
        ELSE licutoff:=0;
 IF y<0 THEN BEGIN
              topcut:=-y;
              y:=0
             END
        ELSE topcut:=0;

 breite1:=(breite + 3) shr 2;  {Breite einer Zeile fuer die erste, zweite,}
 breite2:=(breite + 2) shr 2;  {dritte und vierte Bitplane}
 breite3:=(breite + 1) shr 2;
 breite4:=(breite + 0) shr 2;

 {Anfangsadressen der 4 Bitplanes berechnen; dabei evtl. linken cutoff mit}
 {einbeziehen (plus 4 Bytes zum ueberspringen von "breite" und "hoehe"    }
 licut_div4:=licutoff shr 2;
 pl_adr1:=4 +licut_div4 +topcut*breite1;
 pl_adr2:=4 +licut_div4 +topcut*breite2 +hoehe*breite1;
 pl_adr3:=4 +licut_div4 +topcut*breite3 +hoehe*(breite1+breite2);
 pl_adr4:=4 +licut_div4 +topcut*breite4 +hoehe*(breite1+breite2+breite3);

 {licutoff mod 4 gibt an, in welcher Reihenfolge die Punkte aus dem Heap }
 {gelesen werden muessen: 0 = Planereihenfolge (1,2,3,4); 1=(2,3,4,1);   }
 {2=(3,4,1,2); 3=(4,1,2,3); zu beachten ist, dass die Breiten der einzel-}
 {nen Bitplanetabellen natuerlich mit diesen verbunden bleibt und deshalb}
 {mitgetauscht werden muss!}
 ASM
    CLD
    MOV AX,licutoff
    AND AL,3
    OR AL,AL
    JE @no_exchange
    CMP AL,1
    JNE @L10

    MOV AX,pl_adr2     {Verschiebung um 1 Bit: }
    MOV BX,pl_adr3     {AX=Plane2,BX=Plane3,CX=Plane4,DX=Plane1}
    MOV CX,pl_adr4
    MOV DX,pl_adr1     {wrap-around, deshalb: Adresse um 1 erhoehen, was  }
    INC DX             {einer Weitersetzung um 4 Bildpunkte entspricht    }
    MOV pl_adr1,AX     {(z.B.: Pixel (1,5,9,...),(2,6,10,...),(3,7,11,...)}
    MOV pl_adr2,BX     {und (0,4,8,...); letztere Bitplane wird um 1 Byte }
    MOV pl_adr3,CX     {weitergesetzt: liefert (richtige) (4,8,12,...)    }
    MOV pl_adr4,DX     {Folge (Planes abwechselnd von oben nach unten lesen!}
    MOV AX,breite2     {Jetzt Planebreiten: }
    MOV BX,breite3     {AX=Plane2,BX=Plane3,CX=Plane4,DX=Plane1}
    MOV CX,breite4
    MOV DX,breite1
    JMP @store

  @L10:
    CMP AL,2
    JNE @L20

    MOV AX,pl_adr3     {Verschiebung um 2 Bit: }
    MOV BX,pl_adr4     {AX=Plane3,BX=Plane4,CX=Plane1,DX=Plane2}
    MOV CX,pl_adr1
    INC CX
    MOV DX,pl_adr2
    INC DX
    MOV pl_adr1,AX
    MOV pl_adr2,BX
    MOV pl_adr3,CX
    MOV pl_adr4,DX
    MOV AX,breite3     {dto. fuer Planebreiten: }
    MOV BX,breite4     {AX=Plane3,BX=Plane4,CX=Plane1,DX=Plane2}
    MOV CX,breite1
    MOV DX,breite2
    JMP @store
  @L20:
    MOV AX,pl_adr4     {Verschiebung um 3 Bit: }
    MOV BX,pl_adr1     {AX=Plane4,BX=Plane1,CX=Plane2,DX=Plane3}
    INC BX
    MOV CX,pl_adr2
    INC CX
    MOV DX,pl_adr3
    INC DX
    MOV pl_adr1,AX
    MOV pl_adr2,BX
    MOV pl_adr3,CX
    MOV pl_adr4,DX
    MOV AX,breite4     {dto. fuer Planebreiten: }
    MOV BX,breite1     {AX=Plane4,BX=Plane1,CX=Plane2,DX=Plane3}
    MOV CX,breite2
    MOV DX,breite3
  @store:
    MOV breite1,AX
    MOV breite2,BX
    MOV breite3,CX
    MOV breite4,DX

  @no_exchange:        {jetzt gilt: (pl_adr?,breite?) enthalten die Source-}
                       {Bitplanes/-breiten in der richtigen Reihenfolge    }

    MOV AX,topcut
    SUB hoehe,AX       {Hoehe um evtl. oberen cutoff korrigieren}
    MOV AX,licutoff
    SUB breite,AX      {dto. fuer Breite und linken cutoff}

    MOV AX,x           {falls Ausschnitt ueber rechten Bildschirmrand}
    ADD AX,breite      {ragen wuerde: rechten cutoff bestimmen       }
    SUB AX,XMAX+1
    JLE @no_recutoff
    SUB breite,AX      {AX Punkte rechts abschneiden}
  @no_recutoff:

    MOV AX,y           {genau dasselbe fuer unteren Bildschirmrand}
    ADD AX,hoehe
    SUB AX,YMAX+1
    JLE @no_bocutoff
    SUB hoehe,AX       {AX Zeilen unten abschneiden}
  @no_bocutoff:


    LDS SI,p
    ADD pl_adr2,SI       {Offsetanteil des Zeigers zu den Planeadr. addieren}
    ADD pl_adr3,SI
    ADD pl_adr4,SI

    ADD SI,pl_adr1    {breite,hoehe und Teile oberhalb des Bildschirms}
    MOV ES,SegmAdr

    MOV DI,y
    SHL DI,1
    MOV DI,CS:[OFFSET gadr + DI]  {DI:=y*LINESIZE}
    MOV AX,x
    MOV BL,AL
    SHR AX,1
    SHR AX,1
    ADD DI,AX         {DI:=y*LINESIZE +(x DIV 4)}
    MOV StartAdr,DI
    MOV actualAdr,DI

    AND BX,3          {Startplane:=x mod 3}
    MOV AH,CS:[OFFSET CS_TranslateTab + BX]
    MOV AL,2
    MOV DX,3C4h
    OUT DX,AX         {als Schreibplane anwaehlen}

    MOV DX,hoehe
    MOV DI,actualAdr

    {DS:SI = Zeiger auf Daten, ES:DI = Zieladresse dafuer auf dem Schirm,}
    {AH = Bitmaske fuer Zugriff, AL = 2 }
    MOV BX,breite
    ADD BX,3
    SHR BX,1
    SHR BX,1
    mov cx,bx
  @L1:
    push si
    REP MOVSB
    pop si
    mov cx,bx
    add si,breite1
    MOV DI,actualAdr
    ADD DI,LINESIZE
    MOV actualAdr,DI
    DEC DX
    JNE @L1


    SHL AH,1          {naechste Bitplane anwaehlen; bei einem wrap von}
    CMP AH,16         {Bitplane 3 zu Bitplane 0 muss dabei die Start- }
    JNE @nowrap1      {adresse um 1 Byte weitergesetzt werden         }
    MOV AH,1
    INC StartAdr
  @nowrap1:
    MOV DX,3C4h
    OUT DX,AX
    MOV SI,pl_adr2
    MOV DI,StartAdr
    MOV actualAdr,DI
    MOV DX,hoehe
    MOV BX,breite
    INC BX
    INC BX
    SHR BX,1
    SHR BX,1
    mov cx,bx
  @L2:
    push si
    REP MOVSB
    pop si
    mov cx,bx
    add si,breite2
    MOV DI,actualAdr
    ADD DI,LINESIZE
    MOV actualAdr,DI
    DEC DX
    JNE @L2


    SHL AH,1
    CMP AH,16
    JNE @nowrap2
    MOV AH,1
    INC StartAdr
  @nowrap2:
    MOV DX,3C4h
    OUT DX,AX
    MOV SI,pl_adr3
    MOV DI,StartAdr
    MOV actualAdr,DI
    MOV DX,hoehe
    MOV BX,breite
    INC BX
    SHR BX,1
    SHR BX,1
    mov cx,bx
  @L3:
    push si
    REP MOVSB
    pop si
    mov cx,bx
    add si,breite3
    MOV DI,actualAdr
    ADD DI,LINESIZE
    MOV actualAdr,DI
    DEC DX
    JNE @L3


    SHL AH,1
    CMP AH,16
    JNE @nowrap3
    MOV AH,1
    INC StartAdr
  @nowrap3:
    MOV DX,3C4h
    OUT DX,AX
    MOV SI,pl_adr4
    MOV DI,StartAdr
    MOV actualAdr,DI
    MOV DX,hoehe
    MOV BX,breite
    SHR BX,1
    SHR BX,1
    mov cx,bx
  @L4:
    push si
    REP MOVSB
    pop si
    mov cx,bx
    add si,breite4
    MOV DI,actualAdr
    ADD DI,LINESIZE
    MOV actualAdr,DI
    DEC DX
    JNE @L4

    MOV AX,SEG @DATA
    MOV DS,AX
 END;

END;

PROCEDURE Screen(pa:BYTE);
{ in: pa = anzuzeigende Bildschirmseite (0..3) }
{out: - }
{rem: Es wurde auf die Darstellung der Grafikseite pa umgeschaltet     }
{     Dabei wurde NICHT auf irgendwelche Retrace-Signale synchronisiert}
{     Sinnvoll sind nur die Seiten 0 oder 1, es findet aber keine      }
{     Ueberpruefung statt!}
BEGIN
 ASM
  MOV DX,$3D4                {CRT-Controller}
  MOV AL,$0D                 {LB-Startadress-Register}
  CLI                        {Darf keinenfalls unterbrochen werden!}
  OUT DX,AL
  INC DX
                             {Realisiere "AX:=Offset_Adr[pa]":}
  MOV BL,pa
  MOV SI,BX
  AND SI,3                   {Page-Wert *2 (da Worteintraege!)}
  SHL SI,1                   {dazu Startadresse des Feldes addieren}
  ADD SI,OFFSET Offset_Adr-StartIndex*2  {evtl. Verschiebung korrigieren}
  LODSW                      {und Wert holen}
  OUT DX,AL                  {LB der neuen Startadresse setzen}
  DEC DX
  MOV AL,$0C
  OUT DX,AL
  INC DX
  MOV AL,AH                  {HB der neuen Startadresse setzen}
  OUT DX,AL
  STI
 END;
END;

PROCEDURE InitGraph;
{ in: PAGE = aktuelle Grafikseite}
{out: - }
{rem: Schaltet die VGA-Karte in den 320x200x256x4-Modus; ACHTUNG!    }
{     Dieser Modus ist verschieden vom Modus $13 der VGA!!!          }
{     Dabei wird auf die Darstellung der Seite 1-PAGE geschaltet     }
BEGIN
INLINE(
  $B8/$13/$00/ {0100: MOV    AX,0013 ;Mit BIOS Grafikmodus $13       }
  $CD/$10/     {0103: INT    10      ;(=320x200x256) setzen          }
  $BA/$C4/$03/ {0105: MOV    DX,03C4 ;Sequenzer ansteuern und dort   }
  $B0/$04/     {0108: MOV    AL,04   ;das Speichermodus-Register     }
  $EE/         {010A: OUT    DX,AL   ;auswaehlen                     }
  $42/         {010B: INC    DX      ;Dessen Daten ueber das zuge-   }
  $EC/         {010C: IN     AL,DX   ;hoerige Datenregister einlesen }
  $24/$F7/     {010D: AND    AL,F7   ;Bit 3:=0:4 Planes nicht chainen}
  $0C/$04/     {010F: OR     AL,04   ;Bit 2:=1:kein odd/even-Mechan. }
  $EE/         {0111: OUT    DX,AL   ;Neuen Wert wirksam machen      }
  $BA/$C4/$03/ {0112: MOV    DX,03C4 ;S.o.: Sequenzer-Register 2     }
  $B0/$02/     {0115: MOV    AL,02   ;(=Map-Maske)                   }
  $EE/         {0117: OUT    DX,AL   ;auswaehlen,...                 }
  $42/         {0118: INC    DX                                      }
  $B0/$0F/     {0119: MOV    AL,0F   ;...und Zugriff auf alle 4      }
  $EE/         {011B: OUT    DX,AL   ;Bitmaps erlauben               }
  $B8/$00/$A0/ {011C: MOV    AX,A000 ;Ab Segment $A000               }
  $8E/$C0/     {011F: MOV    ES,AX   ;$8000 logische Woerter=        }
  $29/$FF/     {0121: SUB    DI,DI   ;4*$8000 physikalische Woerter  }
  $89/$F8/     {0123: MOV    AX,DI   ;(wg. den 4 Bitplanes) auf 0    }
  $B9/$00/$80/ {0125: MOV    CX,8000 ;setzen                         }
  $FC/         {0128: CLD                                            }
  $F2/         {0129: REPNZ                                          }
  $AB/         {012A: STOSW                                          }
  $BA/$D4/$03/ {012B: MOV    DX,03D4 ;CRT-Controller ansteuern       }
  $B0/$14/     {012E: MOV    AL,14   ;Underline-location-Register    }
  $EE/         {0130: OUT    DX,AL   ;auswaehlen                     }
  $42/         {0131: INC    DX      ;Wert aus zugehoerigem          }
  $EC/         {0132: IN     AL,DX   ;Datenregister auslesen         }
  $24/$BF/     {0133: AND    AL,BF   ;Bit 6:=0: keine Doppelwort-    }
  $EE/         {0135: OUT    DX,AL   ;adressierung von Daten im Bild-}
  $4A/         {0136: DEC    DX      ;schirmspeicher durchfuehren    }
  $B0/$17/     {0137: MOV    AL,17   ;Mode-control-Register          }
  $EE/         {0139: OUT    DX,AL   ;auswaehlen                     }
  $42/         {013A: INC    DX                                      }
  $EC/         {013B: IN     AL,DX                                   }
  $0C/$40/     {013C: OR     AL,40   ;Bit 6:=1: Adressierung des     }
  $EE          {013E: OUT    DX,AL   ;Speichers=lineares Bitfeld     }
  );
 Screen(1-PAGE);  {sichtbar ist immer die nichtaktuelle Grafikseite}
END;


PROCEDURE Line(x1,y1,x2,y2:INTEGER; pa:BYTE);
{ in: x1,y1,x2,y2 = Koordinaten zweier Punkte, }
{     Color       = Farbe (0..255)             }
{     StartVirtualX,StartVirtualY = linke obere Bildschirmecke         }
{     pa          = Grafikseite, auf der gezeichnet werden soll (0..2) }
{out: - }
{rem: Es wurde eine Linie von den VIRTUELLEN Punkten (x1,y1) nach (x2,y2)  }
{     in der Farbe COLOR gezeichnet; die Routine fuehrt dabei selber die   }
{     Umrechnung der angeg. Koordinaten in absolute Bildschirmkoordinaten  }
{     sowie evtl. notwendige Clipping-Schritte aus.                        }
{     Die Linie wird NICHT automatisch in den Hintergrund uebernommen,     }
{     d.h.: sie ist nur fuer einen Animationszyklus sichtbar (soll sie     }
{     permanent bleiben, so muss sie in den Hintergrund gezeichnet werden!)}
{     (Deshalb ist es sinnvoll, diese Routine NACH Aufruf von ANIMATE aus- }
{     zufuehren, da die gezeichnete Linie sonst sofort wieder verschwindet)}
CONST CodeLinks =$7;  {%0111}
      CodeRechts=$B;  {%1011}
      CodeOben  =$D;  {%1101}
      CodeUnten =$E;  {%1110}
BEGIN
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)
  THEN Error:=Err_InvalidPageNumber
  ELSE
  {zuerst Linie auf sichtbaren Bereich zurechtklippen; dazu Sutherland-}
  {Cohen-Algorithmus verwenden: 4 Bit-Code: links|rechts|oben|unten    }
  ASM
     CLD
     MOV CL,$F         {mit %1111 anfangen}
     MOV AX,x2
     SUB AX,StartVirtualX   {x2 in absolute Koordinaten umrechnen}
     MOV x2,AX
     OR AX,AX          {x2<0 ?}
     JL @GC1Punkt2     {ja, Flag fuer "Punkt links vom Fenster" belassen}
     AND CL,CodeLinks  {nein, Flag ruecksetzen}
   @GC1Punkt2:
     CMP AX,XMAX       {x2>XMAX ?}
     JG @GC2Punkt2     {ja, Flag fuer "Punkt rechts vom Fenster" belassen}
     AND CL,CodeRechts {nein, Flag ruecksetzen}
   @GC2Punkt2:
     MOV AX,y2
     SUB AX,StartVirtualY   {y2 in absolute Koordinaten umrechnen}
     MOV y2,AX
     OR AX,AX          {y2<0 ?}
     JL @GC3Punkt2     {ja, Flag fuer "Punkt oberhalb des Fensters" bel.}
     AND CL,CodeOben   {nein, Flag ruecksetzen}
   @GC3Punkt2:
     CMP AX,YMAX       {y2>YMAX ?}
     JG @GC4Punkt2     {ja, Flag fuer "Punkt unterhalb des Fensters" bel.}
     AND CL,CodeUnten
   @GC4Punkt2:         {CL enthaelt jetzt den Gebietscode fuer Punkt 2}

     MOV AX,x1
     SUB AX,StartVirtualX   {x1 in absolute Koordinaten umrechnen}
     MOV x1,AX
     MOV AX,y1
     SUB AX,StartVirtualY   {y1 in absolute Koordinaten umrechnen}
     MOV y1,AX

   @Punkt1:
     MOV CH,$F         {mit %1111 anfangen}
     MOV AX,x1
     OR AX,AX          {x1<0 ?}
     JL @GC1Punkt1     {ja, Flag fuer "Punkt links vom Fenster" belassen}
     AND CH,CodeLinks  {nein, Flag ruecksetzen}
   @GC1Punkt1:
     CMP AX,XMAX       {x1>XMAX ?}
     JG @GC2Punkt1     {ja, Flag fuer "Punkt rechts vom Fenster" belassen}
     AND CH,CodeRechts {nein, Flag ruecksetzen}
   @GC2Punkt1:
     MOV AX,y1
     OR AX,AX          {y1<0 ?}
     JL @GC3Punkt1     {ja, Flag fuer "Punkt oberhalb des Fensters" bel.}
     AND CH,CodeOben   {nein, Flag ruecksetzen}
   @GC3Punkt1:
     CMP AX,YMAX       {y1>YMAX ?}
     JG @GC4Punkt1     {ja, Flag fuer "Punkt unterhalb des Fensters" bel.}
     AND CH,CodeUnten
   @GC4Punkt1:         {CH enthaelt jetzt den Gebietscode fuer Punkt 1}

   {CL enthaelt den Gebietscode fuer Punkt 2, CH den fuer Punkt 1}

     MOV AX,CX
     AND AL,AH         {Code1 AND Code2 <>0 ?}
     JNZ @LineReady    {ja, Linie ganz ausserhalb des Windows}
     MOV AX,CX
     OR AL,AH          {Code1 OR Code2 =0 ?}
     JZ @DrawLine      {ja, Linie ganz innerhalb des Windows}

   {Nun eigentliches Clipping vornehmen: }
     MOV AX,CX
     OR AH,AH          {Code1 =0 ?}
     JNZ @CL3          {nein, alles ok}
     MOV AX,x1         {ja, Punkte vertauschen!}
     XCHG AX,x2
     MOV x1,AX
     MOV AX,y1
     XCHG AX,y2
     MOV y1,AX
     XCHG CL,CH
   @CL3:
     MOV AL,CH        {AL:=Code1}
     MOV BX,x2
     SUB BX,x1        {BX:=x2-x1}
     MOV SI,y2
     SUB SI,y1        {SI:=y2-y1}
     TEST AL,NOT CodeLinks     {Punkt1 links des Windows?}
     JZ @CL4                   {nein}
     {ja, neue Koordinaten berechnen: y1:=y1+(y2-y1)/(x2-x1)*(WindowX1-X1) }
     {und x1:=WindowX1   (dabei ist WindowX1 = 0) }
     XOR AX,AX
     XCHG AX,x1       {x1:=0}
     NEG AX           {AX:=-x1old}
     IMUL SI
     IDIV BX
     ADD y1,AX
     JMP @Punkt1

   @CL4:
     TEST AL,NOT CodeRechts    {Punkt1 rechts des Windows?}
     JZ @CL5                   {nein}
     {ja, berechne: y1:=y1+(y2-y1)/(x2-x1)*(WindowX2-X1), x1:=WindowX2 }
     { (wobei WindowX2=XMAX) }
     MOV AX,XMAX
     SUB AX,x1
     IMUL SI
     IDIV BX
     ADD y1,AX
     MOV x1,XMAX
     JMP @Punkt1

   @CL5:
     TEST AL,NOT CodeOben      {Punkt1 oberhalb des Windows?}
     JZ @CL6                   {nein}
     {ja, berechne x1:=x1+(x2-x1)/(y2-y1)*(WindowY1-y1), y1:=WindowY1 }
     { (wobei WindowY1=0) }
     XOR AX,AX
     XCHG AX,y1
     NEG AX
     IMUL BX
     IDIV SI
     ADD x1,AX
     JMP @Punkt1

   @CL6:
     TEST AL,NOT CodeUnten     {Punkt unterhalb des Windows?}
     JZ @Punkt1                {nein}
     {ja, berechne x1:=x1+(x2-x1)/(y2-y1)*(WindowY2-y1), y1:=WindowY2 }
     { (wobei WindowY2=YMAX) }
     MOV AX,YMAX
     SUB AX,y1
     IMUL BX
     IDIV SI
     ADD x1,AX
     MOV y1,YMAX
     JMP @Punkt1

   {Hier gilt: die beiden Punkte wurden auf den sichtbaren Bereich zurecht-}
   {gestutzt; sollte die Gerade keinen sichtbaren Teil besitzen, so wurde  }
   {direkt zu @LineReady verzweigt! }
   @DrawLine:
     PUSH BP
     MOV Steigung,0  {Flag zuruecksetzen}
     MOV CX,x2
     SUB CX,x1       {Punkt1 rechts von Punkt2 ?}
     JGE @posDX      {nein}
     NEG CX          {ja, Punkte vertauschen}
     MOV AX,x1
     XCHG AX,x2
     MOV x1,AX
     MOV AX,y1
     XCHG AX,y2
     MOV y1,AX

   @posDX:
     MOV DI,y1
     SHL DI,1
     MOV DI,CS:[OFFSET gadr + DI]   {DI:=y1*LINESIZE}
     MOV AX,x1
     MOV BL,AL
     SHR AX,1
     SHR AX,1
     ADD DI,AX       {DI:=y1*LINESIZE+(x1 DIV 4) }

     AND BX,3        {BX:=(x1 AND 4) }
     MOV DH,[OFFSET TranslateTab + BX]  {Maske fuer VRAM-Zugriff holen}
     MOV DL,2

     MOV BL,pa       {BH=0 -> BX=Zeichenseite}
     SHL BX,1
     ADD BX,OFFSET Segment_Adr -StartIndex*2
     MOV ES,[BX]

     {ES:DI=Zeiger auf Grafikadresse von Punkt1, DX=Zugriffsmaske dafuer}
     MOV SI,LINESIZE
     MOV BX,y2
     SUB BX,y1       {Punkt1 unterhalb von Punkt2 ?}
     JG @posDY       {nein}
     NEG BX          {ja, deltaY und Zeileninkrement negieren}
     NEG SI

   @posDY:
     CMP BX,CX       {deltaY>deltaX ?}
     JLE @flach      {nein: geringe Steigung, <=1 }
     XCHG BX,CX      {ja, deltas vertauschen und Flag setzen}
     MOV Steigung,1

   {Jetzt Bresenham-Parameter berechnen: 2*DY, 2*DY-DX, 2*(DY-DX) }
   @flach:
     SHL BX,1
     MOV DY_mal2,BX
     SUB BX,CX
     MOV BP,BX       {BP:=2*DY-DX}
     SUB BX,CX
     MOV DY_m_DX_mal2,BX
     INC CX          {CX:=Anzahl Pixel}
     MOV BL,Color
     MOV BH,1
     CMP Steigung,0  {steile Linie?}
     JNZ @high1      {ja}

   @low1:            {nein}
     MOV AX,3C4h
     XCHG AX,DX
     OUT DX,AX       {richtige Bitplane anwaehlen}
     MOV DX,AX       {Maske wieder nach DX retten}
     MOV AL,BL       {Farbe fuer Punkt holen}
     STOSB           {Punkt setzen}
     SHL DH,1        {Maske fuer naechsten Punkt berechnen    }
     CMP DH,16       {noch mit derselben Adresse ansprechbar? }
     JE @nextbyte1   {nein, Adr. muss(te) um 1 erhoeht werden }
     DEC DI          {ja, Erhoehung von DI rueckgängig machen }
   @low1b:
     OR BP,BP
     JGE @low2
     ADD BP,DY_mal2
     LOOP @low1
     JMP @raus
   @nextbyte1:
     MOV DH,BH       {Maske auf 1 zuruecksetzen}
     JMP @low1b      {Rest wie gehabt}

   @low2:
     ADD BP,DY_m_DX_mal2
     ADD DI,SI
     LOOP @low1
     JMP @raus


   @high1:
     MOV AX,3C4h
     XCHG AX,DX
     OUT DX,AX
     MOV DX,AX
     MOV AL,BL
   @high1b:
     OR BP,BP
     JGE @high2
     ADD BP,DY_mal2
     MOV ES:[DI],AL
     ADD DI,SI
     LOOP @high1b
     JMP @raus

   @high2:
     ADD BP,DY_m_DX_mal2
     SHL DH,1
     CMP DH,16
     JE @nextbyte2
     MOV ES:[DI],AL
     ADD DI,SI
     LOOP @high1
     JMP @raus
   @nextbyte2:
     MOV DH,BH
     STOSB
     ADD DI,SI
     LOOP @high1

   @raus:
     POP BP
   @LineReady:
  END;
END;

PROCEDURE BackgroundLine(x1,y1,x2,y2:INTEGER);
{ in: x1,y1,x2,y2 = Koordinaten zweier Punkte, }
{     Color       = Farbe (0..255)             }
{     StartVirtualX,StartVirtualY = linke obere Bildschirmecke        }
{out: - }
{rem: Es wurde eine Linie von den VIRTUELLEN Punkten (x1,y1) nach (x2,y2) }
{     in der Farbe COLOR in den Hintergrundspeicher gezeichnet; die Rou-  }
{     tine fuehrt dabei selber alle notwendigen Umrechnungen und Clipping-}
{     schritte aus.                                                       }
{     Die Linie wird NICHT sofort sichtbar, sondern erst im naechsten Ani-}
{     mationszyklus (dann allerdings permanent)! (Deshalb ist es sinnvoll,}
{     diese Routine VOR dem Aufruf von ANIMATE auszufuehren, da dann alle }
{     Aenderungen (durch ANIMATE) sofort sichtbar werden)                 }
{     Da als Hintergrundseite BACKGNDADR verwendet wird, ist die Verwen-  }
{     dung der Routine nur fuer den Hintergrundmodus STATIC sinnvoll!     }
BEGIN
 Line(x1,y1,x2,y2,BACKGNDPAGE)
END;

FUNCTION GetPixel(x,y:INTEGER):BYTE; ASSEMBLER;
{ in: x,y    = VIRTUELLE Punktkoordinaten des auszulesenden Punktes}
{     PAGEADR= Grafikseite(nsegment), aus der gelesen werden soll  }
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke    }
{out: Farbe des Punktes}
{rem: Liegt der Punkt ausserhalb des sichtbaren Bereichs, so wird  }
{     "0" als Ergebniswert zurueckgeliefert}
{     Achtung! Da PAGEADR immer die nichtsichtbare Grafikseite     }
{     bezeichnet, liest diese Routine auch von dort die Punkte ein!}
ASM
 XOR AL,AL              {AL mit 0 vorbesetzen}
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BL,3     {BL = X MOD 4 = Leseplane}
 MOV AL,4
 MOV AH,BL
 MOV DX,3CEh

 MOV ES,PAGEADR
 CLI
 OUT DX,AX
 MOV AL,ES:[DI]
 STI
@offscrn:
END;

FUNCTION BackgroundGetPixel(x,y:INTEGER):BYTE; ASSEMBLER;
{ in: x,y   = VIRTUELLE Punktkoordinaten des auszulesenden Punktes}
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke   }
{out: Farbe des Punktes der Hintergrundseite}
{rem: Liegt der Punkt ausserhalb des sichtbaren Bereichs, so wird }
{     "0" als Ergebniswert zurueckgeliefert}
{     Da als Hintergrundseite BACKGNDADR verwendet wird, ist die  }
{     Routine nur fuer den Hintergrundmodus STATIC sinnvoll!      }
ASM
 XOR AL,AL              {AL mit 0 vorbesetzen}
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BL,3     {BL = X MOD 4 = Leseplane}
 MOV AL,4
 MOV AH,BL
 MOV DX,3CEh
 MOV ES,BACKGNDADR
 CLI
 OUT DX,AX
 MOV AL,ES:[DI]
 STI
@offscrn:
END;

FUNCTION PageGetPixel(x,y:INTEGER; pa:BYTE):BYTE; ASSEMBLER;
{ in: x,y   = VIRTUELLE Punktkoordinaten des auszulesenden Punktes}
{     pa    = Grafikseite (0..3), von der der Punkt ausgelesen    }
{             werden soll}
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke   }
{out: Farbe des Punktes der Hintergrundseite}
{rem: Liegt der Punkt ausserhalb des sichtbaren Bereichs, so wird }
{     "0" als Ergebniswert zurueckgeliefert}
{     Soll von der gerade SICHTBAREN Seite gelesen werden, so ist }
{     beim Aufruf als Seite "1-PAGE" anzugeben!                   }
{     Sinnvolle Werte fuer "pa" sind nur 0 und 1 (und evtl. BACK- }
{     GNDPAGE, wenn der Hintergrundmodus STATIC benutzt wird), es }
{     findet jedoch keine Ueberpruefung statt!}
ASM
 XOR AL,AL              {AL mit 0 vorbesetzen}
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BX,3     {BL = X MOD 4 = Leseplane; BH = 0}
 MOV AL,4
 MOV AH,BL
 MOV BL,pa    {BH=0 -> BX = Grafikseite}
 AND BX,3     {nur Seiten 0..3}
 SHL BX,1
 ADD BX,OFFSET Segment_Adr-StartIndex*2
 MOV ES,[BX]

 CLI
 MOV DX,3CEh
 OUT DX,AX
 MOV AL,ES:[DI]
 STI
@offscrn:
END;


PROCEDURE PutPixel(x,y:INTEGER; color:Byte); ASSEMBLER;
{ in: x,y    = VIRTUELLE Punktkoordinaten des zu zeichnenden Punktes}
{     color  = Farbwert fuer den zu zeichnenden Punkt}
{     1-PAGE = Grafikseite, auf der gezeichnet werden soll}
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke}
{out: - }
{rem: Der Punkt (x,y) wurde in absolute Bildschirmkoordinaten umgerechnet  }
{     und gezeichnet (sofern er auf dem sichtbaren Bildschirmfenster liegt)}
{     Der Punkt wird NICHT automatisch in den Hintergrundspeicher ueber-   }
{     nommen, d.h.: er ist nur einen Animationszyklus lang sichtbar!       }
{     (Deshalb ist es sinnvoll, diese Routine NACH Aufruf von ANIMATE aus- }
{     zufuehren, da der gezeichnete Punkt sonst sofort wieder verschwindet)}
ASM
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BX,3
 MOV AH,[OFFSET TranslateTab + BX]
 MOV AL,2
 MOV DX,3C4h

 MOV BX,1     {ES:=Segment_Adr[1-PAGE], denn 1-PAGE=sichtbare Seite}
 SUB BX,PAGE
 SHL BX,1
 ADD BX,OFFSET Segment_Adr-StartIndex*2
 MOV ES,[BX]

 CLI
 OUT DX,AX
 MOV AL,color
 STOSB
 STI
@offscrn:
END;

PROCEDURE BackgroundPutPixel(x,y:INTEGER; color:Byte); ASSEMBLER;
{ in: x,y   = VIRTUELLE Punktkoordinaten des zu zeichnenden Punktes}
{     color = Farbwert fuer den zu zeichnenden Punkt}
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke}
{out: - }
{rem: Der Punkt (x,y) wurde in absolute Bildschirmkoordinaten umgerechnet und}
{     in den Hintergrund gezeichnet (sofern er im sichtbaren Bereich liegt)  }
{     Der Punkt wird NICHT sofort sichtbar, sondern erst nach einem Anima-   }
{     tionszyklus (dann aber permanent) (Deshalb ist es sinnvoll, diese Rou- }
{     tine VOR dem Aufruf von ANIMATE auszufuehren, so dass evtl. Aenderungen}
{     des Hintergrundes "sofort" sichtbar werden!)                           }
{     Da als Hintergrundseite BACKGNDADR verwendet wird, ist die Verwendung  }
{     der Routine nur fuer den Hintergrundmodus STATIC sinnvoll!}
ASM
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BX,3
 MOV AH,[OFFSET TranslateTab + BX]
 MOV AL,2
 MOV DX,3C4h
 MOV ES,BACKGNDADR
 CLI
 OUT DX,AX
 MOV AL,color
 STOSB
 STI
@offscrn:
END;

PROCEDURE PagePutPixel(x,y:INTEGER; color,pa:Byte); ASSEMBLER;
{ in: x,y    = VIRTUELLE Punktkoordinaten des zu zeichnenden Punktes}
{     color  = Farbwert fuer den zu zeichnenden Punkt}
{     pa     = Grafikseite (0..3), auf der gezeichnet werden soll   }
{     PAGEADR= Grafikseite(nsegment), auf der gezeichnet werden soll}
{     StartVirtualX, StartVirtualY = linke obere Bildschirmecke}
{out: - }
{rem: Der Punkt (x,y) wurde in absolute Bildschirmkoordinaten umgerechnet  }
{     und gezeichnet (sofern er auf dem sichtbaren Bildschirmfenster liegt)}
{     Soll auf die gerade SICHTBARE Seite gezeichnet werden, so ist}
{     beim Aufruf als Seite "1-PAGE" anzugeben!                    }
{     Auch hier gilt, dass der gezeichnete Punkt NICHT automatisch }
{     in den Hintergrundspeicher uebernommen wird, d.h.: er ist nur}
{     bis zum naechsten Animationszyklus (= Aufruf von ANIMATE)    }
{     sichtbar! (Deshalb ist es sinnvoll, diese Routine NACH Aufruf}
{     von ANIMATE auszufuehren, da der gezeichnete Punkt sonst so- }
{     fort wieder verschwindet!)                                   }
{     Sinnvolle Werte fuer "pa" sind nur 0 und 1 (und evtl. BACK-  }
{     GNDPAGE, wenn der Hintergrundmodus STATIC benutzt wird), es  }
{     findet jedoch keine Ueberpruefung statt!}
ASM
 MOV DI,y
 SUB DI,StartVirtualY   {y in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP DI,YMAX
 JG @offscrn
 MOV BX,x
 SUB BX,StartVirtualX   {x in absolute Koordinaten umrechnen}
 JS @offscrn
 CMP BX,XMAX
 JG @offscrn
 SHL DI,1
 MOV DI,CS:[OFFSET gadr + DI]
 {DI = Y*LINESIZE, BX = X, Koordinaten zulaessig}
 MOV AX,BX
 SHR AX,1
 SHR AX,1
 ADD DI,AX    {DI = Y*LINESIZE+(X SHR 2) }
 AND BX,3
 MOV AH,[OFFSET TranslateTab + BX]
 MOV AL,2
 MOV DX,3C4h
 MOV BL,pa    {BH=0 -> BX=Grafikseite}
 SHL BX,1
 ADD BX,OFFSET Segment_Adr+StartIndex*2
 MOV ES,[BX]

 CLI
 OUT DX,AX
 MOV AL,color
 STOSB
 STI
@offscrn:
END;


PROCEDURE OutTextXY(x,y:INTEGER; pa:BYTE; s:STRING);
{ in: (x,y)  = (virtuelle) Startkoordinaten des auszugebenden Textes}
{     s      = auszugebender Textstring                             }
{     pa     = Grafikseite, auf der der Text ausgegeben werden soll }
{     GraphTextColor=Textfarbe                                      }
{     GraphTextBackground=Farbe fuer Texthintergrund;ist dieser Wert}
{            =GraphTextColor, so werden nur die Textpixel gezeichnet}
{             und die umgebenden Pixel unveraendert gelassen (=nor- }
{             males Verhalten von TurboPascal's OutText-Routinen!)  }
{     GraphTextOrientation="vertical" oder "horizontal"             }
{     StartVirtualX,StartVirtualY = linke obere Bildschirmecke      }
{out: Text wurde auf dem Bildschirm ausgegeben                      }
VAR z,b,bit,i:BYTE;
    data:Fontchar;
BEGIN
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)
  THEN BEGIN
        Error:=Err_InvalidPageNumber;
        exit
       END;
 FOR i:=1 TO Length(s) DO
 BEGIN
  data:=FontData[ord(s[i])];
  FOR z:=0 TO FontHeight-1 DO
   BEGIN
    b:=data[z];
    FOR bit:=0 TO FontWidth-1 DO
     IF b and FontMask[bit]<>0
      THEN PagePutPixel(x+bit,y+z,GraphTextColor,pa)
      ELSE IF (GraphTextColor<>GraphTextBackground)
            THEN PagePutPixel(x+bit,y+z,GraphTextBackground,pa);
   END;
  IF GraphTextOrientation=horizontal
   THEN INC(x,FontWidth)
   ELSE INC(y,FontHeight);
 END;
END;

PROCEDURE BackgroundOutTextXY(x,y:INTEGER; s:STRING);
{rem: Wie OutTextXY(), aber es wird in den Hintergrund geschrieben und}
{     nicht in die durch PAGEADR spezifizierte Seite!                 }
{     Da als Hintergrundseite BACKGNDADR verwendet wird, ist die      }
{     Routine nur fuer den Hintergrundmodus STATIC sinnvoll!}
VAR z,b,bit,i:BYTE;
    data:Fontchar;
BEGIN
 FOR i:=1 TO Length(s) DO
 BEGIN
  data:=FontData[ord(s[i])];
  FOR z:=0 TO FontHeight-1 DO
   BEGIN
    b:=data[z];
    FOR bit:=0 TO FontWidth-1 DO
     IF b and FontMask[bit]<>0
      THEN BackgroundPutPixel(x+bit,y+z,GraphTextColor)
      ELSE IF (GraphTextColor<>GraphTextBackground)
            THEN BackgroundPutPixel(x+bit,y+z,GraphTextBackground);
   END;
  IF GraphTextOrientation=horizontal
   THEN INC(x,FontWidth)
   ELSE INC(y,FontHeight);
 END;
END;


FUNCTION Hitdetect(s1,s2:INTEGER):BOOLEAN; ASSEMBLER;
{ in: s1,s2 = Spritepositionsnummern zweier Sprites}
{     SpriteN[s1],SpriteX[s1],SpriteY[s1] = Spritedaten von Sprite s1    }
{     SpriteN[s2],SpriteX[s2],SpriteY[s2] = Spritedaten von Sprite s2    }
{out: TRUE/FALSE fuer "Sprites kollidieren"/"Sprites kollidieren nicht"  }
{rem: Diese Ueberpruefung geschieht punktgenau und ist unabhaengig davon,}
{     ob die Sprites z.Z. gerade sichtbar sind oder nicht.               }
{     Inaktive Sprites (SpriteN[s?]=0) koennen nicht miteinander kollid. }
{     Ein Sprite kann nicht mit sich selbst kollidieren (s1=s2 -> FALSE) }
ASM
     MOV SI,s1               {1.Parameter s1 vom Stack holen}
     MOV DI,s2               {2.Parameter s2 vom Stack holen}
     CMP SI,DI
     JE  @NOHIT1             {Sprite kann sich nicht selbst treffen}
     SHL SI,1
     mov cx,[SI + OFFSET SpriteN]
     jcxz @NOHIT1            {Sprite <>0, d.h.: ueberhaupt aktiv?}
     SHL DI,1
     MOV BX,[DI + OFFSET SpriteN]
     OR  BX,BX               {dto. fuer anderes Sprite}
     JNE @PRUEF2
   @NOHIT1:
     JMP @NOHIT7             {inaktive Sprites koennen auch nicht}
                             {kollidieren -> FALSE zurueckgeben  }
{hier: SI (DI) = Zeiger auf 1. (2.) Sprite in ?WRTD[..] ,}
{      CX (BX) = Spritenummer von Sprite 1 (2)           }
{(etwas spaeter wird dann DS (ES) = Segment der Spr.daten von Spr.1 (2) )}
   @PRUEF2:
     MOV AX,[SI + OFFSET SpriteY]
     MOV DX,[DI + OFFSET SpriteY]
     mov si,[SI + OFFSET SpriteX]  {SI=x1}
     mov di,[DI + OFFSET SpriteX]  {DI=x2}
     shl bx,1                      {BX=Spritenummer2*2}
     mov es,[BX + OFFSET SPRITEAD] {ES=Segment der Spritedaten2}
     mov bx,cx                     {(CX=Spritenummer1)}
     shl bx,1                      {BX=Spritenummer1*2}
     MOV ds,[BX + OFFSET SPRITEAD]

     mov [y1],ax
     mov [y2],dx
     sub dx,ax
     mov CS:WORD PTR @y2_y1+1,dx
     mov [x1],si
     mov [x2],di
     mov dx,di
     sub dx,si
     mov CS:WORD PTR @x2_x1+1,dx
     mov ax,es:[Left]              {AX=Zeiger auf linke Randdaten}
     mov CS:WORD PTR @lirand2+1,ax
     mov ax,es:[Right]             {AX=Zeiger auf rechte Randdaten}
     mov CS:WORD PTR @rerand2+1,ax
     mov ax,es:[Top]               {AX=Zeiger auf obere Randdaten}
     mov CS:WORD PTR @orand2+1,ax
     mov ax,es:[Bottom]            {AX=Zeiger auf untere Randdaten}
     mov CS:WORD PTR @urand2+1,ax
     mov ax,es:[Breite]            {AX=max. Breite in 4er-Gruppen}
     shl al,1
     shl al,1
     mov CS:WORD PTR @breite2+1,ax {*4 = Breite in Punkten}
     mov ax,es:[Hoehe]
     mov CS:WORD PTR @hoehe2+1,ax  {Hoehe von Sprite2 in Punkten}

     MOV AX,[Left]                 {AX=Zeiger auf linke Randdaten}
     MOV CS:WORD PTR @LIRAND1+1,AX
     MOV AX,[Right]                {AX=Zeiger auf rechte Randdaten}
     MOV CS:WORD PTR @RERAND1+1,AX
     MOV AX,[Top]                  {AX=Zeiger auf obere Randdaten}
     MOV CS:WORD PTR @ORAND1+1,AX
     MOV AX,[Bottom]               {AX=Zeiger auf untere Randdaten}
     MOV CS:WORD PTR @URAND1+1,AX
     MOV BX,[Breite]               {BX=max. Breite in 4er-Gruppen}
     SHL BX,1
     SHL BX,1                      {*4 = Breite in Punkten}
     MOV CS:WORD PTR @BREITE1+2,BX

     lea bx,[si+bx-1]              {BX:=x1+breite1-1  (=x1last)}
   @breite2:
     mov bp,1234h                  {Dummywert}
     mov cx,bp                     {CX=breite2 brauchen wir spaeter nochmal}
     lea bp,[di+bp-1]              {BP:=x2+breite2-1  (=x2last)}
     cmp bx,bp
     jle @noex1
     mov bp,bx
   @noex1:                         {hier: BP=max(x1last,x2last)  (=maxx)}
     cmp si,di
     jle @X1_klgl_X2
     xchg si,di
   @X1_klgl_X2:                    {hier: SI=min(x1,x2)  (=minx)}
     stc
     sbb si,bp                     {SI:=minx-maxx-1=-(maxx-minx+1)}
   @breite1:
     add cx,1234h                  {(Dummywert)  CX:=breite1+breite2}
     add cx,si                     {CX:=breite1+breite2-(maxx-minx+1)}
     dec cx           {CX:=breite1+breite2-(maxx-minx+1)-1  (=ueberlappx-1)}
     js @NOHIT2                    {kein Treffer, wenn ueberlappx<=0}
     mov [ueberlappx_1],cx

     mov ax,[Hoehe]
     mov bx,ax                     {BX:=hoehe1}
     mov di,[y1]                   {DI:=y1}
     add ax,di                     {AX:=y1+hoehe1}
     dec ax                        {AX:=y1+hoehe1-1  (=y1last)}
   @hoehe2:
     mov si,1234h
     mov dx,[y2]
     add dx,si                     {DX:=y2+hoehe2}
     dec dx                        {DX:=y2+hoehe2-1  (=y2last)}
     cmp ax,dx
     jge @noex2
     mov ax,dx
   @noex2:                         {hier: AX=max(y1last,y2last)  (=maxy)}
     mov dx,[y2]
     cmp di,dx                     {(DI=y1)}
     jle @noex3
     mov di,dx
   @noex3:                         {hier: DI=min(y1,y2)  (=miny)}
     sub di,ax                     {DI:=miny-maxy=-(maxy-miny)}
     lea ax,[bx+si-2]              {AX:=hoehe1+hoehe2-2}
     add ax,di          {AX:=hoehe1+hoehe2-(maxy-miny+1)-1  (=ueberlappy-1)}
     js @NOHIT2                    {kein Treffer, wenn ueberlappy<=0}
     mov [ueberlappy_1],ax

{hier: AX=ueberlappy-1, CX=ueberlappx-1}
   @x2_x1:
     mov dx,1234h                  {Dummywert}
     xor bx,bx                     {ab jetzt: BX=0 !}
     or dx,dx
     js @X2_X1_kl_0                {if x2-x1>=0 then...}
     mov [hit2xfirst],bx           {...hit2xfirst:=0}
     mov [hit1xfirst],dx           {...hit1xfirst:=x2-x1}
     jmp @Yhits    {SHORT}

{Sprungleiste fuer NOHIT (passt hier gut hin)}
   @NOHIT2:
     JMP @NOHIT7

{jetzt wieder normales Programm}
   @X2_X1_kl_0:                    {else (x2-x1<0)...}
     mov [hit1xfirst],bx           {...hit1xfirst:=0}
     neg dx                        {DX:=x1-x2}
     mov [hit2xfirst],dx           {...hit2xfirst:=x1-x2}

   @Yhits:                         {hier: AX=ueberlappy-1}
   @y2_y1:
     mov dx,1234h                  {Dummywert}
     or dx,dx
     js @Y2_Y1_kl_0                {if y2-y1>=0 then...}
     mov [hit2yfirst],bx           {...hit2yfirst:=0}
     mov [hit1yfirst],dx           {...hit1yfirst:=y2-y1}
     jmp @iterate  {SHORT}
   @Y2_Y1_kl_0:                    {else (y2-y1<0)...}
     mov [hit1yfirst],bx           {...hit1yfirst:=0}
     neg dx                        {DX:=y1-y2}
     mov [hit2yfirst],dx           {...hit2yfirst:=y1-y2}

{Nun werden iterativ die ueberlappenden Zeilen und Spalten exakt geprueft}
   @iterate:
     mov cx,[ueberlappy_1]         {Anzahl der zu vergleichenden Zeilen -1}
     shl cx,1                      {*2, da Word-Werte!}
   @lirand1:
     mov si,1234h                  {Dummywert}
   @lirand2:
     mov di,1234h                  {Dummywert}
   @rerand1:
     mov bx,1234h                  {Dummywert}
   @rerand2:
     mov bp,1234h                  {Dummywert}
     sub bx,si                     {BX:=rerand1-lirand1}
     sub bp,di                     {BP:=rerand2-lirand2}
     mov ax,[hit1yfirst]
     shl ax,1
     add si,ax                  {SI:=1.Zeile, in der Sp.1 mit Sp.2 ueberlappt}
     mov ax,[hit2yfirst]
     shl ax,1
     add di,ax                  {DI:=1.Zeile, in der Sp.2 mit Sp.1 ueberlappt}
     add si,cx                  {dto., letzte Zeile}
     add di,cx
   @one_line:
     mov ax,[si]                   {DS:AX:=x1li[Zeile]}
     mov dx,es:[di]                {ES:DX:=x2li[Zeile]}
     add ax,[x1]                   {AX:=x1li[Zeile]+x1  (=c)}
     add dx,[x2]                   {DX:=x2li[Zeile]+x2  (=d)}
     cmp ax,dx
     jge @C_grgl_D
     mov ax,dx
   @C_grgl_D:                      {hier: AX=max(c,d)}
     mov cx,[si+bx]                {DS:CX:=x1re[Zeile]}
     mov dx,es:[di+bp]             {ES:DX:=x2re[Zeile]}
     add cx,[x1]                   {CX:=x1re[Zeile]+x1  (=a)}
     add dx,[x2]                   {DX:=x2re[Zeile]+x2  (=b)}
     cmp cx,dx
     jle @A_klgl_B
     mov cx,dx
   @A_klgl_B:                      {hier: CX=min(a,b)}
     cmp cx,ax                     {min(a,b)>=max(c,d) ?}
     jge @found_Xhit               {ja: Treffer in X-Richtung gefunden!}
     dec si                        {naechste Zeile (-> Word-Werte!)}
     dec si
     dec di
     dec di
     dec WORD PTR [ueberlappy_1]
     jns @one_line
{kein Treffer in X-Richtung -> ueberhaupt kein Treffer!}
     jmp @NOHIT7

{ansonsten: Treffer in X-Ri., jetzt noch Y-Ri. pruefen (analog zu oben) und }
{"Treffer!" nur dann ausgeben, falls auch mind. 1 Treffer in Y-Ri. existiert}
   @found_Xhit:
     mov cx,[ueberlappx_1]         {Anzahl der zu vergleichenden Spalten -1}
     shl cx,1                      {*2, da Word-Werte!}
   @orand1:
     mov si,1234h                  {Dummywert}
   @orand2:
     mov di,1234h                  {Dummywert}
   @urand1:
     mov bx,1234h                  {Dummywert}
   @urand2:
     mov bp,1234h                  {Dummywert}
     sub bx,si                     {BX:=urand1-orand1}
     sub bp,di                     {BP:=urand2-orand2}
     mov ax,[hit1xfirst]
     shl ax,1                      {*2, da Word-Werte!}
     add si,ax                     {SI:=orand1+2*hit1xfirst}
     mov ax,[hit2xfirst]
     shl ax,1                      {*2, da Word-Werte!}
     add di,ax                     {DI:=orand2+2*hit2xfirst}
     add si,cx
     add di,cx
   @one_column: mov ax,[si]        {AX:=y1ob[Spalte]}
     cmp ax,16000                  {Dummywert fuer "leere Spalte"?}
     je @next_column               {ja, also sicherlich kein Treffer}
     mov dx,es:[di]                {DX:=y2ob[Spalte]}
     cmp dx,16000                  {auch 2.Sprite pruefen: "leere Spalte"?}
     je @next_column               {ja, kein Treffer}
     add ax,[y1]                   {AX:=y1ob+y1  (=c)}
     add dx,[y2]                   {DX:=y2ob+y2  (=d)}
     cmp ax,dx
     jge @C_grgl_D2
     mov ax,dx
   @C_grgl_D2:                     {hier: AX=max(c,d)}
     mov cx,[si+bx]                {DS:CX:=y1un[Spalte]}
     mov dx,es:[di+bp]             {ES:DX:=y2un[Spalte]}
     add cx,[y1]                   {CX:=y1un+y1  (=a)}
     add dx,[y2]                   {DX:=y2un+y2  (=b)}
     cmp cx,dx
     jle @A_klgl_B2
     mov cx,dx
   @A_klgl_B2:                     {hier: CX=min(a,b)}
     cmp cx,ax                     {min(a,b)>=max(c,d) ?}
     jge @HIT2                     {ja: Treffer gefunden!}
   @next_column:
     dec si                        {nein, naechste Spalte (-> Word-Werte!)}
     dec si
     dec di
     dec di
     dec WORD PTR [ueberlappx_1]
     jns @one_column

   @NOHIT7:
     XOR AX,AX                     {als Ergebnis 0 = FALSE zurueckgeben}
     JMP @TREFF_END  {SHORT}
   @HIT2:
     MOV AX,1                      {als Ergebnis 1 = TRUE zurueckgeben}

   @TREFF_END:
     mov dx,seg @DATA              {BP wird von TP wiederhergestellt}
     mov ds,dx
END;


PROCEDURE Animate;
{ in: PAGEADR = aktuelle Grafikseite(nadresse),auf der gezeichnet werden soll}
{     BACKGNDADR = Hintergrundseite(nadresse) }
{     BACKGROUNDMODE = STATIC/SCROLLING fuer festen/scrollbaren Hintergrund  }
{     SpriteN[] = Spritenummern der darzustellenden Sprites }
{     SpriteX[],SpriteY[] = deren zugehoergigen (virtuellen) Koordinaten}
{     StartVirtualX,StartVirtualY = obere linke Bildschirmecke  }
{     (PAGE = aktuell dargestellte Grafikseite) }
{out: PAGE = 0/1, wenn PAGE vorher 1/0 war }
{     PAGEADR = neue, aktuelle Grafikseite(nadresse) }
{rem: Animate loescht den Inhalt der alten Grafik (mithilfe der Hintergrund- }
{     seite), zeichnet alle sichtbaren Sprites, synchronisiert auf das vert. }
{     Retracesignal und schaltet dann auf die so fertiggestellte Seite um.   }
VAR offsetXTiles,offsetYTiles,offsetXPix,offsetYPix:INTEGER;
    leftcut,rightcut,topcut,bottomcut,tiles:WORD;
    x,y,xpix,ypix,xtil,ytil,actindex,randindex,index:INTEGER;
    offscreenFlag:BYTE;
    yt,xt:INTEGER;
BEGIN
 ASM
    CLD
    {zuerst das Hintergrundbild auf die aktuelle Seite kopieren:}
    CMP BackgroundMode,STATIC   {welcher Hintergrundmodus?}
    JE @static_bckgnd
    JMP @scrolling_bckgnd

  @static_bckgnd:
    MOV AX,0F02h                {alle 4 Planes gleichzeitig beschreiben}
    MOV DX,3C4h
    OUT DX,AX
    MOV AX,4105h                {Schreibmodus 1 waehlen}
    MOV DX,3CEh
    OUT DX,AX

    MOV ES,PAGEADR              {Grafikseite mit Hintergrundmuster fuellen}
    MOV DS,BACKGNDADR
    XOR SI,SI
    MOV DI,SI
    MOV CX,PAGESIZE

    REP MOVSB

    MOV AX,SEG @DATA
    MOV DS,AX
    MOV AX,4005h     {Writemode 0 setzen}
    MOV DX,3CEh
    OUT DX,AX

    JMP @Sprites_zeichnen

  {---------------------------------}

  @scrolling_bckgnd:      {ab hier: Hintergrund aus Kacheln zusammensetzen}
    MOV AX,StartVirtualY
    MOV BX,AX             {AX=BX=StartVirtualY}
    SUB AX,BackY1
    ADD AX,15             {offsetYTiles:=(StartVirtualY-BackY1+15)DIV16}
    SAR AX,1
    SAR AX,1
    SAR AX,1
    SAR AX,1
    MOV offsetYTiles,AX
    MOV ytil,AX           {ytil:=offsetYTiles}
    DEC AX
    IMUL XTiles
    DEC AX
    MOV actIndex,AX       {actIndex:=(ytil-1)*XTiles-1, "+xtil" kommt spaeter}

    MOV AX,16
    SUB AX,BX             {BX=StartVirtualY}
    AND AX,$F
    MOV ypix,AX           {ypix:=(16-StartVirtualY) AND $F}
    SUB AX,200
    AND AX,$F
    MOV bottomcut,AX      {bottomcut:=(ypix-200) AND $F}

    AND BX,$F             {offsetYPix:=topcut:=StartVirtualY AND $F}
    MOV topcut,BX
    MOV offsetYPix,BX

    MOV AX,StartVirtualX
    MOV BX,AX             {AX=BX=StartVirtualX}
    SUB AX,BackX1
    ADD AX,15             {offsetXTiles:=(StartVirtualX-BackX1+15)DIV16}
    SAR AX,1
    SAR AX,1
    SAR AX,1
    SAR AX,1
    MOV offsetXTiles,AX
    MOV xtil,AX           {xtil:=offsetXTiles}
    ADD actIndex,AX       {actIndex:=(ytil-1)*XTiles-1+xtil}

    MOV AX,16
    SUB AX,BX             {BX=StartVirtualX}
    AND AX,$F
    MOV xpix,AX           {xpix=rightcut:=(16-StartVirtualX) AND $F}
    MOV rightcut,AX

    AND BX,$F             {offsetXPix:=leftcut:=StartVirtualX AND $F}
    MOV leftcut,BX
    MOV offsetXPix,BX

    MOV AX,(XMAX+1)/16-1
    SUB BL,1              {C=1, wenn leftcut=0}
    ADC AX,0
    MOV tiles,AX          {tiles:=19+ord(leftcut=0)}


    CMP topcut,0          {wenn topcut=0 ist, braucht die oberste}
    JE @do_innertiles     {Tilezeile nicht gesondert gezeichnet werden}

    {oberste Tilezeile:}
    MOV DX,xtil
    MOV xt,DX
    MOV AX,ytil
    DEC AX
    MOV yt,AX
    MOV CL,1
    JS @offscreen
    CMP AX,YTiles
    JAE @offscreen
    DEC CL
  @offscreen:  {CL=0/1 fuer offscreenFlag=false/true=(yt<0) OR (yt>=YTiles}
    MOV offscreenFlag,CL

    CMP leftcut,0          {wenn leftcut=0 ist, muss die linke obere}
    JE @do_upperinnertiles {Ecke nicht gesondert gezeichnet werden  }

    {linke obere Eck-Tile zeichnen:}    {CL=offscreenFlag, DX=xt}
    XOR SI,SI   {Tileindex bestimmen:}
    DEC CL      {IF offscreenFlag OR (xt-1<0) OR (xt-1>=XTiles) }
    JZ @go1     { THEN index:=0 ELSE index:=actIndex}
    DEC DX
    JS @go1
    CMP DX,XTiles
    JAE @go1
    MOV SI,actIndex  {=yt*XTiles+(xt-1)}
  @go1:

       {PROCEDURE DrawUpperLeftTile(leftcut,topcut:INTEGER; index:WORD);}
       { in: leftcut = Anzahl der links abzuschneidenden Tile-Spalten}
       {     topcut  = dto., oben}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR bei (0,0) gezeichnet}
       {rem: Tile wurde links und oben entsprechend geschnitten}
       {     leftcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)}
       {     topcut  muss zwischen 0 und 15 liegen, 16 ist verboten}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,topcut
       MOV CX,16
       SUB CX,SI   {CX:=16-topcut = Anzahl zu zeichnende Zeilen}
       SHL SI,1
       SHL SI,1    {fuer jede oben abgeschnittene Zeile 4 Bytes}
       ADD SI,AX   {zur (Offset-)Quelladresse in Page 3 addieren}

       XOR DI,DI   {die erste Zieladresse ist DI:=0*LINESIZE+(0 div 4)=0}

       MOV AX,leftcut
       MOV BX,AX   {Kopie von leftcut in BX aufbewahren}
       SHR AX,1
       SHR AX,1
       ADD SI,AX   {SI um cutoff in Bytes weitersetzen=leftcut div 4}

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,BX   {BP:=16+3-leftcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-leftcut) SHR 2 }


       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3CEh
       AND BL,3
       MOV AH,BL   {AH:=leftcut mod 4}
       MOV AL,4

       JNE @mode0a {nur falls leftcut mod 4=0 ist koennen wir WriteMode1 verwenden}

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3CEh, BP=16+3-leftcut}
       {    Dass BP "3 zu gross ist" stoert nicht, da dieser Code nur ausge-  }
       {    fuehrt wird, wenn leftcut mod 4=0, also 16+3-leftcut=...11b, die  }
       {    "11b" werden also beim Rechtsschieben eh abgeschnitten!}
       MOV AX,4105h
       OUT DX,AX   {WriteMode 1 waehlen}
       MOV DX,3C4h
       MOV AX,0F02h    {alle 4 Planes gleichzeitig bearbeiten}
       OUT DX,AX
       JMP @lastPlane1  {abkuerzen}
       {---}

     @mode0a:
       OUT DX,AX   {aktuelle Leseplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3C4h
       MOV AX,0102h    {Schreibplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-leftcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile1a:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile1a
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0202h  {Schreibplane 1 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap1a
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap1a:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+2-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile2a:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile2a
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0402h  {Schreibplane 2 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap2a
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap2a:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+1-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile3a:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile3a
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0802h  {Schreibplane 3 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap3a
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap3a:
       OUT DX,AX


       DEC BP        {BP:=16-leftcut}
     @lastplane1:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BP:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

                       {Quell- und Zieladresse nicht mehr retten}
       MOV BX,CX   {BX:=Zeilenzaehler}
     @eineZeile4a:
       MOV CX,BP   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BX
       JNZ @eineZeile4a

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

  {Nun alle anderen oberen Kacheln, die nur oben (aber nicht seitlich)}
  {abgeschnitten sind: }
  @do_upperinnertiles:
    MOV AX,xpix
    MOV x,AX
    INC actIndex

  @repeat1:  {Schleife wird genau "tiles" mal durchlaufen}
    MOV CL,offscreenFlag
    XOR SI,SI  {Tileindex bestimmen:}
    DEC CL     {IF offscreenFlag OR (xt<0) OR (xt>=XTiles) }
    JZ @go2    { THEN index:=0 ELSE index:=actIndex =yt*XTiles+xt}
    MOV DX,xt
    OR DX,DX
    JS @go2
    CMP DX,XTiles
    JAE @go2
    MOV SI,actIndex
  @go2:


       {PROCEDURE DrawUpperTile(x,topcut:INTEGER; index:WORD);}
       { in: (x,0) = linke obere Ecke der zu zeichnenden Tile,}
       {     topcut= Anzahl abzuschneidende Zeilen}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile muss links, rechts (und unten) ganz auf dem Bildschirm sein}
       {     topcut muss im Stapel liegen (da DS andersweitig belegt wird!}
       {     topcut muss Werte zwischen 0..15 haben, 16 ist unzulaessig!}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1    {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV SI,topcut  {Dazu kommen die oben abgeschnittenen Zeilen:}
       MOV CX,16      {fuer jede Zeile 4 Bytes}
       SUB CX,SI      {CX:=16-topcut = zu zeichnende Zeilen}
       SHL SI,1
       SHL SI,1
       ADD SI,AX   {SI = Zeiger auf erstes zu kopierendes Tilebyte}

       {Die erste Zieladresse ist DI:=0*LINESIZE+(x div 4) = x div 4}
       MOV DI,x
       MOV BX,DI   {Kopie von X in BX aufbewahren}
       SHR DI,1
       SHR DI,1

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,CX
       SHL BP,1
       SHL BP,1    {BP := (zu zeichnende Zeilen)*4 }

       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3C4h
       MOV AL,2
       AND BX,3    {BX:=Start-Schreibplane (Start-LESEplane=0)}
       JNE @mode0b {nur falls x mod 4=0 ist koennen wir WriteMode1 verwenden}

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!}
       MOV AH,0Fh  {alle 4 Planes gleichzeitig bearbeiten}
       OUT DX,AX
       MOV AX,4105h
       MOV DX,3CEh
       OUT DX,AX   {WriteMode 1 waehlen}
       MOV BX,CX   {BX:=CX=zu zeichnende Zeilen}
       JMP @lastPlane2  {abkuerzen}
       {---}

     @mode0b:
       MOV AH,CS:[OFFSET CS_TranslateTab+BX]
       OUT DX,AX
       PUSH AX     {aktuelle Schreibplane merken}

       MOV DX,3CEh
       MOV AX,0004h    {aktuelle Leseplane 0 waehlen}
       OUT DX,AX

       MOV AX,LINESIZE-4 {Korrekturfaktor fuer Zeilenadressen}
       MOV BX,CX   {zu zeichnende Zeilenzahl nach BX retten}
     @eineZeile1b:
       MOVSB
       MOVSB
       MOVSB
       MOVSB
       ADD DI,AX   {DI auf naechste Zeile setzen}
       LOOP @eineZeile1b

       MOV AX,0104h  {DX=3CEh -> Leseplane 1 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap1b
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap1b:
       OUT DX,AX
       PUSH AX
       SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
       SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
       SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
       SUB SI,BP     {SI auch zuruecksetzen}


       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=zu zeichnende Zeilenanzahl}
     @eineZeile2b:
       MOVSB
       MOVSB
       MOVSB
       MOVSB
       ADD DI,AX   {DI auf naechste Zeile setzen}
       LOOP @eineZeile2b

       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap2b
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap2b:
       OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
       PUSH AX
       MOV DX,3CEh
       MOV AX,0204h  {Leseplane 2 setzen}
       OUT DX,AX
       SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
       SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
       SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
       SUB SI,BP     {SI auch zuruecksetzen}


       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=zu zeichnende Zeilenzahl}
     @eineZeile3b:
       MOVSB
       MOVSB
       MOVSB
       MOVSB
       ADD DI,AX   {DI auf naechste Zeile setzen}
       LOOP @eineZeile3b

       MOV AX,0304h
       OUT DX,AX     {DX=3CEh -> Leseplane 3 waehlen}
       MOV DX,3C4h
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap3b
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap3b:
       OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
       SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
       SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
       SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
       SUB SI,BP     {SI auch zuruecksetzen}


     @lastPlane2:
       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=zu zeichnende Zeilenzahl}
     @eineZeile4b:
       MOVSB
       MOVSB
       MOVSB
       MOVSB
       ADD DI,AX   {DI auf naechste Zeile setzen}
       LOOP @eineZeile4b

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

    INC xt        {Tilezaehler in X-Richtung erhoehen}
    INC actIndex  {wg. actIndex=yt*XTiles+xt auch actIndex erhoehen}
    MOV AX,x      {aktuelle X-Koord. auch weitersetzen: x:=x+16}
    ADD AX,16
    MOV x,AX
    CMP AX,XMAX+1-16
    JBE @repeat1  {bis rechte obere Ecke erreicht}

    {Jetzt obere rechte Eck-Kachel - falls nicht schon gezeichnet:}
    CMP AX,XMAX+1
    JE @label1

    MOV CL,offscreenFlag  {ja, Ecke muss noch gezeichnet werden}
    XOR SI,SI  {Tileindex bestimmen:}
    DEC CL     {IF offscreenFlag OR (xt<0) OR (xt>=XTiles) }
    JZ @go3    { THEN index:=0 ELSE index:=actIndex =yt*XTiles+xt}
    MOV DX,xt
    OR DX,DX
    JS @go3
    CMP DX,XTiles
    JAE @go3
    MOV SI,actIndex
  @go3:


       {PROCEDURE DrawUpperRightTile(x,rightcut,topcut:INTEGER; index:WORD);}
       { in: (x,0) = linke obere Ecke der zu zeichnenden Tile}
       {     rightcut = Anzahl der rechts abzuschneidenden Tile-Spalten}
       {     topcut   = dto., oben}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile wurde rechts und oben entsprechend geschnitten}
       {     topcut muss zwischen 0 und 15 liegen, 16 ist verboten}
       {     rightcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)!}
       {     rightcut koennte auch berechnet werden (fuer x>xmax-16) gemaess}
       {     rightcut := x+15-xmax }
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,topcut
       MOV CX,16
       SUB CX,SI   {CX:=16-topcut = Anzahl zu zeichnende Zeilen}
       SHL SI,1
       SHL SI,1    {fuer jede oben abgeschnittene Zeile 4 Bytes}
       ADD SI,AX   {zur (Offset-)Quelladresse in Page 3 addieren}

       MOV DI,x    {erste Zieladresse ist DI:=0*LINESIZE +(x div 4)=x div 4}
       MOV BX,DI   {Kopie von x nach BX}
       SHR DI,1
       SHR DI,1

       MOV AX,rightcut

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,AX   {BP:=16+3-rightcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-rightcut) SHR 2 }
       MOV AH,AL   {rightcut nach AH retten}

       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3C4h
       MOV AL,2
       AND BX,3

       JNE @mode0c {nur falls x mod 4=0 _und_ rightcut mod 4=0 ist koennen}
       AND AH,3    {wir WriteMode1 verwenden!}
       JNE @mode0c

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3C4h, BP=16+3-rightcut}
       MOV AH,0Fh
       OUT DX,AX    {alle 4 Planes gleichzeitig bearbeiten}
       MOV DX,3CEh
       MOV AX,4105h {WriteMode 1 waehlen}
       OUT DX,AX
       SUB BP,3     {BP auf die richtige Groesse bringen}
       JMP @lastPlane3  {abkuerzen}
       {---}

     @mode0c:
       MOV AH,CS:[OFFSET CS_TranslateTab +BX]
       OUT DX,AX   {aktuelle Schreibplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3CEh
       MOV AX,0004h    {Leseplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-rightcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile1c:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile1c
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3CEh
       MOV AX,0104h  {Leseplane 1 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap1c
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap1c:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+2-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile2c:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile2c
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3CEh
       MOV AX,0204h  {Leseplane 2 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap2c
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap2c:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+1-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile3c:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile3c
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3CEh
       MOV AX,0304h  {Leseplane 3 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap3c
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap3c:
       OUT DX,AX
                     {Wert nicht mehr pushen!}


       DEC BP        {BP:=16-rightcut}
     @lastplane3:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BP:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

                       {Quell- und Zieladresse nicht mehr retten}
       MOV BX,CX   {BX:=Zeilenzaehler}
     @eineZeile4c:
       MOV CX,BP   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BX
       JNZ @eineZeile4c

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

  @label1:
    MOV AX,actIndex  {actIndex fuer naechste Tilezeile justieren:}
    STC              {actIndex:=actIndex-tiles+1}
    SBB AX,tiles
    MOV actIndex,AX

  {Jetzt alle vollstaendig im Inneren des Bildschirms gelegenen }
  {(d.h.: nicht abgeschnittene) Tiles zeichnen:}
  @do_innertiles:
    MOV AX,ypix
    MOV y,AX
    MOV AX,actIndex
    ADD AX,XTiles
    MOV RandIndex,AX
    INC AX
    MOV actIndex,AX

  @repeat2:
    MOV AX,ytil
    MOV CL,1
    OR AX,AX
    JS @go4
    CMP AX,YTiles
    JAE @go4
    DEC CL
  @go4:
    MOV offscreenFlag,CL
    MOV AX,xpix
    MOV x,AX
    MOV AX,offsetXTiles
    MOV xtil,AX

  @repeat3:
    MOV CL,offscreenFlag
    XOR SI,SI
    DEC CL
    JZ @go5
    OR AX,AX
    JS @go5
    CMP AX,XTiles
    JAE @go5
    MOV SI,actIndex
  @go5:


       {PROCEDURE DrawInnerTile(x,y:INTEGER; index:WORD);}
       { in: (x,y) = linke obere Ecke der zu zeichnenden Tile,}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile muss ganz auf dem Bildschirm sein, es findet}
       {     kein Clipping statt}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV DI,y    {die erste Zieladresse ist DI:=y*LINESIZE+(x div 4)}
       SHL DI,1
       MOV DI,CS:[OFFSET GADR + DI]
       MOV AX,x
       MOV BX,AX   {Kopie von X in BX aufbewahren}
       SHR AX,1
       SHR AX,1
       ADD DI,AX

       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3C4h
       MOV AL,2
       AND BX,3    {BX:=Start-Schreibplane (Start-LESEplane=0)}
       JNE @mode0d {nur falls x mod 4=0 ist koennen wir WriteMode1 verwenden}

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!}
       MOV AH,0Fh  {alle 4 Planes gleichzeitig bearbeiten}
       OUT DX,AX
       MOV AX,4105h
       MOV DX,3CEh
       OUT DX,AX   {WriteMode 1 waehlen}
       MOV BX,4
       JMP @lastPlane4  {abkuerzen}
       {---}

     @mode0d:
       MOV AH,CS:[OFFSET CS_TranslateTab+BX]
       OUT DX,AX
       PUSH AX     {aktuelle Schreibplane merken}

       MOV DX,3CEh
       MOV AX,0004h    {aktuelle Leseplane 0 waehlen}
       OUT DX,AX

       MOV BX,AX   {16 horizontale Punkte = 4 Bytes pro Zeile: BX:=4}
                   {(nutzt aus, dass AX zufaelligerweise gerade 4 ist)}
       MOV AX,LINESIZE-4 {Korrekturfaktor fuer Zeilenadressen}

       MOV CX,BX   {CX:=4}
       REP MOVSB   {1.Zeile zeichnen}
       ADD DI,AX   {DI auf naechste Zeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX

       MOV AX,0104h  {DX=3CEh -> Leseplane 1 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap1d
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap1d:
       OUT DX,AX
       PUSH AX
       SUB DI,16*LINESIZE   {DI wieder auf Ursprungsadresse zuruecksetzen}
       SUB SI,16*4          {SI auch}


       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=4}
       REP MOVSB   {1.Zeile zeichnen}
       ADD DI,AX   {DI auf naechste Zeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX

       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap2d
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap2d:
       OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
       PUSH AX
       MOV DX,3CEh
       MOV AX,0204h  {Leseplane 2 setzen}
       OUT DX,AX
       SUB DI,16*LINESIZE
       SUB SI,16*4


       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=4}
       REP MOVSB   {1.Zeile zeichnen}
       ADD DI,AX   {DI auf naechste Zeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX

       MOV AX,0304h
       OUT DX,AX     {DX=3CEh -> Leseplane 3 waehlen}
       MOV DX,3C4h
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap3d
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap3d:
       OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
       SUB DI,16*LINESIZE
       SUB SI,16*4


     @lastPlane4:
       MOV AX,LINESIZE-4
       MOV CX,BX   {CX:=4}
       REP MOVSB   {1.Zeile zeichnen}
       ADD DI,AX   {DI auf naechste Zeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       MOV CX,BX
       REP MOVSB   {16.Zeile}


       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

    INC actIndex
    MOV AX,xtil
    INC AX
    MOV xtil,AX
    MOV DX,x
    ADD DX,16
    MOV x,DX
    CMP DX,XMAX+1-16
    JBE @repeat3

    INC ytil
    MOV AX,actIndex
    SUB AX,tiles
    ADD AX,XTiles
    MOV actIndex,AX
    MOV AX,y
    ADD AX,16
    MOV y,AX
    CMP AX,YMAX+1-16
    JBE @repeat2

    {Jetzt die untere rechte Tile - falls sie nicht bereits schon gezeichnet}
    {wurde: }
    CMP bottomcut,0
    JE @do_raender
    MOV DX,offsetXTiles
    MOV xt,DX
    MOV AX,ytil
    MOV yt,AX
    MOV CL,1
    OR AX,AX
    JS @go6
    CMP AX,YTiles
    JAE @go6
    DEC CL
  @go6:
    MOV offscreenFlag,CL
    CMP leftcut,0
    JE @label2
    XOR SI,SI
    DEC CL
    JZ @go7
    DEC DX  {DX=xt-1}
    JS @go7
    CMP DX,XTiles
    JAE @go7
    MOV SI,actIndex
    DEC SI
  @go7:

       {PROCEDURE DrawLowerLeftTile(y,leftcut,bottomcut:INTEGER; index:WORD);}
       { in: (0,y) = linke obere Ecke der zu zeichnenden Tile}
       {     leftcut = Anzahl der links abzuschneidenden Tile-Spalten}
       {     bottomcut = dto., unten}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile wurde links und unten entsprechend geschnitten}
       {     leftcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)}
       {     bottomcut muss zwischen 0 und 15 liegen, 16 ist verboten}
       {     bottomcut koennte auch ausgerechnet werden ueber:}
       {     bottomcut:=y+15-ymax}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV DI,y    {erste Zieladresse ist DI:=y*LINESIZE+(0 div 4)=y*LINESIZE}
       SHL DI,1
       MOV DI,CS:[OFFSET GADR + DI]

       MOV AX,leftcut
       MOV BX,AX   {Kopie von leftcut in BX aufbewahren}
       SHR AX,1
       SHR AX,1
       ADD SI,AX   {SI um cutoff in Bytes weitersetzen=leftcut div 4}

       MOV CX,16
       SUB CX,bottomcut  {CX:=16-bottomcut = zu zeichnende Zeilen}

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,BX   {BP:=16+3-leftcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-leftcut) SHR 2 }


       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3CEh
       AND BL,3
       MOV AH,BL   {AH:=leftcut mod 4}
       MOV AL,4

       JNE @mode0e {nur falls leftcut mod 4=0 ist koennen wir WriteMode1 verwenden}

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3CEh, BP=16+3-leftcut}
       {    Dass BP "3 zu gross ist" stoert nicht, da dieser Code nur ausge-  }
       {    fuehrt wird, wenn leftcut mod 4=0, also 16+3-leftcut=...11b, die  }
       {    "11b" werden also beim Rechtsschieben eh abgeschnitten!}
       MOV AX,4105h
       OUT DX,AX   {WriteMode 1 waehlen}
       MOV DX,3C4h
       MOV AX,0F02h    {alle 4 Planes gleichzeitig bearbeiten}
       OUT DX,AX
       JMP @lastPlane5  {abkuerzen}
       {---}

     @mode0e:
       OUT DX,AX   {aktuelle Leseplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3C4h
       MOV AX,0102h    {Schreibplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-leftcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile1d:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile1d
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0202h  {Schreibplane 1 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap1e
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap1e:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+2-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile2d:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile2d
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0402h  {Schreibplane 2 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap2e
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap2e:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+1-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile3d:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile3d
       POP CX
       POP BP
       POP DI
       POP SI

       MOV DX,3C4h
       MOV AX,0802h  {Schreibplane 3 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap3e
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap3e:
       OUT DX,AX
                     {Wert nicht mehr pushen!}


       DEC BP        {BP:=16-leftcut}
     @lastplane5:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BP:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

       MOV BX,CX   {BX:=Zeilenzaehler}
     @eineZeile4d:
       MOV CX,BP   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BX
       JNZ @eineZeile4d

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

  @label2:
    MOV AX,xpix
    MOV x,AX

    {Jetzt die unteren Tiles, die nicht seitlich abgeschnitten sind:}
    @repeat4:
      MOV CL,offscreenFlag
      XOR SI,SI
      DEC CL
      JZ @go8
      MOV AX,xt
      OR AX,AX
      JS @go8
      CMP AX,XTiles
      JAE @go8
      MOV SI,actIndex
    @go8:

         {PROCEDURE DrawLowerTile(x,y,bottomcut:INTEGER; index:WORD);}
         { in: (x,y) = linke obere Ecke der zu zeichnenden Tile}
         {     bottomcut = Anzahl der unten abzuschneidenden Tile-Zeilen}
         {     SI = index = Tilenummer}
         {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
         {rem: Tile wurde unten entsprechend geschnitten}
         {     bottomcut muss zwischen 0 und 15 liegen, 16 ist verboten!}
         {     bottomcut koennte auch ausgerechnet werden ueber:}
         {     bottomcut:=y+15-ymax}
         MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
         XOR AL,AL

         SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
         RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
         SHR AH,1    {rechtsschieben!}
         RCR AL,1
         MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

         MOV DI,y    {die erste Zieladresse ist DI:=y*LINESIZE+(x div 4)}
         SHL DI,1
         MOV DI,CS:[OFFSET GADR + DI]
         MOV AX,x
         MOV BX,AX   {Kopie von X in BX aufbewahren}
         SHR AX,1
         SHR AX,1
         ADD DI,AX

         MOV CX,16
         SUB CX,bottomcut  {CX:=zu zeichnende Zeilenzahl = 16-bottomcut}

         {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
         {verwendet werden!}
         PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
         MOV BP,CX
         SHL BP,1
         SHL BP,1    {BP := (zu zeichnende Zeilen)*4 }

         MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
         MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

         MOV DX,3C4h
         MOV AL,2
         AND BX,3    {BX:=Start-Schreibplane (Start-LESEplane=0)}
         JNE @mode0f {nur falls x mod 4=0 ist koennen wir WriteMode1 verwenden}

         {--- "Abkuerzung" ueber WriteMode 1 moeglich!}
         MOV AH,0Fh  {alle 4 Planes gleichzeitig bearbeiten}
         OUT DX,AX
         MOV AX,4105h
         MOV DX,3CEh
         OUT DX,AX   {WriteMode 1 waehlen}
         MOV BX,CX   {BX:=CX=zu zeichnende Zeilen}
         JMP @lastPlane6  {abkuerzen}
         {---}

       @mode0f:
         MOV AH,CS:[OFFSET CS_TranslateTab+BX]
         OUT DX,AX
         PUSH AX     {aktuelle Schreibplane merken}

         MOV DX,3CEh
         MOV AX,0004h    {aktuelle Leseplane 0 waehlen}
         OUT DX,AX

         MOV AX,LINESIZE-4 {Korrekturfaktor fuer Zeilenadressen}
         MOV BX,CX   {zu zeichnende Zeilenzahl nach BX retten}
       @eineZeile1e:
         MOVSB
         MOVSB
         MOVSB
         MOVSB
         ADD DI,AX   {DI auf naechste Zeile setzen}
         LOOP @eineZeile1e

         MOV AX,0104h  {DX=3CEh -> Leseplane 1 waehlen}
         OUT DX,AX

         MOV DX,3C4h   {naechste Schreibplane:}
         POP AX
         SHL AH,1
         CMP AH,16
         JNE @nowrap1f
         MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
         INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
       @nowrap1f:
         OUT DX,AX
         PUSH AX
         SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
         SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
         SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
         SUB SI,BP     {SI auch zuruecksetzen}


         MOV AX,LINESIZE-4
         MOV CX,BX   {CX:=zu zeichnende Zeilenanzahl}
       @eineZeile2e:
         MOVSB
         MOVSB
         MOVSB
         MOVSB
         ADD DI,AX   {DI auf naechste Zeile setzen}
         LOOP @eineZeile2e

         POP AX
         SHL AH,1
         CMP AH,16
         JNE @nowrap2f
         MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
         INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
       @nowrap2f:
         OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
         PUSH AX
         MOV DX,3CEh
         MOV AX,0204h  {Leseplane 2 setzen}
         OUT DX,AX
         SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
         SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
         SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
         SUB SI,BP     {SI auch zuruecksetzen}


         MOV AX,LINESIZE-4
         MOV CX,BX   {CX:=zu zeichnende Zeilenzahl}
       @eineZeile3e:
         MOVSB
         MOVSB
         MOVSB
         MOVSB
         ADD DI,AX   {DI auf naechste Zeile setzen}
         LOOP @eineZeile3e

         MOV AX,0304h
         OUT DX,AX     {DX=3CEh -> Leseplane 3 waehlen}
         MOV DX,3C4h
         POP AX
         SHL AH,1
         CMP AH,16
         JNE @nowrap3f
         MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
         INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
       @nowrap3f:
         OUT DX,AX     {DX=3C4h -> Schreibplane setzen}
         SHL BX,1      {DI wieder auf Ursprungsadresse zuruecksetzen:}
         SUB DI,CS:[OFFSET GADR + BX] {dazu DI um (zu zeichnende Zeilen)*LINESIZE}
         SHR BX,1      {verringern (N.B.: BX=zu zeichnende Zeilen)}
         SUB SI,BP     {SI auch zuruecksetzen}


       @lastPlane6:
         MOV AX,LINESIZE-4
         MOV CX,BX   {CX:=zu zeichnende Zeilenzahl}
       @eineZeile4e:
         MOVSB
         MOVSB
         MOVSB
         MOVSB
         ADD DI,AX   {DI auf naechste Zeile setzen}
         LOOP @eineZeile4e

         {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
         {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
         MOV AX,4005h
         MOV DX,3CEh
         OUT DX,AX
         {---}

         POP BP
         MOV AX,SEG @Data  {DS wiederherstellen}
         MOV DS,AX

    INC xt
    INC actIndex
    MOV AX,x
    ADD AX,16
    MOV x,AX
    CMP AX,XMAX+1-16
    JBE @repeat4

    {Jetzt evtl. noch die untere rechte Eck-Tile:}
    CMP AX,XMAX+1
    JE @do_raender
    MOV CL,offscreenFlag
    XOR SI,SI
    DEC CL
    JZ @go9
    MOV AX,xt
    OR AX,AX
    JS @go9
    CMP AX,XTiles
    JAE @go9
    MOV SI,actIndex
  @go9:

       {PROCEDURE DrawLowerRightTile(x,y,rightcut,bottomcut:INTEGER; index:WORD);}
       { in: (x,y) = linke obere Ecke der zu zeichnenden Tile}
       {     rightcut = Anzahl der rechts abzuschneidenden Tile-Spalten}
       {     bottomcut = dto., unten}
       {     bottomcut muss zwischen 0 und 15 liegen, 16 ist verboten}
       {     bottomcut koennte auch ausgerechnet werden ueber:}
       {     bottomcut:=y+15-ymax}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile wurde rechts und unten entsprechend geschnitten}
       {     rightcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)!}
       {     rightcut koennte auch berechnet werden (fuer x>xmax-16) gemaess}
       {     rightcut := x+15-xmax }
       {     bottomcut muss zwischen 0 und 15 liegen, 16 ist verboten}
       {     bottomcut koennte auch ausgerechnet werden ueber:}
       {     bottomcut:=y+15-ymax}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV DI,y    {die erste Zieladresse ist DI:=y*LINESIZE+(x div 4)}
       SHL DI,1
       MOV DI,CS:[OFFSET GADR + DI]
       MOV AX,x
       MOV BX,AX   {Kopie von x nach BX}
       SHR AX,1
       SHR AX,1
       ADD DI,AX

       MOV AX,rightcut
       MOV CX,16
       SUB CX,bottomcut  {CX:=16-bottomcut = zu zeichnende Zeilen}

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,AX   {BP:=16+3-rightcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-rightcut) SHR 2 }
       MOV AH,AL   {rightcut nach AH retten}


       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3C4h
       MOV AL,2
       AND BX,3

       JNE @mode0g {nur falls x mod 4=0 _und_ rightcut mod 4=0 ist koennen}
       AND AH,3    {wir WriteMode1 verwenden!}
       JNE @mode0g

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3C4h, BP=16+3-rightcut}
       MOV AH,0Fh
       OUT DX,AX    {alle 4 Planes gleichzeitig bearbeiten}
       MOV DX,3CEh
       MOV AX,4105h {WriteMode 1 waehlen}
       OUT DX,AX
       SUB BP,3     {BP auf die richtige Groesse bringen}
       JMP @lastPlane7  {abkuerzen}
       {---}

     @mode0g:
       MOV AH,CS:[OFFSET CS_TranslateTab +BX]
       OUT DX,AX   {aktuelle Schreibplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3CEh
       MOV AX,0004h    {Leseplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-rightcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile1f:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile1f
       POP CX
       POP BP
       POP DI
       POP SI


       MOV DX,3CEh
       MOV AX,0104h  {Leseplane 1 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap1g
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap1g:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+2-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile2f:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile2f
       POP CX
       POP BP
       POP DI
       POP SI


       MOV DX,3CEh
       MOV AX,0204h  {Leseplane 2 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap2g
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap2g:
       OUT DX,AX
       PUSH AX


       DEC BP        {BP:=16+1-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       PUSH SI     {Quell- und Zieladresse fuer weitere Planes retten}
       PUSH DI
       PUSH BP     {BP retten}
       PUSH CX     {CX = Zeilenzaehler retten}
       MOV BP,CX   {BP:=Zeilenzaehler}
     @eineZeile3f:
       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BP
       JNZ @eineZeile3f
       POP CX
       POP BP
       POP DI
       POP SI


       MOV DX,3CEh
       MOV AX,0304h  {Leseplane 3 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap3g
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap3g:
       OUT DX,AX
                     {Wert nicht mehr pushen!}


       DEC BP        {BP:=16-rightcut}
     @lastplane7:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BX:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

       MOV BX,CX   {BX:=Zeilenzaehler}
     @eineZeile4g:
       MOV CX,BP   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {eine Zeile uebertragen}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       DEC BX
       JNZ @eineZeile4g

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

  {Nun noch die Kacheln links und/oder rechts entlang des Bildschirmrandes:}
  @do_raender:
    CMP leftcut,0  {oder rightcut, denn da 320 durch 16 teilbar ist,}
    JE @ende       {ist leftcut=0  <=>  rightcut=0 }

    MOV AX,offsetXTiles
    DEC AX
    MOV xt,AX
    MOV AX,ypix
    MOV y,AX
    INC tiles
    MOV AX,offsetYTiles
    MOV yt,AX

  @repeat5:
    MOV CL,1
    OR AX,AX
    JS @go10
    CMP AX,YTiles
    JAE @go10
    DEC CL
  @go10:
    MOV offscreenFlag,CL
    XOR SI,SI
    DEC CL
    JZ @go11
    MOV AX,xt
    OR AX,AX
    JS @go11
    CMP AX,XTiles
    JAE @go11
    MOV SI,RandIndex
  @go11:


       {PROCEDURE DrawLeftTile(y,leftcut:INTEGER; index:WORD);}
       { in: (0,y) = linke obere Ecke der zu zeichnenden Tile}
       {     leftcut = Anzahl der links abzuschneidenden Tile-Spalten}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile wurde links entsprechend geschnitten}
       {     leftcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)}
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV DI,y    {erste Zieladresse ist DI:=y*LINESIZE+(0 div 4)=y*LINESIZE}
       SHL DI,1
       MOV DI,CS:[OFFSET GADR + DI]

       MOV AX,leftcut
       MOV BX,AX   {Kopie von leftcut in BX aufbewahren}
       SHR AX,1
       SHR AX,1
       ADD SI,AX   {SI um cutoff in Bytes weitersetzen=leftcut div 4}

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,BX   {BP:=16+3-leftcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-leftcut) SHR 2 }


       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3CEh
       AND BL,3
       MOV AH,BL   {AH:=leftcut mod 4}
       MOV AL,4

       JNE @mode0h {nur falls leftcut mod 4=0 ist koennen wir WriteMode1 verwenden}

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3CEh, BP=16+3-leftcut}
       {    Dass BP "3 zu gross ist" stoert nicht, da dieser Code nur ausge-  }
       {    fuehrt wird, wenn leftcut mod 4=0, also 16+3-leftcut=...11b, die  }
       {    "11b" werden also beim Rechtsschieben eh abgeschnitten!}
       MOV AX,4105h
       OUT DX,AX   {WriteMode 1 waehlen}
       MOV DX,3C4h
       MOV AX,0F02h    {alle 4 Planes gleichzeitig bearbeiten}
       OUT DX,AX
       JMP @lastPlane8  {abkuerzen}
       {---}

     @mode0h:
       OUT DX,AX   {aktuelle Leseplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3C4h
       MOV AX,0102h    {Schreibplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-leftcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3C4h
       MOV AX,0202h  {Schreibplane 1 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap1h
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap1h:
       OUT DX,AX
       PUSH AX
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4


       DEC BP        {BP:=16+2-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane1_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3C4h
       MOV AX,0402h  {Schreibplane 2 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap2h
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap2h:
       OUT DX,AX
       PUSH AX
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4


       DEC BP        {BP:=16+1-leftcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane2_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3C4h
       MOV AX,0802h  {Schreibplane 3 waehlen}
       OUT DX,AX

       MOV DX,3CEh   {naechste Leseplane:}
       POP AX
       INC AH
       AND AH,3      {um 1 erhoehen MOD 4}
       JNE @nowrap3h
       INC SI        {nach Plane 3 kommt wieder Plane 0, aber}
                     {die Quelladresse hat sich um 1 Byte erhoeht}
     @nowrap3h:
       OUT DX,AX
                     {Wert nicht mehr pushen!}
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4



       DEC BP        {BP:=16-leftcut}
     @lastplane8:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BP:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

       MOV CX,BP   {CX:=Bytes_je_Plane2_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BP
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {16.Zeile}

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

    MOV CL,offscreenFlag
    XOR SI,SI
    DEC CL
    JZ @go12
    MOV AX,xt
    MOV DX,tiles
    ADD AX,DX
    JS @go12
    CMP AX,XTiles
    JAE @go12
    MOV SI,RandIndex
    ADD SI,DX
  @go12:


       {PROCEDURE DrawRightTile(x,y,rightcut:INTEGER; index:WORD);}
       { in: (x,y) = linke obere Ecke der zu zeichnenden Tile}
       {     rightcut = Anzahl der rechts abzuschneidenden Tile-Spalten}
       {     SI = index = Tilenummer}
       {out: Tile wurde auf aktueller Seite PAGEADR gezeichnet}
       {rem: Tile wurde rechts entsprechend geschnitten}
       {     rightcut muss zwischen 0 und 15 liegen, 16 ist erlaubt (aber unsinnig)!}
       {     rightcut koennte auch berechnet werden (fuer x>xmax-16) gemaess}
       {     rightcut := x+15-xmax }
       MOV AH,[OFFSET BackTile +SI]  {AH:=BackTile[index]}
       XOR AL,AL

       SHR AH,1    {AX:=Kachel*64 =Kachel SHL 6 =(Kachel SHL 8) SHR 2}
       RCR AL,1    {deshalb: AH:=Kachel und anschliessend AX um 2 Bit}
       SHR AH,1    {rechtsschieben!}
       RCR AL,1
       MOV SI,AX   {Das ist zugleich die (Offset-)Quelladresse in Page 3}

       MOV DI,y    {die erste Zieladresse ist DI:=y*LINESIZE+(x div 4)}
       SHL DI,1
       MOV DI,CS:[OFFSET GADR + DI]
       MOV AX,x
       MOV BX,AX   {Kopie von x nach BX}
       SHR AX,1
       SHR AX,1
       ADD DI,AX

       MOV CX,rightcut

       {Jetzt wird keine Variable aus dem Stack mehr gebraucht: BP kann}
       {verwendet werden!}
       PUSH BP     {wird beim Verlassen der Prozedur gebraucht!}
       MOV BP,16+3
       SUB BP,CX   {BP:=16+3-rightcut, denn die Zahl der Bytes je Zeile fuer}
                   {Plane i berechnet sich zu (16+3-i-rightcut) SHR 2 }


       MOV ES,PAGEADR    {(Segment-)Zieladresse ist aktive Grafikseite}
       MOV DS,SCROLLADR  {(Segment-)Quelladresse ist die Seite SCROLLPAGE}

       MOV DX,3C4h
       MOV AL,2
       AND BX,3

       JNE @mode0i {nur falls x mod 4=0 _und_ rightcut mod 4=0 ist koennen}
       AND CX,3    {wir WriteMode1 verwenden!}
       JNE @mode0i

       {--- "Abkuerzung" ueber WriteMode 1 moeglich!: DX=3C4h, BP=16+3-rightcut}
       MOV AH,0Fh
       OUT DX,AX    {alle 4 Planes gleichzeitig bearbeiten}
       MOV DX,3CEh
       MOV AX,4105h {WriteMode 1 waehlen}
       OUT DX,AX
       SUB BP,3     {BP auf die richtige Groesse bringen}
       JMP @lastPlane9  {abkuerzen}
       {---}

     @mode0i:
       MOV AH,CS:[OFFSET CS_TranslateTab +BX]
       OUT DX,AX   {aktuelle Schreibplane waehlen}
       PUSH AX     {und fuer spaeter merken}

       MOV DX,3CEh
       MOV AX,0004h    {Leseplane 0 waehlen}
       OUT DX,AX

       MOV BX,BP
       SHR BX,1
       SHR BX,1    {BX:=Bytes_je_Plane0_Zeile = (16+3-rightcut) DIV 4}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane0_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane0_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane0_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3CEh
       MOV AX,0104h  {Leseplane 1 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap1i
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap1i:
       OUT DX,AX
       PUSH AX
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4


       DEC BP        {BP:=16+2-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane1_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane1_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane1_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane1_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3CEh
       MOV AX,0204h  {Leseplane 2 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap2i
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap2i:
       OUT DX,AX
       PUSH AX
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4


       DEC BP        {BP:=16+1-rightcut}
       MOV BX,BP
       SHR BX,1
       SHR BX,1      {BX:=Bytes_je_Plane2_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BX       { := LINESIZE-Bytes_je_Plane2_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BX       { := 4-Bytes_je_Plane2_Zeile}

       MOV CX,BX   {CX:=Bytes_je_Plane2_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BX
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BX
       REP MOVSB   {16.Zeile}
       ADD DI,AX
       ADD SI,DX


       MOV DX,3CEh
       MOV AX,0304h  {Leseplane 3 waehlen}
       OUT DX,AX

       MOV DX,3C4h   {naechste Schreibplane:}
       POP AX
       SHL AH,1
       CMP AH,16
       JNE @nowrap3i
       MOV AH,1      {nach Plane 3 kommt wieder Plane 0, aber}
       INC DI        {die Zieladresse hat sich um 1 Byte erhoeht}
     @nowrap3i:
       OUT DX,AX
                     {Wert nicht mehr pushen!}
       SUB DI,16*LINESIZE   {DI und SI wieder zuruecksetzen}
       SUB SI,16*4



       DEC BP        {BP:=16-rightcut}
     @lastplane9:
                     {BP direkt verwenden}
       SHR BP,1
       SHR BP,1      {BP:=Bytes_je_Plane3_Zeile}

       MOV AX,LINESIZE {Korrekturfaktor fuer Zeilenzieladressen}
       SUB AX,BP       { := LINESIZE-Bytes_je_Plane3_Zeile}
       MOV DX,4        {Korrekturfaktor fuer Tilezeilenquelladressen}
       SUB DX,BP       { := 4-Bytes_je_Plane3_Zeile}

       MOV CX,BP   {CX:=Bytes_je_Plane2_Zeile}
       REP MOVSB   {1.Zeile}
       ADD DI,AX   {DI auf naechste Zielzeile setzen}
       ADD SI,DX   {SI auf naechste Tilequellzeile setzen}
       MOV CX,BP
       REP MOVSB   {2.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {3.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {4.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {5.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {6.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {7.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {8.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {9.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {10.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {11.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {12.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {13.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {14.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {15.Zeile}
       ADD DI,AX
       ADD SI,DX
       MOV CX,BP
       REP MOVSB   {16.Zeile}

       {--- falls Abkuerzung genommen wurde, muss WriteMode 0 wieder}
       {    gesetzt werden; hier der Einfachheit immer neugesetzt   }
       MOV AX,4005h
       MOV DX,3CEh
       OUT DX,AX
       {---}

       POP BP
       MOV AX,SEG @Data  {DS wiederherstellen}
       MOV DS,AX

    MOV AX,RandIndex
    ADD AX,XTiles
    MOV RandIndex,AX
    MOV AX,yt
    INC AX
    MOV yt,AX
    MOV DX,y
    ADD DX,16
    MOV y,DX
    CMP DX,YMAX+1-16
    JBE @repeat5
  @ende:


  {------- ab hier: Sprites auf aktuelle Grafikseite bringen}
  @Sprites_zeichnen:
    MOV SI,NMAX*2
    PUSH BP      {BP nachher wieder poppen!}

    @zeichne:

    {DS = normales Datensegment, ES = Grafikseitensegment, }
    {SI = Spritepositionsnummer*2                          }
    @SZeich:
      MOV BX,[SI + OFFSET SpriteN]   {BX=SpriteN[?]=Spriteladenummer}
      SHL BX,1                       {BX = Spriteladenummer*2}

    {Jetzt: "SpriteN[?]:=SpriteN[NextSprite[?]]" berechnen:}
      MOV BX,[BX + OFFSET NextSprite] {AX=NextSprite[SpriteN[?]]}
      MOV [SI + OFFSET SpriteN],BX    {als neue SpriteN[?] uebernehmen}
      SHL BX,1


      JNZ @aktiv
      JMP @noSprite


    @aktiv:
      PUSH SI           {Spritepositionsnummer*2 retten}

      MOV DX,[SI + OFFSET SpriteX]   {if SpriteX>xmax then skip_sprite }
      SUB DX,StartVirtualX           {virtuelle -> absolute Koordinaten}
      CMP DX,XMAX
      JLE @L0
    @ToSprite_fertig:                {Sprungleiste zu @Sprite_fertig}
      JMP @Sprite_fertig
    @L0:
      MOV CS:WORD PTR @akt_SpriteX+1,DX
      MOV DI,[SI + OFFSET SpriteY]   {DI = SpriteY_virtuell}
      SUB DI,StartVirtualY           {DI = SpriteY (absolut!)}
      MOV DS,[BX + OFFSET SPRITEAD]  {!!!DS = ^Spritedaten !!!}
      MOV AX,[Breite]                {AX = Breite in 4er-Gruppen}
      MOV CS:WORD PTR @max_Breite+1,AX
      MOV SI,AX                      {SI = dto.}
      SHL AX,1
      SHL AX,1                       {AX = max_Breite_in_Punkten}
      ADD AX,DX                      {AX = max_Breite_in_Punkten+SpriteX}
      JS @ToSprite_fertig
      MOV BX,DI                      {if SpriteY>=0 then starty:=+SpriteY}
      NEG DI                         {              else starty:=-SpriteY}
      MOV BP,DI
      JG @Top_cut
      XOR DI,DI
    @Top_cut:                        {DI = starty, BP = -SpriteY}
      MOV AX,[Hoehe]                 {AX = Hoehe (in Zeilen)  }
      CMP DI,AX                      {if starty>=Hoehe then skip_sprite}
      JGE @ToSprite_fertig
      ADD BP,YMAX                    {BP = -SpriteY+ymax}
      JL @ToSprite_fertig            {(etwas frei:) }
      CMP AX,BP                      {if Hoehe+SpriteY>ymax   }
      JG @To_then                    { then [ endy:=199-SpriteY }
      DEC AX                         {       if endy<0 then skip_sprite ] }
      MOV BP,AX                      { else endy:=Hoehe-1 }

    {BP = endy, SI=[@max_Breite+1] = max_Breite_in_4er_Gruppen, }
    {DI = starty, BX = SpriteY, DX=[@akt_SpriteX+1] = SpriteX,  }
    {DS = ^Spritedaten, ES = ^Grafikseite}
    @To_then:
      MOV AX,BP
      SUB BP,DI

      SHL BP,1
      MOV [End_min_Start],BP         {= (endy-starty)*2 =Yaktuell*2}
      ADD BX,AX
      SHL BX,1
      MOV BX,CS:[OFFSET gadr + BX]   {BX=zeilenadr:=(endy+SpriteY)*LINESIZE}
      MOV [zeilenadr],BX             {auch nach [zeilenadr] }
      MOV BP,DX
      MUL SI                         {AX = endy*max_breite_in_4er =yoffset}
      MOV [yoffset_],AX              {auch nach [yoffset_]}
      SHL DI,1                       {DI = starty*2}
      MOV CS:WORD PTR @Starty_2+1,DI {auch nach [@Starty_2+1] }

      {kleiner Einschub: anhand des Modusbytes des Sprites entscheiden, ob}
      {eine andere Routine zur Darstellung des Sprites als die gerade ak- }
      {tive benoetigt wird und wenn ja, diese in Position bringen!        }
      {Verwendete Register: AX und SI                                     }
       MOV AL,[Modus]                {Modusbyte des Sprites holen}
       XOR AH,AH
       SHL AX,1
       MOV SI,AX
       MOV SI,CS:[OFFSET Adressen +SI] {Pointer auf zugehoerige Routine holen}
       MOV AX,CS:[SI]
       CMP AX,CS:[WORD PTR @Patch1]    {ist diese Routine bereits aktiv?}
       JE @no_newcode                  {ja, nix zu tun}
       PUSH DS                         {nein, kopiere die Routine an die}
       PUSH CS                         {entsprechenden Stellen}
       POP DS
       MOV [WORD PTR @Patch1],AX
       MOV [WORD PTR @Patch2],AX
       MOV [WORD PTR @Patch3],AX
       MOV [WORD PTR @Patch4],AX
       INC SI
       INC SI
       LODSW
       MOV [WORD PTR @Patch1+2],AX
       MOV [WORD PTR @Patch2+2],AX
       MOV [WORD PTR @Patch3+2],AX
       MOV [WORD PTR @Patch4+2],AX
       LODSW
       MOV [WORD PTR @Patch1+4],AX
       MOV [WORD PTR @Patch2+4],AX
       MOV [WORD PTR @Patch3+4],AX
       MOV [WORD PTR @Patch4+4],AX
       LODSW
       MOV [WORD PTR @Patch1+6],AX
       MOV [WORD PTR @Patch2+6],AX
       MOV [WORD PTR @Patch3+6],AX
       MOV [WORD PTR @Patch4+6],AX
       LODSW
       MOV [WORD PTR @Patch1+8],AX
       MOV [WORD PTR @Patch2+8],AX
       MOV [WORD PTR @Patch3+8],AX
       MOV [WORD PTR @Patch4+8],AX
       LODSW
       MOV [WORD PTR @Patch1+10],AX
       MOV [WORD PTR @Patch2+10],AX
       MOV [WORD PTR @Patch3+10],AX
       MOV [WORD PTR @Patch4+10],AX
       LODSW
       MOV [WORD PTR @Patch1+12],AX
       MOV [WORD PTR @Patch2+12],AX
       MOV [WORD PTR @Patch3+12],AX
       MOV [WORD PTR @Patch4+12],AX
       LODSW
       MOV [WORD PTR @Patch1+14],AX
       MOV [WORD PTR @Patch2+14],AX
       MOV [WORD PTR @Patch3+14],AX
       MOV [WORD PTR @Patch4+14],AX

       POP DS                          {DS wiederherstellen}
     @no_newcode:


    {(AX=)[yoffset_]     = yoffset }
    { BX=[zeilenadr]     = (endy+SpriteY)*LINESIZE}
    { DI=[@Starty_2+1]   = starty*2}
    {(SI=[@max_Breite+1] = max_Breite_in_4er_) }
    { BP                 = SpriteX}
    { DS                 = ^Spritedaten}
    { ES                 = ^Grafikseite}
    { [end_min_start]    = (endy-starty)*2 =Yaktuell*2}
    { [@max_Breite+1]    = max_Breite_in_4er_Gruppen  }
    { [@akt_SpriteX+1]   = SpriteX}
    @eine_Zeile:
      MOV SI,[end_min_start]         {SI = Yaktuell*2 }
      ADD SI,DI                      {startx:=sprite[WORD PTR sprite[L]+  }
      MOV DI,SI                      {               (Yaktuell+starty)*2] }
      ADD SI,[Left]
      MOV SI,[SI]                    {SI = startx, DI = (Yaktuell+starty)*2}
      MOV AX,BP                      
      ADD AX,SI                      {AX=bildschirmstartx:=SpriteX+startx   }
      CMP AX,XMAX                    {if bildschirmstartx>xmax then skip_zeile}
      JG @ToZeile_fertig
      MOV CX,SI                      {CX=startx}
      OR AX,AX                       {licutoff_in_Punkten:=startx}
      JGE @L1                        {if bildschirmstartx<0 then }
      SUB SI,AX                      { [dec(startx,bildschirmstartx) }
      XOR AX,AX                      {  bildschirmstartx:=0          }
      MOV CX,BP                      {  licutoff_in_Punkten:=-SpriteX] }
      NEG CX
    @L1:                             {CX=[licutoff_]= licutoff_in_Punkten, }
      MOV [licutoff_],CX             {SI = startx, AX = bildschirmstartx   }
      ADD DI,[Right]
      MOV DI,[DI]                    {DI=endx:=sprite[WORD PTR sprite[R]+  }
                                     {                (Yaktuell+starty)*2] }
      MOV DX,BP                      
      NEG DX                         {DX = -SpriteX }
      MOV BP,DI
      SUB BP,SI                      {BP = endx-startx }
      SUB DX,DI
      ADD DX,XMAX                    {DX=ueberhang:=xmax-(SpriteX+endx) }
      JNS @kein_Ueberhang_rechts
      ADD BP,DX
    @kein_Ueberhang_rechts:          {BP = sichtbare Breite dieser Zeile -1}
      OR BP,BP
      JNS @L6
    @ToZeile_fertig:
      JMP @Zeile_fertig              {if Breite<=0 then skip_zeile }
    @L6:
      ADD BP,4

      { AX               = bildschirmstartx}
      { BX=[zeilenadr]   = (endy+SpriteY)*LINESIZE }
      { CX=[licutoff_]   = licutoff_in_Punkten}
      {(DX               = (negativer) ueberhang (falls Wert<0) ) }
      {(SI               = startx) }
      {(DI               = endx) }
      { BP               = Breite fuer diese Zeile in Punkten +3 }
      { DS               = ^Spritedaten}
      { ES               = ^Grafikseite}
      { [@max_Breite+1]  = max_Breite_in_4er_) }
      { [end_min_start]  = (endy-starty)*2 =Yaktuell*2}
      { [@Starty_2+1]    = starty*2}
      { [@max_Breite+1]  = max_Breite_in_4er_Gruppen, }
      { [@akt_SpriteX+1] = SpriteX}
      MOV [bildx],AX                 {bildschirmstartx retten}
      MOV DX,CX                      {DX = licutoff_in_Punkten}
      MOV CX,BP
      SHR CX,1
      SHR CX,1                       {CX = Breite DIV 4}
      JCXZ @Plane1

      {SI=Quellzeiger:=sprite[WORD PTR (licutoff_in_Punkten+0 AND 3)*2 }
      {                       +(licutoff_in_Punkten+0) DIV 4 +yoffset  }
      MOV SI,DX
      AND SI,3                       
      SHL SI,1                       {SI = ((licutoff_in_Punkten+0) AND 3)*2}
      MOV SI,[SI]                    
      MOV DI,DX
      SHR DI,1
      SHR DI,1
      ADD SI,DI
      ADD SI,[yoffset_]              {SI = sprite[WORD PTR (licutoff_...)] }
                                     {     +(licutoff_in_Punkten+i) DIV 4  }
                                     {     +yoffset                        }

      {DI=Zielzeiger:=(bildschirmstartx+0) DIV 4 +zeilenadr}
      MOV DI,AX                      {DI = bildschirmstartx }
      SHR DI,1
      SHR DI,1
      ADD DI,BX
      MOV BL,AL
      AND BX,3                       {BX = (bildschirmstartx+i) AND 3 }
      MOV AH,Translate[BX]           {AH = 1,2,4,8 fuer BX=0,1,2,3    }
      MOV AL,2
      MOV DX,3C4h
      OUT DX,AX                      {Plane auswaehlen}

      XCHG BX,DI
      {CX Bytes von DS:SI nach ES:BX uebertragen }
      {Hierher kommt die Routine zur Datenuebertragung!}
    @Patch1:
      db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

    @Plane1:
      MOV DX,[bildx]
      INC DX                         {DX = bildschirmstartx+1}
      MOV BX,DX
      SHR BX,1
      SHR BX,1                       {BX=zielzeiger:=(bildschirmstartx+1) }
      ADD BX,[zeilenadr]             {                DIV 4 +zeilenadr    }
      MOV CX,BP
      DEC CX                         {CX = Breite dieser Zeile +3 -1 }
      SHR CX,1
      SHR CX,1                       {CX = Bytes_zu_moven fuer i=1 }
      JCXZ @Plane2
      MOV DI,[licutoff_]
      INC DI                         {DI = (licutoff_in_Punkten+1) }
      MOV SI,DI
      AND SI,3
      SHL SI,1                       {SI = ((licutoff_in_Punkten+1) AND 3)*2}
      MOV SI,[SI]                    {SI = sprite[WORD PTR licutoff_...]  }
      SHR DI,1                       {     +(licutoff_in_Punkten+1) DIV 4 }
      SHR DI,1                       {     +yoffset                       }
      ADD SI,DI
      ADD SI,[yoffset_]              {SI = Quellzeiger, }
                                     {DI = (licutoff_in_Punkten+1) DIV 4 }

      MOV DI,DX                      {DI = bildschirmstartx+1}
      AND DI,3                       {DI = (bildschirmstartx+1) AND 3 }
      MOV AH,Translate[DI]           {Maske fuer Portzugriff laden}
      MOV AL,2
      MOV DX,3C4h                    {Plane anwaehlen}
      OUT DX,AX

      {Hierher kommt die Routine zur Datenuebertragung!}
    @Patch2:
      db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

    @Plane2:
      MOV DX,[bildx]
      ADD DX,2
      MOV BX,DX
      SHR BX,1
      SHR BX,1
      ADD BX,[zeilenadr]
      MOV CX,BP
      SUB CX,2
      SHR CX,1
      SHR CX,1
      JCXZ @Plane3
      MOV DI,[licutoff_]
      ADD DI,2
      MOV SI,DI
      AND SI,3
      SHL SI,1
      MOV SI,[SI]
      SHR DI,1
      SHR DI,1
      ADD SI,DI
      ADD SI,[yoffset_]

      MOV DI,DX                      {DI = bildschirmstartx+2}
      AND DI,3                       {DI = (bildschirmstartx+1) AND 3 }
      MOV AH,Translate[DI]
      MOV AL,2
      MOV DX,3C4h
      OUT DX,AX

      {Hierher kommt die Routine zur Datenuebertragung!}
    @Patch3:
      db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

    @Plane3:
      MOV DX,[bildx]
      ADD DX,3
      MOV BX,DX
      SHR BX,1
      SHR BX,1
      ADD BX,[zeilenadr]
      MOV CX,BP
      SUB CX,3
      SHR CX,1
      SHR CX,1
      JCXZ @Zeile_fertig
      MOV DI,[licutoff_]
      ADD DI,3
      MOV SI,DI
      AND SI,3
      SHL SI,1
      MOV SI,[SI]
      SHR DI,1
      SHR DI,1
      ADD SI,DI
      ADD SI,[yoffset_]

      MOV DI,DX                      {DI = bildschirmstartx+3}
      AND DI,3                       {DI = (bildschirmstartx+1) AND 3 }
      MOV AH,Translate[DI]
      MOV AL,2
      MOV DX,3C4h
      OUT DX,AX

      {Hierher kommt die Routine zur Datenuebertragung!}
    @Patch4:
      db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

    @Zeile_fertig:
      MOV AX,[yoffset_]
    @max_Breite:
      SUB AX,1234
      MOV [yoffset_],AX
      MOV BX,[zeilenadr]
      SUB BX,LINESIZE
      MOV [zeilenadr],BX
      SUB WORD PTR [end_min_start],2
      JS @Sprite_fertig

    @Starty_2:
      MOV DI,1234
    @akt_SpriteX:
      MOV BP,1234
      JMP @eine_Zeile

    @Sprite_fertig:
      POP SI
      MOV AX,SEG @Data
      MOV DS,AX

    @noSprite:
      DEC SI
      DEC SI
      JS @fertig
      JMP @zeichne
    @fertig:

      POP BP

    {Die Grafikseite ist nun fertiggestellt und muss noch angezeigt werden:}
      cli
      mov  dx,3DAh
    @WaitNotVSyncLoop:
      in   al,dx
      and  al,8
      jnz  @WaitNotVSyncLoop
    @WaitVSyncLoop:
      in   al,dx
      and  al,8
      jz   @WaitVSyncLoop

      MOV DX,$3D4                {CRT-Controller}
      MOV AL,$0D                 {LB-Startadress-Register}
      OUT DX,AL
      INC DX
                                 { Realisiere "AX:=Offset_Adr[Page]": }
      MOV SI,PAGE                {Page-Wert *2 (da Worteintraege!)}
      MOV BX,SI                  {Page-Wert in BX merken!}
      SHL SI,1                   {dazu Startadresse des Feldes addieren}
      ADD SI,OFFSET Offset_Adr-StartIndex*2  {evtl. Verschiebung korrigieren}
      LODSW                      {und Wert holen}
      OUT DX,AL                  {LB der neuen Startadresse setzen}
      DEC DX
      MOV AL,$0C
      OUT DX,AL
      INC DX
      MOV AL,AH                  {HB der neuen Startadresse setzen}
      OUT DX,AL
      STI

      NEG BX       {neuer PAGE-Wert := 1-alter PAGE-Wert, d.h.:  }
      ADD BX,1     {IF PAGE=0 THEN PAGE:=1 ELSE (PAGE=1) PAGE:=0 }
      MOV PAGE,BX

      SHL BX,1     {neuer PAGEADR-Wert := Segment_Adr[PAGE] }
      ADD BX,OFFSET Segment_Adr-StartIndex*2
      MOV AX,[BX]
      MOV PAGEADR,AX

      {Jetzt ueberpruefen, ob gesetzte Zyklus(mindest)zeit abgelaufen ist:}
    @L10:
      MOV AL,TimeFlag   {Bit 7 = 0/1 fuer Zeit ist abgelaufen/laeuft noch }
      AND AL,$80
      JE @L10

      {Zeitueberwachung fuer naechsten Zyklus starten:}
      MOV AL,IsAT                 {ist das ein AT/386? ($0/$80=ja/nein)}
      OR AL,AL                    {Zeitmechanismus geht nur auf AT/386 }
      JNE @L11                    {anderenfalls keine Zeitueberwachung!}
      MOV TimeFlag,AL             {AL=0 zugleich als Init-Wert benutzen}
      MOV DX,WORD PTR CycleTime   {Mindestzykluszeit (in Mikrosekunden)}
      MOV CX,WORD PTR CycleTime+2 {eintragen: CX=HIGH-Word, DX=LOW-Word}
      MOV BX,OFFSET TimeFlag      {ES:BX=Zeiger auf TimeFlag, Bit 7=0/1}
      MOV AX,DS                   {fuer: Zeit laeuft noch/ist um       }
      MOV ES,AX
      MOV AX,8300h                {Zeitueberwachung starten}
      INT 15h
    @L11:
 END
END;


FUNCTION LoadSprite(name:String; number:WORD):WORD;
{ in: name   = Name des zu ladenden Sprite-Files (Typ: "*.COD" / "*.LIB" )}
{     number = Nummer, die das erste Sprite aus diesem File bekommen soll }
{out: Anzahl der aus dem File gelesenen Sprites (0 = Fehler trat auf)     }
{rem: Die Routine erkennt automatisch, ob es sich bei dem File um ein ein-}
{     zelnes Sprite oder eine ganze Spritebibliothek handelt und laedt    }
{     alle Spritedaten auf den Heap, und zwar derart, dass die Adresse    }
{     immer auf eine Segmentgrenze fällt. Diese Anfangsadressen werden    }
{     dann in der Tabelle SPRITEAD[number] abgelegt; sind mehrere Sprites }
{     in der Datei so werden sie mit fortlaufender Nummer eingetragen,    }
{     also number+i }
LABEL quit_loop;
VAR p1,p2:Pointer;
    len:LONGINT;
    f:File;
    count,Kopf:WORD;
    Header:SpriteHeader;
BEGIN
 count:=0;  {Zahl der bisher eingelesenen Sprites}
 Kopf:=SizeOf(SpriteHeader);
 assign(f,name);
 {$I-} reset(f,1); {$I+}
 if (ioresult<>0)
  THEN BEGIN  {Datei existiert nicht oder nicht unter diesem Pfad}
        Error:=Err_FileIO;
        loadSprite:=0; exit
       END;
 len:=filesize(f);  {Dateilaenge ermitteln}
 if (maxavail<len+16)
  THEN BEGIN
        Error:=Err_NotEnoughMemory;
        goto quit_loop;
       END;
 if (number=0) or (number>LoadMAX)
  THEN BEGIN
        Error:=Err_InvalidSpritenumber;
        goto quit_loop;
       END;
 WHILE NOT EOF(f) DO
 BEGIN
  {Zunaechst den Spriteheader einlesen: }
  {$I-}     {jetzt den Spriteheader vià BLOCKREAD auf den Heap laden}
  blockread(f,Header,Kopf);
  {$I+}

  IF (ioresult<>0)
   THEN BEGIN
         Error:=Err_FileIO;
         goto quit_loop;
        END;
  IF (Header.Kennung[1]<>'K') or (Header.Kennung[2]<>'R')
   THEN BEGIN
         Error:=Err_NoSprite;
         goto quit_loop;
        END;
  IF (Header.SpriteLength>MaxAvail+15)    {noch genug Platz da?}
   THEN BEGIN
         Error:=Err_NotEnoughMemory;
         goto quit_loop;
        END;

  {Jetzt eigentliche Spritedaten einlesen: }
  getmem(p1,Header.SpriteLength+15);       {genug Platz reservieren}
  IF (LONGINT(p1) mod 16)=0
   THEN p2:=p1                             {p2 auf Segmentgrenze bringen}
   ELSE LONGINT(p2):=LONGINT(p1) + (16-LONGINT(p1) mod 16);

  MOVE(Header,p2^,Kopf);  {Spriteheader auf Heap bringen}
  LONGINT(p1):=LONGINT(p2)+Kopf;   {zeigt genau hinter den Header}

  {$I-}     {jetzt den "Rest" des Sprites laden}
  blockread(f,p1^,Header.SpriteLength-Kopf);
  {$I+}
  IF (ioresult<>0)
   THEN BEGIN
         Error:=Err_FileIO;
         goto quit_loop;
        END;

  {der Spritenummer zuordnen:}
  spritead[number+count]:=(longint(p2) shr 16)
                         +(longint(p2) and 65535) shr 4;
  INC(count);
 END;

quit_loop: ;
 close(f);
 loadSprite:=count
END;

FUNCTION LoadTile(name:STRING; number:BYTE):WORD;
{ in: name   = Name des zu ladenden Sprite-Files (Typ: "*.COD" / "*.LIB" )}
{     number = 0..255 = Tilenummer fuer das erste Sprite der Datei        }
{out: Anzahl der aus dem File gelesenen Tiles (0 = Fehler trat auf)       }
{rem: Die Routine erkennt automatisch, ob es sich bei dem File um ein ein-}
{     zelnes Sprite oder eine ganze Spritebibliothek handelt und laedt    }
{     alle Sprites, zerlegt diese in Tiles und legt sie in der 4.Grafik-  }
{     seite ab, beginnend mit der uebergebenen Nummer number              }
{     Da eine Kachel 16x16 Punkte gross ist, muessen die Sprites ein Viel-}
{     faches von 16 Punkten in x- und y-Richtung sein                     }
{     Enthaelt die Datei mehrere Tiles, so werden sie zeilenweise geladen,}
{     jede Zeile dabei in der Reihenfolge von links nach rechts           }
LABEL quit_loop;
TYPE split=RECORD loword,hiword:WORD END;
VAR p1:Pointer;
    len,ad,p:LONGINT;
    f:File;
    count,Kopf,ZielOfs,step,yoffset:WORD;
    pSeg,pOfs:ARRAY[0..3] OF WORD;
    Breite_in_Tiles,Hoehe_in_Tiles,x,y,i,zeilen:BYTE;
    Header:SpriteHeader;
BEGIN
 count:=0;  {Zahl der bisher eingelesenen Sprites}
 Kopf:=SizeOf(SpriteHeader);
 assign(f,name);
 {$I-} reset(f,1); {$I+}
 if (ioresult<>0)
  THEN BEGIN  {Datei existiert nicht oder nicht unter diesem Pfad}
        Error:=Err_FileIO;
        LoadTile:=0; exit
       END;
 len:=filesize(f);  {Dateilaenge ermitteln}
 if (maxavail<len+16)
  THEN BEGIN
        Error:=Err_NotEnoughMemory;
        goto quit_loop;
       END;
 WHILE NOT EOF(f) DO
 BEGIN
  {Zunaechst den Spriteheader einlesen: }
  {$I-}     {jetzt den Spriteheader vià BLOCKREAD auf den Heap laden}
  blockread(f,Header,Kopf);
  {$I+}

  IF (ioresult<>0)
   THEN BEGIN
         Error:=Err_FileIO;
         goto quit_loop;
        END;
  IF (Header.Kennung[1]<>'K') or (Header.Kennung[2]<>'R')
   THEN BEGIN
         Error:=Err_NoTile;  {oder Err_NoSprite!}
         goto quit_loop
        END;
  IF (Header.Breite_in_4er_Gruppen MOD 4<>0) OR
     (Header.Hoehe_in_Zeilen MOD 16<>0)   {Groesse Vielfaches von 16?}
   THEN BEGIN
         Error:=Err_NoTile;
         goto quit_loop
        END
   ELSE BEGIN {ja, Anzahl Tiles in diesem Spritefile ermitteln}
         Breite_in_Tiles:=Header.Breite_in_4er_Gruppen SHR 2;
         Hoehe_in_Tiles :=Header.Hoehe_in_Zeilen SHR 4;
         step:=Breite_in_Tiles*4; {Schrittweite beim laden}
        END;
  IF (Header.SpriteLength>MaxAvail)    {noch genug Platz da?}
   THEN BEGIN
         Error:=Err_NotEnoughMemory;
         goto quit_loop;
        END;

  {Jetzt eigentliche Spritedaten einlesen: }
  getmem(p1,Header.SpriteLength);      {genug Platz reservieren}

  {$I-}     {jetzt den "Rest" des Sprites laden}
  blockread(f,p1^,Header.SpriteLength-Kopf);
  {$I+}
  IF (ioresult<>0)
   THEN BEGIN
         Error:=Err_FileIO;
         goto quit_loop;
        END;

  ad:=(LONGINT(split(p1).HiWord) SHL 4) + split(p1).LoWord - Kopf;
  FOR i:=0 TO 3 DO
   BEGIN
    p:=ad+Header.Zeiger_auf_Plane[i]; pSeg[i]:=p SHR 4; pOfs[i]:=p AND $F;
   END;

  FOR y:=0 TO Pred(Hoehe_in_Tiles) DO
   BEGIN
    yoffset:=y*Breite_in_Tiles*16*(16 DIV 4);
    FOR x:=0 TO Pred(Breite_in_Tiles) DO
     BEGIN
      IF count+number>255
       THEN BEGIN
             Error:=Err_InvalidTileNumber;
             goto quit_loop
            END;
      ZielOfs:=(number+count) SHL 6;
      FOR i:=0 TO 3 DO
       BEGIN
        PORTW[$3C4]:=(TranslateTab[i] SHL 8) + 2;
        FOR zeilen:=0 TO 15 DO
         BEGIN
          move(mem[pSeg[i]:pOfs[i] + yoffset + zeilen*step + x*(16 DIV 4)],
               mem[SCROLLADR:ZielOfs + zeilen*(16 DIV 4)],
               16 DIV 4);
         END;
       END;

      INC(count);
     END;
   END;
  Dispose(p1);
 END;

quit_loop: ;
 close(f);
 LoadTile:=count
END;

PROCEDURE SetBackgroundScrollRange(x1,y1,x2,y2:INTEGER);
{ in: (x1,y1) = linke obere Ecke des Bereiches (virtuelle Koord.)}
{     (x2,y2) = dto., rechte untere Ecke}
{out: (BackX1,BackY1), (BackX2,BackY2) = auf 16er Raster gerundete Koord.  }
{     XTiles, YTiles = Breite und Hoehe des gewaehlten Bereiches in Kacheln}
{rem: Die li. obere Ecke wird nach links oben gezogen, die re. untere nach }
{     rechts unten!}
{     Die Anwendung dieser Routine ist natuerlich nur fuer den Hintergrund-}
{     modus SCROLLING sinnvoll}
BEGIN
 BackX1:=x1 AND $FFF0; BackX2:=x2 OR $F;
 BackY1:=y1 AND $FFF0; BackY2:=y2 OR $F;
 xtiles:=succ(BackX2-BackX1) shr 4;
 ytiles:=succ(BackY2-BackY1) shr 4;
 IF (xtiles OR ytiles)<=0
  THEN Error:=Err_InvalidCoordinates
 ELSE IF xtiles*ytiles>MaxTiles
  THEN Error:=Err_BackgroundToBig;
END;

PROCEDURE SetBackgroundMode(mode:BYTE);
{ in: mode = gewuenschter Hintergrundmodus STATIC oder SCROLLING}
{out: Backgroundmode = gesetzter Modus STATIC/SCROLLING}
BEGIN
 IF (mode<>STATIC) AND (mode<>SCROLLING)
  THEN Error:=Err_InvalidMode
  ELSE Backgroundmode:=mode
END;

PROCEDURE PutTile(x,y:INTEGER; TileNr:BYTE);
{ in: x,y   = virtuelle Koordinate, an die die Kachel plaziert werden soll}
{out: TileNr= Nummer der zu plazierenden Kachel}
{rem: Der Punkt (x,y) wird zunaechst auf 16er Raster gebracht!  }
{     Die Anwendung dieser Routine ist nur fuer den Hintergrund-}
{     modus SCROLLING sinnvoll}
VAR index:WORD;
BEGIN
 ASM
    MOV AX,x        {berechne relativen X-Abstand vom linken Rand des}
    SUB AX,BackX1   {definierten Bereiches in "x" mittels der Formel:}
    SAR AX,1        { x:=((x AND $FFF0)-BackX1) DIV 16 (nicht SHR 4)!}
    SAR AX,1        {"AND $FFF0" kann dabei entfallen, da in BackX1  }
    SAR AX,1        {die letzte Hex-Ziffer $0 ist!                   }
    SAR AX,1
    MOV x,AX

    MOV AX,y        {dto. fuer Abstand der y-Koordinate vom oberen   }
    SUB AX,BackY1   {Rand: y:=((y AND $FFF0)-BackY1) DIV 16          }
    SAR AX,1
    SAR AX,1
    SAR AX,1
    SAR AX,1
    MOV y,AX
 END;

 IF (x<0) OR (x>=XTiles) OR (y<0) OR (y>=YTiles)
  THEN Error:=Err_InvalidCoordinates
  ELSE BEGIN {jede Kachelzeile ist XTiles breit, jede Kachel 16x16 Punkte}
        index:=y*XTiles+x;  {eigentlich: (x MOD XTiles)}
        BackTile[index]:=TileNr;
       END;
END;


PROCEDURE SetModeByte(Sp:WORD; M:BYTE);
{ in: Sp = SpriteLADEnummer, dessen Modusbyte veraendert werden soll}
{out: M  = Methode, mit der Sp ab sofort dargestellt werden soll:   }
{          Display_NORMAL, Display_FAST oder Display_SHADOW         }
{rem: Existiert das Sprite noch nicht oder ist der Modus nicht er-  }
{     laubt, so geschieht nicht!                                    }
VAR ad:WORD;
BEGIN
 ad:=SPRITEAD[Sp];
 IF ad=0 THEN Error:=Err_InvalidSpriteNumber {Sprite muss schon geladen sein}
 ELSE IF (M<>Display_NORMAL) AND (M<>Display_FAST) AND (M<>Display_SHADOW)
  THEN Error:=Err_InvalidMode  {nur diese 3 Modi sind zulaessig}
 ELSE MEM[ad:Modus]:=M
END;

FUNCTION GetModeByte(Sp:WORD):BYTE;
{ in: Sp    = Spritenummer, dessen Modusbyte abgefragt werden soll}
{out: Methode, die fuer Sp momentan gesetzt ist: Display_NORMAL,  }
{     Display_FAST, Display_SHADOW bzw. Display_UNKNOWN, wenn das }
{     Sprite noch nicht geladen wurde!}
VAR ad:WORD;
BEGIN
 ad:=SPRITEAD[Sp];
 IF (ad=0)
  THEN GetModeByte:=Display_UNKNOWN     {Sprite noch nicht geladen}
  ELSE GetModeByte:=MEM[SPRITEAD[Sp]:Modus]
END;

PROCEDURE FillPage(pa,color:Byte);
{ in: pa    = die Seite, die gefuellt werden soll (0..3)}
{     color = Fuellfarbe fuer die Seite}
{out: Grafikseite "pa" wurde mit der Farbe "Color" gefuellt    }
{rem: Sinnvoll sind nur die Seiten 0,1 und BACKGNDPAGE, jedoch }
{     ist SCROLLPAGE ebenfalls erlaubt}
BEGIN
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE) AND (pa<>SCROLLPAGE)
  THEN Error:=Err_InvalidPageNumber
  ELSE BEGIN
        portw[$3C4]:=$0F02; {im Map-Mask Register alle 4 Ebenen selektieren}
        fillchar(MEM[Segment_Adr[pa]:0],PAGESIZE,Color)
       END;
END;

PROCEDURE FillBackground(color:BYTE);
{ in: color = Fuellfarbe fuer die Hintergrundseite BACKGNDPAGE        }
{out: Die Grafikseite BACKGNDPAGE wurde mit der Farbe "Color" gefuellt}
{rem: Die Routine ist nur fuer den Hintergrundmodus STATIC sinnvoll   }
BEGIN
 FillPage(BACKGNDPAGE,color)
END;

PROCEDURE GetBackgroundFromPage(pa:Byte);
{in : pa = 0 oder 1 }
{out: -             }
{rem: Der Hintergrundspeicher BACKGNDPAGE wird mit dem Inhalt der an- }
{     gegebenen Grafikseite gefuellt.}
{     Die Routine ist nur fuer den Hintergrundmodus STATIC sinnvoll   }
VAR p:POINTER;
BEGIN
 IF (pa<>0) AND (pa<>1)
  THEN Error:=Err_InvalidPageNumber
  ELSE BEGIN
        portw[$3c4]:=$0f02; {im MapMask Register alle 4 Ebenen selektieren}
        port[$3ce]:=5;      {Schreibmodus 1 }
        port[$3cf]:=port[$3cf] OR 1;   {oder direkt :=$41}
        p:=Ptr(Segment_Adr[pa],$0000);
        MOVE(p^,MEM[BACKGNDADR:0],PAGESIZE);
        portw[$3cf]:=port[$3cf] and $FC; {Schreibmodus 0 (oder direkt:=$40)}
       END;
END;

PROCEDURE WritePage(name:STRING; pa:BYTE);
{ in: name     = Filename fuer das abzuspeichernde Bild}
{     pa       = abzuspeichernde Seite (0..2) }
{     PAGESIZE = Groesse einer Bitplane       }
{     PICHeader= einzutragende Kennung fuer Bilderdateien}
{out: - }
{rem: Die Grafik auf Seite "pa" wurde in der Datei "name" als Bitmap abgelegt}
{     Diese Datei ist 4*PAGESIZE+3 = 64003 Bytes gross: 320x200 Punkte zu je }
{     1 Byte plus Length(PICHeader) als Kennung}
VAR f:FILE;
    i,oldMode:BYTE;
    fehler:BOOLEAN;
BEGIN
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)
  THEN BEGIN
        Error:=Err_InvalidPageNumber; exit
       END;
 {$I-}
 Assign(f,name); fehler:=IOResult<>0;
 Rewrite(f,1);   fehler:=fehler OR (IOResult<>0);
 BlockWrite(f,PICHeader[1],Length(PICHeader));
 fehler:=fehler OR (IOResult<>0);
 {$I+}
 IF fehler
  THEN BEGIN
        {$I-} Close(f); {$I+}
        Error:=Err_FileIO; exit
       END;
 port[$3ce]:=5;       {alten Lese-/Schreibmodus merken}
 oldMode:=port[$3cf];
 port[$3cf]:=$40;     {Lesemodus 0 setzen}
 FOR i:=0 TO 3 DO
  BEGIN
   portw[$3CE]:=4+(i shl 8); {Lese-Plane anwaehlen}
   {$I-}
   BlockWrite(f,mem[Segment_Adr[pa]:0],PAGESIZE);
   {$I+}
   fehler:=fehler OR (IOResult<>0);
  END;
 {$I-}
 Close(f);
 {$I+}
 fehler:=fehler OR (IOResult<>0);
 port[$3ce]:=5;       {alten Lese-/Schreibmodus setzen}
 port[$3cf]:=oldMode;
 IF fehler THEN Error:=Err_FileIO
END;

PROCEDURE LoadPage(name:STRING; pa:BYTE);
{ in: name     = Filename fuer das zu ladende Bild}
{     pa       = Zielseite, in die das Bild geladen werden soll (0..2) }
{     PAGESIZE = Groesse einer Bitplane    }
{     PICHeader= Kennung fuer Bilderdateien}
{out: - }
{rem: Die Bitmap-Grafik in der Datei "name" wurde in die Seite "pa" geladen}
VAR f:FILE;
    i,oldMode:BYTE;
    fehler:BOOLEAN;
    s:STRING[3];
BEGIN
 IF (pa<>0) AND (pa<>1) AND (pa<>BACKGNDPAGE)
  THEN BEGIN
        Error:=Err_InvalidPageNumber; exit
       END;
 {$I-}
 Assign(f,name); fehler:=IOResult<>0;
 Reset(f,1);     fehler:=fehler OR (IOResult<>0);
 s[0]:=PICHeader[0];
 BlockRead(f,s[1],Length(PICHeader)); fehler:=fehler OR (IOResult<>0);
 {$I+}
 IF fehler
  THEN BEGIN
        {$I-} Close(f); {$I+}
        Error:=Err_FileIO; exit
       END
  ELSE IF (FileSize(f)<>4*PAGESIZE+Length(PICHeader)) OR (s<>PICHeader)
  THEN BEGIN
        {$I-} Close(f); {$I+}
        Error:=Err_NoPicture; exit
       END;
 port[$3ce]:=5;       {alten Lese-/Schreibmodus merken}
 oldMode:=port[$3cf];
 port[$3cf]:=$40;     {Schreibmodus 0 setzen}
 FOR i:=0 TO 3 DO
  BEGIN
   portw[$3c4]:=2+(TranslateTab[i] shl 8); {Schreib-Plane anwaehlen}
   {$I-}
   BlockRead(f,mem[Segment_Adr[pa]:0],PAGESIZE);
   {$I+}
   fehler:=fehler OR (IOResult<>0);
  END;
 {$I-}
 Close(f);
 {$I+}
 fehler:=fehler OR (IOResult<>0);
 port[$3ce]:=5;       {alten Lese-/Schreibmodus setzen}
 port[$3cf]:=oldMode;
 IF fehler THEN Error:=Err_FileIO
END;

PROCEDURE WriteBackgroundPage(name:STRING);
{ in: name       = Filename fuer das abzuspeichernde Hintergrundbild}
{     BACKGNDPAGE= abzuspeichernde Seite (=2) }
{     PAGESIZE   = Groesse einer Bitplane     }
{     PICHeader  = einzutragende Kennung fuer Bilderdateien}
{out: - }
{rem: Die Grafik der Hintergrundseite wurde in der Datei "name" abgelegt}
{     Diese Datei ist 4*PAGESIZE+3 = 64003 Bytes gross: 320x200 Punkte  }
{     zu je 1 Byte plus Length(PICHeader) als Kennung}
{     Die Routine ist nur fuer den Hintergrundmodus STATIC sinnvoll     }
BEGIN
 WritePage(name,BACKGNDPAGE)
END;

PROCEDURE LoadBackgroundPage(name:STRING);
{ in: name       = Filename fuer das zu ladende Hintergrundbild}
{     BACKGNDPAGE= Zielseite, in die das Bild geladen werden soll (=2) }
{     PAGESIZE = Groesse einer Bitplane    }
{     PICHeader= Kennung fuer Bilderdateien}
{out: - }
{rem: Die Bitmap-Grafik in der Datei "name" wurde in die Hintergrundseite}
{     Die Routine ist nur fuer den Hintergrundmodus STATIC sinnvoll}
{     BACKGNDPAGE geladen}
BEGIN
 LoadPage(name,BACKGNDPAGE)
END;



PROCEDURE InitRoutines;
{ in: - }
{out: SpriteN[],SPRITEAD[] wurden auf "gaenzlich leer" initialisiert     }
{     NextSprite[] wurde so gesetzt, dass jedes Sprite sein eigener Nach-}
{     folger ist}
{     PAGE, PAGEADR wurden auf die Grafikseite 0 eingestellt}
{     BACKGNDADR wurde auf die Hintergrundseite gesetzt}
{     SCROLLADR wurde auf die scrollbare Hintergrundseite gesetzt}
{     BACKGROUNDMODE wurde auf STATIC gesetzt}
{     StartVirtualX,StartVirtualY = 0 (d.h.: virtuelle = absolute Koord. }
{     oldMode = alter Grafikmodus}
{     Error wurde gesetzt, falls keine VGA-Karte im System enthalten ist }
{     CycleTime = 0, d.h.: keine Mindestzeit fuer einen Animationszyklus }
{     Color = 15, d.h.: weiss (dto.) }
{rem: Diese Prozedur sollte einmal - ganz zu Beginn - zur Initialisierung}
{     des Animationspaketes aufgerufen werden                            }
VAR i:WORD;

    FUNCTION IsVGA:BOOLEAN;
    BEGIN
     WITH Regs DO
      BEGIN
       AX:=$1A00;
       Intr($10,Regs);
       IsVGA:=(AL=$1A) AND    {VGA Identify-Adapter-Function unterstuetzt?}
              ( (BL=7) OR (BL=8) )  {VGAMono oder VGAColor - Adapter}
      END;
    END;

BEGIN
 IF IsVGA THEN Error:=Err_None
          ELSE Error:=Err_NoVGA;

 SetCycleTime(0);

 fillchar(SpriteN,sizeof(SpriteN),0);
 fillchar(SPRITEAD,sizeof(SPRITEAD),0);

 FOR i:=0 TO LoadMAX DO NextSprite[i]:=i;

 BACKGNDADR:=Segment_Adr[BACKGNDPAGE];  {Segmentadresse der Hintergrundseite}

 PAGE:=0;   {Seite, auf der gezeichnet werden soll}
 PAGEADR:=Segment_Adr[PAGE];
 SCROLLADR:=Segment_Adr[SCROLLPAGE];
 SetBackgroundMode(STATIC);

 StartVirtualX:=0; StartVirtualY:=0;    {virtuelle = absolute Koordinaten}
 Color:=white;            {aktuelle Zeichenfarbe sei Weiss }
 regs.ah:=$f; intr($10,regs); oldMode:=regs.al;

END;

PROCEDURE CloseRoutines;
{ in: oldMode = alter Grafikmodus, auf den zurueckgeschaltet werden soll}
{out: - }
BEGIN
 regs.al:=oldMode; regs.ah:=0; intr($10,regs);
END;

FUNCTION GetErrorMessage:STRING;
{ in: Error = Nummer des aufgetretenen Fehlers}
{out: den Fehler in Worten}
BEGIN
 CASE Error OF
  Err_None:GetErrorMessage:='No Error';
  Err_NotEnoughMemory:GetErrorMessage:='Not enough memory available on heap';
  Err_FileIO:GetErrorMessage:='I/O-error with file';
  Err_InvalidSpriteNumber:GetErrorMessage:='Invalid sprite number used';
  Err_NoSprite:GetErrorMessage:='No (or corrupted) sprite file';
  Err_InvalidPageNumber:GetErrorMessage:='Invalid page number used';
  Err_NoVGA:GetErrorMessage:='No VGA-card found';
  Err_NoPicture:GetErrorMessage:='No (or corrupted) picture file';
  Err_InvalidPercentage:GetErrorMessage:='Percentage value must be 0..100';
  Err_NoTile:GetErrorMessage:='No (or corrupted) tile/sprite file';
  Err_InvalidTileNumber:GetErrorMessage:='Invalid tile number used';
  Err_InvalidCoordinates:GetErrorMessage:='Invalid coordinates used';
  Err_BackgroundToBig:GetErrorMessage:='Background too big for tile-buffer';
  Err_InvalidMode:GetErrorMessage:='Only STATIC or SCROLLING allowed here';
  Err_InvalidSpriteLoadNumber:GetErrorMessage:='Invalid spriteload number used';
  ELSE GetErrorMessage:='Unknown error';
 END;
END;


BEGIN

 InitRoutines;

END.
[ RETURN TO DIRECTORY ]