Copy project

This commit is contained in:
schmelczerandras 2020-04-19 12:48:06 +02:00
parent ce7a467af7
commit 26a0fd884f
173 changed files with 157458 additions and 1 deletions

View file

@ -0,0 +1,214 @@
#include "ai.h"
#include <stdbool.h>
#include <avr/io.h>
#include "../object_container/object_container.h"
#include "../types/astronaut/astronaut.h"
#include "../types/spaceship/spaceship.h"
#include "../../util/rectangle/rectangle.h"
#include "../../util/random/random.h"
#include "../../driver/display/display.h"
#define AI_ACTION_COUNT 5
typedef bool (*Predicate)(Rectangle*, Object*);
typedef void (*Execution)(Object*);
typedef struct {
Predicate predicate;
Execution execution;
SpaceshipPart* spaceshipPart;
bool onlyOneAstronautCanDoIt;
Vec2 deltaCenter;
bool isSomeoneDoingThis;
} AIAction;
static AIAction actions[AI_ACTION_COUNT];
static Vec2 whichDirectionToMove(Object* astronaut, Vec2 position) {
bool const isTargetOnUpperFloor = isOnUpperFloor((Rectangle){position, (Vec2){0, 0}}); // else it's on the lower floor
Vec2 const ladder = add(LADDER_BOUNDING_BOX.position, spaceshipObject->position);
Vec2 astronautCenter = getCenter(getBoundingBox(astronaut));
Vec2 target = astronautCenter;
if ((
isOnUpperFloor(getBoundingBox(astronaut)) && isTargetOnUpperFloor
) || (
isOnLowerFloor(getBoundingBox(astronaut)) && !isTargetOnUpperFloor
)) {
target.x = position.x;
} else if (isOnLadder(getBoundingBox(astronaut))){
target.y = add(position, (Vec2){0, isTargetOnUpperFloor ? -10 : 10}).y;
} else {
target.x = ladder.x;
}
return clampVec2(substract(target, astronautCenter));
}
static void carefullyMoveAstronaut(Object* astronaut, Vec2 target) {
if (getIsControllingSpaceship(astronaut)) {
makeAstronautDoAction(astronaut);
}
if (!getIsControllingSpaceship(astronaut)) {
moveAstronaut(astronaut, whichDirectionToMove(astronaut, target));
}
}
static void carefullyMoveSpaceship(Object* astronaut, Vec2 target) {
if (!getIsControllingSpaceship(astronaut)) {
makeAstronautDoAction(astronaut);
}
if (getIsControllingSpaceship(astronaut)) {
Vec2 direction = clampVec2(
substract(
target,
getCenter(getBoundingBox(spaceshipObject))
)
);
moveAstronaut(astronaut, (Vec2){direction.x, 0});
moveAstronaut(astronaut, (Vec2){0, direction.y});
}
}
static bool shouldShootTurret(
__attribute__((unused)) Rectangle* boundingBox,
__attribute__((unused)) Object* astronaut
) {
return getIntersectingObjectOfType(
(Rectangle){
add(TURRET_POSITION, spaceshipObject->position),
(Vec2){63, 1}
},
&Asteroid
);
}
static bool shouldControlSpaceship(
__attribute__((unused)) Rectangle* boundingBox,
__attribute__((unused)) Object* astronaut
) {
return getCountOf(&Asteroid) > 0;
}
static void executeControlSpaceship(Object* astronaut) {
carefullyMoveSpaceship(
astronaut,
add(
getCenter(getBoundingBox(getFirstOfType(&Asteroid))),
(Vec2){-30, 0}
)
);
}
static bool shouldRepairSpaceship(Rectangle* boundingBox, Object* astronaut) {
return (
(
areIntersecting(*boundingBox, getBoundingBox(astronaut)) &&
spaceshipObject->as.spaceship.healthLoss > 0
) ||
spaceshipObject->as.spaceship.healthLoss >= MAX_HEALTH / 2
);
}
static bool shouldCenterSpaceship(Rectangle* boundingBox, Object* astronaut) {
return (
!actions[0].isSomeoneDoingThis &&
(
areIntersecting(*boundingBox, getBoundingBox(astronaut)) ||
getCenter(getBoundingBox(spaceshipObject)).x != getCenter(WINDOW).x ||
getCenter(getBoundingBox(spaceshipObject)).y != getCenter(WINDOW).y
)
);
}
static void executeCenterSpaceship(Object* astronaut) {
carefullyMoveSpaceship(
astronaut,
getCenter(WINDOW)
);
}
static bool shouldSocialize(
__attribute__((unused)) Rectangle* boundingBox,
__attribute__((unused)) Object* astronaut
) {
return true;
}
static void executeSocialize(Object* astronaut) {
if (getRandomNumber() % 150 == 0) {
makeAstronautDoAction(astronaut);
}
}
static AIAction actions[AI_ACTION_COUNT] = {
(AIAction) {
.predicate = shouldControlSpaceship,
.execution = executeControlSpaceship,
.spaceshipPart = spaceshipParts + COMMAND_PANEL_INDEX,
.onlyOneAstronautCanDoIt = true,
.deltaCenter = {-3, 0}
},
(AIAction) {
.predicate = shouldRepairSpaceship,
.execution = makeAstronautDoAction,
.spaceshipPart = spaceshipParts + BEDS_INDEX,
.onlyOneAstronautCanDoIt = true,
.deltaCenter = {2, 0}
},
(AIAction) {
.predicate = shouldShootTurret,
.execution = makeAstronautDoAction,
.spaceshipPart = spaceshipParts + TURRET_CONTROLLER_INDEX,
.onlyOneAstronautCanDoIt = true,
.deltaCenter = {-3, 0}
},
(AIAction) {
.predicate = shouldCenterSpaceship,
.execution = executeCenterSpaceship,
.spaceshipPart = spaceshipParts + COMMAND_PANEL_INDEX,
.onlyOneAstronautCanDoIt = true,
.deltaCenter = {-3, 0}
},
(AIAction) {
.predicate = shouldSocialize,
.execution = executeSocialize,
.spaceshipPart = spaceshipParts + TABLE_INDEX,
.onlyOneAstronautCanDoIt = false,
.deltaCenter = {2, 0}
},
};
void handleAI() {
for (uint8_t j = 0; j < ACTION_COUNT; j++) {
actions[j].isSomeoneDoingThis = false;
}
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == &Astronaut && objects + i != character) {
for (uint8_t j = 0; j < ACTION_COUNT; j++) {
AIAction* currentAction = actions + j;
Rectangle boundingBox = getBoundingBoxOfSpaceshipPart(currentAction->spaceshipPart);
Object* astronautIntersectingBoundingBox = getIntersectingObjectOfType(boundingBox, &Astronaut);
if (
isSpaceshipPartActivated(currentAction->spaceshipPart) &&
(!currentAction->onlyOneAstronautCanDoIt || (!currentAction->isSomeoneDoingThis && astronautIntersectingBoundingBox != character)) &&
currentAction->predicate(&boundingBox, objects + i)
) {
if (!areIntersecting(boundingBox, getBoundingBox(objects + i))) {
carefullyMoveAstronaut(objects + i, add(getCenter(boundingBox), currentAction->deltaCenter));
} else {
currentAction->execution(objects + i);
}
currentAction->isSomeoneDoingThis = true;
break;
}
}
}
}
}

View file

@ -0,0 +1,8 @@
#ifndef AI_H
#define AI_H
// If there are non player controlled astronauts
// control them according to some basic rule set
void handleAI();
#endif

View file

@ -0,0 +1,66 @@
#include "commands.h"
#include "../../objects/object_container/object_container.h"
#include "../../mediator/mediator.h"
static struct {
Command received[COMMAND_BUFFER_SIZE];
uint8_t start;
uint8_t end;
Command previous;
} commands;
static inline bool areThereAnyCommandsLeft() {
return commands.start != commands.end;
}
static inline Command getNextCommand() {
Command top = commands.received[commands.start++];
commands.start %= COMMAND_BUFFER_SIZE;
return top;
}
void addCommand(Command command) {
commands.received[commands.end++] = command;
commands.end %= COMMAND_BUFFER_SIZE;
}
void handleCommands() {
while(areThereAnyCommandsLeft()) {
Command next = getNextCommand();
if (next == repeat) {
next = commands.previous;
} else {
commands.previous = next;
}
switch(next) {
case increaseContrast:
changeDisplayContrast(CONTRAST_STEP);
break;
case decreaseContrast:
changeDisplayContrast(-CONTRAST_STEP);
break;
case moveLeft:
moveAstronaut(character, directions[west]);
break;
case moveRight:
moveAstronaut(character, directions[east]);
break;
case moveUp:
moveAstronaut(character, directions[north]);
break;
case moveDown:
moveAstronaut(character, directions[south]);
break;
case action:
makeAstronautDoAction(character);
commands.previous = noAction;
break;
default:
break;
}
}
}

View file

@ -0,0 +1,34 @@
#ifndef COMMANDS_H
#define COMMANDS_H
// There can be no more than COMMAND_BUFFER_SIZE commands
// waiting for processing simultaneously
#define COMMAND_BUFFER_SIZE 8
// increaseContrast and decreaseContrast changes the contrast
// with this value
#define CONTRAST_STEP 15
// The possible inputs of the system
// Coincidentally these are the codes of the IR remote
// controller's buttons.
typedef enum {
noCommand = 0,
repeat = 1,
increaseContrast = 87,
decreaseContrast = 31,
moveUp = 231,
moveDown = 181,
moveLeft = 239,
moveRight = 165,
action = 199,
} Command;
// Add a new command to the buffer
// It will not be processed immediately.
void addCommand(Command command);
// Process every command in the buffer at once in a FIFO manner
void handleCommands();
#endif

View file

@ -0,0 +1,78 @@
#include "event_generator.h"
#include <avr/io.h>
#include <stdbool.h>
#include "../object.h"
#include "../object_container/object_container.h"
#include "../types/spaceship/spaceship.h"
#include "../types/background/background.h"
#include "../types/astronaut/astronaut.h"
#include "../types/asteroid/asteroid.h"
#include "null.h"
#include "../../util/random/random.h"
#include "../../driver/display/display.h"
typedef bool (*Predicate)(Rectangle*);
static inline Vec2 getRandomPosition() {
return (Vec2) {
getRandomNumber() % DISPLAY_WIDTH_IN_PIXELS,
getRandomNumber() % DISPLAY_HEIGHT_IN_PIXELS
};
}
static void generate(Prototype const* type, Predicate predicate) {
Object* emptySpace = getEmptyObjectSpace();
if (emptySpace == NULL || getRandomNumber() != 0) {
return;
}
for (uint8_t tryCount = 0; tryCount < TRY_COUNT; tryCount++) {
Rectangle proposedBoundingBox = (Rectangle){getRandomPosition(), getSizeFromPrototype(type)};
if (predicate(&proposedBoundingBox)) {
createObject(type, emptySpace);
emptySpace->position = proposedBoundingBox.position;
return;
}
}
}
bool generateAstronautPredicate(Rectangle* proposedBoundingBox) {
return (
(
(getCountOf(&Astronaut) == 1 && spaceshipObject->as.spaceship.progress >= hasTable) ||
(getCountOf(&Astronaut) == 2 && spaceshipObject->as.spaceship.progress >= hasFullCrew)
) &&
getIntersectingObjectOfType(*proposedBoundingBox, &Astronaut) == NULL &&
isOnboard(*proposedBoundingBox)
);
}
bool generateAsteroidPredicate(Rectangle* proposedBoundingBox) {
return (
getCountOf(&Asteroid) < MAX_ASTEROID_COUNT &&
isInside(*proposedBoundingBox, WINDOW) &&
getIntersectingObjectOfType(*proposedBoundingBox, &Spaceship) == NULL &&
getIntersectingObjectOfType(*proposedBoundingBox, &Asteroid) == NULL
);
}
void createObjects() {
createObject(&Background, getEmptyObjectSpace());
createObject(&Spaceship, spaceshipObject);
spaceshipObject->position = (Vec2){EXHAUST_BOUNDING_BOX.size.x, DISPLAY_HEIGHT_IN_PIXELS / 2 - getSize(spaceshipObject).y / 2};
createObject(&Astronaut, character);
Rectangle upperFloor = translateRectangle(UPPER_FLOOR_BOUNDING_BOX, spaceshipObject->position);
character->position = add(upperFloor.position, (Vec2){10, 1});
}
void generateEvents() {
generate(&Astronaut, generateAstronautPredicate);
generate(&Asteroid, generateAsteroidPredicate);
}

View file

@ -0,0 +1,18 @@
#ifndef EVENT_GENERATOR_H
#define EVENT_GENERATOR_H
#define MAX_ASTEROID_COUNT 2
// For minimizing code size the position of generated objects is
// decided randomly. If it fits then it stays. For each generated object
// can be a maximum of TRY_COUNT tries.
#define TRY_COUNT 16
// Create the necessary objects in order to start the game
// These include the background, spaceship and the player's character.
void createObjects();
// Generate asteroids and astronaut randomly based on a set of conditions
void generateEvents();
#endif

View file

@ -0,0 +1,41 @@
#include "object.h"
#include "null.h"
Object* createObject(Prototype const* prototype, Object* holder) {
Object empty = {0};
*holder = empty;
holder->prototype = prototype;
return holder;
}
void tickObject(Object* object, uint8_t previousFrameTime) {
if (object->prototype != NULL) {
((TickMethod)pgm_read_word(&object->prototype->tick))(object, previousFrameTime);
}
}
void drawObject(Object* object, Rectangle compositingWindow) {
if (object->prototype != NULL && areIntersecting(getBoundingBox(object), compositingWindow)) {
((DrawMethod)pgm_read_word(&object->prototype->draw))(object, compositingWindow);
}
}
Vec2 getSizeFromPrototype(Prototype const* prototype) {
// required for casting
uint16_t read = pgm_read_word(&prototype->size);
Vec2* v = (Vec2*) &read;
return *v;
}
Vec2 getSize(Object const* object) {
return getSizeFromPrototype(object->prototype);
}
void move(Object* object, Vec2 value) {
object->position = add(object->position, value);
}
Rectangle getBoundingBox(Object const* object) {
return (Rectangle){object->position, getSize(object)};
}

View file

@ -0,0 +1,68 @@
#ifndef OBJECT_H
#define OBJECT_H
#include <avr/io.h>
#include <avr/pgmspace.h>
#include "../util/rectangle/rectangle.h"
#include "types/asteroid/asteroid.h"
#include "types/astronaut/astronaut.h"
#include "types/background/background.h"
#include "types/spaceship/spaceship.h"
#include "types/bullet/bullet.h"
#include "types/heart/heart.h"
#include "prototype.h"
// Objects (they could have been called GameObjects) have a simple
// hierarchy. A prototype/flyweight motivated system is used.
// Each type has some common data and methods which are stored
// in their respective prototype.
// This module provides us with the methods to easily and mostly
// transparently access an object's prototype.
typedef union {
struct _background_t background;
struct _spaceship_t spaceship;
struct _astronaut_t astronaut;
struct _asteroid_t asteroid;
struct _bullet_t bullet;
struct _heart_t heart;
} object_specific_data_t;
struct _object_t {
Prototype const* prototype;
Vec2 position;
object_specific_data_t as;
};
// Set the prototype of the holder and initialize all the holder's
// vale to zero. Return the freshly updated holder
Object* createObject(Prototype const* prototype, Object* holder);
// Call the tick function referenced in the object's prototype
// on the object itself
// Object might react to the elapsed time.
// Does nothing when called with NULL.
void tickObject(Object* object, uint8_t previousFrameTime);
// Call the draw function referenced in the object's prototype
// on the object itself.
// Does nothing when called with NULL.
void drawObject(Object* object, Rectangle compositingWindow);
// Find out the prototype of the object and return the size of that
Vec2 getSize(Object const* object);
Vec2 getSizeFromPrototype(Prototype const* prototype);
// Move the position of the object by a vector
void move(Object* object, Vec2 value);
// Get a new rectangle from the objects position and its
// prototype's size
Rectangle getBoundingBox(Object const* object);
#endif

View file

@ -0,0 +1,57 @@
#include "object_container.h"
#include "../event_generator/event_generator.h"
Object* getFirstOfType(Prototype const* type) {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == type) {
return objects + i;
}
}
return NULL;
}
uint8_t getCountOf(Prototype const* type) {
uint8_t count = 0;
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == type) {
count++;
}
}
return count;
}
Object* getIntersectingObjectOfType(Rectangle boundingBox, Prototype const* type) {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == type && areIntersecting(boundingBox, getBoundingBox(objects + i))) {
return objects + i;
}
}
return NULL;
}
void clearObject(Object* object) {
createObject(NULL, object);
}
void drawObjects(Rectangle window) {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
drawObject(objects + i, window);
}
}
void tickObjects(uint8_t previousFrameTime) {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
tickObject(objects + i, previousFrameTime);
}
}
void initializeObjectContainer() {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
clearObject(objects + i);
}
}

View file

@ -0,0 +1,58 @@
#ifndef OBJECT_HANDLER_H
#define OBJECT_HANDLER_H
#include <stdbool.h>
#include <avr/io.h>
#include "null.h"
#include "../object.h"
#include "../../util/rectangle/rectangle.h"
#include "../../util/vec2/vec2.h"
// Contain up to OBJECT_COUNT objects.
// Provide the basic functionality to access, search and modify
// these objects.
// For ease of use, there are some convenience global variables for accessing
// objects that are very commonly accessed.
#define OBJECT_COUNT 10
#define BACKGROUND_INDEX 0
#define SPACESHIP_INDEX 1
#define CHARACTER_INDEX 2
#define spaceshipObject (objects + SPACESHIP_INDEX)
#define character (objects + CHARACTER_INDEX)
// The actual container
Object objects[OBJECT_COUNT];
// may return NULL
Object* getFirstOfType(Prototype const* type);
// may return NULL
#define getEmptyObjectSpace() getFirstOfType(NULL)
// Return the number of objects with a prototype being type
uint8_t getCountOf(Prototype const* type);
// Return a reference to a random object intersecting boundingBox and having
// a prototype of type
Object* getIntersectingObjectOfType(Rectangle boundingBox, Prototype const* type);
// Call the tick method of every object
// objects might respond to the elapsed time
void tickObjects(uint8_t previousFrameTime);
// Call the draw method of every object
void drawObjects(Rectangle window);
// Delete the object given by its address from objects
// It achieves this by setting the object's prototype to NULL.
void clearObject(Object* object);
// Delete every object inside of objects
void initializeObjectContainer();
#endif

View file

@ -0,0 +1,22 @@
#include "prototype.h"
Prototype temp;
static void loadPrototype(Prototype* prototype) {
/*for (uint8_t i = 0; i < sizeof(prototype); i++) {
((uint8_t*)(&temp))[i] = pgm_read_byte(prototype + i);
}*/
temp = *prototype;
}
void tickObjectFromPrototype(Object* object) {
loadPrototype(object->prototype);
temp.tick(object);
}
void drawObjectFromPrototype(Object* object) {
/*loadPrototype(object->prototype);
temp.draw(object);*/
}

View file

@ -0,0 +1,27 @@
#ifndef PROTOTYPE_H
#define PROTOTYPE_H
#include <avr/io.h>
#include "../util/rectangle/rectangle.h"
// See more information in object.h
struct _object_t;
typedef struct _object_t Object;
// Update the inner state of the given object
// The first argument is the object itself, the second is the elapsed
// time in milliseconds.
typedef void (*TickMethod)(Object*, uint8_t);
// Draw the given object if its overlapping with the given rectangle
typedef void (*DrawMethod)(Object*, Rectangle);
typedef struct {
TickMethod tick;
DrawMethod draw;
Vec2 size;
} Prototype;
#endif

View file

@ -0,0 +1,51 @@
#include "asteroid.h"
#include <avr/pgmspace.h>
#include "../sprites.h"
#include "../../object.h"
#include "../../object_container/object_container.h"
#include "../../../util/vec2/vec2.h"
#include "../../../util/random/random.h"
#include "../../../driver/display/display.h"
bool mineAsteroid(Object* asteroid) {
return ++asteroid->as.asteroid.animationFrame == IDLE_FRAME_COUNT;
}
static void tick(Object* asteroid, uint8_t previousFrameTime) {
if (asteroid->as.asteroid.animationFrame < IDLE_FRAME_COUNT) {
return;
}
if (++asteroid->as.asteroid.timeSinceLastFrameChange == EXPLODING_FRAME_CHANGE_INTERVAL) {
if (++asteroid->as.asteroid.animationFrame >= IDLE_FRAME_COUNT + EXPLODING_FRAME_COUNT) {
clearObject(asteroid);
} else {
asteroid->as.asteroid.timeSinceLastFrameChange = 0;
}
}
}
bool isAsteroidIntersectingWithSpaceship(Object* asteroid, Object* spaceship) {
Rectangle bb = getBoundingBox(asteroid);
startIntersectionTest(bb);
drawObject(asteroid, bb);
drawObject(spaceship, bb);
return endIntersectionTest();
}
static void draw(Object* asteroid, __attribute__((unused)) Rectangle compositingWindow) {
drawBitmapFromProgMem(
getBoundingBox(asteroid),
small_asteroid[asteroid->as.asteroid.animationFrame],
asteroid->position.x % 2
);
}
const Prototype Asteroid PROGMEM = {
.tick = tick,
.draw = draw,
.size = ASTEROID_SIZE,
};

View file

@ -0,0 +1,25 @@
#ifndef ASTEROID_H
#define ASTEROID_H
#include <avr/io.h>
#include <stdbool.h>
#include "../../prototype.h"
#define ASTEROID_SIZE ((Vec2){8, 8})
#define IDLE_FRAME_COUNT 4
#define EXPLODING_FRAME_COUNT 3
#define EXPLODING_FRAME_CHANGE_INTERVAL 3
const Prototype Asteroid;
bool mineAsteroid(Object* asteroid);
bool isAsteroidIntersectingWithSpaceship(Object* asteroid, Object* spaceship);
struct _asteroid_t {
bool isMirrored;
uint8_t timeSinceLastFrameChange;
uint8_t animationFrame;
};
#endif

View file

@ -0,0 +1,146 @@
#include "astronaut.h"
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "bitwise.h"
#include "../../../driver/display/display.h"
#include "../heart/heart.h"
#include "../../object_container/object_container.h"
#include "../sprites.h"
#include "../../../util/vec2/vec2.h"
#include "../../../util/random/random.h"
#include "../../object.h"
#define IS_MIRRORED_BIT 0
#define IS_CONTROLLING_SPACESHIP_BIT 1
#define WAS_DOING_ACTION_BIT 2
static inline bool getIsMirrored(Object* astronaut) {
return getBit(astronaut->as.astronaut.flags, IS_MIRRORED_BIT);
}
static inline void setIsMirrored(Object* astronaut, bool value) {
modifyBit(astronaut->as.astronaut.flags, IS_MIRRORED_BIT, value);
}
static inline bool getWasDoingAction(Object* astronaut) {
return getBit(astronaut->as.astronaut.flags, WAS_DOING_ACTION_BIT);
}
static inline void setWasDoingAction(Object* astronaut, bool value) {
modifyBit(astronaut->as.astronaut.flags, WAS_DOING_ACTION_BIT, value);
}
bool getIsControllingSpaceship(Object* astronaut) {
return getBit(astronaut->as.astronaut.flags, IS_CONTROLLING_SPACESHIP_BIT);
}
static void setIsControllingSpaceship(Object* astronaut, bool value) {
modifyBit(astronaut->as.astronaut.flags, IS_CONTROLLING_SPACESHIP_BIT, value);
}
static inline void applyGravity(Object* astronaut) {
if (!isOnLadder(getBoundingBox(astronaut)) && !isBottomOnFloor(getBoundingBox(astronaut))) {
move(astronaut, directions[south]);
}
}
static void tick(Object* astronaut, uint8_t previousFrameTime) {
if (astronaut->as.astronaut.timeSinceLastAction < TIME_BETWEEN_ACTION_CHANGE) {
astronaut->as.astronaut.timeSinceLastAction += previousFrameTime;
}
applyGravity(astronaut);
}
void moveAstronaut(Object* astronaut, Vec2 direction) {
if (
astronaut->as.astronaut.timeSinceLastAction < TIME_BETWEEN_ACTION_CHANGE
&& !getWasDoingAction(astronaut)
) {
return;
}
astronaut->as.astronaut.timeSinceLastAction = 0;
setWasDoingAction(astronaut, false);
if (getIsControllingSpaceship(astronaut)) {
moveSpaceship(direction);
} else {
Vec2 proposedPosition = add(astronaut->position, direction);
Rectangle proposedBoundingBox = (Rectangle){proposedPosition, getSize(astronaut)};
if (isOnboard(proposedBoundingBox)) {
astronaut->position = proposedPosition;
astronaut->as.astronaut.animationFrame = (astronaut->as.astronaut.animationFrame + 1) % MOVE_FRAME_COUNT;
}
if (direction.x == 0) {
astronaut->as.astronaut.animationFrame = 0;
}
setIsMirrored(astronaut, direction.x < 0);
}
}
void makeAstronautDoAction(Object* astronaut) {
if (
astronaut->as.astronaut.timeSinceLastAction < TIME_BETWEEN_ACTION_CHANGE
&& getWasDoingAction(astronaut)
) {
return;
}
astronaut->as.astronaut.timeSinceLastAction = 0;
setWasDoingAction(astronaut, true);
if (getIsControllingSpaceship(astronaut)) {
setIsControllingSpaceship(astronaut, false);
} else {
Object* heart;
switch (getPossibleActionFromSpaceship(astronaut)) {
case shootTurret:
shootTurretOfSpaceship();
break;
case showLove:
heart = getEmptyObjectSpace();
if (heart != NULL) {
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (
objects[i].prototype == &Astronaut &&
areIntersecting(getBoundingBox(astronaut), getBoundingBox(objects + i)) &&
objects + i != astronaut
) {
createObject(&Heart, heart);
heart->position = add(astronaut->position, (Vec2){(getRandomNumber() % 11) - 5, -(getRandomNumber() % 5) - 10});
break;
}
}
}
break;
case repairingSpaceship:
if (spaceshipObject->as.spaceship.healthLoss > 0) {
spaceshipObject->as.spaceship.healthLoss--;
}
break;
case controllingSpaceship:
setIsControllingSpaceship(astronaut, true);
break;
default:
break;
}
}
}
static void draw(Object* astronaut, __attribute__((unused)) Rectangle compositingWindow) {
drawBitmapFromProgMem(
getBoundingBox(astronaut),
small_character_moving[astronaut->as.astronaut.animationFrame],
getIsMirrored(astronaut)
);
}
const Prototype Astronaut PROGMEM = {
.tick = tick,
.draw = draw,
.size = ASTRONAUT_SIZE
};

View file

@ -0,0 +1,37 @@
#ifndef ASTRONAUT_H
#define ASTRONAUT_H
#include "../../prototype.h"
#include <stdbool.h>
#define ASTRONAUT_SIZE ((Vec2){5, 5})
#define MOVE_FRAME_COUNT 4
// Between two consecutive actions (or movements)
// there has to be at least this many milliseconds
#define TIME_BETWEEN_ACTION_CHANGE 40
typedef enum {
noAction = 0,
controllingSpaceship,
shootTurret,
showLove,
repairingSpaceship,
ACTION_COUNT
} Action;
const Prototype Astronaut;
struct _astronaut_t {
uint8_t flags;
uint8_t animationFrame;
uint8_t timeSinceLastAction;
};
void moveAstronaut(Object* astronaut, Vec2 unitVector);
void makeAstronautDoAction(Object* astronaut);
bool getIsControllingSpaceship(Object* astronaut);
#endif

View file

@ -0,0 +1,71 @@
#include "background.h"
#include <stdbool.h>
#include <avr/pgmspace.h>
#include "../../object.h"
#include "../../../util/rectangle/rectangle.h"
#include "../../../util/random/random.h"
#include "../sprites.h"
#include "../../../driver/display/display.h"
typedef struct {
Vec2 position;
uint8_t type;
} Star;
static Star backgroundStars[STAR_COUNT];
static Star createStarOnTheRight() {
return (Star){
(Vec2){DISPLAY_WIDTH_IN_PIXELS, getRandomNumber() % (DISPLAY_HEIGHT_IN_PIXELS - STAR_SIZE)},
getRandomNumber() % STAR_SHAPE_COUNT
};
}
static void tick(Object* background, uint8_t previousFrameTime) {
background->as.background.movementState++;
for (uint8_t i = 0; i < STAR_COUNT; i++) {
switch (i % 3) {
case 0:
backgroundStars[i].position.x -= 1;
break;
case 1:
backgroundStars[i].position.x -= background->as.background.movementState & 1;
break;
case 2:
backgroundStars[i].position.x -= ~background->as.background.movementState & 1;
break;
}
if (backgroundStars[i].position.x == -STAR_SIZE) {
backgroundStars[i] = createStarOnTheRight();
}
}
}
static void draw(__attribute__((unused)) Object* background, Rectangle compositingWindow) {
for (uint8_t i = 0; i < STAR_COUNT; i++) {
Rectangle starBoundingBox = (Rectangle){backgroundStars[i].position, (Vec2){STAR_SIZE, STAR_SIZE}};
if (areIntersecting(compositingWindow, starBoundingBox)) {
drawBitmapFromProgMem(
starBoundingBox,
stars[backgroundStars[i].type],
false
);
}
}
}
void initializeBackground() {
for (uint8_t i = 0; i < STAR_COUNT; i++) {
backgroundStars[i] = createStarOnTheRight();
backgroundStars[i].position.x = getRandomNumber();
}
}
const Prototype Background PROGMEM = {
.tick = tick,
.draw = draw,
.size = (Vec2){DISPLAY_WIDTH_IN_PIXELS, DISPLAY_HEIGHT_IN_PIXELS} // == WINDOW.size
};

View file

@ -0,0 +1,18 @@
#ifndef BACKGROUND_H
#define BACKGROUND_H
#include "../../prototype.h"
#define STAR_COUNT 8
#define STAR_SIZE 3
#define STAR_SHAPE_COUNT 3
const Prototype Background;
struct _background_t {
uint8_t movementState;
};
void initializeBackground();
#endif

View file

@ -0,0 +1,41 @@
#include "bullet.h"
#include "../asteroid/asteroid.h"
#include "../spaceship/spaceship.h"
#include "../../object.h"
#include "../../object_container/object_container.h"
#include "null.h"
#include "../../../driver/display/display.h"
static void tick(Object* bullet, uint8_t previousFrameTime) {
if (bullet->as.bullet.wereIntersectingInThePreviousFrame) {
clearObject(bullet);
return;
}
move(bullet, directions[east]);
Object* asteroid = getIntersectingObjectOfType(getBoundingBox(bullet), &Asteroid);
if (asteroid != NULL) {
if (mineAsteroid(asteroid) || mineAsteroid(asteroid)) {
onAsteroidMined();
}
bullet->as.bullet.wereIntersectingInThePreviousFrame = true;
}
if (!areIntersecting(getBoundingBox(bullet), WINDOW)) {
bullet->as.bullet.wereIntersectingInThePreviousFrame = true;
}
}
static void draw(Object* bullet, __attribute__((unused)) Rectangle compositingWindow) {
drawFilledRectangle((Rectangle){bullet->position, BULLET_SIZE}, 0, 0xFF);
}
const Prototype Bullet PROGMEM = {
.tick = tick,
.draw = draw,
.size = BULLET_SIZE,
};

View file

@ -0,0 +1,16 @@
#ifndef BULLET_H
#define BULLET_H
#include "../../prototype.h"
#include <stdbool.h>
#define BULLET_SIZE ((Vec2){5, 1})
const Prototype Bullet;
struct _bullet_t {
bool wereIntersectingInThePreviousFrame;
};
#endif

View file

@ -0,0 +1,33 @@
#include "heart.h"
#include <avr/pgmspace.h>
#include "../../../driver/display/display.h"
#include "../sprites.h"
#include "../../object.h"
#include "../../object_container/object_container.h"
static void tick(Object* heart, uint8_t previousFrameTime) {
if (++heart->as.heart.timeSinceLastChange == HEART_ANIMATION_INTERVAL) {
heart->as.heart.animationFrame = (heart->as.heart.animationFrame + 1) % HEART_FRAME_COUNT;
heart->as.heart.timeSinceLastChange = 0;
}
if (++heart->as.heart.timeLived == TIME_TO_LIVE) {
clearObject(heart);
}
}
static void draw(Object* heart, __attribute__((unused)) Rectangle compositingWindow) {
drawBitmapFromProgMem(
getBoundingBox(heart),
heart_blinking[heart->as.heart.animationFrame],
false
);
}
const Prototype Heart PROGMEM = {
.tick = tick,
.draw = draw,
.size = HEART_SIZE,
};

View file

@ -0,0 +1,22 @@
#ifndef HEART_H
#define HEART_H
#include "../../prototype.h"
#define HEART_SIZE ((Vec2){7, 6})
#define TIME_TO_LIVE 128
#define HEART_FRAME_COUNT 2
#define HEART_ANIMATION_INTERVAL 12
const Prototype Heart;
struct _heart_t {
uint8_t animationFrame;
uint8_t timeLived;
uint8_t timeSinceLastChange;
};
#endif

View file

@ -0,0 +1,208 @@
#include "spaceship.h"
#include <avr/pgmspace.h>
#include "../../object.h"
#include "../../../driver/display/display.h"
#include "../astronaut/astronaut.h"
#include "../asteroid/asteroid.h"
#include "../../object_container/object_container.h"
#include "../sprites.h"
#include "../../../util/vec2/vec2.h"
#include "../../../util/random/random.h"
#include "bitwise.h"
static uint8_t flickerState;
SpaceshipPart spaceshipParts[SPACESHIP_PART_COUNT] = {
[TABLE_INDEX] = (SpaceshipPart) {
{{7, 8}, {3, 3}},
table[0],
showLove,
false
},
[BEDS_INDEX] = (SpaceshipPart) {
{{3, 12}, {8, 6}},
beds[0],
repairingSpaceship,
false
},
[COMMAND_PANEL_INDEX] = (SpaceshipPart) {
{{26, 7}, {7, 4}},
NULL,
controllingSpaceship,
true,
},
[TURRET_CONTROLLER_INDEX] = (SpaceshipPart) {
{{26, 12}, {7, 6}},
turret_controller[0],
shootTurret,
false
}
};
bool isOnUpperFloor(Rectangle boundingBox) {
return isInside(boundingBox, translateRectangle(UPPER_FLOOR_BOUNDING_BOX, spaceshipObject->position));
}
bool isOnLowerFloor(Rectangle boundingBox) {
return isInside(boundingBox, translateRectangle(LOWER_FLOOR_BOUNDING_BOX, spaceshipObject->position));
}
bool isBottomOnFloor(Rectangle boundingBox) {
return (
add(spaceshipObject->position, UPPER_FLOOR_BOUNDING_BOX.position).y + UPPER_FLOOR_BOUNDING_BOX.size.y == boundingBox.position.y + boundingBox.size.y ||
add(spaceshipObject->position, LOWER_FLOOR_BOUNDING_BOX.position).y + LOWER_FLOOR_BOUNDING_BOX.size.y == boundingBox.position.y + boundingBox.size.y
);
}
bool isOnLadder(Rectangle boundingBox) {
return areIntersecting(boundingBox, translateRectangle(LADDER_BOUNDING_BOX, spaceshipObject->position));
}
bool isOnboard(Rectangle boundingBox) {
return isOnLowerFloor(boundingBox) || isOnUpperFloor(boundingBox) || isOnLadder(boundingBox);
}
Rectangle getBoundingBoxOfSpaceshipPart(SpaceshipPart* part) {
return translateRectangle(part->boundingBox, spaceshipObject->position);
}
void shootTurretOfSpaceship() {
Object* bullet = getEmptyObjectSpace();
if (getEmptyObjectSpace() != NULL && spaceshipObject->as.spaceship.healthLoss < MAX_HEALTH - 1) {
createObject(&Bullet, bullet);
bullet->position = add(TURRET_POSITION, spaceshipObject->position);
spaceshipObject->as.spaceship.healthLoss++;
}
}
void onAsteroidMined() {
switch (++spaceshipObject->as.spaceship.progress) {
case hasBeds:
setBit(spaceshipObject->as.spaceship.activatedParts, BEDS_INDEX);
break;
case hasTurret:
setBit(spaceshipObject->as.spaceship.activatedParts, TURRET_CONTROLLER_INDEX);
break;
case hasTable:
setBit(spaceshipObject->as.spaceship.activatedParts, TABLE_INDEX);
break;
default:
break;
}
}
void moveSpaceship(Vec2 direction) {
Vec2 proposedPosition = add(spaceshipObject->position, direction);
if (!isInside(translateRectangle(IN_VIEW_BOUNDING_BOX, proposedPosition), WINDOW)) {
return;
}
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == &Astronaut) {
move(objects + i, direction);
}
}
move(spaceshipObject, direction);
spaceshipObject->position = proposedPosition;
for (uint8_t i = 0; i < OBJECT_COUNT; i++) {
if (objects[i].prototype == &Asteroid && isAsteroidIntersectingWithSpaceship(objects + i, spaceshipObject)) {
if (mineAsteroid(objects + i)) {
spaceshipObject->as.spaceship.healthLoss += 2;
onAsteroidMined();
}
}
}
}
Action getPossibleActionFromSpaceship(Object* astronaut) {
for (uint8_t i = 0; i < SPACESHIP_PART_COUNT; i++) {
SpaceshipPart* part = spaceshipParts + i;
if (
isSpaceshipPartActivated(part) && areIntersecting(getBoundingBoxOfSpaceshipPart(part), getBoundingBox(astronaut))) {
return part->possibleAction;
}
}
return noAction;
}
void tick(Object* spaceship, uint8_t previousFrameTime) {
flickerState = !flickerState;
if (spaceship->as.spaceship.healthLoss >= MAX_HEALTH) {
spaceship->as.spaceship.healthLoss++;
}
}
bool isSpaceshipPartActivated(SpaceshipPart* part) {
return part->alwaysActiveDoNotDraw || ((spaceshipObject->as.spaceship.activatedParts >> (part - spaceshipParts)) & 1);
}
static inline void drawSpaceshipHealthBar() {
uint8_t actualBarLength = spaceshipObject->as.spaceship.healthLoss * BAR_LENGTH / MAX_HEALTH;
drawFilledRectangle(
(Rectangle){add(spaceshipObject->position, (Vec2){BAR_END_POSITION.x - actualBarLength, BAR_END_POSITION.y}), (Vec2){actualBarLength, 1}},
0xFF, 0x00
);
}
static inline void drawSpaceshipParts(Rectangle compositingWindow) {
for (uint8_t i = 0; i < SPACESHIP_PART_COUNT; i++) {
if (
!(spaceshipParts + i)->alwaysActiveDoNotDraw &&
isSpaceshipPartActivated(spaceshipParts + i) &&
areIntersecting(compositingWindow, getBoundingBoxOfSpaceshipPart(spaceshipParts + i))
) {
drawBitmapFromProgMem(getBoundingBoxOfSpaceshipPart(spaceshipParts + i), spaceshipParts[i].sprite, false);
}
}
}
static inline void drawExhaust(Rectangle compositingWindow) {
Rectangle exhaustRectangle = translateRectangle(EXHAUST_BOUNDING_BOX, spaceshipObject->position);
if (
areIntersecting(compositingWindow, exhaustRectangle) &&
flickerState
) {
drawBitmapFromProgMem(exhaustRectangle, exhaust[0], false);
}
}
static inline void drawGlitches() {
for (uint8_t i = 0; i < spaceshipObject->as.spaceship.healthLoss - MAX_HEALTH; i++) {
Rectangle r = translateRectangle((Rectangle){(Vec2){getRandomNumber() % SPACESHIP_SIZE.x, getRandomNumber() % SPACESHIP_SIZE.y}, (Vec2){8, 8}}, spaceshipObject->position);
if (areIntersecting(r, WINDOW)) {
drawFilledRectangle(r, 0xFF, 0x00);
}
}
}
static void draw(Object* spaceship, Rectangle compositingWindow) {
drawBitmapFromProgMem(
getBoundingBox(spaceship),
spaceship_idle[0],
false
);
drawSpaceshipParts(compositingWindow);
drawSpaceshipHealthBar();
if (spaceship->as.spaceship.healthLoss > MAX_HEALTH) {
drawGlitches();
} else {
drawExhaust(compositingWindow);
}
}
const Prototype Spaceship PROGMEM = {
.tick = tick,
.draw = draw,
.size = SPACESHIP_SIZE,
};

View file

@ -0,0 +1,72 @@
#ifndef SPACESHIP_H
#define SPACESHIP_H
#include "../../prototype.h"
#include "../../../util/rectangle/rectangle.h"
#include "../astronaut/astronaut.h"
#include <stdbool.h>
#define SPACESHIP_SIZE ((Vec2){36, 23})
#define IN_VIEW_BOUNDING_BOX ((Rectangle){(Vec2){7, 4}, (Vec2){22, 15}})
#define UPPER_FLOOR_BOUNDING_BOX ((Rectangle){(Vec2){8, 5}, (Vec2){19, 6}})
#define LOWER_FLOOR_BOUNDING_BOX ((Rectangle){(Vec2){5, 12}, (Vec2){23, 6}})
#define EXHAUST_BOUNDING_BOX ((Rectangle){(Vec2){-4, 9}, (Vec2){5, 5}})
#define TURRET_POSITION ((Vec2){35, 11})
#define LADDER_BOUNDING_BOX ((Rectangle){(Vec2){12, 10}, (Vec2){1, 4}})
#define BOBBING_INTERVAL 130
#define SPACESHIP_PART_COUNT 4
#define TABLE_INDEX 3
#define BEDS_INDEX 2
#define COMMAND_PANEL_INDEX 1
#define TURRET_CONTROLLER_INDEX 0
#define BAR_END_POSITION ((Vec2){33, 11})
#define BAR_LENGTH 4
#define MAX_HEALTH 8
typedef struct {
Rectangle boundingBox;
uint16_t** sprite;
Action possibleAction;
bool alwaysActiveDoNotDraw;
} SpaceshipPart;
SpaceshipPart spaceshipParts[SPACESHIP_PART_COUNT];
const Prototype Spaceship;
typedef enum {
hasBeds = 1,
hasTurret = 3,
hasTable = 5,
hasFullCrew = 9
} Progress;
struct _spaceship_t {
uint8_t healthLoss;
uint8_t progress;
uint8_t activatedParts;
};
bool isOnboard(Rectangle boundingBox);
void moveSpaceship(Vec2 direction);
Rectangle getBoundingBoxOfSpaceshipPart(SpaceshipPart* part);
bool isBottomOnFloor(Rectangle boundingBox);
bool isOnUpperFloor(Rectangle boundingBox);
bool isOnLowerFloor(Rectangle boundingBox);
bool isOnLadder(Rectangle boundingBox);
void onAsteroidMined();
bool isSpaceshipPartActivated(SpaceshipPart* part);
void shootTurretOfSpaceship();
Action getPossibleActionFromSpaceship(Object* astronaut);
#endif

View file

@ -0,0 +1,16 @@
#include "sprites.h"
#include <avr/eeprom.h>
// AUTO-GENERATED
const uint16_t small_character_moving[4][5][1] EEMEM = {{{0x606},{0x1f1d},{0xf0b},{0x1f1d},{0x606}},{{0x606},{0x1f1d},{0xf0b},{0xf0d},{0x1616}},{{0x606},{0x1f1d},{0xf0b},{0x1f1d},{0x606}},{{0x606},{0xf0d},{0x1f1b},{0x1f1d},{0x606}}};
const uint16_t spaceship_idle[1][36][3] EEMEM = {{{0x404,0x1414,0x1010},{0x404,0x3e3e,0x1010},{0xceca,0xffff,0x3828},{0xee6a,0xff8a,0x3929},{0xff3b,0xff09,0x7f6f},{0xff91,0xff08,0x7f44},{0xff51,0xff08,0x7f44},{0xff31,0xff08,0x7f44},{0xff1f,0xff08,0x7f7c},{0xf111,0xff08,0x4744},{0xf010,0xff08,0x704},{0xf010,0xfff8,0x707},{0xf010,0xff28,0x705},{0xf010,0xfff8,0x707},{0xf010,0xff08,0x704},{0xf010,0xff08,0x704},{0xf010,0xff08,0x704},{0xf010,0xff08,0x704},{0xf414,0xff08,0x1714},{0xf414,0xff08,0x1714},{0xfc1c,0xff08,0x1f1c},{0xfc1c,0xff08,0x1f1c},{0xf818,0xff08,0xf0c},{0xf010,0xff08,0x704},{0xf010,0xff08,0x704},{0xf010,0xff08,0x704},{0xf010,0xff0c,0x704},{0xf010,0xff0a,0x704},{0xf0b0,0xff1f,0x706},{0xe020,0xff1c,0x302},{0xe060,0xff1c,0x303},{0xc0c0,0xff9c,0x101},{0x8080,0xffdd,0x0},{0x0,0x7f7f,0x0},{0x0,0x1c1c,0x0},{0x0,0x808,0x0}}};
const uint16_t table[1][3][1] EEMEM = {{{0x707},{0x101},{0x101}}};
const uint16_t exhaust[1][5][1] EEMEM = {{{0x404},{0xe0e},{0xe0e},{0x1f1f},{0x404}}};
const uint16_t small_asteroid[7][8][1] EEMEM = {{{0x1c1c},{0x7e7e},{0xfef2},{0xfffb},{0xffff},{0xffdf},{0x7e7e},{0x3c3c}},{{0x0},{0x3c3c},{0x7e72},{0x7e7a},{0x7e7e},{0x7e5e},{0x3c3c},{0x0}},{{0x0},{0x0},{0x3030},{0x7878},{0x7c7c},{0x7c5c},{0x3838},{0x0}},{{0x0},{0x0},{0x3030},{0x3838},{0x3838},{0x1010},{0x0},{0x0}},{{0x0},{0x3030},{0x4848},{0x4444},{0x4444},{0x2828},{0x1010},{0x0}},{{0x2828},{0x8484},{0x8080},{0x0},{0x8282},{0x4444},{0x2828},{0x0}},{{0x202},{0x0},{0x0},{0x0},{0x0},{0x0},{0x8181},{0x4242}}};
const uint16_t stars[3][3][1] EEMEM = {{{0x202},{0x707},{0x202}},{{0x505},{0x202},{0x505}},{{0x0},{0x303},{0x303}}};
const uint16_t heart_blinking[2][7][1] EEMEM = {{{0x606},{0x909},{0x1111},{0x2222},{0x1111},{0x909},{0x606}},{{0x606},{0xf0f},{0x1f1f},{0x3e3e},{0x1f1f},{0xf0f},{0x606}}};
const uint16_t beds[1][8][1] EEMEM = {{{0x707},{0xc0c},{0x2424},{0x2424},{0x2424},{0x2424},{0x2424},{0x2424}}};
const uint16_t turret_controller[1][7][1] EEMEM = {{{0x3030},{0x808},{0xf0f},{0x101},{0x101},{0x101},{0x101}}};

View file

@ -0,0 +1,19 @@
#ifndef SPRITES_H
#define SPRITES_H
#include <avr/io.h>
// AUTO-GENERATED
const uint16_t small_character_moving[4][5][1];
const uint16_t spaceship_idle[1][36][3];
const uint16_t table[1][3][1];
const uint16_t exhaust[1][5][1];
const uint16_t small_asteroid[7][8][1];
const uint16_t stars[3][3][1];
const uint16_t heart_blinking[2][7][1];
const uint16_t beds[1][8][1];
const uint16_t turret_controller[1][7][1];
#endif