mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-21 23:19:02 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c5275247e | |||
| a0185d28eb | |||
| 2500df310f | |||
| 54f9ff116b | |||
| 47f231ad63 | |||
| f90d3fabce | |||
| 4e276cdc71 | |||
| 0eb03373b1 | |||
| d5ea0f0369 | |||
| 22cc311447 | |||
| 4fbba205ad | |||
| 7b91103118 | |||
| c0aa4a1738 | |||
| e26f77ee77 | |||
| f53262783a | |||
| b456c18008 | |||
| fa4b74fd6f | |||
| 18bc2cf11c |
@@ -1 +1 @@
|
||||
v2.0.2
|
||||
v2.1.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.1.0
|
||||
v2.2.0
|
||||
|
||||
@@ -301,7 +301,7 @@ AISRecentEntryDetailView::AISRecentEntryDetailView(NavigationView& nav) {
|
||||
ais::format::text(entry_.name),
|
||||
0,
|
||||
GeoPos::alt_unit::METERS,
|
||||
GeoPos::spd_unit::NONE,
|
||||
GeoPos::spd_unit::KNOTS,
|
||||
ais::format::latlon_float(entry_.last_position.latitude.normalized()),
|
||||
ais::format::latlon_float(entry_.last_position.longitude.normalized()),
|
||||
entry_.last_position.true_heading,
|
||||
@@ -324,7 +324,31 @@ AISRecentEntryDetailView& AISRecentEntryDetailView::operator=(const AISRecentEnt
|
||||
|
||||
void AISRecentEntryDetailView::update_position() {
|
||||
if (send_updates)
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground);
|
||||
geomap_view->update_position(ais::format::latlon_float(entry_.last_position.latitude.normalized()), ais::format::latlon_float(entry_.last_position.longitude.normalized()), (float)entry_.last_position.true_heading, 0, entry_.last_position.speed_over_ground > 1022 ? 0 : entry_.last_position.speed_over_ground);
|
||||
}
|
||||
|
||||
bool AISRecentEntryDetailView::add_map_marker(const AISRecentEntry& entry) {
|
||||
if (geomap_view && send_updates) {
|
||||
GeoMarker marker{};
|
||||
marker.lon = ais::format::latlon_float(entry.last_position.longitude.normalized());
|
||||
marker.lat = ais::format::latlon_float(entry.last_position.latitude.normalized());
|
||||
marker.angle = entry.last_position.true_heading;
|
||||
marker.tag = entry.call_sign.empty() ? to_string_dec_uint(entry.mmsi) : entry.call_sign;
|
||||
auto markerStored = geomap_view->store_marker(marker);
|
||||
return markerStored == MARKER_STORED;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void AISRecentEntryDetailView::update_map_markers(AISRecentEntries& entries) {
|
||||
if (geomap_view && send_updates) {
|
||||
geomap_view->clear_markers();
|
||||
for (const auto& entry : entries) {
|
||||
// if (entry.last_position.latitude.is_valid() && entry.last_position.longitude.is_valid()) {
|
||||
add_map_marker(entry);
|
||||
// }
|
||||
}
|
||||
update_position(); // to update view
|
||||
}
|
||||
}
|
||||
|
||||
void AISRecentEntryDetailView::focus() {
|
||||
@@ -415,14 +439,27 @@ AISAppView::AISAppView(NavigationView& nav)
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
audio::output::start();
|
||||
}
|
||||
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
AISAppView::~AISAppView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AISAppView::on_tick_second() {
|
||||
++timer_seconds;
|
||||
if (timer_seconds % 10 == 0) {
|
||||
if (recent_entry_detail_view.hidden()) return;
|
||||
recent_entry_detail_view.update_map_markers(recent);
|
||||
}
|
||||
}
|
||||
|
||||
void AISAppView::focus() {
|
||||
options_channel.focus();
|
||||
}
|
||||
@@ -461,6 +498,7 @@ void AISAppView::on_show_detail(const AISRecentEntry& entry) {
|
||||
recent_entry_detail_view.hidden(false);
|
||||
recent_entry_detail_view.set_entry(entry);
|
||||
recent_entry_detail_view.focus();
|
||||
recent_entry_detail_view.update_map_markers(recent);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -125,10 +125,14 @@ class AISRecentEntryDetailView : public View {
|
||||
void update_position();
|
||||
void focus() override;
|
||||
void paint(Painter&) override;
|
||||
bool add_map_marker(const AISRecentEntry& entry);
|
||||
void update_map_markers(AISRecentEntries& entries);
|
||||
|
||||
AISRecentEntryDetailView(const AISRecentEntryDetailView& Entry);
|
||||
AISRecentEntryDetailView& operator=(const AISRecentEntryDetailView& Entry);
|
||||
|
||||
GeoMapView* get_geomap_view() { return geomap_view; }
|
||||
|
||||
private:
|
||||
AISRecentEntry entry_{};
|
||||
|
||||
@@ -215,6 +219,8 @@ class AISAppView : public View {
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
SignalToken signal_token_tick_second{};
|
||||
uint8_t timer_seconds = 0;
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::AISPacket,
|
||||
@@ -229,6 +235,7 @@ class AISAppView : public View {
|
||||
void on_packet(const ais::Packet& packet);
|
||||
void on_show_list();
|
||||
void on_show_detail(const AISRecentEntry& entry);
|
||||
void on_tick_second();
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -67,6 +68,50 @@ uint64_t copy_mac_address_to_uint64(const uint8_t* macAddress) {
|
||||
return result;
|
||||
}
|
||||
|
||||
MAC_VENDOR_STATUS lookup_mac_vendor_status(const uint8_t* mac_address, std::string& vendor_name) {
|
||||
static bool db_checked = false;
|
||||
static bool db_exists = false;
|
||||
|
||||
if (!db_checked) {
|
||||
database db;
|
||||
database::MacAddressDBRecord dummy_record;
|
||||
int test_result = db.retrieve_macaddress_record(&dummy_record, "000000");
|
||||
db_exists = (test_result != DATABASE_NOT_FOUND);
|
||||
db_checked = true;
|
||||
}
|
||||
|
||||
if (!db_exists) {
|
||||
vendor_name = "macaddress.db not found";
|
||||
return MAC_DB_NOT_FOUND;
|
||||
}
|
||||
|
||||
database db;
|
||||
database::MacAddressDBRecord record;
|
||||
|
||||
// Convert MAC address to hex string
|
||||
std::string mac_hex = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Only need first 3 bytes for OUI
|
||||
mac_hex += to_string_hex(mac_address[i], 2);
|
||||
}
|
||||
|
||||
int result = db.retrieve_macaddress_record(&record, mac_hex);
|
||||
|
||||
if (result == DATABASE_RECORD_FOUND) {
|
||||
vendor_name = std::string(record.vendor_name);
|
||||
return MAC_VENDOR_FOUND;
|
||||
} else {
|
||||
vendor_name = "Unknown";
|
||||
return MAC_VENDOR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
std::string lookup_mac_vendor(const uint8_t* mac_address) {
|
||||
std::string vendor_name;
|
||||
lookup_mac_vendor_status(mac_address, vendor_name);
|
||||
return vendor_name;
|
||||
}
|
||||
|
||||
void reverse_byte_array(uint8_t* arr, int length) {
|
||||
int start = 0;
|
||||
int end = length - 1;
|
||||
@@ -150,8 +195,15 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line = to_string_mac_address(entry.packetData.macAddress, 6, false);
|
||||
}
|
||||
|
||||
std::string hitsStr;
|
||||
|
||||
if (!entry.informationString.empty()) {
|
||||
hitsStr = entry.informationString;
|
||||
} else {
|
||||
hitsStr = to_string_dec_int(entry.numHits);
|
||||
}
|
||||
|
||||
// Pushing single digit values down right justified.
|
||||
std::string hitsStr = to_string_dec_int(entry.numHits);
|
||||
int hitsDigits = hitsStr.length();
|
||||
uint8_t hits_spacing = 8 - hitsDigits;
|
||||
|
||||
@@ -165,7 +217,10 @@ void RecentEntriesTable<BleRecentEntries>::draw(
|
||||
line += pad_string_with_spaces(db_spacing) + dbStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
|
||||
Style row_style = (entry.vendor_status == MAC_VENDOR_FOUND) ? style : Style{style.font, style.background, Color::grey()};
|
||||
|
||||
painter.draw_string(target_rect.location(), row_style, line);
|
||||
}
|
||||
|
||||
BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const BleRecentEntry& entry)
|
||||
@@ -178,10 +233,14 @@ BleRecentEntryDetailView::BleRecentEntryDetailView(NavigationView& nav, const Bl
|
||||
&text_mac_address,
|
||||
&label_pdu_type,
|
||||
&text_pdu_type,
|
||||
&label_vendor,
|
||||
&text_vendor,
|
||||
&labels});
|
||||
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
nav.pop();
|
||||
@@ -370,6 +429,10 @@ void BleRecentEntryDetailView::paint(Painter& painter) {
|
||||
|
||||
void BleRecentEntryDetailView::set_entry(const BleRecentEntry& entry) {
|
||||
entry_ = entry;
|
||||
text_mac_address.set(to_string_mac_address(entry.packetData.macAddress, 6, false));
|
||||
text_pdu_type.set(pdu_type_to_string(entry.pduType));
|
||||
std::string vendor_name = lookup_mac_vendor(entry.packetData.macAddress);
|
||||
text_vendor.set(vendor_name);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -483,7 +546,6 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
check_log.on_select = [this](Checkbox&, bool v) {
|
||||
str_log = "";
|
||||
logging = v;
|
||||
|
||||
if (logger && logging)
|
||||
@@ -505,6 +567,7 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
|
||||
button_clear_list.on_select = [this](Button&) {
|
||||
recent.clear();
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
button_switch.on_select = [&nav](Button&) {
|
||||
@@ -546,7 +609,10 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
|
||||
options_filter.on_change = [this](size_t index, int32_t v) {
|
||||
filter_index = (uint8_t)index;
|
||||
recent.clear();
|
||||
handle_filter_options(v);
|
||||
uniqueParsing = filter_index == 2 ? true : false;
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(channel_index, true);
|
||||
@@ -719,10 +785,61 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
if (!logging) {
|
||||
str_log = "";
|
||||
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
|
||||
|
||||
// Start of Packet stuffing.
|
||||
// Masking off the top 2 bytes to avoid invalid keys.
|
||||
|
||||
uint64_t key = macAddressEncoded & 0xFFFFFFFFFFFF;
|
||||
bool packetExists = false;
|
||||
|
||||
// If found store into tempEntry to modify.
|
||||
auto it = find(recent, key);
|
||||
if (it != recent.end()) {
|
||||
recent.push_front(*it);
|
||||
recent.erase(it);
|
||||
updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
|
||||
packetExists = true;
|
||||
} else {
|
||||
recent.emplace_front(key);
|
||||
truncate_entries(recent);
|
||||
|
||||
packetExists = updateEntry(packet, recent.front(), (ADV_PDU_TYPE)packet->type);
|
||||
|
||||
// If parsing failed, remove entry.
|
||||
if (!packetExists) {
|
||||
recent.erase(recent.begin());
|
||||
}
|
||||
}
|
||||
|
||||
if (packetExists) {
|
||||
handle_filter_options(options_filter.selected_index());
|
||||
handle_entries_sort(options_sort.selected_index());
|
||||
|
||||
if (!searchList.empty()) {
|
||||
auto it = searchList.begin();
|
||||
|
||||
while (it != searchList.end()) {
|
||||
std::string searchStr = (std::string)*it;
|
||||
|
||||
if (recent.front().dataString.find(searchStr) != std::string::npos) {
|
||||
searchList.erase(it);
|
||||
found_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
|
||||
}
|
||||
}
|
||||
|
||||
log_ble_packet(packet);
|
||||
}
|
||||
|
||||
void BLERxView::log_ble_packet(BlePacketData* packet) {
|
||||
str_console = "";
|
||||
str_console += pdu_type_to_string((ADV_PDU_TYPE)packet->type);
|
||||
str_console += " Len:";
|
||||
str_console += to_string_dec_uint(packet->size);
|
||||
@@ -736,49 +853,14 @@ void BLERxView::on_data(BlePacketData* packet) {
|
||||
str_console += to_string_hex(packet->data[i], 2);
|
||||
}
|
||||
|
||||
uint64_t macAddressEncoded = copy_mac_address_to_uint64(packet->macAddress);
|
||||
|
||||
// Start of Packet stuffing.
|
||||
// Masking off the top 2 bytes to avoid invalid keys.
|
||||
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF);
|
||||
updateEntry(packet, entry, (ADV_PDU_TYPE)packet->type);
|
||||
|
||||
// Add entries if they meet the criteria.
|
||||
// auto value = filter;
|
||||
// resetFilteredEntries(recent, [&value](const BleRecentEntry& entry) {
|
||||
// return (entry.dataString.find(value) == std::string::npos) && (entry.nameString.find(value) == std::string::npos);
|
||||
// });
|
||||
handle_filter_options(options_filter.selected_index());
|
||||
|
||||
handle_entries_sort(options_sort.selected_index());
|
||||
|
||||
// Log at End of Packet.
|
||||
if (logger && logging) {
|
||||
logger->log_raw_data(str_console + "\r\n");
|
||||
logger->log_raw_data(str_console);
|
||||
}
|
||||
|
||||
if (serial_logging) {
|
||||
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
|
||||
}
|
||||
str_console = "";
|
||||
|
||||
if (!searchList.empty()) {
|
||||
auto it = searchList.begin();
|
||||
|
||||
while (it != searchList.end()) {
|
||||
std::string searchStr = (std::string)*it;
|
||||
|
||||
if (entry.dataString.find(searchStr) != std::string::npos) {
|
||||
searchList.erase(it);
|
||||
found_count++;
|
||||
break;
|
||||
}
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
text_found_count.set(to_string_dec_uint(found_count) + "/" + to_string_dec_uint(total_count));
|
||||
}
|
||||
}
|
||||
|
||||
void BLERxView::on_filter_change(std::string value) {
|
||||
@@ -841,13 +923,10 @@ void BLERxView::on_timer() {
|
||||
timer_count = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
baseband::set_btlerx(channel_number);
|
||||
|
||||
int randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
baseband::set_btlerx(randomChannel);
|
||||
channel_number = (channel_number < 39) ? channel_number + 1 : 37;
|
||||
}
|
||||
}
|
||||
if (ble_rx_error != BLE_RX_NO_ERROR) {
|
||||
@@ -923,16 +1002,18 @@ BLERxView::~BLERxView() {
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
|
||||
bool BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type) {
|
||||
std::string data_string;
|
||||
|
||||
bool success = false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < packet->dataLen; i++) {
|
||||
data_string += to_string_hex(packet->data[i], 2);
|
||||
}
|
||||
|
||||
entry.dbValue = packet->max_dB;
|
||||
entry.dbValue = packet->max_dB - (receiver_model.lna() + receiver_model.vga() + (receiver_model.rf_amp() ? 14 : 0));
|
||||
entry.timestamp = to_string_timestamp(rtc_time::now());
|
||||
entry.dataString = data_string;
|
||||
|
||||
@@ -948,9 +1029,14 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.packetData.macAddress[4] = packet->macAddress[4];
|
||||
entry.packetData.macAddress[5] = packet->macAddress[5];
|
||||
|
||||
entry.numHits++;
|
||||
entry.pduType = pdu_type;
|
||||
entry.channelNumber = channel_number;
|
||||
entry.numHits++;
|
||||
|
||||
if (entry.vendor_status == MAC_VENDOR_UNKNOWN) {
|
||||
std::string vendor_name;
|
||||
entry.vendor_status = lookup_mac_vendor_status(entry.packetData.macAddress, vendor_name);
|
||||
}
|
||||
|
||||
// Parse Data Section into buffer to be interpretted later.
|
||||
for (int i = 0; i < packet->dataLen; i++) {
|
||||
@@ -960,35 +1046,55 @@ void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry,
|
||||
entry.include_name = check_name.value();
|
||||
|
||||
// Only parse name for advertisment packets and empty name entries
|
||||
if ((pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) && entry.nameString.empty()) {
|
||||
ADV_PDU_PAYLOAD_TYPE_0_2_4_6* advertiseData = (ADV_PDU_PAYLOAD_TYPE_0_2_4_6*)entry.packetData.data;
|
||||
|
||||
uint8_t currentByte = 0;
|
||||
uint8_t length = 0;
|
||||
uint8_t type = 0;
|
||||
|
||||
std::string decoded_data;
|
||||
for (currentByte = 0; (currentByte < entry.packetData.dataLen);) {
|
||||
length = advertiseData->Data[currentByte++];
|
||||
type = advertiseData->Data[currentByte++];
|
||||
|
||||
// Subtract 1 because type is part of the length.
|
||||
for (int i = 0; i < length - 1; i++) {
|
||||
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
|
||||
if (type == 0x08 || type == 0x09) {
|
||||
decoded_data += (char)advertiseData->Data[currentByte];
|
||||
}
|
||||
currentByte++;
|
||||
}
|
||||
if (!decoded_data.empty()) {
|
||||
entry.nameString = std::move(decoded_data);
|
||||
break;
|
||||
}
|
||||
if (pdu_type == ADV_IND || pdu_type == ADV_NONCONN_IND || pdu_type == SCAN_RSP || pdu_type == ADV_SCAN_IND) {
|
||||
if (uniqueParsing) {
|
||||
// Add your unique beacon parsing function here.
|
||||
}
|
||||
|
||||
if (!success && !uniqueParsing) {
|
||||
success = parse_beacon_data(packet->data, packet->dataLen, entry.nameString, entry.informationString);
|
||||
}
|
||||
|
||||
} else if (pdu_type == ADV_DIRECT_IND || pdu_type == SCAN_REQ) {
|
||||
ADV_PDU_PAYLOAD_TYPE_1_3* directed_mac_data = (ADV_PDU_PAYLOAD_TYPE_1_3*)entry.packetData.data;
|
||||
reverse_byte_array(directed_mac_data->A1, 6);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool BLERxView::parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString) {
|
||||
uint8_t currentByte, currentLength, currentType = 0;
|
||||
std::string tempName = "";
|
||||
|
||||
for (currentByte = 0; currentByte < length;) {
|
||||
currentLength = data[currentByte++];
|
||||
currentType = data[currentByte++];
|
||||
|
||||
// Subtract 1 because type is part of the length.
|
||||
for (int i = 0; ((i < currentLength - 1) && (currentByte < length)); i++) {
|
||||
// parse the name of bluetooth device: 0x08->Shortened Local Name; 0x09->Complete Local Name
|
||||
if (currentType == 0x08 || currentType == 0x09) {
|
||||
tempName += (char)data[currentByte];
|
||||
}
|
||||
currentByte++;
|
||||
}
|
||||
|
||||
if (!tempName.empty()) {
|
||||
nameString = tempName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
informationString = "";
|
||||
|
||||
if (!informationString.empty()) {
|
||||
// Option to change title of Hits Column.
|
||||
// Setting to default for now.
|
||||
columns.set(1, "Hits", 7);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 Furrtek
|
||||
* Copyright (C) 2023 TJ Baginski
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "database.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "usb_serial_thread.hpp"
|
||||
@@ -72,10 +74,17 @@ typedef enum {
|
||||
RESERVED8 = 15
|
||||
} ADV_PDU_TYPE;
|
||||
|
||||
typedef enum {
|
||||
MAC_VENDOR_UNKNOWN = 0,
|
||||
MAC_VENDOR_FOUND = 1,
|
||||
MAC_VENDOR_NOT_FOUND = 2,
|
||||
MAC_DB_NOT_FOUND = 3
|
||||
} MAC_VENDOR_STATUS;
|
||||
|
||||
struct BleRecentEntry {
|
||||
using Key = uint64_t;
|
||||
|
||||
static constexpr Key invalid_key = 0xffffffff;
|
||||
static constexpr Key invalid_key = 0xFFFFFFFFFFFF;
|
||||
|
||||
uint64_t macAddress;
|
||||
int dbValue;
|
||||
@@ -83,10 +92,12 @@ struct BleRecentEntry {
|
||||
std::string timestamp;
|
||||
std::string dataString;
|
||||
std::string nameString;
|
||||
std::string informationString;
|
||||
bool include_name;
|
||||
uint16_t numHits;
|
||||
ADV_PDU_TYPE pduType;
|
||||
uint8_t channelNumber;
|
||||
MAC_VENDOR_STATUS vendor_status;
|
||||
bool entryFound;
|
||||
|
||||
BleRecentEntry()
|
||||
@@ -101,10 +112,12 @@ struct BleRecentEntry {
|
||||
timestamp{},
|
||||
dataString{},
|
||||
nameString{},
|
||||
informationString{},
|
||||
include_name{},
|
||||
numHits{},
|
||||
pduType{},
|
||||
channelNumber{},
|
||||
vendor_status{MAC_VENDOR_UNKNOWN},
|
||||
entryFound{} {
|
||||
}
|
||||
|
||||
@@ -152,6 +165,13 @@ class BleRecentEntryDetailView : public View {
|
||||
{9 * 8, 1 * 16, 17 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_vendor{
|
||||
{{0 * 8, 2 * 16}, "Vendor:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Text text_vendor{
|
||||
{7 * 8, 2 * 16, 23 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 3 * 16}, "Len", Theme::getInstance()->fg_light->foreground},
|
||||
{{5 * 8, 3 * 16}, "Type", Theme::getInstance()->fg_light->foreground},
|
||||
@@ -198,15 +218,18 @@ class BLERxView : public View {
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
std::unique_ptr<UsbSerialThread> usb_serial_thread{};
|
||||
void on_data(BlePacketData* packetData);
|
||||
void log_ble_packet(BlePacketData* packet);
|
||||
void on_filter_change(std::string value);
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void file_error();
|
||||
void on_timer();
|
||||
void handle_entries_sort(uint8_t index);
|
||||
void handle_filter_options(uint8_t index);
|
||||
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
bool parse_beacon_data(const uint8_t* data, uint8_t length, std::string& nameString, std::string& informationString);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState radio_state_{
|
||||
2402000000 /* frequency */,
|
||||
4000000 /* bandwidth */,
|
||||
@@ -216,6 +239,7 @@ class BLERxView : public View {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t sort_index{0};
|
||||
uint8_t filter_index{0};
|
||||
bool uniqueParsing = false;
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool serial_logging{false};
|
||||
@@ -243,7 +267,7 @@ class BLERxView : public View {
|
||||
bool auto_channel = false;
|
||||
|
||||
int16_t timer_count{0};
|
||||
int16_t timer_period{6}; // 100ms
|
||||
int16_t timer_period{2}; // 25ms
|
||||
|
||||
std::string filterBuffer{};
|
||||
std::string listFileBuffer{};
|
||||
@@ -307,9 +331,10 @@ class BLERxView : public View {
|
||||
|
||||
OptionsField options_filter{
|
||||
{18 * 8 + 2, 2 * 8},
|
||||
4,
|
||||
7,
|
||||
{{"Data", 0},
|
||||
{"MAC", 1}}};
|
||||
{"MAC", 1},
|
||||
{"Unique", 2}}};
|
||||
|
||||
Checkbox check_log{
|
||||
{10 * 8, 4 * 8 + 2},
|
||||
@@ -361,10 +386,10 @@ class BLERxView : public View {
|
||||
BleRecentEntries recent{};
|
||||
BleRecentEntries tempList{};
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Mac Address", 17},
|
||||
RecentEntriesColumns columns{{
|
||||
{"Name", 17},
|
||||
{"Hits", 7},
|
||||
{"dB", 4},
|
||||
{"dBm", 4},
|
||||
}};
|
||||
|
||||
BleRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
@@ -319,7 +319,8 @@ void BLETxView::on_tx_progress(const bool done) {
|
||||
|
||||
BLETxView::BLETxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&button_open,
|
||||
add_children({&dataEditView,
|
||||
&button_open,
|
||||
&text_filename,
|
||||
&progressbar,
|
||||
&check_rand_mac,
|
||||
@@ -340,7 +341,6 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch});
|
||||
@@ -430,6 +430,23 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_change = [this](uint8_t value) {
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
packets[current_packet].advertisementData[dataBytePos] = uint_to_char(value, 16);
|
||||
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
}
|
||||
};
|
||||
|
||||
dataEditView.on_cursor_moved = [this]() {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
@@ -531,7 +548,6 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
for (const std::string& str : strings) {
|
||||
dataFile.write(str.c_str(), str.size());
|
||||
dataFile.write("\n", 1);
|
||||
;
|
||||
}
|
||||
|
||||
dataFile.~File();
|
||||
@@ -546,6 +562,7 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
dataEditView.cursor_set(cursor_pos.line, cursor_pos.col);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
@@ -287,9 +287,6 @@ class BLETxView : public View {
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
|
||||
|
||||
Console console{
|
||||
{0, 9 * 18, screen_width, screen_height - 80}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, screen_width, screen_height - 80}};
|
||||
|
||||
|
||||
@@ -228,6 +228,8 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
// Collect all entries first
|
||||
std::list<fileman_entry> all_entries;
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||
@@ -235,36 +237,40 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
insert_sorted(all_entries, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
insert_sorted(all_entries, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// paginating
|
||||
auto list_size = entry_list.size();
|
||||
nb_pages = 1 + (list_size / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (list_size > start) {
|
||||
if (list_size < stop)
|
||||
stop = list_size;
|
||||
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
|
||||
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
|
||||
// Calculate pagination
|
||||
nb_pages = (all_entries.size() + items_per_page - 1) / items_per_page;
|
||||
if (nb_pages == 0) nb_pages = 1;
|
||||
|
||||
size_t start_idx = pagination * items_per_page;
|
||||
size_t end_idx = std::min(start_idx + items_per_page, all_entries.size());
|
||||
|
||||
// Add "parent" directory if not at the root and on first page
|
||||
if (!dir_path.empty() && pagination == 0) {
|
||||
entry_list.push_back({parent_dir_path.string(), 0, true});
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (list_size > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
// Add prev page navigation if not on first page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
entry_list.push_back({str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
|
||||
// Add entries for current page
|
||||
auto it = all_entries.begin();
|
||||
std::advance(it, start_idx);
|
||||
|
||||
for (size_t i = start_idx; i < end_idx && it != all_entries.end(); i++, ++it) {
|
||||
entry_list.push_back(*it);
|
||||
}
|
||||
|
||||
// Add next page navigation if not on last page
|
||||
if (end_idx < all_entries.size()) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,30 +326,49 @@ void FileManBaseView::focus() {
|
||||
} else {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
// Set menu to the correct page and select the correct item
|
||||
menu_view.set_highlighted(prev_highlight % items_per_page);
|
||||
}
|
||||
|
||||
// Push directory - store the global index (page * items_per_page + local_index)
|
||||
void FileManBaseView::push_dir(const fs::path& path) {
|
||||
if (path == parent_dir_path) {
|
||||
pop_dir();
|
||||
} else {
|
||||
// Save global index (combines page number and item position)
|
||||
saved_index_stack.push_back(menu_view.highlighted_index() + (pagination * items_per_page));
|
||||
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(true);
|
||||
reload_current(true); // Reset pagination when entering new directory
|
||||
}
|
||||
}
|
||||
|
||||
void FileManBaseView::pop_dir() {
|
||||
if (saved_index_stack.empty())
|
||||
if (current_path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to parent directory
|
||||
current_path = current_path.parent_path();
|
||||
reload_current(true);
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Restore the previous global index if available
|
||||
if (!saved_index_stack.empty()) {
|
||||
uint32_t global_index = saved_index_stack.back();
|
||||
saved_index_stack.pop_back();
|
||||
|
||||
// Calculate pagination from global index
|
||||
pagination = global_index / items_per_page;
|
||||
|
||||
// Calculate local index within the page
|
||||
prev_highlight = global_index % items_per_page;
|
||||
restoring_navigation = true;
|
||||
}
|
||||
|
||||
reload_current(false); // Important: don't reset pagination
|
||||
}
|
||||
|
||||
std::string get_extension(std::string t) {
|
||||
std::string FileManBaseView::get_extension(const std::string& t) const {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return {};
|
||||
@@ -372,7 +397,9 @@ void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
if (!restoring_navigation) {
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
}
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
@@ -411,10 +438,14 @@ void FileManBaseView::refresh_list() {
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
restoring_navigation = false;
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
if (reset_pagination) pagination = 0;
|
||||
// Only reset pagination if explicitly requested
|
||||
if (reset_pagination) {
|
||||
pagination = 0;
|
||||
}
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
@@ -489,63 +520,6 @@ void FileLoadView::refresh_widgets(const bool) {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
/* FileSaveView **************************************************************/
|
||||
/*
|
||||
FileSaveView::FileSaveView(
|
||||
NavigationView& nav,
|
||||
const fs::path& path,
|
||||
const fs::path& file
|
||||
) : nav_{ nav },
|
||||
path_{ path },
|
||||
file_{ file }
|
||||
{
|
||||
add_children({
|
||||
&text_path,
|
||||
&button_edit_path,
|
||||
&text_name,
|
||||
&button_edit_name,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
});
|
||||
|
||||
button_edit_path.on_select = [this](Button&) {
|
||||
buffer_ = path_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string&) {
|
||||
path_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_edit_name.on_select = [this](Button&) {
|
||||
buffer_ = file_.string();
|
||||
text_prompt(nav_, buffer_, max_filename_length,ENTER_KEYBOARD_MODE_ALPHA,
|
||||
[this](std::string&) {
|
||||
file_ = buffer_;
|
||||
refresh_widgets();
|
||||
});
|
||||
};
|
||||
|
||||
button_save.on_select = [this](Button&) {
|
||||
if (on_save)
|
||||
on_save(path_ / file_);
|
||||
else
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [this](Button&) {
|
||||
nav_.pop();
|
||||
};
|
||||
|
||||
refresh_widgets();
|
||||
}
|
||||
|
||||
void FileSaveView::refresh_widgets() {
|
||||
text_path.set(truncate(path_, 30));
|
||||
text_name.set(truncate(file_, 30));
|
||||
set_dirty();
|
||||
}
|
||||
*/
|
||||
/* FileManagerView ***********************************************************/
|
||||
|
||||
void FileManagerView::refresh_widgets(const bool v) {
|
||||
|
||||
@@ -64,6 +64,7 @@ class FileManBaseView : public View {
|
||||
uint32_t prev_highlight = 0;
|
||||
uint8_t pagination = 0;
|
||||
uint8_t nb_pages = 1;
|
||||
bool restoring_navigation = false;
|
||||
static constexpr size_t max_filename_length = 20;
|
||||
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
|
||||
static constexpr size_t items_per_page = 20;
|
||||
@@ -96,6 +97,7 @@ class FileManBaseView : public View {
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
std::string get_extension(const std::string& t) const;
|
||||
void copy_waterfall(std::filesystem::path path);
|
||||
|
||||
NavigationView& nav_;
|
||||
|
||||
@@ -84,6 +84,7 @@ void SubGhzDView::focus() {
|
||||
SubGhzDView::SubGhzDView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
|
||||
@@ -129,6 +129,9 @@ class SubGhzDView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "log_file.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -81,21 +82,75 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
void TextViewer::enable_long_press() {
|
||||
// Enable long press on "Select".
|
||||
SwitchesState config;
|
||||
config[toUType(Switch::Sel)] = true;
|
||||
set_switches_long_press_config(config);
|
||||
}
|
||||
|
||||
void TextViewer::on_focus() {
|
||||
enable_long_press();
|
||||
}
|
||||
|
||||
bool TextViewer::on_key(const KeyEvent key) {
|
||||
int16_t delta_col = 0;
|
||||
int16_t delta_line = 0;
|
||||
|
||||
if (key == KeyEvent::Left)
|
||||
delta_col = -1;
|
||||
else if (key == KeyEvent::Right)
|
||||
delta_col = 1;
|
||||
else if (key == KeyEvent::Up)
|
||||
delta_line = -1;
|
||||
else if (key == KeyEvent::Down)
|
||||
delta_line = 1;
|
||||
else if (key == KeyEvent::Select && on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
if (key == KeyEvent::Select) {
|
||||
// Toggle 'digit' mode with long-press.
|
||||
if (digit_mode_ || key_is_long_pressed(key)) {
|
||||
digit_mode_ = !digit_mode_;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (digit_mode_) {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delta_col == 0 && delta_line == 0) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case KeyEvent::Left:
|
||||
delta_col = -1;
|
||||
break;
|
||||
case KeyEvent::Right:
|
||||
delta_col = 1;
|
||||
break;
|
||||
case KeyEvent::Up:
|
||||
delta_line = -1;
|
||||
break;
|
||||
case KeyEvent::Down:
|
||||
delta_line = 1;
|
||||
break;
|
||||
case KeyEvent::Select: {
|
||||
if (on_select) {
|
||||
on_select();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Always allow cursor direction to be updated.
|
||||
@@ -111,19 +166,42 @@ bool TextViewer::on_key(const KeyEvent key) {
|
||||
bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
bool updated = false;
|
||||
|
||||
if (cursor_.dir == ScrollDirection::Horizontal)
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
if (digit_mode_) {
|
||||
if (delta > 0) {
|
||||
set_value(value_ == 0x0F ? 0x00 : value_ + 1);
|
||||
} else if (delta < 0) {
|
||||
set_value(value_ == 0x00 ? 0x0F : value_ - 1);
|
||||
}
|
||||
|
||||
updated = true;
|
||||
} else {
|
||||
if (cursor_.dir == ScrollDirection::Horizontal) {
|
||||
updated = apply_scrolling_constraints(0, delta);
|
||||
} else {
|
||||
delta *= 16;
|
||||
updated = apply_scrolling_constraints(delta, 0);
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
redraw();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::set_value(uint8_t new_value) {
|
||||
if (new_value != value_) {
|
||||
value_ = new_value;
|
||||
|
||||
if (on_change) {
|
||||
on_change(value_);
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
|
||||
@@ -54,10 +54,13 @@ class TextViewer : public Widget {
|
||||
|
||||
std::function<void()> on_select{};
|
||||
std::function<void()> on_cursor_moved{};
|
||||
std::function<void(uint8_t)> on_change{};
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
void set_value(uint8_t new_value);
|
||||
void on_focus() override;
|
||||
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
@@ -74,6 +77,7 @@ class TextViewer : public Widget {
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
void enable_long_press();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
@@ -95,6 +99,9 @@ class TextViewer : public Widget {
|
||||
int8_t char_height{};
|
||||
uint8_t max_line{};
|
||||
uint8_t max_col{};
|
||||
bool digit_mode_{false};
|
||||
uint8_t value_{0};
|
||||
bool allow_digit_mode_{true};
|
||||
|
||||
/* Returns true if the cursor was updated. */
|
||||
bool apply_scrolling_constraints(
|
||||
|
||||
@@ -107,6 +107,7 @@ void WeatherView::focus() {
|
||||
WeatherView::WeatherView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&channel,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
|
||||
@@ -147,6 +147,9 @@ class WeatherView : public View {
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, 6 * 8, 4},
|
||||
};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{screen_width - 2 * 8, 0 * 16}};
|
||||
|
||||
@@ -186,13 +186,14 @@ void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_fsk(const size_t deviation) {
|
||||
void set_fsk(const uint8_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes) {
|
||||
const FSKRxConfigureMessage message{
|
||||
taps_200k_decim_0,
|
||||
taps_16k0_decim_1,
|
||||
taps_11k0_channel,
|
||||
2,
|
||||
deviation};
|
||||
samplesPerSymbol,
|
||||
syncWord,
|
||||
syncWordLength,
|
||||
preamble,
|
||||
preambleLength,
|
||||
numDataBytes};
|
||||
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ void set_pitch_rssi(int32_t avg, bool enabled);
|
||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space, const uint8_t afsk_repeat, const uint32_t afsk_bw, const uint8_t symbol_count);
|
||||
void kill_afsk();
|
||||
void set_afsk(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);
|
||||
void set_fsk(const size_t deviation);
|
||||
void set_fsk(const uint32_t samplesPerSymbol, const uint32_t syncWord, const uint8_t syncWordLength, const uint32_t preamble, const uint8_t preambleLength, uint16_t numDataBytes);
|
||||
void set_aprs(const uint32_t baudrate);
|
||||
|
||||
void set_btlerx(uint8_t channel_number);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -56,6 +57,16 @@ int database::retrieve_aircraft_record(AircraftDBRecord* record, std::string sea
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term) {
|
||||
file_path = macaddress_dir / u"macaddress.db";
|
||||
index_item_length = 7;
|
||||
record_length = 64;
|
||||
|
||||
result = retrieve_record(file_path, index_item_length, record_length, record, search_term);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
int database::retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term) {
|
||||
if (search_term.empty())
|
||||
return DATABASE_RECORD_NOT_FOUND;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2016 Furrtek
|
||||
* Copyright (C) 2022 Arjan Onwezen
|
||||
* Copyright (C) 2025 Tommaso Ventafridda
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
@@ -60,6 +61,12 @@ class database {
|
||||
|
||||
int retrieve_aircraft_record(AircraftDBRecord* record, std::string search_term);
|
||||
|
||||
struct MacAddressDBRecord {
|
||||
char vendor_name[64]; // vendor/manufacturer name
|
||||
};
|
||||
|
||||
int retrieve_macaddress_record(MacAddressDBRecord* record, std::string search_term);
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path = ""; // path including filename
|
||||
int index_item_length = 0; // length of index item
|
||||
@@ -77,4 +84,4 @@ class database {
|
||||
int retrieve_record(std::filesystem::path file_path, int index_item_length, int record_length, void* record, std::string search_term);
|
||||
};
|
||||
|
||||
#endif /*__DATABASE_H__*/
|
||||
#endif /*__DATABASE_H__*/
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_battleship.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BattleshipView>();
|
||||
}
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_battleship.application_information"), used)) application_information_t _application_information_battleship = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::battleship::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "Battleship",
|
||||
/*.bitmap_data = */ {
|
||||
// Pirate galleon - 16x16
|
||||
0x80, 0x00, // ........#.......
|
||||
0x80, 0x00, // ........#.......
|
||||
0x80, 0x00, // ........#.......
|
||||
0xC0, 0x01, // .......###......
|
||||
0xE0, 0x03, // ......#####.....
|
||||
0xF0, 0x07, // .....#######....
|
||||
0xF8, 0x0F, // ....#########...
|
||||
0xFC, 0x1F, // ...###########..
|
||||
0xFE, 0x3F, // ..#############.
|
||||
0x00, 0x01, // .......#........
|
||||
0x00, 0x01, // .......#........
|
||||
0x00, 0x01, // .......#........
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFF, 0xFF, // ################
|
||||
0xFC, 0x3F, // ..############..
|
||||
},
|
||||
/*.icon_color = */ ui::Color::blue().v,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = */ {'P', 'P', 'O', '2'}, // Use POCSAG2 baseband (larger than FSKTX)
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_BATTLESHIP_H__
|
||||
#define __UI_BATTLESHIP_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "message.hpp"
|
||||
#include "pocsag.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace ui::external_app::battleship {
|
||||
|
||||
constexpr uint8_t GRID_SIZE = 10;
|
||||
constexpr uint8_t CELL_SIZE = 24;
|
||||
constexpr uint8_t GRID_OFFSET_X = 0;
|
||||
constexpr uint8_t GRID_OFFSET_Y = 32;
|
||||
|
||||
enum class ShipType : uint8_t {
|
||||
CARRIER = 5,
|
||||
BATTLESHIP = 4,
|
||||
CRUISER = 3,
|
||||
SUBMARINE = 3,
|
||||
DESTROYER = 2
|
||||
};
|
||||
|
||||
enum class GameState : uint8_t {
|
||||
MENU,
|
||||
PLACING_SHIPS,
|
||||
WAITING_FOR_OPPONENT,
|
||||
MY_TURN,
|
||||
OPPONENT_TURN,
|
||||
GAME_OVER
|
||||
};
|
||||
|
||||
enum class CellState : uint8_t {
|
||||
EMPTY,
|
||||
SHIP,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK
|
||||
};
|
||||
|
||||
enum class MessageType : uint8_t {
|
||||
READY,
|
||||
SHOT,
|
||||
HIT,
|
||||
MISS,
|
||||
SUNK,
|
||||
WIN
|
||||
};
|
||||
|
||||
struct Ship {
|
||||
ShipType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
bool horizontal;
|
||||
uint8_t hits;
|
||||
bool placed;
|
||||
|
||||
uint8_t size() const {
|
||||
return static_cast<uint8_t>(type);
|
||||
}
|
||||
|
||||
bool is_sunk() const {
|
||||
return hits >= size();
|
||||
}
|
||||
};
|
||||
|
||||
struct GameMessage {
|
||||
MessageType type;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
};
|
||||
|
||||
class BattleshipView : public View {
|
||||
public:
|
||||
BattleshipView(NavigationView& nav);
|
||||
~BattleshipView();
|
||||
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
bool on_touch(const TouchEvent event) override;
|
||||
bool on_encoder(const EncoderEvent delta) override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
|
||||
std::string title() const override { return "Battleship"; }
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
RxRadioState rx_radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
TxRadioState tx_radio_state_{
|
||||
433920000 /* frequency */,
|
||||
1750000 /* bandwidth */,
|
||||
2280000 /* sampling rate */
|
||||
};
|
||||
|
||||
// Settings
|
||||
bool sound_enabled{true};
|
||||
bool rf_amp_enabled{false};
|
||||
uint8_t lna_gain{24};
|
||||
uint8_t vga_gain{24};
|
||||
uint8_t tx_gain{35};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"battleship",
|
||||
app_settings::Mode::RX_TX,
|
||||
{{"rx_freq"sv, &rx_frequency},
|
||||
{"tx_freq"sv, &tx_frequency},
|
||||
{"rf_amp"sv, &rf_amp_enabled}}};
|
||||
|
||||
GameState game_state{GameState::MENU};
|
||||
bool is_red_team{false};
|
||||
bool opponent_ready{false};
|
||||
uint8_t wins{0};
|
||||
uint8_t losses{0};
|
||||
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> my_grid{};
|
||||
std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE> enemy_grid{};
|
||||
|
||||
std::string current_status{"Choose your team!"};
|
||||
std::string current_score{"W:0 L:0"};
|
||||
|
||||
std::array<Ship, 5> my_ships{};
|
||||
uint8_t current_ship_index{0};
|
||||
bool placing_horizontal{true};
|
||||
uint8_t ships_remaining{5};
|
||||
uint8_t enemy_ships_remaining{5};
|
||||
|
||||
uint8_t cursor_x{0};
|
||||
uint8_t cursor_y{0};
|
||||
uint8_t target_x{0};
|
||||
uint8_t target_y{0};
|
||||
bool touch_enabled{true};
|
||||
|
||||
uint32_t tx_frequency{433920000};
|
||||
uint32_t rx_frequency{433920000};
|
||||
bool is_transmitting{false};
|
||||
|
||||
// POCSAG decoding state
|
||||
pocsag::EccContainer ecc{};
|
||||
pocsag::POCSAGState pocsag_state{&ecc};
|
||||
uint32_t last_address{0};
|
||||
|
||||
// UI Elements - Menu/Settings Screen
|
||||
Text text_title{{60, 2, 120, 24}, "BATTLESHIP"};
|
||||
Text text_subtitle{{40, 20, 160, 16}, "Naval Combat Game"};
|
||||
|
||||
Rectangle rect_radio_settings{{12, 40, 216, 118}, Color::dark_grey()};
|
||||
Text label_radio{{17, 45, 100, 16}, "RADIO SETUP"};
|
||||
ButtonWithEncoder button_frequency{{17, 65, 11 * 8, 20}, ""};
|
||||
|
||||
// Radio controls
|
||||
Text label_rf_amp{{17, 90, 35, 16}, "AMP:"};
|
||||
Checkbox checkbox_rf_amp{{55, 90}, 3, "", false};
|
||||
|
||||
Text label_lna{{17, 118, 30, 16}, "LNA:"};
|
||||
NumberField field_lna{{50, 118}, 2, {0, 40}, 8, ' '};
|
||||
|
||||
Text label_vga{{90, 118, 30, 16}, "VGA:"};
|
||||
NumberField field_vga{{125, 118}, 2, {0, 62}, 8, ' '};
|
||||
|
||||
Text label_tx_gain{{155, 118, 25, 16}, "TX:"};
|
||||
NumberField field_tx_gain{{185, 118}, 2, {0, 47}, 8, ' '};
|
||||
|
||||
Rectangle rect_audio_settings{{12, 164, 216, 45}, Color::dark_grey()};
|
||||
Text label_audio{{17, 169, 80, 16}, "AUDIO"};
|
||||
Checkbox checkbox_sound{{17, 187}, 8, "Sound On", true};
|
||||
Text label_volume{{110, 187, 50, 16}, "Volume:"};
|
||||
AudioVolumeField field_volume{{165, 187}};
|
||||
|
||||
Rectangle rect_team_selection{{12, 217, 216, 75}, Color::dark_grey()};
|
||||
Text label_team{{17, 222, 110, 16}, "SELECT TEAM"};
|
||||
Button button_red_team{{25, 242, 85, 45}, "RED\nTEAM"};
|
||||
Button button_blue_team{{130, 242, 85, 45}, "BLUE\nTEAM"};
|
||||
|
||||
// In-game UI elements
|
||||
RSSI rssi{{21 * 8, 0, 6 * 8, 4}};
|
||||
Text text_status{{10, 16, 220, 16}, ""};
|
||||
Text text_score{{170, 16, 60, 16}, ""};
|
||||
Button button_rotate{{10, 265, 65, 32}, "Rotate"};
|
||||
Button button_place{{82, 265, 65, 32}, "Place"};
|
||||
Button button_fire{{82, 265, 65, 32}, "Fire!"};
|
||||
Button button_menu{{155, 265, 65, 32}, "Menu"};
|
||||
|
||||
// Methods
|
||||
void init_game();
|
||||
void reset_game();
|
||||
void start_team(bool red);
|
||||
void setup_ships();
|
||||
void draw_menu_screen(Painter& painter);
|
||||
void draw_grid(Painter& painter, uint8_t grid_x, uint8_t grid_y, const std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid, bool show_ships, bool is_offense_grid = false);
|
||||
void draw_cell(Painter& painter, uint8_t cell_x, uint8_t cell_y, CellState state, bool show_ships, bool is_offense_grid, bool is_cursor);
|
||||
void draw_ship_preview(Painter& painter);
|
||||
void place_ship();
|
||||
bool can_place_ship(uint8_t x, uint8_t y, uint8_t size, bool horizontal);
|
||||
void fire_at_position();
|
||||
void process_shot(uint8_t x, uint8_t y);
|
||||
void update_score();
|
||||
bool is_cursor_at(uint8_t x, uint8_t y, bool is_offense_grid);
|
||||
|
||||
void send_message(const GameMessage& msg);
|
||||
void process_message(const std::string& message);
|
||||
bool parse_coords(const std::string& coords, uint8_t& x, uint8_t& y);
|
||||
void mark_ship_sunk(uint8_t x, uint8_t y, std::array<std::array<CellState, GRID_SIZE>, GRID_SIZE>& grid);
|
||||
|
||||
void configure_radio_tx();
|
||||
void configure_radio_rx();
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::POCSAGPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const POCSAGPacketMessage*>(p);
|
||||
on_pocsag_packet(message);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_tx_progress{
|
||||
Message::ID::TXProgress,
|
||||
[this](const Message* const p) {
|
||||
const auto message = static_cast<const TXProgressMessage*>(p);
|
||||
on_tx_progress(message->progress, message->done);
|
||||
}};
|
||||
|
||||
void on_pocsag_packet(const POCSAGPacketMessage* message);
|
||||
void on_tx_progress(const uint32_t progress, const bool done);
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::battleship
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_blackjack.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BlackjackView>();
|
||||
}
|
||||
} // namespace ui::external_app::blackjack
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_blackjack.application_information"), used)) application_information_t _application_information_blackjack = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::blackjack::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Blackjack",
|
||||
{
|
||||
// Diamond icon 16x16
|
||||
0x00, 0x00, // ................
|
||||
0x80, 0x01, // .......##.......
|
||||
0xC0, 0x03, // ......####......
|
||||
0xE0, 0x07, // .....######.....
|
||||
0xF0, 0x0F, // ....########....
|
||||
0xF8, 0x1F, // ...##########...
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFE, 0x7F, // .##############.
|
||||
0xFC, 0x3F, // ..############..
|
||||
0xF8, 0x1F, // ...##########...
|
||||
0xF0, 0x0F, // ....########....
|
||||
0xE0, 0x07, // .....######.....
|
||||
0xC0, 0x03, // ......####......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x00, 0x00, // ................
|
||||
},
|
||||
ui::Color::red().v, // Red color for diamonds
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#include "ui_blackjack.hpp"
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
|
||||
// Global variables
|
||||
static BlackjackView* current_instance = nullptr;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
static Painter painter;
|
||||
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
void game_timer_check() {
|
||||
if (current_instance) {
|
||||
current_instance->update_game();
|
||||
}
|
||||
}
|
||||
|
||||
BlackjackView::BlackjackView(NavigationView& nav)
|
||||
: nav_{nav}, game_timer{} {
|
||||
add_children({&dummy});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
}
|
||||
|
||||
BlackjackView::~BlackjackView() {
|
||||
current_instance = nullptr;
|
||||
}
|
||||
|
||||
void BlackjackView::on_show() {
|
||||
draw_menu_static();
|
||||
}
|
||||
|
||||
void BlackjackView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
init_deck();
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BlackjackView::clear_screen() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
}
|
||||
|
||||
void BlackjackView::init_deck() {
|
||||
// Initialize deck with card values 0-51
|
||||
// 0-12 = Spades (A-K), 13-25 = Hearts, 26-38 = Diamonds, 39-51 = Clubs
|
||||
for (int i = 0; i < 52; i++) {
|
||||
deck[i] = i;
|
||||
}
|
||||
shuffle_deck();
|
||||
}
|
||||
|
||||
void BlackjackView::shuffle_deck() {
|
||||
// Simple shuffle using random swaps
|
||||
for (int i = 51; i > 0; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
uint8_t temp = deck[i];
|
||||
deck[i] = deck[j];
|
||||
deck[j] = temp;
|
||||
}
|
||||
deck_position = 0;
|
||||
}
|
||||
|
||||
uint8_t BlackjackView::draw_card() {
|
||||
if (deck_position >= 52) {
|
||||
shuffle_deck();
|
||||
}
|
||||
return deck[deck_position++];
|
||||
}
|
||||
|
||||
int BlackjackView::get_card_value(uint8_t card) {
|
||||
int value = (card % 13) + 1; // 1-13
|
||||
if (value > 10) value = 10; // Face cards worth 10
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t BlackjackView::get_card_suit(uint8_t card) {
|
||||
return card / 13; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
|
||||
}
|
||||
|
||||
std::string BlackjackView::get_card_string(uint8_t card) {
|
||||
int value = (card % 13) + 1;
|
||||
std::string result;
|
||||
|
||||
if (value == 1)
|
||||
result = "A";
|
||||
else if (value == 11)
|
||||
result = "J";
|
||||
else if (value == 12)
|
||||
result = "Q";
|
||||
else if (value == 13)
|
||||
result = "K";
|
||||
else if (value == 10)
|
||||
result = "10"; // Special case for 10
|
||||
else
|
||||
result = std::to_string(value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int BlackjackView::calculate_hand_value(uint8_t* cards, uint8_t count) {
|
||||
int value = 0;
|
||||
int aces = 0;
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
int card_val = get_card_value(cards[i]);
|
||||
if (card_val == 1) {
|
||||
aces++;
|
||||
value += 1;
|
||||
} else {
|
||||
value += card_val;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert aces from 1 to 11 if beneficial
|
||||
while (aces > 0 && value + 10 <= 21) {
|
||||
value += 10;
|
||||
aces--;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void BlackjackView::deal_cards() {
|
||||
// Clear hands
|
||||
player_card_count = 0;
|
||||
dealer_card_count = 0;
|
||||
|
||||
// Deal initial cards
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
|
||||
dealer_hidden = true;
|
||||
game_state = GameState::PLAYING;
|
||||
}
|
||||
|
||||
void BlackjackView::player_hit() {
|
||||
if (player_card_count < MAX_CARDS_IN_HAND) {
|
||||
player_cards[player_card_count++] = draw_card();
|
||||
|
||||
int value = calculate_hand_value(player_cards, player_card_count);
|
||||
if (value > 21) {
|
||||
// Bust!
|
||||
cash = (cash >= bet) ? cash - bet : 0;
|
||||
losses++;
|
||||
game_state = GameState::GAME_OVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::player_stay() {
|
||||
dealer_hidden = false;
|
||||
game_state = GameState::DEALER_TURN;
|
||||
dealer_timer = 0;
|
||||
}
|
||||
|
||||
void BlackjackView::dealer_turn() {
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
|
||||
if (dealer_value < 17 && dealer_card_count < MAX_CARDS_IN_HAND) {
|
||||
dealer_cards[dealer_card_count++] = draw_card();
|
||||
} else {
|
||||
check_game_over();
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::check_game_over() {
|
||||
int player_value = calculate_hand_value(player_cards, player_card_count);
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
|
||||
if (player_value > 21) {
|
||||
// Player bust (already handled)
|
||||
} else if (dealer_value > 21) {
|
||||
// Dealer bust - player wins
|
||||
cash += bet;
|
||||
wins++;
|
||||
} else if (player_value > dealer_value) {
|
||||
// Player wins
|
||||
cash += bet;
|
||||
wins++;
|
||||
} else if (player_value < dealer_value) {
|
||||
// Dealer wins
|
||||
cash = (cash >= bet) ? cash - bet : 0;
|
||||
losses++;
|
||||
}
|
||||
// Else it's a tie - no money changes hands
|
||||
|
||||
if (cash > high_score) {
|
||||
high_score = cash;
|
||||
}
|
||||
|
||||
game_state = GameState::GAME_OVER;
|
||||
}
|
||||
|
||||
void BlackjackView::update_game() {
|
||||
switch (game_state) {
|
||||
case GameState::MENU:
|
||||
draw_menu();
|
||||
break;
|
||||
|
||||
case GameState::BETTING:
|
||||
draw_betting();
|
||||
break;
|
||||
|
||||
case GameState::PLAYING:
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::DEALER_TURN:
|
||||
if (++dealer_timer >= 60) { // 1 second delay
|
||||
dealer_timer = 0;
|
||||
dealer_turn();
|
||||
}
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::GAME_OVER:
|
||||
draw_game();
|
||||
break;
|
||||
|
||||
case GameState::STATS:
|
||||
draw_stats();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_menu_static() {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_rules = *ui::Theme::getInstance()->fg_cyan;
|
||||
|
||||
painter.draw_string({84, 20}, style_title, "BLACKJACK");
|
||||
|
||||
// Draw rules
|
||||
painter.draw_string({70, 55}, style_rules, "-- RULES --");
|
||||
painter.draw_string({61, 75}, style_text, "Get close to 21");
|
||||
painter.draw_string({61, 90}, style_text, "without going over");
|
||||
painter.draw_string({61, 110}, style_text, "Dealer hits on 16");
|
||||
painter.draw_string({61, 125}, style_text, "Dealer stays on 17");
|
||||
painter.draw_string({61, 145}, style_text, "Blackjack pays 1:1");
|
||||
|
||||
// Controls
|
||||
painter.draw_string({65, 175}, style_rules, "-- CONTROLS --");
|
||||
painter.draw_string({61, 195}, style_text, "SELECT: Start/Hit");
|
||||
painter.draw_string({61, 210}, style_text, "LEFT: Stats");
|
||||
painter.draw_string({61, 225}, style_text, "RIGHT: Exit/Stay");
|
||||
|
||||
// Draw high score
|
||||
painter.draw_string({61, 250}, style_text, "High Score: $" + std::to_string(high_score));
|
||||
}
|
||||
|
||||
void BlackjackView::draw_menu() {
|
||||
// Only handle the flashing text animation
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
if (blink_state) {
|
||||
auto style = *ui::Theme::getInstance()->fg_yellow;
|
||||
painter.draw_string({55, 280}, style, "* PRESS SELECT *");
|
||||
} else {
|
||||
// Clear just the text area
|
||||
painter.fill_rectangle({55, 278, 130, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_stats() {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_value = *ui::Theme::getInstance()->fg_yellow;
|
||||
|
||||
painter.draw_string({75, 30}, style_title, "STATISTICS");
|
||||
|
||||
painter.draw_string({30, 80}, style_text, "Wins:");
|
||||
painter.draw_string({150, 80}, style_value, std::to_string(wins));
|
||||
|
||||
painter.draw_string({30, 100}, style_text, "Losses:");
|
||||
painter.draw_string({150, 100}, style_value, std::to_string(losses));
|
||||
|
||||
// Win percentage
|
||||
uint32_t total = wins + losses;
|
||||
if (total > 0) {
|
||||
uint32_t win_pct = (wins * 100) / total;
|
||||
painter.draw_string({30, 120}, style_text, "Win %:");
|
||||
painter.draw_string({150, 120}, style_value, std::to_string(win_pct) + "%");
|
||||
}
|
||||
|
||||
painter.draw_string({30, 160}, style_text, "High Score:");
|
||||
painter.draw_string({150, 160}, style_value, "$" + std::to_string(high_score));
|
||||
|
||||
painter.draw_string({30, 180}, style_text, "Cash:");
|
||||
painter.draw_string({150, 180}, style_value, "$" + std::to_string(cash));
|
||||
|
||||
painter.draw_string({40, 250}, style_text, "SELECT: Back");
|
||||
}
|
||||
|
||||
void BlackjackView::draw_betting() {
|
||||
static uint32_t last_bet = 0;
|
||||
static bool betting_drawn = false;
|
||||
|
||||
if (!betting_drawn) {
|
||||
clear_screen();
|
||||
|
||||
auto style_title = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
painter.draw_string({70, 40}, style_title, "PLACE BET");
|
||||
painter.draw_string({30, 80}, style_text, "Cash: $" + std::to_string(cash));
|
||||
|
||||
painter.draw_string({30, 140}, style_text, "ENCODER: +/- $10");
|
||||
painter.draw_string({30, 160}, style_text, "SELECT: Deal");
|
||||
|
||||
betting_drawn = true;
|
||||
last_bet = 0;
|
||||
}
|
||||
|
||||
// Update bet display
|
||||
if (bet != last_bet) {
|
||||
painter.fill_rectangle({30, 110, 150, 20}, Color::black());
|
||||
painter.draw_string({30, 110}, *ui::Theme::getInstance()->fg_yellow, "Bet: $" + std::to_string(bet));
|
||||
last_bet = bet;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_game() {
|
||||
static int last_player_count = -1;
|
||||
static int last_dealer_count = -1;
|
||||
static GameState last_game_state = GameState::MENU;
|
||||
static uint32_t last_bet = 0;
|
||||
|
||||
// Clear and redraw if hands changed or game state changed
|
||||
if (player_card_count != last_player_count || dealer_card_count != last_dealer_count || game_state != last_game_state) {
|
||||
clear_screen();
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({10, 10}, style, "Cash: $" + std::to_string(cash));
|
||||
painter.draw_string({140, 10}, style, "Bet: $" + std::to_string(bet));
|
||||
|
||||
// Draw dealer hand with value next to label
|
||||
auto style_value = *ui::Theme::getInstance()->fg_yellow;
|
||||
painter.draw_string({10, 45}, *ui::Theme::getInstance()->fg_light, "Dealer:");
|
||||
if (!dealer_hidden || game_state == GameState::GAME_OVER) {
|
||||
int dealer_value = calculate_hand_value(dealer_cards, dealer_card_count);
|
||||
painter.draw_string({70, 45}, style_value, "(" + std::to_string(dealer_value) + ")");
|
||||
}
|
||||
draw_hand(10, 65, dealer_cards, dealer_card_count, true);
|
||||
|
||||
// Draw player hand with value next to label
|
||||
painter.draw_string({10, 165}, *ui::Theme::getInstance()->fg_light, "You:");
|
||||
int player_value = calculate_hand_value(player_cards, player_card_count);
|
||||
painter.draw_string({50, 165}, style_value, "(" + std::to_string(player_value) + ")");
|
||||
draw_hand(10, 185, player_cards, player_card_count, false);
|
||||
|
||||
// Draw controls or result
|
||||
if (game_state == GameState::PLAYING) {
|
||||
auto style_text = *ui::Theme::getInstance()->fg_light;
|
||||
painter.draw_string({30, 290}, style_text, "SELECT: Hit");
|
||||
painter.draw_string({130, 290}, style_text, "RIGHT: Stay");
|
||||
} else if (game_state == GameState::GAME_OVER) {
|
||||
const Style* style_result = ui::Theme::getInstance()->fg_yellow;
|
||||
std::string result;
|
||||
|
||||
if (player_value > 21) {
|
||||
result = "BUST! You Lose";
|
||||
style_result = ui::Theme::getInstance()->fg_red;
|
||||
} else if (calculate_hand_value(dealer_cards, dealer_card_count) > 21) {
|
||||
result = "Dealer Bust! Win!";
|
||||
style_result = ui::Theme::getInstance()->fg_green;
|
||||
} else if (player_value > calculate_hand_value(dealer_cards, dealer_card_count)) {
|
||||
result = "You Win!";
|
||||
style_result = ui::Theme::getInstance()->fg_green;
|
||||
} else if (player_value < calculate_hand_value(dealer_cards, dealer_card_count)) {
|
||||
result = "You Lose";
|
||||
style_result = ui::Theme::getInstance()->fg_red;
|
||||
} else {
|
||||
result = "Push (Tie)";
|
||||
}
|
||||
|
||||
// Draw result
|
||||
painter.draw_string({60, 270}, *style_result, result);
|
||||
|
||||
// Draw compact bet selector in top right area
|
||||
auto style_bet = *ui::Theme::getInstance()->fg_cyan;
|
||||
painter.draw_string({140, 25}, style_bet, "Next: $" + std::to_string(bet));
|
||||
|
||||
// Show controls
|
||||
painter.draw_string({10, 290}, *ui::Theme::getInstance()->fg_light, "SELECT: Deal ENC: +/-");
|
||||
}
|
||||
|
||||
last_player_count = player_card_count;
|
||||
last_dealer_count = dealer_card_count;
|
||||
last_game_state = game_state;
|
||||
last_bet = bet;
|
||||
} else if (game_state == GameState::GAME_OVER && bet != last_bet) {
|
||||
// Only update the bet display when it changes
|
||||
painter.fill_rectangle({140, 25, 90, 16}, Color::black());
|
||||
painter.draw_string({140, 25}, *ui::Theme::getInstance()->fg_cyan, "Next: $" + std::to_string(bet));
|
||||
last_bet = bet;
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large) {
|
||||
if (large) {
|
||||
// Large suit symbols for center of card
|
||||
switch (suit) {
|
||||
case 0: // Spades
|
||||
// Top curve
|
||||
painter.fill_rectangle({x + 9, y + 4, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 8, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 7, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 6, y + 7, 8, 2}, color);
|
||||
painter.fill_rectangle({x + 5, y + 8, 10, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 9, 12, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 10, 14, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 11, 16, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 12, 18, 2}, color);
|
||||
painter.fill_rectangle({x + 0, y + 13, 20, 3}, color);
|
||||
painter.fill_rectangle({x + 1, y + 16, 18, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 17, 16, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 18, 14, 1}, color);
|
||||
// Stem
|
||||
painter.fill_rectangle({x + 9, y + 19, 2, 4}, color);
|
||||
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 23, 6, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 24, 8, 1}, color);
|
||||
break;
|
||||
|
||||
case 1: // Hearts
|
||||
// Left bump
|
||||
painter.fill_rectangle({x + 3, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 7, 8, 3}, color);
|
||||
painter.fill_rectangle({x + 0, y + 9, 9, 3}, color);
|
||||
// Right bump
|
||||
painter.fill_rectangle({x + 13, y + 5, 4, 2}, color);
|
||||
painter.fill_rectangle({x + 12, y + 6, 6, 2}, color);
|
||||
painter.fill_rectangle({x + 11, y + 7, 8, 3}, color);
|
||||
painter.fill_rectangle({x + 11, y + 9, 9, 3}, color);
|
||||
// Body
|
||||
painter.fill_rectangle({x + 1, y + 11, 18, 3}, color);
|
||||
painter.fill_rectangle({x + 2, y + 14, 16, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 16, 14, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 18, 12, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 19, 10, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 20, 8, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 21, 6, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 22, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 23, 2, 1}, color);
|
||||
break;
|
||||
|
||||
case 2: // Diamonds
|
||||
painter.fill_rectangle({x + 10, y + 3, 1, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 5, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 6, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 7, 9, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 8, 11, 1}, color);
|
||||
painter.fill_rectangle({x + 4, y + 9, 13, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 10, 15, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 11, 17, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 12, 19, 1}, color);
|
||||
painter.fill_rectangle({x + 0, y + 13, 21, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 14, 19, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 15, 17, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 16, 15, 1}, color);
|
||||
painter.fill_rectangle({x + 4, y + 17, 13, 1}, color);
|
||||
painter.fill_rectangle({x + 5, y + 18, 11, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 19, 9, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 8, y + 21, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 9, y + 22, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 10, y + 23, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 3: // Clubs
|
||||
// Center circle
|
||||
painter.fill_rectangle({x + 8, y + 8, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 9, 7, 3}, color);
|
||||
painter.fill_rectangle({x + 8, y + 12, 5, 1}, color);
|
||||
// Left circle
|
||||
painter.fill_rectangle({x + 3, y + 11, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 12, 6, 3}, color);
|
||||
painter.fill_rectangle({x + 3, y + 15, 4, 1}, color);
|
||||
// Right circle
|
||||
painter.fill_rectangle({x + 14, y + 11, 4, 1}, color);
|
||||
painter.fill_rectangle({x + 13, y + 12, 6, 3}, color);
|
||||
painter.fill_rectangle({x + 14, y + 15, 4, 1}, color);
|
||||
// Connect circles
|
||||
painter.fill_rectangle({x + 6, y + 13, 9, 2}, color);
|
||||
// Stem
|
||||
painter.fill_rectangle({x + 9, y + 16, 3, 4}, color);
|
||||
painter.fill_rectangle({x + 8, y + 19, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 7, y + 20, 7, 1}, color);
|
||||
painter.fill_rectangle({x + 6, y + 21, 9, 1}, color);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Small suit symbols
|
||||
switch (suit) {
|
||||
case 0: // Spades - small
|
||||
painter.fill_rectangle({x + 2, y + 1, 3, 3}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 5, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 2}, color);
|
||||
break;
|
||||
|
||||
case 1: // Hearts - small
|
||||
painter.fill_rectangle({x + 1, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 2, 5, 2}, color);
|
||||
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 2: // Diamonds - small
|
||||
painter.fill_rectangle({x + 3, y + 1, 1, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 2, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 5, 1}, color);
|
||||
painter.fill_rectangle({x + 2, y + 4, 3, 1}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 1, 1}, color);
|
||||
break;
|
||||
|
||||
case 3: // Clubs - small
|
||||
painter.fill_rectangle({x + 3, y + 1, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 1, y + 3, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 4, y + 3, 2, 2}, color);
|
||||
painter.fill_rectangle({x + 3, y + 5, 2, 2}, color);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_card(int x, int y, uint8_t card, bool hidden) {
|
||||
const int width = 60;
|
||||
const int height = 80;
|
||||
|
||||
// Draw card background
|
||||
painter.fill_rectangle({x, y, width, height}, Color::white());
|
||||
painter.draw_rectangle({x, y, width, height}, Color::black());
|
||||
painter.draw_rectangle({x + 1, y + 1, width - 2, height - 2}, Color::grey()); // Inner border
|
||||
|
||||
if (hidden) {
|
||||
// Draw card back pattern - diagonal lines
|
||||
for (int i = 4; i < width - 4; i += 6) {
|
||||
for (int j = 4; j < height - 4; j += 6) {
|
||||
painter.fill_rectangle({x + i, y + j, 3, 3}, Color::blue());
|
||||
painter.fill_rectangle({x + i + 3, y + j + 3, 3, 3}, Color::red());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Draw card value
|
||||
uint8_t suit = get_card_suit(card);
|
||||
Color suit_color = (suit == 1 || suit == 2) ? Color::red() : Color::black();
|
||||
|
||||
const auto* base_style = ui::Theme::getInstance()->fg_light;
|
||||
Style card_style{
|
||||
.font = base_style->font,
|
||||
.background = Color::white(),
|
||||
.foreground = suit_color};
|
||||
|
||||
std::string value_str = get_card_string(card);
|
||||
|
||||
// Draw value in top-left corner
|
||||
painter.draw_string({x + 4, y + 4}, card_style, value_str);
|
||||
|
||||
// Draw small suit symbol next to value
|
||||
int suit_x = (value_str == "10") ? x + 20 : x + 12;
|
||||
draw_suit_symbol(suit_x, y + 4, suit, suit_color, false);
|
||||
|
||||
// Draw value in bottom-right corner
|
||||
int bottom_x = (value_str == "10") ? x + width - 24 : x + width - 16;
|
||||
painter.draw_string({bottom_x, y + height - 18}, card_style, value_str);
|
||||
|
||||
// Draw small suit symbol in bottom-right
|
||||
draw_suit_symbol(x + width - 10, y + height - 16, suit, suit_color, false);
|
||||
|
||||
// Draw large suit symbol in center
|
||||
draw_suit_symbol(x + 20, y + 28, suit, suit_color, true);
|
||||
}
|
||||
}
|
||||
|
||||
void BlackjackView::draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer) {
|
||||
// Calculate total width needed
|
||||
const int card_width = 60;
|
||||
const int overlap = 40; // Amount of overlap when cards need to fit
|
||||
const int max_width = 230 - x; // Available width on screen
|
||||
|
||||
int spacing;
|
||||
if (count == 1) {
|
||||
spacing = 0;
|
||||
} else if (count == 2) {
|
||||
spacing = card_width + 5; // Small gap for 2 cards
|
||||
} else {
|
||||
// Calculate spacing to fit all cards
|
||||
int total_overlap_width = card_width + (count - 1) * overlap;
|
||||
if (total_overlap_width <= max_width) {
|
||||
spacing = overlap;
|
||||
} else {
|
||||
// Need more overlap to fit
|
||||
spacing = (max_width - card_width) / (count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
bool hide = is_dealer && dealer_hidden && i == 1;
|
||||
int card_x = x + (i * spacing);
|
||||
draw_card(card_x, y, cards[i], hide);
|
||||
}
|
||||
}
|
||||
|
||||
bool BlackjackView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
switch (game_state) {
|
||||
case GameState::MENU:
|
||||
if (cash < 10) {
|
||||
cash = 100; // Reset if broke - maybe should provide https://gamblersanonymous.org/ link if they also lost their wife and house
|
||||
wins = 0;
|
||||
losses = 0;
|
||||
}
|
||||
game_state = GameState::BETTING;
|
||||
break;
|
||||
|
||||
case GameState::BETTING:
|
||||
deal_cards();
|
||||
break;
|
||||
|
||||
case GameState::PLAYING:
|
||||
player_hit();
|
||||
break;
|
||||
|
||||
case GameState::GAME_OVER:
|
||||
// Deal new hand with current bet
|
||||
if (cash >= bet) {
|
||||
deal_cards();
|
||||
} else if (cash >= 10) {
|
||||
// Not enough for current bet, reduce to what they can afford
|
||||
bet = 10;
|
||||
deal_cards();
|
||||
} else {
|
||||
// Broke, reset game
|
||||
cash = 100;
|
||||
wins = 0;
|
||||
losses = 0;
|
||||
game_state = GameState::MENU;
|
||||
draw_menu_static();
|
||||
}
|
||||
break;
|
||||
|
||||
case GameState::STATS:
|
||||
game_state = GameState::MENU;
|
||||
draw_menu_static();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
} else if (key == KeyEvent::Left) {
|
||||
if (game_state == GameState::MENU) {
|
||||
game_state = GameState::STATS;
|
||||
}
|
||||
return true;
|
||||
} else if (key == KeyEvent::Right) {
|
||||
if (game_state == GameState::MENU) {
|
||||
nav_.pop();
|
||||
return true;
|
||||
} else if (game_state == GameState::PLAYING) {
|
||||
player_stay();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlackjackView::on_encoder(const EncoderEvent delta) {
|
||||
if (game_state == GameState::BETTING || game_state == GameState::GAME_OVER) {
|
||||
if (delta > 0 && bet + 10 <= cash) {
|
||||
bet += 10;
|
||||
} else if (delta < 0 && bet >= 20) {
|
||||
bet -= 10;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::blackjack
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Blackjack Game for Portapack Mayhem
|
||||
* Ported / Enhanced / Graphically made awesome by RocketGod (https://betaskynet.com)
|
||||
* Based on BlackJack 83 for TI Calculator by Harper Maddox (was written in Assembly)
|
||||
*/
|
||||
|
||||
#ifndef __UI_BLACKJACK_H__
|
||||
#define __UI_BLACKJACK_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ui::external_app::blackjack {
|
||||
|
||||
// Game states
|
||||
enum class GameState {
|
||||
MENU,
|
||||
BETTING,
|
||||
PLAYING,
|
||||
DEALER_TURN,
|
||||
GAME_OVER,
|
||||
STATS
|
||||
};
|
||||
|
||||
// Card structure
|
||||
struct Card {
|
||||
uint8_t value; // 1-13 (Ace=1, Jack=11, Queen=12, King=13)
|
||||
uint8_t suit; // 0=Spades, 1=Hearts, 2=Diamonds, 3=Clubs
|
||||
|
||||
Card()
|
||||
: value(0), suit(0) {}
|
||||
Card(uint8_t v, uint8_t s)
|
||||
: value(v), suit(s) {}
|
||||
};
|
||||
|
||||
// Timer class
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
// Function declarations
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
class BlackjackView : public View {
|
||||
public:
|
||||
BlackjackView(NavigationView& nav);
|
||||
~BlackjackView();
|
||||
|
||||
void on_show() override;
|
||||
std::string title() const override { return "Blackjack"; }
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
// Public for timer callback
|
||||
GameState game_state = GameState::MENU;
|
||||
void update_game();
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
bool initialized = false;
|
||||
|
||||
// Game variables
|
||||
static constexpr uint8_t MAX_CARDS_IN_HAND = 11; // Maximum possible cards before bust
|
||||
uint8_t deck[52];
|
||||
uint8_t deck_position = 0;
|
||||
|
||||
uint8_t player_cards[MAX_CARDS_IN_HAND];
|
||||
uint8_t player_card_count = 0;
|
||||
|
||||
uint8_t dealer_cards[MAX_CARDS_IN_HAND];
|
||||
uint8_t dealer_card_count = 0;
|
||||
|
||||
// Game state variables
|
||||
uint32_t cash = 100;
|
||||
uint32_t bet = 10;
|
||||
uint32_t wins = 0;
|
||||
uint32_t losses = 0;
|
||||
uint32_t high_score = 100;
|
||||
bool dealer_hidden = true;
|
||||
|
||||
// UI state
|
||||
uint8_t menu_selection = 0;
|
||||
bool blink_state = true;
|
||||
uint32_t blink_counter = 0;
|
||||
uint32_t dealer_timer = 0;
|
||||
|
||||
// Timer
|
||||
Ticker game_timer;
|
||||
|
||||
// Methods
|
||||
void init_deck();
|
||||
void shuffle_deck();
|
||||
uint8_t draw_card();
|
||||
void deal_cards();
|
||||
int calculate_hand_value(uint8_t* cards, uint8_t count);
|
||||
int get_card_value(uint8_t card);
|
||||
uint8_t get_card_suit(uint8_t card);
|
||||
std::string get_card_string(uint8_t card);
|
||||
void player_hit();
|
||||
void player_stay();
|
||||
void dealer_turn();
|
||||
void check_game_over();
|
||||
void reset_game();
|
||||
|
||||
// Drawing methods
|
||||
void draw_menu();
|
||||
void draw_menu_static();
|
||||
void draw_stats();
|
||||
void draw_game();
|
||||
void draw_betting();
|
||||
void draw_card(int x, int y, uint8_t card, bool hidden = false);
|
||||
void draw_hand(int x, int y, uint8_t* cards, uint8_t count, bool is_dealer = false);
|
||||
void draw_suit_symbol(int x, int y, uint8_t suit, Color color, bool large);
|
||||
void clear_screen();
|
||||
|
||||
// Settings
|
||||
app_settings::SettingsManager settings_{
|
||||
"blackjack",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"cash"sv, &cash},
|
||||
{"wins"sv, &wins},
|
||||
{"losses"sv, &losses},
|
||||
{"highscore"sv, &high_score}}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::blackjack
|
||||
|
||||
#endif /* __UI_BLACKJACK_H__ */
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_dinogame.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<DinoGameView>();
|
||||
}
|
||||
} // namespace ui::external_app::dinogame
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_dinogame.application_information"), used)) application_information_t _application_information_dinogame = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::dinogame::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Dino Game",
|
||||
{
|
||||
// Cactus icon 16x16
|
||||
0x00, 0x00, // ................
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x98, 0x19, // ...##..##..##...
|
||||
0x98, 0x19, // ...##..##..##...
|
||||
0x98, 0x19, // ...##..##..##...
|
||||
0x98, 0x19, // ...##..##..##...
|
||||
0xF8, 0x1F, // ...##########...
|
||||
0xF0, 0x0F, // ....########....
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x80, 0x01, // .......##.......
|
||||
0x00, 0x00, // ................
|
||||
},
|
||||
ui::Color::green().v,
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
standing - 34x36
|
||||
ducking - 46x24
|
||||
*/
|
||||
const uint16_t dino_default[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_leftstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_rightstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_ducking_leftstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_ducking_rightstep[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0050 (80)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00C0 (192)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x00F0 (240)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0170 (368)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01A0 (416)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01C0 (448)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01E0 (480)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0250 (592)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0280 (640)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0310 (784)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03A0 (928)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t dino_gameover[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0040 (64)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0060 (96)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0080 (128)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0xFFFF, 0x528A, // 0x00A0 (160)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x00E0 (224)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0130 (304)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0140 (320)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0150 (336)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0160 (352)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0180 (384)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0190 (400)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0200 (512)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0260 (608)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0270 (624)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0290 (656)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02B0 (688)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02D0 (720)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x02E0 (736)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x02F0 (752)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0300 (768)
|
||||
0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0320 (800)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0380 (896)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x03A0 (928)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03B0 (944)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03C0 (960)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03E0 (992)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0400 (1024)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0410 (1040)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0420 (1056)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0430 (1072)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0440 (1088)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0450 (1104)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0460 (1120)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0470 (1136)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0480 (1152)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0490 (1168)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04B0 (1200)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04C0 (1216)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
@@ -0,0 +1,121 @@
|
||||
// 34x27
|
||||
|
||||
const uint16_t pterodactyl_upflop[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0030 (48)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, // 0x00E0 (224)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0100 (256)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0120 (288)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0360 (864)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
|
||||
const uint16_t pterodactyl_downflop[] PROGMEM = {
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0040 (64)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0070 (112)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0080 (128)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x0090 (144)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, // 0x00B0 (176)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00C0 (192)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x00D0 (208)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00E0 (224)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0100 (256)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
|
||||
0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0140 (320)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0160 (352)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0180 (384)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0190 (400)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01B0 (432)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01C0 (448)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01D0 (464)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01E0 (480)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x01F0 (496)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0200 (512)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0210 (528)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0220 (544)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0230 (560)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0240 (576)
|
||||
0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0250 (592)
|
||||
0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0260 (608)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0270 (624)
|
||||
0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, // 0x0280 (640)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02A0 (672)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02C0 (704)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02D0 (720)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02F0 (752)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0300 (768)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0310 (784)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, 0xFFFF, 0xFFFF, // 0x0320 (800)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, 0x528A, 0x528A, // 0x0340 (832)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x528A, // 0x0360 (864)
|
||||
0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0370 (880)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0380 (896)
|
||||
0xFFFF, 0x528A, 0x528A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0390 (912)
|
||||
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
|
||||
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#include "ui_dinogame.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
|
||||
#define PROGMEM
|
||||
#include "sprites/dino.h"
|
||||
#include "sprites/pterodactyl.h"
|
||||
|
||||
#undef PROGMEM
|
||||
|
||||
// Global variables
|
||||
Ticker game_timer;
|
||||
Painter painter;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
static DinoGameView* current_instance = nullptr;
|
||||
|
||||
const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
// Drawing functions
|
||||
void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
}
|
||||
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
}
|
||||
|
||||
// Timer implementation
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
// String helper
|
||||
std::string DinoGameView::score_to_string(uint32_t score) {
|
||||
std::string score_s = std::to_string(score);
|
||||
if (score_s.length() < 5) {
|
||||
std::string temp = "";
|
||||
for (uint8_t i = 0; i < 5 - score_s.length(); ++i) temp += "0";
|
||||
temp += score_s;
|
||||
score_s = temp;
|
||||
}
|
||||
return score_s;
|
||||
}
|
||||
|
||||
// Game timer callback
|
||||
void game_timer_check() {
|
||||
if (current_instance) {
|
||||
if (current_instance->game_state == GameState::PLAYING) {
|
||||
current_instance->game_loop();
|
||||
} else if (current_instance->game_state == GameState::MENU) {
|
||||
current_instance->show_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DinoGameView::DinoGameView(NavigationView& nav)
|
||||
: nav_{nav}, bird_info{}, game_timer{} {
|
||||
add_children({&dummy, &button_difficulty});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
|
||||
button_difficulty.on_select = [this](Button&) {
|
||||
easy_mode = !easy_mode;
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
};
|
||||
}
|
||||
|
||||
void DinoGameView::on_show() {
|
||||
}
|
||||
|
||||
void DinoGameView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void DinoGameView::init_game() {
|
||||
game_state = GameState::MENU;
|
||||
menu_initialized = false;
|
||||
blink_state = true;
|
||||
blink_counter = 0;
|
||||
new_game();
|
||||
}
|
||||
|
||||
void DinoGameView::new_game() {
|
||||
steps = 0;
|
||||
score = 0;
|
||||
collided = false;
|
||||
ducking = false;
|
||||
duck_timer = 0;
|
||||
displayedGameOver = false;
|
||||
bird_info.inGame = false;
|
||||
bird_info.x_offset = 320;
|
||||
bird_info.y_offset = 0;
|
||||
bird_info.y_velocity = 0;
|
||||
jumping = false;
|
||||
falling = false;
|
||||
jumpHeight = 0;
|
||||
last_dino_y = DINO_Y;
|
||||
last_bird_x = -1;
|
||||
last_bird_y = -1;
|
||||
last_ducking = false;
|
||||
last_runstate = false;
|
||||
ground_offset = 0;
|
||||
speed_modifier = 0;
|
||||
runstate = false;
|
||||
score_drawn = false;
|
||||
last_score = 999999;
|
||||
obstacle_spawn_timer = 100; // Initial delay before first obstacle
|
||||
|
||||
// Initialize obstacles
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
obstacles[i].active = false;
|
||||
obstacles[i].x = -100;
|
||||
obstacles[i].last_x = -100;
|
||||
}
|
||||
|
||||
cls();
|
||||
}
|
||||
|
||||
void DinoGameView::show_menu() {
|
||||
if (!menu_initialized) {
|
||||
cls();
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_medium;
|
||||
auto style_title = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
// Draw title
|
||||
painter.draw_string({80, 60}, style_title, "DINO GAME");
|
||||
|
||||
// Draw instructions
|
||||
painter.draw_string({45, 130}, style, "SELECT: Jump/Start");
|
||||
painter.draw_string({65, 150}, style, "DOWN: Duck");
|
||||
painter.draw_string({50, 170}, style, "Avoid obstacles!");
|
||||
|
||||
// Draw high score
|
||||
draw_high_score();
|
||||
|
||||
menu_initialized = true;
|
||||
}
|
||||
|
||||
// Show difficulty button
|
||||
button_difficulty.hidden(false);
|
||||
|
||||
// Animate the menu dino
|
||||
bool menu_run_frame = (blink_counter / 15) % 2;
|
||||
|
||||
// Clear previous dino position
|
||||
fillrect(103, 90, 103 + DINO_WIDTH, 90 + DINO_HEIGHT, Black);
|
||||
|
||||
// Draw animated dino
|
||||
draw_dino_at(103, 90, false, menu_run_frame);
|
||||
|
||||
// Blinking start prompt
|
||||
auto style_prompt = *ui::Theme::getInstance()->fg_light;
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
painter.fill_rectangle({55, 258, 130, 20}, Color::black());
|
||||
if (blink_state) {
|
||||
painter.draw_string({55, 260}, style_prompt, "* PRESS SELECT *");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::show_game_over() {
|
||||
if (!displayedGameOver) {
|
||||
displayedGameOver = true;
|
||||
|
||||
// Clear the last normal dino position
|
||||
if (last_ducking) {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
|
||||
} else {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
|
||||
}
|
||||
|
||||
// Draw the game over dino sprite
|
||||
draw_dino_sprite(DINO_X, DINO_Y, dino_gameover);
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_light;
|
||||
auto style_score = *ui::Theme::getInstance()->fg_medium;
|
||||
|
||||
// Game over text
|
||||
painter.draw_string({85, 70}, style, "GAME OVER");
|
||||
|
||||
// Show final score
|
||||
std::string score_text = "SCORE: " + score_to_string(score);
|
||||
int score_x = (240 - score_text.length() * 8) / 2;
|
||||
painter.draw_string({score_x, 90}, style_score, score_text);
|
||||
|
||||
painter.draw_string({65, 110}, style, "SELECT TO RETRY");
|
||||
|
||||
// Update high score
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
painter.draw_string({55, 130}, style, "NEW HIGH SCORE!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::game_loop() {
|
||||
button_difficulty.hidden(true);
|
||||
|
||||
if (collided && game_state == GameState::PLAYING) {
|
||||
game_state = GameState::GAME_OVER;
|
||||
show_game_over();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update ground animation
|
||||
ground_offset = (ground_offset + GAME_SPEED_BASE + speed_modifier) % 20;
|
||||
|
||||
// Clear only the game area (not the whole screen to reduce flicker)
|
||||
draw_ground();
|
||||
|
||||
// Update and draw obstacles
|
||||
update_obstacles();
|
||||
|
||||
// Manage bird
|
||||
manage_bird();
|
||||
|
||||
// Handle jumping
|
||||
if (jumping) {
|
||||
if (!falling) jumpHeight += JUMP_SPEED + speed_modifier;
|
||||
if (jumpHeight > JUMP_MAX_HEIGHT && !falling) falling = true;
|
||||
if (falling) jumpHeight -= JUMP_SPEED + speed_modifier;
|
||||
if (jumpHeight < 0) {
|
||||
falling = false;
|
||||
jumping = false;
|
||||
jumpHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle auto-stand from duck
|
||||
if (ducking && duck_timer > 0) {
|
||||
duck_timer--;
|
||||
if (duck_timer == 0) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
|
||||
// Update run animation
|
||||
if (get_steps() % (10 - speed_modifier) == 0) runstate = !runstate;
|
||||
|
||||
// Draw dino with minimal redraw
|
||||
int current_dino_y = jumping ? (DINO_Y - jumpHeight) : (ducking ? DINO_DUCK_Y : DINO_Y);
|
||||
|
||||
// Clear old dino position more precisely
|
||||
if (current_dino_y != last_dino_y || runstate != last_runstate || ducking != last_ducking) {
|
||||
// Clear based on last state
|
||||
if (last_ducking) {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_DUCK_WIDTH, last_dino_y + DINO_DUCK_HEIGHT, Black);
|
||||
} else {
|
||||
fillrect(DINO_X, last_dino_y, DINO_X + DINO_WIDTH, last_dino_y + DINO_HEIGHT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw dino at new position
|
||||
if (jumping) {
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_default);
|
||||
} else if (ducking) {
|
||||
if (runstate)
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_leftstep);
|
||||
else
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_ducking_rightstep);
|
||||
} else {
|
||||
if (runstate)
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_leftstep);
|
||||
else
|
||||
draw_dino_sprite(DINO_X, current_dino_y, dino_rightstep);
|
||||
}
|
||||
|
||||
// Update last state
|
||||
last_dino_y = current_dino_y;
|
||||
last_ducking = ducking;
|
||||
last_runstate = runstate;
|
||||
|
||||
// Check collisions
|
||||
check_collision();
|
||||
|
||||
// Update score
|
||||
score = get_steps() / 10;
|
||||
if (score % 100 == 0 && score > 0 && get_steps() % 1000 == 0) {
|
||||
if (speed_modifier < 5) speed_modifier++;
|
||||
}
|
||||
|
||||
step();
|
||||
draw_current_score();
|
||||
draw_high_score();
|
||||
}
|
||||
|
||||
void DinoGameView::draw_ground() {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
|
||||
// Clear ground area
|
||||
fillrect(0, ground_y, 320, ground_y + GROUND_HEIGHT, Black);
|
||||
|
||||
// Draw ground line
|
||||
painter.draw_hline({0, ground_y}, 320, Color::white());
|
||||
painter.draw_hline({0, ground_y + 1}, 320, Color::dark_grey());
|
||||
|
||||
// Draw ground texture
|
||||
for (int x = -ground_offset; x < 320; x += 20) {
|
||||
painter.draw_hline({x, ground_y + 3}, 10, Color::dark_grey());
|
||||
painter.draw_hline({x + 5, ground_y + 5}, 5, Color::dark_grey());
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::update_obstacles() {
|
||||
// Move obstacles
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active) {
|
||||
// Clear old position
|
||||
if (obstacles[i].last_x != obstacles[i].x && obstacles[i].last_x < 320) {
|
||||
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
|
||||
}
|
||||
|
||||
obstacles[i].last_x = obstacles[i].x;
|
||||
obstacles[i].x -= GAME_SPEED_BASE + speed_modifier;
|
||||
|
||||
// Remove off-screen obstacles
|
||||
if (obstacles[i].x < -50) {
|
||||
obstacles[i].active = false;
|
||||
clear_obstacle_area(obstacles[i].last_x, obstacles[i].width + 10, obstacles[i].height + 10);
|
||||
} else {
|
||||
// Draw obstacle at new position
|
||||
draw_obstacle(obstacles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement spawn timer
|
||||
if (obstacle_spawn_timer > 0) {
|
||||
obstacle_spawn_timer -= GAME_SPEED_BASE + speed_modifier;
|
||||
}
|
||||
|
||||
// Check if we should spawn a new obstacle
|
||||
if (obstacle_spawn_timer <= 0) {
|
||||
// Try to spawn an obstacle
|
||||
bool spawned = false;
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (!obstacles[i].active) {
|
||||
obstacles[i].active = true;
|
||||
obstacles[i].x = 320;
|
||||
obstacles[i].last_x = 320;
|
||||
obstacles[i].type = rand() % 4;
|
||||
|
||||
// Set obstacle dimensions based on type
|
||||
switch (obstacles[i].type) {
|
||||
case 0: // Small cactus
|
||||
obstacles[i].width = 15;
|
||||
obstacles[i].height = 25;
|
||||
break;
|
||||
case 1: // Large cactus
|
||||
obstacles[i].width = 20;
|
||||
obstacles[i].height = 35;
|
||||
break;
|
||||
case 2: // Double cactus
|
||||
obstacles[i].width = 35;
|
||||
obstacles[i].height = 30;
|
||||
break;
|
||||
case 3: // Triple cactus
|
||||
obstacles[i].width = 45;
|
||||
obstacles[i].height = 28;
|
||||
break;
|
||||
}
|
||||
|
||||
spawned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawned) {
|
||||
// Set timer for next obstacle with random variation
|
||||
obstacle_spawn_timer = MIN_OBSTACLE_DISTANCE + (rand() % (MAX_OBSTACLE_DISTANCE - MIN_OBSTACLE_DISTANCE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::clear_obstacle_area(int x, int width, int height) {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
// Only clear the obstacle area, not the entire vertical space
|
||||
fillrect(x - 5, ground_y - height - 5, x + width + 5, ground_y, Black);
|
||||
}
|
||||
|
||||
void DinoGameView::draw_obstacle(const SimpleObstacle& obstacle) {
|
||||
if (!obstacle.active) return;
|
||||
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
int y = ground_y - obstacle.height;
|
||||
|
||||
// Draw cactus with green color
|
||||
switch (obstacle.type) {
|
||||
case 0: // Small single cactus
|
||||
fillrect(obstacle.x + 5, y, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 8, obstacle.x + 5, y + 15, Green);
|
||||
fillrect(obstacle.x + 10, y + 12, obstacle.x + 15, y + 19, Green);
|
||||
// Add darker edges for definition
|
||||
painter.draw_vline({obstacle.x + 4, y + 8}, 7, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 10, y + 12}, 7, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 1: // Large single cactus
|
||||
fillrect(obstacle.x + 7, y, obstacle.x + 13, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 10, obstacle.x + 7, y + 20, Green);
|
||||
fillrect(obstacle.x + 13, y + 15, obstacle.x + 20, y + 25, Green);
|
||||
fillrect(obstacle.x + 5, y + 5, obstacle.x + 7, y + 12, Green);
|
||||
fillrect(obstacle.x + 13, y + 8, obstacle.x + 15, y + 15, Green);
|
||||
// Add darker edges
|
||||
painter.draw_vline({obstacle.x + 6, y}, ground_y - y, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 13, y}, ground_y - y, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 2: // Double cactus
|
||||
fillrect(obstacle.x + 5, y + 5, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 12, obstacle.x + 5, y + 18, Green);
|
||||
fillrect(obstacle.x + 10, y + 15, obstacle.x + 15, y + 22, Green);
|
||||
fillrect(obstacle.x + 25, y + 10, obstacle.x + 30, y + 17, Green);
|
||||
// Darker outlines
|
||||
painter.draw_vline({obstacle.x + 4, y + 5}, ground_y - y - 5, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
|
||||
break;
|
||||
|
||||
case 3: // Triple cactus
|
||||
fillrect(obstacle.x + 5, y + 8, obstacle.x + 10, ground_y, Green);
|
||||
fillrect(obstacle.x + 20, y, obstacle.x + 25, ground_y, Green);
|
||||
fillrect(obstacle.x + 35, y + 5, obstacle.x + 40, ground_y, Green);
|
||||
fillrect(obstacle.x, y + 15, obstacle.x + 5, y + 20, Green);
|
||||
fillrect(obstacle.x + 25, y + 8, obstacle.x + 30, y + 15, Green);
|
||||
fillrect(obstacle.x + 40, y + 12, obstacle.x + 45, y + 18, Green);
|
||||
// Darker outlines
|
||||
painter.draw_vline({obstacle.x + 4, y + 8}, ground_y - y - 8, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 19, y}, ground_y - y, Color::dark_green());
|
||||
painter.draw_vline({obstacle.x + 34, y + 5}, ground_y - y - 5, Color::dark_green());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::manage_bird() {
|
||||
if (!bird_info.inGame) {
|
||||
// Only spawn bird if no obstacles are too close
|
||||
bool obstacle_nearby = false;
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active && obstacles[i].x > 200) {
|
||||
obstacle_nearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Randomly spawn bird if no nearby obstacles
|
||||
if (!obstacle_nearby && rand() % 400 == 0 && get_steps() > 100) {
|
||||
bird_info.inGame = true;
|
||||
bird_info.x_offset = 320;
|
||||
bird_info.y_offset = 0;
|
||||
bird_info.y_velocity = easy_mode ? 0 : (rand() % 3) - 1; // No vertical movement in easy mode
|
||||
bird_info.y_position = BirdPosition::DOWN; // Always spawn at standing height in easy mode
|
||||
}
|
||||
} else {
|
||||
// Calculate bird Y position
|
||||
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
|
||||
int current_y = base_y + bird_info.y_offset;
|
||||
|
||||
// Clear previous bird position
|
||||
if (last_bird_x < 320 && last_bird_x >= -BIRD_WIDTH) {
|
||||
int clear_start_x = last_bird_x;
|
||||
int clear_end_x = last_bird_x + BIRD_WIDTH;
|
||||
|
||||
if (clear_start_x < 0) clear_start_x = 0;
|
||||
if (clear_end_x > 320) clear_end_x = 320;
|
||||
|
||||
if (clear_end_x > clear_start_x) {
|
||||
fillrect(clear_start_x, last_bird_y, clear_end_x, last_bird_y + BIRD_HEIGHT, Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Move bird
|
||||
bird_info.x_offset -= GAME_SPEED_BASE + speed_modifier + 1;
|
||||
|
||||
// Update vertical position only in hard mode
|
||||
if (!easy_mode) {
|
||||
bird_info.y_offset += bird_info.y_velocity;
|
||||
if (rand() % 30 == 0) {
|
||||
bird_info.y_velocity = (rand() % 3) - 1;
|
||||
}
|
||||
|
||||
// Keep bird within bounds
|
||||
if (current_y < GAME_AREA_TOP + 10) {
|
||||
current_y = GAME_AREA_TOP + 10;
|
||||
bird_info.y_offset = current_y - base_y;
|
||||
bird_info.y_velocity = 1;
|
||||
} else if (current_y > GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10) {
|
||||
current_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - BIRD_HEIGHT - 10;
|
||||
bird_info.y_offset = current_y - base_y;
|
||||
bird_info.y_velocity = -1;
|
||||
}
|
||||
} else {
|
||||
// In easy mode, keep bird at perfect ducking height
|
||||
// Bird should hit standing dino but miss ducking dino
|
||||
current_y = DINO_Y - 10; // Position bird just above ducking height
|
||||
}
|
||||
|
||||
// Animate wings
|
||||
if (get_steps() % 15 == 0) bird_info.flop = !bird_info.flop;
|
||||
|
||||
// Draw bird only if any part is visible
|
||||
if (bird_info.x_offset > -BIRD_WIDTH && bird_info.x_offset < 320) {
|
||||
if (bird_info.flop) {
|
||||
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_upflop);
|
||||
} else {
|
||||
draw_bird_sprite(bird_info.x_offset, current_y, pterodactyl_downflop);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last position
|
||||
last_bird_x = bird_info.x_offset;
|
||||
last_bird_y = current_y;
|
||||
|
||||
// Remove bird only when completely off screen
|
||||
if (bird_info.x_offset < -BIRD_WIDTH - 5) {
|
||||
bird_info.inGame = false;
|
||||
last_bird_x = -1;
|
||||
last_bird_y = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_dino_sprite(int x, int y, const uint16_t* sprite) {
|
||||
int height, width;
|
||||
|
||||
// Determine dimensions based on which sprite we're drawing
|
||||
if (sprite == dino_ducking_leftstep || sprite == dino_ducking_rightstep) {
|
||||
height = DINO_DUCK_HEIGHT;
|
||||
width = DINO_DUCK_WIDTH;
|
||||
} else {
|
||||
height = DINO_HEIGHT;
|
||||
width = DINO_WIDTH;
|
||||
}
|
||||
|
||||
for (int dy = 0; dy < height; dy++) {
|
||||
int run_start = -1;
|
||||
uint16_t run_color = 0;
|
||||
|
||||
for (int dx = 0; dx < width; dx++) {
|
||||
uint16_t pixel = sprite[dy * width + dx];
|
||||
|
||||
if (pixel != TRANSPARENT_COLOR) {
|
||||
if (run_start == -1 || pixel != run_color) {
|
||||
// Draw previous run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
}
|
||||
run_start = dx;
|
||||
run_color = pixel;
|
||||
}
|
||||
} else {
|
||||
// Draw previous run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw final run if any
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, width - run_start, 1}, Color(run_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_bird_sprite(int x, int y, const uint16_t* sprite) {
|
||||
for (int dy = 0; dy < BIRD_HEIGHT; dy++) {
|
||||
int run_start = -1;
|
||||
uint16_t run_color = 0;
|
||||
|
||||
for (int dx = 0; dx < BIRD_WIDTH; dx++) {
|
||||
// Skip pixels that would be off-screen
|
||||
if (x + dx < 0 || x + dx >= 320) {
|
||||
// End any current run
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t pixel = sprite[dy * BIRD_WIDTH + dx];
|
||||
|
||||
if (pixel != TRANSPARENT_COLOR) {
|
||||
if (pixel == SPRITE_COLOR) {
|
||||
pixel = Color::red().v;
|
||||
}
|
||||
|
||||
if (run_start == -1 || pixel != run_color) {
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
}
|
||||
run_start = dx;
|
||||
run_color = pixel;
|
||||
}
|
||||
} else {
|
||||
if (run_start != -1) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, dx - run_start, 1}, Color(run_color));
|
||||
run_start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (run_start != -1 && x + run_start < 320) {
|
||||
int width = BIRD_WIDTH - run_start;
|
||||
if (x + run_start + width > 320) {
|
||||
width = 320 - (x + run_start);
|
||||
}
|
||||
if (width > 0) {
|
||||
painter.fill_rectangle({x + run_start, y + dy, width, 1}, Color(run_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_dino_at(int x, int y, bool is_ducking, bool run_frame) {
|
||||
if (is_ducking) {
|
||||
if (run_frame) {
|
||||
draw_dino_sprite(x, y, dino_ducking_leftstep);
|
||||
} else {
|
||||
draw_dino_sprite(x, y, dino_ducking_rightstep);
|
||||
}
|
||||
} else {
|
||||
if (run_frame) {
|
||||
draw_dino_sprite(x, y, dino_leftstep);
|
||||
} else {
|
||||
draw_dino_sprite(x, y, dino_default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::check_collision() {
|
||||
int dino_left = DINO_X;
|
||||
int dino_right = DINO_X + (ducking ? DINO_DUCK_WIDTH : DINO_WIDTH);
|
||||
int dino_top = ducking ? DINO_DUCK_Y : (DINO_Y - jumpHeight);
|
||||
int dino_bottom = dino_top + (ducking ? DINO_DUCK_HEIGHT : DINO_HEIGHT);
|
||||
|
||||
// Give a small hitbox reduction for fairness
|
||||
dino_left += 5;
|
||||
dino_right -= 5;
|
||||
dino_top += 5;
|
||||
dino_bottom -= 5;
|
||||
|
||||
// Check cactus collisions
|
||||
for (int i = 0; i < MAX_OBSTACLES; i++) {
|
||||
if (obstacles[i].active) {
|
||||
int ground_y = GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT;
|
||||
int obs_top = ground_y - obstacles[i].height;
|
||||
int obs_bottom = ground_y;
|
||||
|
||||
if (dino_right > obstacles[i].x &&
|
||||
dino_left < obstacles[i].x + obstacles[i].width &&
|
||||
dino_bottom > obs_top &&
|
||||
dino_top < obs_bottom) {
|
||||
collided = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check bird collision
|
||||
if (bird_info.inGame) {
|
||||
int base_y = (bird_info.y_position == BirdPosition::UP) ? BIRD_Y_UP : BIRD_Y_DOWN;
|
||||
int bird_y = base_y + bird_info.y_offset;
|
||||
|
||||
if (dino_right > bird_info.x_offset + 5 &&
|
||||
dino_left < bird_info.x_offset + BIRD_WIDTH - 5 &&
|
||||
dino_bottom > bird_y + 5 &&
|
||||
dino_top < bird_y + BIRD_HEIGHT - 5) {
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_current_score() {
|
||||
auto style = *ui::Theme::getInstance()->fg_medium;
|
||||
|
||||
if (last_score != score) {
|
||||
painter.fill_rectangle({10, 28, 60, 14}, Color::black());
|
||||
painter.draw_string({10, 30}, style, score_to_string(score));
|
||||
last_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::draw_high_score() {
|
||||
auto style = *ui::Theme::getInstance()->fg_light;
|
||||
painter.fill_rectangle({152, 28, 100, 14}, Color::black());
|
||||
painter.draw_string({152, 30}, style, "HI " + score_to_string(highScore));
|
||||
}
|
||||
|
||||
void DinoGameView::jump() {
|
||||
if (!jumping) {
|
||||
jumping = true;
|
||||
// If we're ducking when we jump, stand up
|
||||
if (ducking) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::duck() {
|
||||
if (!jumping) {
|
||||
ducking = true;
|
||||
duck_timer = 60; // 1 second at 60 FPS
|
||||
}
|
||||
}
|
||||
|
||||
void DinoGameView::stand() {
|
||||
ducking = false;
|
||||
duck_timer = 0;
|
||||
}
|
||||
|
||||
void DinoGameView::step() {
|
||||
++steps;
|
||||
}
|
||||
|
||||
bool DinoGameView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == GameState::MENU) {
|
||||
game_state = GameState::PLAYING;
|
||||
new_game();
|
||||
draw_high_score();
|
||||
} else if (game_state == GameState::PLAYING && !collided) {
|
||||
jump();
|
||||
} else if (game_state == GameState::GAME_OVER || collided) {
|
||||
game_state = GameState::PLAYING; // Restart immediately
|
||||
new_game();
|
||||
draw_high_score();
|
||||
}
|
||||
} else if (key == KeyEvent::Down) {
|
||||
if (game_state == GameState::PLAYING && !collided) {
|
||||
duck();
|
||||
}
|
||||
} else if (key == KeyEvent::Up) {
|
||||
if (game_state == GameState::PLAYING && !collided) {
|
||||
stand();
|
||||
}
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DinoGameView::on_encoder(const EncoderEvent delta) {
|
||||
(void)delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::dinogame
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chrome Dino Game for Portapack Mayhem
|
||||
* Based on the original DinoGame by various contributors
|
||||
*/
|
||||
|
||||
#ifndef __UI_DINOGAME_H__
|
||||
#define __UI_DINOGAME_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "file.hpp"
|
||||
#include <string>
|
||||
#include "app_settings.hpp"
|
||||
|
||||
namespace ui::external_app::dinogame {
|
||||
|
||||
// Color definitions
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
// Game constants
|
||||
#define DINO_WIDTH 34
|
||||
#define DINO_HEIGHT 36
|
||||
#define DINO_DUCK_WIDTH 45
|
||||
#define DINO_DUCK_HEIGHT 22
|
||||
#define BIRD_WIDTH 34
|
||||
#define BIRD_HEIGHT 27
|
||||
#define GROUND_HEIGHT 10
|
||||
#define GAME_AREA_TOP 78
|
||||
#define GAME_AREA_HEIGHT 160
|
||||
#define DINO_X 30
|
||||
#define DINO_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_HEIGHT)
|
||||
#define DINO_DUCK_Y (GAME_AREA_TOP + GAME_AREA_HEIGHT - GROUND_HEIGHT - DINO_DUCK_HEIGHT)
|
||||
#define BIRD_Y_UP (GAME_AREA_TOP + 20)
|
||||
#define BIRD_Y_DOWN (GAME_AREA_TOP + 60)
|
||||
#define JUMP_MAX_HEIGHT 70
|
||||
#define JUMP_SPEED 3
|
||||
#define GAME_SPEED_BASE 3
|
||||
#define SPRITE_COLOR 0x528A
|
||||
#define TRANSPARENT_COLOR 0xFFFF
|
||||
#define MIN_OBSTACLE_DISTANCE 300
|
||||
#define MAX_OBSTACLE_DISTANCE 600
|
||||
|
||||
// Game states
|
||||
enum class GameState {
|
||||
MENU,
|
||||
PLAYING,
|
||||
GAME_OVER
|
||||
};
|
||||
|
||||
// Walk styles
|
||||
enum class WalkStyle {
|
||||
WALKING,
|
||||
DUCKING
|
||||
};
|
||||
|
||||
// Bird position
|
||||
enum class BirdPosition {
|
||||
UP,
|
||||
DOWN
|
||||
};
|
||||
|
||||
// Structures
|
||||
struct Pterodactyl {
|
||||
bool inGame = false;
|
||||
BirdPosition y_position = BirdPosition::UP;
|
||||
int16_t x_offset = 320;
|
||||
int16_t y_offset = 0;
|
||||
int8_t y_velocity = 0;
|
||||
bool flop = false;
|
||||
};
|
||||
|
||||
// Simple obstacle structure with proper initialization
|
||||
struct SimpleObstacle {
|
||||
bool active;
|
||||
int16_t x;
|
||||
int16_t last_x;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t type; // 0=small, 1=large, 2=double, 3=triple
|
||||
|
||||
SimpleObstacle()
|
||||
: active(false), x(0), last_x(0), width(0), height(0), type(0) {}
|
||||
};
|
||||
|
||||
// External references to sprite data
|
||||
extern const uint16_t dino_default[];
|
||||
extern const uint16_t dino_leftstep[];
|
||||
extern const uint16_t dino_rightstep[];
|
||||
extern const uint16_t dino_ducking_leftstep[];
|
||||
extern const uint16_t dino_ducking_rightstep[];
|
||||
extern const uint16_t dino_gameover[];
|
||||
extern const uint16_t pterodactyl_upflop[];
|
||||
extern const uint16_t pterodactyl_downflop[];
|
||||
|
||||
// Global painter
|
||||
extern Painter painter;
|
||||
|
||||
// Function declarations
|
||||
void cls();
|
||||
void fillrect(int x1, int y1, int x2, int y2, int color);
|
||||
void rect(int x1, int y1, int x2, int y2, int color);
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
// Ticker class
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
// Main view class
|
||||
class DinoGameView : public View {
|
||||
public:
|
||||
DinoGameView(NavigationView& nav);
|
||||
void on_show() override;
|
||||
|
||||
std::string title() const override { return "Dino Game"; }
|
||||
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
// Public for timer callback
|
||||
GameState game_state = GameState::MENU;
|
||||
void game_loop();
|
||||
void show_menu();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
|
||||
// Game variables
|
||||
static constexpr uint8_t MAX_OBSTACLES = 1;
|
||||
SimpleObstacle obstacles[MAX_OBSTACLES];
|
||||
Pterodactyl bird_info;
|
||||
|
||||
int16_t jumpHeight = 0;
|
||||
uint8_t ground_offset = 0;
|
||||
uint8_t speed_modifier = 0;
|
||||
bool runstate = false;
|
||||
bool jumping = false;
|
||||
bool falling = false;
|
||||
bool collided = false;
|
||||
bool ducking = false;
|
||||
bool displayedGameOver = false;
|
||||
bool last_ducking = false;
|
||||
bool last_runstate = false;
|
||||
|
||||
uint32_t steps = 0;
|
||||
uint32_t score = 0;
|
||||
uint32_t highScore = 0;
|
||||
uint32_t last_score = 999999; // Initialize to impossible value
|
||||
int32_t next_obstacle_distance = 100;
|
||||
int16_t last_obstacle_x = 320; // Track position of last spawned obstacle
|
||||
int32_t obstacle_spawn_timer = 0;
|
||||
|
||||
// Position tracking for minimal redraw
|
||||
int16_t last_dino_y = DINO_Y;
|
||||
int16_t last_bird_x = -1;
|
||||
int16_t last_bird_y = -1;
|
||||
uint8_t duck_timer = 0;
|
||||
|
||||
// Menu animation
|
||||
bool menu_initialized = false;
|
||||
bool blink_state = true;
|
||||
uint32_t blink_counter = 0;
|
||||
|
||||
// Current Score
|
||||
bool score_drawn = false;
|
||||
|
||||
// Game timer
|
||||
Ticker game_timer;
|
||||
|
||||
// Private methods
|
||||
void init_game();
|
||||
void new_game();
|
||||
void update_obstacles();
|
||||
void spawn_obstacle();
|
||||
void draw_screen();
|
||||
void draw_ground();
|
||||
void draw_obstacle(const SimpleObstacle& obstacle);
|
||||
void clear_obstacle_area(int x, int width, int height);
|
||||
void draw_dino_sprite(int x, int y, const uint16_t* sprite);
|
||||
void draw_bird_sprite(int x, int y, const uint16_t* sprite);
|
||||
void draw_dino_at(int x, int y, bool is_ducking, bool run_frame);
|
||||
void manage_bird();
|
||||
void draw_score();
|
||||
void draw_high_score();
|
||||
void draw_current_score();
|
||||
void show_game_over();
|
||||
void jump();
|
||||
void duck();
|
||||
void stand();
|
||||
void check_collision();
|
||||
void step();
|
||||
uint32_t get_steps() { return steps; }
|
||||
std::string score_to_string(uint32_t score);
|
||||
|
||||
bool easy_mode = false;
|
||||
|
||||
Button button_difficulty{
|
||||
{70, 195, 100, 20},
|
||||
"Mode: HARD"};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"dinogame",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"highscore"sv, &highScore},
|
||||
{"easy_mode"sv, &easy_mode}}};
|
||||
|
||||
Button dummy{
|
||||
{screen_width, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::dinogame
|
||||
|
||||
#endif /* __UI_DINOGAME_H__ */
|
||||
+20
@@ -193,6 +193,10 @@ set(EXTCPPSRC
|
||||
external/breakout/main.cpp
|
||||
external/breakout/ui_breakout.cpp
|
||||
|
||||
#dinogame
|
||||
external/dinogame/main.cpp
|
||||
external/dinogame/ui_dinogame.cpp
|
||||
|
||||
#doom
|
||||
external/doom/main.cpp
|
||||
external/doom/ui_doom.cpp
|
||||
@@ -216,6 +220,18 @@ set(EXTCPPSRC
|
||||
#detector_rx
|
||||
external/detector_rx/main.cpp
|
||||
external/detector_rx/ui_detector_rx.cpp
|
||||
|
||||
#space_invaders
|
||||
external/spaceinv/main.cpp
|
||||
external/spaceinv/ui_spaceinv.cpp
|
||||
|
||||
#blackjack
|
||||
external/blackjack/main.cpp
|
||||
external/blackjack/ui_blackjack.cpp
|
||||
|
||||
#battleship
|
||||
external/battleship/main.cpp
|
||||
external/battleship/ui_battleship.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -265,10 +281,14 @@ set(EXTAPPLIST
|
||||
snake
|
||||
stopwatch
|
||||
breakout
|
||||
dinogame
|
||||
doom
|
||||
debug_pmem
|
||||
scanner
|
||||
level
|
||||
gfxeq
|
||||
detector_rx
|
||||
spaceinv
|
||||
blackjack
|
||||
battleship
|
||||
)
|
||||
|
||||
+27
@@ -75,6 +75,10 @@ MEMORY
|
||||
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
|
||||
ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
|
||||
ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k
|
||||
ram_external_app_dinogame (rwx) : org = 0xADE50000, len = 32k
|
||||
ram_external_app_spaceinv (rwx) : org = 0xADE60000, len = 32k
|
||||
ram_external_app_blackjack (rwx) : org = 0xADE70000, len = 32k
|
||||
ram_external_app_battleship (rwx) : org = 0xADE80000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -391,6 +395,29 @@ SECTIONS
|
||||
*(*ui*external_app*detector_rx*);
|
||||
} > ram_external_app_detector_rx
|
||||
|
||||
.external_app_dinogame : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_dinogame.application_information));
|
||||
*(*ui*external_app*dinogame*);
|
||||
} > ram_external_app_dinogame
|
||||
|
||||
.external_app_spaceinv : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_spaceinv.application_information));
|
||||
*(*ui*external_app*spaceinv*);
|
||||
} > ram_external_app_spaceinv
|
||||
|
||||
.external_app_blackjack : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_blackjack.application_information));
|
||||
*(*ui*external_app*blackjack*);
|
||||
} > ram_external_app_blackjack
|
||||
|
||||
.external_app_battleship : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_battleship.application_information));
|
||||
*(*ui*external_app*battleship*);
|
||||
} > ram_external_app_battleship
|
||||
|
||||
}
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_spaceinv.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<SpaceInvadersView>();
|
||||
}
|
||||
} // namespace ui::external_app::spaceinv
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_spaceinv.application_information"), used)) application_information_t _application_information_spaceinv = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::spaceinv::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
|
||||
"Space Invaders",
|
||||
{
|
||||
// Space Invader alien icon 16x16
|
||||
0x00, 0x18, // .......##.......
|
||||
0x00, 0x3C, // ......####......
|
||||
0x00, 0x7E, // .....######.....
|
||||
0x00, 0xDB, // ....##.##.##....
|
||||
0x00, 0xFF, // ....########....
|
||||
0x00, 0xFF, // ....########....
|
||||
0x00, 0x24, // ......#..#......
|
||||
0x00, 0x66, // .....##..##.....
|
||||
0x00, 0x42, // .....#....#.....
|
||||
0x00, 0x00, // ................
|
||||
0x00, 0x24, // ......#..#......
|
||||
0x00, 0x18, // .......##.......
|
||||
0x00, 0x24, // ......#..#......
|
||||
0x00, 0x42, // .....#....#.....
|
||||
0x00, 0x81, // ....#......#....
|
||||
0x00, 0x00, // ................
|
||||
},
|
||||
ui::Color::magenta().v,
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
} // namespace ui::external_app::spaceinv
|
||||
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_spaceinv.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
|
||||
// Game constants
|
||||
#define PLAYER_WIDTH 26
|
||||
#define PLAYER_HEIGHT 16
|
||||
#define PLAYER_Y 280
|
||||
#define BULLET_WIDTH 3
|
||||
#define BULLET_HEIGHT 10
|
||||
#define BULLET_SPEED 8
|
||||
#define MAX_BULLETS 3
|
||||
#define INVADER_WIDTH 20
|
||||
#define INVADER_HEIGHT 16
|
||||
#define INVADER_ROWS 5 // Classic 5 rows
|
||||
#define INVADER_COLS 6 // Reduced for more movement space on narrow PP screen
|
||||
#define INVADER_GAP_X 10
|
||||
#define INVADER_GAP_Y 8 // Gap between invaders
|
||||
#define MAX_ENEMY_BULLETS 3
|
||||
#define ENEMY_BULLET_SPEED 3
|
||||
|
||||
// Game state
|
||||
static int game_state = 0; // 0=menu, 1=playing, 2=game over, 3=wave_complete
|
||||
static bool initialized = false;
|
||||
static int player_x = 120;
|
||||
static uint32_t score = 0;
|
||||
static int lives = 3;
|
||||
static uint32_t wave = 1;
|
||||
static uint32_t speed_bonus = 0;
|
||||
static uint32_t wave_complete_timer = 0;
|
||||
|
||||
// Cannon Bullets
|
||||
static int bullet_x[MAX_BULLETS] = {0, 0, 0};
|
||||
static int bullet_y[MAX_BULLETS] = {0, 0, 0};
|
||||
static bool bullet_active[MAX_BULLETS] = {false, false, false};
|
||||
|
||||
// Enemy bullets
|
||||
static int enemy_bullet_x[MAX_ENEMY_BULLETS] = {0, 0, 0};
|
||||
static int enemy_bullet_y[MAX_ENEMY_BULLETS] = {0, 0, 0};
|
||||
static bool enemy_bullet_active[MAX_ENEMY_BULLETS] = {false, false, false};
|
||||
static uint32_t enemy_fire_counter = 0;
|
||||
|
||||
// Invaders
|
||||
static bool invaders[INVADER_ROWS][INVADER_COLS];
|
||||
static int invaders_x = 40;
|
||||
static int invaders_y = 60;
|
||||
static int invader_direction = 1;
|
||||
static uint32_t invader_move_counter = 0;
|
||||
static uint32_t invader_move_delay = 30;
|
||||
static bool invader_animation_frame = false;
|
||||
|
||||
// Menu state
|
||||
static bool menu_initialized = false;
|
||||
static bool game_over_initialized = false;
|
||||
static bool blink_state = true;
|
||||
static uint32_t blink_counter = 0;
|
||||
static int16_t prompt_x = 0;
|
||||
|
||||
// Timer
|
||||
static Ticker game_timer;
|
||||
static Callback game_update_callback = nullptr;
|
||||
static uint32_t game_update_timeout = 0;
|
||||
static uint32_t game_update_counter = 0;
|
||||
|
||||
// Painter
|
||||
static Painter painter;
|
||||
|
||||
// For high score access
|
||||
static SpaceInvadersView* current_instance = nullptr;
|
||||
|
||||
void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ticker::attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
|
||||
void Ticker::detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
static void init_invaders() {
|
||||
// Initialize invader array
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
invaders[row][col] = true;
|
||||
}
|
||||
}
|
||||
invaders_x = 40;
|
||||
invaders_y = 60;
|
||||
invader_direction = 1;
|
||||
invader_move_counter = 0;
|
||||
|
||||
// Clear any active enemy bullets
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
enemy_bullet_active[i] = false;
|
||||
}
|
||||
|
||||
// Clear any active player bullets
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
bullet_active[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void save_high_score() {
|
||||
if (current_instance && score > current_instance->highScore) {
|
||||
current_instance->highScore = score;
|
||||
// Settings are automatically saved when the view is destroyed
|
||||
}
|
||||
}
|
||||
|
||||
static void init_menu() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
|
||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
||||
|
||||
int16_t screen_width = 240;
|
||||
int16_t title_x = (screen_width - 15 * 8) / 2;
|
||||
int16_t divider_width = 24 * 8;
|
||||
int16_t divider_x = (screen_width - divider_width) / 2;
|
||||
int16_t instruction_width = 22 * 8;
|
||||
int16_t instruction_x = (screen_width - instruction_width) / 2;
|
||||
int16_t prompt_width = 12 * 8;
|
||||
prompt_x = (screen_width - prompt_width) / 2;
|
||||
|
||||
painter.fill_rectangle({0, 30, screen_width, 30}, Color::black());
|
||||
painter.draw_string({title_x, 42}, style_green, "SPACE INVADERS");
|
||||
|
||||
painter.draw_string({divider_x, 70}, style_yellow, "========================");
|
||||
|
||||
painter.fill_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::black());
|
||||
painter.draw_rectangle({instruction_x - 5, 100, instruction_width + 10, 80}, Color::white());
|
||||
|
||||
painter.draw_string({instruction_x, 110}, style_cyan, " ROTARY: MOVE SHIP");
|
||||
painter.draw_string({instruction_x, 130}, style_cyan, " SELECT: FIRE");
|
||||
painter.draw_string({instruction_x, 150}, style_cyan, " DEFEND EARTH!");
|
||||
|
||||
// Draw point values
|
||||
auto style_purple = *ui::Theme::getInstance()->fg_magenta;
|
||||
painter.draw_string({50, 190}, style_purple, "TOP ROW = 30 PTS");
|
||||
painter.draw_string({50, 205}, style_yellow, "MID ROW = 20 PTS");
|
||||
painter.draw_string({50, 220}, style_green, "BOT ROW = 10 PTS");
|
||||
|
||||
// Draw high score
|
||||
if (current_instance) {
|
||||
auto style_white = *ui::Theme::getInstance()->fg_light;
|
||||
std::string high_score_text = "HIGH SCORE: " + std::to_string(current_instance->highScore);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 240}, style_white, high_score_text);
|
||||
}
|
||||
|
||||
menu_initialized = true;
|
||||
}
|
||||
|
||||
static void init_game_over() {
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
|
||||
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
|
||||
auto style_white = *ui::Theme::getInstance()->fg_light;
|
||||
|
||||
int16_t screen_width = 240;
|
||||
|
||||
// Game Over title
|
||||
int16_t title_x = (screen_width - 9 * 8) / 2;
|
||||
painter.draw_string({title_x, 42}, style_red, "GAME OVER");
|
||||
|
||||
// Divider
|
||||
int16_t divider_width = 24 * 8;
|
||||
int16_t divider_x = (screen_width - divider_width) / 2;
|
||||
painter.draw_string({divider_x, 70}, style_yellow, "========================");
|
||||
|
||||
// Score box
|
||||
int16_t box_width = 22 * 8;
|
||||
int16_t box_x = (screen_width - box_width) / 2;
|
||||
painter.fill_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::black());
|
||||
painter.draw_rectangle({box_x - 5, 100, box_width + 10, 80}, Color::white());
|
||||
|
||||
// Display scores
|
||||
std::string score_text = "SCORE: " + std::to_string(score);
|
||||
int16_t score_x = (screen_width - score_text.length() * 8) / 2;
|
||||
painter.draw_string({score_x, 120}, style_cyan, score_text);
|
||||
|
||||
std::string wave_text = "WAVE: " + std::to_string(wave);
|
||||
int16_t wave_x = (screen_width - wave_text.length() * 8) / 2;
|
||||
painter.draw_string({wave_x, 140}, style_cyan, wave_text);
|
||||
|
||||
// High score section
|
||||
if (current_instance) {
|
||||
if (score > current_instance->highScore) {
|
||||
// New high score!
|
||||
std::string new_high = "NEW HIGH SCORE!";
|
||||
int16_t new_high_x = (screen_width - new_high.length() * 8) / 2;
|
||||
painter.draw_string({new_high_x, 200}, style_yellow, new_high);
|
||||
|
||||
std::string high_score_text = std::to_string(score);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 220}, style_yellow, high_score_text);
|
||||
} else {
|
||||
// Show existing high score
|
||||
std::string high_label = "HIGH SCORE:";
|
||||
int16_t label_x = (screen_width - high_label.length() * 8) / 2;
|
||||
painter.draw_string({label_x, 200}, style_white, high_label);
|
||||
|
||||
std::string high_score_text = std::to_string(current_instance->highScore);
|
||||
int16_t high_score_x = (screen_width - high_score_text.length() * 8) / 2;
|
||||
painter.draw_string({high_score_x, 220}, style_white, high_score_text);
|
||||
}
|
||||
}
|
||||
|
||||
game_over_initialized = true;
|
||||
}
|
||||
|
||||
static void draw_player() {
|
||||
// Clear the entire player area
|
||||
painter.fill_rectangle({0, PLAYER_Y - 4, 240, PLAYER_HEIGHT + 8}, Color::black());
|
||||
|
||||
// Draw the classic Space Invaders cannon
|
||||
Color green = Color::green();
|
||||
|
||||
// Top part - the cannon barrel
|
||||
painter.draw_hline({player_x + 12, PLAYER_Y}, 2, green);
|
||||
painter.draw_hline({player_x + 12, PLAYER_Y + 1}, 2, green);
|
||||
|
||||
// Upper body
|
||||
painter.draw_hline({player_x + 11, PLAYER_Y + 2}, 4, green);
|
||||
painter.draw_hline({player_x + 11, PLAYER_Y + 3}, 4, green);
|
||||
painter.draw_hline({player_x + 10, PLAYER_Y + 4}, 6, green);
|
||||
painter.draw_hline({player_x + 10, PLAYER_Y + 5}, 6, green);
|
||||
|
||||
// Main body
|
||||
painter.draw_hline({player_x + 2, PLAYER_Y + 6}, 22, green);
|
||||
painter.draw_hline({player_x + 2, PLAYER_Y + 7}, 22, green);
|
||||
painter.draw_hline({player_x + 1, PLAYER_Y + 8}, 24, green);
|
||||
painter.draw_hline({player_x + 1, PLAYER_Y + 9}, 24, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 10}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 11}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 12}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 13}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 14}, 26, green);
|
||||
painter.draw_hline({player_x, PLAYER_Y + 15}, 26, green);
|
||||
}
|
||||
|
||||
static void draw_invader(int row, int col) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
|
||||
// Clear the area first
|
||||
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
|
||||
// Different colors and shapes for different rows
|
||||
Color color;
|
||||
if (row == 0) {
|
||||
color = Color::red(); // Top row - 30 points
|
||||
} else if (row == 1 || row == 2) {
|
||||
color = Color::yellow(); // Middle rows - 20 points
|
||||
} else {
|
||||
color = Color::green(); // Bottom rows - 10 points
|
||||
}
|
||||
|
||||
// Draw different invader types based on row
|
||||
if (row == 0) {
|
||||
// Top row - squid-like invader (30 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1 - arms down
|
||||
painter.draw_hline({x + 8, y + 2}, 4, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 4}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 5}, 16, color);
|
||||
painter.draw_hline({x + 2, y + 6}, 16, color);
|
||||
painter.draw_hline({x, y + 7}, 20, color);
|
||||
painter.draw_hline({x, y + 8}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 9}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 4, y + 10}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 10}, 2, color);
|
||||
} else {
|
||||
// Frame 2 - arms up
|
||||
painter.draw_hline({x + 8, y + 2}, 4, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 4}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 5}, 16, color);
|
||||
painter.draw_hline({x + 2, y + 6}, 16, color);
|
||||
painter.draw_hline({x, y + 7}, 20, color);
|
||||
painter.draw_hline({x, y + 8}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 8, y + 9}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 10}, 2, color);
|
||||
painter.draw_hline({x + 16, y + 10}, 2, color);
|
||||
}
|
||||
} else if (row == 1 || row == 2) {
|
||||
// Middle rows - crab-like invader (20 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1 - arms in
|
||||
painter.draw_hline({x + 4, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 6, color);
|
||||
painter.draw_hline({x + 8, y + 5}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 5}, 6, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 7}, 16, color);
|
||||
painter.draw_hline({x + 4, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 4, color);
|
||||
} else {
|
||||
// Frame 2 - arms out
|
||||
painter.draw_hline({x + 4, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 2}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 3}, 8, color);
|
||||
painter.draw_hline({x + 16, y + 3}, 2, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 6, color);
|
||||
painter.draw_hline({x + 8, y + 5}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 5}, 6, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 2, y + 7}, 16, color);
|
||||
painter.draw_hline({x + 4, y + 8}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 2, color);
|
||||
painter.draw_hline({x, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 6, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 12, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 18, y + 9}, 2, color);
|
||||
}
|
||||
} else {
|
||||
// Bottom rows - octopus-like invader (10 points)
|
||||
if (invader_animation_frame) {
|
||||
// Frame 1
|
||||
painter.draw_hline({x + 6, y + 2}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 3}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 20, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 12, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 8, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 4, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 14, y + 9}, 2, color);
|
||||
} else {
|
||||
// Frame 2
|
||||
painter.draw_hline({x + 6, y + 2}, 8, color);
|
||||
painter.draw_hline({x + 4, y + 3}, 12, color);
|
||||
painter.draw_hline({x + 2, y + 4}, 16, color);
|
||||
painter.draw_hline({x, y + 5}, 20, color);
|
||||
painter.draw_hline({x, y + 6}, 20, color);
|
||||
painter.draw_hline({x + 4, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 12, y + 7}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 8, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 14, y + 8}, 4, color);
|
||||
painter.draw_hline({x + 2, y + 9}, 2, color);
|
||||
painter.draw_hline({x + 16, y + 9}, 2, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_all_invaders() {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
draw_invader(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_all_invaders() {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
painter.fill_rectangle({x, y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_all_enemy_bullets() {
|
||||
// Clear any visible enemy bullets
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (enemy_bullet_active[i]) {
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
enemy_bullet_active[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_enemy_bullet() {
|
||||
// Find a random invader to fire from
|
||||
int active_count = 0;
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) active_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (active_count == 0) return;
|
||||
|
||||
int shooter = rand() % active_count;
|
||||
int count = 0;
|
||||
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
if (count == shooter) {
|
||||
// Fire from this invader
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (!enemy_bullet_active[i]) {
|
||||
int x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
enemy_bullet_x[i] = x + INVADER_WIDTH / 2;
|
||||
enemy_bullet_y[i] = y + INVADER_HEIGHT;
|
||||
enemy_bullet_active[i] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_enemy_bullets() {
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
if (enemy_bullet_active[i]) {
|
||||
// Clear old position - but protect the border line
|
||||
if (enemy_bullet_y[i] != 49) { // Don't clear if we're exactly on the border line
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
}
|
||||
|
||||
enemy_bullet_y[i] += ENEMY_BULLET_SPEED;
|
||||
|
||||
if (enemy_bullet_y[i] > 320) {
|
||||
enemy_bullet_active[i] = false;
|
||||
} else {
|
||||
// Draw at new position
|
||||
painter.fill_rectangle({enemy_bullet_x[i], enemy_bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::red());
|
||||
|
||||
// Check collision with player
|
||||
if (enemy_bullet_x[i] >= player_x &&
|
||||
enemy_bullet_x[i] <= player_x + PLAYER_WIDTH &&
|
||||
enemy_bullet_y[i] >= PLAYER_Y &&
|
||||
enemy_bullet_y[i] <= PLAYER_Y + PLAYER_HEIGHT) {
|
||||
// Player hit!
|
||||
enemy_bullet_active[i] = false;
|
||||
lives--;
|
||||
|
||||
// Update lives display
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.fill_rectangle({5, 30, 100, 20}, Color::black());
|
||||
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
|
||||
|
||||
if (lives <= 0) {
|
||||
game_state = 2; // Game over
|
||||
save_high_score();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_invaders() {
|
||||
uint32_t adjusted_delay = (speed_bonus >= invader_move_delay) ? 1 : invader_move_delay - speed_bonus;
|
||||
|
||||
if (++invader_move_counter >= adjusted_delay) {
|
||||
invader_move_counter = 0;
|
||||
|
||||
// Clear old positions
|
||||
clear_all_invaders();
|
||||
|
||||
// Check bounds - find the actual leftmost and rightmost invaders
|
||||
int leftmost = INVADER_COLS;
|
||||
int rightmost = -1;
|
||||
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
if (invaders[row][col]) {
|
||||
if (col < leftmost) leftmost = col;
|
||||
if (col > rightmost) rightmost = col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool hit_edge = false;
|
||||
if (invader_direction > 0) {
|
||||
// Moving right - check rightmost invader
|
||||
int right_edge = invaders_x + rightmost * (INVADER_WIDTH + INVADER_GAP_X) + INVADER_WIDTH;
|
||||
if (right_edge >= 240) {
|
||||
hit_edge = true;
|
||||
}
|
||||
} else {
|
||||
// Moving left - check leftmost invader
|
||||
int left_edge = invaders_x + leftmost * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
if (left_edge <= 0) {
|
||||
hit_edge = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Move invaders
|
||||
if (hit_edge) {
|
||||
invaders_y += 15; // Move down
|
||||
invader_direction = -invader_direction;
|
||||
} else {
|
||||
invaders_x += invader_direction * 8; // Horizontal movement
|
||||
}
|
||||
|
||||
// Toggle animation frame
|
||||
invader_animation_frame = !invader_animation_frame;
|
||||
|
||||
// Draw new positions
|
||||
draw_all_invaders();
|
||||
}
|
||||
|
||||
// Enemy firing logic - only in hard mode or always with lower frequency
|
||||
if (!current_instance || !current_instance->easy_mode) {
|
||||
if (++enemy_fire_counter >= 120) { // Fire every 2 seconds at 60fps
|
||||
enemy_fire_counter = 0;
|
||||
fire_enemy_bullet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fire_bullet() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (!bullet_active[i]) {
|
||||
bullet_active[i] = true;
|
||||
bullet_x[i] = player_x + PLAYER_WIDTH / 2 - BULLET_WIDTH / 2;
|
||||
bullet_y[i] = PLAYER_Y - BULLET_HEIGHT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_bullets() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (bullet_active[i]) {
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
|
||||
bullet_y[i] -= BULLET_SPEED;
|
||||
|
||||
if (bullet_y[i] < 50) {
|
||||
bullet_active[i] = false;
|
||||
} else {
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::white());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_collisions() {
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
if (!bullet_active[i]) continue;
|
||||
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (!invaders[row][col]) continue;
|
||||
|
||||
int inv_x = invaders_x + col * (INVADER_WIDTH + INVADER_GAP_X);
|
||||
int inv_y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
|
||||
// Simple collision check
|
||||
if (bullet_x[i] < inv_x + INVADER_WIDTH &&
|
||||
bullet_x[i] + BULLET_WIDTH > inv_x &&
|
||||
bullet_y[i] < inv_y + INVADER_HEIGHT &&
|
||||
bullet_y[i] + BULLET_HEIGHT > inv_y) {
|
||||
// Hit!
|
||||
bullet_active[i] = false;
|
||||
painter.fill_rectangle({bullet_x[i], bullet_y[i], BULLET_WIDTH, BULLET_HEIGHT}, Color::black());
|
||||
|
||||
invaders[row][col] = false;
|
||||
painter.fill_rectangle({inv_x, inv_y, INVADER_WIDTH, INVADER_HEIGHT}, Color::black());
|
||||
|
||||
// Score based on row: top=30, middle=20, bottom=10
|
||||
if (row == 0) {
|
||||
score += 30;
|
||||
} else if (row == 1 || row == 2) {
|
||||
score += 20;
|
||||
} else {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_wave_complete() {
|
||||
// Check if all invaders are destroyed
|
||||
bool all_destroyed = true;
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
all_destroyed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!all_destroyed) break;
|
||||
}
|
||||
|
||||
if (all_destroyed) {
|
||||
// Next wave!
|
||||
wave++;
|
||||
speed_bonus = (wave - 1) * 3; // Each wave is slightly faster
|
||||
if (speed_bonus > 15) speed_bonus = 15; // Cap the speed increase
|
||||
|
||||
// Clear any enemy bullets before transitioning
|
||||
clear_all_enemy_bullets();
|
||||
|
||||
// Set state to wave complete and start timer
|
||||
game_state = 3;
|
||||
wave_complete_timer = 60; // 1 second at 60fps
|
||||
|
||||
// Clear screen and show wave message - but preserve the border line
|
||||
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line at 49
|
||||
|
||||
// Redraw the border line to ensure it's intact - stupid bullet clearing wants to damage it
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
std::string wave_text = "WAVE " + std::to_string(wave);
|
||||
int wave_x = (240 - wave_text.length() * 8) / 2;
|
||||
painter.draw_string({wave_x, 150}, style, wave_text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if invaders reached player
|
||||
for (int row = 0; row < INVADER_ROWS; row++) {
|
||||
for (int col = 0; col < INVADER_COLS; col++) {
|
||||
if (invaders[row][col]) {
|
||||
int y = invaders_y + row * (INVADER_HEIGHT + INVADER_GAP_Y);
|
||||
if (y + INVADER_HEIGHT >= PLAYER_Y) { // Actual collision with player
|
||||
game_state = 2; // Game over
|
||||
save_high_score();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void game_timer_check() {
|
||||
if (game_state == 0) {
|
||||
// Menu state
|
||||
if (!menu_initialized) {
|
||||
init_menu();
|
||||
}
|
||||
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_red;
|
||||
if (blink_state) {
|
||||
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
|
||||
} else {
|
||||
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
} else if (game_state == 1) {
|
||||
// Playing state
|
||||
update_bullets();
|
||||
update_enemy_bullets();
|
||||
update_invaders();
|
||||
check_collisions();
|
||||
check_wave_complete();
|
||||
|
||||
// Update score display
|
||||
static uint32_t last_score = 999999;
|
||||
if (score != last_score) {
|
||||
last_score = score;
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.fill_rectangle({5, 10, 100, 20}, Color::black());
|
||||
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
|
||||
|
||||
// Redraw border line after score update (in case it was damaged) - stupid bullet clearing wants to damage it
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
}
|
||||
|
||||
// Periodically redraw the border line to fix any damage because it's a pain in the ass
|
||||
static uint32_t border_redraw_counter = 0;
|
||||
if (++border_redraw_counter >= 60) { // Once per second - might make less if causing flickering
|
||||
border_redraw_counter = 0;
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
}
|
||||
} else if (game_state == 2) {
|
||||
// Game over state
|
||||
if (!game_over_initialized) {
|
||||
init_game_over();
|
||||
}
|
||||
|
||||
if (++blink_counter >= 30) {
|
||||
blink_counter = 0;
|
||||
blink_state = !blink_state;
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_red;
|
||||
if (blink_state) {
|
||||
painter.draw_string({prompt_x, 260}, style, "PRESS SELECT");
|
||||
} else {
|
||||
painter.fill_rectangle({prompt_x, 260, 16 * 8, 20}, Color::black());
|
||||
}
|
||||
}
|
||||
} else if (game_state == 3) {
|
||||
// Wave complete state - wait for timer
|
||||
if (--wave_complete_timer <= 0) {
|
||||
// Timer expired, start next wave
|
||||
game_state = 1;
|
||||
|
||||
// Clear and redraw game area - preserve border line
|
||||
painter.fill_rectangle({0, 51, 240, 269}, Color::black()); // Start at 51 to preserve line
|
||||
|
||||
// Restore UI
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
|
||||
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
|
||||
|
||||
// Redraw the border line in case it was damaged again
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
// Initialize new wave of invaders
|
||||
init_invaders();
|
||||
draw_all_invaders();
|
||||
draw_player();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpaceInvadersView::SpaceInvadersView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&dummy, &button_difficulty});
|
||||
current_instance = this;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 60.0);
|
||||
|
||||
// Update button text based on loaded setting
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
|
||||
button_difficulty.on_select = [this](Button&) {
|
||||
easy_mode = !easy_mode;
|
||||
button_difficulty.set_text(easy_mode ? "Mode: EASY" : "Mode: HARD");
|
||||
// Settings will be saved when the view is destroyed
|
||||
};
|
||||
}
|
||||
|
||||
SpaceInvadersView::~SpaceInvadersView() {
|
||||
// Settings are automatically saved when destroyed
|
||||
current_instance = nullptr;
|
||||
}
|
||||
|
||||
void SpaceInvadersView::on_show() {
|
||||
}
|
||||
|
||||
void SpaceInvadersView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
game_state = 0;
|
||||
menu_initialized = false;
|
||||
game_over_initialized = false;
|
||||
blink_state = true;
|
||||
blink_counter = 0;
|
||||
player_x = 107;
|
||||
score = 0;
|
||||
lives = 3;
|
||||
wave = 1;
|
||||
speed_bonus = 0;
|
||||
|
||||
// Initialize arrays
|
||||
for (int i = 0; i < MAX_BULLETS; i++) {
|
||||
bullet_active[i] = false;
|
||||
bullet_x[i] = 0;
|
||||
bullet_y[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
|
||||
enemy_bullet_active[i] = false;
|
||||
enemy_bullet_x[i] = 0;
|
||||
enemy_bullet_y[i] = 0;
|
||||
}
|
||||
|
||||
init_invaders();
|
||||
}
|
||||
}
|
||||
|
||||
void SpaceInvadersView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool SpaceInvadersView::on_encoder(const EncoderEvent delta) {
|
||||
if (game_state == 1) {
|
||||
if (delta > 0) {
|
||||
player_x += 5;
|
||||
if (player_x > 214) player_x = 214;
|
||||
draw_player();
|
||||
} else if (delta < 0) {
|
||||
player_x -= 5;
|
||||
if (player_x < 0) player_x = 0;
|
||||
draw_player();
|
||||
}
|
||||
|
||||
set_dirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpaceInvadersView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == 0) {
|
||||
// Start game
|
||||
game_state = 1;
|
||||
button_difficulty.hidden(true);
|
||||
score = 0;
|
||||
lives = 3;
|
||||
wave = 1;
|
||||
speed_bonus = 0;
|
||||
invader_move_delay = 30;
|
||||
enemy_fire_counter = 0;
|
||||
init_invaders();
|
||||
|
||||
painter.fill_rectangle({0, 0, 240, 320}, Color::black());
|
||||
painter.draw_hline({0, 49}, 240, Color::white());
|
||||
|
||||
auto style = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({5, 10}, style, "Score: 0");
|
||||
painter.draw_string({5, 30}, style, "Lives: 3");
|
||||
|
||||
draw_all_invaders();
|
||||
draw_player();
|
||||
|
||||
set_dirty();
|
||||
} else if (game_state == 1) {
|
||||
fire_bullet();
|
||||
} else if (game_state == 2) {
|
||||
// Return to menu
|
||||
game_state = 0;
|
||||
menu_initialized = false;
|
||||
game_over_initialized = false;
|
||||
button_difficulty.hidden(false);
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::spaceinv
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_SPACEINV_H__
|
||||
#define __UI_SPACEINV_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "app_settings.hpp"
|
||||
|
||||
namespace ui::external_app::spaceinv {
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() = default;
|
||||
void attach(Callback func, double delay_sec);
|
||||
void detach();
|
||||
};
|
||||
|
||||
void check_game_timer();
|
||||
void game_timer_check();
|
||||
|
||||
class SpaceInvadersView : public View {
|
||||
public:
|
||||
SpaceInvadersView(NavigationView& nav);
|
||||
~SpaceInvadersView(); // Destructor will trigger settings save
|
||||
void on_show() override;
|
||||
|
||||
std::string title() const override { return "Space Invaders"; }
|
||||
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_encoder(const EncoderEvent event) override;
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
uint32_t highScore = 0;
|
||||
bool easy_mode = false;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Button button_difficulty{
|
||||
{70, 285, 100, 20},
|
||||
"Mode: HARD"};
|
||||
|
||||
app_settings::SettingsManager settings_{
|
||||
"spaceinv",
|
||||
app_settings::Mode::NO_RF,
|
||||
{{"highscore"sv, &highScore},
|
||||
{"easy_mode"sv, &easy_mode}}};
|
||||
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::spaceinv
|
||||
|
||||
#endif /* __UI_SPACEINV_H__ */
|
||||
@@ -52,3 +52,4 @@ const std::filesystem::path ook_editor_dir = u"OOKFILES";
|
||||
const std::filesystem::path hopper_dir = u"HOPPER";
|
||||
const std::filesystem::path subghz_dir = u"SUBGHZ";
|
||||
const std::filesystem::path waterfalls_dir = u"WATERFALLS";
|
||||
const std::filesystem::path macaddress_dir = u"MACADDRESS";
|
||||
|
||||
@@ -54,5 +54,6 @@ extern const std::filesystem::path ook_editor_dir;
|
||||
extern const std::filesystem::path hopper_dir;
|
||||
extern const std::filesystem::path subghz_dir;
|
||||
extern const std::filesystem::path waterfalls_dir;
|
||||
extern const std::filesystem::path macaddress_dir;
|
||||
|
||||
#endif /* __FILE_PATH_H__ */
|
||||
|
||||
@@ -28,10 +28,10 @@ Optional<File::Error> LogFile::write_entry(const std::string& entry) {
|
||||
|
||||
Optional<File::Error> LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
|
||||
std::string timestamp = to_string_timestamp(datetime);
|
||||
return write_line(timestamp + " " + entry);
|
||||
return write_raw(timestamp + " " + entry);
|
||||
}
|
||||
|
||||
Optional<File::Error> LogFile::write_line(const std::string& message) {
|
||||
Optional<File::Error> LogFile::write_raw(const std::string& message) {
|
||||
auto error = file.write_line(message);
|
||||
if (!error) {
|
||||
file.sync();
|
||||
|
||||
@@ -39,11 +39,10 @@ class LogFile {
|
||||
|
||||
Optional<File::Error> write_entry(const std::string& entry);
|
||||
Optional<File::Error> write_entry(const rtc::RTC& datetime, const std::string& entry);
|
||||
Optional<File::Error> write_raw(const std::string& message);
|
||||
|
||||
private:
|
||||
File file{};
|
||||
|
||||
Optional<File::Error> write_line(const std::string& message);
|
||||
};
|
||||
|
||||
#endif /*__LOG_FILE_H__*/
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
namespace ui {
|
||||
|
||||
RecentEntriesColumns::RecentEntriesColumns(
|
||||
const std::initializer_list<RecentEntriesColumn> columns)
|
||||
std::initializer_list<RecentEntriesColumn> columns)
|
||||
: _columns{columns} {
|
||||
}
|
||||
|
||||
|
||||
@@ -133,14 +133,27 @@ class RecentEntriesColumns {
|
||||
public:
|
||||
using ContainerType = std::vector<RecentEntriesColumn>;
|
||||
|
||||
RecentEntriesColumns(
|
||||
const std::initializer_list<RecentEntriesColumn> columns);
|
||||
RecentEntriesColumns(std::initializer_list<RecentEntriesColumn> columns);
|
||||
|
||||
ContainerType::const_iterator begin() const { return std::begin(_columns); }
|
||||
ContainerType::const_iterator end() const { return std::end(_columns); }
|
||||
ContainerType::iterator begin() { return _columns.begin(); }
|
||||
ContainerType::iterator end() { return _columns.end(); }
|
||||
ContainerType::const_iterator begin() const { return _columns.begin(); }
|
||||
ContainerType::const_iterator end() const { return _columns.end(); }
|
||||
|
||||
void set(size_t index, const std::string& name, size_t width) {
|
||||
if (index < _columns.size()) {
|
||||
_columns[index] = {name, width};
|
||||
}
|
||||
}
|
||||
|
||||
const RecentEntriesColumn& at(size_t index) const {
|
||||
return _columns.at(index);
|
||||
}
|
||||
|
||||
size_t size() const { return _columns.size(); }
|
||||
|
||||
private:
|
||||
const ContainerType _columns;
|
||||
ContainerType _columns;
|
||||
};
|
||||
|
||||
class RecentEntriesHeader : public Widget {
|
||||
|
||||
@@ -110,6 +110,7 @@ GeoPos::GeoPos(
|
||||
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
|
||||
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
|
||||
if (speed_unit_ == MPH) text_speed_unit.set("mph");
|
||||
if (speed_unit_ == KNOTS) text_speed_unit.set("knots");
|
||||
if (speed_unit_ == HIDDEN) {
|
||||
text_speed_unit.hidden(true);
|
||||
label_spd_position.hidden(true);
|
||||
|
||||
@@ -81,6 +81,7 @@ class GeoPos : public View {
|
||||
NONE = 0,
|
||||
MPH,
|
||||
KMPH,
|
||||
KNOTS,
|
||||
HIDDEN = 255
|
||||
};
|
||||
|
||||
@@ -134,7 +135,7 @@ class GeoPos : public View {
|
||||
{12 * 8, 0 * 16, 2 * 8, 16},
|
||||
""};
|
||||
Text text_speed_unit{
|
||||
{25 * 8, 0 * 16, 4 * 8, 16},
|
||||
{25 * 8, 0 * 16, 5 * 8, 16},
|
||||
""};
|
||||
|
||||
NumberField field_lat_degrees{
|
||||
|
||||
@@ -397,13 +397,6 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PSON sonde)
|
||||
|
||||
### FSK TX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_fsk.cpp
|
||||
)
|
||||
DeclareTargets(PFSK fsktx)
|
||||
|
||||
### FSK RX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
@@ -411,6 +404,12 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PFSR fskrx)
|
||||
|
||||
### FSK TX
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_fsk.cpp
|
||||
)
|
||||
DeclareTargets(PFSK fsktx)
|
||||
|
||||
### Microphone transmit
|
||||
|
||||
|
||||
+134
-129
@@ -27,7 +27,16 @@
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
uint32_t BTLERxProcessor::crc_init_reorder(uint32_t crc_init) {
|
||||
inline float BTLERxProcessor::get_phase_diff(const complex16_t& sample0, const complex16_t& sample1) {
|
||||
// Calculate the phase difference between two samples.
|
||||
float dI = sample1.real() * sample0.real() + sample1.imag() * sample0.imag();
|
||||
float dQ = sample1.imag() * sample0.real() - sample1.real() * sample0.imag();
|
||||
float phase_diff = atan2f(dQ, dI);
|
||||
|
||||
return phase_diff;
|
||||
}
|
||||
|
||||
inline uint32_t BTLERxProcessor::crc_init_reorder(uint32_t crc_init) {
|
||||
int i;
|
||||
uint32_t crc_init_tmp, crc_init_input, crc_init_input_tmp;
|
||||
|
||||
@@ -53,7 +62,7 @@ uint32_t BTLERxProcessor::crc_init_reorder(uint32_t crc_init) {
|
||||
return (crc_init_tmp);
|
||||
}
|
||||
|
||||
uint_fast32_t BTLERxProcessor::crc_update(uint_fast32_t crc, const void* data, size_t data_len) {
|
||||
inline uint_fast32_t BTLERxProcessor::crc_update(uint_fast32_t crc, const void* data, size_t data_len) {
|
||||
const unsigned char* d = (const unsigned char*)data;
|
||||
unsigned int tbl_idx;
|
||||
|
||||
@@ -67,7 +76,7 @@ uint_fast32_t BTLERxProcessor::crc_update(uint_fast32_t crc, const void* data, s
|
||||
return crc & 0xffffff;
|
||||
}
|
||||
|
||||
uint_fast32_t BTLERxProcessor::crc24_byte(uint8_t* byte_in, int num_byte, uint32_t init_hex) {
|
||||
inline uint_fast32_t BTLERxProcessor::crc24_byte(uint8_t* byte_in, int num_byte, uint32_t init_hex) {
|
||||
uint_fast32_t crc = init_hex;
|
||||
|
||||
crc = crc_update(crc, byte_in, num_byte);
|
||||
@@ -75,7 +84,7 @@ uint_fast32_t BTLERxProcessor::crc24_byte(uint8_t* byte_in, int num_byte, uint32
|
||||
return (crc);
|
||||
}
|
||||
|
||||
bool BTLERxProcessor::crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_init) {
|
||||
inline bool BTLERxProcessor::crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_init) {
|
||||
int crc24_checksum;
|
||||
|
||||
crc24_checksum = crc24_byte(tmp_byte, body_len, crc_init); // 0x555555 --> 0xaaaaaa. maybe because byte order
|
||||
@@ -87,7 +96,7 @@ bool BTLERxProcessor::crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_in
|
||||
return (crc24_checksum != checksumReceived);
|
||||
}
|
||||
|
||||
void BTLERxProcessor::scramble_byte(uint8_t* byte_in, int num_byte, const uint8_t* scramble_table_byte, uint8_t* byte_out) {
|
||||
inline void BTLERxProcessor::scramble_byte(uint8_t* byte_in, int num_byte, const uint8_t* scramble_table_byte, uint8_t* byte_out) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_byte; i++) {
|
||||
@@ -95,7 +104,7 @@ void BTLERxProcessor::scramble_byte(uint8_t* byte_in, int num_byte, const uint8_
|
||||
}
|
||||
}
|
||||
|
||||
int BTLERxProcessor::verify_payload_byte(int num_payload_byte, ADV_PDU_TYPE pdu_type) {
|
||||
inline int BTLERxProcessor::verify_payload_byte(int num_payload_byte, ADV_PDU_TYPE pdu_type) {
|
||||
// Should at least have 6 bytes for the MAC Address.
|
||||
// Also ensuring that there is at least 1 byte of data.
|
||||
if (num_payload_byte <= 6) {
|
||||
@@ -122,120 +131,125 @@ int BTLERxProcessor::verify_payload_byte(int num_payload_byte, ADV_PDU_TYPE pdu_
|
||||
return 0;
|
||||
}
|
||||
|
||||
void BTLERxProcessor::handleBeginState() {
|
||||
int num_symbol_left = dst_buffer.count / SAMPLE_PER_SYMBOL; // One buffer sample consist of I and Q.
|
||||
inline void BTLERxProcessor::resetOffsetTracking() {
|
||||
frequency_offset = 0.0f;
|
||||
frequency_offset_estimate = 0.0f;
|
||||
phase_buffer_index = 0;
|
||||
memset(phase_buffer, 0, sizeof(phase_buffer));
|
||||
}
|
||||
|
||||
static uint8_t demod_buf_access[SAMPLE_PER_SYMBOL][LEN_DEMOD_BUF_ACCESS];
|
||||
inline void BTLERxProcessor::resetBitPacketIndex() {
|
||||
memset(rb_buf, 0, sizeof(rb_buf));
|
||||
packet_index = 0;
|
||||
bit_index = 0;
|
||||
}
|
||||
|
||||
uint32_t uint32_tmp = DEFAULT_ACCESS_ADDR;
|
||||
uint8_t accessAddrBits[LEN_DEMOD_BUF_ACCESS];
|
||||
inline void BTLERxProcessor::resetToDefaultState() {
|
||||
parseState = Parse_State_Begin;
|
||||
resetOffsetTracking();
|
||||
resetBitPacketIndex();
|
||||
crc_init_internal = crc_init_reorder(crc_initalVale);
|
||||
}
|
||||
|
||||
uint32_t accesssAddress = 0;
|
||||
inline void BTLERxProcessor::demodulateFSKBits(int num_demod_byte) {
|
||||
for (; packet_index < num_demod_byte; packet_index++) {
|
||||
for (; bit_index < 8; bit_index++) {
|
||||
if (samples_eaten >= (int)dst_buffer.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filling up addressBits with the access address we are looking to find.
|
||||
for (int i = 0; i < 32; i++) {
|
||||
accessAddrBits[i] = 0x01 & uint32_tmp;
|
||||
uint32_tmp = (uint32_tmp >> 1);
|
||||
float phaseSum = 0.0f;
|
||||
for (int k = 0; k < SAMPLE_PER_SYMBOL; ++k) {
|
||||
float phase = get_phase_diff(
|
||||
dst_buffer.p[samples_eaten + k],
|
||||
dst_buffer.p[samples_eaten + k + 1]);
|
||||
phaseSum += phase;
|
||||
}
|
||||
|
||||
// phaseSum /= (SAMPLE_PER_SYMBOL);
|
||||
// phaseSum -= frequency_offset;
|
||||
|
||||
/*
|
||||
alternate method. faster, but less precise. with this, you need to check against this: if (samples_eaten >= (int)dst_buffer.count + SAMPLE_PER_SYMBOL) (not so good...)
|
||||
int I0 = dst_buffer.p[samples_eaten].real();
|
||||
int Q0 = dst_buffer.p[samples_eaten].imag();
|
||||
int I1 = dst_buffer.p[samples_eaten + 1 * SAMPLE_PER_SYMBOL].real();
|
||||
int Q1 = dst_buffer.p[samples_eaten + 1 * SAMPLE_PER_SYMBOL].imag();
|
||||
bool bitDecision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0;
|
||||
*/
|
||||
|
||||
bool bitDecision = (phaseSum > 0.0f);
|
||||
rb_buf[packet_index] = rb_buf[packet_index] | (bitDecision << bit_index);
|
||||
|
||||
samples_eaten += SAMPLE_PER_SYMBOL;
|
||||
}
|
||||
|
||||
bit_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline void BTLERxProcessor::handleBeginState() {
|
||||
uint32_t validAccessAddress = DEFAULT_ACCESS_ADDR;
|
||||
static uint32_t accesssAddress = 0;
|
||||
|
||||
const int demod_buf_len = LEN_DEMOD_BUF_ACCESS; // For AA
|
||||
int demod_buf_offset = 0;
|
||||
int hit_idx = (-1);
|
||||
bool unequal_flag = false;
|
||||
|
||||
memset(demod_buf_access, 0, SAMPLE_PER_SYMBOL * demod_buf_len);
|
||||
|
||||
for (int i = 0; i < num_symbol_left * SAMPLE_PER_SYMBOL; i += SAMPLE_PER_SYMBOL) {
|
||||
int sp = ((demod_buf_offset - demod_buf_len + 1) & (demod_buf_len - 1));
|
||||
for (int i = samples_eaten; i < (int)dst_buffer.count; i += SAMPLE_PER_SYMBOL) {
|
||||
float phaseDiff = 0;
|
||||
|
||||
for (int j = 0; j < SAMPLE_PER_SYMBOL; j++) {
|
||||
// Sample and compare with the adjacent next sample.
|
||||
int I0 = dst_buffer.p[i + j].real();
|
||||
int Q0 = dst_buffer.p[i + j].imag();
|
||||
int I1 = dst_buffer.p[i + j + 1].real();
|
||||
int Q1 = dst_buffer.p[i + j + 1].imag();
|
||||
|
||||
int phase_idx = j;
|
||||
|
||||
demod_buf_access[phase_idx][demod_buf_offset] = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0;
|
||||
|
||||
int k = sp;
|
||||
unequal_flag = false;
|
||||
|
||||
accesssAddress = 0;
|
||||
|
||||
for (int p = 0; p < demod_buf_len; p++) {
|
||||
if (demod_buf_access[phase_idx][k] != accessAddrBits[p]) {
|
||||
unequal_flag = true;
|
||||
hit_idx = (-1);
|
||||
break;
|
||||
}
|
||||
|
||||
accesssAddress = (accesssAddress & (~(1 << p))) | (demod_buf_access[phase_idx][k] << p);
|
||||
|
||||
k = ((k + 1) & (demod_buf_len - 1));
|
||||
}
|
||||
|
||||
if (unequal_flag == false) {
|
||||
hit_idx = (i + j - (demod_buf_len - 1) * SAMPLE_PER_SYMBOL);
|
||||
break;
|
||||
}
|
||||
phaseDiff += get_phase_diff(dst_buffer.p[i + j], dst_buffer.p[i + j + 1]);
|
||||
}
|
||||
|
||||
if (unequal_flag == false) {
|
||||
// disabled, due to not used anywhere
|
||||
/* phase_buffer[phase_buffer_index] = phaseDiff / (SAMPLE_PER_SYMBOL);
|
||||
phase_buffer_index = (phase_buffer_index + 1) % ROLLING_WINDOW;
|
||||
*/
|
||||
|
||||
bool bitDecision = (phaseDiff > 0);
|
||||
|
||||
accesssAddress = (accesssAddress >> 1 | (bitDecision << 31));
|
||||
|
||||
int errors = __builtin_popcount(accesssAddress ^ validAccessAddress) & 0xFFFFFFFF;
|
||||
|
||||
if (!errors) {
|
||||
hit_idx = i + SAMPLE_PER_SYMBOL;
|
||||
|
||||
// disabled, due to not used anywhere
|
||||
/* for (int k = 0; k < ROLLING_WINDOW; k++) {
|
||||
frequency_offset_estimate += phase_buffer[k];
|
||||
}
|
||||
frequency_offset = frequency_offset_estimate / ROLLING_WINDOW;
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
demod_buf_offset = ((demod_buf_offset + 1) & (demod_buf_len - 1));
|
||||
}
|
||||
|
||||
if (hit_idx == -1) {
|
||||
// Process more samples.
|
||||
samples_eaten = (int)dst_buffer.count + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
symbols_eaten += hit_idx;
|
||||
|
||||
symbols_eaten += (8 * NUM_ACCESS_ADDR_BYTE * SAMPLE_PER_SYMBOL); // move to the beginning of PDU header
|
||||
|
||||
num_symbol_left = num_symbol_left - symbols_eaten;
|
||||
samples_eaten += hit_idx;
|
||||
|
||||
parseState = Parse_State_PDU_Header;
|
||||
}
|
||||
|
||||
void BTLERxProcessor::handlePDUHeaderState() {
|
||||
int num_demod_byte = 2; // PDU header has 2 octets
|
||||
|
||||
symbols_eaten += 8 * num_demod_byte * SAMPLE_PER_SYMBOL;
|
||||
|
||||
if (symbols_eaten > (int)dst_buffer.count) {
|
||||
inline void BTLERxProcessor::handlePDUHeaderState() {
|
||||
if (samples_eaten > (int)dst_buffer.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Jump back down to the beginning of PDU header.
|
||||
sample_idx = symbols_eaten - (8 * num_demod_byte * SAMPLE_PER_SYMBOL);
|
||||
demodulateFSKBits(NUM_PDU_HEADER_BYTE);
|
||||
|
||||
packet_index = 0;
|
||||
|
||||
for (int i = 0; i < num_demod_byte; i++) {
|
||||
rb_buf[packet_index] = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int I0 = dst_buffer.p[sample_idx].real();
|
||||
int Q0 = dst_buffer.p[sample_idx].imag();
|
||||
int I1 = dst_buffer.p[sample_idx + 1].real();
|
||||
int Q1 = dst_buffer.p[sample_idx + 1].imag();
|
||||
|
||||
bit_decision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0;
|
||||
rb_buf[packet_index] = rb_buf[packet_index] | (bit_decision << j);
|
||||
|
||||
sample_idx += SAMPLE_PER_SYMBOL;
|
||||
}
|
||||
|
||||
packet_index++;
|
||||
if (packet_index < NUM_PDU_HEADER_BYTE || bit_index != 0) {
|
||||
resetToDefaultState();
|
||||
return;
|
||||
}
|
||||
|
||||
scramble_byte(rb_buf, num_demod_byte, scramble_table[channel_number], rb_buf);
|
||||
scramble_byte(rb_buf, 2, scramble_table[channel_number], rb_buf);
|
||||
|
||||
pdu_type = (ADV_PDU_TYPE)(rb_buf[0] & 0x0F);
|
||||
// uint8_t tx_add = ((rb_buf[0] & 0x40) != 0);
|
||||
@@ -244,38 +258,25 @@ void BTLERxProcessor::handlePDUHeaderState() {
|
||||
|
||||
// Not a valid Advertise Payload.
|
||||
if ((payload_len < 6) || (payload_len > 37)) {
|
||||
parseState = Parse_State_Begin;
|
||||
resetToDefaultState();
|
||||
return;
|
||||
} else {
|
||||
parseState = Parse_State_PDU_Payload;
|
||||
}
|
||||
}
|
||||
|
||||
void BTLERxProcessor::handlePDUPayloadState() {
|
||||
int i;
|
||||
int num_demod_byte = (payload_len + 3);
|
||||
symbols_eaten += 8 * num_demod_byte * SAMPLE_PER_SYMBOL;
|
||||
inline void BTLERxProcessor::handlePDUPayloadState() {
|
||||
const int num_demod_byte = (payload_len + 3);
|
||||
|
||||
if (symbols_eaten > (int)dst_buffer.count) {
|
||||
if (samples_eaten > (int)dst_buffer.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_demod_byte; i++) {
|
||||
rb_buf[packet_index] = 0;
|
||||
demodulateFSKBits(num_demod_byte + NUM_PDU_HEADER_BYTE);
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int I0 = dst_buffer.p[sample_idx].real();
|
||||
int Q0 = dst_buffer.p[sample_idx].imag();
|
||||
int I1 = dst_buffer.p[sample_idx + 1].real();
|
||||
int Q1 = dst_buffer.p[sample_idx + 1].imag();
|
||||
|
||||
bit_decision = (I0 * Q1 - I1 * Q0) > 0 ? 1 : 0;
|
||||
rb_buf[packet_index] = rb_buf[packet_index] | (bit_decision << j);
|
||||
|
||||
sample_idx += SAMPLE_PER_SYMBOL;
|
||||
}
|
||||
|
||||
packet_index++;
|
||||
if (packet_index < (num_demod_byte + NUM_PDU_HEADER_BYTE) || bit_index != 0) {
|
||||
resetToDefaultState();
|
||||
return;
|
||||
}
|
||||
|
||||
scramble_byte(rb_buf + 2, num_demod_byte, scramble_table[channel_number] + 2, rb_buf + 2);
|
||||
@@ -310,6 +311,8 @@ void BTLERxProcessor::handlePDUPayloadState() {
|
||||
// Skip Header Byte and MAC Address
|
||||
uint8_t startIndex = 8;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < payload_len - 6; i++) {
|
||||
blePacketData.data[i] = rb_buf[startIndex++];
|
||||
}
|
||||
@@ -322,46 +325,50 @@ void BTLERxProcessor::handlePDUPayloadState() {
|
||||
}
|
||||
}
|
||||
|
||||
parseState = Parse_State_Begin;
|
||||
resetToDefaultState();
|
||||
}
|
||||
|
||||
void BTLERxProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
// Pulled this implementation from channel_stats_collector.c to time slice a specific packet's dB.
|
||||
// a less computationally expensive method
|
||||
max_dB = -128;
|
||||
uint32_t max_squared = 0;
|
||||
|
||||
int8_t imag = 0;
|
||||
int8_t real = 0;
|
||||
void* src_p = buffer.p;
|
||||
|
||||
while (src_p < &buffer.p[buffer.count]) {
|
||||
const uint32_t sample = *__SIMD32(src_p)++;
|
||||
const uint32_t mag_sq = __SMUAD(sample, sample);
|
||||
if (mag_sq > max_squared) {
|
||||
max_squared = mag_sq;
|
||||
imag = ((complex8_t*)src_p)->imag();
|
||||
real = ((complex8_t*)src_p)->real();
|
||||
}
|
||||
}
|
||||
|
||||
const float max_squared_f = max_squared;
|
||||
max_dB = mag2_to_dbv_norm(max_squared_f * (1.0f / (32768.0f * 32768.0f)));
|
||||
max_dB = mag2_to_dbm_8bit_normalized(real, imag, 1.0f, 50.0f);
|
||||
|
||||
// 4Mhz 2048 samples
|
||||
// Decimated by 4 to achieve 2048/4 = 512 samples at 1 sample per symbol.
|
||||
decim_0.execute(buffer, dst_buffer);
|
||||
feed_channel_stats(dst_buffer);
|
||||
|
||||
symbols_eaten = 0;
|
||||
samples_eaten = 0;
|
||||
|
||||
// Handle parsing based on parseState
|
||||
if (parseState == Parse_State_Begin) {
|
||||
handleBeginState();
|
||||
}
|
||||
while (samples_eaten < (int)dst_buffer.count) {
|
||||
// Handle parsing based on parseState
|
||||
if (parseState == Parse_State_Begin) {
|
||||
handleBeginState();
|
||||
}
|
||||
|
||||
if (parseState == Parse_State_PDU_Header) {
|
||||
handlePDUHeaderState();
|
||||
}
|
||||
if (parseState == Parse_State_PDU_Header) {
|
||||
handlePDUHeaderState();
|
||||
}
|
||||
|
||||
if (parseState == Parse_State_PDU_Payload) {
|
||||
handlePDUPayloadState();
|
||||
if (parseState == Parse_State_PDU_Payload) {
|
||||
handlePDUPayloadState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,11 +379,9 @@ void BTLERxProcessor::on_message(const Message* const message) {
|
||||
|
||||
void BTLERxProcessor::configure(const BTLERxConfigureMessage& message) {
|
||||
channel_number = message.channel_number;
|
||||
decim_0.configure(taps_BTLE_1M_PHY_decim_0.taps);
|
||||
decim_0.configure(taps_BTLE_Dual_PHY.taps);
|
||||
|
||||
configured = true;
|
||||
|
||||
crc_init_internal = crc_init_reorder(crc_initalVale);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -47,6 +47,8 @@ class BTLERxProcessor : public BasebandProcessor {
|
||||
static constexpr int LEN_DEMOD_BUF_ACCESS{32};
|
||||
static constexpr uint32_t DEFAULT_ACCESS_ADDR{0x8E89BED6};
|
||||
static constexpr int NUM_ACCESS_ADDR_BYTE{4};
|
||||
static constexpr int NUM_PDU_HEADER_BYTE{2};
|
||||
static constexpr int ROLLING_WINDOW{32};
|
||||
|
||||
enum Parse_State {
|
||||
Parse_State_Begin = 0,
|
||||
@@ -81,8 +83,8 @@ class BTLERxProcessor : public BasebandProcessor {
|
||||
};
|
||||
|
||||
static constexpr size_t baseband_fs = 4000000;
|
||||
static constexpr size_t audio_fs = baseband_fs / 8 / 8 / 2;
|
||||
|
||||
float get_phase_diff(const complex16_t& sample0, const complex16_t& sample1);
|
||||
uint_fast32_t crc_update(uint_fast32_t crc, const void* data, size_t data_len);
|
||||
uint_fast32_t crc24_byte(uint8_t* byte_in, int num_byte, uint32_t init_hex);
|
||||
bool crc_check(uint8_t* tmp_byte, int body_len, uint32_t crc_init);
|
||||
@@ -95,6 +97,12 @@ class BTLERxProcessor : public BasebandProcessor {
|
||||
// void demod_byte(int num_byte, uint8_t *out_byte);
|
||||
int verify_payload_byte(int num_payload_byte, ADV_PDU_TYPE pdu_type);
|
||||
|
||||
void resetOffsetTracking();
|
||||
void resetBitPacketIndex();
|
||||
void resetToDefaultState();
|
||||
|
||||
void demodulateFSKBits(int num_demod_byte);
|
||||
|
||||
void handleBeginState();
|
||||
void handlePDUHeaderState();
|
||||
void handlePDUPayloadState();
|
||||
@@ -120,13 +128,18 @@ class BTLERxProcessor : public BasebandProcessor {
|
||||
BlePacketData blePacketData{};
|
||||
|
||||
Parse_State parseState{Parse_State_Begin};
|
||||
uint16_t packet_index{0};
|
||||
int sample_idx{0};
|
||||
int symbols_eaten{0};
|
||||
int samples_eaten{0};
|
||||
uint8_t bit_decision{0};
|
||||
uint8_t payload_len{0};
|
||||
uint8_t pdu_type{0};
|
||||
int32_t max_dB{0};
|
||||
uint16_t packet_index{0};
|
||||
uint8_t bit_index{0};
|
||||
|
||||
float frequency_offset_estimate{0.0f};
|
||||
float frequency_offset{0.0f};
|
||||
float phase_buffer[ROLLING_WINDOW] = {0.0f};
|
||||
int phase_buffer_index = 0;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
|
||||
+246
-166
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include "proc_fsk_rx.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#include "event_m4.hpp"
|
||||
|
||||
@@ -32,135 +33,253 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
using namespace std;
|
||||
using namespace dsp::decimate;
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
/* Count of bits that differ between the two values. */
|
||||
uint8_t diff_bit_count(uint32_t left, uint32_t right) {
|
||||
uint32_t diff = left ^ right;
|
||||
uint8_t count = 0;
|
||||
for (size_t i = 0; i < sizeof(diff) * 8; ++i) {
|
||||
if (((diff >> i) & 0x1) == 1)
|
||||
++count;
|
||||
float FSKRxProcessor::detect_peak_power(const buffer_c8_t& buffer, int N) {
|
||||
int32_t power = 0;
|
||||
|
||||
// Initial window power
|
||||
for (int i = 0; i < N; i++) {
|
||||
int16_t i_sample = buffer.p[i].real();
|
||||
int16_t q_sample = buffer.p[i].imag();
|
||||
power += i_sample * i_sample + q_sample * q_sample;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
} // namespace
|
||||
power = power / N;
|
||||
|
||||
/* AudioNormalizer ***************************************/
|
||||
// Convert to dB over noise floor
|
||||
float power_db = 10.0f * log10f((float)power / noise_floor);
|
||||
|
||||
void AudioNormalizer::execute_in_place(const buffer_f32_t& audio) {
|
||||
// Decay min/max every second (@24kHz).
|
||||
if (counter_ >= 24'000) {
|
||||
// 90% decay factor seems to work well.
|
||||
// This keeps large transients from wrecking the filter.
|
||||
max_ *= 0.9f;
|
||||
min_ *= 0.9f;
|
||||
counter_ = 0;
|
||||
calculate_thresholds();
|
||||
}
|
||||
// If too weak, treat as no signal
|
||||
if (power_db <= 0.0f) return 0;
|
||||
|
||||
counter_ += audio.count;
|
||||
|
||||
for (size_t i = 0; i < audio.count; ++i) {
|
||||
auto& val = audio.p[i];
|
||||
|
||||
if (val > max_) {
|
||||
max_ = val;
|
||||
calculate_thresholds();
|
||||
}
|
||||
if (val < min_) {
|
||||
min_ = val;
|
||||
calculate_thresholds();
|
||||
}
|
||||
|
||||
if (val >= t_hi_)
|
||||
val = 1.0f;
|
||||
else if (val <= t_lo_)
|
||||
val = -1.0f;
|
||||
else
|
||||
val = 0.0;
|
||||
}
|
||||
return power_db;
|
||||
}
|
||||
|
||||
void AudioNormalizer::calculate_thresholds() {
|
||||
auto center = (max_ + min_) / 2.0f;
|
||||
auto range = (max_ - min_) / 2.0f;
|
||||
void FSKRxProcessor::agc_correct_iq(const buffer_c8_t& buffer, int N, float measured_power) {
|
||||
float power_db = 10.0f * log10f(measured_power / noise_floor);
|
||||
float error_db = target_power_db - power_db;
|
||||
|
||||
// 10% off center force either +/-1.0f.
|
||||
// Higher == larger dead zone.
|
||||
// Lower == more false positives.
|
||||
auto threshold = range * 0.1;
|
||||
t_hi_ = center + threshold;
|
||||
t_lo_ = center - threshold;
|
||||
}
|
||||
|
||||
/* FSKRxProcessor ******************************************/
|
||||
|
||||
void FSKRxProcessor::clear_data_bits() {
|
||||
data = 0;
|
||||
bit_count = 0;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::handle_sync(bool inverted) {
|
||||
clear_data_bits();
|
||||
has_sync_ = true;
|
||||
inverted = inverted;
|
||||
word_count = 0;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::process_bits(const buffer_c8_t& buffer) {
|
||||
// Process all of the bits in the bits queue.
|
||||
while (buffer.count > 0) {
|
||||
// Wait until data_ is full.
|
||||
if (bit_count < data_bit_count)
|
||||
continue;
|
||||
|
||||
// Wait for the sync frame.
|
||||
if (!has_sync_) {
|
||||
if (diff_bit_count(data, sync_codeword) <= 2)
|
||||
handle_sync(/*inverted=*/false);
|
||||
else if (diff_bit_count(data, ~sync_codeword) <= 2)
|
||||
handle_sync(/*inverted=*/true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* FSKRxProcessor ***************************************/
|
||||
|
||||
FSKRxProcessor::FSKRxProcessor() {
|
||||
}
|
||||
|
||||
void FSKRxProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) {
|
||||
if (error_db <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decimate by current decim 0 and decim 1.
|
||||
float gain_scalar = powf(10.0f, error_db / 20.0f);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
buffer.p[i] = {(int8_t)(buffer.p[i].real() * gain_scalar), (int8_t)(buffer.p[i].imag() * gain_scalar)};
|
||||
}
|
||||
}
|
||||
|
||||
float FSKRxProcessor::get_phase_diff(const complex16_t& sample0, const complex16_t& sample1) {
|
||||
// Calculate the phase difference between two samples.
|
||||
float dI = sample1.real() * sample0.real() + sample1.imag() * sample0.imag();
|
||||
float dQ = sample1.imag() * sample0.real() - sample1.real() * sample0.imag();
|
||||
float phase_diff = atan2f(dQ, dI);
|
||||
|
||||
return phase_diff;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::demodulateFSKBits(const buffer_c16_t& decimator_out, int num_demod_byte) {
|
||||
for (; packet_index < num_demod_byte; packet_index++) {
|
||||
for (; bit_index < 8; bit_index++) {
|
||||
if (samples_eaten >= (int)decimator_out.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
float phaseSum = 0.0f;
|
||||
for (int k = 0; k < SAMPLE_PER_SYMBOL - 1; ++k) {
|
||||
float phase = get_phase_diff(
|
||||
decimator_out.p[samples_eaten + k],
|
||||
decimator_out.p[samples_eaten + k + 1]);
|
||||
phaseSum += phase;
|
||||
}
|
||||
|
||||
phaseSum /= (SAMPLE_PER_SYMBOL - 1);
|
||||
phaseSum -= frequency_offset;
|
||||
|
||||
bool bitDecision = (phaseSum > 0.0f);
|
||||
|
||||
rb_buf[packet_index] |= (bitDecision << (7 - bit_index));
|
||||
|
||||
samples_eaten += SAMPLE_PER_SYMBOL;
|
||||
}
|
||||
|
||||
bit_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FSKRxProcessor::resetPreambleTracking() {
|
||||
frequency_offset = 0.0f;
|
||||
frequency_offset_estimate = 0.0f;
|
||||
phase_buffer_index = 0;
|
||||
memset(phase_buffer, 0, sizeof(phase_buffer));
|
||||
}
|
||||
|
||||
void FSKRxProcessor::resetBitPacketIndex() {
|
||||
packet_index = 0;
|
||||
bit_index = 0;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::resetToDefaultState() {
|
||||
parseState = Parse_State_Wait_For_Peak;
|
||||
peak_timeout = 0;
|
||||
fskPacketData.power = 0.0f;
|
||||
resetPreambleTracking();
|
||||
resetBitPacketIndex();
|
||||
}
|
||||
|
||||
void FSKRxProcessor::handlePreambleState(const buffer_c16_t& decimator_out) {
|
||||
const uint32_t validPreamble = DEFAULT_PREAMBLE;
|
||||
static uint32_t preambleValue = 0;
|
||||
|
||||
int hit_idx = -1;
|
||||
|
||||
for (; samples_eaten < (int)decimator_out.count; samples_eaten += SAMPLE_PER_SYMBOL) {
|
||||
float phaseSum = 0.0f;
|
||||
|
||||
for (int j = 0; j < SAMPLE_PER_SYMBOL - 1; j++) {
|
||||
phaseSum += get_phase_diff(decimator_out.p[samples_eaten + j], decimator_out.p[samples_eaten + j + 1]);
|
||||
}
|
||||
|
||||
phase_buffer[phase_buffer_index] = phaseSum / (SAMPLE_PER_SYMBOL - 1);
|
||||
phase_buffer_index = (phase_buffer_index + 1) % ROLLING_WINDOW;
|
||||
|
||||
bool bitDecision = (phaseSum > 0.0f);
|
||||
preambleValue = (preambleValue << 1) | bitDecision;
|
||||
|
||||
int errors = __builtin_popcountl(preambleValue ^ validPreamble) & 0xFFFFFFFF;
|
||||
|
||||
if (errors == 0) {
|
||||
hit_idx = samples_eaten + SAMPLE_PER_SYMBOL;
|
||||
fskPacketData.syncWord = preambleValue;
|
||||
fskPacketData.max_dB = max_dB;
|
||||
|
||||
for (int k = 0; k < ROLLING_WINDOW; k++) {
|
||||
frequency_offset_estimate += phase_buffer[k];
|
||||
}
|
||||
|
||||
frequency_offset = frequency_offset_estimate / ROLLING_WINDOW;
|
||||
|
||||
fskPacketData.frequency_offset_hz = (frequency_offset * demod_input_fs) / (2.0f * M_PI);
|
||||
|
||||
preambleValue = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit_idx == -1) {
|
||||
samples_eaten = samples_eaten;
|
||||
return;
|
||||
}
|
||||
|
||||
samples_eaten = hit_idx;
|
||||
parseState = Parse_State_Sync;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::handleSyncWordState(const buffer_c16_t& decimator_out) {
|
||||
const int syncword_bytes = 4;
|
||||
const uint32_t validSyncWord = DEFAULT_SYNC_WORD;
|
||||
|
||||
if ((int)decimator_out.count - samples_eaten <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
demodulateFSKBits(decimator_out, syncword_bytes);
|
||||
|
||||
if (packet_index < syncword_bytes || bit_index != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t receivedSyncWord = (rb_buf[0] << 24) | (rb_buf[1] << 16) | (rb_buf[2] << 8) | rb_buf[3];
|
||||
|
||||
int errors = __builtin_popcountl(receivedSyncWord ^ validSyncWord) & 0xFFFFFFFF;
|
||||
|
||||
if (errors <= 3) {
|
||||
fskPacketData.syncWord = receivedSyncWord;
|
||||
parseState = Parse_State_PDU_Payload;
|
||||
memset(fskPacketData.data, 0, sizeof(fskPacketData.data));
|
||||
} else {
|
||||
resetToDefaultState();
|
||||
}
|
||||
|
||||
memset(rb_buf, 0, sizeof(rb_buf));
|
||||
resetBitPacketIndex();
|
||||
}
|
||||
|
||||
void FSKRxProcessor::handlePDUPayloadState(const buffer_c16_t& decimator_out) {
|
||||
if ((int)decimator_out.count - samples_eaten <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
demodulateFSKBits(decimator_out, NUM_DATA_BYTE);
|
||||
|
||||
if (packet_index < NUM_DATA_BYTE || bit_index != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
fskPacketData.dataLen = NUM_DATA_BYTE;
|
||||
|
||||
// Copy the decoded bits to the packet data
|
||||
for (int i = 0; i < NUM_DATA_BYTE; i++) {
|
||||
fskPacketData.data[i] |= rb_buf[i];
|
||||
}
|
||||
|
||||
FSKRxPacketMessage data_message{&fskPacketData};
|
||||
shared_memory.application_queue.push(data_message);
|
||||
|
||||
memset(rb_buf, 0, sizeof(rb_buf));
|
||||
|
||||
resetToDefaultState();
|
||||
}
|
||||
|
||||
void FSKRxProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured || parseState == Parse_State_Parsing_Data) return;
|
||||
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
spectrum_samples += decim_1_out.count;
|
||||
samples_eaten = 0;
|
||||
|
||||
if (spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.feed(decim_1_out, channel_filter_low_f,
|
||||
channel_filter_high_f, channel_filter_transition);
|
||||
}
|
||||
while ((int)decim_1_out.count - samples_eaten > 0) {
|
||||
if ((parseState == Parse_State_Wait_For_Peak) || (parseState == Parse_State_Preamble)) {
|
||||
float power = detect_peak_power(buffer, buffer.count);
|
||||
|
||||
// process_bits();
|
||||
if (power) {
|
||||
parseState = Parse_State_Preamble;
|
||||
agc_power = power;
|
||||
fskPacketData.power = power;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the status.
|
||||
samples_processed += buffer.count;
|
||||
if (agc_power) {
|
||||
agc_correct_iq(buffer, buffer.count, agc_power);
|
||||
}
|
||||
|
||||
if (samples_processed >= stat_update_threshold) {
|
||||
// send_packet(data);
|
||||
samples_processed -= stat_update_threshold;
|
||||
if (parseState == Parse_State_Preamble) {
|
||||
peak_timeout++;
|
||||
|
||||
// 960,000 fs / 2048 samples = 468.75 Hz, so 55 calls is about 0.053 seconds before timeout.
|
||||
if (peak_timeout == 4) {
|
||||
resetToDefaultState();
|
||||
} else {
|
||||
handlePreambleState(decim_1_out);
|
||||
}
|
||||
}
|
||||
|
||||
if (parseState == Parse_State_Sync) {
|
||||
handleSyncWordState(decim_1_out);
|
||||
}
|
||||
|
||||
if (parseState == Parse_State_PDU_Payload) {
|
||||
handlePDUPayloadState(decim_1_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +290,7 @@ void FSKRxProcessor::on_message(const Message* const message) {
|
||||
break;
|
||||
case Message::ID::UpdateSpectrum:
|
||||
case Message::ID::SpectrumStreamingConfig:
|
||||
channel_spectrum.on_message(message);
|
||||
// channel_spectrum.on_message(message);
|
||||
break;
|
||||
|
||||
case Message::ID::SampleRateConfig:
|
||||
@@ -179,7 +298,7 @@ void FSKRxProcessor::on_message(const Message* const message) {
|
||||
break;
|
||||
|
||||
case Message::ID::CaptureConfig:
|
||||
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
||||
// capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -188,83 +307,65 @@ void FSKRxProcessor::on_message(const Message* const message) {
|
||||
}
|
||||
|
||||
void FSKRxProcessor::configure(const FSKRxConfigureMessage& message) {
|
||||
// Extract message variables.
|
||||
deviation = message.deviation;
|
||||
channel_decimation = message.channel_decimation;
|
||||
// channel_filter_taps = message.channel_filter;
|
||||
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
}
|
||||
|
||||
void FSKRxProcessor::capture_config(const CaptureConfigMessage& message) {
|
||||
if (message.config) {
|
||||
audio_output.set_stream(std::make_unique<StreamInput>(message.config));
|
||||
} else {
|
||||
audio_output.set_stream(nullptr);
|
||||
}
|
||||
SAMPLE_PER_SYMBOL = message.samplesPerSymbol;
|
||||
DEFAULT_SYNC_WORD = message.syncWord;
|
||||
NUM_SYNC_WORD_BYTE = message.syncWordLength;
|
||||
DEFAULT_PREAMBLE = message.preamble;
|
||||
NUM_PREAMBLE_BYTE = message.preambleLength;
|
||||
NUM_DATA_BYTE = message.numDataBytes;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::sample_rate_config(const SampleRateConfigMessage& message) {
|
||||
const auto sample_rate = message.sample_rate;
|
||||
|
||||
// The actual sample rate is the requested rate * the oversample rate.
|
||||
// See oversample.hpp for more details on oversampling.
|
||||
baseband_fs = sample_rate * toUType(message.oversample_rate);
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
|
||||
// TODO: Do we need to use the taps that the decimators get configured with?
|
||||
channel_filter_low_f = taps_200k_decim_1.low_frequency_normalized * sample_rate;
|
||||
channel_filter_high_f = taps_200k_decim_1.high_frequency_normalized * sample_rate;
|
||||
channel_filter_transition = taps_200k_decim_1.transition_normalized * sample_rate;
|
||||
|
||||
// Compute the scalar that corrects the oversample_rate to be x8 when computing
|
||||
// the spectrum update interval. The original implementation only supported x8.
|
||||
// TODO: Why is this needed here but not in proc_replay? There must be some other
|
||||
// assumption about x8 oversampling in some component that makes this necessary.
|
||||
const auto oversample_correction = toUType(message.oversample_rate) / 8.0;
|
||||
|
||||
// The spectrum update interval controls how often the waterfall is fed new samples.
|
||||
spectrum_interval_samples = sample_rate / (spectrum_rate_hz * oversample_correction);
|
||||
spectrum_samples = 0;
|
||||
|
||||
// For high sample rates, the M4 is busy collecting samples so the
|
||||
// waterfall runs slower. Reduce the update interval so it runs faster.
|
||||
// NB: Trade off: looks nicer, but more frequent updates == more CPU.
|
||||
if (sample_rate >= 1'500'000)
|
||||
spectrum_interval_samples /= (sample_rate / 750'000);
|
||||
|
||||
switch (message.oversample_rate) {
|
||||
case OversampleRate::x4:
|
||||
// M4 can't handle 2 decimation passes for sample rates needing x4.
|
||||
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<NoopDecim>();
|
||||
break;
|
||||
|
||||
case OversampleRate::x8:
|
||||
// M4 can't handle 2 decimation passes for sample rates <= 600k.
|
||||
if (message.sample_rate < 600'000) {
|
||||
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
|
||||
} else {
|
||||
// Using 180k taps to provide better filtering with a single pass.
|
||||
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_180k_wfm_decim_0.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim8>().configure(taps_180k_wfm_decim_0.taps);
|
||||
decim_1.set<NoopDecim>();
|
||||
}
|
||||
break;
|
||||
|
||||
case OversampleRate::x16:
|
||||
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
|
||||
break;
|
||||
|
||||
case OversampleRate::x32:
|
||||
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
|
||||
break;
|
||||
|
||||
case OversampleRate::x64:
|
||||
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
|
||||
decim_0.set<dsp::decimate::FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
|
||||
decim_1.set<dsp::decimate::FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -282,33 +383,12 @@ void FSKRxProcessor::sample_rate_config(const SampleRateConfigMessage& message)
|
||||
// size_t channel_filter_input_fs = decim_1_output_fs;
|
||||
// size_t channel_filter_output_fs = channel_filter_input_fs / channel_decimation;
|
||||
|
||||
size_t demod_input_fs = decim_1_output_fs;
|
||||
|
||||
send_packet((uint32_t)demod_input_fs);
|
||||
demod_input_fs = decim_1_output_fs;
|
||||
|
||||
// Set ready to process data.
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::flush() {
|
||||
// word_extractor.flush();
|
||||
}
|
||||
|
||||
void FSKRxProcessor::reset() {
|
||||
clear_data_bits();
|
||||
has_sync_ = false;
|
||||
inverted = false;
|
||||
word_count = 0;
|
||||
|
||||
samples_processed = 0;
|
||||
}
|
||||
|
||||
void FSKRxProcessor::send_packet(uint32_t data) {
|
||||
data_message.is_data = true;
|
||||
data_message.value = data;
|
||||
shared_memory.application_queue.push(data_message);
|
||||
}
|
||||
|
||||
/* main **************************************************/
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "spectrum_collector.hpp"
|
||||
@@ -46,21 +45,6 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
/* Normalizes audio stream to +/-1.0f */
|
||||
class AudioNormalizer {
|
||||
public:
|
||||
void execute_in_place(const buffer_f32_t& audio);
|
||||
|
||||
private:
|
||||
void calculate_thresholds();
|
||||
|
||||
uint32_t counter_ = 0;
|
||||
float min_ = 99.0f;
|
||||
float max_ = -99.0f;
|
||||
float t_hi_ = 1.0;
|
||||
float t_lo_ = 1.0;
|
||||
};
|
||||
|
||||
/* A decimator that just returns the source buffer. */
|
||||
class NoopDecim {
|
||||
public:
|
||||
@@ -111,44 +95,92 @@ class MultiDecimator {
|
||||
|
||||
class FSKRxProcessor : public BasebandProcessor {
|
||||
public:
|
||||
FSKRxProcessor();
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
size_t baseband_fs = 1024000; // aka: sample_rate
|
||||
static constexpr int ROLLING_WINDOW{32};
|
||||
static constexpr uint16_t MAX_BUFFER_SIZE{512};
|
||||
|
||||
enum Parse_State {
|
||||
Parse_State_Wait_For_Peak = 0,
|
||||
Parse_State_Preamble,
|
||||
Parse_State_Sync,
|
||||
Parse_State_PDU_Payload,
|
||||
Parse_State_Parsing_Data
|
||||
};
|
||||
|
||||
size_t baseband_fs = 960000;
|
||||
uint8_t stat_update_interval = 10;
|
||||
uint32_t stat_update_threshold = baseband_fs / stat_update_interval;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
float detect_peak_power(const buffer_c8_t& buffer, int N);
|
||||
void agc_correct_iq(const buffer_c8_t& buffer, int N, float measured_power);
|
||||
float get_phase_diff(const complex16_t& sample0, const complex16_t& sample1);
|
||||
void demodulateFSKBits(const buffer_c16_t& decimator_out, int num_demod_byte);
|
||||
void resetPreambleTracking();
|
||||
void resetBitPacketIndex();
|
||||
void resetToDefaultState();
|
||||
|
||||
void handlePreambleState(const buffer_c16_t& decimator_out);
|
||||
void handleSyncWordState(const buffer_c16_t& decimator_out);
|
||||
void handlePDUPayloadState(const buffer_c16_t& decimator_out);
|
||||
|
||||
void configure(const FSKRxConfigureMessage& message);
|
||||
void capture_config(const CaptureConfigMessage& message);
|
||||
void sample_rate_config(const SampleRateConfigMessage& message);
|
||||
void flush();
|
||||
void reset();
|
||||
void send_packet(uint32_t data);
|
||||
void process_bits(const buffer_c8_t& buffer);
|
||||
|
||||
void clear_data_bits();
|
||||
void handle_sync(bool inverted);
|
||||
|
||||
/* Returns true if the batch has as sync frame. */
|
||||
bool has_sync() const { return has_sync_; }
|
||||
|
||||
/* Set once app is ready to receive messages. */
|
||||
bool configured = false;
|
||||
|
||||
/* Buffer for decimated IQ data. */
|
||||
std::array<complex16_t, 512> dst{};
|
||||
std::array<complex16_t, MAX_BUFFER_SIZE> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
|
||||
/* Buffer for demodulated audio. */
|
||||
std::array<float, 16> audio{};
|
||||
const buffer_f32_t audio_buffer{audio.data(), audio.size()};
|
||||
uint8_t rb_buf[MAX_BUFFER_SIZE];
|
||||
|
||||
dsp::demodulate::FM demod{};
|
||||
int rb_head{-1};
|
||||
int32_t g_threshold{0};
|
||||
uint8_t channel_number{0};
|
||||
|
||||
uint16_t process = 0;
|
||||
|
||||
bool configured{false};
|
||||
FskPacketData fskPacketData{};
|
||||
|
||||
Parse_State parseState{Parse_State_Wait_For_Peak};
|
||||
|
||||
int sample_idx{0};
|
||||
int samples_eaten{0};
|
||||
|
||||
int32_t max_dB{0};
|
||||
int8_t real{0};
|
||||
int8_t imag{0};
|
||||
|
||||
uint16_t peak_timeout{0};
|
||||
float noise_floor{12.0}; // Using LNA 40 and VGA 20. 10.0 was 40/0 ratio.
|
||||
float target_power_db{5.0};
|
||||
float agc_power{0.0f};
|
||||
|
||||
float frequency_offset_estimate{0.0f};
|
||||
float frequency_offset{0.0f};
|
||||
float phase_buffer[ROLLING_WINDOW] = {0.0f};
|
||||
int phase_buffer_index = 0;
|
||||
|
||||
uint16_t packet_index{0};
|
||||
uint8_t bit_index{0};
|
||||
size_t demod_input_fs{0};
|
||||
|
||||
uint8_t SAMPLE_PER_SYMBOL{1};
|
||||
uint32_t DEFAULT_PREAMBLE{0xAAAAAAAA};
|
||||
uint32_t DEFAULT_SYNC_WORD{0xFFFFFFFF};
|
||||
uint8_t NUM_SYNC_WORD_BYTE{4};
|
||||
uint8_t NUM_PREAMBLE_BYTE{4};
|
||||
uint16_t NUM_DATA_BYTE = MAX_BUFFER_SIZE - NUM_SYNC_WORD_BYTE - NUM_PREAMBLE_BYTE;
|
||||
|
||||
SpectrumCollector channel_spectrum{};
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
static constexpr auto spectrum_rate_hz = 50.0f;
|
||||
|
||||
/* The actual type will be configured depending on the sample rate. */
|
||||
MultiDecimator<
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim4,
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8>
|
||||
@@ -159,9 +191,7 @@ class FSKRxProcessor : public BasebandProcessor {
|
||||
NoopDecim>
|
||||
decim_1{};
|
||||
|
||||
/* Filter to 24kHz and demodulate. */
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||
size_t deviation = 3750;
|
||||
// fir_taps_real<32> channel_filter_taps = 0;
|
||||
size_t channel_decimation = 2;
|
||||
int32_t channel_filter_low_f = 0;
|
||||
@@ -172,51 +202,6 @@ class FSKRxProcessor : public BasebandProcessor {
|
||||
FMSquelch squelch{};
|
||||
uint64_t squelch_history = 0;
|
||||
|
||||
// /* LPF to reduce noise. POCSAG supports 2400 baud, but that falls
|
||||
// * nicely into the transition band of this 1800Hz filter.
|
||||
// * scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False) */
|
||||
// IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f},
|
||||
// {1.00000000f, -1.34896775f, 0.51398189f}}};
|
||||
|
||||
/* Attempts to de-noise and normalize signal. */
|
||||
AudioNormalizer normalizer{};
|
||||
|
||||
/* Handles writing audio stream to hardware. */
|
||||
AudioOutput audio_output{};
|
||||
|
||||
/* Holds the data sent to the app. */
|
||||
AFSKDataMessage data_message{false, 0};
|
||||
|
||||
/* Used to keep track of how many samples were processed
|
||||
* between status update messages. */
|
||||
uint32_t samples_processed = 0;
|
||||
|
||||
/* Number of bits in 'data_' member. */
|
||||
static constexpr uint8_t data_bit_count = sizeof(uint32_t) * 8;
|
||||
|
||||
/* Sync frame codeword. */
|
||||
static constexpr uint32_t sync_codeword = 0x12345678;
|
||||
|
||||
/* When true, sync frame has been received. */
|
||||
bool has_sync_ = false;
|
||||
|
||||
/* When true, bit vales are flipped in the codewords. */
|
||||
bool inverted = false;
|
||||
|
||||
uint32_t data = 0;
|
||||
uint8_t bit_count = 0;
|
||||
uint8_t word_count = 0;
|
||||
|
||||
/* LPF to reduce noise. POCSAG supports 2400 baud, but that falls
|
||||
* nicely into the transition band of this 1800Hz filter.
|
||||
* scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False) */
|
||||
IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f},
|
||||
{1.00000000f, -1.34896775f, 0.51398189f}}};
|
||||
|
||||
SpectrumCollector channel_spectrum{};
|
||||
size_t spectrum_interval_samples = 0;
|
||||
size_t spectrum_samples = 0;
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
RSSIThread rssi_thread{};
|
||||
|
||||
@@ -1649,4 +1649,36 @@ static constexpr fir_taps_real<24> taps_BTLE_2M_PHY_decim_0 = {
|
||||
|
||||
}},
|
||||
};
|
||||
|
||||
// Tested to be better at capturing both 4.0 and 5.0 device. Better attenuation at channel end.
|
||||
static constexpr fir_taps_real<24> taps_BTLE_Dual_PHY = {
|
||||
.low_frequency_normalized = -750000.0f / 4000000.0f,
|
||||
.high_frequency_normalized = 750000.0f / 4000000.0f,
|
||||
.transition_normalized = 250000.0f / 4000000.0f,
|
||||
.taps = {{3,
|
||||
-5,
|
||||
-97,
|
||||
-144,
|
||||
317,
|
||||
1099,
|
||||
396,
|
||||
-2887,
|
||||
-4814,
|
||||
1912,
|
||||
18134,
|
||||
32767,
|
||||
32767,
|
||||
18134,
|
||||
1912,
|
||||
-4814,
|
||||
-2887,
|
||||
396,
|
||||
1099,
|
||||
317,
|
||||
-144,
|
||||
-97,
|
||||
-5,
|
||||
3}},
|
||||
};
|
||||
|
||||
#endif /*__DSP_FIR_TAPS_H__*/
|
||||
|
||||
+41
-15
@@ -134,6 +134,7 @@ class Message {
|
||||
NoaaAptRxConfigure = 77,
|
||||
NoaaAptRxStatusData = 78,
|
||||
NoaaAptRxImageData = 79,
|
||||
FSKPacket = 80,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -452,6 +453,17 @@ struct BlePacketData {
|
||||
uint8_t dataLen;
|
||||
};
|
||||
|
||||
struct FskPacketData {
|
||||
int8_t real;
|
||||
int8_t imag;
|
||||
int max_dB;
|
||||
uint8_t data[512];
|
||||
uint16_t dataLen;
|
||||
uint64_t syncWord;
|
||||
float power;
|
||||
float frequency_offset_hz;
|
||||
};
|
||||
|
||||
class BLEPacketMessage : public Message {
|
||||
public:
|
||||
constexpr BLEPacketMessage(
|
||||
@@ -463,6 +475,17 @@ class BLEPacketMessage : public Message {
|
||||
BlePacketData* packet{nullptr};
|
||||
};
|
||||
|
||||
class FSKRxPacketMessage : public Message {
|
||||
public:
|
||||
constexpr FSKRxPacketMessage(
|
||||
FskPacketData* packet)
|
||||
: Message{ID::FSKPacket},
|
||||
packet{packet} {
|
||||
}
|
||||
|
||||
FskPacketData* packet{nullptr};
|
||||
};
|
||||
|
||||
class CodedSquelchMessage : public Message {
|
||||
public:
|
||||
constexpr CodedSquelchMessage(
|
||||
@@ -1127,24 +1150,27 @@ class FSKConfigureMessage : public Message {
|
||||
class FSKRxConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr FSKRxConfigureMessage(
|
||||
const fir_taps_real<24> decim_0_filter,
|
||||
const fir_taps_real<32> decim_1_filter,
|
||||
const fir_taps_real<32> channel_filter,
|
||||
const size_t channel_decimation,
|
||||
const size_t deviation)
|
||||
const uint8_t samplesPerSymbol,
|
||||
const uint32_t syncWord,
|
||||
const uint8_t syncWordLength,
|
||||
const uint32_t preamble,
|
||||
const uint8_t preambleLength,
|
||||
const uint16_t numDataBytes)
|
||||
: Message{ID::FSKRxConfigure},
|
||||
decim_0_filter(decim_0_filter),
|
||||
decim_1_filter(decim_1_filter),
|
||||
channel_filter(channel_filter),
|
||||
channel_decimation{channel_decimation},
|
||||
deviation{deviation} {
|
||||
samplesPerSymbol(samplesPerSymbol),
|
||||
syncWord(syncWord),
|
||||
syncWordLength(syncWordLength),
|
||||
preamble(preamble),
|
||||
preambleLength(preambleLength),
|
||||
numDataBytes(numDataBytes) {
|
||||
}
|
||||
|
||||
const fir_taps_real<24> decim_0_filter;
|
||||
const fir_taps_real<32> decim_1_filter;
|
||||
const fir_taps_real<32> channel_filter;
|
||||
const size_t channel_decimation;
|
||||
const size_t deviation;
|
||||
const uint8_t samplesPerSymbol;
|
||||
const uint32_t syncWord;
|
||||
const uint8_t syncWordLength;
|
||||
const uint32_t preamble;
|
||||
const uint8_t preambleLength;
|
||||
const uint16_t numDataBytes;
|
||||
};
|
||||
|
||||
class POCSAGConfigureMessage : public Message {
|
||||
|
||||
@@ -91,6 +91,26 @@ float mag2_to_dbv_norm(const float mag2) {
|
||||
return (fast_log2(mag2) - mag2_log2_max) * mag2_to_db_factor;
|
||||
}
|
||||
|
||||
// Function to calculate dBm and normalize based on LNA and VGA settings
|
||||
float mag2_to_dbm_8bit_normalized(int8_t real, int8_t imag, float v_ref, float R) {
|
||||
// Step 1: Normalize IQ values (convert 8-bit signed to -1.0 to +1.0)
|
||||
float I = real / 127.0f; // Map the 8-bit real part to the [-1, 1] range
|
||||
float Q = imag / 127.0f; // Map the 8-bit imaginary part to the [-1, 1] range
|
||||
|
||||
// Step 2: Compute the magnitude squared (I^2 + Q^2)
|
||||
float mag2 = I * I + Q * Q;
|
||||
|
||||
// Step 3: Convert the magnitude squared to actual power (in watts)
|
||||
float voltage_squared = mag2 * v_ref * v_ref;
|
||||
float power_watts = voltage_squared / R;
|
||||
|
||||
// Step 4: Convert the power to dBm (multiply by 1000 to convert watts to milliwatts)
|
||||
float power_milliwatts = power_watts * 1000.0f;
|
||||
float dbm_measured = 10.0f * log10f(power_milliwatts);
|
||||
|
||||
return dbm_measured;
|
||||
}
|
||||
|
||||
// Integer in and out approximation
|
||||
// >40 times faster float sqrt(x*x+y*y) on Cortex M0
|
||||
// derived from https://dspguru.com/dsp/tricks/magnitude-estimator/
|
||||
|
||||
@@ -90,6 +90,7 @@ float fast_log2(const float val);
|
||||
float fast_pow2(const float val);
|
||||
|
||||
float mag2_to_dbv_norm(const float mag2);
|
||||
float mag2_to_dbm_8bit_normalized(int8_t real, int8_t imag, float v_ref, float R);
|
||||
|
||||
inline float magnitude_squared(const std::complex<float> c) {
|
||||
const auto r = c.real();
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt
|
||||
- Run Python 3 script: `./make_airlines_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
- Move file "airlines.db" to /ADSB folder on SDCARD
|
||||
|
||||
@@ -325,7 +325,7 @@ AMT,ATA Airlines,,United States
|
||||
AMU,Air Macau,AIR MACAO,Macao
|
||||
AMV,AMC Airlines,,Egypt
|
||||
AMW,Air Midwest,,United States
|
||||
AMX,Aeroméxico,AEROMEXICO,Mexico
|
||||
AMX,Aeromexico,AEROMEXICO,Mexico
|
||||
AMY,Air Ambar,AIR AMBAR,Dominican Republic
|
||||
AMZ,Amiya Airline,AMIYA AIR,Nigeria
|
||||
ANA,All Nippon Airways,ALL NIPPON,Japan
|
||||
@@ -408,7 +408,7 @@ ARC,Air Routing International Corp.,,United States
|
||||
ARD,Aerocondor,,Portugal
|
||||
ARE,Aires Aerovías de Integración Regional S.A.,AIRES,Colombia
|
||||
ARF,Aero Flight,,Germany
|
||||
ARG,Aerolíneas Argentinas,ARGENTINA,Argentina
|
||||
ARG,Aerolineas Argentinas,ARGENTINA,Argentina
|
||||
ARH,Aerohelicopteros,AEROHELCA,Venezuela
|
||||
ARI,Aero Vics,AEROVICS,Mexico
|
||||
ARJ,Aerojet de Costa Rica S.A.,,Costa Rica
|
||||
@@ -820,7 +820,7 @@ BNT,Bentiu Air Transport,BENTIU AIR,Sudan
|
||||
BNV,Benane Aviation Corporation,BENANE,Mauritania
|
||||
BNW,British North West Airlines,BRITISH NORTH,United Kingdom
|
||||
BNX,LAI - Línea Aérea IAACA,AIR BARINAS,Venezuela
|
||||
BNZ,Aerolíneas Bonanza,AERO BONANZA,Mexico
|
||||
BNZ,Bonza,BONZA,Australia
|
||||
BOA,Boniair,KUMANOVO,Macedonia
|
||||
BOB,Backbone A/S,BACKBONE,Denmark
|
||||
BOC,Aerobona,AEROBONA,Mexico
|
||||
@@ -1465,6 +1465,7 @@ DET,DETA Air,SAMAL,Kazakhstan
|
||||
DEV,Red Devils Parachute Display Team,RED DEVILS,United Kingdom
|
||||
DFA,Aero Coach Aviation,AERO COACH,United States
|
||||
DFC,Aeropartner,DARK BLUE,Czech Republic
|
||||
DFL,Babcock AirAmbulance,MEDIFLIGHT,Sweden
|
||||
DFS,Dwyer Aircraft Services,DWYAIR,United States
|
||||
DGA,Yellow River Delta General Aviation,YELLOW RIVER,China
|
||||
DGO,DGO Jet,DGO JET,Mexico
|
||||
@@ -1592,8 +1593,10 @@ DVA,Discovery Airways,DISCOVERY AIRWAYS,United States
|
||||
DVB,Don Avia,DONSEBAI,Kazakhstan
|
||||
DVI,Aero Davinci International,AERO DAVINCI,Mexico
|
||||
DVN,Adventia,,Spain
|
||||
DVY,Legends Airways,DEVIL RAY,United States
|
||||
DWA,Dense Airways,,United States
|
||||
DWD,Deutsche Luftverkehrsgesellschaft (DLT),,Germany
|
||||
DWI,Arajet,DOMINICAN,Dominican Republic
|
||||
DWN,Dawn Air,DAWN AIR,United States
|
||||
DWR,Delaware Skyways,DELAWARE,United States
|
||||
DWT,Darwin Airline,DARWIN,Switzerland
|
||||
@@ -1905,6 +1908,7 @@ EZJ,Ezjet GT,GUYANA JET,Guyana
|
||||
EZS,easyJet Switzerland,TOPSWISS,Switzerland
|
||||
EZX,Eagle Express Air Charter,EAGLEXPRESS,Malaysia
|
||||
EZY,easyJet UK,EASY,United Kingdom
|
||||
EZZ,ETF Airways,ENTERPRIZE,Croatia
|
||||
FAB,First Air,FIRST AIR,Canada
|
||||
FAC,Atlantic Helicopters,FAROECOPTER,Denmark
|
||||
FAD,Rog-Air,AIR FRONTIER,Canada
|
||||
@@ -2034,7 +2038,7 @@ FJI,Fiji Airways,PACIFIC,Fiji
|
||||
FJK,Fly Jet Kz,,Kazakhstan
|
||||
FJM,Fly Jamaica Airways,GREENHEART,Jamaica
|
||||
FJO,Flexjet Operations Malta Limited,FLEX MALTA,Malta
|
||||
FRJ,Fly Jordan,SOLITAIRE AIR,Jordan
|
||||
FJR,Fly Jordan,SOLITAIRE AIR,Jordan
|
||||
FJS,Florida Jet Service,FLORIDAJET,United States
|
||||
FKA,Flying kangaroo Airline,,Australia
|
||||
FKI,FLM Aviation Mohrdieck,KIEL AIR,Germany
|
||||
@@ -2397,7 +2401,7 @@ GPL,DLR,GERMAN POLAR,Germany
|
||||
GPM,Grup Air-Med,GRUPOMED,Spain
|
||||
GPR,GPM Aeroservicio,GPM AEROSERVICIO,Mexico
|
||||
GPX,GP Aviation,,Bulgaria
|
||||
GRA,Guardian Air Asset Management,FLEX,South Africa
|
||||
GRA,Costa Rica Green Airways,,,Costa Rica
|
||||
GRB,Phoenix Air Group,GREY BIRD,United States
|
||||
GRD,National Grid plc,GRID,United Kingdom
|
||||
GRE,Air Scotland,GREECE AIRWAYS,Greece
|
||||
@@ -2459,7 +2463,7 @@ GWN,Gwyn Aviation,GWYN,United Kingdom
|
||||
GWR,Aura Airlines,LEMON,Spain
|
||||
GWS,General Airways,GENAIR,South Africa
|
||||
GWY,USA3000 Airlines,GETAWAY,United States
|
||||
GXA,Grixona,GRIXONA,Moldova
|
||||
GXA,GlobalX Airlines,GEMINI,United States
|
||||
GXG,GermanXL,,Germany
|
||||
GXL,Star XL German Airlines,STARDUST,Germany
|
||||
GXY,Galaxy Airlines,GALAX,Japan
|
||||
@@ -2608,6 +2612,7 @@ HMC,Heliamerica De Mexico,HELIAMERICA,Mexico
|
||||
HMD,Charlie Hammonds Flying Service,HAMMOND,United States
|
||||
HME,Babcock MCS,HELIMED,United Kingdom
|
||||
HMF,Norrlandsflyg,LIFEGUARD SWEDEN,Sweden
|
||||
HMJ,Harmony Jets,HARMONG JETS,Malta
|
||||
HMM,Hamra Air,HAMRA,United Arab Emirates
|
||||
HMP,Papair Terminal,PAPAIR TERMINAL,Haiti
|
||||
HMR,North American Charters,HAMMER,Canada
|
||||
@@ -2623,6 +2628,7 @@ HNR,Haiti National Airlines (HANA),HANAIR,Haiti
|
||||
HNT,Helicopteros Internacionales,HELICOP INTER,Mexico
|
||||
HNX,Hankook Airline,,South Korea
|
||||
HOA,Hola Airlines,HOLA,Spain
|
||||
HOB,HL Aircraft LLC,HOBBY JET,United States
|
||||
HOG,Mahogany Air Charters,HOGAN AIR,Zambia
|
||||
HOL,Holiday Airlines (US Airline),HOLIDAY,United States
|
||||
HOM,Aero Homex,AERO HOMEX,Mexico
|
||||
@@ -3077,7 +3083,7 @@ JSM,Jet Stream,,Moldova
|
||||
JSN,Suncor Energy Inc,JETSUN,Canada
|
||||
JSP,Palmer Aviation,PALMER,United Kingdom
|
||||
JSR,Jusur airways,,Egypt
|
||||
JST,Eastern Australia Airlines,,Australia
|
||||
JST,Jetstar Airways,,Australia
|
||||
JSV,Japan Aircraft Service,,Japan
|
||||
JSW,Jigsaw Project,,United Kingdom
|
||||
JSX,JetSuiteX Air,BIGSTRIPE,United States
|
||||
@@ -3617,6 +3623,7 @@ MAK,MAT Macedonian Airlines,MAKAVIO,Macedonia
|
||||
MAL,Morningstar Air Express,MORNINGSTAR,Canada
|
||||
MAM,Aeródromo De La Mancha,AEROMAN,Spain
|
||||
MAN,Mannion Air Charter,MANNION,United States
|
||||
MAO,Mayo Clinic,MAYO,United States
|
||||
MAQ,Mac Aviation,MAC AVIATION,Spain
|
||||
MAR,March Helicopters,MARCH,United Kingdom
|
||||
MAS,Malaysia Airlines,MALAYSIAN,Malaysia
|
||||
@@ -3633,6 +3640,7 @@ MBC,Airjet Exploracao Aerea de Carga,MABECO,Angola
|
||||
MBE,Martin-Baker,MARTIN,United Kingdom
|
||||
MBG,Zephyr Aviation,CHALGROVE,United Kingdom
|
||||
MBI,Mountain Bird,MOUNTAIN BIRD,United States
|
||||
MBK,Chrono Jet,MATBLACK,Canada
|
||||
MBL,First City Air,FIRST CITY,United Kingdom
|
||||
MBN,Zambian Airways,ZAMBIANA,Zambia
|
||||
MBO,Mobil Oil,MOBIL,Canada
|
||||
@@ -4441,6 +4449,7 @@ PDI,Paradise Island Airways,PARADISE ISLAND,United States
|
||||
PDQ,PDQ Air Charter,DISPATCH,United States
|
||||
PDR,COMAV,SPEEDSTER,Namibia
|
||||
PDT,Piedmont Airlines,PIEDMONT,United States
|
||||
PDU,Purdue University,PURDUE,United States
|
||||
PDV,Elicar,ELICAR,Italy
|
||||
PDY,Pen-Avia,PENDLEY,United Kingdom
|
||||
PEA,Pan Europeenne Air Service,,France
|
||||
@@ -4564,6 +4573,7 @@ PNH,Panh,KUBAN LIK,Russia
|
||||
PNK,Air Pink,AIRPINK,Serbia
|
||||
PNL,Aero Personal,AEROPERSONAL,Mexico
|
||||
PNM,Panorama,PANORAMA,Spain
|
||||
PNO,Panorama Aviation,PANO,Canada
|
||||
PNP,Pineapple Air,PINEAPPLE AIR,Bahamas
|
||||
PNR,PAN Air,SKYJET,Spain
|
||||
PNS,Survey Udara (Penas),PENAS,Indonesia
|
||||
@@ -4931,6 +4941,7 @@ RNB,Rosneft-Baltika,ROSBALT,Russia
|
||||
RND,Rutland Aviation,RUTLAND,United Kingdom
|
||||
RNE,Air Salone,AIR SALONE,Sierra Leone
|
||||
RNG,Orange Aircraft Leasing,ORANGE,Netherlands
|
||||
RNI,Rennia Aviation,RENNIA,United States
|
||||
RNM,Aeronem Air Cargo,AEROMNEM,Ecuador
|
||||
RNR,Air Cargo Masters,RUNNER,United States
|
||||
RNS,Ronso,RONSO,Mexico
|
||||
@@ -5216,6 +5227,7 @@ SFU,Solent Flight,SAINTS,United Kingdom
|
||||
SFW,Safi Airways,SAFI,AF
|
||||
SFX,S.K. Logistics,SWAMP FOX,United States
|
||||
SFY,Gulf Flite Center,SKY FLITE,United States
|
||||
SFZ,Pionair,SKYFORCE,Australia
|
||||
SGA,Air Saigon,AIR SAIGON,Vietnam
|
||||
SGB,Sky King Inc.,SONGBIRD,United States
|
||||
SGC,SGC Aviation,SAINT GEORGE,Austria
|
||||
@@ -5270,7 +5282,7 @@ SIE,Sierra Express,SEREX,United States
|
||||
SIH,Skynet Airlines,BLUEJET,Ireland
|
||||
SII,Aero Servicios Ejecutivos Internacionales,ASEISA,Mexico
|
||||
SIJ,Seco International,,Japan
|
||||
SIL,Servicios Aeronáuticos Integrales,SERVICIOS INTEGRALES,Mexico
|
||||
SIL,Silver Airways,SILVER WINGS,United States
|
||||
SIM,Star Air,,Sierra Leone
|
||||
SIO,Sirio,SIRIO,Italy
|
||||
SIP,Air Spirit,AIR SPIRIT,United States
|
||||
@@ -5802,6 +5814,7 @@ TKC,Tikal Jets Airlines,TIKAL,Guatemala
|
||||
TKE,Take Air Line,ISLAND BIRD,France
|
||||
TKJ,Tarkim Aviation,TARKIM AVIATION,Turkey
|
||||
TKK,FlyADVANCED,,United States
|
||||
TKM,JM Family Aviation,TACOMA,United States
|
||||
TKR,Canadian Interagency Forest Fire Centre,TANKER,Canada
|
||||
TKS,Tomsk-Avia,,Russia
|
||||
TKX,Tropical International Airways,TROPEXPRESS,Saint Kitts and Nevis
|
||||
@@ -5827,6 +5840,7 @@ TLW,Teamline Air,Teamline,Austria
|
||||
TLX,Telesis Transair,TELESIS,United States
|
||||
TLY,Top Fly,TOPFLY,Spain
|
||||
TMA,Trans Mediterranean Airlines,TANGO LIMA,Lebanon
|
||||
TMB,Volato,TOMBO,United States
|
||||
TMC,Travel Management Company,TRAIL BLAZER,United States
|
||||
TMD,Transmandu,TRANSMANDU,Venezuela
|
||||
TME,Aero Taxi del Centro de Mexico,TAXICENTRO,Mexico
|
||||
@@ -6086,6 +6100,7 @@ UDC,DonbassAero,DONBASS AERO,Ukraine
|
||||
UDN,Dniproavia,DNIEPRO,Ukraine
|
||||
UEA,United Eagle Airlines,UNITED EAGLE,China
|
||||
UED,Air LA,AIR L-A,United States
|
||||
UEE,United Eagle,EAGLE SLOVENIA,Slovenia
|
||||
UEJ,Jetcorp,,United States
|
||||
UES,Ues-Avia Aircompany,AVIASYSTEM,Ukraine
|
||||
UEU,United European Airlines,UNITED EUROPEAN,Romania
|
||||
@@ -6200,7 +6215,7 @@ VAL,Voyageur Airways,VOYAGEUR,Canada
|
||||
VAM,Ameravia,AMERAVIA,Uruguay
|
||||
VAN,Caravan Air,CAMEL,Mauritania
|
||||
VAP,Phuket Air,PHUKET AIR,Thailand
|
||||
VAR,Veca Airlines,VECA,El Salvador
|
||||
VAR,United Aviate Academy,VARNEY,United States
|
||||
VAS,ATRAN Cargo Airlines,ATRAN,Russian Federation
|
||||
VAT,Visionair,VISIONAIR,Ireland
|
||||
VAU,V Australia Airlines,,Australia
|
||||
@@ -6278,7 +6293,7 @@ VIS,Vision Air International,,Pakistan
|
||||
VIT,Aviastar Mandiri,AVIASTAR,Indonesia
|
||||
VIV,Aeroenlaces Nacionales,AEROENLACES,Mexico
|
||||
VIZ,Aerovis Airlines,AEROVIZ,Ukraine
|
||||
VJA,ValuJet Airlines,CRITTER,United States
|
||||
VJA,Vista America,ICONIC,United States
|
||||
VJC,Vietjet Air,VIETJET,Vietnam
|
||||
VJE,AvJet Routing,,United Arab Emirates
|
||||
VJM,Viajes Ejecutivos Mexicanos,VIAJES MEXICANOS,Mexico
|
||||
@@ -6320,7 +6335,7 @@ VNG,Aero Servicios Vanguardia,VANGUARDIA,Mexico
|
||||
VNK,Vipport Joint Stock Company,,Russia
|
||||
VNL,Vanilla Air,VANILLA,Japan
|
||||
VNP,Virgin Pacific,,Fiji
|
||||
VNT,Avient Air Zambia,AVIENT,Zambia
|
||||
VNT,Ventura,VENTURA,United States
|
||||
VNX,Fly Advance,VANCE,United States
|
||||
VNZ,Tbilaviamsheni,TBILAVIA,Georgia
|
||||
VOA,Viaggio Air,VIAGGIO,Bulgaria
|
||||
@@ -6331,7 +6346,7 @@ VOI,Volaris,VOLARIS,Mexico
|
||||
VOL,Blue Chip Jet,BLUE SPEED,Sweden
|
||||
VOO,Volotea,,Spain
|
||||
VOR,Flight Calibration Services Ltd.,FLIGHT CAL,United Kingdom
|
||||
VOS,Rovos Air,ROVOS,South Africa
|
||||
VOS,Volaris El Salvador,JETSAL,South Africa
|
||||
VOZ,Virgin Australia,VELOCITY,Australia
|
||||
VPA,DanubeWings,VIP TAXI,Slovakia
|
||||
VPB,Veteran Air,VETERAN,Ukraine
|
||||
@@ -6422,7 +6437,7 @@ WAU,Wizz Air Ukraine,,Ukraine
|
||||
WAV,Warbelow's Air Ventures,WARBELOW,United States
|
||||
WAW,Wings Airways,WING SHUTTLE,United States
|
||||
WAY,Airways,GARONNE,France
|
||||
WAZ,Wizz Air,WIZZ SKY,United Arab Emirates
|
||||
WAZ,Wizz Air Abu Dabi,WIZZ SKY,United Arab Emirates
|
||||
WBA,Finncomm Airlines,WESTBIRD,Finland
|
||||
WBR,Multi-Aero,WEBER,United States
|
||||
WCA,West Coast Airways,WEST-LEONE,Sierra Leone
|
||||
@@ -6499,6 +6514,8 @@ WLX,West Air Luxembourg,WEST LUX,Luxembourg
|
||||
WMA,Watermakers Air,WATERMAKERS,United States
|
||||
WML,Chantilly Air,MARLIN,United States
|
||||
WMN,Trident Aircraft,WATERMAN,United States
|
||||
WMT,Wizz Air Malta,WIZZAIR MALTA,Malta
|
||||
WMU,W.M. Uni. College of Aviation,SKYBRONCO,United States
|
||||
WNA,Winair,WINAIR,United States
|
||||
WNR,Wondair on Demand Aviation,WONDAIR,Spain
|
||||
WOA,World Airways,WORLD,United States
|
||||
@@ -6535,6 +6552,7 @@ WTP,Westpoint Air,WESTPOINT,Canada
|
||||
WTV,Western Aviators,WESTAVIA,United States
|
||||
WUE,Adolf Wurth GmbH,FASTY,Germany
|
||||
WUK,Wizz Air UK,WIZZ GO,United Kingdom
|
||||
WUP,Wheels Up,UPJET,United States
|
||||
WVA,Hand D Aviation,WABASH VALLEY,United States
|
||||
WVL,Wizz Air Bulgaria,WIZZBUL,Bulgaria
|
||||
WVZ,Winair,,HR
|
||||
@@ -6548,7 +6566,7 @@ WYC,Wycombe Air Centre,WYCOMBE,United Kingdom
|
||||
WYG,Wyoming Airlines,WYOMING,United States
|
||||
WYT,2 Sqn No 1 Elementary Flying Training School,WYTON,United Kingdom
|
||||
WZP,Zip,ZIPPER,Canada
|
||||
WZZ,Wizz Air,WIZZAIR,Hungary
|
||||
WZZ,Wizz Air Hungary,WIZZAIR,Hungary
|
||||
X9F,FRA Air,,Germany
|
||||
XAA,Aeronautical Radio Inc,,United States
|
||||
XAB,Xabre Aerolineas,AERO XABRE,Mexico
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2021 ArjanOnwezen
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -22,26 +23,251 @@
|
||||
# as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
import csv
|
||||
import os
|
||||
import shutil
|
||||
import unicodedata
|
||||
icao_codes=bytearray()
|
||||
airlines_countries=bytearray()
|
||||
row_count=0
|
||||
database=open("airlines.db", "wb")
|
||||
with open('airlinecodes.txt', 'rt', encoding="utf-8") as csv_file:
|
||||
sorted_lines=sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
|
||||
icao_code=row[0]
|
||||
# Normalize some unicode characters
|
||||
airline=unicodedata.normalize('NFKD', row[1][:32]).encode('ascii', 'ignore')
|
||||
country=unicodedata.normalize('NFKD', row[3][:32]).encode('ascii', 'ignore')
|
||||
if len(icao_code) == 3 :
|
||||
airline_padding=bytearray()
|
||||
country_padding=bytearray()
|
||||
icao_codes=icao_codes+bytearray(icao_code+'\0', encoding='ascii')
|
||||
airline_padding=bytearray('\0' * (32 - len(airline)), encoding='ascii')
|
||||
country_padding=bytearray('\0' * (32 - len(country)), encoding='ascii')
|
||||
airlines_countries=airlines_countries+bytearray(airline+airline_padding+country+country_padding)
|
||||
row_count+=1
|
||||
database.write(icao_codes+airlines_countries)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Set, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class AirlineRecord:
|
||||
"""Represents an airline record with all relevant fields"""
|
||||
|
||||
icao_code: str
|
||||
airline: str
|
||||
country: str
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.icao_code)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AirlineRecord):
|
||||
return False
|
||||
return self.icao_code == other.icao_code
|
||||
|
||||
def data_equals(self, other):
|
||||
"""Check if all data fields are equal (excluding ICAO code which is the key)"""
|
||||
if not isinstance(other, AirlineRecord):
|
||||
return False
|
||||
return self.airline == other.airline and self.country == other.country
|
||||
|
||||
|
||||
def backup_previous_file() -> bool:
|
||||
"""Backup the existing airlinecodes.txt file if it exists. Returns True if backup was made."""
|
||||
if os.path.exists("airlinecodes.txt"):
|
||||
shutil.copy2("airlinecodes.txt", "airlinecodes_previous.txt")
|
||||
print("Backed up previous airline codes file")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def download_airline_codes() -> None:
|
||||
"""Download the airline codes file from GitHub"""
|
||||
url = "https://raw.githubusercontent.com/kx1t/planefence-airlinecodes/main/airlinecodes.txt"
|
||||
print(f"Downloading airline codes database from {url}...")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, "airlinecodes.txt")
|
||||
print("Download completed successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error downloading airline codes file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_airline_file(filename: str) -> Dict[str, AirlineRecord]:
|
||||
"""Parse airline codes file and return a dictionary of records"""
|
||||
records = {}
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return records
|
||||
|
||||
try:
|
||||
with open(filename, "rt", encoding="utf-8") as csv_file:
|
||||
sorted_lines = sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(
|
||||
sorted_lines,
|
||||
quotechar='"',
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
skipinitialspace=True,
|
||||
):
|
||||
if len(row) >= 4: # Ensure we have enough columns
|
||||
icao_code = row[0]
|
||||
# Normalize some unicode characters
|
||||
airline = (
|
||||
unicodedata.normalize("NFKD", row[1][:32])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
country = (
|
||||
unicodedata.normalize("NFKD", row[3][:32])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
|
||||
if len(icao_code) == 3:
|
||||
records[icao_code] = AirlineRecord(
|
||||
icao_code=icao_code, airline=airline, country=country
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not parse airline file {filename}: {e}")
|
||||
return {}
|
||||
|
||||
return records
|
||||
|
||||
|
||||
def compare_records(
|
||||
old_records: Dict[str, AirlineRecord], new_records: Dict[str, AirlineRecord]
|
||||
) -> Tuple[Set[str], Set[str], Set[str]]:
|
||||
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
|
||||
old_keys = set(old_records.keys())
|
||||
new_keys = set(new_records.keys())
|
||||
|
||||
new_icao_codes = new_keys - old_keys
|
||||
deleted_icao_codes = old_keys - new_keys
|
||||
|
||||
# Check for changes in existing records
|
||||
changed_icao_codes = set()
|
||||
for icao in old_keys & new_keys:
|
||||
if not old_records[icao].data_equals(new_records[icao]):
|
||||
changed_icao_codes.add(icao)
|
||||
|
||||
return new_icao_codes, deleted_icao_codes, changed_icao_codes
|
||||
|
||||
|
||||
def write_database(records: Dict[str, AirlineRecord]) -> int:
|
||||
"""Write records to database file using original format"""
|
||||
icao_codes = bytearray()
|
||||
airlines_countries = bytearray()
|
||||
row_count = 0
|
||||
|
||||
# Sort by ICAO code for consistency
|
||||
sorted_records = sorted(records.values(), key=lambda r: r.icao_code)
|
||||
|
||||
for record in sorted_records:
|
||||
# Normalize and encode data (same as original)
|
||||
airline = unicodedata.normalize("NFKD", record.airline[:32]).encode(
|
||||
"ascii", "ignore"
|
||||
)
|
||||
country = unicodedata.normalize("NFKD", record.country[:32]).encode(
|
||||
"ascii", "ignore"
|
||||
)
|
||||
|
||||
# Add ICAO code with null terminator (original format)
|
||||
icao_codes = icao_codes + bytearray(record.icao_code + "\0", encoding="ascii")
|
||||
|
||||
# Add padded data fields (original format)
|
||||
airline_padding = bytearray("\0" * (32 - len(airline)), encoding="ascii")
|
||||
country_padding = bytearray("\0" * (32 - len(country)), encoding="ascii")
|
||||
|
||||
airlines_countries = airlines_countries + bytearray(
|
||||
airline + airline_padding + country + country_padding
|
||||
)
|
||||
row_count += 1
|
||||
|
||||
with open("airlines.db", "wb") as database:
|
||||
database.write(icao_codes + airlines_countries)
|
||||
|
||||
return row_count
|
||||
|
||||
|
||||
def create_database() -> None:
|
||||
"""Create the airline database from the downloaded file"""
|
||||
# Backup existing file before downloading new one
|
||||
has_previous = backup_previous_file()
|
||||
|
||||
# Download new file
|
||||
download_airline_codes()
|
||||
|
||||
# Parse both files for comparison
|
||||
old_records = {}
|
||||
if has_previous:
|
||||
print("Parsing previous airline codes file...")
|
||||
old_records = parse_airline_file("airlinecodes_previous.txt")
|
||||
print(f"Found {len(old_records)} records in previous file")
|
||||
|
||||
print("Parsing new airline codes file...")
|
||||
new_records = parse_airline_file("airlinecodes.txt")
|
||||
print(f"Found {len(new_records)} records in new file")
|
||||
|
||||
# Compare records if we have a previous version
|
||||
if old_records:
|
||||
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
|
||||
old_records, new_records
|
||||
)
|
||||
|
||||
# Print change statistics
|
||||
print("\n" + "=" * 50)
|
||||
print("AIRLINE CODES CHANGE SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f"New records: {len(new_icao_codes):>8}")
|
||||
print(f"Deleted records: {len(deleted_icao_codes):>8}")
|
||||
print(f"Changed records: {len(changed_icao_codes):>8}")
|
||||
print(f"Total records: {len(new_records):>8}")
|
||||
print("=" * 50)
|
||||
|
||||
# Show examples of changes (limited to avoid spam)
|
||||
if new_icao_codes and len(new_icao_codes) <= 15:
|
||||
print(f"\nNew ICAO codes: {', '.join(sorted(new_icao_codes))}")
|
||||
elif new_icao_codes:
|
||||
sample_new = sorted(list(new_icao_codes))[:10]
|
||||
print(
|
||||
f"\nSample new ICAO codes: {', '.join(sample_new)} (and {len(new_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
if deleted_icao_codes and len(deleted_icao_codes) <= 15:
|
||||
print(f"Deleted ICAO codes: {', '.join(sorted(deleted_icao_codes))}")
|
||||
elif deleted_icao_codes:
|
||||
sample_deleted = sorted(list(deleted_icao_codes))[:10]
|
||||
print(
|
||||
f"Sample deleted ICAO codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
if changed_icao_codes and len(changed_icao_codes) <= 15:
|
||||
print(f"Changed ICAO codes: {', '.join(sorted(changed_icao_codes))}")
|
||||
elif changed_icao_codes:
|
||||
sample_changed = sorted(list(changed_icao_codes))[:10]
|
||||
print(
|
||||
f"Sample changed ICAO codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-10} more)"
|
||||
)
|
||||
|
||||
# Show some specific examples of changes
|
||||
if changed_icao_codes:
|
||||
print(f"\nExample changes:")
|
||||
for icao in sorted(list(changed_icao_codes))[:3]:
|
||||
old_rec = old_records[icao]
|
||||
new_rec = new_records[icao]
|
||||
if old_rec.airline != new_rec.airline:
|
||||
print(
|
||||
f" {icao}: Airline '{old_rec.airline}' → '{new_rec.airline}'"
|
||||
)
|
||||
if old_rec.country != new_rec.country:
|
||||
print(
|
||||
f" {icao}: Country '{old_rec.country}' → '{new_rec.country}'"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"\nNo previous airline codes file found - this appears to be the first run"
|
||||
)
|
||||
|
||||
# Create database from new records
|
||||
print("\nCreating airline database...")
|
||||
row_count = write_database(new_records)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
|
||||
|
||||
def cleanup_temp() -> None:
|
||||
"""Cleanup temporary files created during the process"""
|
||||
if os.path.exists("airlinecodes_previous.txt"):
|
||||
os.remove("airlinecodes_previous.txt")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
create_database()
|
||||
cleanup_temp()
|
||||
except Exception as e:
|
||||
print(f"Error creating airline database: {e}")
|
||||
else:
|
||||
print("Airline database created successfully.")
|
||||
|
||||
@@ -3,6 +3,5 @@
|
||||
Licensed under [GNU GPL v3](../../../LICENSE)
|
||||
|
||||
USAGE:
|
||||
- Copy file from: https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
- Run Python 3 script: `./make_icao24_db.py`
|
||||
- Copy file to /ADSB folder on SDCARD
|
||||
- Move "icao24.db" file to /ADSB folder on SDCARD
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 ArjanOnwezen
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -20,43 +21,275 @@
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create icao24.db, used for ADS-B receiver application, using
|
||||
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
# https://opensky-network.org/datasets/metadata/aircraftDatabase.csv
|
||||
# as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
import csv
|
||||
import unicodedata
|
||||
icao24_codes=bytearray()
|
||||
data=bytearray()
|
||||
row_count=0
|
||||
import os
|
||||
import shutil
|
||||
import urllib.request
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Set, Tuple
|
||||
|
||||
database=open("icao24.db", "wb")
|
||||
|
||||
with open('aircraftDatabase.csv', 'rt') as csv_file:
|
||||
sorted_lines=sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(sorted_lines, quotechar='"', delimiter=',', quoting=csv.QUOTE_ALL, skipinitialspace=True):
|
||||
# only store in case enough info is available
|
||||
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
|
||||
icao24_code=row[0][:6].upper()
|
||||
registration=row[1][:8].encode('ascii', 'ignore')
|
||||
manufacturer=row[3][:32].encode('ascii', 'ignore')
|
||||
model=row[4][:32].encode('ascii', 'ignore')
|
||||
# in case icao aircraft type isn't, use ac type like BALL for balloon
|
||||
if len(row[8]) == 3:
|
||||
actype=row[8][:3].encode('ascii', 'ignore')
|
||||
else:
|
||||
actype=row[5][:4].encode('ascii', 'ignore')
|
||||
owner=row[13][:32].encode('ascii', 'ignore')
|
||||
operator=row[9][:32].encode('ascii', 'ignore')
|
||||
#padding
|
||||
icao24_codes.extend(bytearray(icao24_code+'\0', encoding='ascii'))
|
||||
registration_padding=bytearray('\0' * (9 - len(registration)), encoding='ascii')
|
||||
manufacturer_padding=bytearray('\0' * (33 - len(manufacturer)), encoding='ascii')
|
||||
model_padding=bytearray('\0' * (33 - len(model)), encoding='ascii')
|
||||
actype_padding=bytearray('\0' * (5 - len(actype)), encoding='ascii')
|
||||
owner_padding=bytearray('\0' * (33 - len(owner)), encoding='ascii')
|
||||
operator_padding=bytearray('\0' * (33 - len(operator)), encoding='ascii')
|
||||
data.extend(bytearray(registration+registration_padding+manufacturer+manufacturer_padding+model+model_padding+actype+actype_padding+owner+owner_padding+operator+operator_padding))
|
||||
row_count+=1
|
||||
database.write(icao24_codes+data)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
@dataclass
|
||||
class AircraftRecord:
|
||||
"""Represents an aircraft record with all relevant fields"""
|
||||
|
||||
icao24: str
|
||||
registration: str
|
||||
manufacturer: str
|
||||
model: str
|
||||
actype: str
|
||||
owner: str
|
||||
operator: str
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.icao24)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AircraftRecord):
|
||||
return False
|
||||
return self.icao24 == other.icao24
|
||||
|
||||
def data_equals(self, other):
|
||||
"""Check if all data fields are equal (excluding ICAO24 which is the key)"""
|
||||
if not isinstance(other, AircraftRecord):
|
||||
return False
|
||||
return (
|
||||
self.registration == other.registration
|
||||
and self.manufacturer == other.manufacturer
|
||||
and self.model == other.model
|
||||
and self.actype == other.actype
|
||||
and self.owner == other.owner
|
||||
and self.operator == other.operator
|
||||
)
|
||||
|
||||
|
||||
def backup_previous_csv() -> bool:
|
||||
"""Backup the existing CSV file if it exists. Returns True if backup was made."""
|
||||
if os.path.exists("aircraftDatabase.csv"):
|
||||
shutil.copy2("aircraftDatabase.csv", "aircraftDatabase_previous.csv")
|
||||
print("Backed up previous CSV file")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def download_aircraft_database() -> None:
|
||||
"""Download the aircraft database file from OpenSky Network"""
|
||||
url = "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv"
|
||||
print(f"Downloading aircraft database from {url}...")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, "aircraftDatabase.csv")
|
||||
print("Download completed successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error downloading aircraft database file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_csv_file(filename: str) -> Dict[str, AircraftRecord]:
|
||||
"""Parse CSV file and return a dictionary of records"""
|
||||
records = {}
|
||||
|
||||
if not os.path.exists(filename):
|
||||
return records
|
||||
|
||||
try:
|
||||
with open(filename, "rt", encoding="utf-8") as csv_file:
|
||||
sorted_lines = sorted(csv_file.readlines()[1:])
|
||||
for row in csv.reader(
|
||||
sorted_lines,
|
||||
quotechar='"',
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
skipinitialspace=True,
|
||||
):
|
||||
# only store in case enough info is available
|
||||
if len(row) == 27 and len(row[0]) == 6 and len(row[1]) > 0:
|
||||
icao24_code = row[0][:6].upper()
|
||||
registration = row[1][:8]
|
||||
manufacturer = row[3][:32]
|
||||
model = row[4][:32]
|
||||
# in case icao aircraft type isn't, use ac type like BALL for balloon
|
||||
if len(row[8]) == 3:
|
||||
actype = row[8][:3]
|
||||
else:
|
||||
actype = row[5][:4]
|
||||
owner = row[13][:32]
|
||||
operator = row[9][:32]
|
||||
|
||||
records[icao24_code] = AircraftRecord(
|
||||
icao24=icao24_code,
|
||||
registration=registration,
|
||||
manufacturer=manufacturer,
|
||||
model=model,
|
||||
actype=actype,
|
||||
owner=owner,
|
||||
operator=operator,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not parse CSV file {filename}: {e}")
|
||||
return {}
|
||||
|
||||
return records
|
||||
|
||||
|
||||
def compare_records(
|
||||
old_records: Dict[str, AircraftRecord], new_records: Dict[str, AircraftRecord]
|
||||
) -> Tuple[Set[str], Set[str], Set[str]]:
|
||||
"""Compare old and new records, return sets of new, deleted, and changed ICAO codes"""
|
||||
old_keys = set(old_records.keys())
|
||||
new_keys = set(new_records.keys())
|
||||
|
||||
new_icao_codes = new_keys - old_keys
|
||||
deleted_icao_codes = old_keys - new_keys
|
||||
|
||||
# Check for changes in existing records
|
||||
changed_icao_codes = set()
|
||||
for icao in old_keys & new_keys:
|
||||
if not old_records[icao].data_equals(new_records[icao]):
|
||||
changed_icao_codes.add(icao)
|
||||
|
||||
return new_icao_codes, deleted_icao_codes, changed_icao_codes
|
||||
|
||||
|
||||
def write_database(records: Dict[str, AircraftRecord]) -> int:
|
||||
"""Write records to database file using original format"""
|
||||
# bytearrays to store icao24 codes and data
|
||||
icao24_codes = bytearray()
|
||||
data = bytearray()
|
||||
row_count = 0
|
||||
|
||||
# Sort by ICAO code for consistency
|
||||
sorted_records = sorted(records.values(), key=lambda r: r.icao24)
|
||||
|
||||
for record in sorted_records:
|
||||
# Encode data fields
|
||||
registration = record.registration.encode("ascii", "ignore")
|
||||
manufacturer = record.manufacturer.encode("ascii", "ignore")
|
||||
model = record.model.encode("ascii", "ignore")
|
||||
actype = record.actype.encode("ascii", "ignore")
|
||||
owner = record.owner.encode("ascii", "ignore")
|
||||
operator = record.operator.encode("ascii", "ignore")
|
||||
|
||||
# Add ICAO code with null terminator (original format)
|
||||
icao24_codes.extend(bytearray(record.icao24 + "\0", encoding="ascii"))
|
||||
|
||||
# Add padded data fields (original format)
|
||||
registration_padding = bytearray(
|
||||
"\0" * (9 - len(registration)), encoding="ascii"
|
||||
)
|
||||
manufacturer_padding = bytearray(
|
||||
"\0" * (33 - len(manufacturer)), encoding="ascii"
|
||||
)
|
||||
model_padding = bytearray("\0" * (33 - len(model)), encoding="ascii")
|
||||
actype_padding = bytearray("\0" * (5 - len(actype)), encoding="ascii")
|
||||
owner_padding = bytearray("\0" * (33 - len(owner)), encoding="ascii")
|
||||
operator_padding = bytearray("\0" * (33 - len(operator)), encoding="ascii")
|
||||
|
||||
data.extend(
|
||||
bytearray(
|
||||
registration
|
||||
+ registration_padding
|
||||
+ manufacturer
|
||||
+ manufacturer_padding
|
||||
+ model
|
||||
+ model_padding
|
||||
+ actype
|
||||
+ actype_padding
|
||||
+ owner
|
||||
+ owner_padding
|
||||
+ operator
|
||||
+ operator_padding
|
||||
)
|
||||
)
|
||||
row_count += 1
|
||||
|
||||
with open("icao24.db", "wb") as database:
|
||||
database.write(icao24_codes + data)
|
||||
|
||||
return row_count
|
||||
|
||||
|
||||
def create_database() -> None:
|
||||
"""Create the ICAO24 database from the downloaded file"""
|
||||
# Backup existing CSV before downloading new one
|
||||
has_previous = backup_previous_csv()
|
||||
|
||||
# Download new CSV
|
||||
download_aircraft_database()
|
||||
|
||||
# Parse both CSV files for comparison
|
||||
old_records = {}
|
||||
if has_previous:
|
||||
print("Parsing previous CSV file...")
|
||||
old_records = parse_csv_file("aircraftDatabase_previous.csv")
|
||||
print(f"Found {len(old_records)} records in previous CSV")
|
||||
|
||||
print("Parsing new CSV file...")
|
||||
new_records = parse_csv_file("aircraftDatabase.csv")
|
||||
print(f"Found {len(new_records)} records in new CSV")
|
||||
|
||||
# Compare records if we have a previous version
|
||||
if old_records:
|
||||
new_icao_codes, deleted_icao_codes, changed_icao_codes = compare_records(
|
||||
old_records, new_records
|
||||
)
|
||||
|
||||
# Print change statistics
|
||||
print("\n" + "=" * 50)
|
||||
print("CSV CHANGE SUMMARY")
|
||||
print("=" * 50)
|
||||
print(f"New records: {len(new_icao_codes):>8}")
|
||||
print(f"Deleted records: {len(deleted_icao_codes):>8}")
|
||||
print(f"Changed records: {len(changed_icao_codes):>8}")
|
||||
print(f"Total records: {len(new_records):>8}")
|
||||
print("=" * 50)
|
||||
|
||||
# Show examples of changes (limited to avoid spam)
|
||||
if new_icao_codes and len(new_icao_codes) <= 10:
|
||||
print(f"\nNew ICAO24 codes: {', '.join(sorted(new_icao_codes))}")
|
||||
elif new_icao_codes:
|
||||
sample_new = sorted(list(new_icao_codes))[:5]
|
||||
print(
|
||||
f"\nSample new ICAO24 codes: {', '.join(sample_new)} (and {len(new_icao_codes)-5} more)"
|
||||
)
|
||||
|
||||
if deleted_icao_codes and len(deleted_icao_codes) <= 10:
|
||||
print(f"Deleted ICAO24 codes: {', '.join(sorted(deleted_icao_codes))}")
|
||||
elif deleted_icao_codes:
|
||||
sample_deleted = sorted(list(deleted_icao_codes))[:5]
|
||||
print(
|
||||
f"Sample deleted ICAO24 codes: {', '.join(sample_deleted)} (and {len(deleted_icao_codes)-5} more)"
|
||||
)
|
||||
|
||||
if changed_icao_codes and len(changed_icao_codes) <= 10:
|
||||
print(f"Changed ICAO24 codes: {', '.join(sorted(changed_icao_codes))}")
|
||||
elif changed_icao_codes:
|
||||
sample_changed = sorted(list(changed_icao_codes))[:5]
|
||||
print(
|
||||
f"Sample changed ICAO24 codes: {', '.join(sample_changed)} (and {len(changed_icao_codes)-5} more)"
|
||||
)
|
||||
else:
|
||||
print("\nNo previous CSV found - this appears to be the first run")
|
||||
|
||||
# Create database from new records
|
||||
print("\nCreating ICAO24 database...")
|
||||
row_count = write_database(new_records)
|
||||
print("Total of", row_count, "ICAO codes stored in database")
|
||||
|
||||
|
||||
def cleanup_previous_csv() -> None:
|
||||
"""Remove the previous CSV file if it exists"""
|
||||
if os.path.exists("aircraftDatabase_previous.csv"):
|
||||
os.remove("aircraftDatabase_previous.csv")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
create_database()
|
||||
cleanup_previous_csv()
|
||||
except Exception as e:
|
||||
print(f"Error creating ICAO24 database: {e}")
|
||||
else:
|
||||
print("ICAO24 database created successfully.")
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2025 Tommaso Ventafridda
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Create macaddress.db, used for BLE receiver application, using
|
||||
# https://standards-oui.ieee.org/oui/oui.txt as a source.
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
import urllib.request
|
||||
import unicodedata
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def download_oui_file() -> str:
|
||||
"""Download the OUI file from IEEE"""
|
||||
url = "https://standards-oui.ieee.org/oui/oui.txt"
|
||||
print(f"Downloading OUI database from {url}...")
|
||||
try:
|
||||
with urllib.request.urlopen(url) as response:
|
||||
content = response.read().decode("utf-8")
|
||||
print("Download completed successfully.")
|
||||
return content
|
||||
except Exception as e:
|
||||
print(f"Error downloading OUI file: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def parse_oui_data(content: str) -> List[Tuple[str, str]]:
|
||||
"""Parse the OUI file content and extract MAC prefixes and vendor names"""
|
||||
entries = []
|
||||
lines = content.split("\n")
|
||||
|
||||
for _, line in enumerate(lines):
|
||||
# Look for lines with (hex) pattern
|
||||
if "(hex)" in line:
|
||||
# Extract MAC prefix and vendor name
|
||||
match = re.match(
|
||||
r"^([0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2})\s+\(hex\)\s+(.+)$",
|
||||
line.strip(),
|
||||
)
|
||||
if match:
|
||||
# Remove dashes: "28-6F-B9" -> "286FB9"
|
||||
mac_prefix = match.group(1).replace(
|
||||
"-", ""
|
||||
)
|
||||
vendor_name = match.group(2).strip()
|
||||
|
||||
# Normalize vendor name and limit to 63 characters
|
||||
vendor_name = (
|
||||
unicodedata.normalize("NFKD", vendor_name[:63])
|
||||
.encode("ascii", "ignore")
|
||||
.decode("ascii")
|
||||
)
|
||||
|
||||
if mac_prefix and vendor_name:
|
||||
entries.append((mac_prefix, vendor_name))
|
||||
|
||||
print(f"Parsed {len(entries)} MAC address entries.")
|
||||
return entries
|
||||
|
||||
|
||||
def create_database(
|
||||
entries: List[Tuple[str, str]], output_filename: str = "macaddress.db"
|
||||
):
|
||||
"""Create the binary database file."""
|
||||
# Sort entries by MAC prefix for binary search
|
||||
entries.sort(key=lambda x: x[0])
|
||||
|
||||
mac_prefixes = bytearray()
|
||||
vendor_data = bytearray()
|
||||
row_count = 0
|
||||
|
||||
for mac_prefix, vendor_name in entries:
|
||||
# MAC prefix: 6 hex chars + null terminator = 7 bytes
|
||||
mac_prefixes += bytearray(mac_prefix + "\0", encoding="ascii")
|
||||
|
||||
# Vendor name: pad to 64 bytes
|
||||
vendor_bytes = vendor_name.encode("ascii", "ignore")
|
||||
vendor_padding = bytearray("\0" * (64 - len(vendor_bytes)), encoding="ascii")
|
||||
vendor_data += vendor_bytes + vendor_padding
|
||||
|
||||
row_count += 1
|
||||
|
||||
# Write database: index section followed by data section
|
||||
with open(output_filename, "wb") as database:
|
||||
database.write(mac_prefixes + vendor_data)
|
||||
|
||||
print(f"Created database '{output_filename}' with {row_count} MAC address entries.")
|
||||
print(f"Index section: {len(mac_prefixes)} bytes")
|
||||
print(f"Data section: {len(vendor_data)} bytes")
|
||||
print(f"Total database size: {len(mac_prefixes) + len(vendor_data)} bytes")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to create the MAC address database."""
|
||||
try:
|
||||
oui_content = download_oui_file()
|
||||
entries = parse_oui_data(oui_content)
|
||||
|
||||
if not entries:
|
||||
print("No valid entries found in OUI file!")
|
||||
return
|
||||
|
||||
create_database(entries)
|
||||
|
||||
print("MAC address database creation completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating MAC address database: {e}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
+1
-1
Submodule hackrf updated: 65bf67f9b5...651d0a7f23
@@ -0,0 +1,6 @@
|
||||
Upgraded standard case<br>https://www.printables.com/model/1337429-upgraded-hackrf-portapack-h4h4m-case
|
||||
|
||||
|
||||
Upgraded 18650 version case<br>https://www.printables.com/model/1337451-18650-power-hackrf-portapack-h4m-case
|
||||
|
||||
Official case, made for injection molding. Can be 3D printed, but has very tight tolerances.<br>https://www.printables.com/model/1019145-official-hackrf-portapack-h4h4m-case/comments
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
# Drone Analog FPV Channels
|
||||
# https://oscarliang.com/fpv-channels/
|
||||
# 5.8GHz Analog FPV Channels
|
||||
f=5865000000,d=FPV Ch A-1
|
||||
f=5845000000,d=FPV Ch A-2
|
||||
f=5825000000,d=FPV Ch A-3
|
||||
@@ -40,30 +39,6 @@ f=5806000000,d=FPV Ch R-5
|
||||
f=5843000000,d=FPV Ch R-6
|
||||
f=5880000000,d=FPV Ch R-7
|
||||
f=5917000000,d=FPV Ch R-8
|
||||
f=5362000000,d=FPV Ch D-1
|
||||
f=5399000000,d=FPV Ch D-2
|
||||
f=5436000000,d=FPV Ch D-3
|
||||
f=5473000000,d=FPV Ch D-4
|
||||
f=5510000000,d=FPV Ch D-5
|
||||
f=5547000000,d=FPV Ch D-6
|
||||
f=5584000000,d=FPV Ch D-7
|
||||
f=5621000000,d=FPV Ch D-8
|
||||
f=5325000000,d=FPV Ch U-1
|
||||
f=5348000000,d=FPV Ch U-2
|
||||
f=5366000000,d=FPV Ch U-3
|
||||
f=5384000000,d=FPV Ch U-4
|
||||
f=5402000000,d=FPV Ch U-5
|
||||
f=5420000000,d=FPV Ch U-6
|
||||
f=5438000000,d=FPV Ch U-7
|
||||
f=5456000000,d=FPV Ch U-8
|
||||
f=5474000000,d=FPV Ch O-1
|
||||
f=5492000000,d=FPV Ch O-2
|
||||
f=5510000000,d=FPV Ch O-3
|
||||
f=5528000000,d=FPV Ch O-4
|
||||
f=5546000000,d=FPV Ch O-5
|
||||
f=5564000000,d=FPV Ch O-6
|
||||
f=5582000000,d=FPV Ch O-7
|
||||
f=5600000000,d=FPV Ch O-8
|
||||
f=5333000000,d=FPV Ch L-1
|
||||
f=5373000000,d=FPV Ch L-2
|
||||
f=5413000000,d=FPV Ch L-3
|
||||
@@ -72,11 +47,14 @@ f=5493000000,d=FPV Ch L-5
|
||||
f=5533000000,d=FPV Ch L-6
|
||||
f=5573000000,d=FPV Ch L-7
|
||||
f=5613000000,d=FPV Ch L-8
|
||||
f=5653000000,d=FPV Ch H-1
|
||||
f=5693000000,d=FPV Ch H-2
|
||||
f=5733000000,d=FPV Ch H-3
|
||||
f=5773000000,d=FPV Ch H-4
|
||||
f=5813000000,d=FPV Ch H-5
|
||||
f=5853000000,d=FPV Ch H-6
|
||||
f=5893000000,d=FPV Ch H-7
|
||||
f=5933000000,d=FPV Ch H-8
|
||||
|
||||
# 1.2-1.3GHz Analog Channels
|
||||
f=1080000000,d=FPV Ch 1
|
||||
f=1120000000,d=FPV Ch 2
|
||||
f=1160000000,d=FPV Ch 3
|
||||
f=1200000000,d=FPV Ch 4
|
||||
f=1240000000,d=FPV Ch 5
|
||||
f=1258000000,d=FPV Ch 6
|
||||
f=1280000000,d=FPV Ch 7
|
||||
f=1320000000,d=FPV Ch 8
|
||||
f=1360000000,d=FPV Ch 9
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user