Compare commits

..

No commits in common. "e140686bac332c01c8e6c9f6fab98453219ae258" and "4cc709c2a0a0eda016ca344b077a14139ff74b86" have entirely different histories.

35 changed files with 275 additions and 1511 deletions

1
.gitignore vendored
View File

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

111
README.md
View File

@ -1,111 +0,0 @@
# 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,15 +39,10 @@ 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| %>
@ -65,16 +60,11 @@ ASM_TEMPLATE = <<~ASM
end end
ASM ASM
H_TEMPLATE = <<~H C_TEMPLATE = <<~C
#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 *);
@ -84,7 +74,7 @@ H_TEMPLATE = <<~H
<% end %> <% end %>
#endif #endif
H C
class AssemblerSprite class AssemblerSprite
def initialize( def initialize(
@ -154,33 +144,9 @@ 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:,
@ -213,13 +179,12 @@ 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(H_TEMPLATE).result_with_hash(sprites:, palette:) fh.puts ERB.new(C_TEMPLATE).result_with_hash(sprites:)
end end
puts "sprites.{asm,h} written" puts "sprites.{asm,h} written"

View File

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

View File

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

View File

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

7
build.bat Normal file
View File

@ -0,0 +1,7 @@
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,12 +2,11 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "system/vga.h"
#include "game.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,
@ -39,33 +38,13 @@ 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[4]; int availableBullets[3];
float beamOffsetX, beamOffsetY;
if (rabbitWeaponry->cooldown > 0) return 0; if (rabbitWeaponry->cooldown > 0) return 0;
mouseAngle = rabbitPosition->mouseAngle; mouseAngle = rabbitPosition->mouseAngle;
@ -73,11 +52,15 @@ 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) {
if (!maybeFireShotCount( for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition, if (rabbitBulletPosition[i].isActive == 0) {
availableBullets, okToFire = 1;
1 availableBullets[0] = i;
)) return 0; break;
}
}
if (!okToFire) return 0;
setupBullet( setupBullet(
&rabbitBulletPosition[availableBullets[0]], &rabbitBulletPosition[availableBullets[0]],
@ -88,11 +71,14 @@ 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
if (!maybeFireShotCount( for (i = 0; i < RABBIT_BULLET_LIMIT; ++i) {
rabbitBulletPosition, if (rabbitBulletPosition[i].isActive == 0) {
availableBullets, availableBullets[okToFire++] = i;
3 if (okToFire == 3) break;
)) 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) {
@ -104,27 +90,6 @@ 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;
@ -267,8 +232,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)
@ -296,7 +261,6 @@ 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;
@ -314,17 +278,17 @@ void buildCollisionGrids(
switch (playerPowerup->type) { switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
which = &shotgun; shotgun.x = playerPowerup->x;
shotgun.y = playerPowerup->y;
determineGridPositionsForSprite(&shotgun);
break; break;
case POWERUP_TYPE_BEAM_WEAPON: case POWERUP_TYPE_SHIELD_KILLER:
which = &beam; shieldKiller.x = playerPowerup->x;
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];
} }
@ -410,6 +374,8 @@ int handleEnemyBulletToRabbitCollisions(
return hitCount; return hitCount;
} }
#define KNOCKBACK_DISTANCE (3)
void knockbackEnemy( void knockbackEnemy(
struct EnemyPosition *enemy, struct EnemyPosition *enemy,
int angle int angle
@ -424,8 +390,7 @@ 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;
@ -448,7 +413,7 @@ void handleRabbitBulletToEnemyCollisions(
populateTargetCollision(&enemy); populateTargetCollision(&enemy);
if (isCollision()) { if (isCollision()) {
enemyPosition[resolvedEnemyIdx].hitPoints -= rabbitWeaponry->damage; enemyPosition[resolvedEnemyIdx].hitPoints--;
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;
@ -498,8 +463,6 @@ 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];
@ -508,84 +471,16 @@ int handleRabbitToPowerupCollision(
switch (playerPowerup->type) { switch (playerPowerup->type) {
case POWERUP_TYPE_SHOTGUN: case POWERUP_TYPE_SHOTGUN:
which = &shotgun; shotgun.x = playerPowerup->x;
shotgun.y = playerPowerup->y;
populateTargetCollision(&shotgun);
break; break;
case POWERUP_TYPE_BEAM_WEAPON: case POWERUP_TYPE_SHIELD_KILLER:
which = &beam; shieldKiller.x = playerPowerup->x;
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,8 +12,7 @@ void advanceRabbitBullets(
void handleRabbitBulletToEnemyCollisions( void handleRabbitBulletToEnemyCollisions(
struct BulletPosition[], struct BulletPosition[],
struct EnemyPosition[], struct EnemyPosition[]
struct RabbitWeaponry*
); );
int handleRabbitToEnemyCollisions( int handleRabbitToEnemyCollisions(
@ -49,18 +48,3 @@ 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
);

View File

@ -1,159 +0,0 @@
#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
View File

@ -1,59 +0,0 @@
#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,23 +1,13 @@
#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 (10) #define RABBIT_MOTION_MAX_SPEED (12)
#define RABBIT_MOTION_VELOCITY_DECAY (5) #define RABBIT_MOTION_VELOCITY_DECAY (5)
#define RABBIT_BULLET_LIMIT (9) #define RABBIT_BULLET_LIMIT (9)
@ -38,60 +28,25 @@
#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 / 5) #define ENEMY_BULLET_LIMIT (ENEMY_MAX_COUNT / 4)
#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 - 1) #define ENEMY_BULLET_VELOCITY (RABBIT_BULLET_VELOCITY - 2)
#define ENEMY_FIRE_MIN_DELAY (50) #define ENEMY_FIRE_MIN_DELAY (45)
#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 (1) #define DIFFICULTY_SPAWN_COOLDOWN_REDUCTION (2)
#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

View File

@ -1,28 +0,0 @@
#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,6 +1,3 @@
#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>
@ -14,7 +11,7 @@
* CuStr * CuStr
*-------------------------------------------------------------------------*/ *-------------------------------------------------------------------------*/
char* CuStrAlloc(size_t size) char* CuStrAlloc(int size)
{ {
char* newStr = (char*) malloc( sizeof(char) * (size) ); char* newStr = (char*) malloc( sizeof(char) * (size) );
return newStr; return newStr;
@ -22,7 +19,7 @@ char* CuStrAlloc(size_t size)
char* CuStrCopy(const char* old) char* CuStrCopy(const char* old)
{ {
size_t len = strlen(old); int len = strlen(old);
char* newStr = CuStrAlloc(len + 1); char* newStr = CuStrAlloc(len + 1);
strcpy(newStr, old); strcpy(newStr, old);
return newStr; return newStr;
@ -57,7 +54,7 @@ void CuStringDelete(CuString *str)
free(str); free(str);
} }
void CuStringResize(CuString* str, size_t newSize) void CuStringResize(CuString* str, int 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;
@ -65,7 +62,7 @@ void CuStringResize(CuString* str, size_t newSize)
void CuStringAppend(CuString* str, const char* text) void CuStringAppend(CuString* str, const char* text)
{ {
size_t length; int length;
if (text == NULL) { if (text == NULL) {
text = "NULL"; text = "NULL";
@ -96,9 +93,9 @@ void CuStringAppendFormat(CuString* str, const char* format, ...)
CuStringAppend(str, buf); CuStringAppend(str, buf);
} }
void CuStringInsert(CuString* str, const char* text, size_t pos) void CuStringInsert(CuString* str, const char* text, int pos)
{ {
size_t length = strlen(text); int 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)
@ -132,7 +129,6 @@ 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);
} }
@ -157,9 +153,7 @@ 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;
free(tc->message); tc->message = string->buffer;
tc->message = CuStringNew();
CuStringAppend(tc->message, string->buffer);
if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0); if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
} }
@ -333,7 +327,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->buffer); failCount, testCase->name, testCase->message);
} }
} }
CuStringAppend(details, "\n!!!FAILURES!!!\n"); CuStringAppend(details, "\n!!!FAILURES!!!\n");

View File

@ -3,13 +3,12 @@
#include <setjmp.h> #include <setjmp.h>
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h>
#define CUTEST_VERSION "CuTest 1.5c" #define CUTEST_VERSION "CuTest 1.5"
/* CuString */ /* CuString */
char* CuStrAlloc(size_t size); char* CuStrAlloc(int 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)))
@ -20,8 +19,8 @@ char* CuStrCopy(const char* old);
typedef struct typedef struct
{ {
size_t length; int length;
size_t size; int size;
char* buffer; char* buffer;
} CuString; } CuString;
@ -31,8 +30,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, size_t pos); void CuStringInsert(CuString* str, const char* text, int pos);
void CuStringResize(CuString* str, size_t newSize); void CuStringResize(CuString* str, int newSize);
void CuStringDelete(CuString* str); void CuStringDelete(CuString* str);
/* CuTest */ /* CuTest */
@ -47,7 +46,7 @@ struct CuTest
TestFunction function; TestFunction function;
int failed; int failed;
int ran; int ran;
CuString *message; const char* message;
jmp_buf *jumpBuf; jmp_buf *jumpBuf;
}; };
@ -87,8 +86,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,7 +18,16 @@
#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];
@ -41,19 +50,6 @@ 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;
@ -65,13 +61,12 @@ 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 = determinePowerupCooldownTime(globalGameState.difficulty); playerPowerup.cooldown = 20 + rand() % 10;
playerPowerup.type = POWERUP_TYPE_SHOTGUN; playerPowerup.type = POWERUP_TYPE_SHOTGUN;
playerPowerup.isActive = 0; playerPowerup.isActive = 0;
} }
@ -94,6 +89,54 @@ 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,
@ -154,12 +197,12 @@ void setupPowerupSprites() {
); );
buildCompiledSprite( buildCompiledSprite(
&sprite_beam, &sprite_shieldKiller,
&beam, &shieldKiller,
SPRITE_BEAM_WIDTH, SPRITE_SHIELDKILLER_WIDTH,
SPRITE_BEAM_HEIGHT, SPRITE_SHIELDKILLER_HEIGHT,
SPRITE_BEAM_OFFSET_X, SPRITE_SHIELDKILLER_OFFSET_X,
SPRITE_BEAM_OFFSET_Y SPRITE_SHIELDKILLER_OFFSET_Y
); );
} }
@ -167,10 +210,7 @@ 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() {
@ -216,22 +256,20 @@ 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:
which = &shotgun; shotgun.x = playerPowerup.x;
shotgun.y = playerPowerup.y;
drawCompiledSprite(&shotgun);
break; break;
case POWERUP_TYPE_BEAM_WEAPON: case POWERUP_TYPE_SHIELD_KILLER:
which = &beam; shieldKiller.x = playerPowerup.x;
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) {
@ -244,9 +282,11 @@ void drawOnlyMouseArena() {
mouse.y = rabbitPosition.oldMousePosition[1]; mouse.y = rabbitPosition.oldMousePosition[1];
drawOnlyArenaForSprite(&mouse); drawOnlyArenaForSprite(&mouse);
mouse.x = rabbitPosition.oldMouseDotPosition[0]; bounds.top = rabbitPosition.oldMouseDotPosition[1];
mouse.y = rabbitPosition.oldMouseDotPosition[1]; bounds.bottom = rabbitPosition.oldMouseDotPosition[1];
drawOnlyArenaForSprite(&mouse); bounds.left = rabbitPosition.oldMouseDotPosition[0];
bounds.right = rabbitPosition.oldMouseDotPosition[0];
drawOnlyArena(&bounds);
} }
void drawOnlyRabbitArena() { void drawOnlyRabbitArena() {
@ -256,23 +296,21 @@ 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:
which = &shotgun; shotgun.x = playerPowerup.x;
shotgun.y = playerPowerup.y;
drawOnlyArenaForSprite(&shotgun);
break; break;
case POWERUP_TYPE_BEAM_WEAPON: case POWERUP_TYPE_SHIELD_KILLER:
which = &beam; shieldKiller.x = playerPowerup.x;
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;
} }
@ -330,8 +368,11 @@ int setupGame() {
installKeyboardHandler(); installKeyboardHandler();
initializeDrawBuffer(); initializeDrawBuffer();
buildDifficultyBands(); fh = fopen("sprtsht.bmp", "rb");
buildHitPointRages(); if (readBMPIntoNewMemory(fh, &spritesheetImage)) return 1;
fclose(fh);
spritesheetImage.transparentColor = 0;
setupWallSprites(); setupWallSprites();
setupRabbitSprites(); setupRabbitSprites();
@ -343,8 +384,8 @@ int setupGame() {
setupPowerupSprites(); setupPowerupSprites();
setVideoMode(VIDEO_MODE_VGA_256); setVideoMode(VIDEO_MODE_VGA_256);
bmp256ColorPaletteToVGAColorPalette(&spritesheetImage, vgaColors);
setVGAColors(palette, PALETTE_COLOR_COUNT); setVGAColors(vgaColors, 256);
activateMouse(&mouseStatus); activateMouse(&mouseStatus);
@ -370,7 +411,8 @@ void handleMovement() {
&mouseStatus &mouseStatus
); );
calculateTargetAngle( calculateTargetAngle(
&rabbitPosition &rabbitPosition,
&mouseStatus
); );
} }
@ -383,9 +425,12 @@ void handleCombat() {
&rabbitWeaponry, &rabbitWeaponry,
rabbitBulletPosition rabbitBulletPosition
)) { )) {
fireCurrentWeaponOnce( if (rabbitWeaponry.currentWeaponRemainingRounds > 0) {
&rabbitWeaponry rabbitWeaponry.currentWeaponRemainingRounds--;
); if (rabbitWeaponry.currentWeaponRemainingRounds == 0) {
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
}
}
} }
} }
@ -419,7 +464,7 @@ void handleCombat() {
); );
if (didHitRabbit) { if (didHitRabbit) {
globalGameState.health -= ENEMY_COLLISION_DAMAGE * didHitRabbit; health -= ENEMY_COLLISION_DAMAGE * didHitRabbit;
} }
didHitRabbit = handleEnemyBulletToRabbitCollisions( didHitRabbit = handleEnemyBulletToRabbitCollisions(
@ -428,19 +473,18 @@ void handleCombat() {
); );
if (didHitRabbit) { if (didHitRabbit) {
globalGameState.health -= ENEMY_BULLET_DAMAGE * didHitRabbit; 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 = playerPowerup.type; rabbitWeaponry.currentWeapon = WEAPON_TYPE_SPREAD_SHOT_GUN;
rabbitWeaponry.currentWeaponRemainingRounds = determineWeaponRounds(globalGameState.difficulty); rabbitWeaponry.currentWeaponRemainingRounds = (globalGameState.difficulty + 1) * SHOTGUN_ROUNDS_PER_LEVEL;
} }
} }
@ -469,123 +513,6 @@ 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;
@ -606,37 +533,18 @@ int main(void) {
enemyPosition, enemyPosition,
&rabbitPosition &rabbitPosition
); );
handleCombat();
handleRedraw();
handleEnemyKills(
enemyPosition,
&globalGameState,
&playerPowerup,
&rabbitWeaponry
);
handleMovement(); handleMovement();
handleRedraw();
handleCombat();
handleEnemyKills();
calculateUpgradesAvailable(); sprintf(buffer, "Hit: %d", kills);
redrawGameStats(); renderStringToDrawBuffer(buffer, 1, 0, 210, 20);
sprintf(buffer, "Health: %d ", health);
if (keyboardKeydownState.KEY_P && upgradesAvailable.damage) { renderStringToDrawBuffer(buffer, 1, 0, 210, 30);
globalGameState.coins -= damageUpgradeCosts[globalGameState.damageUpgradeLevel]; sprintf(buffer, "Rnds: %d ", rabbitWeaponry.currentWeaponRemainingRounds);
globalGameState.damageUpgradeLevel++; renderStringToDrawBuffer(buffer, 1, 0, 210, 40);
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,15 +9,6 @@ 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

2
in_progress.md Normal file
View File

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

View File

@ -1,5 +1,4 @@
obj = sprites.o game.o bmpload.o arena.o movement.o combat.o spawn.o const.o powerup.o obj = sprites.o game.o bmpload.o arena.o movement.o combat.o spawn.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
@ -20,14 +19,10 @@ 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_obj) system/system.lib .SYMBOLIC test: test.c spawn_test.c spawn.c .SYMBOLIC
wcl386 -fe=test -bt=dos -l=dos4g $(test_obj) cutest-1.5/CuTest.c system/system.lib wcl386 -fe=test -bt=dos -l=dos4g test.c spawn.c spawn_test.c cutest-1.5/CuTest.c
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,5 +1,4 @@
#include <math.h> #include <math.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "game.h" #include "game.h"
@ -9,9 +8,6 @@
#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,
@ -29,131 +25,33 @@ 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;
} }
// now this is ray casting
struct TargetMousePosition {
int x, y;
};
void constrainMousePosition(
struct RabbitPosition *pos,
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( void calculateTargetAngle(
struct RabbitPosition *pos struct RabbitPosition *pos,
struct MouseStatus *mouseStatus
) { ) {
float distanceX, distanceY; float distanceX, distanceY;
float angle, fixHypotenuse, fixDistance; float angle;
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;
}
pos->mouseAngle = angle; distanceX = sin(angle * DEG2RAD) * MOUSE_DISTANCE;
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];
targetMousePosition.x = pos->rabbitPosition[0]; pos->mousePosition[0] = pos->rabbitPosition[0] + distanceX;
targetMousePosition.y = pos->rabbitPosition[1]; pos->mousePosition[1] = pos->rabbitPosition[1] + distanceY;
constrainMousePosition(pos, &targetMousePosition); pos->mouseAngle = angle;
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,13 +18,18 @@ 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;
@ -49,23 +54,23 @@ struct BulletPosition {
}; };
struct EnemyPosition { struct EnemyPosition {
int hitPoints;
int enemyPosition[2];
int oldEnemyPosition[2];
int enemyMoveDelayStep;
int enemyFireDelayStep;
char isActive; char isActive;
char willBeInactive; char willBeInactive;
char wasKilled; char wasKilled;
char hitPoints;
char hasLeftGate; char hasLeftGate;
char gateExitedFrom; char gateExitedFrom;
int enemyPosition[2];
int oldEnemyPosition[2];
int enemyMoveDelayStep;
int enemyFireDelayStep;
}; };
void calculateTargetAngle(struct RabbitPosition*); void calculateTargetAngle(struct RabbitPosition*, struct MouseStatus*);
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*);

View File

@ -1,30 +0,0 @@
#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;
}

View File

@ -1,47 +0,0 @@
#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);
}
}

View File

@ -1,16 +0,0 @@
#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

View File

@ -1,125 +0,0 @@
#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,8 +17,21 @@ 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 hitPointRanges[difficulty][roll]; return HIT_POINT_RANGES[difficulty][roll];
} }
void selectEnemySpawnLocation( void selectEnemySpawnLocation(

View File

@ -39,6 +39,17 @@ 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);
@ -51,8 +62,8 @@ void TestSpawnEnemy(CuTest *tc) {
spawnEnemy(&spawnDetails, &globalGameState); spawnEnemy(&spawnDetails, &globalGameState);
// randomized values // randomized values
CuAssertIntEquals(tc, 1, enemyPosition.hitPoints); CuAssertIntEquals(tc, 2, enemyPosition.hitPoints);
CuAssertIntEquals(tc, 108, enemyPosition.enemyFireDelayStep); CuAssertIntEquals(tc, 103, enemyPosition.enemyFireDelayStep);
// from spawndetails // from spawndetails
CuAssertIntEquals(tc, 1, enemyPosition.gateExitedFrom); CuAssertIntEquals(tc, 1, enemyPosition.gateExitedFrom);
@ -65,6 +76,7 @@ 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,66 +7,11 @@ PUBLIC sprite_mouse_
PUBLIC sprite_bullet_ PUBLIC sprite_bullet_
PUBLIC sprite_enemy_ PUBLIC sprite_enemy_
PUBLIC sprite_shotgun_ PUBLIC sprite_shotgun_
PUBLIC sprite_beam_ PUBLIC sprite_shieldKiller_
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
@ -1391,10 +1336,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
@ -1614,155 +1559,11 @@ sprite_shotgun_:
ret ret
sprite_beam_: sprite_shieldKiller_:
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,11 +2,6 @@
#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 *);
@ -66,9 +61,9 @@ extern void sprite_mouse(byte *);
extern void sprite_bullet(byte *); extern void sprite_bullet(byte *);
#define SPRITE_BULLET_WIDTH (2) #define SPRITE_BULLET_WIDTH (4)
#define SPRITE_BULLET_HEIGHT (2) #define SPRITE_BULLET_HEIGHT (4)
#define SPRITE_BULLET_OFFSET_X (1) #define SPRITE_BULLET_OFFSET_X (1)
@ -97,15 +92,15 @@ extern void sprite_shotgun(byte *);
#define SPRITE_SHOTGUN_OFFSET_Y (6) #define SPRITE_SHOTGUN_OFFSET_Y (6)
extern void sprite_beam(byte *); extern void sprite_shieldKiller(byte *);
#define SPRITE_BEAM_WIDTH (12) #define SPRITE_SHIELDKILLER_WIDTH (12)
#define SPRITE_BEAM_HEIGHT (12) #define SPRITE_SHIELDKILLER_HEIGHT (12)
#define SPRITE_BEAM_OFFSET_X (6) #define SPRITE_SHIELDKILLER_OFFSET_X (6)
#define SPRITE_BEAM_OFFSET_Y (6) #define SPRITE_SHIELDKILLER_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: [17, 29] position: [16, 28]
dimensions: [2, 2] dimensions: [4, 4]
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]
beam: shieldKiller:
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,9 +13,6 @@ 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,9 +10,6 @@ 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,7 +2,6 @@
#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>
@ -129,68 +128,26 @@ 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) {
charRowDetails.c = font8x8_basic[currentCharacter][fontY]; charRow = font8x8_basic[currentCharacter][fontY];
charRowDetails.pos = drawBuffer + fontYs[fontY] + charX;
plotCharRow(&charRowDetails); for (fontX = 0; fontX < 8; ++fontX) {
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,38 +1,20 @@
#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 beforeAll() { void RunAllTests(void) {
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) {
return RunAllTests(); RunAllTests();
} }