News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_AMSDOS

Moving Graphical Images Down the Screen.

Started by AMSDOS, 11:52, 29 August 12

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ervin

#25
If you do decide to delve into CCZ80, there are some gotchas related to low-level operations.

It looks like C, and behaves like it in many ways, but it's actually simpler. It's like a pre-C language in some ways, that feels like it has some feet in the assembler pond, though it doesn't actually involve assembly language unless you need to inline it.

The upshot of this is that it produces fast binaries!
If you decide to try it and have any questions, feel free to ask away!

My fps testing method is not very scientific. It's fairly accurate, but it's not perfect.
:)

I have an 8-bit counter variable (data type BYTE in ccz80 terms), which I set to 0 at the start of the program.
After a frame of my game/program is displayed, I increment the counter variable, and print it to the screen.


counter++;
locate(18,25);
printb(counter);


Now, I use an ULTRA HIGH TECH method called... the stopwatch!
(Well, the stopwatch app on my phone).  8)
At the same moment I hit Enter to run my program, I also start the stopwatch.
When the counter gets to 250, I stop the stopwatch, and take note of the time elapsed.

Now, this program I've been looking at today takes 12.6 seconds to render 250 frames.
However, I know that calling PRINTB 250 times takes 3.5 seconds.
I know this because putting in a second PRINTB alongside the first one makes the program take 3.5 seconds longer to get to 250 frames.
So, I subtract the 3.5 seconds because I don't want that being factored into the fps. That leaves 9.1 seconds.

250 frames in 9.1 seconds is approx 27 fps.

It's not ultra precise, but it's a handy way to check that optimisations are going in the right direction.

AMSDOS

Quote from: ervin on 07:35, 09 September 13
If you do decide to delve into CCZ80, there are some gotchas related to low-level operations.

It looks like C, and behaves like it in many ways, but it's actually simpler. It's like a pre-C language in some ways, that feels like it has some feet in the assembler pond, though it doesn't actually involve assembly language unless you need to inline it.

The upshot of this is that it produces fast binaries!
If you decide to try it and have any questions, feel free to ask away!

It looks interesting, though I don't seem to have much time have a look at it properly and seem to play around with the old languages found on Amstrad.  :D Though I was wondering how good the Assembly files were compared with CPC BASIC 3, or are they sort of on par with one another? The Assembly Files produced with CPC BASIC 3 are okay, though a bit of a nightmare trying to get them to work in Winape Assembler.

QuoteMy fps testing method is not very scientific. It's fairly accurate, but it's not perfect.
:)

I have an 8-bit counter variable (data type BYTE in ccz80 terms), which I set to 0 at the start of the program.
After a frame of my game/program is displayed, I increment the counter variable, and print it to the screen.


counter++;
locate(18,25);
printb(counter);


Now, I use an ULTRA HIGH TECH method called... the stopwatch!
(Well, the stopwatch app on my phone).  8)
At the same moment I hit Enter to run my program, I also start the stopwatch.
When the counter gets to 250, I stop the stopwatch, and take note of the time elapsed.

Now, this program I've been looking at today takes 12.6 seconds to render 250 frames.
However, I know that calling PRINTB 250 times takes 3.5 seconds.
I know this because putting in a second PRINTB alongside the first one makes the program take 3.5 seconds longer to get to 250 frames.
So, I subtract the 3.5 seconds because I don't want that being factored into the fps. That leaves 9.1 seconds.

250 frames in 9.1 seconds is approx 27 fps.

It's not ultra precise, but it's a handy way to check that optimisations are going in the right direction.

That's interesting. So I presume that if the playing field was smaller like the 8x8 example and POKEs were used instead of PRINT, then the fps would be higher? I guess that would explain why a lot of the later Amstrad games used Spectrum size screen to increase the speed of the game.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

#27
The asm files produced by each one are largely on par, though CPC Basic 3-produced asm files seem to involve some wrapper functions which may slow them down a tad.
Though any slowdown may be un-noticable in real-world scenarios.

If you find CPC Basic 3 easier to use, I have no hesitation recommending it.
Except that its handling of REAL numbers can be kind of tricky (REAL number functions are handled as RSXs and require a bit of tricky setup to get working).

Regarding the fps, 8*8 would indeed be considerably faster than 8*20.
I'll try to measure the difference tomorrow (I left the ccz80 code on my work network, and don't have access to it now!)

In the case of games, yes indeed, spectrum-sized screens are certainly faster than full cpc-sized screens to redraw.
Even so, a spectrum-sized screen on the cpc is still considerably heavier on the cpu than the same sized screen would be on the speccy.
In fact, for the same sized screen, the cpc's screen would need around twice the number of bytes written to memory, not taking the speccy's colour attribute table into account.

I believe this is the primary reason why speccy games are pretty much always faster (some considerably so) than the amstrad version of the same game. There are some exceptions of course, but they are typically games that may use the cpc's hardware scroll to relieve the load on the z80, and to be honest I can't think of any examples right now!

[EDIT] Using the ccz80 version with optimised inlined assembler, on an 8*8 display, I seem to be getting 65-70 fps! It certainly looks amazingly quick!

Here's the ccz80 code:


include "cpc6128.ccz80", "cpcbasic.ccz80";

array word Data_00600 = { "0", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00610 = { "1", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00620 = { "0", "0", "1", "0", "0", "1", "0", "0" };
array word Data_00630 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00640 = { "0", "1", "0", "0", "0", "0", "0", "0" };
array word Data_00650 = { "1", "0", "1", "0", "0", "0", "0", "0" };
array word Data_00660 = { "0", "0", "0", "1", "1", "0", "0", "0" };
array word Data_00670 = { "0", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00680 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00690 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00700 = { "1", "0", "0", "0", "0", "1", "1", "1" };
array word Data_00710 = { "1", "1", "1", "0", "0", "0", "0", "1" };
array word Data_00720 = { "1", "1", "1", "0", "0", "0", "0", "1" };
array word Data_00730 = { "1", "0", "0", "1", "1", "0", "0", "1" };
array word Data_00740 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00750 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00760 = { "1", "0", "0", "1", "0", "0", "1", "1" };
array word Data_00770 = { "1", "0", "0", "1", "1", "0", "0", "1" };
array word Data_00780 = { "1", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00790 = { "1", "1", "0", "0", "1", "1", "1", "1" };

byte VarByte_Base;
word VarWord_Charaddr;
byte VarByte_Colour;
byte VarByte_M;
byte VarByte_N;
byte VarByte_S;
byte VarByte_X;
byte VarByte_Y;

byte counter;
counter=0;

array byte TabByte_Map[161 * 1];
// array byte TabByte_Scr[161 * 1];
array byte TabByte_Scr[65 * 1];

SetDataPointer(Data_00600);

for (VarByte_N = 1; 160 >= VarByte_N; ++VarByte_N)
  *(TabByte_Map + VarByte_N) = Val(Data());

// for (VarByte_N = 1; 160 >= VarByte_N; ++VarByte_N)
//   *(TabByte_Scr + VarByte_N) = 0;

for (VarByte_N = 1; 64 >= VarByte_N; ++VarByte_N)
  *(TabByte_Scr + VarByte_N) = 0;

Mode(0);
Ink(0, 0, 0);
Ink(1, 26, 26);

VarByte_Base = 1;

Line_00300:
VarByte_S = 1;
VarByte_M = VarByte_Base;
VarWord_Charaddr = 49312;

// for (VarByte_Y = 3; 22 >= VarByte_Y; ++VarByte_Y)
for (VarByte_Y = 1; 8 >= VarByte_Y; ++VarByte_Y)
{
  for (VarByte_X = 1; 8 >= VarByte_X; ++VarByte_X)
  {
    if (*(TabByte_Scr + VarByte_S) == *(TabByte_Map + VarByte_M))
      GoTo Line_00360;

    *(TabByte_Scr + VarByte_S) = *(TabByte_Map + VarByte_M);

    if (*(TabByte_Map + VarByte_M) == 1)
      VarByte_Colour = 192;
    else
      VarByte_Colour = 0;

    asm
    {
      "ld a,(_VarByte_Colour)",
      "ld bc,2048",
      "ld de,8192",

      "ld hl,(_VarWord_Charaddr)","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
    }

Line_00360:
    VarByte_S = (VarByte_S + 1);
    VarByte_M = (VarByte_M + 1);
    VarWord_Charaddr = (VarWord_Charaddr + 4);
  }

  if (VarByte_M > 160)
    VarByte_M = 1;

  VarWord_Charaddr = ((VarWord_Charaddr - 32) + 80);
}

VarByte_Base = (VarByte_Base + 8);

if (VarByte_Base > 160)
  VarByte_Base = 1;

// counter++;
// locate(16,25);
// printb(counter);

GoTo Line_00300;

return;


AMSDOS

Quote from: ervin on 14:59, 09 September 13
The asm files produced by each one are largely on par, though CPC Basic 3-produced asm files seem to involve some wrapper functions which may slow them down a tad.
Though any slowdown may be un-noticable in real-world scenarios.

You'll have to excuse my whinging cause I like to see neat and tidy Assembly source code, and I like to see Data in DEFB or DEFW statements and not DEFB or DEFW statements which points to a variable which points to a value that way which just looks messy.  :'(

QuoteIf you find CPC Basic 3 easier to use, I have no hesitation recommending it.
Except that its handling of REAL numbers can be kind of tricky (REAL number functions are handled as RSXs and require a bit of tricky setup to get working).

I can probably do some mockup in CPC BASIC 3, the next phase will be using Sprites, just trying to remember the dimensions of the sprites cause they were larger than 8x8, I think they were 16x16, the one I'm using for the Square blocks I think is 16x16, so will be able to space them or just space the X & Y variables.
I was also going to play around with this in Hisoft Pascal, though the easiest way to make level data to get it into an array (cause it doesn't support constant array's), is to write the data into assembly, save it, convert it to a Hisoft file to load into an array, which I have made programs to do this.

QuoteRegarding the fps, 8*8 would indeed be considerably faster than 8*20.
I'll try to measure the difference tomorrow (I left the ccz80 code on my work network, and don't have access to it now!)

In the case of games, yes indeed, spectrum-sized screens are certainly faster than full cpc-sized screens to redraw.
Even so, a spectrum-sized screen on the cpc is still considerably heavier on the cpu than the same sized screen would be on the speccy.
In fact, for the same sized screen, the cpc's screen would need around twice the number of bytes written to memory, not taking the speccy's colour attribute table into account.

I believe this is the primary reason why speccy games are pretty much always faster (some considerably so) than the amstrad version of the same game. There are some exceptions of course, but they are typically games that may use the cpc's hardware scroll to relieve the load on the z80, and to be honest I can't think of any examples right now!

So if an Amstrad adjusted the size of the screen to a Spectrum screen, it slows it down normally, but if the screen remained as an Amstrad standard screen, but was made to look like a Spectrum Size screen, it would run faster?

Quote[EDIT] Using the ccz80 version with optimised inlined assembler, on an 8*8 display, I seem to be getting 65-70 fps! It certainly looks amazingly quick!

Here's the ccz80 code:


include "cpc6128.ccz80", "cpcbasic.ccz80";

array word Data_00600 = { "0", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00610 = { "1", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00620 = { "0", "0", "1", "0", "0", "1", "0", "0" };
array word Data_00630 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00640 = { "0", "1", "0", "0", "0", "0", "0", "0" };
array word Data_00650 = { "1", "0", "1", "0", "0", "0", "0", "0" };
array word Data_00660 = { "0", "0", "0", "1", "1", "0", "0", "0" };
array word Data_00670 = { "0", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00680 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00690 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00700 = { "1", "0", "0", "0", "0", "1", "1", "1" };
array word Data_00710 = { "1", "1", "1", "0", "0", "0", "0", "1" };
array word Data_00720 = { "1", "1", "1", "0", "0", "0", "0", "1" };
array word Data_00730 = { "1", "0", "0", "1", "1", "0", "0", "1" };
array word Data_00740 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00750 = { "0", "0", "0", "0", "0", "0", "0", "0" };
array word Data_00760 = { "1", "0", "0", "1", "0", "0", "1", "1" };
array word Data_00770 = { "1", "0", "0", "1", "1", "0", "0", "1" };
array word Data_00780 = { "1", "0", "0", "0", "0", "0", "0", "1" };
array word Data_00790 = { "1", "1", "0", "0", "1", "1", "1", "1" };

byte VarByte_Base;
word VarWord_Charaddr;
byte VarByte_Colour;
byte VarByte_M;
byte VarByte_N;
byte VarByte_S;
byte VarByte_X;
byte VarByte_Y;

byte counter;
counter=0;

array byte TabByte_Map[161 * 1];
// array byte TabByte_Scr[161 * 1];
array byte TabByte_Scr[65 * 1];

SetDataPointer(Data_00600);

for (VarByte_N = 1; 160 >= VarByte_N; ++VarByte_N)
  *(TabByte_Map + VarByte_N) = Val(Data());

// for (VarByte_N = 1; 160 >= VarByte_N; ++VarByte_N)
//   *(TabByte_Scr + VarByte_N) = 0;

for (VarByte_N = 1; 64 >= VarByte_N; ++VarByte_N)
  *(TabByte_Scr + VarByte_N) = 0;

Mode(0);
Ink(0, 0, 0);
Ink(1, 26, 26);

VarByte_Base = 1;

Line_00300:
VarByte_S = 1;
VarByte_M = VarByte_Base;
VarWord_Charaddr = 49312;

// for (VarByte_Y = 3; 22 >= VarByte_Y; ++VarByte_Y)
for (VarByte_Y = 1; 8 >= VarByte_Y; ++VarByte_Y)
{
  for (VarByte_X = 1; 8 >= VarByte_X; ++VarByte_X)
  {
    if (*(TabByte_Scr + VarByte_S) == *(TabByte_Map + VarByte_M))
      GoTo Line_00360;

    *(TabByte_Scr + VarByte_S) = *(TabByte_Map + VarByte_M);

    if (*(TabByte_Map + VarByte_M) == 1)
      VarByte_Colour = 192;
    else
      VarByte_Colour = 0;

    asm
    {
      "ld a,(_VarByte_Colour)",
      "ld bc,2048",
      "ld de,8192",

      "ld hl,(_VarWord_Charaddr)","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",

      "ld hl,(_VarWord_Charaddr)","add hl,de","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
      "inc hl","ld (hl),a",
    }

Line_00360:
    VarByte_S = (VarByte_S + 1);
    VarByte_M = (VarByte_M + 1);
    VarWord_Charaddr = (VarWord_Charaddr + 4);
  }

  if (VarByte_M > 160)
    VarByte_M = 1;

  VarWord_Charaddr = ((VarWord_Charaddr - 32) + 80);
}

VarByte_Base = (VarByte_Base + 8);

if (VarByte_Base > 160)
  VarByte_Base = 1;

// counter++;
// locate(16,25);
// printb(counter);

GoTo Line_00300;

return;


That's just nuts, sounds like a pretty difficult game, may have to adjust the level data around cause that information looks like it would work well on one of the later levels.  :o
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

#29
Quote from: AMSDOS on 11:20, 10 September 13
So if an Amstrad adjusted the size of the screen to a Spectrum screen, it slows it down normally, but if the screen remained as an Amstrad standard screen, but was made to look like a Spectrum Size screen, it would run faster?

No, that's not quite how it works.
It's all about how many bytes need to be written to video ram in order to display a frame.

Reading back my previous message, I can see that I kind of rambled on a bit (I do that from time to time, sorry about that).
Because the spectrum screen takes between 6 and 7 KB (I can't remember the exact figure), and the cpc screen takes up 16KB, it simply takes longer to paint graphics on the cpc than it does on the spectrum, because more bytes need to be written to.

So a number of tricks and techniques need to be employed to speed up the display of sprites etc.
You used one of those techniques in your program - only update parts of the screen that have changed.

Yes, you're right about the messy tables in the ccz80 code I posted.
I largely left the generated code alone (from compiling with cpc basic 3), so it still contains a number of cpc basic 3 wrappers.
I'll have a go at removing some of those and see what happens...

ervin

#30
Okay, the 20*8 version is now running at 31 fps.
I removed the CPC Basic 3 wrapper functions, and optimised things a bit further.


include "cpc6128.ccz80";

byte counter;
counter=0;

byte base;
byte colour;
byte n;

word charPtr;
word mapPtr;
word scrPtr;

array byte scr[160];

array byte map={
0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,
0,0,1,0,0,1,0,0,
0,0,0,0,0,0,0,0,
0,1,0,0,0,0,0,0,
1,0,1,0,0,0,0,0,
0,0,0,1,1,0,0,0,
0,0,0,0,0,0,0,1,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
1,0,0,0,0,1,1,1,
1,1,1,0,0,0,0,1,
1,1,1,0,0,0,0,1,
1,0,0,1,1,0,0,1,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
1,0,0,1,0,0,1,1,
1,0,0,1,1,0,0,1,
1,0,0,0,0,0,0,1,
1,1,0,0,1,1,1,1,
};

for (n=0;n<=159;n++)
  *(scr+n)=0;

base=0;

mode(0);
ink(0, 0, 0);
ink(1, 26, 26);

LINE_00300:

charPtr=#c0a0;
mapPtr=map+base;
scrPtr=scr;

repeat(20) // 20 rows
{
  repeat( // 8 columns
  {
    if (*(scrPtr)==*(mapPtr))
      goto LINE_00360;

    *(scrPtr)=*(mapPtr);

    if (*(mapPtr)==0)
      colour=0;
    else
      colour=#c0;

    asm
    {
    "ld a,(_colour)",
    "ld bc,2048",
    "ld de,8192",

    "ld hl,(_charPtr)","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,bc","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,de","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,de","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,de","add hl,bc","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",

    "ld hl,(_charPtr)","add hl,de","add hl,bc","add hl,bc","add hl,bc","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    "inc hl","ld (hl),a",
    }

LINE_00360:

    charPtr+=4;
    mapPtr++;
    scrPtr++;
  }

  if (mapPtr==(map+160))
    mapPtr=map;

  charPtr+=80-32;
}

base+=8;

if (base==160)
  base=0;

// counter++;
// locate(16,25);
// printb(counter);

goto LINE_00300;

return;

AMSDOS

I'm having a look at the earlier BASIC code to see how I could write it in Assembly, at the moment I'm unsure how successful it will be writing it straight into Assembly cause I haven't really compared the contents of Array's before and do a jump based on that. Other little things will need to be changed too I think, to do with the start value for the array. I could probably gather some assembly code from CPC BASIC 3 which reflects how assembly tackles, if I need clues.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

As I've been learning assembly, I've found the asm files spat out by ccz80 to be incredibly helpful.
It leaves the actual ccz80 code in the comments, and the relevant asm code is placed with each commented-out bit of ccz80 code.
This of course means the entire program is broken down into small blocks of asm, which is very easy to learn from and understand.
(Also helps with a bit of optimisation).

So yes, using the asm generated by CPC Basic 3 will be very useful indeed.
The only thing to watch out for is that CPC Basic 3 compiles to ccz80 code, with some Basic-related wrapper functions.
This is then compiled into a binary file (and is where the asm is derived from).

Good luck!
(I'm happy to help if you need any ideas/tips - I'm enjoying this stuff).

AMSDOS

Quote from: ervin on 13:19, 11 September 13
As I've been learning assembly, I've found the asm files spat out by ccz80 to be incredibly helpful.
It leaves the actual ccz80 code in the comments, and the relevant asm code is placed with each commented-out bit of ccz80 code.
This of course means the entire program is broken down into small blocks of asm, which is very easy to learn from and understand.
(Also helps with a bit of optimisation).

So yes, using the asm generated by CPC Basic 3 will be very useful indeed.
The only thing to watch out for is that CPC Basic 3 compiles to ccz80 code, with some Basic-related wrapper functions.
This is then compiled into a binary file (and is where the asm is derived from).

Good luck!
(I'm happy to help if you need any ideas/tips - I'm enjoying this stuff).

Well this is what I have at the moment:


org &4000

ld a,0
ld bc,0
call &bc32
ld a,1
ld bc,&1a1a
call &bc32

.setbase
ld a,(isone)
ld (base),a

.return ld a,(isone)
ld (s),a
ld a,(base)
ld (m),a

.y_loop ld a,(y)
inc a
ld (y),a

.x_loop ld a,(x)
inc a
ld (x),a

ld hl,scr
ld a,(s)
ld c,a
ld b,0
add hl,bc
push hl
ld hl,map
ld a,(m)
ld c,a
ld b,0
add hl,bc
ld a,(hl)
pop hl
cp (hl)
jr z,skip

ld hl,map
ld a,(m)
ld c,a
ld b,0
add hl,bc
ex hl,de
ld hl,scr
ld a,(s)
ld c,a
ld b,0
add hl,bc

ld (hl),e
inc hl
ld (hl),d

ld hl,(y) ;; {
;; ld h,a ;; {
;; ld a,(y) ;; { Position Cursor
;; ld l,a ;; {
call &bb75 ;; {

ld hl,map
ld a,(m)
ld c,a
ld b,0
add hl,bc
ld a,(hl)
cp 1
jr nz,skip2

ld a,143
call &bb5a

cp 0
jr z,skip

.skip2 ld a,32
call &bb5a

.skip ld a,(s)
inc a
ld (s),a

ld a,(m)
inc a
ld (m),a

ld a,(x)
ld b,a
ld a,(xcount)
cp b
jr nz,x_loop

ld a,(m)
ld b,a
ld a,160
cp b
jr nc,chk_y

ld a,(isone)
ld (m),a

.chk_y ld a,(x)
xor a
ld (x),a

ld a,(y)
ld b,a
ld a,(ycount)
cp b
jp nz,y_loop

ld a,(x)
xor a
ld (x),a

ld a,(y)
xor a
ld (y),a

ld a,(base)
add a,8
ld (base),a
ld b,a
ld a,160
cp b
jp c,setbase

jp return
ret

.isone defb 1
.base defb 1
.m defb 0
.s defb 0
.y defb 0
.x defb 0
.xcount defb 8
.ycount defb 7
.scr defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0

.map defb 0,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,0
defb 0,0,1,0,0,1,0,0
defb 0,0,0,0,0,0,0,0
defb 0,1,0,0,0,0,0,0
defb 1,0,1,0,0,0,0,0
defb 0,0,0,1,1,0,0,0
defb 0,0,0,0,0,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,0,0,1,1,1
defb 1,1,1,0,0,0,0,1
defb 1,1,1,0,0,0,0,1
defb 1,0,0,1,1,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,1,0,0,1,1
defb 1,0,0,1,1,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,1,0,0,1,1,1,1


which seems to work, though I think it'll need some adjustments, I'm not 100% sure the pattern I've made in map is the pattern which is being produced here either and there's some other funny things happening as well when I pause the emulator, the loop structure seems fine, but the poking of scr seems to be bigger than 64 bytes if I have xcount & ycount set to 8. I set ycount to 7 which seems to prevent anything being written over into map though, where before this was being overwritten.
The use of Firmware in this circumstance I think is in reality slowing the program down, though for demonstration purposes it looks fine.

Some of the ideas here I grabbed from CPC BASIC 3 Assembly Code, I could probably improve this by having SCRPOS & MAPPOS variables which point to those Arrays in particular, I tried this earlier, but was crashing the system, I was having a lot of problems happening when I tried that, which I've since figured out and other bits of code which CPC BASIC 3 used I could seriously cut-down by simplifying the conditions and setting conditional jumping to the relevant routine.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

AMSDOS

Here's an update which looks a bit better & it's showing the data correctly (based on the 8x8 BASIC version) and I've added some comments:

org &4000

ld a,0
ld bc,0
call &bc32 ;; Set up Inks
ld a,1
ld bc,&1a1a
call &bc32

.setbase
ld a,(isone)
ld (base),a ;; base = 1

.return ld a,(isone)
ld (s),a ;; s = 1 (for position of Scr Array)
ld a,(base)
ld (m),a ;; m = base ( for position of map array)

.y_loop ld a,(y)
inc a
ld (y),a ;; increment y

.x_loop ld a,(x)
inc a
ld (x),a ;; increment x

ld hl,scr ;; setup scr array
ld a,(s) ;; s determines position
ld c,a ;; has to be incremented
ld b,0 ;; using bc
add hl,bc ;; hl holds position of scr
push hl ;; needs to be preserved
ld hl,map ;; setup map array
ld a,(m) ;; with m as position of it
ld c,a ;; again using
ld b,0 ;; bc to incrment with
add hl,bc ;; hl holds position of map
ld a,(hl) ;; store this into accumulator
pop hl ;; restore position of scr array
cp (hl) ;; compare contents scr with map position
jr z,skip ;; if it's the same then jump to skip

ld hl,map ;; }
ld a,(m) ;; }
ld c,a ;; }
ld b,0 ;; }
add hl,bc ;; }
ex hl,de ;; }
ld hl,scr ;; } Otherwise scr position
ld a,(s) ;; } equals map position
ld c,a ;; }
ld b,0 ;; }
add hl,bc ;; }
ld (hl),e ;; }
inc hl ;; }
ld (hl),d ;; }

ld hl,(y) ;; Position
call &bb75 ;; Cursor

ld hl,map ;;
ld a,(m) ;;
ld c,a ;;
ld b,0 ;; if position of map
add hl,bc ;; is not equal 1 then
ld a,(hl) ;; jump to skip2
cp 1 ;;
jr nz,skip2 ;;

ld a,143 ;; Otherwise
call &bb5a ;; print block

.skip2 ld a,32 ;; else area
call &bb5a ;; is blank

.skip ld a,(s)
inc a ;; increment s to next position
ld (s),a

ld a,(m)
inc a ;; increment m to next position
ld (m),a

ld a,(x) ;; if x has not reached
ld b,a
ld a,(xcount) ;; value determine by xcount
cp b
jr nz,x_loop ;; jump back to x_loop for another pass

ld a,(m) ;; }
ld b,a ;; } if m has not reached a value 
ld a,161 ;; } of 161 or more then jump to chk_y
cp b ;; }
jr nc,chk_y ;; }

ld a,(isone)
ld (m),a ;; otherwise m = 1

.chk_y ld a,(x)
xor a
ld (x),a ;; x = 0

ld a,(y) ;; if y has not reached
ld b,a
ld a,(ycount) ;; value determine by ycount
cp b
jp nz,y_loop ;; jump back to y_loop for another pass

ld a,(x)
xor a
ld (x),a ;; x = 0

ld a,(y)
xor a
ld (y),a ;; y = 0

ld a,(base) ;; }
add a,8 ;; } increment base by 8
ld (base),a ;; }
ld b,a
ld a,161 ;; if base > 161
cp b
jp c,setbase ;; jump to setbase if value has been reached

jp return ;; otherwise jump to return position
ret

.isone defb 1 ;; Constant value of 1
.base defb 0
.m defb 0
.s defb 0
.y defb 0
.x defb 0
.xcount defb 8
.ycount defb 8
.scr defb 0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0

.map defb 0
defb 0,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,0
defb 0,0,1,0,0,1,0,0
defb 0,0,0,0,0,0,0,0
defb 0,1,0,0,0,0,0,0
defb 1,0,1,0,0,0,0,0
defb 0,0,0,1,1,0,0,0
defb 0,0,0,0,0,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,0,0,1,1,1
defb 1,1,1,0,0,0,0,1
defb 1,1,1,0,0,0,0,1
defb 1,0,0,1,1,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,1,0,0,1,1
defb 1,0,0,1,1,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,1,0,0,1,1,1,1


I'm not sure if it's really necessary to have the code to check if scr = map bit and if it does jump to skip, it works with or without it. Likewise with the BASIC version - Line 330, however I noticed with the BASIC version having that bit of code there, speeds up the program. From assembly the code seems to swiftly function, I've left the code there to make it easier to follow the BASIC<->Assembly Versions, though it could be removed if one desires.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

#35
It seems that you're doing something wrong in this bit:


ld hl,map  ;; }
ld a,(m)  ;; }
ld c,a   ;; }
ld b,0   ;; }
add hl,bc  ;; }

;; ex hl,de  ;; }
ld e,(hl)

ld hl,scr  ;; } Otherwise scr position
ld a,(s)  ;; } equals map position
ld c,a   ;; }
ld b,0   ;; }
add hl,bc  ;; }

ld (hl),e  ;; }
;; inc hl   ;; }
;; ld (hl),d  ;; }


With the EX DE,HL you were putting the memory location of MAP+(m) into DE.
And then you're putting the low & high bytes of that memory location into the contents of memory location SCR+(s).

I think you should be putting the contents of memory location MAP+(m) into the contents of memory location SCR+(s).

I've commented out the EX DE,HL and put in LD E,(HL). This stores the contents of memory location MAP+(m) in the E register.

I've then written the value in E to memory location SCR+(s), and commented out the last 2 lines.

This has made the program quite a bit faster.

ervin

#36
Here are some more tidy-ups to x_loop.


.x_loop
ld a,(x)
inc a
ld (x),a  ;; increment x

ld hl,scr
ld bc,(s)
ld b,0
add hl,bc
ld a,(hl)

ld hl,map
ld bc,(m)
ld b,0
add hl,bc

cp (hl)   ;; compare contents scr with map position
jr z,skip  ;; if it's the same then jump to skip

ld hl,map  ;; }
ld bc,(m)
ld b,0
add hl,bc  ;; }
ld a,(hl)

ld hl,scr  ;; } Otherwise scr position
ld bc,(s)
ld b,0
add hl,bc  ;; }
ld (hl),a  ;; }

ld hl,(y)  ;; Position
call &bb75  ;; Cursor

ld hl,map  ;;
ld bc,(m)
ld b,0
add hl,bc  ;; is not equal 1 then
ld a,(hl)  ;; jump to skip2

cp 1   ;;
jr nz,skip2  ;;

ld a,143  ;; Otherwise
call &bb5a  ;; print block

ervin

#37
Some minor algorithm changes, and a number of optimisations, so it's a bit faster.
The code is a bit shorter too, and hopefully easier to understand.

I've also used &BB5D (txt wr char) to print the characters, which may be a tiny bit faster as well.

Also, I'm not really sure how, but now 8 columns are being printed (which I presume is the desired behaviour), as 9 were being printed before. I'm not sure which of my changes did it though!  8)


org &4000
ld a,0
ld bc,0
call &bc32 
ld a,1
ld bc,&1a1a
call &bc32
.setbase
ld a,isone
ld (base),a 
.return
ld a,isone
ld (s),a 

ld a,(base)
ld (m),a 
.y_loop
ld hl,y
inc (hl)
.x_loop
ld hl,x
inc (hl)

ld hl,map
ld de,(m)
ld d,0
add hl,de
ld a,(hl)

ld hl,scr
ld bc,(s)
ld b,0
add hl,bc

cp (hl)   
jr z,skip 

ld (hl),a 

ld hl,(y) 
call &bb75 

ld hl,map 
add hl,de
ld a,(hl) 
cp 1   
jr nz,skip2 

ld a,143 
call &bb5d 
jp skip
.skip2
ld a,32   
call &bb5d 
.skip
ld hl,s
inc (hl)
ld hl,m
inc (hl)
ld a,(x) 
cp xcount
jr nz,x_loop 

ld a,(m) 
cp 161
jr nz,chk_y
ld a,isone
ld (m),a 
.chk_y
xor a
ld (x),a 

ld a,(y) 
cp ycount
jr nz,y_loop 
xor a
ld (y),a 
ld a,(base) 
add a,8   
ld (base),a 
cp 161
jp nc,setbase
jp return 
.isone equ 1 
.base defb 0
.m defb 0
.s defb 0
.y defb 0
.x defb 0
.xcount equ 8
.ycount equ 8
.scr defb 0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
.map defb 0
defb 0,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,0
defb 0,0,1,0,0,1,0,0
defb 0,0,0,0,0,0,0,0
defb 0,1,0,0,0,0,0,0
defb 1,0,1,0,0,0,0,0
defb 0,0,0,1,1,0,0,0
defb 0,0,0,0,0,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,0,0,1,1,1
defb 1,1,1,0,0,0,0,1
defb 1,1,1,0,0,0,0,1
defb 1,0,0,1,1,0,0,1
defb 0,0,0,0,0,0,0,0
defb 0,0,0,0,0,0,0,0
defb 1,0,0,1,0,0,1,1
defb 1,0,0,1,1,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,0,0,0,0,0,0,1
defb 1,1,0,0,1,1,1,1

AMSDOS

Those changes look good, unfortunately I was in the process of changing the program around when you posted those, though I still haven't quite got it the way I want it to work, I also moved around the x & y counter and changed those loops to be just one mainloop.

But the optimisations look interesting and I had no idea you could simply increase the value in memory with "inc (hl)" like that. Normally I like to make the most out of HL or (HL) cause I can follow the program a little bit easier which is why I use ex hl,de to put things into DE, and it works out the same as a LD DE,(xxxx). I don't understand why I cannot do an "ADD HL,A", why is that illegal? It would have been so handy to increment a Register Pair with just a Register instead of a Register Pair with a Register pair. I was thinking perhaps it would be better to sacrifice a byte, by leaving it as 0 and have the high Register bit (i.e. "H","D" or "B") point to that value, like of like how I setup the Y co-ordinate value, so L holds the "Y" coordinate value and "H" goes into the "X" coordinate value and it just saves doing a "LD B,0" or whatever.

I wasn't fussed about the Displaying routine and after I nutted out these changes I wanted to make, I was going to try and some a sprite routine working in there instead.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

Quote from: AMSDOS on 09:32, 17 September 13
But the optimisations look interesting and I had no idea you could simply increase the value in memory with "inc (hl)" like that. Normally I like to make the most out of HL or (HL) cause I can follow the program a little bit easier which is why I use ex hl,de to put things into DE, and it works out the same as a LD DE,(xxxx). I don't understand why I cannot do an "ADD HL,A", why is that illegal? It would have been so handy to increment a Register Pair with just a Register instead of a Register Pair with a Register pair. I was thinking perhaps it would be better to sacrifice a byte, by leaving it as 0 and have the high Register bit (i.e. "H","D" or "B") point to that value, like of like how I setup the Y co-ordinate value, so L holds the "Y" coordinate value and "H" goes into the "X" coordinate value and it just saves doing a "LD B,0" or whatever.

Yes indeed, I have on MANY occasions wished that there was something like ADD HL,A.
Unfortunately that command simply doesn't exist.
:(

TFM

A is a 8 bit register. HL is a 16 bit register. So they don't fit.

But instead of LD HL,A you can do:

LD H,&00
LD L,A

Now it some of your registers is zero by accident (maybe B after an LDIR), then you could do:

LD H,B
LD L,A

TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

cngsoft

TFM is right, and there's more: if you absolutely need to do ADD HL,A and you don't mind losing the contents of A or getting flags without the expected meanings, you can do this:

ADD L
LD L,A
ADC H
SUB L
LD H,A

5 bytes of 4 T each.
(if you can't see the banner right now my server is currently offline)

TFM

#42
Quote from: cngsoft on 14:30, 19 September 13
ADD L
LD L,A
ADC H
SUB L
LD H,A

Excellent!
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

AMSDOS

I appreciate working solutions to the ADD HL,A problem, unfortunately I was only wondering why an 8bit register couldn't be added into a 16bit register, it doesn't bug me to add a 16bit register with another 16bit register, I just thought it was a bit of a waste using another 16bit register to add to the first one.  ;D
I guess the problem in this context is I dabble with High-Level Languages where it's normally custom to use a Variable Type specifically what it's meant to do.  ;D
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

TFM

Oh, well it's like apples and pears.
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

AMSDOS

Well I've tried to come up with a more graphical example which works, but the graphics which is a brick I've grabbed from EgoTrips screenshot are moving across the screen rather than up the screen. I'm not sure what's happening there. There is also a problem with the size of the data which is going across the screen, I'm still trying say that the data is 8x8, but the bricks I've defined are 16x16 in size, so I'm trying to space them more across the screen, though when the program begins with the two bricks at the top of the screen, there should only be one on the first line and one at the start of the 3rd line.
Not sure if it has something to do with where things are finishing and returning when the drawing takes place in the sprite routine, it's about the only thing I haven't checked. The rest of the program seemed to work when I had the Simple Squares.
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

Oooh I'm looking forward to figuring out what you've done here.
These little programming projects are fun.  :)

AMSDOS

Quote from: ervin on 12:35, 01 October 13
Oooh I'm looking forward to figuring out what you've done here.
These little programming projects are fun.  :)

I tried applying some of your earlier routines to see if that would make a difference, though I've had no luck with them. It looks like there's a problem with the Loop Counter(s) not returning back to where their suppose to be which maybe why things are moving upward diagonally with that previous post, so maybe if one issue is fixed, it will solve the other?
* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

ervin

#48
I'm converting your code to ccz80, so that I can understand it more easily in order to find the problem.
Fingers crossed!  :)

[EDIT] Getting there, but I've gotta come back to it next week, as I won't have access to my PC for the next few days!  :'( Catch you then.

AMSDOS

I've prepared an example of this from BASIC which is using Sean McManus' Easi-Sprite Definer, so the process seems to be working with that, though the y-loop is only counting one at a time and my x-loop increments twice, so that might be what's wrong with the assembly.

The assembly I'm only writing as an example if people want to show the process of moving something without actually moving the screen, though it's fairly basic in nature and makes good use the firmware, though anyone is more than welcome to analyse it and enhance it, so I'm not overly bothered if I cannot find a solution for it.   :D

At this stage this BASIC looks interesting cause it's moving quite well, probably due to the use of ESD to display the Bricks, so it'll be interesting to compare when it gets compiled.

* Using the old Amstrad Languages :D   * with the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

Powered by SMFPacks Menu Editor Mod