Compare commits

..

10 Commits

35 changed files with 1513 additions and 277 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
*.bak *.bak
*.lib *.lib
capture/ capture/
*.zip

111
README.md Normal file
View File

@ -0,0 +1,111 @@
# DOS VGA Arena Shooter Game
A Smash TV/Robotron-like game to help me learn a whole bunch of things
about game programming, DOS & PC programming, x86 assembler & C, and VGA graphics!
<center>
<iframe title="DOS Arena Game -- Version 1" width="560" height="315" src="https://makertube.net/videos/embed/027d4ca0-f9ed-4f0d-b47e-15b92a197b87" frameborder="0" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe>
</center>
## Play!
The latest build of the game is under Releases.
[Send any feedback you wish](https://theindustriousrabbit.com/about) -- it's
appreciated! -- but I really don't know when I'll get back to working on this.
## Setup
Builds are done on a host Linux system with Open Watcom 2.0 installed to
a local directory. On my machine, that's `~/Applications/open-watcom-v2`.
Tests and game are run in DOSBox-X.
### Clone and build Open Watcom 2 for DOS cross-compilation
You'll need to build Open Watcom 2 from source to get DOS compilation on Linux.
[Follow the directions on their GitHub wiki](https://github.com/open-watcom/open-watcom-v2/wiki/Build).
You may need DOSBox installed for the Open Watcom 2 build.
### Modify `setup.sh` to use your local Open Watcom install
Changing the path of `export WATCOM` should be enough
### Install Ruby and the RMagick gem
The spritesheet builder uses Ruby and RMagick to produce the x86 assembler
spritesheets from `spritesheet.bmp`.
### Install DOSBox-X
I used [the Flatpak version](https://flathub.org/apps/com.dosbox_x.DOSBox-X) and created a wrapper script in my `PATH`:
```sh
#!/bin/sh
flatpak run com.dosbox_x.DOSBox-X "$@"
```
### Try building the game & tests
`bin/game` builds and runs `game.exe` and `bin/test` builds and runs the
unit tests.
### Make a release
`bin/release` makes `release.zip` which contains the game and the DOS/4GW
wrapper.
## What's in here?
### System access code
Everything in the `system` folder is about directly accessing the PC hardware:
* `keyboard.c` accepts keyboard input using a keyboard interrupt.
* `mouse_io.c` accepts mouse input.
* `pc_stuff.c` does various PC things with interrupts.
* `vga.c` accesses the VGA card and has drawing routines built for
32-bit protected mode code and using the 320x200x256 chained VGA mode (mode 10h).
### Unit tests
I'm using the [CuTest](https://github.com/ennorehling/cutest) library to write basic unit tests for the game.
I wanted to try out C unit testing and see what libraries could work on
retro machines, and CuTest fit the bill. The tests run directly in DOS.
### Compiled sprites
I automated the pipeline to building compiled sprites from a bitmap file.
Originally, I was doing memory copies from that bitmap file, and no matter
how much I optimized them, they were slow slow slow. Having Ruby build the
sprites as a series of `mov` instructions sped up the game immensely.
### Inline assembler
There's some non-trivial inline assembler in `system/vga.c` for drawing
font glyphs. https://github.com/dhepper/font8x8 is the source of the
font. I probably could have gotten the performance fast enough in pure C,
but I really wanted to try some inline assembler.
### Bitmap loading code
Before switching to compiled sprites, I wrote code to load BMP files to
a memory location of your choosing, as well as to extract the palette for
feeding to the VGA card.
### Open Watcom `wmake` Makefiles
The `Makefile`s are not sophisticated, but it took me quite a while to
research Open Watcom's slightly different syntax for `Makefiles`. The GNU
make docs didn't help too much.
## License
MIT License on the code. If you use any code verbatim, or do anything else with this,
let me know!
CuTest has its own license.
Any art that's not the spritesheet is (C) John Bintz, all rights reserved. That
should only be the `chicken.bmp` file that's used as an example for
bitmap loading code.

View File

@ -39,10 +39,15 @@ MovBytePtr = Data.define(:offset, :data)
ASM_TEMPLATE = <<~ASM ASM_TEMPLATE = <<~ASM
<% sprites.each do |sprite| %> <% sprites.each do |sprite| %>
PUBLIC <%= sprite.exported_name %><% end %> PUBLIC <%= sprite.exported_name %><% end %>
PUBLIC _palette
.386 .386
.model flat,c .model flat,c
.DATA
_palette:
<%= palette.flat_map { |color| color.to_asm_struct }.join("\n") %>
.CODE .CODE
<% sprites.each do |sprite| %> <% sprites.each do |sprite| %>
@ -60,11 +65,16 @@ ASM_TEMPLATE = <<~ASM
end end
ASM ASM
C_TEMPLATE = <<~C H_TEMPLATE = <<~H
#ifndef __SPRITES_H__ #ifndef __SPRITES_H__
#define __SPRITES_H__ #define __SPRITES_H__
#include "types.h" #include "types.h"
#include "system/vga.h"
#define PALETTE_COLOR_COUNT (<%= palette.length %>)
extern struct VGAColor palette[<%= palette.length %>];
<% sprites.each do |sprite| %> <% sprites.each do |sprite| %>
extern void <%= sprite.function_name %>(byte *); extern void <%= sprite.function_name %>(byte *);
@ -74,7 +84,7 @@ C_TEMPLATE = <<~C
<% end %> <% end %>
#endif #endif
C H
class AssemblerSprite class AssemblerSprite
def initialize( def initialize(
@ -144,9 +154,33 @@ screen = Screen.new(
sprite_data = [] sprite_data = []
palette = nil
VGAColor = Data.define(:red, :green, :blue) do
def to_asm_struct
[
" BYTE #{red}",
" BYTE #{green}",
" BYTE #{blue}"
]
end
end
data["files"].each do |spritesheet| data["files"].each do |spritesheet|
image = Magick::Image.read(spritesheet["file"]).first image = Magick::Image.read(spritesheet["file"]).first
unless palette
palette = image.colors.times.map do |i|
pixel = Magick::Pixel.from_color(image.colormap(i))
VGAColor.new(
red: pixel.red >> 10,
green: pixel.green >> 10,
blue: pixel.blue >> 10
)
end
end
spritesheet["sprites"].each do |name, details| spritesheet["sprites"].each do |name, details|
sprite_data << SpriteData.new( sprite_data << SpriteData.new(
image:, image:,
@ -179,12 +213,13 @@ sprites = sprite_data.map do |sd|
) )
end end
File.open('sprites.asm', 'w') do |fh| File.open('sprites.asm', 'w') do |fh|
fh.puts ERB.new(ASM_TEMPLATE).result_with_hash(sprites:) fh.puts ERB.new(ASM_TEMPLATE).result_with_hash(sprites:)
end end
File.open('sprites.h', 'w') do |fh| File.open('sprites.h', 'w') do |fh|
fh.puts ERB.new(C_TEMPLATE).result_with_hash(sprites:) fh.puts ERB.new(H_TEMPLATE).result_with_hash(sprites:, palette:)
end end
puts "sprites.{asm,h} written" puts "sprites.{asm,h} written"

3
bin/game Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
wmake clean && wmake && dosbox-x -nolog game.exe

5
bin/release Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
wmake clean && wmake
cp $WATCOM/binw/dos4gw.exe .
zip -u release.zip game.exe dos4gw.exe

4
bin/test Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
wmake clean && wmake test && dosbox-x -c 132x60 -nolog test.exe

View File

@ -1,7 +0,0 @@
wcc386.exe -q bmp_loader.c
wcc386.exe -q mouse_io.c
wcc386.exe -q pc_stuff.c
wcc386.exe -q vga.c
wcc386.exe -q keyboard.c
wcl386.exe -q game.c bmp_load.obj mouse_io.obj pc_stuff.obj vga.obj keyboard.obj

185
combat.c
View File

@ -2,11 +2,12 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "game.h" #include "system/vga.h"
#include "game.h"
#include "powerup.h"
#include "movement.h" #include "movement.h"
#include "const.h" #include "const.h"
#include "system/vga.h"
void setupBullet( void setupBullet(
struct BulletPosition *bullet, struct BulletPosition *bullet,
@ -38,13 +39,33 @@ void setupBullet(
bullet->velocityStep = 0; bullet->velocityStep = 0;
} }
int maybeFireShotCount(
struct BulletPosition rabbitBulletPosition[],
int availableBullets[],
int count
) {
int i, remaining = count;
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
if (rabbitBulletPosition[i].isActive == 0) {
availableBullets[count - remaining] = i;
remaining--;
if (remaining == 0) break;
}
}
return remaining == 0;
}
int attemptToFireRabbitBullet( int attemptToFireRabbitBullet(
struct RabbitPosition *rabbitPosition, struct RabbitPosition *rabbitPosition,
struct RabbitWeaponry *rabbitWeaponry, struct RabbitWeaponry *rabbitWeaponry,
struct BulletPosition rabbitBulletPosition[] struct BulletPosition rabbitBulletPosition[]
) { ) {
int okToFire = 0, i, mouseAngle; int okToFire = 0, i, mouseAngle;
int availableBullets[3]; int availableBullets[4];
float beamOffsetX, beamOffsetY;
if (rabbitWeaponry->cooldown > 0) return 0; if (rabbitWeaponry->cooldown > 0) return 0;
mouseAngle = rabbitPosition->mouseAngle; mouseAngle = rabbitPosition->mouseAngle;
@ -52,15 +73,11 @@ int attemptToFireRabbitBullet(
rabbitWeaponry->cooldown = RABBIT_BULLET_COOLDOWN; rabbitWeaponry->cooldown = RABBIT_BULLET_COOLDOWN;
if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SINGLE_SHOT_GUN) { if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SINGLE_SHOT_GUN) {
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) { if (!maybeFireShotCount(
if (rabbitBulletPosition[i].isActive == 0) { rabbitBulletPosition,
okToFire = 1; availableBullets,
availableBullets[0] = i; 1
break; )) return 0;
}
}
if (!okToFire) return 0;
setupBullet( setupBullet(
&rabbitBulletPosition[availableBullets[0]], &rabbitBulletPosition[availableBullets[0]],
@ -71,14 +88,11 @@ int attemptToFireRabbitBullet(
); );
} else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SPREAD_SHOT_GUN) { } else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_SPREAD_SHOT_GUN) {
// make sure three bullets are available // make sure three bullets are available
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) { if (!maybeFireShotCount(
if (rabbitBulletPosition[i].isActive == 0) { rabbitBulletPosition,
availableBullets[okToFire++] = i; availableBullets,
if (okToFire == 3) break; 3
} )) return 0;
}
if (okToFire < 3) return 0;
// if so, fire away // if so, fire away
for (i = 0; i < 3; ++i) { for (i = 0; i < 3; ++i) {
@ -90,6 +104,27 @@ int attemptToFireRabbitBullet(
RABBIT_BULLET_VELOCITY RABBIT_BULLET_VELOCITY
); );
} }
} else if (rabbitWeaponry->currentWeapon == WEAPON_TYPE_BEAM_SHOT_GUN) {
// make sure two bullets are available
if (!maybeFireShotCount(
rabbitBulletPosition,
availableBullets,
2
)) return 0;
// if so, fire away
for (i = 0; i < 2; ++i) {
beamOffsetX = sin(mouseAngle * DEG2RAD) * i * 2;
beamOffsetY = -cos(mouseAngle * DEG2RAD) * i * 2;
setupBullet(
&rabbitBulletPosition[availableBullets[i]],
rabbitPosition->rabbitPosition[0] + beamOffsetX,
rabbitPosition->rabbitPosition[1] + beamOffsetY,
mouseAngle,
RABBIT_BULLET_VELOCITY
);
}
} }
return 1; return 1;
@ -232,8 +267,8 @@ int rabbitBulletGrid[4][RABBIT_BULLET_LIMIT];
int enemyGrid[4][ENEMY_MAX_COUNT]; int enemyGrid[4][ENEMY_MAX_COUNT];
int enemyBulletGrid[4][ENEMY_BULLET_LIMIT]; int enemyBulletGrid[4][ENEMY_BULLET_LIMIT];
int rabbitBulletGridIndex[4], int rabbitBulletGridIndex[4],
enemyGridIndex[4], enemyGridIndex[4],
enemyBulletGridIndex[4]; enemyBulletGridIndex[4];
int gridPosition[4]; int gridPosition[4];
#define CALCULATE_GRID_POS(x,y) ((x / COLLISION_GRID_SIZE) + (y / COLLISION_GRID_SIZE) * 2) #define CALCULATE_GRID_POS(x,y) ((x / COLLISION_GRID_SIZE) + (y / COLLISION_GRID_SIZE) * 2)
@ -261,6 +296,7 @@ void buildCollisionGrids(
struct PlayerPowerup *playerPowerup struct PlayerPowerup *playerPowerup
) { ) {
int grid, i; int grid, i;
struct CompiledSpriteRender *which;
for (grid = 0; grid < 4; ++grid) { for (grid = 0; grid < 4; ++grid) {
rabbitBulletGridIndex[grid] = 0; rabbitBulletGridIndex[grid] = 0;
@ -278,17 +314,17 @@ void buildCollisionGrids(
switch (playerPowerup->type) { switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup->x; which = &shotgun;
shotgun.y = playerPowerup->y;
determineGridPositionsForSprite(&shotgun);
break; break;
case POWERUP_TYPE_SHIELD_KILLER: case POWERUP_TYPE_BEAM_WEAPON:
shieldKiller.x = playerPowerup->x; which = &beam;
shieldKiller.y = playerPowerup->y;
determineGridPositionsForSprite(&shieldKiller);
break; break;
} }
which->x = playerPowerup->x;
which->y = playerPowerup->y;
determineGridPositionsForSprite(which);
for (grid = 0; grid < 4; ++grid) { for (grid = 0; grid < 4; ++grid) {
powerupGrid[grid] = gridPosition[grid]; powerupGrid[grid] = gridPosition[grid];
} }
@ -374,8 +410,6 @@ int handleEnemyBulletToRabbitCollisions(
return hitCount; return hitCount;
} }
#define KNOCKBACK_DISTANCE (3)
void knockbackEnemy( void knockbackEnemy(
struct EnemyPosition *enemy, struct EnemyPosition *enemy,
int angle int angle
@ -390,7 +424,8 @@ void knockbackEnemy(
void handleRabbitBulletToEnemyCollisions( void handleRabbitBulletToEnemyCollisions(
struct BulletPosition rabbitBulletPosition[], struct BulletPosition rabbitBulletPosition[],
struct EnemyPosition enemyPosition[] struct EnemyPosition enemyPosition[],
struct RabbitWeaponry *rabbitWeaponry
) { ) {
int bulletIdx, enemyIdx, grid; int bulletIdx, enemyIdx, grid;
int resolvedBulletIdx, resolvedEnemyIdx; int resolvedBulletIdx, resolvedEnemyIdx;
@ -413,7 +448,7 @@ void handleRabbitBulletToEnemyCollisions(
populateTargetCollision(&enemy); populateTargetCollision(&enemy);
if (isCollision()) { if (isCollision()) {
enemyPosition[resolvedEnemyIdx].hitPoints--; enemyPosition[resolvedEnemyIdx].hitPoints -= rabbitWeaponry->damage;
if (enemyPosition[resolvedEnemyIdx].hitPoints < 1) { if (enemyPosition[resolvedEnemyIdx].hitPoints < 1) {
enemyPosition[resolvedEnemyIdx].willBeInactive = 1; enemyPosition[resolvedEnemyIdx].willBeInactive = 1;
enemyPosition[resolvedEnemyIdx].wasKilled = 1; enemyPosition[resolvedEnemyIdx].wasKilled = 1;
@ -463,6 +498,8 @@ int handleRabbitToPowerupCollision(
struct RabbitPosition *rabbitPosition, struct RabbitPosition *rabbitPosition,
struct PlayerPowerup *playerPowerup struct PlayerPowerup *playerPowerup
) { ) {
struct CompiledSpriteRender *which;
if (!playerPowerup->isActive) return 0; if (!playerPowerup->isActive) return 0;
rabbit.x = rabbitPosition->rabbitPosition[0]; rabbit.x = rabbitPosition->rabbitPosition[0];
@ -471,16 +508,84 @@ int handleRabbitToPowerupCollision(
switch (playerPowerup->type) { switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup->x; which = &shotgun;
shotgun.y = playerPowerup->y;
populateTargetCollision(&shotgun);
break; break;
case POWERUP_TYPE_SHIELD_KILLER: case POWERUP_TYPE_BEAM_WEAPON:
shieldKiller.x = playerPowerup->x; which = &beam;
shieldKiller.y = playerPowerup->y;
populateTargetCollision(&shieldKiller);
break; break;
} }
which->x = playerPowerup->x;
which->y = playerPowerup->y;
populateTargetCollision(which);
return isCollision(); return isCollision();
} }
int processEnemyKillStates(
struct EnemyPosition enemyPosition[]
) {
int i, currentKillCount = 0;
for (i = 0; i < ENEMY_MAX_COUNT; ++i) {
if (enemyPosition[i].wasKilled) {
enemyPosition[i].wasKilled = 0;
currentKillCount++;
}
}
return currentKillCount;
}
void handleEnemyKills(
struct EnemyPosition enemyPosition[],
struct GlobalGameState *globalGameState,
struct PlayerPowerup *playerPowerup,
struct RabbitWeaponry *rabbitWeaponry
) {
int i, currentKillCount;
int originalDifficulty;
int healthIncrease;
currentKillCount = processEnemyKillStates(enemyPosition);
healthIncrease = globalGameState->healthGainPerKill * currentKillCount;
if (healthIncrease > 0) {
healthIncrease = rand() % healthIncrease + 1;
globalGameState->health += healthIncrease;
if (globalGameState->health > globalGameState->maxHealth) globalGameState->health = globalGameState->maxHealth;
}
globalGameState->kills += currentKillCount;
if (currentKillCount > 0) {
processPowerupCooldown(
playerPowerup,
globalGameState,
rabbitWeaponry,
currentKillCount
);
originalDifficulty = globalGameState->difficulty;
for (i = 0; i < MAX_DIFFICULTY; ++i) {
if (globalGameState->kills > difficultyBands[i]) {
globalGameState->difficulty = i + 1;
}
}
if (originalDifficulty != globalGameState->difficulty) {
globalGameState->coins += (globalGameState->difficulty - originalDifficulty);
}
}
}
void fireCurrentWeaponOnce(struct RabbitWeaponry *rabbitWeaponry) {
if (rabbitWeaponry->currentWeaponRemainingRounds > 0) {
rabbitWeaponry->currentWeaponRemainingRounds--;
if (rabbitWeaponry->currentWeaponRemainingRounds == 0) {
rabbitWeaponry->currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
}
}
}

View File

@ -12,7 +12,8 @@ void advanceRabbitBullets(
void handleRabbitBulletToEnemyCollisions( void handleRabbitBulletToEnemyCollisions(
struct BulletPosition[], struct BulletPosition[],
struct EnemyPosition[] struct EnemyPosition[],
struct RabbitWeaponry*
); );
int handleRabbitToEnemyCollisions( int handleRabbitToEnemyCollisions(
@ -48,3 +49,18 @@ int handleRabbitToPowerupCollision(
struct RabbitPosition *rabbitPosition, struct RabbitPosition *rabbitPosition,
struct PlayerPowerup *playerPowerup struct PlayerPowerup *playerPowerup
); );
void handleEnemyKills(
struct EnemyPosition enemyPosition[],
struct GlobalGameState *globalGameState,
struct PlayerPowerup *playerPowerup,
struct RabbitWeaponry *rabbitWeaponry
);
void fireCurrentWeaponOnce(struct RabbitWeaponry*);
int maybeFireShotCount(
struct BulletPosition rabbitBulletPosition[],
int availableBullets[],
int count
);

159
combat_test.c Normal file
View File

@ -0,0 +1,159 @@
#include "cutest-1.5/CuTest.h"
#include "combat.h"
#include "game.h"
#include "movement.h"
#include "const.h"
#include "powerup.h"
int processEnemyKillStates(struct EnemyPosition[]);
void TestProcessEmenyKillStates(CuTest *tc) {
struct EnemyPosition enemyPosition[ENEMY_MAX_COUNT];
int i, result;
for (i = 0; i < ENEMY_MAX_COUNT; ++i) {
enemyPosition[i].wasKilled = 0;
}
enemyPosition[0].wasKilled = 1;
enemyPosition[ENEMY_MAX_COUNT - 1].wasKilled = 1;
result = processEnemyKillStates(enemyPosition);
CuAssertIntEquals(tc, 2, result);
}
void TestFireCurrentWeaponOnce_NoRoundsRemaining(CuTest *tc) {
struct RabbitWeaponry rabbitWeaponry;
rabbitWeaponry.currentWeaponRemainingRounds = 0;
rabbitWeaponry.currentWeapon = 99;
fireCurrentWeaponOnce(&rabbitWeaponry);
CuAssertIntEquals(tc, 99, rabbitWeaponry.currentWeapon);
CuAssertIntEquals(tc, 0, rabbitWeaponry.currentWeaponRemainingRounds);
}
void TestFireCurrentWeaponOnce_OneRoundRemaining(CuTest *tc) {
struct RabbitWeaponry rabbitWeaponry;
rabbitWeaponry.currentWeaponRemainingRounds = 1;
rabbitWeaponry.currentWeapon = 99;
fireCurrentWeaponOnce(&rabbitWeaponry);
CuAssertIntEquals(tc, WEAPON_TYPE_SINGLE_SHOT_GUN, rabbitWeaponry.currentWeapon);
CuAssertIntEquals(tc, 0, rabbitWeaponry.currentWeaponRemainingRounds);
}
void TestFireCurrentWeaponOnce_TwoRoundsRemaining(CuTest *tc) {
struct RabbitWeaponry rabbitWeaponry;
rabbitWeaponry.currentWeaponRemainingRounds = 2;
rabbitWeaponry.currentWeapon = 99;
fireCurrentWeaponOnce(&rabbitWeaponry);
CuAssertIntEquals(tc, 99, rabbitWeaponry.currentWeapon);
CuAssertIntEquals(tc, 1, rabbitWeaponry.currentWeaponRemainingRounds);
}
void TestMaybeFireShotCount_NotOK(CuTest *tc) {
struct BulletPosition rabbitBulletPosition[RABBIT_BULLET_LIMIT];
int availableBullets[1] = { -1 };
int i, result;
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition[i].isActive = 1;
}
result = maybeFireShotCount(
rabbitBulletPosition,
availableBullets,
1
);
CuAssertIntEquals(tc, 0, result);
CuAssertIntEquals(tc, availableBullets[0], -1);
}
void TestMaybeFireShotCount_OK(CuTest *tc) {
struct BulletPosition rabbitBulletPosition[RABBIT_BULLET_LIMIT];
int availableBullets[1] = { -1 };
int i, result;
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition[i].isActive = 1;
}
rabbitBulletPosition[4].isActive = 0;
result = maybeFireShotCount(
rabbitBulletPosition,
availableBullets,
1
);
CuAssertIntEquals(tc, 1, result);
CuAssertIntEquals(tc, availableBullets[0], 4);
}
void TestMaybeFireShotCount_NotEnoughAvailable(CuTest *tc) {
struct BulletPosition rabbitBulletPosition[RABBIT_BULLET_LIMIT];
int availableBullets[2] = { -1, -1 };
int i, result;
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition[i].isActive = 1;
}
rabbitBulletPosition[4].isActive = 0;
result = maybeFireShotCount(
rabbitBulletPosition,
availableBullets,
2
);
CuAssertIntEquals(tc, 0, result);
CuAssertIntEquals(tc, 4, availableBullets[0]);
CuAssertIntEquals(tc, -1, availableBullets[1]);
}
void TestMaybeFireShotCount_EnoughAvailable(CuTest *tc) {
struct BulletPosition rabbitBulletPosition[RABBIT_BULLET_LIMIT];
int availableBullets[2] = { -1, -1 };
int i, result;
for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition[i].isActive = 1;
}
rabbitBulletPosition[4].isActive = 0;
rabbitBulletPosition[6].isActive = 0;
result = maybeFireShotCount(
rabbitBulletPosition,
availableBullets,
2
);
CuAssertIntEquals(tc, 1, result);
CuAssertIntEquals(tc, 4, availableBullets[0]);
CuAssertIntEquals(tc, 6, availableBullets[1]);
}
CuSuite *CombatGetSuite() {
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestProcessEmenyKillStates);
SUITE_ADD_TEST(suite, TestFireCurrentWeaponOnce_NoRoundsRemaining);
SUITE_ADD_TEST(suite, TestFireCurrentWeaponOnce_OneRoundRemaining);
SUITE_ADD_TEST(suite, TestFireCurrentWeaponOnce_TwoRoundsRemaining);
SUITE_ADD_TEST(suite, TestMaybeFireShotCount_NotOK);
SUITE_ADD_TEST(suite, TestMaybeFireShotCount_OK);
SUITE_ADD_TEST(suite, TestMaybeFireShotCount_NotEnoughAvailable);
SUITE_ADD_TEST(suite, TestMaybeFireShotCount_EnoughAvailable);
return suite;
}

59
const.c Normal file
View File

@ -0,0 +1,59 @@
#include "system/vga.h"
#include "const.h"
int difficultyBands[MAX_DIFFICULTY];
int hitPointRanges[MAX_DIFFICULTY][4];
int damageUpgradeCosts[3] = { 5, 11, 18 };
int healthUpgradeCosts[3] = { 4, 9, 15 };
int healthGainUpgradeCosts[3] = { 6, 13, 21 };
struct SpriteBounds bounds;
// TODO: centralize these outside of game.c
struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
beam;
void buildDifficultyBands() {
int i;
float current = 0;
float increaseBy = BASE_KILLS;
for (i = 0; i < MAX_DIFFICULTY; ++i) {
current += increaseBy;
difficultyBands[i] = (int)current;
increaseBy *= KILLS_NEEDED_FOR_NEXT_LEVEL_MULTIPLIER;
}
}
void buildHitPointRages() {
int currentRange[4] = { 1, 1, 1, 1 };
int i, j, countOfCurrent = 0, tmp;
for (i = 0; i < MAX_DIFFICULTY; ++i) {
for (j = 0; j < 4; ++j) {
hitPointRanges[i][j] = currentRange[j];
}
countOfCurrent++;
if (countOfCurrent == HIT_POINT_DIFFICULTY_INCREASE_DELAY) {
countOfCurrent = 0;
currentRange[3]++;
for (j = 3; j > 0; --j) {
if (currentRange[j] > currentRange[j - 1]) {
tmp = currentRange[j - 1];
currentRange[j - 1] = currentRange[j];
currentRange[j] = tmp;
}
}
}
}
};

57
const.h
View File

@ -1,13 +1,23 @@
#ifndef __CONST_H__
#define __CONST_H__
#include "system/vga.h"
#define TILE_SIZE (20) #define TILE_SIZE (20)
#define ARENA_WIDTH_TILES (10) #define ARENA_WIDTH_TILES (10)
#define ARENA_HEIGHT_TILES (10) #define ARENA_HEIGHT_TILES (10)
#define COLLISION_GRID_SIZE (ARENA_WIDTH_TILES / 2 * TILE_SIZE) #define COLLISION_GRID_SIZE (ARENA_WIDTH_TILES / 2 * TILE_SIZE)
#define ARENA_LEFT_EDGE (TILE_SIZE)
#define ARENA_TOP_EDGE (TILE_SIZE)
#define ARENA_RIGHT_EDGE (ARENA_WIDTH_TILES - 1) * TILE_SIZE
#define ARENA_BOTTOM_EDGE (ARENA_WIDTH_TILES - 1) * TILE_SIZE
#define MOUSE_DISTANCE (32) #define MOUSE_DISTANCE (32)
#define RABBIT_MOTION_DRAG (2) #define RABBIT_MOTION_DRAG (2)
#define RABBIT_MOTION_ACCELERATION (5) #define RABBIT_MOTION_ACCELERATION (5)
#define RABBIT_MOTION_MAX_SPEED (12) #define RABBIT_MOTION_MAX_SPEED (10)
#define RABBIT_MOTION_VELOCITY_DECAY (5) #define RABBIT_MOTION_VELOCITY_DECAY (5)
#define RABBIT_BULLET_LIMIT (9) #define RABBIT_BULLET_LIMIT (9)
@ -28,25 +38,60 @@
#define DEG2RAD (3.14159/180) #define DEG2RAD (3.14159/180)
#define ENEMY_MAX_COUNT (50) #define ENEMY_MAX_COUNT (50)
#define ENEMY_BULLET_LIMIT (ENEMY_MAX_COUNT / 4) #define ENEMY_BULLET_LIMIT (ENEMY_MAX_COUNT / 5)
#define ENEMY_MOVE_SPEED (1) #define ENEMY_MOVE_SPEED (1)
#define ENEMY_MOVE_DELAY (3) #define ENEMY_MOVE_DELAY (3)
#define ENEMY_BULLET_VELOCITY (RABBIT_BULLET_VELOCITY - 2) #define ENEMY_BULLET_VELOCITY (RABBIT_BULLET_VELOCITY - 1)
#define ENEMY_FIRE_MIN_DELAY (45) #define ENEMY_FIRE_MIN_DELAY (50)
#define ENEMY_FIRE_VARIABLE_DELAY (60) #define ENEMY_FIRE_VARIABLE_DELAY (60)
#define ENEMY_BULLET_DAMAGE (3) #define ENEMY_BULLET_DAMAGE (3)
#define ENEMY_COLLISION_DAMAGE (6) #define ENEMY_COLLISION_DAMAGE (6)
#define ENEMY_KILL_HEALTH_GAIN (1)
#define ENEMY_HIT_POINT_DIFFICULTY_INCREASE_EVERY (4) #define ENEMY_HIT_POINT_DIFFICULTY_INCREASE_EVERY (4)
#define BASE_ENEMY_SPAWN_COOLDOWN (30) #define BASE_ENEMY_SPAWN_COOLDOWN (30)
#define DIFFICULTY_SPAWN_COOLDOWN_REDUCTION (2) #define DIFFICULTY_SPAWN_COOLDOWN_REDUCTION (1)
#define MINIMUM_ENEMY_SPAWN_COOLDOWN (3) #define MINIMUM_ENEMY_SPAWN_COOLDOWN (3)
#define VARIABLE_ENEMY_SPAWN_COOLDOWN (10) #define VARIABLE_ENEMY_SPAWN_COOLDOWN (10)
#define POWERUP_RESPAWN_COOLDOWN_PER_LEVEL (10) #define POWERUP_RESPAWN_COOLDOWN_PER_LEVEL (10)
#define SHOTGUN_ROUNDS_PER_LEVEL (25) #define SHOTGUN_ROUNDS_PER_LEVEL (25)
#define MAX_DIFFICULTY (40)
#define BASE_KILLS (20)
#define KILLS_NEEDED_FOR_NEXT_LEVEL_MULTIPLIER (1.06)
#define HIT_POINT_DIFFICULTY_INCREASE_DELAY (4)
#define WEAPON_TYPE_SINGLE_SHOT_GUN (0)
#define WEAPON_TYPE_SPREAD_SHOT_GUN (1)
#define WEAPON_TYPE_BEAM_SHOT_GUN (2)
#define POWERUP_TYPE_SHOTGUN (1)
#define POWERUP_TYPE_BEAM_WEAPON (2)
#define KNOCKBACK_DISTANCE (3)
#define HEALTH_UPGRADE_GAIN (10)
extern int difficultyBands[];
extern int hitPointRanges[MAX_DIFFICULTY][4];
extern int damageUpgradeCosts[];
extern int healthUpgradeCosts[];
extern int healthGainUpgradeCosts[];
extern struct SpriteBounds bounds;
extern struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
beam;
void buildDifficultyBands();
void buildHitPointRages();
#endif

28
const_test.c Normal file
View File

@ -0,0 +1,28 @@
#include "cutest-1.5/CuTest.h"
#include "const.h"
#include <stdio.h>
void TestBuildDifficultyBands(CuTest *tc) {
buildDifficultyBands();
CuAssertIntEquals(tc, 20, difficultyBands[0]);
CuAssertIntEquals(tc, 41, difficultyBands[1]);
CuAssertIntEquals(tc, 3095, difficultyBands[MAX_DIFFICULTY - 1]);
}
void TestBuildHitPointRanges(CuTest *tc) {
buildHitPointRages();
CuAssertIntEquals(tc, 1, hitPointRanges[0][0]);
CuAssertIntEquals(tc, 1, hitPointRanges[1][0]);
CuAssertIntEquals(tc, 2, hitPointRanges[4][0]);
CuAssertIntEquals(tc, 1, hitPointRanges[4][3]);
}
CuSuite *ConstGetSuite() {
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestBuildDifficultyBands);
SUITE_ADD_TEST(suite, TestBuildHitPointRanges);
return suite;
}

View File

@ -1,3 +1,6 @@
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h> #include <assert.h>
#include <setjmp.h> #include <setjmp.h>
#include <stdlib.h> #include <stdlib.h>
@ -11,7 +14,7 @@
* CuStr * CuStr
*-------------------------------------------------------------------------*/ *-------------------------------------------------------------------------*/
char* CuStrAlloc(int size) char* CuStrAlloc(size_t size)
{ {
char* newStr = (char*) malloc( sizeof(char) * (size) ); char* newStr = (char*) malloc( sizeof(char) * (size) );
return newStr; return newStr;
@ -19,7 +22,7 @@ char* CuStrAlloc(int size)
char* CuStrCopy(const char* old) char* CuStrCopy(const char* old)
{ {
int len = strlen(old); size_t len = strlen(old);
char* newStr = CuStrAlloc(len + 1); char* newStr = CuStrAlloc(len + 1);
strcpy(newStr, old); strcpy(newStr, old);
return newStr; return newStr;
@ -54,7 +57,7 @@ void CuStringDelete(CuString *str)
free(str); free(str);
} }
void CuStringResize(CuString* str, int newSize) void CuStringResize(CuString* str, size_t newSize)
{ {
str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize); str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize);
str->size = newSize; str->size = newSize;
@ -62,7 +65,7 @@ void CuStringResize(CuString* str, int newSize)
void CuStringAppend(CuString* str, const char* text) void CuStringAppend(CuString* str, const char* text)
{ {
int length; size_t length;
if (text == NULL) { if (text == NULL) {
text = "NULL"; text = "NULL";
@ -93,9 +96,9 @@ void CuStringAppendFormat(CuString* str, const char* format, ...)
CuStringAppend(str, buf); CuStringAppend(str, buf);
} }
void CuStringInsert(CuString* str, const char* text, int pos) void CuStringInsert(CuString* str, const char* text, size_t pos)
{ {
int length = strlen(text); size_t length = strlen(text);
if (pos > str->length) if (pos > str->length)
pos = str->length; pos = str->length;
if (str->length + length + 1 >= str->size) if (str->length + length + 1 >= str->size)
@ -129,6 +132,7 @@ CuTest* CuTestNew(const char* name, TestFunction function)
void CuTestDelete(CuTest *t) void CuTestDelete(CuTest *t)
{ {
if (!t) return; if (!t) return;
CuStringDelete(t->message);
free(t->name); free(t->name);
free(t); free(t);
} }
@ -153,7 +157,9 @@ static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* str
CuStringInsert(string, buf, 0); CuStringInsert(string, buf, 0);
tc->failed = 1; tc->failed = 1;
tc->message = string->buffer; free(tc->message);
tc->message = CuStringNew();
CuStringAppend(tc->message, string->buffer);
if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
} }
@ -327,7 +333,7 @@ void CuSuiteDetails(CuSuite* testSuite, CuString* details)
{ {
failCount++; failCount++;
CuStringAppendFormat(details, "%d) %s: %s\n", CuStringAppendFormat(details, "%d) %s: %s\n",
failCount, testCase->name, testCase->message); failCount, testCase->name, testCase->message->buffer);
} }
} }
CuStringAppend(details, "\n!!!FAILURES!!!\n"); CuStringAppend(details, "\n!!!FAILURES!!!\n");

View File

@ -3,12 +3,13 @@
#include <setjmp.h> #include <setjmp.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h>
#define CUTEST_VERSION "CuTest 1.5" #define CUTEST_VERSION "CuTest 1.5c"
/* CuString */ /* CuString */
char* CuStrAlloc(int size); char* CuStrAlloc(size_t size);
char* CuStrCopy(const char* old); char* CuStrCopy(const char* old);
#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE))) #define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
@ -19,8 +20,8 @@ char* CuStrCopy(const char* old);
typedef struct typedef struct
{ {
int length; size_t length;
int size; size_t size;
char* buffer; char* buffer;
} CuString; } CuString;
@ -30,8 +31,8 @@ void CuStringRead(CuString* str, const char* path);
void CuStringAppend(CuString* str, const char* text); void CuStringAppend(CuString* str, const char* text);
void CuStringAppendChar(CuString* str, char ch); void CuStringAppendChar(CuString* str, char ch);
void CuStringAppendFormat(CuString* str, const char* format, ...); void CuStringAppendFormat(CuString* str, const char* format, ...);
void CuStringInsert(CuString* str, const char* text, int pos); void CuStringInsert(CuString* str, const char* text, size_t pos);
void CuStringResize(CuString* str, int newSize); void CuStringResize(CuString* str, size_t newSize);
void CuStringDelete(CuString* str); void CuStringDelete(CuString* str);
/* CuTest */ /* CuTest */
@ -46,7 +47,7 @@ struct CuTest
TestFunction function; TestFunction function;
int failed; int failed;
int ran; int ran;
const char* message; CuString *message;
jmp_buf *jumpBuf; jmp_buf *jumpBuf;
}; };
@ -86,8 +87,8 @@ void CuAssertPtrEquals_LineMsg(CuTest* tc,
#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac)) #define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac)) #define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",(p != NULL)) #define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",((p) != NULL))
#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),(p != NULL)) #define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),((p) != NULL))
/* CuSuite */ /* CuSuite */

322
game.c
View File

@ -18,16 +18,7 @@
#include "combat.h" #include "combat.h"
#include "game.h" #include "game.h"
#include "spawn.h" #include "spawn.h"
#include "powerup.h"
// TODO: centralize these outside of game.c
struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
shieldKiller;
struct SpriteBounds bounds;
struct BMPImage spritesheetImage; struct BMPImage spritesheetImage;
struct VGAColor vgaColors[256]; struct VGAColor vgaColors[256];
@ -50,6 +41,19 @@ struct BulletPosition enemyBulletPosition[ENEMY_BULLET_LIMIT];
struct RabbitWeaponry rabbitWeaponry; struct RabbitWeaponry rabbitWeaponry;
struct PlayerPowerup playerPowerup; struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState = {
.spawnCooldown = 0,
.difficulty = 0,
.kills = 0,
.coins = 0,
.health = RABBIT_HEALTH_MAX,
.maxHealth = RABBIT_HEALTH_MAX,
.healthGainPerKill = 1,
.damageUpgradeLevel = 0,
.healthUpgradeLevel = 0,
.healthGainUpgradeLevel = 0
};
void setupRabbitBullets() { void setupRabbitBullets() {
int i; int i;
@ -61,12 +65,13 @@ void setupRabbitBullets() {
rabbitWeaponry.cooldown = 0; rabbitWeaponry.cooldown = 0;
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN; rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
rabbitWeaponry.currentWeaponRemainingRounds = 0; rabbitWeaponry.currentWeaponRemainingRounds = 0;
rabbitWeaponry.damage = 1;
} }
void setupPowerup() { void setupPowerup() {
playerPowerup.x = 100; playerPowerup.x = 100;
playerPowerup.y = 100; playerPowerup.y = 100;
playerPowerup.cooldown = 20 + rand() % 10; playerPowerup.cooldown = determinePowerupCooldownTime(globalGameState.difficulty);
playerPowerup.type = POWERUP_TYPE_SHOTGUN; playerPowerup.type = POWERUP_TYPE_SHOTGUN;
playerPowerup.isActive = 0; playerPowerup.isActive = 0;
} }
@ -89,54 +94,6 @@ void setupEnemies() {
} }
} }
struct GlobalGameState globalGameState = {
.spawnCooldown = 0,
.difficulty = 0
};
int kills = 0;
int health = RABBIT_HEALTH_MAX;
int difficultyBands[10] = { 10, 20, 30, 50, 80, 130, 210, 340, 550, 890 };
void handleEnemyKills() {
int i, hadKill, currentKillCount;
currentKillCount = 0;
for (i = 0; i < ENEMY_MAX_COUNT; ++i) {
if (enemyPosition[i].wasKilled) {
enemyPosition[i].wasKilled = 0;
kills++;
currentKillCount++;
health += ENEMY_KILL_HEALTH_GAIN;
if (health > RABBIT_HEALTH_MAX) health = RABBIT_HEALTH_MAX;
hadKill = 1;
}
}
kills += currentKillCount;
playerPowerup.cooldown -= currentKillCount;
if (playerPowerup.cooldown <= 0) {
playerPowerup.x = TILE_SIZE + rand() % ((ARENA_WIDTH_TILES - 2) * TILE_SIZE);
playerPowerup.y = TILE_SIZE + rand() % ((ARENA_HEIGHT_TILES - 2) * TILE_SIZE);
playerPowerup.isActive = 1;
playerPowerup.cooldown = POWERUP_RESPAWN_COOLDOWN_PER_LEVEL * globalGameState.difficulty +
rand() % (POWERUP_RESPAWN_COOLDOWN_PER_LEVEL * globalGameState.difficulty);
}
if (hadKill) {
for (i = 0; i < 10; ++i) {
if (kills > difficultyBands[i]) {
globalGameState.difficulty = i + 1;
}
}
}
}
void setupEnemySprites() { void setupEnemySprites() {
buildCompiledSprite( buildCompiledSprite(
&sprite_enemy, &sprite_enemy,
@ -197,12 +154,12 @@ void setupPowerupSprites() {
); );
buildCompiledSprite( buildCompiledSprite(
&sprite_shieldKiller, &sprite_beam,
&shieldKiller, &beam,
SPRITE_SHIELDKILLER_WIDTH, SPRITE_BEAM_WIDTH,
SPRITE_SHIELDKILLER_HEIGHT, SPRITE_BEAM_HEIGHT,
SPRITE_SHIELDKILLER_OFFSET_X, SPRITE_BEAM_OFFSET_X,
SPRITE_SHIELDKILLER_OFFSET_Y SPRITE_BEAM_OFFSET_Y
); );
} }
@ -210,7 +167,10 @@ void renderMouse() {
mouse.x = rabbitPosition.mousePosition[0]; mouse.x = rabbitPosition.mousePosition[0];
mouse.y = rabbitPosition.mousePosition[1]; mouse.y = rabbitPosition.mousePosition[1];
drawCompiledSprite(&mouse); drawCompiledSprite(&mouse);
drawPixel(rabbitPosition.mouseDotPosition[0], rabbitPosition.mouseDotPosition[1], 2);
mouse.x = rabbitPosition.mouseDotPosition[0];
mouse.y = rabbitPosition.mouseDotPosition[1];
drawCompiledSprite(&mouse);
} }
void renderRabbit() { void renderRabbit() {
@ -256,20 +216,22 @@ void renderEnemyBullets() {
} }
void renderPowerup() { void renderPowerup() {
struct CompiledSpriteRender *which;
if (!playerPowerup.isActive) return; if (!playerPowerup.isActive) return;
switch (playerPowerup.type) { switch (playerPowerup.type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup.x; which = &shotgun;
shotgun.y = playerPowerup.y;
drawCompiledSprite(&shotgun);
break; break;
case POWERUP_TYPE_SHIELD_KILLER: case POWERUP_TYPE_BEAM_WEAPON:
shieldKiller.x = playerPowerup.x; which = &beam;
shieldKiller.y = playerPowerup.y;
drawCompiledSprite(&shieldKiller);
break; break;
} }
which->x = playerPowerup.x;
which->y = playerPowerup.y;
drawCompiledSprite(which);
} }
void drawOnlyArenaForSprite(struct CompiledSpriteRender *sprite) { void drawOnlyArenaForSprite(struct CompiledSpriteRender *sprite) {
@ -282,11 +244,9 @@ void drawOnlyMouseArena() {
mouse.y = rabbitPosition.oldMousePosition[1]; mouse.y = rabbitPosition.oldMousePosition[1];
drawOnlyArenaForSprite(&mouse); drawOnlyArenaForSprite(&mouse);
bounds.top = rabbitPosition.oldMouseDotPosition[1]; mouse.x = rabbitPosition.oldMouseDotPosition[0];
bounds.bottom = rabbitPosition.oldMouseDotPosition[1]; mouse.y = rabbitPosition.oldMouseDotPosition[1];
bounds.left = rabbitPosition.oldMouseDotPosition[0]; drawOnlyArenaForSprite(&mouse);
bounds.right = rabbitPosition.oldMouseDotPosition[0];
drawOnlyArena(&bounds);
} }
void drawOnlyRabbitArena() { void drawOnlyRabbitArena() {
@ -296,21 +256,23 @@ void drawOnlyRabbitArena() {
} }
void drawOnlyPowerupArena() { void drawOnlyPowerupArena() {
struct CompiledSpriteRender *which;
if (!playerPowerup.isActive) return; if (!playerPowerup.isActive) return;
switch (playerPowerup.type) { switch (playerPowerup.type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
shotgun.x = playerPowerup.x; which = &shotgun;
shotgun.y = playerPowerup.y;
drawOnlyArenaForSprite(&shotgun);
break; break;
case POWERUP_TYPE_SHIELD_KILLER: case POWERUP_TYPE_BEAM_WEAPON:
shieldKiller.x = playerPowerup.x; which = &beam;
shieldKiller.y = playerPowerup.y;
drawOnlyArenaForSprite(&shieldKiller);
break; break;
} }
which->x = playerPowerup.x;
which->y = playerPowerup.y;
drawOnlyArenaForSprite(which);
if (playerPowerup.willBeInactive) { if (playerPowerup.willBeInactive) {
playerPowerup.isActive = 0; playerPowerup.isActive = 0;
} }
@ -368,11 +330,8 @@ int setupGame() {
installKeyboardHandler(); installKeyboardHandler();
initializeDrawBuffer(); initializeDrawBuffer();
fh = fopen("sprtsht.bmp", "rb"); buildDifficultyBands();
if (readBMPIntoNewMemory(fh, &spritesheetImage)) return 1; buildHitPointRages();
fclose(fh);
spritesheetImage.transparentColor = 0;
setupWallSprites(); setupWallSprites();
setupRabbitSprites(); setupRabbitSprites();
@ -384,8 +343,8 @@ int setupGame() {
setupPowerupSprites(); setupPowerupSprites();
setVideoMode(VIDEO_MODE_VGA_256); setVideoMode(VIDEO_MODE_VGA_256);
bmp256ColorPaletteToVGAColorPalette(&spritesheetImage, vgaColors);
setVGAColors(vgaColors, 256); setVGAColors(palette, PALETTE_COLOR_COUNT);
activateMouse(&mouseStatus); activateMouse(&mouseStatus);
@ -411,8 +370,7 @@ void handleMovement() {
&mouseStatus &mouseStatus
); );
calculateTargetAngle( calculateTargetAngle(
&rabbitPosition, &rabbitPosition
&mouseStatus
); );
} }
@ -425,12 +383,9 @@ void handleCombat() {
&rabbitWeaponry, &rabbitWeaponry,
rabbitBulletPosition rabbitBulletPosition
)) { )) {
if (rabbitWeaponry.currentWeaponRemainingRounds > 0) { fireCurrentWeaponOnce(
rabbitWeaponry.currentWeaponRemainingRounds--; &rabbitWeaponry
if (rabbitWeaponry.currentWeaponRemainingRounds == 0) { );
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
}
}
} }
} }
@ -464,7 +419,7 @@ void handleCombat() {
); );
if (didHitRabbit) { if (didHitRabbit) {
health -= ENEMY_COLLISION_DAMAGE * didHitRabbit; globalGameState.health -= ENEMY_COLLISION_DAMAGE * didHitRabbit;
} }
didHitRabbit = handleEnemyBulletToRabbitCollisions( didHitRabbit = handleEnemyBulletToRabbitCollisions(
@ -473,18 +428,19 @@ void handleCombat() {
); );
if (didHitRabbit) { if (didHitRabbit) {
health -= ENEMY_BULLET_DAMAGE * didHitRabbit; globalGameState.health -= ENEMY_BULLET_DAMAGE * didHitRabbit;
} }
handleRabbitBulletToEnemyCollisions( handleRabbitBulletToEnemyCollisions(
rabbitBulletPosition, rabbitBulletPosition,
enemyPosition enemyPosition,
&rabbitWeaponry
); );
if (handleRabbitToPowerupCollision(&rabbitPosition, &playerPowerup)) { if (handleRabbitToPowerupCollision(&rabbitPosition, &playerPowerup)) {
playerPowerup.willBeInactive = 1; playerPowerup.willBeInactive = 1;
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SPREAD_SHOT_GUN; rabbitWeaponry.currentWeapon = playerPowerup.type;
rabbitWeaponry.currentWeaponRemainingRounds = (globalGameState.difficulty + 1) * SHOTGUN_ROUNDS_PER_LEVEL; rabbitWeaponry.currentWeaponRemainingRounds = determineWeaponRounds(globalGameState.difficulty);
} }
} }
@ -513,6 +469,123 @@ double averageSpeedCalc;
clock_t startTime; clock_t startTime;
*/ */
struct PriorStats {
int kills;
int health;
int remainingRounds;
int coins;
int damageUpgradeAvailable;
int healthUpgradeAvailable;
int healthGainUpgradeAvailable;
} priorStats = {
.kills = -1,
.health = -1,
.remainingRounds = -1,
.coins = -1,
.damageUpgradeAvailable = -1,
.healthUpgradeAvailable = -1,
.healthGainUpgradeAvailable = -1
};
struct UpgradesAvailable {
char damage;
char health;
char healthGain;
} upgradesAvailable = {
.damage = 0,
.health = 0,
.healthGain = 0,
};
void calculateUpgradesAvailable() {
if (globalGameState.damageUpgradeLevel < 3) {
upgradesAvailable.damage = globalGameState.coins >= damageUpgradeCosts[globalGameState.damageUpgradeLevel];
} else {
upgradesAvailable.damage = 0;
}
if (globalGameState.healthUpgradeLevel < 3) {
upgradesAvailable.health = globalGameState.coins >= healthUpgradeCosts[globalGameState.healthUpgradeLevel];
} else {
upgradesAvailable.health = 0;
}
if (globalGameState.healthGainUpgradeLevel < 3) {
upgradesAvailable.healthGain = globalGameState.coins >= healthGainUpgradeCosts[globalGameState.healthGainUpgradeLevel];
} else {
upgradesAvailable.healthGain = 0;
}
}
void redrawGameStats() {
char buffer[20];
if (priorStats.kills != globalGameState.kills) {
sprintf(buffer, "Hit: %d", globalGameState.kills);
renderStringToDrawBuffer(buffer, 1, 0, 210, 20);
priorStats.kills = globalGameState.kills;
}
if (priorStats.health != globalGameState.health) {
sprintf(buffer, "Health: %d ", globalGameState.health);
renderStringToDrawBuffer(buffer, 1, 0, 210, 30);
priorStats.health = globalGameState.health;
}
if (priorStats.remainingRounds != rabbitWeaponry.currentWeaponRemainingRounds) {
sprintf(buffer, "Rnds: %d ", rabbitWeaponry.currentWeaponRemainingRounds);
renderStringToDrawBuffer(buffer, 1, 0, 210, 40);
priorStats.remainingRounds = rabbitWeaponry.currentWeaponRemainingRounds;
}
if (priorStats.coins != globalGameState.coins) {
sprintf(buffer, "Coins: %d ", globalGameState.coins);
renderStringToDrawBuffer(buffer, 1, 0, 210, 50);
priorStats.coins = globalGameState.coins;
}
if (priorStats.damageUpgradeAvailable != upgradesAvailable.damage) {
if (upgradesAvailable.damage) {
sprintf(buffer, "[P] Dmg up");
} else {
sprintf(buffer, " ");
}
renderStringToDrawBuffer(buffer, 1, 0, 210, 100);
priorStats.damageUpgradeAvailable = upgradesAvailable.damage;
}
if (priorStats.healthUpgradeAvailable != upgradesAvailable.health) {
if (upgradesAvailable.health) {
sprintf(buffer, "[O] Health up");
} else {
sprintf(buffer, " ");
}
renderStringToDrawBuffer(buffer, 1, 0, 210, 110);
priorStats.healthUpgradeAvailable = upgradesAvailable.health;
}
if (priorStats.healthGainUpgradeAvailable != upgradesAvailable.healthGain) {
if (upgradesAvailable.healthGain) {
sprintf(buffer, "[I] HGain up");
} else {
sprintf(buffer, " ");
}
renderStringToDrawBuffer(buffer, 1, 0, 210, 120);
priorStats.healthGainUpgradeAvailable = upgradesAvailable.healthGain;
}
}
int main(void) { int main(void) {
byte *drawBuffer; byte *drawBuffer;
int keepRunning = 1; int keepRunning = 1;
@ -533,18 +606,37 @@ int main(void) {
enemyPosition, enemyPosition,
&rabbitPosition &rabbitPosition
); );
handleMovement();
handleRedraw();
handleCombat(); handleCombat();
handleEnemyKills(); handleRedraw();
handleEnemyKills(
enemyPosition,
&globalGameState,
&playerPowerup,
&rabbitWeaponry
);
handleMovement();
sprintf(buffer, "Hit: %d", kills); calculateUpgradesAvailable();
renderStringToDrawBuffer(buffer, 1, 0, 210, 20); redrawGameStats();
sprintf(buffer, "Health: %d ", health);
renderStringToDrawBuffer(buffer, 1, 0, 210, 30); if (keyboardKeydownState.KEY_P && upgradesAvailable.damage) {
sprintf(buffer, "Rnds: %d ", rabbitWeaponry.currentWeaponRemainingRounds); globalGameState.coins -= damageUpgradeCosts[globalGameState.damageUpgradeLevel];
renderStringToDrawBuffer(buffer, 1, 0, 210, 40); globalGameState.damageUpgradeLevel++;
rabbitWeaponry.damage++;
}
if (keyboardKeydownState.KEY_O && upgradesAvailable.health) {
globalGameState.coins -= healthUpgradeCosts[globalGameState.healthUpgradeLevel];
globalGameState.healthUpgradeLevel++;
globalGameState.maxHealth += HEALTH_UPGRADE_GAIN;
globalGameState.health = globalGameState.maxHealth;
}
if (keyboardKeydownState.KEY_I && upgradesAvailable.healthGain) {
globalGameState.coins -= healthGainUpgradeCosts[globalGameState.healthGainUpgradeLevel];
globalGameState.healthGainUpgradeLevel++;
globalGameState.healthGainPerKill++;
}
waitStartVbl(); waitStartVbl();
copyDrawBufferToDisplay(); copyDrawBufferToDisplay();

9
game.h
View File

@ -9,6 +9,15 @@ extern struct SpriteBounds bounds;
struct GlobalGameState { struct GlobalGameState {
int difficulty; int difficulty;
int spawnCooldown; int spawnCooldown;
int kills;
int health;
int maxHealth;
int healthGainPerKill;
int coins;
int damageUpgradeLevel;
int healthUpgradeLevel;
int healthGainUpgradeLevel;
}; };
#endif #endif

View File

@ -1,2 +0,0 @@
* Preserve video mode
* Rename files to 8.3

View File

@ -1,4 +1,5 @@
obj = sprites.o game.o bmpload.o arena.o movement.o combat.o spawn.o obj = sprites.o game.o bmpload.o arena.o movement.o combat.o spawn.o const.o powerup.o
test_obj = test.o spawn.o spawn_test.o powerup.o powerup_test.o const.o const_test.o combat.o combat_test.o movement.o movement_test.o
all: system/system.lib game.exe test .SYMBOLIC all: system/system.lib game.exe test .SYMBOLIC
@ -19,10 +20,14 @@ sprites.asm: sprtsht.bmp spritesheet.yml
game.exe: $(obj) system/system.lib game.exe: $(obj) system/system.lib
wcl386 -q -fe=game -bt=dos -l=dos4g $(obj) system/system.lib wcl386 -q -fe=game -bt=dos -l=dos4g $(obj) system/system.lib
test: test.c spawn_test.c spawn.c .SYMBOLIC test: $(test_obj) system/system.lib .SYMBOLIC
wcl386 -fe=test -bt=dos -l=dos4g test.c spawn.c spawn_test.c cutest-1.5/CuTest.c wcl386 -fe=test -bt=dos -l=dos4g $(test_obj) cutest-1.5/CuTest.c system/system.lib
clean: .SYMBOLIC clean: .SYMBOLIC
rm -f game.exe
rm -f test.exe
rm *.o rm *.o
rm *.err
rm system/*.o rm system/*.o
rm system/*.lib rm system/*.lib
rm system/*.err

View File

@ -1,4 +1,5 @@
#include <math.h> #include <math.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "game.h" #include "game.h"
@ -8,6 +9,9 @@
#include "system/mouse_io.h" #include "system/mouse_io.h"
#include "system/pc_stuff.h" #include "system/pc_stuff.h"
#include "system/vga.h"
#define SIGN(x) ((x > 0) - (x < 0))
void captureAndLimitMousePosition( void captureAndLimitMousePosition(
struct RabbitPosition *pos, struct RabbitPosition *pos,
@ -25,33 +29,131 @@ void captureAndLimitMousePosition(
if (pos->mouseDotPosition[1] >= MOUSE_LIMIT_BOTTOM) pos->mouseDotPosition[1] = MOUSE_LIMIT_BOTTOM; if (pos->mouseDotPosition[1] >= MOUSE_LIMIT_BOTTOM) pos->mouseDotPosition[1] = MOUSE_LIMIT_BOTTOM;
} }
void calculateTargetAngle( // now this is ray casting
struct TargetMousePosition {
int x, y;
};
void constrainMousePosition(
struct RabbitPosition *pos, struct RabbitPosition *pos,
struct MouseStatus *mouseStatus struct TargetMousePosition *target
) {
int i, didChange, which;
int currentX, currentY;
float xCrossover, yCrossover;
float maxDeltaX, maxDeltaY, fixHypotenuse, fixDistance;
float currentDeltaX, currentDeltaY;
float adjustedDeltaX, adjustedDeltaY;
int considerX, considerY;
maxDeltaX = sin(pos->mouseAngle * DEG2RAD) * MOUSE_DISTANCE;
maxDeltaY = -cos(pos->mouseAngle * DEG2RAD) * MOUSE_DISTANCE;
currentX = target->x + maxDeltaX;
currentY = target->y + maxDeltaY;
if (maxDeltaX > 0) {
currentDeltaX = ARENA_RIGHT_EDGE - target->x;
} else {
currentDeltaX = target->x - ARENA_LEFT_EDGE;
}
if (maxDeltaY > 0) {
currentDeltaY = ARENA_BOTTOM_EDGE - target->y;
} else {
currentDeltaY = target->y - ARENA_TOP_EDGE;
}
which = -1;
considerY = currentY > ARENA_BOTTOM_EDGE || currentY < ARENA_TOP_EDGE;
considerX = currentX > ARENA_RIGHT_EDGE || currentX < ARENA_LEFT_EDGE;
if (considerX || considerY) {
if (considerX && considerY) {
which = currentDeltaY < currentDeltaX;
} else {
which = considerY;
}
}
switch (which) {
case 0: // x
if (maxDeltaX > 0) {
adjustedDeltaX = ARENA_RIGHT_EDGE - target->x;
} else {
adjustedDeltaX = -(target->x - ARENA_LEFT_EDGE);
}
adjustedDeltaY = (adjustedDeltaX / sin(pos->mouseAngle * DEG2RAD)) * -cos(pos->mouseAngle * DEG2RAD);
if (fabs(adjustedDeltaY) > currentDeltaY) {
adjustedDeltaY = SIGN(adjustedDeltaY) * currentDeltaY;
}
break;
case 1: // y
if (maxDeltaY > 0) {
adjustedDeltaY = ARENA_BOTTOM_EDGE - target->y;
} else {
adjustedDeltaY = -(target->y - ARENA_TOP_EDGE);
}
adjustedDeltaX = (adjustedDeltaY / -cos(pos->mouseAngle * DEG2RAD)) * sin(pos->mouseAngle * DEG2RAD);
if (fabs(adjustedDeltaX) > currentDeltaX) {
adjustedDeltaX = SIGN(adjustedDeltaX) * currentDeltaX;
}
break;
}
if (which != -1) {
currentX = target->x + adjustedDeltaX;
currentY = target->y + adjustedDeltaY;
}
target->x = currentX;
target->y = currentY;
}
void calculateTargetAngle(
struct RabbitPosition *pos
) { ) {
float distanceX, distanceY; float distanceX, distanceY;
float angle; float angle, fixHypotenuse, fixDistance;
struct TargetMousePosition targetMousePosition;
// Position the cursor // Position the cursor
distanceX = pos->mouseDotPosition[0] - pos->rabbitPosition[0]; distanceX = pos->mouseDotPosition[0] - pos->rabbitPosition[0];
distanceY = pos->mouseDotPosition[1] - pos->rabbitPosition[1]; distanceY = pos->mouseDotPosition[1] - pos->rabbitPosition[1];
// edge case -- top left corner
if (
pos->rabbitPosition[0] == ARENA_LEFT_EDGE &&
pos->rabbitPosition[1] == ARENA_TOP_EDGE &&
distanceX == 0 &&
distanceY == 0
) {
angle = 315;
} else {
angle = atan2(distanceY, distanceX) * RAD2DEG + 90; angle = atan2(distanceY, distanceX) * RAD2DEG + 90;
if (angle < 0) angle += 360; if (angle < 0) angle += 360;
}
distanceX = sin(angle * DEG2RAD) * MOUSE_DISTANCE; pos->mouseAngle = angle;
distanceY = -cos(angle * DEG2RAD) * MOUSE_DISTANCE;
pos->oldMousePosition[0] = pos->mousePosition[0]; pos->oldMousePosition[0] = pos->mousePosition[0];
pos->oldMousePosition[1] = pos->mousePosition[1]; pos->oldMousePosition[1] = pos->mousePosition[1];
pos->mousePosition[0] = pos->rabbitPosition[0] + distanceX; targetMousePosition.x = pos->rabbitPosition[0];
pos->mousePosition[1] = pos->rabbitPosition[1] + distanceY; targetMousePosition.y = pos->rabbitPosition[1];
pos->mouseAngle = angle; constrainMousePosition(pos, &targetMousePosition);
pos->mousePosition[0] = targetMousePosition.x;
pos->mousePosition[1] = targetMousePosition.y;
} }
#define SIGN(x) ((x > 0) - (x < 0))
void handleEnemyMovement( void handleEnemyMovement(
struct EnemyPosition enemyPosition[], struct EnemyPosition enemyPosition[],

View File

@ -18,18 +18,13 @@ struct RabbitPosition {
int mouseAngle; int mouseAngle;
}; };
#define WEAPON_TYPE_SINGLE_SHOT_GUN (0)
#define WEAPON_TYPE_SPREAD_SHOT_GUN (1)
struct RabbitWeaponry { struct RabbitWeaponry {
char cooldown; char cooldown;
char currentWeapon; char currentWeapon;
int currentWeaponRemainingRounds; int currentWeaponRemainingRounds;
char damage;
}; };
#define POWERUP_TYPE_SHOTGUN (0)
#define POWERUP_TYPE_SHIELD_KILLER (1)
struct PlayerPowerup { struct PlayerPowerup {
int x, y; int x, y;
int cooldown; int cooldown;
@ -54,23 +49,23 @@ struct BulletPosition {
}; };
struct EnemyPosition { struct EnemyPosition {
char isActive; int hitPoints;
char willBeInactive;
char wasKilled;
char hitPoints;
char hasLeftGate;
char gateExitedFrom;
int enemyPosition[2]; int enemyPosition[2];
int oldEnemyPosition[2]; int oldEnemyPosition[2];
int enemyMoveDelayStep; int enemyMoveDelayStep;
int enemyFireDelayStep; int enemyFireDelayStep;
char isActive;
char willBeInactive;
char wasKilled;
char hasLeftGate;
char gateExitedFrom;
}; };
void calculateTargetAngle(struct RabbitPosition*, struct MouseStatus*); void calculateTargetAngle(struct RabbitPosition*);
void captureAndLimitMousePosition(struct RabbitPosition*, struct MouseStatus*); void captureAndLimitMousePosition(struct RabbitPosition*, struct MouseStatus*);
void handleRabbitMovement(struct RabbitPosition*, struct KeyboardKeydownState*); void handleRabbitMovement(struct RabbitPosition*, struct KeyboardKeydownState*);
void handleEnemyMovement(struct EnemyPosition[], struct RabbitPosition*); void handleEnemyMovement(struct EnemyPosition[], struct RabbitPosition*);

30
movement_test.c Normal file
View File

@ -0,0 +1,30 @@
#include "cutest-1.5/CuTest.h"
#include "movement.h"
#include "const.h"
void TestCalculateTargetAngle_AgainstTopRight(CuTest *tc) {
struct RabbitPosition rabbitPosition;
// mouse against very edge
rabbitPosition.mouseDotPosition[0] = 199;
rabbitPosition.mouseDotPosition[1] = 0;
// rabbit against wall
rabbitPosition.rabbitPosition[0] = 177;
rabbitPosition.rabbitPosition[1] = 25;
calculateTargetAngle(&rabbitPosition);
// mouse should not extend (tile width - mouse width) past player
CuAssertIntEquals(tc, 180, rabbitPosition.mousePosition[0]);
CuAssertIntEquals(tc, 21, rabbitPosition.mousePosition[1]);
CuAssertIntEquals(tc, 41, rabbitPosition.mouseAngle);
}
CuSuite *MovementGetSuite() {
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestCalculateTargetAngle_AgainstTopRight);
return suite;
}

47
powerup.c Normal file
View File

@ -0,0 +1,47 @@
#include <stdio.h>
#include <stdlib.h>
#include "game.h"
#include "movement.h"
#include "powerup.h"
#include "const.h"
// powerup should spawn every other difficulty band
// so the first spawn should happen somewhere 10 + (20 * rand())
int determinePowerupCooldownTime(int difficulty) {
if (difficulty > MAX_DIFFICULTY) exit(1);
return difficulty * 10 + rand() % 20;
}
// if every shot lands, you should run out slightly before the next powerup
// should be available
// so for the first rounds should be difficulty(1) + difficulty(2) % rand() or so
int determineWeaponRounds(int difficulty) {
if (difficulty > MAX_DIFFICULTY) exit(1);
return difficultyBands[difficulty] / 2 +
rand() % (difficultyBands[difficulty] / 8);
}
void processPowerupCooldown(
struct PlayerPowerup *playerPowerup,
struct GlobalGameState *globalGameState,
struct RabbitWeaponry *rabbitWeaponry,
int killCount
) {
if (playerPowerup->isActive) return;
//if (rabbitWeaponry->currentWeapon != WEAPON_TYPE_SINGLE_SHOT_GUN) return;
playerPowerup->cooldown -= killCount;
if (playerPowerup->cooldown <= 0) {
playerPowerup->x = TILE_SIZE + rand() % ((ARENA_WIDTH_TILES - 2) * TILE_SIZE);
playerPowerup->y = TILE_SIZE + rand() % ((ARENA_HEIGHT_TILES - 2) * TILE_SIZE);
playerPowerup->isActive = 1;
playerPowerup->willBeInactive = 0;
playerPowerup->type = rand() % 2 + 1;
playerPowerup->cooldown = determinePowerupCooldownTime(globalGameState->difficulty);
}
}

16
powerup.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef __POWERUP_H__
#define __POWERUP_H__
#include "game.h"
#include "movement.h"
int determinePowerupCooldownTime(int difficulty);
int determineWeaponRounds(int difficulty);
void processPowerupCooldown(
struct PlayerPowerup*,
struct GlobalGameState*,
struct RabbitWeaponry*,
int killCount
);
#endif

125
powerup_test.c Normal file
View File

@ -0,0 +1,125 @@
#include "cutest-1.5/CuTest.h"
#include <stdio.h>
#include <stdlib.h>
#include "game.h"
#include "movement.h"
#include "powerup.h"
#include "const.h"
void TestDeterminePowerupCooldownTime(CuTest *tc) {
srand(1);
CuAssertIntEquals(tc, 18, determinePowerupCooldownTime(0));
CuAssertIntEquals(tc, 28, determinePowerupCooldownTime(1));
CuAssertIntEquals(tc, 33, determinePowerupCooldownTime(2));
}
void TestDetermineWeaponRounds(CuTest *tc) {
srand(1);
CuAssertIntEquals(tc, 10, determineWeaponRounds(0));
CuAssertIntEquals(tc, 23, determineWeaponRounds(1));
CuAssertIntEquals(tc, 36, determineWeaponRounds(2));
}
void TestProcessPowerupCooldown_PowerupActive(CuTest *tc) {
struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState;
struct RabbitWeaponry rabbitWeaponry;
playerPowerup.isActive = 1;
playerPowerup.cooldown = 100;
processPowerupCooldown(
&playerPowerup,
&globalGameState,
&rabbitWeaponry,
10
);
CuAssertIntEquals(tc, 100, playerPowerup.cooldown);
CuAssertIntEquals(tc, 1, playerPowerup.isActive);
}
void TestProcessPowerupCooldown_WeaponActive(CuTest *tc) {
struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState;
struct RabbitWeaponry rabbitWeaponry;
playerPowerup.isActive = 0;
playerPowerup.cooldown = 100;
rabbitWeaponry.currentWeapon = 99;
processPowerupCooldown(
&playerPowerup,
&globalGameState,
&rabbitWeaponry,
10
);
CuAssertIntEquals(tc, 90, playerPowerup.cooldown);
CuAssertIntEquals(tc, 0, playerPowerup.isActive);
}
void TestProcessPowerupCooldown_NotTriggered(CuTest *tc) {
struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState;
struct RabbitWeaponry rabbitWeaponry;
playerPowerup.isActive = 0;
playerPowerup.cooldown = 100;
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
processPowerupCooldown(
&playerPowerup,
&globalGameState,
&rabbitWeaponry,
10
);
CuAssertIntEquals(tc, 90, playerPowerup.cooldown);
CuAssertIntEquals(tc, 0, playerPowerup.isActive);
}
void TestProcessPowerupCooldown_Triggered(CuTest *tc) {
struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState;
struct RabbitWeaponry rabbitWeaponry;
srand(1);
playerPowerup.isActive = 0;
playerPowerup.cooldown = 100;
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
playerPowerup.willBeInactive = 1;
globalGameState.difficulty = 0;
processPowerupCooldown(
&playerPowerup,
&globalGameState,
&rabbitWeaponry,
101
);
CuAssertIntEquals(tc, 1, playerPowerup.isActive);
CuAssertIntEquals(tc, 0, playerPowerup.willBeInactive);
// rand
CuAssertIntEquals(tc, 15, playerPowerup.cooldown);
CuAssertIntEquals(tc, 58, playerPowerup.x);
CuAssertIntEquals(tc, 178, playerPowerup.y);
}
CuSuite *PowerupGetSuite() {
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestDeterminePowerupCooldownTime);
SUITE_ADD_TEST(suite, TestDetermineWeaponRounds);
SUITE_ADD_TEST(suite, TestProcessPowerupCooldown_PowerupActive);
SUITE_ADD_TEST(suite, TestProcessPowerupCooldown_WeaponActive);
SUITE_ADD_TEST(suite, TestProcessPowerupCooldown_NotTriggered);
SUITE_ADD_TEST(suite, TestProcessPowerupCooldown_Triggered);
return suite;
}

15
spawn.c
View File

@ -17,21 +17,8 @@ struct SpawnPointRange spawnPointRanges[4] = {
{ .left = TILE_SIZE - 8, .width = TILE_SIZE, .top = TILE_SIZE * 4, .height = TILE_SIZE * 2 }, { .left = TILE_SIZE - 8, .width = TILE_SIZE, .top = TILE_SIZE * 4, .height = TILE_SIZE * 2 },
}; };
int HIT_POINT_RANGES[10][4] = {
{ 1, 1, 1, 1 },
{ 1, 1, 1, 1 },
{ 1, 1, 1, 2 },
{ 1, 1, 1, 2 },
{ 1, 1, 2, 2 },
{ 1, 2, 2, 2 },
{ 1, 2, 2, 2 },
{ 1, 2, 2, 3 },
{ 1, 2, 2, 3 },
{ 1, 2, 3, 3 }
};
int determineEnemyHitPointCalculation(int difficulty, int roll) { int determineEnemyHitPointCalculation(int difficulty, int roll) {
return HIT_POINT_RANGES[difficulty][roll]; return hitPointRanges[difficulty][roll];
} }
void selectEnemySpawnLocation( void selectEnemySpawnLocation(

View File

@ -39,17 +39,6 @@ void TestSelectEnemySpawnLocation(CuTest *tc) {
CuAssertIntEquals(tc, 91, spawnDetails.spawnY); CuAssertIntEquals(tc, 91, spawnDetails.spawnY);
} }
void TestDetermineEnemyHitPointCalculation(CuTest *tc) {
srand(1);
CuAssertIntEquals(tc, 1, determineEnemyHitPointCalculation(0, 0));
CuAssertIntEquals(tc, 1, determineEnemyHitPointCalculation(0, 3));
CuAssertIntEquals(tc, 1, determineEnemyHitPointCalculation(2, 0));
CuAssertIntEquals(tc, 1, determineEnemyHitPointCalculation(2, 2));
CuAssertIntEquals(tc, 2, determineEnemyHitPointCalculation(2, 3));
}
void TestSpawnEnemy(CuTest *tc) { void TestSpawnEnemy(CuTest *tc) {
srand(1); srand(1);
@ -62,8 +51,8 @@ void TestSpawnEnemy(CuTest *tc) {
spawnEnemy(&spawnDetails, &globalGameState); spawnEnemy(&spawnDetails, &globalGameState);
// randomized values // randomized values
CuAssertIntEquals(tc, 2, enemyPosition.hitPoints); CuAssertIntEquals(tc, 1, enemyPosition.hitPoints);
CuAssertIntEquals(tc, 103, enemyPosition.enemyFireDelayStep); CuAssertIntEquals(tc, 108, enemyPosition.enemyFireDelayStep);
// from spawndetails // from spawndetails
CuAssertIntEquals(tc, 1, enemyPosition.gateExitedFrom); CuAssertIntEquals(tc, 1, enemyPosition.gateExitedFrom);
@ -76,7 +65,6 @@ void TestSpawnEnemy(CuTest *tc) {
CuSuite *SpawnGetSuite() { CuSuite *SpawnGetSuite() {
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, TestSelectEnemySpawnLocation); SUITE_ADD_TEST(suite, TestSelectEnemySpawnLocation);
SUITE_ADD_TEST(suite, TestDetermineEnemyHitPointCalculation);
SUITE_ADD_TEST(suite, TestSpawnEnemy); SUITE_ADD_TEST(suite, TestSpawnEnemy);
return suite; return suite;
} }

View File

@ -7,11 +7,66 @@ PUBLIC sprite_mouse_
PUBLIC sprite_bullet_ PUBLIC sprite_bullet_
PUBLIC sprite_enemy_ PUBLIC sprite_enemy_
PUBLIC sprite_shotgun_ PUBLIC sprite_shotgun_
PUBLIC sprite_shieldKiller_ PUBLIC sprite_beam_
PUBLIC _palette
.386 .386
.model flat,c .model flat,c
.DATA
_palette:
BYTE 63
BYTE 0
BYTE 63
BYTE 0
BYTE 0
BYTE 0
BYTE 15
BYTE 16
BYTE 49
BYTE 16
BYTE 16
BYTE 33
BYTE 16
BYTE 16
BYTE 16
BYTE 63
BYTE 63
BYTE 63
BYTE 38
BYTE 31
BYTE 12
BYTE 63
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
BYTE 0
.CODE .CODE
@ -1336,10 +1391,10 @@ sprite_bullet_:
mov ebp, esp mov ebp, esp
mov BYTE PTR [eax + -321], 5
mov BYTE PTR [eax + -320], 5
mov BYTE PTR [eax + -1], 5
mov BYTE PTR [eax + 0], 5 mov BYTE PTR [eax + 0], 5
mov BYTE PTR [eax + 1], 5
mov BYTE PTR [eax + 320], 5
mov BYTE PTR [eax + 321], 5
pop ebp pop ebp
ret ret
@ -1559,11 +1614,155 @@ sprite_shotgun_:
ret ret
sprite_shieldKiller_: sprite_beam_:
push ebp push ebp
mov ebp, esp mov ebp, esp
mov BYTE PTR [eax + -1926], 7
mov BYTE PTR [eax + -1925], 7
mov BYTE PTR [eax + -1924], 7
mov BYTE PTR [eax + -1923], 7
mov BYTE PTR [eax + -1922], 7
mov BYTE PTR [eax + -1921], 7
mov BYTE PTR [eax + -1920], 7
mov BYTE PTR [eax + -1919], 7
mov BYTE PTR [eax + -1918], 7
mov BYTE PTR [eax + -1917], 7
mov BYTE PTR [eax + -1916], 7
mov BYTE PTR [eax + -1915], 7
mov BYTE PTR [eax + -1606], 7
mov BYTE PTR [eax + -1605], 7
mov BYTE PTR [eax + -1604], 7
mov BYTE PTR [eax + -1603], 7
mov BYTE PTR [eax + -1602], 7
mov BYTE PTR [eax + -1601], 7
mov BYTE PTR [eax + -1600], 7
mov BYTE PTR [eax + -1599], 7
mov BYTE PTR [eax + -1598], 7
mov BYTE PTR [eax + -1597], 7
mov BYTE PTR [eax + -1596], 7
mov BYTE PTR [eax + -1595], 7
mov BYTE PTR [eax + -1286], 7
mov BYTE PTR [eax + -1285], 7
mov BYTE PTR [eax + -1284], 7
mov BYTE PTR [eax + -1283], 7
mov BYTE PTR [eax + -1282], 7
mov BYTE PTR [eax + -1281], 7
mov BYTE PTR [eax + -1280], 7
mov BYTE PTR [eax + -1279], 7
mov BYTE PTR [eax + -1278], 7
mov BYTE PTR [eax + -1277], 7
mov BYTE PTR [eax + -1276], 7
mov BYTE PTR [eax + -1275], 7
mov BYTE PTR [eax + -966], 7
mov BYTE PTR [eax + -965], 7
mov BYTE PTR [eax + -964], 7
mov BYTE PTR [eax + -963], 7
mov BYTE PTR [eax + -962], 7
mov BYTE PTR [eax + -961], 7
mov BYTE PTR [eax + -960], 7
mov BYTE PTR [eax + -959], 7
mov BYTE PTR [eax + -958], 7
mov BYTE PTR [eax + -957], 7
mov BYTE PTR [eax + -956], 7
mov BYTE PTR [eax + -955], 7
mov BYTE PTR [eax + -646], 7
mov BYTE PTR [eax + -645], 7
mov BYTE PTR [eax + -644], 7
mov BYTE PTR [eax + -643], 7
mov BYTE PTR [eax + -642], 7
mov BYTE PTR [eax + -641], 7
mov BYTE PTR [eax + -640], 7
mov BYTE PTR [eax + -639], 7
mov BYTE PTR [eax + -638], 7
mov BYTE PTR [eax + -637], 7
mov BYTE PTR [eax + -636], 7
mov BYTE PTR [eax + -635], 7
mov BYTE PTR [eax + -326], 7
mov BYTE PTR [eax + -325], 7
mov BYTE PTR [eax + -324], 7
mov BYTE PTR [eax + -323], 7
mov BYTE PTR [eax + -322], 7
mov BYTE PTR [eax + -321], 7
mov BYTE PTR [eax + -320], 7
mov BYTE PTR [eax + -319], 7
mov BYTE PTR [eax + -318], 7
mov BYTE PTR [eax + -317], 7
mov BYTE PTR [eax + -316], 7
mov BYTE PTR [eax + -315], 7
mov BYTE PTR [eax + -6], 7
mov BYTE PTR [eax + -5], 7
mov BYTE PTR [eax + -4], 7
mov BYTE PTR [eax + -3], 7
mov BYTE PTR [eax + -2], 7
mov BYTE PTR [eax + -1], 7
mov BYTE PTR [eax + 0], 7
mov BYTE PTR [eax + 1], 7
mov BYTE PTR [eax + 2], 7
mov BYTE PTR [eax + 3], 7
mov BYTE PTR [eax + 4], 7
mov BYTE PTR [eax + 5], 7
mov BYTE PTR [eax + 314], 7
mov BYTE PTR [eax + 315], 7
mov BYTE PTR [eax + 316], 7
mov BYTE PTR [eax + 317], 7
mov BYTE PTR [eax + 318], 7
mov BYTE PTR [eax + 319], 7
mov BYTE PTR [eax + 320], 7
mov BYTE PTR [eax + 321], 7
mov BYTE PTR [eax + 322], 7
mov BYTE PTR [eax + 323], 7
mov BYTE PTR [eax + 324], 7
mov BYTE PTR [eax + 325], 7
mov BYTE PTR [eax + 634], 7
mov BYTE PTR [eax + 635], 7
mov BYTE PTR [eax + 636], 7
mov BYTE PTR [eax + 637], 7
mov BYTE PTR [eax + 638], 7
mov BYTE PTR [eax + 639], 7
mov BYTE PTR [eax + 640], 7
mov BYTE PTR [eax + 641], 7
mov BYTE PTR [eax + 642], 7
mov BYTE PTR [eax + 643], 7
mov BYTE PTR [eax + 644], 7
mov BYTE PTR [eax + 645], 7
mov BYTE PTR [eax + 954], 7
mov BYTE PTR [eax + 955], 7
mov BYTE PTR [eax + 956], 7
mov BYTE PTR [eax + 957], 7
mov BYTE PTR [eax + 958], 7
mov BYTE PTR [eax + 959], 7
mov BYTE PTR [eax + 960], 7
mov BYTE PTR [eax + 961], 7
mov BYTE PTR [eax + 962], 7
mov BYTE PTR [eax + 963], 7
mov BYTE PTR [eax + 964], 7
mov BYTE PTR [eax + 965], 7
mov BYTE PTR [eax + 1274], 7
mov BYTE PTR [eax + 1275], 7
mov BYTE PTR [eax + 1276], 7
mov BYTE PTR [eax + 1277], 7
mov BYTE PTR [eax + 1278], 7
mov BYTE PTR [eax + 1279], 7
mov BYTE PTR [eax + 1280], 7
mov BYTE PTR [eax + 1281], 7
mov BYTE PTR [eax + 1282], 7
mov BYTE PTR [eax + 1283], 7
mov BYTE PTR [eax + 1284], 7
mov BYTE PTR [eax + 1285], 7
mov BYTE PTR [eax + 1594], 7
mov BYTE PTR [eax + 1595], 7
mov BYTE PTR [eax + 1596], 7
mov BYTE PTR [eax + 1597], 7
mov BYTE PTR [eax + 1598], 7
mov BYTE PTR [eax + 1599], 7
mov BYTE PTR [eax + 1600], 7
mov BYTE PTR [eax + 1601], 7
mov BYTE PTR [eax + 1602], 7
mov BYTE PTR [eax + 1603], 7
mov BYTE PTR [eax + 1604], 7
mov BYTE PTR [eax + 1605], 7
pop ebp pop ebp
ret ret

View File

@ -2,6 +2,11 @@
#define __SPRITES_H__ #define __SPRITES_H__
#include "types.h" #include "types.h"
#include "system/vga.h"
#define PALETTE_COLOR_COUNT (17)
extern struct VGAColor palette[17];
extern void sprite_arenaWallTop(byte *); extern void sprite_arenaWallTop(byte *);
@ -61,9 +66,9 @@ extern void sprite_mouse(byte *);
extern void sprite_bullet(byte *); extern void sprite_bullet(byte *);
#define SPRITE_BULLET_WIDTH (4) #define SPRITE_BULLET_WIDTH (2)
#define SPRITE_BULLET_HEIGHT (4) #define SPRITE_BULLET_HEIGHT (2)
#define SPRITE_BULLET_OFFSET_X (1) #define SPRITE_BULLET_OFFSET_X (1)
@ -92,15 +97,15 @@ extern void sprite_shotgun(byte *);
#define SPRITE_SHOTGUN_OFFSET_Y (6) #define SPRITE_SHOTGUN_OFFSET_Y (6)
extern void sprite_shieldKiller(byte *); extern void sprite_beam(byte *);
#define SPRITE_SHIELDKILLER_WIDTH (12) #define SPRITE_BEAM_WIDTH (12)
#define SPRITE_SHIELDKILLER_HEIGHT (12) #define SPRITE_BEAM_HEIGHT (12)
#define SPRITE_SHIELDKILLER_OFFSET_X (6) #define SPRITE_BEAM_OFFSET_X (6)
#define SPRITE_SHIELDKILLER_OFFSET_Y (6) #define SPRITE_BEAM_OFFSET_Y (6)

View File

@ -26,8 +26,8 @@ files:
dimensions: [8, 8] dimensions: [8, 8]
offset: [4, 4] offset: [4, 4]
bullet: bullet:
position: [16, 28] position: [17, 29]
dimensions: [4, 4] dimensions: [2, 2]
offset: [1, 1] offset: [1, 1]
enemy: enemy:
position: [0, 20] position: [0, 20]
@ -37,7 +37,7 @@ files:
position: [32, 20] position: [32, 20]
dimensions: [12, 12] dimensions: [12, 12]
offset: [6, 6] offset: [6, 6]
shieldKiller: beam:
position: [44, 20] position: [44, 20]
dimensions: [12, 12] dimensions: [12, 12]
offset: [6, 6] offset: [6, 6]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -13,6 +13,9 @@ void populateKeyboardKeydownState() {
keyboardKeydownState.KEY_A = keystateBits[3] & 0x40; keyboardKeydownState.KEY_A = keystateBits[3] & 0x40;
keyboardKeydownState.KEY_S = keystateBits[3] & 0x80; keyboardKeydownState.KEY_S = keystateBits[3] & 0x80;
keyboardKeydownState.KEY_D = keystateBits[4] & 0x01; keyboardKeydownState.KEY_D = keystateBits[4] & 0x01;
keyboardKeydownState.KEY_I = keystateBits[2] & 0x80;
keyboardKeydownState.KEY_O = keystateBits[3] & 0x01;
keyboardKeydownState.KEY_P = keystateBits[3] & 0x02;
keyboardKeydownState.KEY_ESC = keystateBits[0] & 0x02; keyboardKeydownState.KEY_ESC = keystateBits[0] & 0x02;
} }

View File

@ -10,6 +10,9 @@ struct KeyboardKeydownState {
byte KEY_A; byte KEY_A;
byte KEY_S; byte KEY_S;
byte KEY_D; byte KEY_D;
byte KEY_I;
byte KEY_O;
byte KEY_P;
byte KEY_ESC; byte KEY_ESC;
}; };

View File

@ -2,6 +2,7 @@
#include "pc_stuff.h" #include "pc_stuff.h"
#include <malloc.h> #include <malloc.h>
#include <conio.h> #include <conio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
@ -128,26 +129,68 @@ void setVGAColors(struct VGAColor colors[], int totalColors) {
#define RENDER_STRING_FAILSAFE (80) #define RENDER_STRING_FAILSAFE (80)
struct CharRowDetails {
char c;
char color;
char backgroundColor;
char writeBackgroundColor;
char* pos;
} charRowDetails;
// Write this in inline assembler for practice!
void plotCharRow();
#pragma aux plotCharRow = \
"mov al, [edi]" \
"mov ah, 8" \
"mov bl, [edi+1]" \
"mov bh, [edi+2]" \
"mov cl, [edi+3]" \
"mov edi, [edi + 4]" \
"nextBit: mov ch, al" \
"and ch,1" \
"cmp ch,1" \
"jne background" \
"mov [edi], bl" \
"jmp maybeKeepGoing" \
"background: cmp cl,0" \
"je maybeKeepGoing" \
"mov [edi], bh" \
"maybeKeepGoing: ror al, 1" \
"inc edi" \
"dec ah" \
"jnz nextBit" \
parm [edi] \
modify [eax ebx ecx edi];
void renderStringToDrawBuffer(char* buffer, int color, int backgroundColor, int x, int y) { void renderStringToDrawBuffer(char* buffer, int color, int backgroundColor, int x, int y) {
char *bufferPos = buffer, currentCharacter, charRow; char *bufferPos = buffer, currentCharacter, charRow;
int i, fontX, fontY, currentX = x; int i, fontX, fontY, currentX = x;
int charX, fontYs[8];
charRowDetails.color = color;
if (backgroundColor == -1) {
charRowDetails.backgroundColor = 0;
}
charRowDetails.writeBackgroundColor = backgroundColor != -1;
for (fontY = 0; fontY < 8; ++fontY) {
fontYs[fontY] = (y + fontY) * VGA_DISPLAY_WIDTH;
}
charX = x;
for (i = 0; i < RENDER_STRING_FAILSAFE; ++i) { for (i = 0; i < RENDER_STRING_FAILSAFE; ++i) {
currentCharacter = *(bufferPos++); currentCharacter = *(bufferPos++);
if (!currentCharacter) break; if (!currentCharacter) break;
for (fontY = 0; fontY < 8; ++fontY) { for (fontY = 0; fontY < 8; ++fontY) {
charRow = font8x8_basic[currentCharacter][fontY]; charRowDetails.c = font8x8_basic[currentCharacter][fontY];
charRowDetails.pos = drawBuffer + fontYs[fontY] + charX;
for (fontX = 0; fontX < 8; ++fontX) { plotCharRow(&charRowDetails);
if (charRow & (1 << fontX)) {
drawBuffer[(y + fontY) * VGA_DISPLAY_WIDTH + (i * 8 + x + fontX)] = color;
} else {
if (backgroundColor > -1) {
drawBuffer[(y + fontY) * VGA_DISPLAY_WIDTH + (i * 8 + x + fontX)] = backgroundColor;
}
}
}
} }
charX += 8;
} }
} }

22
test.c
View File

@ -1,20 +1,38 @@
#include "const.h"
#include "cutest-1.5/CuTest.h" #include "cutest-1.5/CuTest.h"
#include <stdio.h> #include <stdio.h>
CuSuite *SpawnGetSuite(); CuSuite *SpawnGetSuite();
CuSuite *PowerupGetSuite();
CuSuite *CombatGetSuite();
CuSuite *ConstGetSuite();
CuSuite *MovementGetSuite();
void RunAllTests(void) { void beforeAll() {
buildDifficultyBands();
buildHitPointRages();
}
int RunAllTests(void) {
CuString *output = CuStringNew(); CuString *output = CuStringNew();
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
beforeAll();
CuSuiteAddSuite(suite, SpawnGetSuite()); CuSuiteAddSuite(suite, SpawnGetSuite());
CuSuiteAddSuite(suite, PowerupGetSuite());
CuSuiteAddSuite(suite, CombatGetSuite());
CuSuiteAddSuite(suite, ConstGetSuite());
CuSuiteAddSuite(suite, MovementGetSuite());
CuSuiteRun(suite); CuSuiteRun(suite);
CuSuiteSummary(suite, output); CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output); CuSuiteDetails(suite, output);
printf("%s\n", output->buffer); printf("%s\n", output->buffer);
return suite->failCount > 0;
} }
int main(void) { int main(void) {
RunAllTests(); return RunAllTests();
} }