Initial (and hopefully last) commit
This commit is contained in:
commit
dea8e8be68
41 changed files with 126867 additions and 0 deletions
BIN
3d/case.stl
Normal file
BIN
3d/case.stl
Normal file
Binary file not shown.
1
3d/readme
Normal file
1
3d/readme
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
https://cad.onshape.com/documents/bb9db5aa964d643ccd7e46c3/w/9284d4a7438acf7663ad1fe7/e/a1c5cd401b084cdfb7fd39db?renderMode=0&uiState=6a0da37ffa326fa20df8f248
|
||||||
5
code/.gitignore
vendored
Normal file
5
code/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
code/.vscode/extensions.json
vendored
Normal file
10
code/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
37
code/include/README
Normal file
37
code/include/README
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
code/lib/README
Normal file
46
code/lib/README
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
16
code/platformio.ini
Normal file
16
code/platformio.ini
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp32-c3]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32-c3-devkitm-1
|
||||||
|
build_flags = -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
framework = arduino
|
||||||
|
lib_deps = olikraus/U8g2@^2.36.18
|
||||||
900
code/src/main.cpp
Normal file
900
code/src/main.cpp
Normal file
|
|
@ -0,0 +1,900 @@
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 9, /* data=*/ 8);
|
||||||
|
Preferences preferences;
|
||||||
|
|
||||||
|
#define BTNU 0
|
||||||
|
#define BTNR 1
|
||||||
|
#define BTND 3
|
||||||
|
#define BTNL 10
|
||||||
|
#define BUZZER 7
|
||||||
|
const uint8_t ledcChannel = 0;
|
||||||
|
const uint16_t SCREEN_W = 128;
|
||||||
|
const uint16_t SCREEN_H = 64;
|
||||||
|
const uint8_t HUD_H = 8;
|
||||||
|
const uint8_t CELL = 4;
|
||||||
|
const uint8_t GRID_W = SCREEN_W / CELL;
|
||||||
|
const uint8_t GRID_H = (SCREEN_H - HUD_H) / CELL;
|
||||||
|
const uint16_t MAX_SNAKE = GRID_W * GRID_H;
|
||||||
|
|
||||||
|
const uint16_t BASE_STEP_MS = 170;
|
||||||
|
const uint16_t MIN_STEP_MS = 85;
|
||||||
|
const uint16_t SPEEDUP_PER_FOOD_MS = 5;
|
||||||
|
const uint16_t SPRINT_STEP_MS = 50;
|
||||||
|
const uint8_t SPRINT_HOLD_FRAMES = 5;
|
||||||
|
|
||||||
|
const uint16_t MIN_TONE_FREQ = 500;
|
||||||
|
const uint16_t MAX_TONE_FREQ = 2000;
|
||||||
|
const uint16_t MENU_DEBOUNCE_MS = 150;
|
||||||
|
const uint16_t NAME_ENTRY_DWELL_MS = 350;
|
||||||
|
const uint8_t LEADERBOARD_SIZE = 5;
|
||||||
|
const uint8_t NAME_LENGTH = 8;
|
||||||
|
const uint8_t LEADERBOARD_BOARDS = 3; // Easy, Medium, Hard
|
||||||
|
|
||||||
|
// Difficulty presets and custom bounds
|
||||||
|
const uint16_t DIFF_EASY_MS = 200;
|
||||||
|
const uint16_t DIFF_MEDIUM_MS = BASE_STEP_MS;
|
||||||
|
const uint16_t DIFF_HARD_MS = 130;
|
||||||
|
const uint16_t CUSTOM_MIN_MS = 85;
|
||||||
|
const uint16_t CUSTOM_MAX_MS = 300;
|
||||||
|
// Difficulty offsets and preset speedup-per-food values
|
||||||
|
const int16_t DIFF_OFFSET_EASY = 30;
|
||||||
|
const int16_t DIFF_OFFSET_MED = 0;
|
||||||
|
const int16_t DIFF_OFFSET_HARD = -40;
|
||||||
|
const uint16_t DIFF_SPEEDUP_EASY = 3;
|
||||||
|
const uint16_t DIFF_SPEEDUP_MED = SPEEDUP_PER_FOOD_MS;
|
||||||
|
const uint16_t DIFF_SPEEDUP_HARD = 7;
|
||||||
|
|
||||||
|
struct Cell {
|
||||||
|
int8_t x;
|
||||||
|
int8_t y;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Direction : uint8_t { UP, RIGHT, DOWN, LEFT };
|
||||||
|
enum GameState : uint8_t { START, PLAYING, GAME_OVER, SETTINGS, LEADERBOARD, NAME_ENTRY };
|
||||||
|
|
||||||
|
struct LeaderboardEntry {
|
||||||
|
char name[NAME_LENGTH + 1];
|
||||||
|
uint16_t score;
|
||||||
|
};
|
||||||
|
|
||||||
|
GameState gameState = START;
|
||||||
|
// toneFreq == 0 means buzzer OFF; otherwise valid range is 500..2000 in 100 Hz steps
|
||||||
|
uint16_t toneFreq = 1200;
|
||||||
|
bool isSprinting = false;
|
||||||
|
uint8_t sprintHoldU = 0, sprintHoldR = 0, sprintHoldD = 0, sprintHoldL = 0;
|
||||||
|
// settingsMenuIndex removed; using settingsScrollOffset and dynamic list
|
||||||
|
uint32_t lastSettingsInputMs = 0;
|
||||||
|
// Settings UI state: scroll offset, total items. Selector stays in middle of visible window.
|
||||||
|
uint8_t settingsScrollOffset = 0;
|
||||||
|
const uint8_t SETTINGS_VISIBLE = 3; // visible rows
|
||||||
|
|
||||||
|
// Difficulty selection
|
||||||
|
uint8_t difficultyPreset = 1; // 0=Easy,1=Medium,2=Hard,3=Custom
|
||||||
|
uint16_t customStepMs = BASE_STEP_MS;
|
||||||
|
// Sprint enable/disable (settings)
|
||||||
|
bool sprintAllowed = true;
|
||||||
|
// Custom advanced params
|
||||||
|
uint16_t customSpeedupPerFood = SPEEDUP_PER_FOOD_MS;
|
||||||
|
uint16_t customMinStepMs = MIN_STEP_MS;
|
||||||
|
uint16_t customMaxStepMs = DIFF_MEDIUM_MS * 2;
|
||||||
|
|
||||||
|
// Calculate dynamic settings total depending on whether custom step row is present
|
||||||
|
uint8_t settingsTotal() {
|
||||||
|
// If custom difficulty selected, include extra rows: custom start, speedup, min, max
|
||||||
|
if (difficultyPreset == 3) return 10; // blank, buzzer, sprint, difficulty, customStart, customSpeedup, customMin, customMax, back, blank
|
||||||
|
return 6; // blank, buzzer, sprint, difficulty, back, blank
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t leaderboardBoardIndexForDifficulty(uint8_t difficulty) {
|
||||||
|
return (difficulty < LEADERBOARD_BOARDS) ? difficulty : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t activeLeaderboardBoardIndex() {
|
||||||
|
return leaderboardBoardIndexForDifficulty(difficultyPreset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NAME_ENTRY: require L+R release before next select
|
||||||
|
bool lrComboReleased = true;
|
||||||
|
LeaderboardEntry leaderboard[LEADERBOARD_BOARDS][LEADERBOARD_SIZE];
|
||||||
|
char pendingName[NAME_LENGTH + 1] = {0};
|
||||||
|
uint16_t pendingScore = 0;
|
||||||
|
uint8_t pendingLeaderboardBoard = 0;
|
||||||
|
uint8_t keyboardRow = 0;
|
||||||
|
uint8_t keyboardCol = 0;
|
||||||
|
Cell snake[MAX_SNAKE];
|
||||||
|
uint16_t snakeLen = 0;
|
||||||
|
Cell food = {0, 0};
|
||||||
|
Direction currentDir = RIGHT;
|
||||||
|
Direction nextDir = RIGHT;
|
||||||
|
uint16_t score = 0;
|
||||||
|
uint16_t highScore = 0;
|
||||||
|
uint32_t lastStepMs = 0;
|
||||||
|
uint32_t stateChangeMs = 0;
|
||||||
|
uint32_t inputIgnoreUntilMs = 0; // ignore button input until this millis() (used after name-entry OK)
|
||||||
|
|
||||||
|
void resetGame();
|
||||||
|
|
||||||
|
bool cellEquals(const Cell &first, const Cell &second) {
|
||||||
|
return first.x == second.x && first.y == second.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSnakeCell(const Cell &target) {
|
||||||
|
for (uint16_t index = 0; index < snakeLen; ++index) {
|
||||||
|
if (cellEquals(snake[index], target)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initLeaderboardDefaults() {
|
||||||
|
for (uint8_t board = 0; board < LEADERBOARD_BOARDS; ++board) {
|
||||||
|
for (uint8_t index = 0; index < LEADERBOARD_SIZE; ++index) {
|
||||||
|
strncpy(leaderboard[board][index].name, "--------", NAME_LENGTH);
|
||||||
|
leaderboard[board][index].name[NAME_LENGTH] = '\0';
|
||||||
|
leaderboard[board][index].score = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadLeaderboardBoard(uint8_t board) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "leader%u", board);
|
||||||
|
const size_t loadedBytes = preferences.getBytes(key, leaderboard[board], sizeof(leaderboard[board]));
|
||||||
|
if (loadedBytes != sizeof(leaderboard[board])) {
|
||||||
|
for (uint8_t index = 0; index < LEADERBOARD_SIZE; ++index) {
|
||||||
|
strncpy(leaderboard[board][index].name, "--------", NAME_LENGTH);
|
||||||
|
leaderboard[board][index].name[NAME_LENGTH] = '\0';
|
||||||
|
leaderboard[board][index].score = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveLeaderboardBoard(uint8_t board) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "leader%u", board);
|
||||||
|
preferences.putBytes(key, leaderboard[board], sizeof(leaderboard[board]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadHighScore() {
|
||||||
|
preferences.begin("snake", true);
|
||||||
|
const bool buzzerEnabledLegacy = preferences.getBool("buzzerEnabled", true);
|
||||||
|
toneFreq = preferences.getUShort("toneFreq", 1200);
|
||||||
|
difficultyPreset = preferences.getUShort("diffPreset", 1);
|
||||||
|
customStepMs = preferences.getUShort("customStepMs", BASE_STEP_MS);
|
||||||
|
customSpeedupPerFood = preferences.getUShort("customSpeedup", SPEEDUP_PER_FOOD_MS);
|
||||||
|
customMinStepMs = preferences.getUShort("customMin", MIN_STEP_MS);
|
||||||
|
customMaxStepMs = preferences.getUShort("customMax", DIFF_MEDIUM_MS * 2);
|
||||||
|
sprintAllowed = preferences.getBool("sprintAllowed", true);
|
||||||
|
initLeaderboardDefaults();
|
||||||
|
for (uint8_t board = 0; board < LEADERBOARD_BOARDS; ++board) {
|
||||||
|
loadLeaderboardBoard(board);
|
||||||
|
}
|
||||||
|
if (toneFreq != 0 && (toneFreq < MIN_TONE_FREQ || toneFreq > MAX_TONE_FREQ || ((toneFreq - MIN_TONE_FREQ) % 100) != 0)) {
|
||||||
|
toneFreq = 1200;
|
||||||
|
}
|
||||||
|
if (!buzzerEnabledLegacy) {
|
||||||
|
toneFreq = 0;
|
||||||
|
}
|
||||||
|
highScore = (difficultyPreset < 3) ? leaderboard[activeLeaderboardBoardIndex()][0].score : 0;
|
||||||
|
preferences.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveHighScore() {
|
||||||
|
preferences.begin("snake", false);
|
||||||
|
preferences.putUShort("toneFreq", toneFreq);
|
||||||
|
preferences.putUShort("diffPreset", difficultyPreset);
|
||||||
|
preferences.putUShort("customStepMs", customStepMs);
|
||||||
|
preferences.putUShort("customSpeedup", customSpeedupPerFood);
|
||||||
|
preferences.putUShort("customMin", customMinStepMs);
|
||||||
|
preferences.putUShort("customMax", customMaxStepMs);
|
||||||
|
preferences.putBool("sprintAllowed", sprintAllowed);
|
||||||
|
for (uint8_t board = 0; board < LEADERBOARD_BOARDS; ++board) {
|
||||||
|
saveLeaderboardBoard(board);
|
||||||
|
}
|
||||||
|
if (difficultyPreset < 3) {
|
||||||
|
preferences.putUShort("highScore", leaderboard[activeLeaderboardBoardIndex()][0].score);
|
||||||
|
} else {
|
||||||
|
preferences.putUShort("highScore", 0);
|
||||||
|
}
|
||||||
|
preferences.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool qualifiesForLeaderboard(uint16_t scoreValue) {
|
||||||
|
if (difficultyPreset >= 3) return false;
|
||||||
|
const uint8_t board = activeLeaderboardBoardIndex();
|
||||||
|
return scoreValue > leaderboard[board][LEADERBOARD_SIZE - 1].score ||
|
||||||
|
leaderboard[board][LEADERBOARD_SIZE - 1].name[0] == '\0' ||
|
||||||
|
leaderboard[board][LEADERBOARD_SIZE - 1].score == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncHighScoreFromLeaderboard(uint8_t board) {
|
||||||
|
highScore = leaderboard[board][0].score;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertLeaderboardEntry(uint8_t board, const char *name, uint16_t scoreValue) {
|
||||||
|
LeaderboardEntry entry = {};
|
||||||
|
strncpy(entry.name, name, NAME_LENGTH);
|
||||||
|
entry.name[NAME_LENGTH] = '\0';
|
||||||
|
entry.score = scoreValue;
|
||||||
|
|
||||||
|
uint8_t insertAt = LEADERBOARD_SIZE;
|
||||||
|
for (uint8_t index = 0; index < LEADERBOARD_SIZE; ++index) {
|
||||||
|
if (scoreValue > leaderboard[board][index].score) {
|
||||||
|
insertAt = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insertAt == LEADERBOARD_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = LEADERBOARD_SIZE - 1; index > static_cast<int>(insertAt); --index) {
|
||||||
|
leaderboard[board][index] = leaderboard[board][index - 1];
|
||||||
|
}
|
||||||
|
leaderboard[board][insertAt] = entry;
|
||||||
|
syncHighScoreFromLeaderboard(board);
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepareNameEntry(uint16_t scoreValue) {
|
||||||
|
pendingScore = scoreValue;
|
||||||
|
memset(pendingName, 0, sizeof(pendingName));
|
||||||
|
keyboardRow = 0;
|
||||||
|
keyboardCol = 0;
|
||||||
|
pendingLeaderboardBoard = activeLeaderboardBoardIndex();
|
||||||
|
gameState = NAME_ENTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void confirmNameEntry() {
|
||||||
|
if (pendingName[0] == '\0') {
|
||||||
|
strncpy(pendingName, "PLAYER", NAME_LENGTH);
|
||||||
|
pendingName[NAME_LENGTH] = '\0';
|
||||||
|
}
|
||||||
|
insertLeaderboardEntry(pendingLeaderboardBoard, pendingName, pendingScore);
|
||||||
|
saveHighScore();
|
||||||
|
resetGame();
|
||||||
|
gameState = LEADERBOARD;
|
||||||
|
// Prevent accidental immediate input if OK was still held: ignore inputs for 500ms
|
||||||
|
inputIgnoreUntilMs = millis() + 500;
|
||||||
|
lastSettingsInputMs = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *keyboardKeyLabel(uint8_t row, uint8_t col) {
|
||||||
|
static const char *layout[4][7] = {
|
||||||
|
{"A", "B", "C", "D", "E", "F", "G"},
|
||||||
|
{"H", "I", "J", "K", "L", "M", "N"},
|
||||||
|
{"O", "P", "Q", "R", "S", "T", "U"},
|
||||||
|
{"V", "W", "X", "Y", "Z", "DEL", "OK"}
|
||||||
|
};
|
||||||
|
return layout[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveKeyboardCursor(int8_t rowDelta, int8_t colDelta) {
|
||||||
|
keyboardRow = (keyboardRow + 4 + rowDelta) % 4;
|
||||||
|
keyboardCol = (keyboardCol + 7 + colDelta) % 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyKeyboardSelection() {
|
||||||
|
const char *label = keyboardKeyLabel(keyboardRow, keyboardCol);
|
||||||
|
if (strcmp(label, "DEL") == 0) {
|
||||||
|
if (strlen(pendingName) > 0) {
|
||||||
|
pendingName[strlen(pendingName) - 1] = '\0';
|
||||||
|
}
|
||||||
|
} else if (strcmp(label, "OK") == 0) {
|
||||||
|
confirmNameEntry();
|
||||||
|
} else if (strlen(pendingName) < NAME_LENGTH) {
|
||||||
|
const size_t currentLength = strlen(pendingName);
|
||||||
|
pendingName[currentLength] = label[0];
|
||||||
|
pendingName[currentLength + 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toneMs(uint16_t hz, uint16_t durationMs) {
|
||||||
|
if (toneFreq == 0) return;
|
||||||
|
ledcWriteTone(ledcChannel, hz);
|
||||||
|
delay(durationMs);
|
||||||
|
ledcWriteTone(ledcChannel, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t buzzerCycleUp(uint16_t current) {
|
||||||
|
if (current == 0) return MIN_TONE_FREQ;
|
||||||
|
if (current < MAX_TONE_FREQ) return current + 100;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t buzzerCycleDown(uint16_t current) {
|
||||||
|
if (current == 0) return MAX_TONE_FREQ;
|
||||||
|
if (current > MIN_TONE_FREQ) return current - 100;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawnFood() {
|
||||||
|
Cell candidate;
|
||||||
|
do {
|
||||||
|
candidate.x = random(0, GRID_W);
|
||||||
|
candidate.y = random(0, GRID_H);
|
||||||
|
} while (isSnakeCell(candidate));
|
||||||
|
food = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetGame() {
|
||||||
|
score = 0;
|
||||||
|
currentDir = RIGHT;
|
||||||
|
nextDir = RIGHT;
|
||||||
|
gameState = START;
|
||||||
|
stateChangeMs = millis();
|
||||||
|
|
||||||
|
snakeLen = 3;
|
||||||
|
const int8_t centerX = GRID_W / 2;
|
||||||
|
const int8_t centerY = GRID_H / 2;
|
||||||
|
snake[0] = {centerX, centerY};
|
||||||
|
snake[1] = {static_cast<int8_t>(centerX - 1), centerY};
|
||||||
|
snake[2] = {static_cast<int8_t>(centerX - 2), centerY};
|
||||||
|
|
||||||
|
spawnFood();
|
||||||
|
lastStepMs = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyButtonPressed() {
|
||||||
|
return digitalRead(BTNU) == LOW ||
|
||||||
|
digitalRead(BTNR) == LOW ||
|
||||||
|
digitalRead(BTND) == LOW ||
|
||||||
|
digitalRead(BTNL) == LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDirectionFromButtons() {
|
||||||
|
if (digitalRead(BTNU) == LOW && currentDir != DOWN) {
|
||||||
|
nextDir = UP;
|
||||||
|
sprintHoldU++;
|
||||||
|
} else {
|
||||||
|
sprintHoldU = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitalRead(BTNR) == LOW && currentDir != LEFT) {
|
||||||
|
nextDir = RIGHT;
|
||||||
|
sprintHoldR++;
|
||||||
|
} else {
|
||||||
|
sprintHoldR = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitalRead(BTND) == LOW && currentDir != UP) {
|
||||||
|
nextDir = DOWN;
|
||||||
|
sprintHoldD++;
|
||||||
|
} else {
|
||||||
|
sprintHoldD = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitalRead(BTNL) == LOW && currentDir != RIGHT) {
|
||||||
|
nextDir = LEFT;
|
||||||
|
sprintHoldL++;
|
||||||
|
} else {
|
||||||
|
sprintHoldL = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSprinting = sprintAllowed && ((sprintHoldU >= SPRINT_HOLD_FRAMES) ||
|
||||||
|
(sprintHoldR >= SPRINT_HOLD_FRAMES) ||
|
||||||
|
(sprintHoldD >= SPRINT_HOLD_FRAMES) ||
|
||||||
|
(sprintHoldL >= SPRINT_HOLD_FRAMES));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t stepIntervalMs() {
|
||||||
|
if (isSprinting) return SPRINT_STEP_MS;
|
||||||
|
int32_t baseMs = BASE_STEP_MS;
|
||||||
|
uint16_t curSpeedup = SPEEDUP_PER_FOOD_MS;
|
||||||
|
uint16_t minStep = MIN_STEP_MS;
|
||||||
|
uint16_t maxStep = BASE_STEP_MS * 2;
|
||||||
|
switch (difficultyPreset) {
|
||||||
|
case 0:
|
||||||
|
baseMs = BASE_STEP_MS + DIFF_OFFSET_EASY;
|
||||||
|
curSpeedup = DIFF_SPEEDUP_EASY;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
baseMs = BASE_STEP_MS + DIFF_OFFSET_MED;
|
||||||
|
curSpeedup = DIFF_SPEEDUP_MED;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
baseMs = BASE_STEP_MS + DIFF_OFFSET_HARD;
|
||||||
|
curSpeedup = DIFF_SPEEDUP_HARD;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
baseMs = customStepMs;
|
||||||
|
curSpeedup = customSpeedupPerFood;
|
||||||
|
minStep = customMinStepMs;
|
||||||
|
maxStep = customMaxStepMs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseMs < (int32_t)minStep) baseMs = minStep;
|
||||||
|
if (baseMs > (int32_t)maxStep) baseMs = maxStep;
|
||||||
|
|
||||||
|
const uint32_t speedDrop = (uint32_t)score * (uint32_t)curSpeedup;
|
||||||
|
if ((uint32_t)baseMs <= (uint32_t)minStep + speedDrop) return minStep;
|
||||||
|
int32_t result = (int32_t)baseMs - (int32_t)speedDrop;
|
||||||
|
if (result < (int32_t)minStep) return minStep;
|
||||||
|
return (uint16_t)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell nextHeadCell() {
|
||||||
|
Cell head = snake[0];
|
||||||
|
switch (currentDir) {
|
||||||
|
case UP: --head.y; break;
|
||||||
|
case RIGHT: ++head.x; break;
|
||||||
|
case DOWN: ++head.y; break;
|
||||||
|
case LEFT: --head.x; break;
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool outOfBounds(const Cell &cell) {
|
||||||
|
return cell.x < 0 || cell.x >= GRID_W || cell.y < 0 || cell.y >= GRID_H;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hitsSnakeBody(const Cell &cell) {
|
||||||
|
for (uint16_t index = 0; index < snakeLen; ++index) {
|
||||||
|
if (cellEquals(snake[index], cell)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runGameStep() {
|
||||||
|
currentDir = nextDir;
|
||||||
|
const Cell newHead = nextHeadCell();
|
||||||
|
|
||||||
|
if (outOfBounds(newHead) || hitsSnakeBody(newHead)) {
|
||||||
|
stateChangeMs = millis();
|
||||||
|
if (qualifiesForLeaderboard(score)) {
|
||||||
|
prepareNameEntry(score);
|
||||||
|
} else {
|
||||||
|
gameState = GAME_OVER;
|
||||||
|
}
|
||||||
|
toneMs(330, 120);
|
||||||
|
toneMs(220, 180);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool ateFood = cellEquals(newHead, food);
|
||||||
|
const uint16_t oldLen = snakeLen;
|
||||||
|
uint16_t newLen = oldLen;
|
||||||
|
if (ateFood && snakeLen < MAX_SNAKE) newLen = oldLen + 1;
|
||||||
|
|
||||||
|
for (uint16_t index = newLen - 1; index > 0; --index) {
|
||||||
|
snake[index] = snake[index - 1];
|
||||||
|
}
|
||||||
|
snake[0] = newHead;
|
||||||
|
snakeLen = newLen;
|
||||||
|
|
||||||
|
if (ateFood) {
|
||||||
|
const uint16_t pointsEarned = isSprinting ? 2 : 1;
|
||||||
|
score += pointsEarned;
|
||||||
|
toneMs(toneFreq, 35);
|
||||||
|
spawnFood();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGame() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
|
||||||
|
char hudText[32];
|
||||||
|
if (isSprinting) {
|
||||||
|
snprintf(hudText, sizeof(hudText), "S:%u B:%u [SPRINT]", score, highScore);
|
||||||
|
} else {
|
||||||
|
snprintf(hudText, sizeof(hudText), "S:%u B:%u", score, highScore);
|
||||||
|
}
|
||||||
|
u8g2.drawStr(0, 7, hudText);
|
||||||
|
|
||||||
|
for (uint16_t index = 0; index < snakeLen; ++index) {
|
||||||
|
const int16_t x = snake[index].x * CELL;
|
||||||
|
const int16_t y = HUD_H + snake[index].y * CELL;
|
||||||
|
u8g2.drawBox(x, y, CELL, CELL);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int16_t foodX = food.x * CELL;
|
||||||
|
const int16_t foodY = HUD_H + food.y * CELL;
|
||||||
|
u8g2.drawFrame(foodX, foodY, CELL, CELL);
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawStartScreen() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||||
|
u8g2.drawStr(32, 20, "SNAKE");
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_4x6_tr);
|
||||||
|
u8g2.drawStr(26, 40, "START: UP");
|
||||||
|
u8g2.drawStr(20, 50, "SETTINGS: RIGHT");
|
||||||
|
if (difficultyPreset == 3) {
|
||||||
|
u8g2.drawStr(4, 60, "NOT AVAILABLE IN CUSTOM");
|
||||||
|
} else {
|
||||||
|
u8g2.drawStr(14, 60, "LEADERBOARD: LEFT");
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGameOverScreen() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||||
|
u8g2.drawStr(28, 20, "GAME OVER");
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
char scoreText[24];
|
||||||
|
snprintf(scoreText, sizeof(scoreText), "Score: %u", score);
|
||||||
|
u8g2.drawStr(28, 35, scoreText);
|
||||||
|
|
||||||
|
char bestText[24];
|
||||||
|
snprintf(bestText, sizeof(bestText), "Best: %u", highScore);
|
||||||
|
u8g2.drawStr(28, 48, bestText);
|
||||||
|
|
||||||
|
u8g2.drawStr(18, 62, "Press any button");
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawLeaderboardScreen() {
|
||||||
|
const uint8_t board = activeLeaderboardBoardIndex();
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||||
|
// char title[28];
|
||||||
|
const char *difficultyNames[3] = {"EASY", "MED ", "HARD"};
|
||||||
|
// snprintf(title, sizeof(title), "LEADERBOARD - %s", difficultyNames[board]);
|
||||||
|
u8g2.drawStr(6, 10, "LEADERBOARD");
|
||||||
|
|
||||||
|
char ch[2] = {0, 0}; // Reusable 1-char string buffer
|
||||||
|
|
||||||
|
if (board == 1) {
|
||||||
|
ch[0] = difficultyNames[board][0]; u8g2.drawStr(93, 26, ch);
|
||||||
|
ch[0] = difficultyNames[board][1]; u8g2.drawStr(98, 36, ch);
|
||||||
|
ch[0] = difficultyNames[board][2]; u8g2.drawStr(103, 46, ch);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ch[0] = difficultyNames[board][0]; u8g2.drawStr(90, 22, ch);
|
||||||
|
ch[0] = difficultyNames[board][1]; u8g2.drawStr(95, 32, ch);
|
||||||
|
ch[0] = difficultyNames[board][2]; u8g2.drawStr(100, 42, ch);
|
||||||
|
ch[0] = difficultyNames[board][3]; u8g2.drawStr(105, 52, ch);
|
||||||
|
};
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
for (uint8_t index = 0; index < LEADERBOARD_SIZE; ++index) {
|
||||||
|
char line[24];
|
||||||
|
snprintf(line, sizeof(line), "%u. %-8s %3u", index + 1, leaderboard[board][index].name, leaderboard[board][index].score);
|
||||||
|
u8g2.drawStr(4, 20 + index * 8, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_4x6_tr);
|
||||||
|
u8g2.drawStr(85, 64, "Back ----->");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawNameEntryScreen() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
u8g2.drawStr(2, 8, "NEW RECORD - TYPE NAME");
|
||||||
|
|
||||||
|
char scoreLine[24];
|
||||||
|
snprintf(scoreLine, sizeof(scoreLine), "Score: %u", pendingScore);
|
||||||
|
u8g2.drawStr(2, 16, scoreLine);
|
||||||
|
|
||||||
|
char nameLine[24];
|
||||||
|
snprintf(nameLine, sizeof(nameLine), "Name: %-8s", pendingName);
|
||||||
|
u8g2.drawStr(2, 24, nameLine);
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
const uint8_t gridTop = 30;
|
||||||
|
const uint8_t cellW = 18;
|
||||||
|
const uint8_t cellH = 8;
|
||||||
|
|
||||||
|
for (uint8_t row = 0; row < 4; ++row) {
|
||||||
|
for (uint8_t col = 0; col < 7; ++col) {
|
||||||
|
const uint8_t x = col * cellW;
|
||||||
|
const uint8_t y = gridTop + row * cellH;
|
||||||
|
const char *label = keyboardKeyLabel(row, col);
|
||||||
|
const bool selected = (row == keyboardRow && col == keyboardCol);
|
||||||
|
if (selected) {
|
||||||
|
u8g2.drawBox(x + 1, y - 7, cellW - 2, cellH - 1);
|
||||||
|
u8g2.setDrawColor(0);
|
||||||
|
}
|
||||||
|
u8g2.drawStr(x + 5, y, label);
|
||||||
|
if (selected) {
|
||||||
|
u8g2.setDrawColor(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_4x6_tr);
|
||||||
|
u8g2.drawStr(2, 62, "Move: arrows Select: L+R");
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawSettingsScreen() {
|
||||||
|
u8g2.clearBuffer();
|
||||||
|
u8g2.setFont(u8g2_font_ncenB08_tr);
|
||||||
|
u8g2.drawStr(26, 16, "SETTINGS");
|
||||||
|
|
||||||
|
u8g2.setFont(u8g2_font_5x8_tr);
|
||||||
|
// Render a small scroll window (3 rows) with selector fixed in the middle
|
||||||
|
const uint8_t total = settingsTotal();
|
||||||
|
for (uint8_t row = 0; row < SETTINGS_VISIBLE; ++row) {
|
||||||
|
const uint8_t itemIndex = settingsScrollOffset + row;
|
||||||
|
const uint8_t y = 28 + row * 10;
|
||||||
|
bool highlight = (row == (SETTINGS_VISIBLE / 2));
|
||||||
|
if (highlight) {
|
||||||
|
u8g2.drawBox(4, y - 8, 120, 10);
|
||||||
|
u8g2.setDrawColor(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[40] = {0};
|
||||||
|
if (itemIndex == 0 || itemIndex == (total - 1)) {
|
||||||
|
// padding
|
||||||
|
} else {
|
||||||
|
if (total == 6) {
|
||||||
|
// 0 blank,1 buzzer,2 sprint,3 difficulty,4 back,5 blank
|
||||||
|
if (itemIndex == 1) {
|
||||||
|
if (toneFreq == 0) snprintf(line, sizeof(line), "Buzzer: OFF");
|
||||||
|
else snprintf(line, sizeof(line), "Buzzer: %u Hz", toneFreq);
|
||||||
|
} else if (itemIndex == 2) {
|
||||||
|
snprintf(line, sizeof(line), "Sprinting: %s", sprintAllowed ? "ON" : "OFF");
|
||||||
|
} else if (itemIndex == 3) {
|
||||||
|
const char *names[4] = {"EASY", "MED", "HARD", "CUSTOM"};
|
||||||
|
snprintf(line, sizeof(line), "Difficulty: %s", names[difficultyPreset]);
|
||||||
|
} else if (itemIndex == 4) {
|
||||||
|
snprintf(line, sizeof(line), "Back (save)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// total == 10 => 0 blank,1 buzzer,2 sprint,3 difficulty,4 customStart,5 customSpeedup,6 customMin,7 customMax,8 back,9 blank
|
||||||
|
if (itemIndex == 1) {
|
||||||
|
if (toneFreq == 0) snprintf(line, sizeof(line), "Buzzer: OFF");
|
||||||
|
else snprintf(line, sizeof(line), "Buzzer: %u Hz", toneFreq);
|
||||||
|
} else if (itemIndex == 2) {
|
||||||
|
snprintf(line, sizeof(line), "Sprinting: %s", sprintAllowed ? "ON" : "OFF");
|
||||||
|
} else if (itemIndex == 3) {
|
||||||
|
const char *names[4] = {"EASY", "MED", "HARD", "CUSTOM"};
|
||||||
|
snprintf(line, sizeof(line), "Difficulty: %s", names[difficultyPreset]);
|
||||||
|
} else if (itemIndex == 4) {
|
||||||
|
snprintf(line, sizeof(line), " Starting speed: %ums", customStepMs);
|
||||||
|
} else if (itemIndex == 5) {
|
||||||
|
snprintf(line, sizeof(line), " Speedup: %u ms/food", customSpeedupPerFood);
|
||||||
|
} else if (itemIndex == 6) {
|
||||||
|
snprintf(line, sizeof(line), " Min: %ums", customMinStepMs);
|
||||||
|
} else if (itemIndex == 7) {
|
||||||
|
snprintf(line, sizeof(line), " Max: %ums", customMaxStepMs);
|
||||||
|
} else if (itemIndex == 8) {
|
||||||
|
snprintf(line, sizeof(line), "Back (save)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8g2.drawStr(8, y, line);
|
||||||
|
if (highlight) {
|
||||||
|
u8g2.setDrawColor(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help line (small)
|
||||||
|
u8g2.setFont(u8g2_font_4x6_tr);
|
||||||
|
u8g2.drawStr(2, 64, "UP/DN: Move L/R: Select/Edit");
|
||||||
|
|
||||||
|
u8g2.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
pinMode(BTNU, INPUT_PULLUP);
|
||||||
|
pinMode(BTNR, INPUT_PULLUP);
|
||||||
|
pinMode(BTND, INPUT_PULLUP);
|
||||||
|
pinMode(BTNL, INPUT_PULLUP);
|
||||||
|
pinMode(BUZZER, OUTPUT);
|
||||||
|
|
||||||
|
ledcSetup(ledcChannel, 2000, 8);
|
||||||
|
ledcAttachPin(BUZZER, ledcChannel);
|
||||||
|
|
||||||
|
u8g2.begin();
|
||||||
|
randomSeed(micros());
|
||||||
|
loadHighScore();
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
updateDirectionFromButtons();
|
||||||
|
|
||||||
|
switch (gameState) {
|
||||||
|
case START: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
if (now >= inputIgnoreUntilMs && digitalRead(BTNL) == LOW && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
if (difficultyPreset < 3) {
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
gameState = LEADERBOARD;
|
||||||
|
}
|
||||||
|
} else if (now >= inputIgnoreUntilMs && digitalRead(BTNR) == LOW && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
gameState = SETTINGS;
|
||||||
|
// prepare scroll so the first selectable item (Buzzer) is under the selector
|
||||||
|
settingsScrollOffset = 0;
|
||||||
|
} else if (now >= inputIgnoreUntilMs && digitalRead(BTNU) == LOW && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
// Reset the game state before starting to avoid resuming a dead snake
|
||||||
|
resetGame();
|
||||||
|
// Now enter playing state
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
delay(60);
|
||||||
|
isSprinting = false;
|
||||||
|
sprintHoldU = sprintHoldR = sprintHoldD = sprintHoldL = 0;
|
||||||
|
gameState = PLAYING;
|
||||||
|
stateChangeMs = now;
|
||||||
|
lastStepMs = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawStartScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SETTINGS: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
// Debounce: only process input after a short delay
|
||||||
|
if (now >= inputIgnoreUntilMs && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
if (digitalRead(BTNU) == LOW) {
|
||||||
|
// UP: scroll up (if possible)
|
||||||
|
if (settingsScrollOffset > 0) settingsScrollOffset--;
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTND) == LOW) {
|
||||||
|
// DOWN: scroll down (if possible)
|
||||||
|
if (settingsScrollOffset + SETTINGS_VISIBLE < settingsTotal()) settingsScrollOffset++;
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTNL) == LOW) {
|
||||||
|
// LEFT: edit focused item
|
||||||
|
const uint8_t focused = settingsScrollOffset + (SETTINGS_VISIBLE / 2);
|
||||||
|
const uint8_t total = settingsTotal();
|
||||||
|
if (focused == 1) {
|
||||||
|
// Buzzer cycle backward: OFF <- 500 <- ... <- 2000
|
||||||
|
toneFreq = buzzerCycleDown(toneFreq);
|
||||||
|
} else if (focused == 2) {
|
||||||
|
// Sprint toggle
|
||||||
|
sprintAllowed = !sprintAllowed;
|
||||||
|
} else if (focused == 3) {
|
||||||
|
// Cycle difficulty presets backward
|
||||||
|
difficultyPreset = (difficultyPreset + 3) % 4;
|
||||||
|
if (difficultyPreset < 3) {
|
||||||
|
highScore = leaderboard[activeLeaderboardBoardIndex()][0].score;
|
||||||
|
} else {
|
||||||
|
highScore = 0;
|
||||||
|
}
|
||||||
|
} else if (focused >= 4 && focused <= 7 && total == 10) {
|
||||||
|
// Custom fields (decrease)
|
||||||
|
if (focused == 4) {
|
||||||
|
if (customStepMs > CUSTOM_MIN_MS) customStepMs -= 10;
|
||||||
|
} else if (focused == 5) {
|
||||||
|
if (customSpeedupPerFood > 1) customSpeedupPerFood -= 1;
|
||||||
|
} else if (focused == 6) {
|
||||||
|
if (customMinStepMs > CUSTOM_MIN_MS) customMinStepMs -= 5;
|
||||||
|
} else if (focused == 7) {
|
||||||
|
if (customMaxStepMs > customMinStepMs + 5) customMaxStepMs -= 5;
|
||||||
|
}
|
||||||
|
} else if ((focused == 4 && total == 6) || (focused == 8 && total == 10)) {
|
||||||
|
// Back
|
||||||
|
saveHighScore();
|
||||||
|
gameState = START;
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTNR) == LOW) {
|
||||||
|
// RIGHT: edit focused item
|
||||||
|
const uint8_t focused = settingsScrollOffset + (SETTINGS_VISIBLE / 2);
|
||||||
|
const uint8_t total = settingsTotal();
|
||||||
|
if (focused == 1) {
|
||||||
|
// Buzzer cycle forward: OFF -> 500 -> ... -> 2000 -> OFF
|
||||||
|
toneFreq = buzzerCycleUp(toneFreq);
|
||||||
|
} else if (focused == 2) {
|
||||||
|
// Sprint toggle (also allow right to toggle)
|
||||||
|
sprintAllowed = !sprintAllowed;
|
||||||
|
} else if (focused == 3) {
|
||||||
|
// Cycle difficulty presets forward
|
||||||
|
difficultyPreset = (difficultyPreset + 1) % 4;
|
||||||
|
if (difficultyPreset < 3) {
|
||||||
|
highScore = leaderboard[activeLeaderboardBoardIndex()][0].score;
|
||||||
|
} else {
|
||||||
|
highScore = 0;
|
||||||
|
}
|
||||||
|
} else if (focused >= 4 && focused <= 7 && total == 10) {
|
||||||
|
// Custom fields (increase)
|
||||||
|
if (focused == 4) {
|
||||||
|
if (customStepMs < CUSTOM_MAX_MS) customStepMs += 10;
|
||||||
|
} else if (focused == 5) {
|
||||||
|
if (customSpeedupPerFood < 255) customSpeedupPerFood += 1;
|
||||||
|
} else if (focused == 6) {
|
||||||
|
if (customMinStepMs < customMaxStepMs - 5) customMinStepMs += 5;
|
||||||
|
} else if (focused == 7) {
|
||||||
|
if (customMaxStepMs < CUSTOM_MAX_MS) customMaxStepMs += 5;
|
||||||
|
}
|
||||||
|
} else if ((focused == 4 && total == 6) || (focused == 8 && total == 10)) {
|
||||||
|
// Back
|
||||||
|
saveHighScore();
|
||||||
|
gameState = START;
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSettingsScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LEADERBOARD: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if (now >= inputIgnoreUntilMs && digitalRead(BTNR) == LOW && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
gameState = START;
|
||||||
|
settingsScrollOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawLeaderboardScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case NAME_ENTRY: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
// Require both L+R to be released before allowing another combo press
|
||||||
|
if (digitalRead(BTNL) == HIGH && digitalRead(BTNR) == HIGH) {
|
||||||
|
lrComboReleased = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check L+R combo first (takes priority over movement)
|
||||||
|
if (now >= inputIgnoreUntilMs && digitalRead(BTNL) == LOW && digitalRead(BTNR) == LOW && lrComboReleased && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
// latch until both released to avoid repeats
|
||||||
|
applyKeyboardSelection();
|
||||||
|
lrComboReleased = false;
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (now >= inputIgnoreUntilMs && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS)) {
|
||||||
|
// Then check individual movement buttons
|
||||||
|
if (digitalRead(BTNU) == LOW) {
|
||||||
|
moveKeyboardCursor(-1, 0);
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTND) == LOW) {
|
||||||
|
moveKeyboardCursor(1, 0);
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTNL) == LOW) {
|
||||||
|
moveKeyboardCursor(0, -1);
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
} else if (digitalRead(BTNR) == LOW) {
|
||||||
|
moveKeyboardCursor(0, 1);
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawNameEntryScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PLAYING: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if (now - lastStepMs >= stepIntervalMs()) {
|
||||||
|
lastStepMs = now;
|
||||||
|
runGameStep();
|
||||||
|
}
|
||||||
|
drawGame();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case GAME_OVER: {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
// Any button press returns to start/reset the game (debounced)
|
||||||
|
if (now >= inputIgnoreUntilMs && (now - lastSettingsInputMs >= MENU_DEBOUNCE_MS) && anyButtonPressed()) {
|
||||||
|
lastSettingsInputMs = now;
|
||||||
|
delay(60);
|
||||||
|
isSprinting = false;
|
||||||
|
sprintHoldU = sprintHoldR = sprintHoldD = sprintHoldL = 0;
|
||||||
|
resetGame();
|
||||||
|
}
|
||||||
|
drawGameOverScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(8);
|
||||||
|
}
|
||||||
11
code/test/README
Normal file
11
code/test/README
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
BIN
pcb/espsnake-backups/espsnake-2026-04-09_213235.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-09_213235.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-09_213903.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-09_213903.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-09_214929.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-09_214929.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-09_220428.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-09_220428.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-09_235941.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-09_235941.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-10_081403.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-10_081403.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-10_092321.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-10_092321.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-10_092902.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-10_092902.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-10_093514.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-10_093514.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-10_132748.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-10_132748.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-13_132147.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-13_132147.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-13_132948.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-13_132948.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-19_094805.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-19_094805.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-29_133556.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-29_133556.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-04-29_134111.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-04-29_134111.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-06_141704.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-06_141704.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-06_142708.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-06_142708.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-06_143754.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-06_143754.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-06_162136.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-06_162136.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-07_085627.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-07_085627.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-13_134000.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-13_134000.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-13_135428.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-13_135428.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-13_140130.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-13_140130.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-16_143108.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-16_143108.zip
Normal file
Binary file not shown.
BIN
pcb/espsnake-backups/espsnake-2026-05-20_140240.zip
Normal file
BIN
pcb/espsnake-backups/espsnake-2026-05-20_140240.zip
Normal file
Binary file not shown.
11984
pcb/espsnake.kicad_pcb
Normal file
11984
pcb/espsnake.kicad_pcb
Normal file
File diff suppressed because it is too large
Load diff
131
pcb/espsnake.kicad_prl
Normal file
131
pcb/espsnake.kicad_prl
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"active_layer": 2,
|
||||||
|
"active_layer_preset": "",
|
||||||
|
"auto_track_width": true,
|
||||||
|
"hidden_netclasses": [],
|
||||||
|
"hidden_nets": [],
|
||||||
|
"high_contrast_mode": 0,
|
||||||
|
"net_color_mode": 1,
|
||||||
|
"opacity": {
|
||||||
|
"images": 0.6,
|
||||||
|
"pads": 1.0,
|
||||||
|
"shapes": 1.0,
|
||||||
|
"tracks": 1.0,
|
||||||
|
"vias": 1.0,
|
||||||
|
"zones": 0.6
|
||||||
|
},
|
||||||
|
"selection_filter": {
|
||||||
|
"dimensions": true,
|
||||||
|
"footprints": true,
|
||||||
|
"graphics": true,
|
||||||
|
"keepouts": true,
|
||||||
|
"lockedItems": false,
|
||||||
|
"otherItems": true,
|
||||||
|
"pads": true,
|
||||||
|
"text": true,
|
||||||
|
"tracks": true,
|
||||||
|
"vias": true,
|
||||||
|
"zones": true
|
||||||
|
},
|
||||||
|
"visible_items": [
|
||||||
|
"vias",
|
||||||
|
"footprint_text",
|
||||||
|
"footprint_anchors",
|
||||||
|
"ratsnest",
|
||||||
|
"grid",
|
||||||
|
"footprints_front",
|
||||||
|
"footprints_back",
|
||||||
|
"footprint_values",
|
||||||
|
"footprint_references",
|
||||||
|
"tracks",
|
||||||
|
"drc_errors",
|
||||||
|
"drawing_sheet",
|
||||||
|
"bitmaps",
|
||||||
|
"pads",
|
||||||
|
"zones",
|
||||||
|
"drc_warnings",
|
||||||
|
"drc_exclusions",
|
||||||
|
"locked_item_shadows",
|
||||||
|
"conflict_shadows",
|
||||||
|
"shapes"
|
||||||
|
],
|
||||||
|
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
|
||||||
|
"zone_display_mode": 0
|
||||||
|
},
|
||||||
|
"git": {
|
||||||
|
"repo_type": "",
|
||||||
|
"repo_username": "",
|
||||||
|
"ssh_key": ""
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"filename": "espsnake.kicad_prl",
|
||||||
|
"version": 5
|
||||||
|
},
|
||||||
|
"net_inspector_panel": {
|
||||||
|
"col_hidden": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"col_order": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9
|
||||||
|
],
|
||||||
|
"col_widths": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"custom_group_rules": [],
|
||||||
|
"expanded_rows": [],
|
||||||
|
"filter_by_net_name": true,
|
||||||
|
"filter_by_netclass": true,
|
||||||
|
"filter_text": "",
|
||||||
|
"group_by_constraint": false,
|
||||||
|
"group_by_netclass": false,
|
||||||
|
"show_unconnected_nets": false,
|
||||||
|
"show_zero_pad_nets": false,
|
||||||
|
"sort_ascending": true,
|
||||||
|
"sorting_column": 0
|
||||||
|
},
|
||||||
|
"open_jobsets": [],
|
||||||
|
"project": {
|
||||||
|
"files": []
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"selection_filter": {
|
||||||
|
"graphics": true,
|
||||||
|
"images": true,
|
||||||
|
"labels": true,
|
||||||
|
"lockedItems": false,
|
||||||
|
"otherItems": true,
|
||||||
|
"pins": true,
|
||||||
|
"symbols": true,
|
||||||
|
"text": true,
|
||||||
|
"wires": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
661
pcb/espsnake.kicad_pro
Normal file
661
pcb/espsnake.kicad_pro
Normal file
|
|
@ -0,0 +1,661 @@
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"3dviewports": [],
|
||||||
|
"design_settings": {
|
||||||
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_fields": false,
|
||||||
|
"apply_defaults_to_fp_shapes": false,
|
||||||
|
"apply_defaults_to_fp_text": false,
|
||||||
|
"board_outline_line_width": 0.05,
|
||||||
|
"copper_line_width": 0.2,
|
||||||
|
"copper_text_italic": false,
|
||||||
|
"copper_text_size_h": 1.5,
|
||||||
|
"copper_text_size_v": 1.5,
|
||||||
|
"copper_text_thickness": 0.3,
|
||||||
|
"copper_text_upright": false,
|
||||||
|
"courtyard_line_width": 0.05,
|
||||||
|
"dimension_precision": 4,
|
||||||
|
"dimension_units": 3,
|
||||||
|
"dimensions": {
|
||||||
|
"arrow_length": 1270000,
|
||||||
|
"extension_offset": 500000,
|
||||||
|
"keep_text_aligned": true,
|
||||||
|
"suppress_zeroes": true,
|
||||||
|
"text_position": 0,
|
||||||
|
"units_format": 0
|
||||||
|
},
|
||||||
|
"fab_line_width": 0.1,
|
||||||
|
"fab_text_italic": false,
|
||||||
|
"fab_text_size_h": 1.0,
|
||||||
|
"fab_text_size_v": 1.0,
|
||||||
|
"fab_text_thickness": 0.15,
|
||||||
|
"fab_text_upright": false,
|
||||||
|
"other_line_width": 0.1,
|
||||||
|
"other_text_italic": false,
|
||||||
|
"other_text_size_h": 1.0,
|
||||||
|
"other_text_size_v": 1.0,
|
||||||
|
"other_text_thickness": 0.15,
|
||||||
|
"other_text_upright": false,
|
||||||
|
"pads": {
|
||||||
|
"drill": 0.0,
|
||||||
|
"height": 1.524,
|
||||||
|
"width": 3.025
|
||||||
|
},
|
||||||
|
"silk_line_width": 0.1,
|
||||||
|
"silk_text_italic": false,
|
||||||
|
"silk_text_size_h": 1.0,
|
||||||
|
"silk_text_size_v": 1.0,
|
||||||
|
"silk_text_thickness": 0.1,
|
||||||
|
"silk_text_upright": false,
|
||||||
|
"zones": {
|
||||||
|
"min_clearance": 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"diff_pair_dimensions": [
|
||||||
|
{
|
||||||
|
"gap": 0.0,
|
||||||
|
"via_gap": 0.0,
|
||||||
|
"width": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"drc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"rule_severities": {
|
||||||
|
"annular_width": "error",
|
||||||
|
"clearance": "error",
|
||||||
|
"connection_width": "warning",
|
||||||
|
"copper_edge_clearance": "error",
|
||||||
|
"copper_sliver": "warning",
|
||||||
|
"courtyards_overlap": "error",
|
||||||
|
"creepage": "error",
|
||||||
|
"diff_pair_gap_out_of_range": "error",
|
||||||
|
"diff_pair_uncoupled_length_too_long": "error",
|
||||||
|
"drill_out_of_range": "error",
|
||||||
|
"duplicate_footprints": "warning",
|
||||||
|
"extra_footprint": "warning",
|
||||||
|
"footprint": "error",
|
||||||
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_mismatch": "warning",
|
||||||
|
"footprint_type_mismatch": "ignore",
|
||||||
|
"hole_clearance": "error",
|
||||||
|
"hole_to_hole": "warning",
|
||||||
|
"holes_co_located": "warning",
|
||||||
|
"invalid_outline": "error",
|
||||||
|
"isolated_copper": "warning",
|
||||||
|
"item_on_disabled_layer": "error",
|
||||||
|
"items_not_allowed": "error",
|
||||||
|
"length_out_of_range": "error",
|
||||||
|
"lib_footprint_issues": "warning",
|
||||||
|
"lib_footprint_mismatch": "warning",
|
||||||
|
"malformed_courtyard": "error",
|
||||||
|
"microvia_drill_out_of_range": "error",
|
||||||
|
"mirrored_text_on_front_layer": "warning",
|
||||||
|
"missing_courtyard": "ignore",
|
||||||
|
"missing_footprint": "warning",
|
||||||
|
"net_conflict": "warning",
|
||||||
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
|
"npth_inside_courtyard": "ignore",
|
||||||
|
"padstack": "warning",
|
||||||
|
"pth_inside_courtyard": "ignore",
|
||||||
|
"shorting_items": "error",
|
||||||
|
"silk_edge_clearance": "warning",
|
||||||
|
"silk_over_copper": "warning",
|
||||||
|
"silk_overlap": "warning",
|
||||||
|
"skew_out_of_range": "error",
|
||||||
|
"solder_mask_bridge": "error",
|
||||||
|
"starved_thermal": "error",
|
||||||
|
"text_height": "warning",
|
||||||
|
"text_on_edge_cuts": "error",
|
||||||
|
"text_thickness": "warning",
|
||||||
|
"through_hole_pad_without_hole": "error",
|
||||||
|
"too_many_vias": "error",
|
||||||
|
"track_angle": "error",
|
||||||
|
"track_dangling": "warning",
|
||||||
|
"track_segment_length": "error",
|
||||||
|
"track_width": "error",
|
||||||
|
"tracks_crossing": "error",
|
||||||
|
"unconnected_items": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"via_dangling": "warning",
|
||||||
|
"zones_intersect": "error"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"max_error": 0.005,
|
||||||
|
"min_clearance": 0.0,
|
||||||
|
"min_connection": 0.0,
|
||||||
|
"min_copper_edge_clearance": 0.5,
|
||||||
|
"min_groove_width": 0.0,
|
||||||
|
"min_hole_clearance": 0.25,
|
||||||
|
"min_hole_to_hole": 0.25,
|
||||||
|
"min_microvia_diameter": 0.2,
|
||||||
|
"min_microvia_drill": 0.1,
|
||||||
|
"min_resolved_spokes": 2,
|
||||||
|
"min_silk_clearance": 0.0,
|
||||||
|
"min_text_height": 0.8,
|
||||||
|
"min_text_thickness": 0.08,
|
||||||
|
"min_through_hole_diameter": 0.3,
|
||||||
|
"min_track_width": 0.0,
|
||||||
|
"min_via_annular_width": 0.1,
|
||||||
|
"min_via_diameter": 0.5,
|
||||||
|
"solder_mask_to_copper_clearance": 0.0,
|
||||||
|
"use_height_for_length_calcs": true
|
||||||
|
},
|
||||||
|
"teardrop_options": [
|
||||||
|
{
|
||||||
|
"td_onpthpad": true,
|
||||||
|
"td_onroundshapesonly": false,
|
||||||
|
"td_onsmdpad": true,
|
||||||
|
"td_ontrackend": false,
|
||||||
|
"td_onvia": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"teardrop_parameters": [
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_round_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_rect_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_track_end",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"track_widths": [
|
||||||
|
0.0,
|
||||||
|
0.5,
|
||||||
|
0.75,
|
||||||
|
1.25
|
||||||
|
],
|
||||||
|
"tuning_pattern_settings": {
|
||||||
|
"diff_pair_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 1.0
|
||||||
|
},
|
||||||
|
"diff_pair_skew_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
},
|
||||||
|
"single_track_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"via_dimensions": [
|
||||||
|
{
|
||||||
|
"diameter": 0.0,
|
||||||
|
"drill": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zones_allow_external_fillets": false
|
||||||
|
},
|
||||||
|
"ipc2581": {
|
||||||
|
"dist": "",
|
||||||
|
"distpn": "",
|
||||||
|
"internal_id": "",
|
||||||
|
"mfg": "",
|
||||||
|
"mpn": ""
|
||||||
|
},
|
||||||
|
"layer_pairs": [],
|
||||||
|
"layer_presets": [],
|
||||||
|
"viewports": []
|
||||||
|
},
|
||||||
|
"boards": [],
|
||||||
|
"cvpcb": {
|
||||||
|
"equivalence_files": []
|
||||||
|
},
|
||||||
|
"erc": {
|
||||||
|
"erc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"pin_map": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rule_severities": {
|
||||||
|
"bus_definition_conflict": "error",
|
||||||
|
"bus_entry_needed": "error",
|
||||||
|
"bus_to_bus_conflict": "error",
|
||||||
|
"bus_to_net_conflict": "error",
|
||||||
|
"different_unit_footprint": "error",
|
||||||
|
"different_unit_net": "error",
|
||||||
|
"duplicate_reference": "error",
|
||||||
|
"duplicate_sheet_names": "error",
|
||||||
|
"endpoint_off_grid": "warning",
|
||||||
|
"extra_units": "error",
|
||||||
|
"footprint_filter": "ignore",
|
||||||
|
"footprint_link_issues": "warning",
|
||||||
|
"four_way_junction": "ignore",
|
||||||
|
"global_label_dangling": "warning",
|
||||||
|
"hier_label_mismatch": "error",
|
||||||
|
"label_dangling": "error",
|
||||||
|
"label_multiple_wires": "warning",
|
||||||
|
"lib_symbol_issues": "warning",
|
||||||
|
"lib_symbol_mismatch": "warning",
|
||||||
|
"missing_bidi_pin": "warning",
|
||||||
|
"missing_input_pin": "warning",
|
||||||
|
"missing_power_pin": "error",
|
||||||
|
"missing_unit": "warning",
|
||||||
|
"multiple_net_names": "warning",
|
||||||
|
"net_not_bus_member": "warning",
|
||||||
|
"no_connect_connected": "warning",
|
||||||
|
"no_connect_dangling": "warning",
|
||||||
|
"pin_not_connected": "error",
|
||||||
|
"pin_not_driven": "error",
|
||||||
|
"pin_to_pin": "warning",
|
||||||
|
"power_pin_not_driven": "error",
|
||||||
|
"same_local_global_label": "warning",
|
||||||
|
"similar_label_and_power": "warning",
|
||||||
|
"similar_labels": "warning",
|
||||||
|
"similar_power": "warning",
|
||||||
|
"simulation_model_issue": "ignore",
|
||||||
|
"single_global_label": "ignore",
|
||||||
|
"unannotated": "error",
|
||||||
|
"unconnected_wire_endpoint": "warning",
|
||||||
|
"undefined_netclass": "error",
|
||||||
|
"unit_value_mismatch": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"wire_dangling": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"pinned_footprint_libs": [],
|
||||||
|
"pinned_symbol_libs": []
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"filename": "espsnake.kicad_pro",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"net_settings": {
|
||||||
|
"classes": [
|
||||||
|
{
|
||||||
|
"bus_width": 12,
|
||||||
|
"clearance": 0.25,
|
||||||
|
"diff_pair_gap": 0.25,
|
||||||
|
"diff_pair_via_gap": 0.25,
|
||||||
|
"diff_pair_width": 0.2,
|
||||||
|
"line_style": 0,
|
||||||
|
"microvia_diameter": 0.3,
|
||||||
|
"microvia_drill": 0.1,
|
||||||
|
"name": "Default",
|
||||||
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 2147483647,
|
||||||
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"track_width": 0.75,
|
||||||
|
"via_diameter": 2.3,
|
||||||
|
"via_drill": 1.0,
|
||||||
|
"wire_width": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bus_width": 12,
|
||||||
|
"clearance": 0.1,
|
||||||
|
"diff_pair_gap": 0.25,
|
||||||
|
"diff_pair_width": 0.2,
|
||||||
|
"line_style": 0,
|
||||||
|
"microvia_diameter": 0.3,
|
||||||
|
"microvia_drill": 0.1,
|
||||||
|
"name": "smol",
|
||||||
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 0,
|
||||||
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"track_width": 0.5,
|
||||||
|
"via_diameter": 1.778,
|
||||||
|
"via_drill": 1.0,
|
||||||
|
"wire_width": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": 4
|
||||||
|
},
|
||||||
|
"net_colors": null,
|
||||||
|
"netclass_assignments": null,
|
||||||
|
"netclass_patterns": [
|
||||||
|
{
|
||||||
|
"netclass": "Default",
|
||||||
|
"pattern": "Net-(J2*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"netclass": "smol",
|
||||||
|
"pattern": "Net-(U3*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pcbnew": {
|
||||||
|
"last_paths": {
|
||||||
|
"gencad": "",
|
||||||
|
"idf": "",
|
||||||
|
"netlist": "",
|
||||||
|
"plot": "",
|
||||||
|
"pos_files": "",
|
||||||
|
"specctra_dsn": "",
|
||||||
|
"step": "",
|
||||||
|
"svg": "",
|
||||||
|
"vrml": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": ""
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"annotate_start_num": 0,
|
||||||
|
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||||
|
"bom_fmt_presets": [],
|
||||||
|
"bom_fmt_settings": {
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"keep_line_breaks": false,
|
||||||
|
"keep_tabs": false,
|
||||||
|
"name": "CSV",
|
||||||
|
"ref_delimiter": ",",
|
||||||
|
"ref_range_delimiter": "",
|
||||||
|
"string_delimiter": "\""
|
||||||
|
},
|
||||||
|
"bom_presets": [],
|
||||||
|
"bom_settings": {
|
||||||
|
"exclude_dnp": false,
|
||||||
|
"fields_ordered": [
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Reference",
|
||||||
|
"name": "Reference",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Qty",
|
||||||
|
"name": "${QUANTITY}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Value",
|
||||||
|
"name": "Value",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "DNP",
|
||||||
|
"name": "${DNP}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from BOM",
|
||||||
|
"name": "${EXCLUDE_FROM_BOM}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from Board",
|
||||||
|
"name": "${EXCLUDE_FROM_BOARD}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Footprint",
|
||||||
|
"name": "Footprint",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Datasheet",
|
||||||
|
"name": "Datasheet",
|
||||||
|
"show": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filter_string": "",
|
||||||
|
"group_symbols": true,
|
||||||
|
"include_excluded_from_bom": true,
|
||||||
|
"name": "Default Editing",
|
||||||
|
"sort_asc": true,
|
||||||
|
"sort_field": "Reference"
|
||||||
|
},
|
||||||
|
"connection_grid_size": 50.0,
|
||||||
|
"drawing": {
|
||||||
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
|
"dashed_lines_gap_length_ratio": 3.0,
|
||||||
|
"default_line_thickness": 6.0,
|
||||||
|
"default_text_size": 50.0,
|
||||||
|
"field_names": [],
|
||||||
|
"intersheets_ref_own_page": false,
|
||||||
|
"intersheets_ref_prefix": "",
|
||||||
|
"intersheets_ref_short": false,
|
||||||
|
"intersheets_ref_show": false,
|
||||||
|
"intersheets_ref_suffix": "",
|
||||||
|
"junction_size_choice": 3,
|
||||||
|
"label_size_ratio": 0.375,
|
||||||
|
"operating_point_overlay_i_precision": 3,
|
||||||
|
"operating_point_overlay_i_range": "~A",
|
||||||
|
"operating_point_overlay_v_precision": 3,
|
||||||
|
"operating_point_overlay_v_range": "~V",
|
||||||
|
"overbar_offset_ratio": 1.23,
|
||||||
|
"pin_symbol_size": 25.0,
|
||||||
|
"text_offset_ratio": 0.15
|
||||||
|
},
|
||||||
|
"legacy_lib_dir": "",
|
||||||
|
"legacy_lib_list": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_format_name": "",
|
||||||
|
"page_layout_descr_file": "",
|
||||||
|
"plot_directory": "",
|
||||||
|
"space_save_all_events": true,
|
||||||
|
"spice_current_sheet_as_root": false,
|
||||||
|
"spice_external_command": "spice \"%I\"",
|
||||||
|
"spice_model_current_sheet_as_root": true,
|
||||||
|
"spice_save_all_currents": false,
|
||||||
|
"spice_save_all_dissipations": false,
|
||||||
|
"spice_save_all_voltages": false,
|
||||||
|
"subpart_first_id": 65,
|
||||||
|
"subpart_id_separator": 0
|
||||||
|
},
|
||||||
|
"sheets": [
|
||||||
|
[
|
||||||
|
"38670a8d-2240-4ffc-83ec-d5b488a7d099",
|
||||||
|
"Root"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text_variables": {}
|
||||||
|
}
|
||||||
8799
pcb/espsnake.kicad_sch
Normal file
8799
pcb/espsnake.kicad_sch
Normal file
File diff suppressed because it is too large
Load diff
104252
pcb/fp-info-cache
Normal file
104252
pcb/fp-info-cache
Normal file
File diff suppressed because it is too large
Load diff
7
pcb/fp-lib-table
Normal file
7
pcb/fp-lib-table
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
(fp_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "ESP32-C3_SUPERMINI_SMD")(type "KiCad")(uri "/home/mia/kicad/_librarys/ESP32-C3_SUPERMINI_SMD")(options "")(descr ""))
|
||||||
|
(lib (name "ESP32-C3_SUPERMINI_TH")(type "KiCad")(uri "/home/mia/kicad/_librarys/ESP32-C3_SUPERMINI_TH")(options "")(descr ""))
|
||||||
|
(lib (name "FS8205A")(type "KiCad")(uri "/home/mia/kicad/_librarys/FS8205A")(options "")(descr ""))
|
||||||
|
(lib (name "OLED_128X64_1.3_I2C")(type "KiCad")(uri "/home/mia/kicad/_librarys/OLED_128X64_1.3_I2C")(options "")(descr ""))
|
||||||
|
)
|
||||||
7
pcb/sym-lib-table
Normal file
7
pcb/sym-lib-table
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
(sym_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "ESP32-C3_SUPERMINI_SMD")(type "KiCad")(uri "/home/mia/kicad/_librarys/ESP32-C3_SUPERMINI_SMD/ESP32-C3_SUPERMINI_SMD.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "ESP32-C3_SUPERMINI_TH")(type "KiCad")(uri "/home/mia/kicad/_librarys/ESP32-C3_SUPERMINI_TH/ESP32-C3_SUPERMINI_TH.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "FS8205A")(type "KiCad")(uri "/home/mia/kicad/_librarys/FS8205A/FS8205A.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "OLED_128X64_1.3_I2C")(type "KiCad")(uri "/home/mia/kicad/_librarys/OLED_128X64_1.3_I2C/OLED_128X64_1.3_I2C.kicad_sym")(options "")(descr ""))
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue