8bitworkshop/presets/x86/snake.c

419 lines
8.5 KiB
C

/*
Compile:
smlrc -seg16 -nobss snake.c snake.asm
nasm -f bin snake.asm -o snake.com
Run snake.com in DOS, 32-bit Windows or DosBox.
*/
asm("org 0x0");
int main(void);
void start(void)
{
asm("mov ax,cs");
asm("mov ds,ax");
asm("mov es,ax");
asm("mov fs,ax");
main();
asm("mov ax,4ch");
asm("int 21h");
}
// defines the game speed
#define DELAY 150
// video mode, video buffer segment and dimensions
#define VMODE 1 // 40x25 color text mode
#define VSEG 0xB800
#define VWIDTH 40
#define VHEIGHT 25
// foreground and background colors
#define FORE_BLACK 0x00
#define FORE_BLUE 0x01
#define FORE_GREEN 0x02
#define FORE_CYAN 0x03
#define FORE_RED 0x04
#define FORE_MAGENTA 0x05
#define FORE_BROWN 0x06
#define FORE_WHITE 0x07
#define FORE_GRAY 0x08
#define FORE_BRIGHT_BLUE 0x09
#define FORE_BRIGHT_GREEN 0x0A
#define FORE_BRIGHT_CYAN 0x0B
#define FORE_BRIGHT_RED 0x0C
#define FORE_BRIGHT_MAGENTA 0x0D
#define FORE_YELLOW 0x0E
#define FORE_BRIGHT_WHITE 0x0F
#define BACK_BLACK 0x00
#define BACK_BLUE 0x10
#define BACK_GREEN 0x20
#define BACK_CYAN 0x30
#define BACK_RED 0x40
#define BACK_MAGENTA 0x50
#define BACK_BROWN 0x60
#define BACK_WHITE 0x70
// key codes
#define KEY_ESCAPE 0x011B
#define KEY_ENTER 0x1C0D
#define KEY_UP 0x4800
#define KEY_LEFT 0x4B00
#define KEY_RIGHT 0x4D00
#define KEY_DOWN 0x5000
// snake state
#define MIN_LENGTH 6
#define MAX_LENGTH (VWIDTH * VHEIGHT)
unsigned snake[MAX_LENGTH][2]; // coordinates
unsigned length;
unsigned direction;
// target
unsigned target[2]; // coordinates
unsigned score;
int BiosKeyAvailable(void)
{
asm("mov ah, 1\n"
"int 0x16\n"
"setnz al\n"
"cbw");
}
unsigned BiosReadKey(void)
{
asm("mov ah, 0\n"
"int 0x16");
}
void BiosSetGfxMode(unsigned mode)
{
asm("mov ah, 0\n"
"mov al, [bp + 4]\n"
"int 0x10");
}
void pokeb(unsigned seg, unsigned ofs, unsigned char val)
{
asm("push ds\n"
"mov ds, [bp + 4]\n"
"mov bx, [bp + 6]\n"
"mov al, [bp + 8]\n"
"mov [bx], al\n"
"pop ds");
}
void poke(unsigned seg, unsigned ofs, unsigned val)
{
asm("push ds\n"
"mov ds, [bp + 4]\n"
"mov bx, [bp + 6]\n"
"mov ax, [bp + 8]\n"
"mov [bx], ax\n"
"pop ds");
}
unsigned char peekb(unsigned seg, unsigned ofs)
{
asm("push ds\n"
"mov ds, [bp + 4]\n"
"mov bx, [bp + 6]\n"
"mov al, [bx]\n"
"mov ah, 0\n"
"pop ds");
}
unsigned peek(unsigned seg, unsigned ofs)
{
asm("push ds\n"
"mov ds, [bp + 4]\n"
"mov bx, [bp + 6]\n"
"mov ax, [bx]\n"
"pop ds");
}
void BiosGetTicks(unsigned ticks[2])
{
unsigned low, high1, high2;
high2 = peek(0x40, 0x6E);
do
{
high1 = high2;
low = peek(0x40, 0x6C);
high2 = peek(0x40, 0x6E);
// make sure the top 16 bits of the ticks counter haven't changed meanwhile
} while (high1 != high2);
ticks[0] = low;
ticks[1] = high2;
}
void delay(unsigned milliseconds)
{
unsigned tcnt = (milliseconds + 27) / 55; // the 32-bit ticks counter increments every 55 ms
unsigned ticks[2][2];
BiosGetTicks(ticks[0]);
for (;;)
{
BiosGetTicks(ticks[1]);
// ticks[1] -= ticks[0]
if (ticks[1][0] < ticks[0][0])
--ticks[1][1];
ticks[1][0] -= ticks[0][0];
ticks[1][1] -= ticks[0][1];
// ticks[1] >= tcnt ?
if (ticks[1][0] >= tcnt || ticks[1][1])
break;
}
}
void lineh(unsigned x, unsigned y, unsigned w, unsigned chr, unsigned color)
{
unsigned ofs = (y * VWIDTH + x) * 2;
unsigned v = (color << 8) | chr;
while (w--)
{
poke(VSEG, ofs, v);
ofs += 2;
}
}
void linev(unsigned x, unsigned y, unsigned h, unsigned chr, unsigned color)
{
unsigned ofs = (y * VWIDTH + x) * 2;
unsigned v = (color << 8) | chr;
while (h--)
{
poke(VSEG, ofs, v);
ofs += VWIDTH * 2;
}
}
void box(unsigned x, unsigned y, unsigned w, unsigned h, unsigned chr, unsigned color, unsigned solid)
{
if (solid)
{
while (h--)
lineh(x, y++, w, chr, color);
}
else
{
lineh(x, y, w, chr, color);
linev(x + w - 1, y, h, chr, color);
lineh(x, y + h - 1, w, chr, color);
linev(x, y, h, chr, color);
}
}
void text(unsigned x, unsigned y, char* s, unsigned color)
{
unsigned ofs = (y * VWIDTH + x) * 2;
while (*s)
{
unsigned v = (color << 8) | *s++;
poke(VSEG, ofs, v);
ofs += 2;
}
}
void number(unsigned x, unsigned y, unsigned n, unsigned color)
{
char s[6];
int i;
for (i = 4; i >= 0; --i)
{
s[i] = '0' + n % 10;
n /= 10;
}
s[5] = 0;
text(x, y, s, color);
}
#define RAND_MAX 0x7FFF
unsigned rand_next[2] = { 1, 0 };
int rand(void)
{
asm("mov eax, [_rand_next]\n"
"imul eax, eax, 1103515245\n"
"add eax, 12345\n"
"mov [_rand_next], eax\n"
"shr eax, 16\n"
"and ax, 0x7FFF\n");
}
void srand(unsigned seed)
{
rand_next[0] = seed;
}
void play(void);
int main(void)
{
unsigned ticks[2];
BiosSetGfxMode(VMODE);
text((VWIDTH - 20) / 2, VHEIGHT / 2, "Press a key to start", BACK_BLACK | FORE_BRIGHT_WHITE);
while (BiosKeyAvailable()) BiosReadKey(); // clear the keyboard buffer, just in case
BiosReadKey();
BiosGetTicks(ticks);
srand(ticks[0]);
play();
BiosSetGfxMode(3); // 80x25 color text mode
return 0;
}
void newtarget(void)
{
for (;;)
{
unsigned bit = 0, i;
i = rand() % ((VWIDTH - 2) * (VHEIGHT - 2));
target[0] = 1 + i % (VWIDTH - 2);
target[1] = 1 + i / (VWIDTH - 2);
for (i = 0; i < length; ++i)
{
if (target[0] == snake[i][0] &&
target[1] == snake[i][1])
{
bit = 1;
break;
}
}
if (!bit)
break;
}
text(target[0], target[1], "$", BACK_GREEN | FORE_YELLOW);
}
void play(void)
{
unsigned i, key;
// set up the field
box(0, 0, VWIDTH, VHEIGHT, ' ', BACK_GREEN | FORE_YELLOW, 1);
box(0, 0, VWIDTH, VHEIGHT, ' ', BACK_BROWN | FORE_BLACK, 0);
text((VWIDTH - 12) / 2, 0, "Score: ", BACK_BROWN | FORE_BLACK);
score = 0;
number((VWIDTH - 12) / 2 + 7, 0, score, BACK_BROWN | FORE_BLACK);
// set up the snake
for (length = 0; length < MIN_LENGTH; ++length)
{
snake[length][0] = VWIDTH / 2;
snake[length][1] = VHEIGHT - 1 - MIN_LENGTH + length;
text(snake[length][0], snake[length][1], "O", BACK_GREEN | FORE_YELLOW);
}
direction = KEY_UP;
// set up the target
newtarget();
for (;;)
{
key = 0;
if (BiosKeyAvailable())
key = BiosReadKey();
delay(DELAY);
// update the direction
switch (key)
{
case KEY_ESCAPE:
return;
case KEY_LEFT:
if (direction != KEY_RIGHT)
direction = key;
break;
case KEY_RIGHT:
if (direction != KEY_LEFT)
direction = key;
break;
case KEY_UP:
if (direction != KEY_DOWN)
direction = key;
break;
case KEY_DOWN:
if (direction != KEY_UP)
direction = key;
break;
}
// move the snake
text(snake[length - 1][0], snake[length - 1][1], " ", BACK_GREEN | FORE_YELLOW); // erase the tail
for (i = length - 1; i > 0; --i) // update the body position
{
snake[i][0] = snake[i - 1][0];
snake[i][1] = snake[i - 1][1];
}
switch (direction) // update the head position
{
case KEY_LEFT:
--snake[0][0];
break;
case KEY_RIGHT:
++snake[0][0];
break;
case KEY_UP:
--snake[0][1];
break;
case KEY_DOWN:
++snake[0][1];
break;
}
text(snake[0][0], snake[0][1], "O", BACK_GREEN | FORE_YELLOW); // draw the head
// check the head against the boundaries
if (snake[0][0] < 1 || snake[0][0] >= VWIDTH - 1 ||
snake[0][1] < 1 || snake[0][1] >= VHEIGHT - 1)
break;
// check for self biting
{
unsigned bit = 0;
for (i = 1; i < length; ++i)
{
if (snake[0][0] == snake[i][0] &&
snake[0][1] == snake[i][1])
{
bit = 1;
break;
}
}
if (bit)
break;
}
// check for hitting the target
if (snake[0][0] == target[0] &&
snake[0][1] == target[1])
{
// enlarge the snake
snake[length][0] = snake[length - 1][0];
snake[length][1] = snake[length - 1][1];
++length;
++score;
number((VWIDTH - 12) / 2 + 7, 0, score, BACK_BROWN | FORE_BLACK);
// set up the target
newtarget();
}
}
text((VWIDTH - 10) / 2, VHEIGHT / 2, "Game Over!", BACK_BLACK | FORE_BRIGHT_WHITE);
delay(1000);
while (BiosKeyAvailable()) BiosReadKey(); // clear the keyboard buffer
BiosReadKey();
}