diff --git a/.gitignore b/.gitignore index 27aa5e8..e8d9acd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ **/.vs **/bin .DS_Store -christmas diff --git a/christmas/AdAstra.atsln b/christmas/AdAstra.atsln new file mode 100644 index 0000000..14ee877 --- /dev/null +++ b/christmas/AdAstra.atsln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Atmel Studio Solution File, Format Version 11.00 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{54F91283-7BC4-4236-8FF9-10F437C3AD48}") = "AdAstra", "AdAstra\AdAstra.cproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|AVR = Release|AVR + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR + {DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/christmas/AdAstra/AdAstra.componentinfo.xml b/christmas/AdAstra/AdAstra.componentinfo.xml new file mode 100644 index 0000000..52c412d --- /dev/null +++ b/christmas/AdAstra/AdAstra.componentinfo.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/christmas/AdAstra/AdAstra.cproj b/christmas/AdAstra/AdAstra.cproj new file mode 100644 index 0000000..6553768 --- /dev/null +++ b/christmas/AdAstra/AdAstra.cproj @@ -0,0 +1,396 @@ + + + + 2.0 + 7.0 + com.Atmel.AVRGCC8.C + dce6c7e3-ee26-4d79-826b-08594b9ad897 + ATtiny85 + none + Executable + C + $(MSBuildProjectName) + .elf + $(MSBuildProjectDirectory)\$(Configuration) + TestProject + TestProject + TestProject + Native + false + false + false + true + 0x20000000 + + false + exception_table + 2 + 0 + 0 + + + + + + + + + + + + + + com.atmel.avrdbg.tool.ispmk2 + 000200212345 + 0x1E930B + ISP + 125000 + + + + 125000 + + ISP + + com.atmel.avrdbg.tool.ispmk2 + 000200212345 + AVRISP mkII + + + + + + + + + com.atmel.avrdbg.tool.simulator + + + Simulator + + + + + + -mmcu=attiny85 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\gcc\dev\attiny85" + True + True + True + True + False + True + True + True + + + NDEBUG + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + ../src/macros + + + Optimize for size (-Os) + True + True + True + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + + + + + ad_astra + .elf + + + + + -mmcu=attiny85 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\gcc\dev\attiny85" + True + True + True + True + False + True + True + + + DEBUG + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + + + Optimize (-O1) + True + True + Default (-g2) + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + + + Default (-Wa,-g) + + + + + + + -mmcu=attiny85 -B "%24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\gcc\dev\attiny85" + True + True + True + True + True + False + True + True + + + NDEBUG + UART_ENABLED + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + + + Optimize for size (-Os) + True + True + True + True + True + + + libm + + + + + %24(PackRepoDir)\atmel\ATtiny_DFP\1.3.229\include + + + + + ad_astra + .elf + bin\WithUART\ + + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/christmas/AdAstra/AdAstra.xml b/christmas/AdAstra/AdAstra.xml new file mode 100644 index 0000000..121a635 --- /dev/null +++ b/christmas/AdAstra/AdAstra.xml @@ -0,0 +1,86 @@ + + + + + + + Device + Startup + + + Atmel + 1.3.0 + C:/Program Files (x86)\Atmel\Studio\7.0\Packs + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.3.229\include + + include + C + + + include + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.3.229\include\avr\iotn85.h + + header + C + RcYmivGpgsCGGCzeWAIjcA== + + include/avr/iotn85.h + + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.3.229\templates\main.c + template + source + C Exe + 9ptzGqB00V1zM0TC00KMag== + + templates/main.c + Main file (.c) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.3.229\templates\main.cpp + template + source + C Exe + YXFphlh0CtZJU+ebktABgQ== + + templates/main.cpp + Main file (.cpp) + + + + C:/Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATtiny_DFP\1.3.229\gcc\dev\attiny85 + + libraryPrefix + GCC + + + gcc/dev/attiny85 + + + + + ATtiny_DFP + C:/Program Files (x86)/Atmel/Studio/7.0/Packs/atmel/ATtiny_DFP/1.3.229/Atmel.ATtiny_DFP.pdsc + 1.3.229 + true + ATtiny85 + + + + Resolved + Fixed + true + + + \ No newline at end of file diff --git a/christmas/AdAstra/src/driver/display/display.c b/christmas/AdAstra/src/driver/display/display.c new file mode 100644 index 0000000..a1b6259 --- /dev/null +++ b/christmas/AdAstra/src/driver/display/display.c @@ -0,0 +1,137 @@ +#include "display.h" + +#include + +#include "bitwise.h" +#include "math.h" +#include "../../hardware_access/hardware_access.h" + + +static uint8_t const configuration[] PROGMEM = { + 0xD5, 0xF0, // set clock frequency + 0x8D, 0x14, // enable charge pump + 0x20, 0x00, // horizontal addressing mode + 0xD6, 0x01, // 2 times vertical zoom + 0x22, 0x00, 0x03, // only draw to the top half of the screen + 0xAF // display on +}; + +static struct { + uint8_t compositingBuffer[DISPLAY_WIDTH_IN_PIXELS]; + Rectangle compositingWindow; + DrawFunction drawEverything; + bool wasIntersection; +} display; + +static inline void commandMode() { + setOutputPin(DISPLAY_DC_OUTPUT_PIN, false); +} + +static inline void dataMode() { + setOutputPin(DISPLAY_DC_OUTPUT_PIN, true); +} + +void setDisplayContrast(uint8_t value) { + commandMode(); + sendByteOnSPI(0x81); + sendByteOnSPI(value); + dataMode(); +} + +void turnDisplayOnOff(bool shouldBeOn) { + commandMode(); + sendByteOnSPI(0x8D | shouldBeOn); // set charge pump on/off + sendByteOnSPI(0x10 | shouldBeOn << 2); // set charge pump on/off + sendByteOnSPI(0xAE | shouldBeOn); // turn display sleep on/off + dataMode(); +} + +void initializeDisplay(DrawFunction drawEverything) { + setOutputPin(DISPLAY_RESET_OUTPUT_PIN, false); + for (volatile uint8_t i = 0; i != 255; i++) + ; + // some time has to elapse before the next line gets called, + // otherwise the display wont turn on + setOutputPin(DISPLAY_RESET_OUTPUT_PIN, true); + + for (uint8_t i = 0; i < sizeof(configuration); i++) { + sendByteOnSPI(pgm_read_byte(configuration + i)); + } + + display.drawEverything = drawEverything; + setDisplayContrast(255); + dataMode(); +} + +void startIntersectionTest(Rectangle compositingWindow) { + display.wasIntersection = false; + display.compositingWindow = compositingWindow; +} + +bool endIntersectionTest() { + for (uint8_t x = 0; x < DISPLAY_WIDTH_IN_PIXELS; x++) { + display.compositingBuffer[x] = 0; + } + return display.wasIntersection; +} + +void drawFrame() { + display.compositingWindow = DEFAULT_COMPOSITING_WINDOW; + for (display.compositingWindow.position.y = 0; display.compositingWindow.position.y < DISPLAY_HEIGHT_IN_PIXELS; display.compositingWindow.position.y += 8) { + display.drawEverything(display.compositingWindow); + + for (uint8_t x = 0; x < DISPLAY_WIDTH_IN_PIXELS; x++) { + sendByteOnSPI(display.compositingBuffer[x]); + sendByteOnSPI(display.compositingBuffer[x]); + display.compositingBuffer[x] = 0; + } + } +} + +static void compositePixelColumn(uint8_t x, uint8_t invertedMask, uint8_t fill) { + if (display.compositingBuffer[x] & fill) { + display.wasIntersection = true; + } + display.compositingBuffer[x] = (display.compositingBuffer[x] & (~invertedMask)) | fill; +} + +void drawBitmapFromProgMem(Rectangle boundingBox, uint16_t const bitmap[boundingBox.size.x][(boundingBox.size.y + 7) / 8], bool isMirrored) { + boundingBox.position = substract(boundingBox.position, display.compositingWindow.position); + + uint8_t spriteY = max(0, -boundingBox.position.y); + uint8_t spriteYByte = spriteY >> 3; + + for (uint8_t x = max(0, -boundingBox.position.x); x < boundingBox.size.x && boundingBox.position.x + x < DISPLAY_WIDTH_IN_PIXELS; x++) { + uint8_t spriteX = isMirrored ? boundingBox.size.x - x - 1 : x; + uint16_t currentPixelColumn = pgm_read_word(&bitmap[spriteX][spriteYByte]); + + uint8_t fill, invertedMask; + if (boundingBox.position.y >= 0) { + fill = (currentPixelColumn & 0x00FF) << boundingBox.position.y; + invertedMask = currentPixelColumn >> 8 << boundingBox.position.y; + } else { + uint16_t lowerPixelColumn = spriteYByte + 1 < (boundingBox.size.y + 7) / 8 ? pgm_read_word(&bitmap[spriteX][spriteYByte + 1]) : 0; + uint8_t shift = spriteY % 8; + uint8_t inverseShift = 8 - shift; + + fill = ((currentPixelColumn & 0x00FF) >> shift) | ((lowerPixelColumn & 0x0FF) << inverseShift); + invertedMask = (currentPixelColumn >> 8 >> shift) | (lowerPixelColumn >> 8 << inverseShift); + } + + compositePixelColumn(boundingBox.position.x + x, invertedMask, fill); + } +} + +void drawFilledRectangle(Rectangle box, uint8_t invertedMask, uint8_t fill) { + box.position = substract(box.position, display.compositingWindow.position); + + uint8_t upperGapHeight = min(8, max(0, box.position.y)); + uint8_t lowerGapHeight = min(8, max(0, 8 - (box.position.y + box.size.y))); + + uint8_t actualFill = (fill >> lowerGapHeight) & (fill << upperGapHeight); + uint8_t actualInvertedMask = (invertedMask >> lowerGapHeight) & (invertedMask << upperGapHeight); + + for (uint8_t x = max(0, box.position.x); x < box.position.x + box.size.x && x < DISPLAY_WIDTH_IN_PIXELS; x++) { + compositePixelColumn(x, actualInvertedMask, actualFill); + } +} diff --git a/christmas/AdAstra/src/driver/display/display.h b/christmas/AdAstra/src/driver/display/display.h new file mode 100644 index 0000000..37681e1 --- /dev/null +++ b/christmas/AdAstra/src/driver/display/display.h @@ -0,0 +1,74 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include + +#include "../../util/rectangle/rectangle.h" + + +/* + SPI driver for D096-12864 +*/ + +#define DISPLAY_RESET_OUTPUT_PIN PB0 +#define DISPLAY_DC_OUTPUT_PIN PB4 + +// A two-times downscaling is used for greater performance +#define DISPLAY_WIDTH_IN_PIXELS 64 +#define DISPLAY_HEIGHT_IN_PIXELS 32 + +// To easily access the window size +#define WINDOW ((Rectangle){(Vec2){0, 0}, (Vec2){DISPLAY_WIDTH_IN_PIXELS, DISPLAY_HEIGHT_IN_PIXELS}}) + +// To conserve RAM, drawing is done in chunks of DEFAULT_COMPOSITING_WINDOW size +// instead of buffering the whole display and writing it out at once +#define DEFAULT_COMPOSITING_WINDOW ((Rectangle){(Vec2){0, 0}, (Vec2){DISPLAY_WIDTH_IN_PIXELS, 8}}) + +typedef void (*DrawFunction)(Rectangle); + +// Call DrawFunction n times after a call to drawFrame has been made +void initializeDisplay(DrawFunction drawEverything); + +// Set a the brightness of the display +// Value can be any number between 0 and 255. +void setDisplayContrast(uint8_t value); + +// To conserve program memory, pixel based intersection test +// is implemented here. +// Calling draw functions after calling startIntersectionTest +// will set a wasIntersection bit appropriately. + +// Clear buffer and wasIntersection bit +void startIntersectionTest(); + +// Make the display go to / return from sleep +void turnDisplayOnOff(bool shouldBeOn); + +// Return wasIntersection bit +bool endIntersectionTest(); + +// Initiate a draw sequence +void drawFrame(); + +// Draw a sprite of size boundingBox.size at boundingBox.position from bitmap +// if isMirrored then mirror around the vertical axis +// Bitmap's data is interpreted the following way: +/* + Each 16 bit word corresponds to an 8 pixel high column. + (These columns are laid out horizontally from left to right. Unfortunately, + the display uses this addressing mode.) The higher 8 bits of the word is the + inverted mask and the lower 8 bits are the fill bits. + newPixelColumn = oldPixelColumn & ~invertedMask | fill; + + This seemingly weird layout is used to take advantage of SIMD operations + and speed up the drawing process significantly. +*/ +void drawBitmapFromProgMem(Rectangle boundingBox, uint16_t const bitmap[boundingBox.size.x][(boundingBox.size.y + 7) / 8], bool isMirrored); + +// Draw a one byte repeated texture covering the parameter box +// for a white rectangle use these arguments: invertedMask: don't care, fill: 0xFF +// for a black rectangle use these arguments: invertedMask: 0xFF, fill: 0x00 +void drawFilledRectangle(Rectangle box, uint8_t invertedMask, uint8_t fill); + +#endif diff --git a/christmas/AdAstra/src/driver/infra/infra.c b/christmas/AdAstra/src/driver/infra/infra.c new file mode 100644 index 0000000..0459e78 --- /dev/null +++ b/christmas/AdAstra/src/driver/infra/infra.c @@ -0,0 +1,144 @@ +#include "infra.h" + +#include +#include + +#include "bitwise.h" +#include "../../hardware_access/hardware_access.h" + + +// (0.5625 + (0.5625 + 1.6875) / 2) / 1000 / timer interval +#define MEAN_OF_0_1_BIT_TIMES 53 +// 9 / 2 / 1000 / timer interval +#define MAYBE_ONE_CHECK_TIME 141 +// some large value +#define TIMEOUT 254 + +typedef enum { + idle, maybeLeadingOne, leadingOneConfirmed, activeFirstBit, active, timingOut +} ProtocolState; + +typedef enum { + noMatch, foundStartingByte, significantByte, waitingForEndOfCommand, waitingForRepeat +} CommandState; + +static struct { + uint8_t current; + uint8_t bitPosition; + ProtocolState protocolState; + CommandState commandState; + OnCommandReceived onCommandReceived; +} infra; + + +static void saveCurrentByte() { + uint8_t byte = infra.current; + infra.current = 0; + infra.bitPosition = 0; + + if (byte == INFRA_ADDRESS) { + infra.commandState = foundStartingByte; + return; + } + + switch (infra.commandState) { + case foundStartingByte: + infra.commandState = significantByte; + break; + case significantByte: + infra.onCommandReceived(byte); + infra.commandState = waitingForEndOfCommand; + break; + case waitingForEndOfCommand: + if (byte == 0) { + infra.commandState = waitingForRepeat; + } + break; + case waitingForRepeat: + if (byte == 0) { + infra.onCommandReceived(REPEAT_CODE); + } + break; + default: + break; + } +} + +static void saveBit(uint8_t bit) { + infra.current <<= 1; + infra.current |= bit; + if (++infra.bitPosition == 8) { + saveCurrentByte(); + } +} + +static inline uint8_t isIrOff() { + return getBit(PINB, IR_PIN); +} + +static inline uint8_t isIrOn() { + return !isIrOff(); +} + +ISR(PCINT0_vect) { + switch (infra.protocolState) { + case idle: + if (isIrOn()) { + enableTimerB(MAYBE_ONE_CHECK_TIME); + infra.protocolState = maybeLeadingOne; + } + break; + case maybeLeadingOne: + infra.protocolState = idle; + break; + case leadingOneConfirmed: + if (isIrOff()) { + infra.protocolState = activeFirstBit; + } + break; + case activeFirstBit: + case timingOut: + if (isIrOn()) { + infra.protocolState = active; + enableTimerB(MEAN_OF_0_1_BIT_TIMES); + } + break; + case active: + if (isIrOn()) { + saveBit(1); + enableTimerB(MEAN_OF_0_1_BIT_TIMES); + } + break; + } +} + +ISR(TIM0_COMPB_vect) { + switch (infra.protocolState) { + case maybeLeadingOne: + if (isIrOn()) { + infra.protocolState = leadingOneConfirmed; + } + disableTimerB(); + break; + case active: + saveBit(0); + infra.protocolState = timingOut; + enableTimerB(TIMEOUT); + break; + case timingOut: + infra.protocolState = idle; + saveCurrentByte(); + disableTimerB(); + break; + default: + disableTimerB(); + break; + } +} + +void initializeInfra(OnCommandReceived onCommandReceived) { + setBit(PORTB, IR_PIN); // enable pull-up + setBit(PCMSK, IR_PIN); // specific pin change interrupt enable + setBit(GIMSK, PCIE); // global on pin change interrupt enable; + infra.onCommandReceived = onCommandReceived; +} diff --git a/christmas/AdAstra/src/driver/infra/infra.h b/christmas/AdAstra/src/driver/infra/infra.h new file mode 100644 index 0000000..312d3e5 --- /dev/null +++ b/christmas/AdAstra/src/driver/infra/infra.h @@ -0,0 +1,24 @@ +#ifndef INFRA_H +#define INFRA_H + +#include +#include + + +/* + Custom NEC implementation using a TSOP4838 +*/ + +#define INFRA_ADDRESS 255 +#define IR_PIN PB3 +#define REPEAT_CODE 1 + +typedef void (*OnCommandReceived)(uint8_t); +typedef void (*OnReceiveStarted)(); + +// Initialize infra and call onCommandReceived with every received byte +// Call onCommandReceived with the argument REPEAT_CODE if a repeat code +// has been received. +void initializeInfra(OnCommandReceived onCommandReceived); + +#endif diff --git a/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.c b/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.c new file mode 100644 index 0000000..6d43090 --- /dev/null +++ b/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.c @@ -0,0 +1,120 @@ +#include "redundant_storage.h" + +#include "../../hardware_access/hardware_access.h" + +#define offsetof(type, member) __builtin_offsetof (type, member) + +#define OBJECT_VALIDITY_MASK (0b00111111) +#define IS_USING_PAGE_A_BIT 6 +#define IS_VALID_BIT 7 +#define METADATA_REDUNDANCY 4 + +typedef struct { + uint8_t redundantBufferA[REDUNDANT_BUFFER_SIZE]; + uint8_t redundantBufferB[REDUNDANT_BUFFER_SIZE]; + uint8_t metadata[METADATA_REDUNDANCY]; +} MemoryLayout; + +static volatile struct { + MemoryLayout buffer; + uint8_t saveIndex; + uint8_t* savePointer; + uint8_t loadIndex; + uint8_t* loadPointer; + uint8_t validMetadata; + bool isEEPROMWriting; +} storage; + +static inline bool isUsingPageA() { + return getBit(storage.validMetadata, IS_USING_PAGE_A_BIT); +} + +bool isValid() { + return getBit(storage.validMetadata, IS_VALID_BIT); +} + +static inline void fixMetadata() { + uint8_t a = storage.buffer.metadata[0]; + uint8_t b = storage.buffer.metadata[1]; + uint8_t c = storage.buffer.metadata[2]; + uint8_t d = storage.buffer.metadata[3]; + + if (a == b || a == c || a == d) { + storage.validMetadata = a; + } else if (b == c || b == d) { + storage.validMetadata = b; + } else { + storage.validMetadata = c; + } +} + +void initializeRedundantStorage() { + for (uint8_t i = 0; i < sizeof(MemoryLayout); i++) { + ((uint8_t*)&storage.buffer)[i] = loadSavedByteEEPROM(i); + } + + fixMetadata(); +} + +void invalidateEEPROM() { + for (uint8_t i = 0; i < sizeof(MemoryLayout); i++) { + ((uint8_t*)&storage.buffer)[i] = 0; + } + + storage.validMetadata = 0; +} + +void onEEPROMWriteFinished(__attribute__((unused)) uint8_t* _) { + storage.isEEPROMWriting = false; +} + +bool startSchedulingObjectsForSaving() { + if (storage.isEEPROMWriting) { + return false; + } + + toggleBit(storage.validMetadata, IS_USING_PAGE_A_BIT); + storage.validMetadata &= ~OBJECT_VALIDITY_MASK; + storage.saveIndex = 0; + storage.savePointer = isUsingPageA() ? + storage.buffer.redundantBufferA + : storage.buffer.redundantBufferB; + + return true; +} + +void scheduleNextObjectForSave(uint8_t* object, uint8_t size) { + setBit(storage.validMetadata, storage.saveIndex); + storage.saveIndex++; + for (uint8_t i = 0; i < size; i++) { + *storage.savePointer = object[i]; + storage.savePointer++; + } +} + +void saveScheduledObjects() { + setBit(storage.validMetadata, IS_VALID_BIT); + + for (uint8_t i = 0; i < METADATA_REDUNDANCY; i++) { + storage.buffer.metadata[i] = storage.validMetadata; + } + + storage.isEEPROMWriting = true; + asyncWriteEEPROM((uint8_t*)&storage.buffer, onEEPROMWriteFinished); +} + +bool loadNextObject(uint8_t* holder, uint8_t size) { + if (storage.loadIndex++ == 0) { + storage.loadPointer = isUsingPageA() ? + storage.buffer.redundantBufferA + : storage.buffer.redundantBufferB; + } + + if (getBit(storage.validMetadata, storage.loadIndex - 1)) { + for (uint8_t i = 0; i < size; i++) { + holder[i] = *storage.loadPointer++; + } + return true; + } + return false; +} diff --git a/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.h b/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.h new file mode 100644 index 0000000..18117a7 --- /dev/null +++ b/christmas/AdAstra/src/driver/redundant_storage/redundant_storage.h @@ -0,0 +1,22 @@ +#ifndef REDUNDANT_STORAGE_H +#define REDUNDANT_STORAGE_H + +#include +#include + + +#define REDUNDANT_BUFFER_SIZE 24 + +void initializeRedundantStorage(); + +bool isValid(); +void invalidateEEPROM(); + +bool startSchedulingObjectsForSaving(); +void scheduleNextObjectForSave(uint8_t* object, uint8_t size); +void saveScheduledObjects(); + +// Returns is loaded object valid +bool loadNextObject(uint8_t* holder, uint8_t size); + +#endif diff --git a/christmas/AdAstra/src/driver/sleep/sleep.c b/christmas/AdAstra/src/driver/sleep/sleep.c new file mode 100644 index 0000000..b6f0adf --- /dev/null +++ b/christmas/AdAstra/src/driver/sleep/sleep.c @@ -0,0 +1,39 @@ +#include "sleep.h" + +#include +#include +#include + +#include "bitwise.h" +#include "../../hardware_access/hardware_access.h" + + +#define TICKS_IN_MILISECOND 31 + +volatile int8_t milisecondsSinceFrameStart; + +void startFrameLoop(FrameFunction function, uint8_t frameLengthInMilliseconds) { + sleep_enable(); + + uint8_t previousFrameTime = 0; + while (function(previousFrameTime)) { + previousFrameTime = milisecondsSinceFrameStart; + + while (milisecondsSinceFrameStart < frameLengthInMilliseconds) { + clearBit(MCUCR, SM1); // idle mode + sleep_cpu(); + } + + milisecondsSinceFrameStart = 0; + } +} + +void powerOff() { + setBit(MCUCR, SM1); // power-down mode + sleep_cpu(); +} + +ISR(TIM0_COMPA_vect) { + milisecondsSinceFrameStart++; + enableTimerA(TICKS_IN_MILISECOND); +} \ No newline at end of file diff --git a/christmas/AdAstra/src/driver/sleep/sleep.h b/christmas/AdAstra/src/driver/sleep/sleep.h new file mode 100644 index 0000000..fe6a7ea --- /dev/null +++ b/christmas/AdAstra/src/driver/sleep/sleep.h @@ -0,0 +1,16 @@ +#ifndef SLEEP_H +#define SLEEP_H + +#include +#include + +// FrameFunction gets previousFrameTime (in milliseconds) as argument +typedef bool (*FrameFunction)(uint8_t); + +// Shut down the machine +void powerOff(); + +// Call function every frameLengthInMilliseconds while it returns true +void startFrameLoop(FrameFunction function, uint8_t frameLengthInMilliseconds); + +#endif diff --git a/christmas/AdAstra/src/hardware_access/eeprom/eeprom.c b/christmas/AdAstra/src/hardware_access/eeprom/eeprom.c new file mode 100644 index 0000000..dac0795 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/eeprom/eeprom.c @@ -0,0 +1,75 @@ +#include "eeprom.h" + +#include +#include +#include + +#include "bitwise.h" + + +uint8_t memory[STORAGE_SIZE] EEMEM; + +static volatile struct { + uint8_t* buffer; + uint8_t positon; + bool isWriting; + OnEEPROMFinished onFinished; +} eeprom; + +uint8_t loadByteEEPROM(uint8_t* address) { + while(getBit(EECR, EEPE)) {} + + EEAR = (uint16_t)address; + setBit(EECR, EERE); + return EEDR; +} + +uint8_t loadSavedByteEEPROM(uint8_t address) { + return loadByteEEPROM(memory + address); +} + +uint16_t loadWordEEPROM(uint16_t const* address) { + uint16_t high = ((uint16_t)loadByteEEPROM((uint8_t*)address + 1)) << 8; + uint16_t low = (uint16_t)loadByteEEPROM((uint8_t*)address); + return high | low; +} + +static inline void saveByteEEPROM(uint8_t* address, uint8_t byte) { + if (byte != loadByteEEPROM(address)) { + EEDR = byte; + setBit(EECR, EEMPE); + setBit(EECR, EEPE); + } +} + +void enableWritingEEPROM() { + modifyBit(EECR, EERIE, eeprom.isWriting); +} + +void disableWritingEEPROM() { + clearBit(EECR, EERIE); // clear eeprom ready interrupt enable +} + +bool asyncWriteEEPROM(uint8_t* buffer, OnEEPROMFinished onFinished) { + if (eeprom.isWriting) { + return false; + } + + eeprom.buffer = buffer; + eeprom.onFinished = onFinished; + eeprom.positon = 0; + eeprom.isWriting = true; + setBit(EECR, EERIE); // set eeprom ready interrupt + + return true; +} + +ISR(EE_RDY_vect) { + saveByteEEPROM(memory + eeprom.positon, eeprom.buffer[eeprom.positon]); + + if (++eeprom.positon == STORAGE_SIZE) { + clearBit(EECR, EERIE); + eeprom.isWriting = false; + eeprom.onFinished(eeprom.buffer); + } +} diff --git a/christmas/AdAstra/src/hardware_access/eeprom/eeprom.h b/christmas/AdAstra/src/hardware_access/eeprom/eeprom.h new file mode 100644 index 0000000..60627c8 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/eeprom/eeprom.h @@ -0,0 +1,15 @@ +#ifndef EEPROM_H +#define EEPROM_H + +#include + + +#define STORAGE_SIZE 52 + +typedef void (*OnEEPROMFinished)(uint8_t*); + +inline void initializeEEPROM() { + EECR = 0; // atomic write +} + +#endif diff --git a/christmas/AdAstra/src/hardware_access/hardware_access.c b/christmas/AdAstra/src/hardware_access/hardware_access.c new file mode 100644 index 0000000..ebc04ea --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/hardware_access.c @@ -0,0 +1,12 @@ +#include "hardware_access.h" + +#include + + +void initializeHardwareAccess() { + sei(); + initializePowerSaving(); + initializeSPI(); + initializeTiming(); + initializeOutputPins(); +} diff --git a/christmas/AdAstra/src/hardware_access/hardware_access.h b/christmas/AdAstra/src/hardware_access/hardware_access.h new file mode 100644 index 0000000..e236e4c --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/hardware_access.h @@ -0,0 +1,39 @@ +#ifndef HARDWARE_ACCESS_H +#define HARDWARE_ACCESS_H + +#include +#include +#include "bitwise.h" + +#include "power_saving/power_saving.h" +#include "spi/spi.h" +#include "timing/timing.h" +#include "output_pins/output_pins.h" + + +/* + This module contains the lowest level functions to manipulate the hardware. + You only have to include this header file which serves as a facade. + The sub-modules' implementation can be freely changed as long as they + still implement these functions. +*/ + +// Initialize every hardware element at once +void initializeHardwareAccess(); + +// Enable interrupt OCCRA for TIMER0 with a modulo of triggerInterruptInXTicks +void enableTimerA(uint8_t triggerInterruptInXTicks); + +// Enable interrupt OCCRB for TIMER0B with a modulo of triggerInterruptInXTicks +void enableTimerB(uint8_t triggerInterruptInXTicks); +void disableTimerB(); + +// Send a single byte on the built-in SPI interface +// The transfer is done in a serial manner to achieve +// greater throughput. +void sendByteOnSPI(uint8_t byte); + +// Set the value of an output pin +void setOutputPin(uint8_t id, bool value); + +#endif diff --git a/christmas/AdAstra/src/hardware_access/output_pins/output_pins.c b/christmas/AdAstra/src/hardware_access/output_pins/output_pins.c new file mode 100644 index 0000000..aa8f07e --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/output_pins/output_pins.c @@ -0,0 +1,11 @@ +#include "output_pins.h" +#include "bitwise.h" + +#include +#include + + +void setOutputPin(uint8_t id, bool value) { + setBit(DDRB, id); + modifyBit(PORTB, id, value); +} diff --git a/christmas/AdAstra/src/hardware_access/output_pins/output_pins.h b/christmas/AdAstra/src/hardware_access/output_pins/output_pins.h new file mode 100644 index 0000000..8d3b8a5 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/output_pins/output_pins.h @@ -0,0 +1,7 @@ +#ifndef GPIO_OUTPUT_PINS_H +#define GPIO_OUTPUT_PINS_H + + +inline void initializeOutputPins() {} + +#endif diff --git a/christmas/AdAstra/src/hardware_access/power_saving/power_saving.h b/christmas/AdAstra/src/hardware_access/power_saving/power_saving.h new file mode 100644 index 0000000..a4abd37 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/power_saving/power_saving.h @@ -0,0 +1,13 @@ +#ifndef POWER_SAVING_H +#define POWER_SAVING_H + +#include +#include "bitwise.h" + + +inline void initializePowerSaving() { + setBit(ACSR, ACD); // disable ADC to save power + PRR = BV(PRTIM1) | BV(PRADC); // disable power to timer1 and ADC +} + +#endif diff --git a/christmas/AdAstra/src/hardware_access/spi/spi.c b/christmas/AdAstra/src/hardware_access/spi/spi.c new file mode 100644 index 0000000..32a31dd --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/spi/spi.c @@ -0,0 +1,30 @@ +#include "spi.h" + +#include + +#define SWAP_BIT (BV(USIWM0) | BV(USICLK) | BV(USITC) | BV(USICS1)) + + +void sendByteOnSPI(uint8_t byte) { + USIDR = byte; + + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; + USICR = SWAP_BIT; +} diff --git a/christmas/AdAstra/src/hardware_access/spi/spi.h b/christmas/AdAstra/src/hardware_access/spi/spi.h new file mode 100644 index 0000000..9eca28a --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/spi/spi.h @@ -0,0 +1,15 @@ +#ifndef SPI_H +#define SPI_H + +#include +#include "bitwise.h" + + +#define DO_PIN PB1 +#define USCK_PIN PB2 + +inline void initializeSPI() { + DDRB |= BV(DO_PIN) | BV(USCK_PIN); // set pin directions for MOSI and SCK +} + +#endif diff --git a/christmas/AdAstra/src/hardware_access/timing/timing.c b/christmas/AdAstra/src/hardware_access/timing/timing.c new file mode 100644 index 0000000..4069557 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/timing/timing.c @@ -0,0 +1,17 @@ +#include "timing.h" +#include "bitwise.h" + + +void enableTimerA(uint8_t triggerInterruptInXTicks) { + OCR0A = TCNT0 + triggerInterruptInXTicks; +} + +void enableTimerB(uint8_t triggerInterruptInXTicks) { + setBit(TIFR, OCF0B); + OCR0B = TCNT0 + triggerInterruptInXTicks; + setBit(TIMSK, OCIE0B); +} + +void disableTimerB() { + clearBit(TIMSK, OCIE0B); +} diff --git a/christmas/AdAstra/src/hardware_access/timing/timing.h b/christmas/AdAstra/src/hardware_access/timing/timing.h new file mode 100644 index 0000000..758f0c1 --- /dev/null +++ b/christmas/AdAstra/src/hardware_access/timing/timing.h @@ -0,0 +1,13 @@ +#ifndef TIMING_H +#define TIMING_H + +#include +#include "bitwise.h" + + +inline void initializeTiming() { + TCCR0B = BV(CS02); // CLK / 256 + setBit(TIMSK, OCIE0A); +} + +#endif diff --git a/christmas/AdAstra/src/macros/bitwise.h b/christmas/AdAstra/src/macros/bitwise.h new file mode 100644 index 0000000..e8eb48a --- /dev/null +++ b/christmas/AdAstra/src/macros/bitwise.h @@ -0,0 +1,11 @@ +#ifndef BITWISE_H +#define BITWISE_H + +#define BV(x) (1 << (x)) +#define modifyBit(P, B, V) ((P) = ((P) & ~BV(B)) | ((V) << B)) +#define setBit(P, B) ((P) |= BV(B)) +#define clearBit(P, B) ((P) &= ~BV(B)) +#define toggleBit(P, B) ((P) ^= BV(B)) +#define getBit(P, B) (((P) & BV(B)) >> (B)) + +#endif diff --git a/christmas/AdAstra/src/macros/math.h b/christmas/AdAstra/src/macros/math.h new file mode 100644 index 0000000..5347d60 --- /dev/null +++ b/christmas/AdAstra/src/macros/math.h @@ -0,0 +1,9 @@ +#ifndef MATH_H +#define MATH_H + + +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) > (b) ? (b) : (a)) +#define abs(a, b) ((a) > 0 ? (a) : (-a)) + +#endif diff --git a/christmas/AdAstra/src/macros/null.h b/christmas/AdAstra/src/macros/null.h new file mode 100644 index 0000000..2090ad6 --- /dev/null +++ b/christmas/AdAstra/src/macros/null.h @@ -0,0 +1,3 @@ +#ifndef NULL +#define NULL ((void*)0) +#endif diff --git a/christmas/AdAstra/src/main.c b/christmas/AdAstra/src/main.c new file mode 100644 index 0000000..77760e4 --- /dev/null +++ b/christmas/AdAstra/src/main.c @@ -0,0 +1,14 @@ +#include "mediator/mediator.h" + + +// Stemming from the module oriented nature of the project +// there is a module responsible for setting up and orchestrating +// the other modules. +// +// From the main function we only have to instruct the mediator to +// do its job. + +int main(void) { + setupConnections(); + startGame(); +} diff --git a/christmas/AdAstra/src/mediator/mediator.c b/christmas/AdAstra/src/mediator/mediator.c new file mode 100644 index 0000000..2ee5689 --- /dev/null +++ b/christmas/AdAstra/src/mediator/mediator.c @@ -0,0 +1,74 @@ +#include "mediator.h" + +#include + +#include "../hardware_access/hardware_access.h" +#include "../objects/object_container/object_container.h" +#include "../objects/event_generator/event_generator.h" +#include "../objects/commands/commands.h" +#include "../objects/ai/ai.h" +#include "../driver/display/display.h" +#include "../driver/infra/infra.h" +#include "../driver/sleep/sleep.h" + +#define TARGET_FRAME_DURATION 20 // ms +#define DEATH_SCREEN_LENGTH 50 // frames +#define SAVE_INTERVAL 50 // every x frames + +static struct { + uint8_t contrast; + uint8_t framesSinceLastSave; + uint8_t deathDownCounter; + uint8_t receivedWakeUpBitCount; +} state = { + .contrast = 255 +}; + +static inline bool handleDeathAnimation() { + if (isSpaceshipDestroyed()) { + setDisplayContrast(state.contrast * (state.deathDownCounter / DEATH_SCREEN_LENGTH)); + if (state.deathDownCounter-- == 0) { + return false; + }; + } + return true; +} + +static bool frameFunction(uint8_t previousFrameTime) { + tickObjects(previousFrameTime); + handleCommands(); + handleAI(); + generateEvents(); + drawFrame(); + return handleDeathAnimation(); +} + +static inline void initializeGame() { + state.deathDownCounter = DEATH_SCREEN_LENGTH; + setDisplayContrast(state.contrast); + initializeBackground(); + initializeObjectContainer(); +} + +void setupConnections() { + initializeHardwareAccess(); + initializeInfra(addCommand); + initializeDisplay(drawObjects); +} + +void startGame() { + while (true) { + initializeGame(); + startFrameLoop(frameFunction, TARGET_FRAME_DURATION); + } +} + +void changeDisplayContrast(int8_t by) { + if (by < 0) { + state.contrast = (state.contrast < -by) ? 0 : (state.contrast + by); + } else { + state.contrast = (state.contrast > 255 - by) ? 255 : (state.contrast + by); + } + + setDisplayContrast(state.contrast); +} diff --git a/christmas/AdAstra/src/mediator/mediator.h b/christmas/AdAstra/src/mediator/mediator.h new file mode 100644 index 0000000..5dba541 --- /dev/null +++ b/christmas/AdAstra/src/mediator/mediator.h @@ -0,0 +1,23 @@ +#ifndef MEDIATOR_H +#define MEDIATOR_H + +#include + + +// Setup the drivers, and business layer objects and their relations +// It is kind of a very basic dependency injection. +void setupConnections(); + +// Start drawing frames and ticking objects +void startGame(); + +// Make the machine go to sleep +void handleOff(); + +// Increase or decrease the contrast (brightness) of the display +// by the given value +// The contrast can be any number between 0 and 255. +// The function automatically clamps the contrast. +void changeDisplayContrast(int8_t by); + +#endif diff --git a/christmas/AdAstra/src/objects/ai/ai.c b/christmas/AdAstra/src/objects/ai/ai.c new file mode 100644 index 0000000..d894545 --- /dev/null +++ b/christmas/AdAstra/src/objects/ai/ai.c @@ -0,0 +1,235 @@ +#include "ai.h" + +#include +#include + +#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*, uint8_t); +typedef void (*Execution)(Object*); + +static uint8_t timeSinceLastAction; + + +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, direction); + } +} + +static void makeAiAstronautDoAction(Object* astronaut) { + if (timeSinceLastAction > AI_ACTION_INTERVAL) { + makeAstronautDoAction(astronaut); + timeSinceLastAction = 0; + } +} + +static bool shouldControlTurret( + __attribute__((unused)) Rectangle* boundingBox, + __attribute__((unused)) Object* astronaut, + __attribute__((unused)) uint8_t astronautId +) { + return getCountOf(&Asteroid) > 0; +} + +static void executeControlTurret(Object* astronaut) { + if (getIntersectingObjectOfType( + (Rectangle){ + add(TURRET_POSITION, spaceshipObject->position), + (Vec2){63, 1} + }, + &Asteroid + )) { + makeAiAstronautDoAction(astronaut); + }; +} + +static bool shouldControlSpaceship( + __attribute__((unused)) Rectangle* boundingBox, + __attribute__((unused)) Object* astronaut, + uint8_t astronautId +) { + return getCountOf(&Asteroid) > 0 + && astronautId == 1 + && spaceshipObject->as.spaceship.healthLoss < MAX_HEALTH / 4 * 3; +} + +static void executeControlSpaceship(Object* astronaut) { + carefullyMoveSpaceship( + astronaut, + add( + getCenter(getBoundingBox(getFirstOfType(&Asteroid))), + (Vec2){-30, 0} + ) + ); +} + +static bool shouldRepairSpaceship(Rectangle* boundingBox, Object* astronaut, __attribute__((unused)) uint8_t astronautId) { + return ( + ( + areIntersecting(*boundingBox, getBoundingBox(astronaut)) && + spaceshipObject->as.spaceship.healthLoss > 0 + ) || + spaceshipObject->as.spaceship.healthLoss >= MAX_HEALTH / 2 + ); +} + +static bool shouldCenterSpaceship(Rectangle* boundingBox, Object* astronaut, __attribute__((unused)) uint8_t astronautId) { + return ( + !actions[1].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, + __attribute__((unused)) uint8_t astronautId +) { + return true; +} + +static void executeSocialize(Object* astronaut) {} + +static AIAction actions[AI_ACTION_COUNT] = { + (AIAction) { + .predicate = shouldRepairSpaceship, + .execution = makeAiAstronautDoAction, + .spaceshipPart = spaceshipParts + BEDS_INDEX, + .onlyOneAstronautCanDoIt = true, + .deltaCenter = {2, 0} + }, + (AIAction) { + .predicate = shouldControlSpaceship, + .execution = executeControlSpaceship, + .spaceshipPart = spaceshipParts + COMMAND_PANEL_INDEX, + .onlyOneAstronautCanDoIt = true, + .deltaCenter = {-3, 0} + }, + (AIAction) { + .predicate = shouldControlTurret, + .execution = executeControlTurret, + .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() { + timeSinceLastAction++; + uint8_t astronautCount = 0; + 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) { + astronautCount++; + 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, astronautCount) + ) { + if (!areIntersecting(boundingBox, getBoundingBox(objects + i))) { + carefullyMoveAstronaut(objects + i, add(getCenter(boundingBox), currentAction->deltaCenter)); + } else { + currentAction->execution(objects + i); + } + currentAction->isSomeoneDoingThis = true; + break; + } + } + } + } +} diff --git a/christmas/AdAstra/src/objects/ai/ai.h b/christmas/AdAstra/src/objects/ai/ai.h new file mode 100644 index 0000000..37c7e6e --- /dev/null +++ b/christmas/AdAstra/src/objects/ai/ai.h @@ -0,0 +1,12 @@ +#ifndef AI_H +#define AI_H + +// Between AI astronauts do actions +// there has to be at least this many frames +#define AI_ACTION_INTERVAL 30 + +// If there are non player controlled astronauts +// control them according to some basic rule set +void handleAI(); + +#endif diff --git a/christmas/AdAstra/src/objects/commands/commands.c b/christmas/AdAstra/src/objects/commands/commands.c new file mode 100644 index 0000000..8399e6c --- /dev/null +++ b/christmas/AdAstra/src/objects/commands/commands.c @@ -0,0 +1,71 @@ +#include "commands.h" + +#include "../../objects/object_container/object_container.h" +#include "../../objects/types/spaceship/spaceship.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 reset: + destroySpaceship(); + commands.previous = noAction; + break; + case action: + makeAstronautDoAction(character); + commands.previous = noAction; + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/christmas/AdAstra/src/objects/commands/commands.h b/christmas/AdAstra/src/objects/commands/commands.h new file mode 100644 index 0000000..5649986 --- /dev/null +++ b/christmas/AdAstra/src/objects/commands/commands.h @@ -0,0 +1,38 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + + +#include + +// 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, + reset = 157, + 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 diff --git a/christmas/AdAstra/src/objects/event_generator/event_generator.c b/christmas/AdAstra/src/objects/event_generator/event_generator.c new file mode 100644 index 0000000..e782e9a --- /dev/null +++ b/christmas/AdAstra/src/objects/event_generator/event_generator.c @@ -0,0 +1,80 @@ +#include "event_generator.h" + +#include +#include + +#include "null.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 "../types/text/text.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 >= hasHalfCrew) || + (getCountOf(&Astronaut) == 2 && spaceshipObject->as.spaceship.progress >= hasTable) + ) && + 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); +} diff --git a/christmas/AdAstra/src/objects/event_generator/event_generator.h b/christmas/AdAstra/src/objects/event_generator/event_generator.h new file mode 100644 index 0000000..8c27ea5 --- /dev/null +++ b/christmas/AdAstra/src/objects/event_generator/event_generator.h @@ -0,0 +1,16 @@ +#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 + +void createObjects(); + +// Generate asteroids and astronaut randomly based on a set of conditions +void generateEvents(); + +#endif diff --git a/christmas/AdAstra/src/objects/object.c b/christmas/AdAstra/src/objects/object.c new file mode 100644 index 0000000..781adef --- /dev/null +++ b/christmas/AdAstra/src/objects/object.c @@ -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)}; +} diff --git a/christmas/AdAstra/src/objects/object.h b/christmas/AdAstra/src/objects/object.h new file mode 100644 index 0000000..700afdd --- /dev/null +++ b/christmas/AdAstra/src/objects/object.h @@ -0,0 +1,78 @@ +#ifndef OBJECT_H +#define OBJECT_H + +#include +#include + +#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/text/text.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 _text_t text; +} object_specific_data_t; + + +struct _object_t { + Prototype const* prototype; + Vec2 position; + object_specific_data_t as; +}; + + +// A simplified object intended for persisting its data +// without saving its 16 bit long reference to its prototype +typedef struct { + Vec2 position; + object_specific_data_t as; +} DTO; + + +// 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 diff --git a/christmas/AdAstra/src/objects/object_container/object_container.c b/christmas/AdAstra/src/objects/object_container/object_container.c new file mode 100644 index 0000000..65178bb --- /dev/null +++ b/christmas/AdAstra/src/objects/object_container/object_container.c @@ -0,0 +1,61 @@ +#include "object_container.h" + +#include "../../driver/redundant_storage/redundant_storage.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); + } + + createObjects(); +} diff --git a/christmas/AdAstra/src/objects/object_container/object_container.h b/christmas/AdAstra/src/objects/object_container/object_container.h new file mode 100644 index 0000000..1d3cac9 --- /dev/null +++ b/christmas/AdAstra/src/objects/object_container/object_container.h @@ -0,0 +1,58 @@ +#ifndef OBJECT_HANDLER_H +#define OBJECT_HANDLER_H + +#include +#include + +#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 +// and create (or load) the starting objects +void initializeObjectContainer(); + +#endif diff --git a/christmas/AdAstra/src/objects/prototype.c b/christmas/AdAstra/src/objects/prototype.c new file mode 100644 index 0000000..0b64dfa --- /dev/null +++ b/christmas/AdAstra/src/objects/prototype.c @@ -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);*/ +} diff --git a/christmas/AdAstra/src/objects/prototype.h b/christmas/AdAstra/src/objects/prototype.h new file mode 100644 index 0000000..50cfdfc --- /dev/null +++ b/christmas/AdAstra/src/objects/prototype.h @@ -0,0 +1,28 @@ +#ifndef PROTOTYPE_H +#define PROTOTYPE_H + +#include + +#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 diff --git a/christmas/AdAstra/src/objects/types/asteroid/asteroid.c b/christmas/AdAstra/src/objects/types/asteroid/asteroid.c new file mode 100644 index 0000000..fd16802 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/asteroid/asteroid.c @@ -0,0 +1,51 @@ +#include "asteroid.h" + +#include + +#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, __attribute__((unused)) 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, Rectangle __attribute__((unused)) 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, +}; diff --git a/christmas/AdAstra/src/objects/types/asteroid/asteroid.h b/christmas/AdAstra/src/objects/types/asteroid/asteroid.h new file mode 100644 index 0000000..176c7f6 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/asteroid/asteroid.h @@ -0,0 +1,25 @@ +#ifndef ASTEROID_H +#define ASTEROID_H + +#include +#include + +#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 diff --git a/christmas/AdAstra/src/objects/types/astronaut/astronaut.c b/christmas/AdAstra/src/objects/types/astronaut/astronaut.c new file mode 100644 index 0000000..fd69a19 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/astronaut/astronaut.c @@ -0,0 +1,137 @@ +#include "astronaut.h" + +#include +#include +#include "bitwise.h" +#include "../../../driver/display/display.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((Vec2){direction.x, 0}); + moveSpaceship((Vec2){0, direction.y}); + } 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* text; + switch (getPossibleActionFromSpaceship(astronaut)) { + case shootTurret: + shootTurretOfSpaceship(); + break; + case showLove: + text = getEmptyObjectSpace(); + if (text != NULL) { + if (!createText(text)) { + speedUpText(); + } + } + 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 +}; diff --git a/christmas/AdAstra/src/objects/types/astronaut/astronaut.h b/christmas/AdAstra/src/objects/types/astronaut/astronaut.h new file mode 100644 index 0000000..0a18852 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/astronaut/astronaut.h @@ -0,0 +1,37 @@ +#ifndef ASTRONAUT_H +#define ASTRONAUT_H + +#include "../../prototype.h" +#include + + +#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 50 + +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 diff --git a/christmas/AdAstra/src/objects/types/background/background.c b/christmas/AdAstra/src/objects/types/background/background.c new file mode 100644 index 0000000..f7a37dc --- /dev/null +++ b/christmas/AdAstra/src/objects/types/background/background.c @@ -0,0 +1,71 @@ +#include "background.h" + +#include +#include + +#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, __attribute__((unused)) 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 +}; diff --git a/christmas/AdAstra/src/objects/types/background/background.h b/christmas/AdAstra/src/objects/types/background/background.h new file mode 100644 index 0000000..a78acef --- /dev/null +++ b/christmas/AdAstra/src/objects/types/background/background.h @@ -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 \ No newline at end of file diff --git a/christmas/AdAstra/src/objects/types/bullet/bullet.c b/christmas/AdAstra/src/objects/types/bullet/bullet.c new file mode 100644 index 0000000..4237410 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/bullet/bullet.c @@ -0,0 +1,40 @@ +#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, __attribute__((unused)) 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, Rectangle __attribute__((unused)) compositingWindow) { + drawFilledRectangle((Rectangle){bullet->position, BULLET_SIZE}, 0, 0xFF); +} + +const Prototype Bullet PROGMEM = { + .tick = tick, + .draw = draw, + .size = BULLET_SIZE, +}; diff --git a/christmas/AdAstra/src/objects/types/bullet/bullet.h b/christmas/AdAstra/src/objects/types/bullet/bullet.h new file mode 100644 index 0000000..bfed188 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/bullet/bullet.h @@ -0,0 +1,16 @@ +#ifndef BULLET_H +#define BULLET_H + +#include "../../prototype.h" +#include + + +#define BULLET_SIZE ((Vec2){5, 1}) + + +const Prototype Bullet; +struct _bullet_t { + bool wereIntersectingInThePreviousFrame; +}; + +#endif diff --git a/christmas/AdAstra/src/objects/types/spaceship/spaceship.c b/christmas/AdAstra/src/objects/types/spaceship/spaceship.c new file mode 100644 index 0000000..2ce819f --- /dev/null +++ b/christmas/AdAstra/src/objects/types/spaceship/spaceship.c @@ -0,0 +1,225 @@ +#include "spaceship.h" + +#include + +#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, 6}, {5, 5}}, + tree[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, __attribute__((unused)) 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); +} + +bool isSpaceshipDestroyed() { + return spaceshipObject->as.spaceship.healthLoss >= MAX_HEALTH; +} + +void destroySpaceship() { + spaceshipObject->as.spaceship.healthLoss = MAX_HEALTH; +} + +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, +}; diff --git a/christmas/AdAstra/src/objects/types/spaceship/spaceship.h b/christmas/AdAstra/src/objects/types/spaceship/spaceship.h new file mode 100644 index 0000000..2070914 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/spaceship/spaceship.h @@ -0,0 +1,75 @@ +#ifndef SPACESHIP_H +#define SPACESHIP_H + + +#include "../../prototype.h" +#include "../../../util/rectangle/rectangle.h" +#include "../astronaut/astronaut.h" + +#include + +#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 = 2, + hasTurret = 5, + hasHalfCrew = 8, + hasTable = 12 +} 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); + +bool isSpaceshipDestroyed(); +void destroySpaceship(); + +void shootTurretOfSpaceship(); +Action getPossibleActionFromSpaceship(Object* astronaut); + +#endif diff --git a/christmas/AdAstra/src/objects/types/sprites.c b/christmas/AdAstra/src/objects/types/sprites.c new file mode 100644 index 0000000..a64db74 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/sprites.c @@ -0,0 +1,16 @@ +#include "sprites.h" + +#include + + +// AUTO-GENERATED + +const uint16_t beds[1][8][1] PROGMEM = {{{0x707},{0xc0c},{0x2424},{0x2424},{0x2424},{0x2424},{0x2424},{0x2424}}}; +const uint16_t exhaust[1][5][1] PROGMEM = {{{0x404},{0xe0e},{0xe0e},{0x1f1f},{0x404}}}; +const uint16_t font[50][4][1] PROGMEM = {{{0xff78},{0xff14},{0xff14},{0xff78}},{{0xff78},{0xff14},{0xff14},{0xff79}},{{0xff7c},{0xff54},{0xff54},{0xff28}},{{0xff38},{0xff44},{0xff44},{0xff28}},{{0xff7c},{0xff44},{0xff44},{0xff38}},{{0xff7c},{0xff54},{0xff54},{0xff44}},{{0xff7c},{0xff54},{0xff55},{0xff44}},{{0xff7c},{0xff14},{0xff14},{0xff04}},{{0xff38},{0xff44},{0xff54},{0xff30}},{{0xff7c},{0xff10},{0xff10},{0xff7c}},{{0xff44},{0xff7c},{0xff44},{0xff44}},{{0xff44},{0xff7c},{0xff45},{0xff44}},{{0xff20},{0xff40},{0xff40},{0xff3c}},{{0xff7c},{0xff10},{0xff28},{0xff44}},{{0xff7c},{0xff40},{0xff40},{0xff40}},{{0xff7c},{0xff0c},{0xff1c},{0xff7c}},{{0xff7c},{0xff08},{0xff30},{0xff7c}},{{0xff38},{0xff44},{0xff44},{0xff38}},{{0xff38},{0xff44},{0xff45},{0xff38}},{{0xff39},{0xff44},{0xff44},{0xff39}},{{0xff39},{0xff45},{0xff45},{0xff39}},{{0xff7c},{0xff14},{0xff14},{0xff08}},{{0xff18},{0xff24},{0xff24},{0xff58}},{{0xff7c},{0xff14},{0xff14},{0xff68}},{{0xff48},{0xff54},{0xff54},{0xff24}},{{0xff04},{0xff7c},{0xff04},{0xff04}},{{0xff7c},{0xff40},{0xff40},{0xff7c}},{{0xff7c},{0xff40},{0xff41},{0xff7c}},{{0xff7d},{0xff40},{0xff40},{0xff7d}},{{0xff7d},{0xff41},{0xff41},{0xff7d}},{{0xff3c},{0xff40},{0xff40},{0xff3c}},{{0xff7c},{0xff60},{0xff70},{0xff7c}},{{0xff44},{0xff38},{0xff38},{0xff44}},{{0xff1c},{0xff70},{0xff10},{0xff1c}},{{0xff64},{0xff54},{0xff54},{0xff4c}},{{0xff7c},{0xff44},{0xff44},{0xff7c}},{{0xff08},{0xff44},{0xff7c},{0xff40}},{{0xff4c},{0xff64},{0xff54},{0xff4c}},{{0xff44},{0xff54},{0xff54},{0xff7c}},{{0xff3c},{0xff20},{0xff70},{0xff20}},{{0xff5c},{0xff54},{0xff54},{0xff24}},{{0xff7c},{0xff54},{0xff54},{0xff74}},{{0xff04},{0xff64},{0xff14},{0xff0c}},{{0xff7c},{0xff54},{0xff54},{0xff7c}},{{0xff5c},{0xff54},{0xff54},{0xff7c}},{{0xff00},{0xffc0},{0xff00},{0xff00}},{{0xff40},{0xff00},{0xff00},{0xff00}},{{0xff5c},{0xff00},{0xff00},{0xff00}},{{0xff08},{0xffa4},{0xff14},{0xff08}},{{0xff00},{0xff00},{0xff00},{0xff00}}}; +const uint16_t small_asteroid[7][8][1] PROGMEM = {{{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 small_character_moving[4][5][1] PROGMEM = {{{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] PROGMEM = {{{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 stars[3][3][1] PROGMEM = {{{0x202},{0x707},{0x202}},{{0x505},{0x202},{0x505}},{{0x0},{0x303},{0x303}}}; +const uint16_t tree[1][5][1] PROGMEM = {{{0x808},{0xa0a},{0x1515},{0xa0a},{0x808}}}; +const uint16_t turret_controller[1][7][1] PROGMEM = {{{0x3030},{0x808},{0xf0f},{0x101},{0x101},{0x101},{0x101}}}; diff --git a/christmas/AdAstra/src/objects/types/sprites.h b/christmas/AdAstra/src/objects/types/sprites.h new file mode 100644 index 0000000..5cbe71d --- /dev/null +++ b/christmas/AdAstra/src/objects/types/sprites.h @@ -0,0 +1,19 @@ +#ifndef SPRITES_H +#define SPRITES_H + +#include + + +// AUTO-GENERATED + +const uint16_t beds[1][8][1]; +const uint16_t exhaust[1][5][1]; +const uint16_t font[50][4][1]; +const uint16_t small_asteroid[7][8][1]; +const uint16_t small_character_moving[4][5][1]; +const uint16_t spaceship_idle[1][36][3]; +const uint16_t stars[3][3][1]; +const uint16_t tree[1][5][1]; +const uint16_t turret_controller[1][7][1]; + +#endif diff --git a/christmas/AdAstra/src/objects/types/text/text.c b/christmas/AdAstra/src/objects/types/text/text.c new file mode 100644 index 0000000..1a4f05a --- /dev/null +++ b/christmas/AdAstra/src/objects/types/text/text.c @@ -0,0 +1,114 @@ +#include "text.h" + +#include + +#include "../asteroid/asteroid.h" +#include "../spaceship/spaceship.h" +#include "../sprites.h" +#include "../../object.h" +#include "../../object_container/object_container.h" +#include "null.h" +#include "math.h" +#include "../../../driver/display/display.h" + +#include +#define STRING_TERMINATOR 255 +#define CHARACTER_WIDTH 4 + +static bool canCreateNext = true; +static int8_t previousTextIndex = -1; +static bool isSpedUp = false; + +bool createText(Object* object) { + if (!canCreateNext) { + return false; + } + + createObject(&Text, object); + object->as.text.xPosition = DISPLAY_WIDTH_IN_PIXELS; + object->as.text.hasSetCanCreate = false; + object->position = (Vec2){0, 0}; + previousTextIndex++; + isSpedUp = false; + uint8_t textCount = 0; + for (uint16_t i = 0; i < 512; i++) { + if (textCount == previousTextIndex) { + if (eeprom_read_byte(i) == STRING_TERMINATOR) { + previousTextIndex = 0; + object->as.text.textStartIndex = 0; + } else { + object->as.text.textStartIndex = i; + } + canCreateNext = false; + return; + } + + if (eeprom_read_byte(i) == STRING_TERMINATOR) { + textCount++; + } + } + + return true; +} + +void speedUpText() { + isSpedUp = true; +} + +static uint8_t getTextLength(Object* text) { + for (uint8_t i = 0; i < 255; i++) { + if (eeprom_read_byte(text->as.text.textStartIndex + i) == STRING_TERMINATOR) { + return i; + } + } + + return 0; +} + +static void tick(Object* text, __attribute__((unused)) uint8_t previousFrameTime) { + if (++text->as.text.timeSinceLastShift > 2 || isSpedUp) { + text->as.text.xPosition--; + text->as.text.timeSinceLastShift = 0; + } + + if ( + !text->as.text.hasSetCanCreate + && getTextLength(text) * (CHARACTER_WIDTH + 1) + text->as.text.xPosition < DISPLAY_WIDTH_IN_PIXELS - CHARACTER_WIDTH - 1 + ) { + canCreateNext = true; + text->as.text.hasSetCanCreate = true; + } + + if (getTextLength(text) * (CHARACTER_WIDTH + 1) < -text->as.text.xPosition) { + clearObject(text); + } +} + +static void draw(Object* text, Rectangle __attribute__((unused)) compositingWindow) { + uint8_t indexOfFirstVisible = max(0, -text->as.text.xPosition / (CHARACTER_WIDTH + 1)); + for (uint8_t i = indexOfFirstVisible; i < indexOfFirstVisible + DISPLAY_WIDTH_IN_PIXELS / (CHARACTER_WIDTH + 1) + 2; i++) { + uint8_t letter = eeprom_read_byte(text->as.text.textStartIndex + i); + if (letter == STRING_TERMINATOR) { + return; + } + + drawBitmapFromProgMem( + (Rectangle){(Vec2){text->as.text.xPosition + i * (CHARACTER_WIDTH + 1), 0}, (Vec2){4, 8}}, + font[letter], + false + ); + + drawFilledRectangle( + (Rectangle){(Vec2){text->as.text.xPosition + i * (CHARACTER_WIDTH + 1) + CHARACTER_WIDTH, 0}, + (Vec2){1, 8}}, + 0xFF, + 0x00 + ); + } +} + +const Prototype Text PROGMEM = { + .tick = tick, + .draw = draw, + .size = TEXT_SIZE, +}; diff --git a/christmas/AdAstra/src/objects/types/text/text.h b/christmas/AdAstra/src/objects/types/text/text.h new file mode 100644 index 0000000..9847c3c --- /dev/null +++ b/christmas/AdAstra/src/objects/types/text/text.h @@ -0,0 +1,21 @@ +#ifndef TEXT_H +#define TEXT_H + +#include "../../prototype.h" +#include + + +#define TEXT_SIZE ((Vec2){64, 8}) + +const Prototype Text; +struct _text_t { + int16_t xPosition; + uint16_t textStartIndex; + uint8_t timeSinceLastShift; + bool hasSetCanCreate; +}; + +bool createText(Object* position); +void speedUpText(); + +#endif diff --git a/christmas/AdAstra/src/objects/types/text/texts.c b/christmas/AdAstra/src/objects/types/text/texts.c new file mode 100644 index 0000000..f586358 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/text/texts.c @@ -0,0 +1,8 @@ +#include "texts.h" + +#include + + +// AUTO-GENERATED + +const uint8_t texts[385] EEMEM = {24, 34, 10, 0, 45, 2, 0, 14, 1, 34, 24, 47, 255, 2, 17, 14, 4, 17, 8, 49, 13, 0, 23, 1, 3, 24, 17, 16, 33, 25, 49, 13, 11, 30, 1, 16, 17, 13, 47, 255, 30, 10, 24, 34, 17, 16, 25, 49, 15, 1, 24, 25, 49, 10, 24, 49, 24, 34, 5, 23, 5, 25, 16, 6, 13, 49, 15, 17, 16, 4, 0, 16, 10, 46, 255, 9, 0, 2, 1, 23, 49, 23, 19, 30, 10, 4, 5, 2, 2, 45, 4, 5, 49, 0, 16, 16, 1, 14, 49, 24, 29, 23, 29, 2, 2, 49, 30, 17, 14, 25, 49, 0, 34, 49, 5, 8, 33, 16, 33, 1, 23, 10, 49, 13, 0, 14, 0, 16, 4, 26, 16, 13, 46, 255, 19, 23, 19, 15, 49, 30, 17, 14, 25, 49, 25, 6, 8, 5, 4, 49, 15, 5, 8, 10, 24, 15, 5, 23, 16, 10, 46, 255, 0, 49, 13, 23, 5, 0, 25, 10, 30, 10, 25, 1, 24, 17, 4, 49, 6, 24, 49, 19, 24, 24, 34, 5, 24, 34, 5, 4, 5, 25, 25, 24, 6, 8, 5, 4, 49, 10, 16, 24, 21, 10, 23, 1, 14, 18, 46, 255, 25, 27, 14, 34, 1, 24, 49, 16, 6, 14, 13, 28, 14, 49, 24, 17, 13, 26, 16, 13, 49, 21, 6, 14, 4, 0, 13, 6, 21, 5, 49, 14, 5, 9, 5, 25, 16, 6, 14, 46, 255, 19, 24, 24, 34, 5, 24, 24, 6, 8, 6, 2, 5, 16, 45, 16, 6, 14, 13, 28, 14, 5, 4, 49, 13, 10, 3, 24, 10, 25, 49, 24, 34, 17, 15, 17, 23, 27, 2, 2, 49, 14, 5, 16, 16, 5, 49, 0, 34, 49, 6, 14, 5, 25, 46, 255, 23, 5, 15, 6, 14, 5, 15, 49, 5, 34, 25, 49, 25, 5, 49, 10, 24, 49, 25, 26, 4, 17, 4, 49, 15, 0, 8, 0, 4, 23, 18, 14, 46, 255, 30, 1, 23, 17, 15, 45, 9, 17, 8, 33, 49, 15, 10, 9, 0, 15, 0, 23, 0, 2, 2, 49, 25, 0, 14, 1, 14, 13, 17, 34, 34, 26, 16, 13, 46, 255, 0, 16, 4, 23, 10, 24, 255, 255, 255}; diff --git a/christmas/AdAstra/src/objects/types/text/texts.h b/christmas/AdAstra/src/objects/types/text/texts.h new file mode 100644 index 0000000..375ffc6 --- /dev/null +++ b/christmas/AdAstra/src/objects/types/text/texts.h @@ -0,0 +1,11 @@ +#ifndef TEXTS_H +#define TEXTS_H + +#include + + +// AUTO-GENERATED + +const uint8_t texts[385]; + +#endif diff --git a/christmas/AdAstra/src/util/random/random.c b/christmas/AdAstra/src/util/random/random.c new file mode 100644 index 0000000..50cce21 --- /dev/null +++ b/christmas/AdAstra/src/util/random/random.c @@ -0,0 +1,14 @@ +#include "random.h" + + +static uint16_t currentValue = SEED; + +static uint16_t getRandom16bitNumberModuloPrime() { + uint16_t bit = currentValue ^ (currentValue >> 2) ^ (currentValue >> 3) ^ (currentValue >> 5); + currentValue = (currentValue >> 1) | (bit << 15); + return currentValue % 1031; +} + +uint8_t getRandomNumber() { + return (uint8_t)(getRandom16bitNumberModuloPrime() ^ getRandom16bitNumberModuloPrime()); +} diff --git a/christmas/AdAstra/src/util/random/random.h b/christmas/AdAstra/src/util/random/random.h new file mode 100644 index 0000000..ea00ab8 --- /dev/null +++ b/christmas/AdAstra/src/util/random/random.h @@ -0,0 +1,14 @@ +#ifndef RANDOM_H +#define RANDOM_H + +#include + + +// Mustn't be zero, should be lower than 65535 +#define SEED 42 + +// Simple LFSR with some improvements to enhance distribution +// while maintaining short execution time +uint8_t getRandomNumber(); + +#endif diff --git a/christmas/AdAstra/src/util/rectangle/rectangle.c b/christmas/AdAstra/src/util/rectangle/rectangle.c new file mode 100644 index 0000000..552eb03 --- /dev/null +++ b/christmas/AdAstra/src/util/rectangle/rectangle.c @@ -0,0 +1,34 @@ +#include "rectangle.h" + + +bool areIntersecting(Rectangle r1, Rectangle r2) { + return ( + r1.position.x < r2.position.x + r2.size.x && + r1.position.x + r1.size.x > r2.position.x && + r1.position.y < r2.position.y + r2.size.y && + r1.position.y + r1.size.y > r2.position.y + ); +} + +bool isInside(Rectangle inner, Rectangle outer) { + return ( + outer.position.x <= inner.position.x && + inner.position.x + inner.size.x <= outer.position.x + outer.size.x && + outer.position.y <= inner.position.y && + inner.position.y + inner.size.y <= outer.position.y + outer.size.y + ); +} + +Vec2 getCenter(Rectangle r) { + return (Vec2) { + r.position.x + r.size.x / 2, + r.position.y + r.size.y / 2 + }; +} + +Rectangle translateRectangle(Rectangle r, Vec2 translate) { + return (Rectangle) { + add(r.position, translate), + r.size + }; +} diff --git a/christmas/AdAstra/src/util/rectangle/rectangle.h b/christmas/AdAstra/src/util/rectangle/rectangle.h new file mode 100644 index 0000000..7e94b07 --- /dev/null +++ b/christmas/AdAstra/src/util/rectangle/rectangle.h @@ -0,0 +1,26 @@ +#ifndef RECTANGLE_H +#define RECTANGLE_H + +#include + +#include "../vec2/vec2.h" + + +typedef struct { + Vec2 position; + Vec2 size; +} Rectangle; + + +bool areIntersecting(Rectangle r1, Rectangle r2); + +// Return true only if inner is fully inside of outer +bool isInside(Rectangle inner, Rectangle outer); + +// Return the geometrical middle point of the given rectangle +Vec2 getCenter(Rectangle r); + +// Return a new rectangle shifted by vector translate +Rectangle translateRectangle(Rectangle r, Vec2 translate); + +#endif diff --git a/christmas/AdAstra/src/util/vec2/vec2.c b/christmas/AdAstra/src/util/vec2/vec2.c new file mode 100644 index 0000000..4a8351b --- /dev/null +++ b/christmas/AdAstra/src/util/vec2/vec2.c @@ -0,0 +1,24 @@ +#include "vec2.h" + + +const Vec2 directions[] = { + [north] = (Vec2){0, -1}, + [west] = (Vec2){-1, 0}, + [south] = (Vec2){0, 1}, + [east] = (Vec2){1, 0} +}; + +Vec2 add(Vec2 v1, Vec2 v2) { + return (Vec2){v1.x + v2.x, v1.y + v2.y}; +} + +Vec2 substract(Vec2 v1, Vec2 v2) { + return (Vec2){v1.x - v2.x, v1.y - v2.y}; +} + +Vec2 clampVec2(Vec2 v) { + return (Vec2){ + v.x == 0 ? 0 : (v.x > 0 ? 1 : -1), + v.y == 0 ? 0 : (v.y > 0 ? 1 : -1) + }; +} diff --git a/christmas/AdAstra/src/util/vec2/vec2.h b/christmas/AdAstra/src/util/vec2/vec2.h new file mode 100644 index 0000000..a8cf263 --- /dev/null +++ b/christmas/AdAstra/src/util/vec2/vec2.h @@ -0,0 +1,26 @@ +#ifndef VEC2_H +#define VEC2_H + +#include + + +typedef struct { + int8_t x; + int8_t y; +} Vec2; + +typedef enum { + north, west, south, east +} Direction; + +// The array directions can be indexed by a Direction and +// it contains vectors pointing into that direction. +const Vec2 directions[4]; + +Vec2 add(Vec2 v1, Vec2 v2); +Vec2 substract(Vec2 v1, Vec2 v2); + +// Return new vector with ll components between -1 and 1 (inclusive) +Vec2 clampVec2(Vec2 vector); + +#endif diff --git a/christmas/Felhasználói dokumentáció.docx b/christmas/Felhasználói dokumentáció.docx new file mode 100644 index 0000000..83e1895 Binary files /dev/null and b/christmas/Felhasználói dokumentáció.docx differ diff --git a/christmas/Felhasználói dokumentáció.pdf b/christmas/Felhasználói dokumentáció.pdf new file mode 100644 index 0000000..df6ec15 Binary files /dev/null and b/christmas/Felhasználói dokumentáció.pdf differ diff --git a/christmas/text_generator/README.md b/christmas/text_generator/README.md new file mode 100644 index 0000000..1bbaaa3 --- /dev/null +++ b/christmas/text_generator/README.md @@ -0,0 +1,15 @@ +# Sprite generator + +## Install + +- Install Python 3.5 or later +- Execute `pip install -r requirements.txt` + +## Use + +- Using [Piskel](https://www.piskelapp.com/) + - draw the sprites + - export them in `zip` format (it's a .zip containing .png files) +- Place the files inside the [spritesheets](spritesheets) folder +- Execute `python main.py` +- The output is automatically placed in the [output](output) folder diff --git a/christmas/text_generator/input/.vscode/settings.json b/christmas/text_generator/input/.vscode/settings.json new file mode 100644 index 0000000..644c232 --- /dev/null +++ b/christmas/text_generator/input/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "spellright.language": [ + "en", + "hu" + ], + "spellright.documentTypes": [ + "markdown", + "latex", + "plaintext" + ], + "cSpell.enableFiletypes": [ + "!plaintext" + ] +} \ No newline at end of file diff --git a/christmas/text_generator/input/adam.txt b/christmas/text_generator/input/adam.txt new file mode 100644 index 0000000..499e415 --- /dev/null +++ b/christmas/text_generator/input/adam.txt @@ -0,0 +1,13 @@ +Szia, Ádám! +Boldog karácsonyt kívánok! +Viszont mást is szeretnék mondani. +Most egy kicsit még távolabb fogunk kerülni egymástól. +Persze biztos vagyok benne, hogy ezután is tartani fogjuk a kapcsolatot. +Viszont jó pillanatnak érzem a mostanit arra, hogy köszönetet mondjak. +Köszönöm az együtt töltött időnket. +Köszönöm, hogy más ember lettem melletted. +Ebben neked is részed volt. +Összességében, nélküled kicsit szomorúbb lenne az élet. +Remélem ezt te is tudod magadról. +Várom, hogy mihamarabb találkozzunk. +Andris \ No newline at end of file diff --git a/christmas/text_generator/input/apa.txt b/christmas/text_generator/input/apa.txt new file mode 100644 index 0000000..6d51cb4 --- /dev/null +++ b/christmas/text_generator/input/apa.txt @@ -0,0 +1,11 @@ +Szia, Apa! +Boldog karácsonyt kívánok! +Viszont mást is szeretnék mondani. +Szeretném megköszönni azt a számtalan segítséget, amit tőled kaptam. +Köszönöm, hogy mindig támogattál és motiváltál. +Köszönöm, hogy mindig mögöttem álltál. +Nélküled most biztosan nem tartanék itt. +Ha ezt nehezen is fejeztem ki, de mindig fontos voltál nekem. +Összességében, nélküled kicsit szomorúbb lenne az élet. +Remélem ezt te is tudod magadról. +Andris diff --git a/christmas/text_generator/input/balazs.txt b/christmas/text_generator/input/balazs.txt new file mode 100644 index 0000000..4918b23 --- /dev/null +++ b/christmas/text_generator/input/balazs.txt @@ -0,0 +1,11 @@ +Szia, Balázs! +Boldog karácsonyt kívánok! +Viszont mást is szeretnék mondani. +Habár rövidebb, de annál sűrűbb volt az egynyári kalandunk. +Öröm volt téged megismerni. +A kreativitásod és összeszedettséged inspiráló. +Túlzás nélkül sokunk példaképe lehetnél. +Összességében, nélküled kicsit szomorúbb lenne az élet. +Remélem ezt te is tudod magadról. +Várom, hogy mihamarabb találkozzunk. +Andris diff --git a/christmas/text_generator/input/oliver.txt b/christmas/text_generator/input/oliver.txt new file mode 100644 index 0000000..e5f20d7 --- /dev/null +++ b/christmas/text_generator/input/oliver.txt @@ -0,0 +1,12 @@ +Szia, Olivér! +Boldog karácsonyt kívánok! +Viszont mást is szeretnék mondani. +Jó pillanatnak érzem a mostanit arra, hogy köszönetet mondjak. +A barátságod, segítséged és humorod meghatározta az elmúlt éveimet. +Ha nem is gondolod így, de nagy hatással voltál rám. +Az elhivatottságod és kitartásod mindig inspirált. +Te vagy az a barát, akit mindenki szeretne. +Összességében, nélküled kicsit szomorúbb lenne az élet. +Remélem ezt te is tudod magadról. +Várom, hogy mihamarabb találkozzunk. +Andris diff --git a/christmas/text_generator/main.py b/christmas/text_generator/main.py new file mode 100644 index 0000000..24466a1 --- /dev/null +++ b/christmas/text_generator/main.py @@ -0,0 +1,121 @@ +import io +import os +import zipfile +from math import ceil +from sys import argv +from typing import Tuple + +from PIL import Image + +c_header = """#include "texts.h" + +#include + + +// AUTO-GENERATED + +""" + +h_header = """#ifndef TEXTS_H +#define TEXTS_H + +#include + + +// AUTO-GENERATED + +""" + +h_footer = """ +#endif +""" + +letters = [ + "a", + "á", + "b", + "c", + "d", + "e", + "é", + "f", + "g", + "h", + "i", + "í", + "j", + "k", + "l", + "m", + "n", + "o", + "ó", + "ö", + "ő", + "p", + "q", + "r", + "s", + "t", + "u", + "ú", + "ü", + "ű", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ",", + ".", + "!", + "?", + " ", +] + + +def generate(input_text: str) -> Tuple[str, str]: + indices = [] + for line in input_text.split("\n"): + for char in line.strip().lower().replace(", ", ","): + indices.append(letters.index(char)) + indices.append(255) + indices.append(255) + + if len(indices) > 512: + print(f"Input is too long ({len(indices)} > 512)") + exit() + + output_h = h_header + f"const uint8_t texts[{len(indices)}];\n" + h_footer + output_c = c_header + f"const uint8_t texts[{len(indices)}] EEMEM = {{" + + output_c += ", ".join(str(i) for i in indices) + output_c += "};\n" + + return output_h, output_c + + +if __name__ == "__main__": + if len(argv) == 1: + print("Specify an input file") + exit() + + with open(argv[1], "r", encoding="utf-8") as f: + input_text = f.read() + + dot_h, dot_c = generate(input_text) + + with open("output/texts.h", "w+") as f: + f.write(dot_h) + with open("output/texts.c", "w+") as f: + f.write(dot_c) diff --git a/christmas/text_generator/requirements.txt b/christmas/text_generator/requirements.txt new file mode 100644 index 0000000..1c12e1b --- /dev/null +++ b/christmas/text_generator/requirements.txt @@ -0,0 +1 @@ +Pillow==8.0.1