SeanColvin.com
Home
Doom Levels
Ascii Art Game
Tetris
Chess
Standing Desk

A Tetris clone, my first big C project. I wrote this using Borland C/C++ 4.5, back before you'd get a funny look for saying that. I started in 1996, and went back and touched it up a few times over the next 2 or 3 years. Rather than using Borland's supplied graphics interface, I wrote my own, directly accessing VGA video memory.

Looking back at this project now, I'll only say that I've learned a lot since. I'm not going to post a download, due to intellectual property issues, but I wil include two screen shots and a source sample.

Screen Shot
Screen Shot

The code below is from video.c, the file that contains all the graphics code for Tetris. I have chosen two functions, pcx_2_raw(), and blit_raw(). These functions make up the most basic building blocks of the graphics code, reading and writing pixels. While these two handle the majority of the graphics in Tetris, there are other functions, for converting raw image data into (and drawing) a run length encoded format with transparency support, as well as loading image palettes, etc.

I picked pcx_2_raw() for the difficult to find bug, the byte padding on uneven width images. This code worked fine for years, until I decided to up the block size from 8 to 11 pixels, with disastrous results. I especially liked this bug because it wasn't my fault, I was working with incomplete file format specs.

blit_raw() is included, because, I admit it, I had to show off the Assembly language. This version is much faster than the code that the compiler was creating, about 4 times faster if I remember correctly. Ironically, speed in this function is limited much more so by the 33 mhz bus I was working with than by the processor, so even a significant decrease in the number of instructions didn't equate to better performance.

Disclaimer: This was written in the late 1990's. If I were to rewrite it today I would brace and indent differently, name variables more clearly and restructure at least a few things. A good example is the goofy business of trying to pass a pair of integers through a character array (lines 41-44, 101-104). For some reason I can't remember, I thought this was a better solution than creating a structure to properly define the data. Looking at it now, it's ridiculous. Which is to say:

I don't write code like this anymore.

001:   //////////////////////////////////////////////////////////////////////////////
002:  // PCX 2 RAW //
003: ///////////////
004: char * pcx_2_raw(char * pcx_file)
005: {
006:    FILE * in_file;
007:    int width, height, c, filler;
008:    unsigned int raw_offset;
009:    unsigned char run_length, data_byte;
010:    char * raw;				// pointer to the raw data
011: 
012:    // Open file
013:    in_file = fopen(pcx_file, "r+b");	// Read Random Binary
014:    if (!in_file)
015:    {
016:       printf("pcx_2_raw: File Open Error: %s", pcx_file);
017:       exit(1);
018:    }
019: 
020:    // Get format data
021:    fseek(in_file, 8, SEEK_SET);    // Jump ahead 8 bytes from start
022:    fread(&width, 2, 1, in_file);	// read 1 2 byte integer
023:    fread(&height, 2, 1, in_file); 	// read 1 more 2 byte int
024:    fseek(in_file, 116, SEEK_CUR);	// Jump ahead 116 from current position
025: 
026:    height++;
027:    width++;
028: 
029:    // Allocate RAM
030:    raw = (char *)malloc(width * height + 4); // image size + format data
031:    if (!raw)
032:    {
033:       printf("Out of RAM!\n");
034:       exit(1);
035:    }
036: 
037:    // Insert format info
038:    //  width, height  low byte first, then high
039:    //  (call it passing integers through a char array)
040:    //  this really should be a struct
041:    *(raw + 0) = (unsigned char)(width & 0x00FF);   // low byte
042:    *(raw + 1) = (unsigned char)(width >> 8);       // high byte
043:    *(raw + 2) = (unsigned char)(height & 0x00FF);  // low byte
044:    *(raw + 3) = (unsigned char)(height >> 8);      // high byte
045:    raw_offset = 4;
046: 
047:    // Read PCX data
048:    //  How's this for a tough bug, Photostyler (Aldus v2.0) is adding
049:    //     a 255 to the end of each line WHEN THE IMAGE WIDTH IS ODD.
050:    //     Why, I have no idea, except that maybe it has to do with the
051:    //     internal data structure so they can achieve some goofy byte-alignment.
052:    //     Anyway, I can't test a different image software package now,
053:    //     So I have to adjust for this behavior here, and hope that
054:    //     It's part of the PCX file format (doubt it).
055:    while (raw_offset < width * height + 4)   // +4 to skip passed the width & height data.
056:    {
057:       fread(&data_byte, 1, 1, in_file);      // Read 1 byte size of data.
058:       if (data_byte >= (unsigned char)193)   // 193 or higher indicates a run.
059:       {
060:          run_length = (data_byte - (unsigned char)192);
061:          fread(&data_byte, 1, 1, in_file);   // Read the color value of the run.
062: 
063:          // check for P Styler EOL marker on odd width images
064:          filler = 0;
065:          if ((width & 0x0001)                // If image width is odd, 
066:                && raw_offset != 4)           //  and we aren't at the beginning.
067:          {
068:             if ((raw_offset-4) % (width) == 0)    // We're at the end of a line.
069:                if ((data_byte == (unsigned char)255) && run_length == 1)
070:                   filler = 1;                       // This is a filler btye.
071:          }
072: 
073:          if (!filler) {   // Only write output if this isn't a filler.
074:             for (c = 0; c < run_length; c++)
075:             {
076:                *(raw + raw_offset) = data_byte;
077:                raw_offset++;
078:             }
079:          }
080:       }
081:       else                // Was not a run, just output a single pixel.
082:       {
083:          *(raw + raw_offset) = data_byte;
084:          raw_offset++;
085:       }
086:    }  // End While
087: 
088:    // Done reading data, clean up.
089:    fclose(in_file);
090:    return raw;
091: }
092: 
093:   //////////////////////////////////////////////////////////////////////////////
094:  // BLIT RAW //
095: //////////////
096: void blit_raw(int x, int y, char * raw)
097: {
098:    int width, height;
099:    unsigned int screen_offset, raw_off, raw_seg;
100: 
101:    width = (unsigned char)*(raw);
102:    width += (*(raw+1) << 8);
103:    height = (unsigned char)*(raw+2);
104:    height += (*(raw+3) << 8);
105:    raw +=4;
106: 
107:    screen_offset = x;
108:    screen_offset += ((y << 8) + (y << 6)); // y*256 + y*64 = y*320
109: 
110:    /* C version **
111:    for (y = 0; y < height; y++)
112:    {
113:       for (x = 0; x < width; x++)
114:       {
115:          *(screen + screen_offset) = *(raw);
116:          raw++;
117:          screen_offset++;
118:       }
119:       screen_offset += 320-width;
120:    }
121:   */
122: 
123:    // ASM version //
124:    // ex:di = destination
125:    // ds:si = source
126:    // cx = number of rep's
127: 
128:    raw_off = FP_OFF(raw);
129:    raw_seg = FP_SEG(raw);
130: 
131:    asm{
132: 
133:       push ds;               // save segments
134:       push es;
135: 
136:       mov ax, width;         // get data format info
137:       mov bx, height;
138: 
139:       mov dx, 0xA000;        // setup destination reg
140:       mov es, dx;
141:       mov di, screen_offset; // setup destination offset
142: 
143:       mov cx, raw_off;       // setup source seg and off
144:       mov dx, raw_seg;
145:       mov si, cx;
146:       mov ds, dx;
147: 
148:       mov dx, 0x0140;        // decimal 320, screen width
149:       sub dx, ax;            // screen width - sprite width
150: 
151:    blit_raw_line:
152:       mov cx, ax;            // blit width (1 line)
153:       rep movsb;             // write data
154:       add di, dx;            // wrap to next line
155:       dec bx;                // dec height counter
156:       jnz blit_raw_line;     // while (counter)
157: 
158:       pop es;                // restore segments
159:       pop ds;
160:    }
161:    return;
162: }