Fire: Attempt to make a fire effect and optimization (C with SDCC)

Pincha aquí para verlo en español

In this tutorial we will try to make a fire effect on our Amstrad. The effect of fire is a more or less simple to program but has the drawback of having much computation per pixel, thus the expected speed is low, we do also entirely in C so that the result will be slower than if we did it completely in assembler.

I have programmed many times this effect in PC, since the time of the VGA, through GDI, Allegro, DirectX and some that do not even remember, but certainly in those cases did not have the limitations that gives us the CPC. We start with the palette, to make a fire effect usually specify a wide palette ranging from white through yellow, orange, red, purple to black to make it cool, in the case of CPC I decided to use a palette of 6 colors. The size also have greatly limited because it requires much calculation, leaving it in just 32x16.

The effect of fire is very simple to program, basically random values are applied at the bottom of the fire and then interpolation is done to simulate the fire rises upwards. This interpolation is simply an average of 4 points as follows:

pixel(x,y) = (pixel(x, y) + pixel(x-1, y-1) + pixel(x, y-1) + pixel(x+1, y-1) - 1) / 4;

Since we are limited to 6 colors and the fire would not rise, we will use an intermediate buffer to calculate the effect of fire, with values ​​from 0 to 60 per pixel then the paint will reduce to our 6 colors.

Without much further explanation we will see the first attempt:

////////////////////////////////////////////////////////////////////////
// Fire01.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//------------------------ Generic functions -------------------------//
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetBorder
////////////////////////////////////////////////////////////////////////
void SetBorder(unsigned char nColor)
{
  __asm
    ld b, 4 (ix)
    ld c, b
    call #0xBC38 ;SCR_SET_BORDER
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode0PixelColor
////////////////////////////////////////////////////////////////////////
void SetMode0PixelColor(unsigned char *pByteAddress, unsigned char nColor, unsigned char nPixel)
{
  unsigned char nByte = *pByteAddress;

  if(nPixel == 0)
  {
    nByte &= 85;

    if(nColor & 1)
      nByte |= 128;

    if(nColor & 2)
      nByte |= 8;

    if(nColor & 4)
      nByte |= 32;

    if(nColor & 8)
      nByte |= 2;
  }
  else
  {
    nByte &= 170;

    if(nColor & 1)
      nByte |= 64;

    if(nColor & 2)
      nByte |= 4;

    if(nColor & 4)
      nByte |= 16;

    if(nColor & 8)
      nByte |= 1;
  }

  *pByteAddress = nByte;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//PutPixelMode0
////////////////////////////////////////////////////////////////////////
void PutPixelMode0(unsigned char nX, unsigned char nY, unsigned char nColor)
{
  unsigned char nPixel = 0;
  unsigned int nAddress = 0xC000 + ((nY / 8) * 80) + ((nY % 8) * 2048) + (nX / 2);
  nPixel = nX % 2;

  SetMode0PixelColor((unsigned char *)nAddress, nColor, nPixel);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//---------------- Specific variables and functions-------------------//
////////////////////////////////////////////////////////////////////////

#define NUM_COLORS 6
const unsigned char FirePalette[NUM_COLORS] = {0, 3, 6, 15, 24, 26};

#define FIRE_WIDTH 32
#define FIRE_HEIGHT 16
unsigned char aFire[FIRE_WIDTH][FIRE_HEIGHT];

#define FIRE_COLOR_RANGE 60 // 6 colors

#define FIRE_X_ORIGIN 80 - (FIRE_WIDTH / 2)
#define FIRE_Y_ORIGIN 200

////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nX = 0;
  unsigned char nY = 0;
  
  SetMode(0);
  SetBorder(0);
  SetPalette(FirePalette, NUM_COLORS);
  
  memset(aFire, 0, sizeof(aFire));
  
  nTimeLast = GetTime();

  while(1)
  {
    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(1, 1);
      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
    
    //fills the botton line with random values
    for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
     aFire[nX][0] = rand() % FIRE_COLOR_RANGE;
     
    //interpolate colors from bottom to top
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      for(nX = 0; nX < FIRE_WIDTH; nX++)
      {
        aFire[nX][nY] = (aFire[nX][nY] + aFire[nX][nY - 1] +
                         (nX > 0 ? aFire[nX - 1][nY - 1] : 0) +
                         (nX < FIRE_WIDTH - 1 ? aFire[nX + 1][nY - 1] : 0) -
                         (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
      }
    }
    
    //screen
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      for(nX = 0; nX < FIRE_WIDTH; nX++)
      {
        PutPixelMode0(FIRE_X_ORIGIN + nX, FIRE_Y_ORIGIN - nY, aFire[nX][nY] / 10 + (aFire[nX][nY] % 10 >= 5 ? 1 : 0));
      }
    }

  }
}
////////////////////////////////////////////////////////////////////////

If you load Fire01.dsk in the emulator, you will see that the fire is very small and has a painful speed of 1 to 2 frames per second...

When I got to this point, I thought about leaving the tutorial and find something else, but I finally decided to try to improve and thus could serve to illustrate how to optimize a program.

The first thing we see is that using PutPixelMode0 and SetMode0PixelColor is a high price for each pixel, since each time calculates the address of video memory and the mask bits to put the color you want, so delete it directly. To avoid having to calculate every time we paint the addresses of each line of video memory, we precalculate them and store in an array, as follows:

unsigned int aScreenAddress[FIRE_HEIGHT * 2];


for(nY = 0; nY < FIRE_HEIGHT * 2; nY++)
  aScreenAddress[nY] = 0xC000 + (((FIRE_Y_ORIGIN - nY) / 8) * 80) +
                       (((FIRE_Y_ORIGIN - nY) % 8) * 2048) + FIRE_X_ORIGIN;

We saw in another tutorial that the rand() function of C is very slow (at least the implementation of SDCC), to remove it we precalculate a set of random numbers:

#define RANDOM_VALUES 1024
unsigned char aRandom[RANDOM_VALUES];


for(nRandom = 0; nRandom < RANDOM_VALUES; nRandom++)
  aRandom[nRandom] = FIRE_COLOR_RANGE / 4 + rand() % (FIRE_COLOR_RANGE * 3 / 4);

When painting in Fire01.c we have to convert our range of 60 to our palette of six colors, with two divisions for each pixel ... to avoid this and also to distribute the palette in a non uniform way, let's make an array of 60, which already contains the value to put on a screen byte, to put the colors in our palette, so it is very simple pass a fire pixel to a screen pixel:

#define FIRE_COLOR_RANGE 60 // 6 colors
const unsigned char aFireColors[FIRE_COLOR_RANGE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
    0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x30, 0x30, 0x30, 0x30, 0x30,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};

Another optimization is to modify the array where we calculate the fire, to have a single dimension, as it is much more optimal. Also modified the code to loop the arrays using pointers rather than always directly access the array[index].

With all these changes and making each point of fire cover 4 pixels on the screen (we do when painting) have Fire02.c:

////////////////////////////////////////////////////////////////////////
// Fire02.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//------------------------ Generic functions -------------------------//
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetBorder
////////////////////////////////////////////////////////////////////////
void SetBorder(unsigned char nColor)
{
  __asm
    ld b, 4 (ix)
    ld c, b
    call #0xBC38 ;SCR_SET_BORDER
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//---------------- Specific variables and functions-------------------//
////////////////////////////////////////////////////////////////////////

#define NUM_COLORS 6
const unsigned char FirePalette[NUM_COLORS] = {0, 3, 6, 15, 24, 26};

#define FIRE_WIDTH 32
#define FIRE_HEIGHT 16
unsigned char aFire[FIRE_WIDTH * FIRE_HEIGHT];

#define FIRE_COLOR_RANGE 60 // 6 colors
const unsigned char aFireColors[FIRE_COLOR_RANGE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
    0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x30, 0x30, 0x30, 0x30, 0x30,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};

#define FIRE_X_ORIGIN 40 - (FIRE_WIDTH / 2)
#define FIRE_Y_ORIGIN 199

unsigned int aScreenAddress[FIRE_HEIGHT * 2];

#define RANDOM_VALUES 1024
unsigned char aRandom[RANDOM_VALUES];

////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nX = 0;
  unsigned char nY = 0;
  unsigned char *pFire1 = NULL;
  unsigned char *pFire2 = NULL;
  unsigned int *pScreenAddress = NULL;
  unsigned char *pScreen1 = NULL;
  unsigned char *pScreen2 = NULL;
  unsigned int nRandom = 0;
  unsigned char *pArray = NULL;
  
  SetMode(0);
  SetBorder(0);
  SetPalette(FirePalette, NUM_COLORS);
  
  memset(aFire, 0, sizeof(aFire));
  
  for(nY = 0; nY < FIRE_HEIGHT * 2; nY++)
    aScreenAddress[nY] = 0xC000 + (((FIRE_Y_ORIGIN - nY) / 8) * 80) +
                                (((FIRE_Y_ORIGIN - nY) % 8) * 2048) + FIRE_X_ORIGIN;
  
  for(nRandom = 0; nRandom < RANDOM_VALUES; nRandom++)
    aRandom[nRandom] = FIRE_COLOR_RANGE / 4 + rand() % (FIRE_COLOR_RANGE * 3 / 4);
  
  nTimeLast = GetTime();

  while(1)
  {
    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(1, 1);
      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
    
    //fills the botton line with random values
    pArray = (unsigned char *)aFire;
    
    for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
    {
     *pArray++ = aRandom[nRandom++];

     if(nRandom >= RANDOM_VALUES)
      nRandom = 0;
    }

    //interpolate colors from bottom to top
    pFire1 = aFire + FIRE_WIDTH; //current row
    pFire2 = aFire; //previous row
     
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      *pFire1 = (*pFire1 + *pFire2 + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
      pFire1++;
      pFire2++;
        
      for(nX = 1; nX < FIRE_WIDTH - 1; nX++)
      {
        
        *pFire1 = (*pFire1 + *pFire2 + *(pFire2 - 1) + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
        pFire1++;
        pFire2++;
      }

      *pFire1 = (*pFire1 + *pFire2 + *(pFire2 - 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
      pFire1++;
      pFire2++;
    }
  
    //screen
    pFire1 = aFire + FIRE_WIDTH;
    pScreenAddress = aScreenAddress;
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      pScreen1 = (unsigned char *)*pScreenAddress;
      pScreen2 = (unsigned char *)*(pScreenAddress + 1);
      pScreenAddress += 2;

      for(nX = 0; nX < FIRE_WIDTH; nX++)
      {
        *pScreen1++ = *pScreen2++ = aFireColors[*pFire1++];
      }
    }

  }
}
////////////////////////////////////////////////////////////////////////

If we load Fire02.dsk in the emulator, we will see that it has improved a lot, being a little bigger and reaching about 8 frames per second:

At this point there is not much to optimize, but you can always scrape some more. For example, to avoid division by 4 for each pixel we let precalculated in an array. Furthermore, after several measurement tests I discovered that at least in SDCC to access an array value is faster to do by *(aArrayName + index) than by the classic aArrayName[index]. In addition to the above changes, I have also unified the interpolation loop with the painting, leaving a source much less easy to understand :-(

////////////////////////////////////////////////////////////////////////
// Fire03.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//------------------------ Generic functions -------------------------//
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetBorder
////////////////////////////////////////////////////////////////////////
void SetBorder(unsigned char nColor)
{
  __asm
    ld b, 4 (ix)
    ld c, b
    call #0xBC38 ;SCR_SET_BORDER
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//---------------- Specific variables and functions-------------------//
////////////////////////////////////////////////////////////////////////

#define NUM_COLORS 6
const unsigned char FirePalette[NUM_COLORS] = {0, 3, 6, 15, 24, 26};

#define FIRE_WIDTH 32
#define FIRE_HEIGHT 16
unsigned char aFire[FIRE_WIDTH * FIRE_HEIGHT];

#define FIRE_COLOR_RANGE 60 // 6 colors
const unsigned char aFireColors[FIRE_COLOR_RANGE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
    0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x30, 0x30, 0x30, 0x30, 0x30,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};

#define FIRE_X_ORIGIN 40 - (FIRE_WIDTH / 2)
#define FIRE_Y_ORIGIN 199

unsigned int aScreenAddress[FIRE_HEIGHT * 2];

#define RANDOM_VALUES 1024
unsigned char aRandom[RANDOM_VALUES];

unsigned char aDiv4[256];

////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nX = 0;
  unsigned char nY = 0;
  unsigned char *pFire1 = NULL;
  unsigned char *pFire2 = NULL;
  unsigned int *pScreenAddress = NULL;
  unsigned char *pScreen1 = NULL;
  unsigned char *pScreen2 = NULL;
  unsigned int nRandom = 0;
  unsigned char *pArray = NULL;
  
  SetMode(0);
  SetBorder(0);
  SetPalette(FirePalette, NUM_COLORS);
  
  memset(aFire, 0, sizeof(aFire));
  
  for(nY = 0; nY < FIRE_HEIGHT * 2; nY++)
    aScreenAddress[nY] = 0xC000 + (((FIRE_Y_ORIGIN - nY) / 8) * 80) +
                      (((FIRE_Y_ORIGIN - nY) % 8) * 2048) + FIRE_X_ORIGIN;
  
  for(nRandom = 0; nRandom < RANDOM_VALUES; nRandom++)
    aRandom[nRandom] = FIRE_COLOR_RANGE / 4 + rand() % (FIRE_COLOR_RANGE * 3 / 4);
  
  memset(aDiv4, 0, sizeof(aDiv4));

  for(nRandom = 0; nRandom <= 240; nRandom++)
    aDiv4[nRandom] = nRandom / 4;
  
  nRandom = 0;
  nTimeLast = GetTime();

  while(1)
  {
    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(1, 1);
      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
    
    //fills the botton line with random values
    pArray = (unsigned char *)aFire;
    
    for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
    {
     *pArray++ = *(aRandom + nRandom++);
     
     if(nRandom >= RANDOM_VALUES)
      nRandom = 0;
    }

    //interpolate colors from bottom to top
    pFire1 = aFire + FIRE_WIDTH; //current row
    pFire2 = aFire; //previous row
    
    pScreenAddress = aScreenAddress;

    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      pScreen1 = (unsigned char *)*pScreenAddress;
      pScreen2 = (unsigned char *)*(pScreenAddress + 1);
      pScreenAddress += 2;

      *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
      *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
      pFire2++;
        
      for(nX = 1; nX < FIRE_WIDTH - 1; nX++)
      {
        
        *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 - 1) + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
        *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
        pFire2++;
      }

      *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 - 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
      *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
      pFire2++;
    }
  }
}
////////////////////////////////////////////////////////////////////////

If we load in the emulator Fire03.dsk we see that with all these changes have barely increased to 10 frames per second, but hey, it's something.

To make something a little cooler, I made some changes so that there are 3 different effects: fire, a match moving and flashes:

////////////////////////////////////////////////////////////////////////
// Fire04.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//------------------------ Generic functions -------------------------//
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetBorder
////////////////////////////////////////////////////////////////////////
void SetBorder(unsigned char nColor)
{
  __asm
    ld b, 4 (ix)
    ld c, b
    call #0xBC38 ;SCR_SET_BORDER
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//---------------- Specific variables and functions-------------------//
////////////////////////////////////////////////////////////////////////

#define NUM_COLORS 6
const unsigned char FirePalette[NUM_COLORS] = {0, 3, 6, 15, 24, 26};

#define FIRE_WIDTH 32
#define FIRE_HEIGHT 16
unsigned char aFire[FIRE_WIDTH * FIRE_HEIGHT];

#define FIRE_COLOR_RANGE 60 // 6 colors
const unsigned char aFireColors[FIRE_COLOR_RANGE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
    0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x30, 0x30, 0x30, 0x30, 0x30,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};

#define FIRE_X_ORIGIN 40 - (FIRE_WIDTH / 2)
#define FIRE_Y_ORIGIN 199

unsigned int aScreenAddress[FIRE_HEIGHT * 2];

#define RANDOM_VALUES 1024
unsigned char aRandom[RANDOM_VALUES];

unsigned char aDiv4[256];

enum _eState
{
  State_Fire,
  State_Splash,
  State_Match,
  State_Max
}_eState;

#define STATE_CHANGE_TICKS 100
#define MATCH_WIDTH 10

////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nX = 0;
  unsigned char nY = 0;
  unsigned char *pFire1 = NULL;
  unsigned char *pFire2 = NULL;
  unsigned int *pScreenAddress = NULL;
  unsigned char *pScreen1 = NULL;
  unsigned char *pScreen2 = NULL;
  unsigned int nRandom = 0;
  unsigned char *pArray = NULL;
  enum _eState eState = State_Splash;
  unsigned int nStateTick = 0;
  char nXMatch = 0;
  char nXMatchDir = 1;
  
  SetMode(0);
  SetBorder(0);
  SetPalette(FirePalette, NUM_COLORS);
  
  memset(aFire, 0, sizeof(aFire));
  
  for(nY = 0; nY < FIRE_HEIGHT * 2; nY++)
    aScreenAddress[nY] = 0xC000 + (((FIRE_Y_ORIGIN - nY) / 8) * 80) +
                    (((FIRE_Y_ORIGIN - nY) % 8) * 2048) + FIRE_X_ORIGIN;
  
  for(nRandom = 0; nRandom < RANDOM_VALUES; nRandom++)
    aRandom[nRandom] = FIRE_COLOR_RANGE / 4 + rand() % (FIRE_COLOR_RANGE * 3 / 4);
  
  memset(aDiv4, 0, sizeof(aDiv4));

  for(nRandom = 0; nRandom <= 240; nRandom++)
    aDiv4[nRandom] = nRandom / 4;
  
  nRandom = 0;
  nTimeLast = GetTime();

  while(1)
  {
    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(1, 1);
      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
    
    nStateTick++;
    if(nStateTick >= STATE_CHANGE_TICKS)
    {
      nStateTick = 0;
      eState = (eState + 1) % State_Max;
      memset(aFire, 0, sizeof(aFire));
    }
    
    if(eState == State_Fire)
    {
      //fills the botton line with random values
      pArray = (unsigned char *)aFire + 1;
      
      for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
      {
       *pArray++ = *(aRandom + nRandom++);
       
       if(nRandom >= RANDOM_VALUES)
        nRandom = 0;
      }
    }
    else if(eState == State_Splash)
    {
      if(nStateTick % 10 < 3)
      {
        memset(aFire, 0, FIRE_WIDTH);
      }
      else
      {
        pArray = (unsigned char *)aFire + 1;
        
        for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
        {
         *pArray++ = *(aRandom + nRandom++);
         
         if(nRandom >= RANDOM_VALUES)
          nRandom = 0;
        }
      }
    }
    else if(eState == State_Match)
    {
      memset(aFire, 0, FIRE_WIDTH);
      //memset(aFire + nXMatch, 40 + rand()%20, MATCH_WIDTH);
      
      pArray = (unsigned char *)aFire + nXMatch;
      
      for(nX = 0; nX < MATCH_WIDTH; nX++)
      {
       *pArray++ = 10 + *(aRandom + nRandom++);
       
       if(nRandom >= RANDOM_VALUES)
        nRandom = 0;
      }     
      
      nXMatch = nXMatch + nXMatchDir;
      
      if(nXMatch > (FIRE_WIDTH - MATCH_WIDTH))
      {
        nXMatch--;
        nXMatchDir = -1;
      }
      
      if(nXMatch < 0)
      {
        nXMatch = 0;
        nXMatchDir = 1;
      }     
    }

    //interpolate colors from bottom to top
    pFire1 = aFire + FIRE_WIDTH; //current row
    pFire2 = aFire; //previous row
    
    pScreenAddress = aScreenAddress;

    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      pScreen1 = (unsigned char *)*pScreenAddress;
      pScreen2 = (unsigned char *)*(pScreenAddress + 1);
      pScreenAddress += 2;

      *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
      *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
      pFire2++;
        
      for(nX = 1; nX < FIRE_WIDTH - 1; nX++)
      {
        
        *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 - 1) + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
        *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
        pFire2++;
      }

      *pFire1 = *(aDiv4 + (*pFire1 + *pFire2 + *(pFire2 - 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)));
        
      *pScreen1++ = *pScreen2++ = *(aFireColors + *pFire1++);
      pFire2++;
    }
  }
}
////////////////////////////////////////////////////////////////////////

If we load on an emulator Fire04.dsk we see the following:

I thought the tutorial end here, but I was not very happy with the final result, so I thought another way to increase the speed and size of the fire. So far all we've seen until now could be said that it was estimated in 'real time', what we will do now is to pre-calculate a specific number of frames of the fire and then just paint them on the screen, and we can also increase the size. I will not go into great detail, as there is no mystery, the final source code would look like this:

////////////////////////////////////////////////////////////////////////
// Fire05.c
// Mochilote - www.cpcmania.com
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


////////////////////////////////////////////////////////////////////////
//------------------------ Generic functions -------------------------//
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//GetTime()
////////////////////////////////////////////////////////////////////////
unsigned char char3,char4;

unsigned int GetTime()
{
  unsigned int nTime = 0;

  __asm
    CALL #0xBD0D ;KL TIME PLEASE
    PUSH HL
    POP DE
    LD HL, #_char3
    LD (HL), D
    LD HL, #_char4
    LD (HL), E
  __endasm;

  nTime = (char3 << 8) + char4;

  return nTime;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetColor
////////////////////////////////////////////////////////////////////////
void SetColor(unsigned char nColorIndex, unsigned char nPaletteIndex)
{
  __asm
    ld a, 4 (ix)
    ld b, 5 (ix)
    ld c, b
    call #0xBC32 ;SCR SET INK
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetPalette
////////////////////////////////////////////////////////////////////////
void SetPalette(const unsigned char *pPalette, unsigned char nNumColors)
{
  unsigned char nColor = 0;

  for(nColor = 0; nColor < nNumColors; nColor++)
    SetColor(nColor, pPalette[nColor]);
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetMode
////////////////////////////////////////////////////////////////////////
void SetMode(unsigned char nMode)
{
  __asm
    ld a, 4 (ix)
    call #0xBC0E ;SCR_SET_MODE
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetBorder
////////////////////////////////////////////////////////////////////////
void SetBorder(unsigned char nColor)
{
  __asm
    ld b, 4 (ix)
    ld c, b
    call #0xBC38 ;SCR_SET_BORDER
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//SetCursor
////////////////////////////////////////////////////////////////////////
void SetCursor(unsigned char nColum, unsigned char nLine)
{
  __asm
    ld h, 4 (ix)
    ld l, 5 (ix)
    call #0xBB75 ;TXT SET CURSOR
  __endasm;
}
////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////
//---------------- Specific variables and functions-------------------//
////////////////////////////////////////////////////////////////////////

#define NUM_COLORS 6
const unsigned char FirePalette[NUM_COLORS] = {0, 3, 6, 15, 24, 26};

#define FIRE_WIDTH 32
#define FIRE_HEIGHT 16

#define FIRE_COLOR_RANGE 60 // 6 colors
const unsigned char aFireColors[FIRE_COLOR_RANGE] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
    0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x30, 0x30, 0x30, 0x30, 0x30,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
    0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};

#define FIRE_X_MARGIN 4
#define FIRE_X_ORIGIN 40 - (FIRE_WIDTH - FIRE_X_MARGIN)
#define FIRE_Y_ORIGIN 199

#define NUM_ADDRESS FIRE_HEIGHT * 4
unsigned int aScreenAddress[NUM_ADDRESS];

#define NUM_FRAMES 65
unsigned char aFireFrames[NUM_FRAMES][FIRE_WIDTH * FIRE_HEIGHT];

////////////////////////////////////////////////////////////////////////
//main
////////////////////////////////////////////////////////////////////////
void main()
{
  unsigned int nFPS = 0;
  unsigned int nTimeLast = 0;
  unsigned char nX = 0;
  unsigned char nY = 0;
  unsigned char nFrame = 0;
  unsigned char *pFire1 = NULL;
  unsigned char *pFire2 = NULL;
  unsigned int nPixel = 0;
  unsigned int *pScreenAddress = NULL;
  unsigned char *pScreen1 = NULL;
  unsigned char *pScreen2 = NULL;
  unsigned int nPreframes = 9;
  
  SetMode(0);
  SetBorder(0);
  SetPalette(FirePalette, NUM_COLORS);
  
  memset(aFireFrames, 0, sizeof(aFireFrames));
  
  for(nY = 0; nY < NUM_ADDRESS; nY++)
    aScreenAddress[nY] = 0xC000 + (((FIRE_Y_ORIGIN - nY) / 8) * 80) +
                  (((FIRE_Y_ORIGIN - nY) % 8) * 2048) + FIRE_X_ORIGIN;
  
  SetCursor(1, 1);
  printf("Calculating...");
  
  for(nFrame = 0; nFrame < NUM_FRAMES; nFrame++)
  {
    if(nPreframes > 0)
      nFrame = 0;
 
    SetCursor(8, 5);
    printf("%u / %u  ", nFrame + 1, NUM_FRAMES);
    
    //fills the botton line with random values
    for(nX = 1; nX < (FIRE_WIDTH - 1); nX++)
     aFireFrames[nFrame][nX] = FIRE_COLOR_RANGE / 4 + rand() % (FIRE_COLOR_RANGE * 3 / 4);

    //interpolate colors from bottom to top
    pFire1 = aFireFrames[nFrame] + FIRE_WIDTH; //current row
    pFire2 = aFireFrames[nFrame]; //previous row
     
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      *pFire1 = (*pFire1 + *pFire2 + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
      pFire1++;
      pFire2++;
        
      for(nX = 1; nX < FIRE_WIDTH - 1; nX++)
      {
        
        *pFire1 = (*pFire1 + *pFire2 + *(pFire2 - 1) + *(pFire2 + 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
        pFire1++;
        pFire2++;
      }

      *pFire1 = (*pFire1 + *pFire2 + *(pFire2 - 1) -
                  (FIRE_COLOR_RANGE / FIRE_HEIGHT)) / 4;
        
      pFire1++;
      pFire2++;
    }

    if(nPreframes > 0)
      nPreframes--;
    
    if(nPreframes == 0)
    {
      if(nFrame < NUM_FRAMES - 1)
        memcpy(aFireFrames[nFrame + 1], aFireFrames[nFrame], FIRE_WIDTH * FIRE_HEIGHT);
  
      for(nPixel = 0; nPixel < FIRE_WIDTH * FIRE_HEIGHT; nPixel++)
        aFireFrames[nFrame][nPixel] = aFireColors[aFireFrames[nFrame][nPixel]];
    }
  }
  
  SetMode(0);
  nTimeLast = GetTime();
  nFrame = 0;

  while(1)
  {
    nFPS++;

    if(GetTime() - nTimeLast >= 300)
    {
      SetCursor(1, 1);
      printf("%u  ", nFPS);

      nTimeLast = GetTime();
      nFPS = 0;
    }
    
    //screen
    pFire1 = aFireFrames[nFrame++] + FIRE_WIDTH;

    if(nFrame >= NUM_FRAMES)
      nFrame = 0;

    pScreenAddress = aScreenAddress;
    for(nY = 1; nY < FIRE_HEIGHT; nY++)
    {
      for(nX = 0; nX < 4; nX++)
      {
        pScreen1 = (unsigned char *)*pScreenAddress++;
        memcpy(pScreen1, pFire1, FIRE_WIDTH - FIRE_X_MARGIN);
        memcpy(pScreen1 + FIRE_WIDTH - FIRE_X_MARGIN, pFire1 + FIRE_X_MARGIN,
               FIRE_WIDTH - FIRE_X_MARGIN);
      }

      pFire1 += FIRE_WIDTH;
    }
  }
}
////////////////////////////////////////////////////////////////////////

If you run in an emulator Fire05.dsk we see the following:

Now the fire has a good size and moves at about 32 frames per second, compared to how we started, has greatly improved :-)

 

You could download a zip with all files (source code, bat to compile, binary and dsk's) here: Fire.zip

 

www.CPCMania.com 2013