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