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