mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
419 lines
8.5 KiB
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();
|
|
}
|