continuing on unit tests, this is so easy

This commit is contained in:
John Bintz 2024-02-29 21:29:23 -05:00
parent dd4fdad4be
commit 64de129fc4
17 changed files with 473 additions and 102 deletions

View File

@ -39,10 +39,15 @@ MovBytePtr = Data.define(:offset, :data)
ASM_TEMPLATE = <<~ASM
<% sprites.each do |sprite| %>
PUBLIC <%= sprite.exported_name %><% end %>
PUBLIC _palette
.386
.model flat,c
.DATA
_palette:
<%= palette.flat_map { |color| color.to_asm_struct }.join("\n") %>
.CODE
<% sprites.each do |sprite| %>
@ -69,9 +74,7 @@ H_TEMPLATE = <<~H
#define PALETTE_COLOR_COUNT (<%= palette.length %>)
struct VGAColor palette[<%= palette.length %>] = {
<%= palette.map(&:to_struct).join(",\n") %>
};
extern struct VGAColor palette[<%= palette.length %>];
<% sprites.each do |sprite| %>
extern void <%= sprite.function_name %>(byte *);
@ -154,8 +157,12 @@ sprite_data = []
palette = nil
VGAColor = Data.define(:red, :green, :blue) do
def to_struct
"{ #{red}, #{green}, #{blue} }"
def to_asm_struct
[
" BYTE #{red}",
" BYTE #{green}",
" BYTE #{blue}"
]
end
end

3
bin/game Executable file
View File

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

4
bin/test Executable file
View File

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

View File

@ -2,11 +2,12 @@
#include <stdio.h>
#include <stdlib.h>
#include "game.h"
#include "system/vga.h"
#include "game.h"
#include "powerup.h"
#include "movement.h"
#include "const.h"
#include "system/vga.h"
void setupBullet(
struct BulletPosition *bullet,
@ -484,3 +485,58 @@ int handleRabbitToPowerupCollision(
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, hadKill, currentKillCount;
currentKillCount = processEnemyKillStates(enemyPosition);
globalGameState->health += ENEMY_KILL_HEALTH_GAIN * currentKillCount;
if (globalGameState->health > RABBIT_HEALTH_MAX) globalGameState->health = RABBIT_HEALTH_MAX;
globalGameState->kills += currentKillCount;
processPowerupCooldown(
playerPowerup,
globalGameState,
rabbitWeaponry,
currentKillCount
);
if (hadKill) {
for (i = 0; i < 10; ++i) {
if (globalGameState->kills > difficultyBands[i]) {
globalGameState->difficulty = i + 1;
}
}
}
}
void fireCurrentWeaponOnce(struct RabbitWeaponry *rabbitWeaponry) {
if (rabbitWeaponry->currentWeaponRemainingRounds > 0) {
rabbitWeaponry->currentWeaponRemainingRounds--;
if (rabbitWeaponry->currentWeaponRemainingRounds == 0) {
rabbitWeaponry->currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
}
}
}

View File

@ -48,3 +48,11 @@ int handleRabbitToPowerupCollision(
struct RabbitPosition *rabbitPosition,
struct PlayerPowerup *playerPowerup
);
void handleEnemyKills(
struct EnemyPosition enemyPosition[],
struct GlobalGameState *globalGameState,
struct PlayerPowerup *playerPowerup
);
void fireCurrentWeaponOnce(struct RabbitWeaponry*);

71
combat_test.c Normal file
View File

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

14
const.c Normal file
View File

@ -0,0 +1,14 @@
#include "system/vga.h"
#include "const.h"
int difficultyBands[MAX_DIFFICULTY] = { 10, 20, 30, 50, 80, 130, 210, 340, 550, 890 };
struct SpriteBounds bounds;
// TODO: centralize these outside of game.c
struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
shieldKiller;

19
const.h
View File

@ -1,3 +1,8 @@
#ifndef __CONST_H__
#define __CONST_H__
#include "system/vga.h"
#define TILE_SIZE (20)
#define ARENA_WIDTH_TILES (10)
#define ARENA_HEIGHT_TILES (10)
@ -50,3 +55,17 @@
#define POWERUP_RESPAWN_COOLDOWN_PER_LEVEL (10)
#define SHOTGUN_ROUNDS_PER_LEVEL (25)
#define MAX_DIFFICULTY (10)
extern int difficultyBands[];
extern struct SpriteBounds bounds;
extern struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
shieldKiller;
#endif

95
game.c
View File

@ -18,16 +18,7 @@
#include "combat.h"
#include "game.h"
#include "spawn.h"
// TODO: centralize these outside of game.c
struct CompiledSpriteRender rabbit,
mouse,
bullet,
enemy,
enemyBullet,
shotgun,
shieldKiller;
struct SpriteBounds bounds;
#include "powerup.h"
struct BMPImage spritesheetImage;
struct VGAColor vgaColors[256];
@ -50,6 +41,13 @@ struct BulletPosition enemyBulletPosition[ENEMY_BULLET_LIMIT];
struct RabbitWeaponry rabbitWeaponry;
struct PlayerPowerup playerPowerup;
struct GlobalGameState globalGameState = {
.spawnCooldown = 0,
.difficulty = 0,
.kills = 0,
.health = RABBIT_HEALTH_MAX
};
void setupRabbitBullets() {
int i;
@ -66,7 +64,7 @@ void setupRabbitBullets() {
void setupPowerup() {
playerPowerup.x = 100;
playerPowerup.y = 100;
playerPowerup.cooldown = 20 + rand() % 10;
playerPowerup.cooldown = determinePowerupCooldownTime(globalGameState.difficulty);
playerPowerup.type = POWERUP_TYPE_SHOTGUN;
playerPowerup.isActive = 0;
}
@ -89,54 +87,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() {
buildCompiledSprite(
&sprite_enemy,
@ -419,12 +369,9 @@ void handleCombat() {
&rabbitWeaponry,
rabbitBulletPosition
)) {
if (rabbitWeaponry.currentWeaponRemainingRounds > 0) {
rabbitWeaponry.currentWeaponRemainingRounds--;
if (rabbitWeaponry.currentWeaponRemainingRounds == 0) {
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SINGLE_SHOT_GUN;
}
}
fireCurrentWeaponOnce(
&rabbitWeaponry
);
}
}
@ -458,7 +405,7 @@ void handleCombat() {
);
if (didHitRabbit) {
health -= ENEMY_COLLISION_DAMAGE * didHitRabbit;
globalGameState.health -= ENEMY_COLLISION_DAMAGE * didHitRabbit;
}
didHitRabbit = handleEnemyBulletToRabbitCollisions(
@ -467,7 +414,7 @@ void handleCombat() {
);
if (didHitRabbit) {
health -= ENEMY_BULLET_DAMAGE * didHitRabbit;
globalGameState.health -= ENEMY_BULLET_DAMAGE * didHitRabbit;
}
handleRabbitBulletToEnemyCollisions(
@ -478,7 +425,7 @@ void handleCombat() {
if (handleRabbitToPowerupCollision(&rabbitPosition, &playerPowerup)) {
playerPowerup.willBeInactive = 1;
rabbitWeaponry.currentWeapon = WEAPON_TYPE_SPREAD_SHOT_GUN;
rabbitWeaponry.currentWeaponRemainingRounds = (globalGameState.difficulty + 1) * SHOTGUN_ROUNDS_PER_LEVEL;
rabbitWeaponry.currentWeaponRemainingRounds = determineShotgunRounds(globalGameState.difficulty);
}
}
@ -531,14 +478,20 @@ int main(void) {
handleMovement();
handleRedraw();
handleCombat();
handleEnemyKills();
handleEnemyKills(
enemyPosition,
&globalGameState,
&playerPowerup
);
sprintf(buffer, "Hit: %d", kills);
sprintf(buffer, "Hit: %d", globalGameState.kills);
renderStringToDrawBuffer(buffer, 1, 0, 210, 20);
sprintf(buffer, "Health: %d ", health);
sprintf(buffer, "Health: %d ", globalGameState.health);
renderStringToDrawBuffer(buffer, 1, 0, 210, 30);
sprintf(buffer, "Rnds: %d ", rabbitWeaponry.currentWeaponRemainingRounds);
renderStringToDrawBuffer(buffer, 1, 0, 210, 40);
sprintf(buffer, "Cool: %d ", playerPowerup.cooldown);
renderStringToDrawBuffer(buffer, 1, 0, 210, 50);
waitStartVbl();
copyDrawBufferToDisplay();

2
game.h
View File

@ -9,6 +9,8 @@ extern struct SpriteBounds bounds;
struct GlobalGameState {
int difficulty;
int spawnCooldown;
int kills;
int health;
};
#endif

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 combat.o combat_test.o
all: system/system.lib game.exe test .SYMBOLIC
@ -19,11 +20,14 @@ sprites.asm: sprtsht.bmp spritesheet.yml
game.exe: $(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
wcl386 -fe=test -bt=dos -l=dos4g test.c spawn.c spawn_test.c cutest-1.5/CuTest.c
test: $(test_obj) system/system.lib .SYMBOLIC
wcl386 -fe=test -bt=dos -l=dos4g $(test_obj) cutest-1.5/CuTest.c system/system.lib
clean: .SYMBOLIC
rm -f game.exe
rm -f test.exe
rm *.o
rm *.err
rm system/*.o
rm system/*.lib
rm system/*.err

46
powerup.c Normal file
View File

@ -0,0 +1,46 @@
#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 difficultyBands[difficulty] + rand() % difficultyBands[difficulty];
}
// 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 determineShotgunRounds(int difficulty) {
if (difficulty > MAX_DIFFICULTY) exit(1);
return difficultyBands[difficulty] +
difficultyBands[difficulty] / 2 +
rand() % (difficultyBands[difficulty] / 2);
}
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->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 determineShotgunRounds(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, 38, determinePowerupCooldownTime(1));
CuAssertIntEquals(tc, 33, determinePowerupCooldownTime(2));
}
void TestDetermineShotgunRounds(CuTest *tc) {
srand(1);
CuAssertIntEquals(tc, 18, determineShotgunRounds(0));
CuAssertIntEquals(tc, 38, determineShotgunRounds(1));
CuAssertIntEquals(tc, 48, determineShotgunRounds(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, 100, 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, 13, 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, TestDetermineShotgunRounds);
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;
}

View File

@ -8,10 +8,65 @@ PUBLIC sprite_bullet_
PUBLIC sprite_enemy_
PUBLIC sprite_shotgun_
PUBLIC sprite_shieldKiller_
PUBLIC _palette
.386
.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 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
BYTE 0
.CODE

View File

@ -6,25 +6,7 @@
#define PALETTE_COLOR_COUNT (17)
struct VGAColor palette[17] = {
{ 63, 0, 63 },
{ 0, 0, 0 },
{ 15, 16, 49 },
{ 16, 16, 33 },
{ 16, 16, 16 },
{ 63, 63, 63 },
{ 38, 31, 12 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 },
{ 0, 0, 0 }
};
extern struct VGAColor palette[17];
extern void sprite_arenaWallTop(byte *);

10
test.c
View File

@ -2,19 +2,25 @@
#include <stdio.h>
CuSuite *SpawnGetSuite();
CuSuite *PowerupGetSuite();
CuSuite *CombatGetSuite();
void RunAllTests(void) {
int RunAllTests(void) {
CuString *output = CuStringNew();
CuSuite *suite = CuSuiteNew();
CuSuiteAddSuite(suite, SpawnGetSuite());
CuSuiteAddSuite(suite, PowerupGetSuite());
CuSuiteAddSuite(suite, CombatGetSuite());
CuSuiteRun(suite);
CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output);
printf("%s\n", output->buffer);
return suite->failCount > 0;
}
int main(void) {
RunAllTests();
return RunAllTests();
}