Games App: Pong, Snake and Tetris
CI / build (pull_request) Successful in 2m20s
Site / build (pull_request) Successful in 10s

A new App (issue #101) that shows its three Games as icons, as the
Launcher shows Apps, and plays the one chosen. A Game left in the middle
is found again paused; one being played keeps the screen lit. Tetris
plays Korobeiniki on a speaker channel of its own, faster with each
level: 0 switches it, 8 and 9 set its volume, both remembered. The drop
is on 1, away from the arrows. The best of each Game is kept.

Played on the device. The speeds are as tuned there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
2026-10-13 22:59:05 +02:00
co-authored by Claude Opus 5.5
parent 68d50b320f
commit ec15c2f52a
15 changed files with 835 additions and 0 deletions
+451
View File
@@ -0,0 +1,451 @@
#include "apps/games_app.h"
#include <Arduino.h>
#include <M5Cardputer.h>
#include <esp_random.h>
#include <algorithm>
#include <cstdio>
#include "app_keys.h"
#include "ui/fonts.h"
#include "ui/icons.h"
#include "ui/theme.h"
#include "ui/widgets.h"
namespace roro {
using namespace games;
namespace {
constexpr uint32_t kFrameMs = 33; // 30 pictures a second while a Game runs
constexpr uint32_t kTapMs = 70; // how long a tap moves Pong's paddle
constexpr int kCell = 6; // Snake's and Tetris's cells, in pixels
const char* const kNames[3] = {"Pong", "Snake", "Tetris"};
const char* const kBestKeys[3] = {"best_pong", "best_snake", "best_tetris"};
// The seven pieces in the theme's seven accents: they follow the theme chosen.
uint16_t pieceColor(uint8_t kind) {
switch (kind) {
case 1: return theme::kAccent;
case 2: return theme::kYellow;
case 3: return theme::kViolet;
case 4: return theme::kGood;
case 5: return theme::kRed;
case 6: return theme::kMessage;
default: return theme::kWarning;
}
}
} // namespace
// The speaker mixes its channels: the tune has one, the beeps another, so a line cleared does
// not cut a note short.
constexpr int kTuneChannel = 0, kBeepChannel = 1;
constexpr uint32_t kBreathMs = 25; // of silence at the end of each note, so two alike are two
void GamesApp::beep(int hz, int ms) {
if (settings_.getBool(Setting::Sound)) M5Cardputer.Speaker.tone(hz, ms, kBeepChannel, true);
}
bool GamesApp::musicOn() {
if (music_ < 0) {
int32_t stored = 1;
music_ = store_.getInt("tetris_music", stored) ? (stored != 0) : 1; // on, until switched off
}
return music_ == 1;
}
// The tune's loudness, in eight steps. The speaker is set for short beeps, at a quarter of what
// it can do: for the tune it is turned up all the way, and each channel's own volume does the
// rest, the tune's by the step chosen, the others' a quarter so a beep stays as loud as it was.
constexpr int kLoudest = 8, kUsualVolume = 4;
// The top is four fifths of all the speaker can do: above that the small speaker distorts.
constexpr uint8_t kStepVolume[kLoudest] = {5, 10, 16, 26, 45, 77, 128, 204}; // 64 is the speaker's usual
int GamesApp::volume() {
if (volume_ < 0) {
int32_t stored = kUsualVolume;
volume_ = store_.getInt("tetris_volume", stored) && stored >= 1 && stored <= kLoudest ? stored : kUsualVolume;
}
return volume_;
}
void GamesApp::setVolume(int level) {
level = std::clamp(level, 1, kLoudest);
if (level == volume()) return;
volume_ = level;
store_.putInt("tetris_volume", level);
if (masterWas_ >= 0) M5Cardputer.Speaker.setChannelVolume(kTuneChannel, kStepVolume[level - 1]);
}
void GamesApp::loud(bool on) {
auto& speaker = M5Cardputer.Speaker;
if (on == (masterWas_ >= 0)) return;
if (on) {
masterWas_ = speaker.getVolume();
speaker.setAllChannelVolume(static_cast<uint8_t>(masterWas_)); // times a master of 255: as before
speaker.setChannelVolume(kTuneChannel, kStepVolume[volume() - 1]);
speaker.setVolume(255);
} else {
speaker.setVolume(static_cast<uint8_t>(masterWas_));
speaker.setAllChannelVolume(255);
masterWas_ = -1;
}
}
void GamesApp::quiet() {
M5Cardputer.Speaker.stop(kTuneChannel);
loud(false);
}
int GamesApp::best(Which w) {
if (best_[w] < 0) {
int32_t stored = 0;
best_[w] = store_.getInt(kBestKeys[w], stored) && stored > 0 ? stored : 0;
}
return best_[w];
}
void GamesApp::onEnter() {
lastMs_ = millis();
requestRedraw();
}
void GamesApp::pause() {
if (which_ != Menu && state_[which_] == State::Playing) state_[which_] = State::Paused;
quiet();
}
void GamesApp::onExit() { pause(); }
void GamesApp::begin() {
uint32_t seed = esp_random();
if (which_ == Pong) pong_.reset(new games::Pong(seed));
else if (which_ == Snake) snake_.reset(new games::Snake(seed));
else if (which_ == Tetris) tetris_.reset(new games::Tetris(seed));
state_[which_] = State::Playing;
tune_.restart();
newBest_ = false;
tapped_ = TetrisInput();
tapMove_ = 0;
lastMs_ = millis();
}
void GamesApp::ended(int score) {
state_[which_] = State::Over;
quiet();
newBest_ = score > best(which_);
if (newBest_) {
best_[which_] = score;
store_.putInt(kBestKeys[which_], score);
}
beep(newBest_ ? 1320 : 220, 250);
}
bool GamesApp::onKey(const KeyEvent& e) {
requestRedraw();
if (which_ == Menu) {
switch (e.key) {
case Key::Left: chosen_ = (chosen_ + 2) % 3; return true;
case Key::Right: chosen_ = (chosen_ + 1) % 3; return true;
case Key::Select:
which_ = static_cast<Which>(chosen_);
lastMs_ = millis();
return true;
default: return false; // Back leaves the App
}
}
State& state = state_[which_];
if (which_ == Tetris && e.key == Key::Char && e.ch == '0') { // the tune, on or off, whatever the game is doing
music_ = musicOn() ? 0 : 1;
store_.putInt("tetris_music", music_);
if (!music_) quiet();
return true;
}
if (which_ == Tetris && e.key == Key::Char && (e.ch == '8' || e.ch == '9')) { // quieter, louder
setVolume(volume() + (e.ch == '9' ? 1 : -1));
return true;
}
if (e.key == Key::Back) { // to the list; the Game waits, paused
pause();
which_ = Menu;
return true;
}
if (state == State::Ready || state == State::Over) {
if (e.key == Key::Select) begin();
return true;
}
if (state == State::Paused) { // any key carries on
state = State::Playing;
lastMs_ = millis();
return true;
}
if (e.key == Key::Char && (e.ch == 'p' || e.ch == 'P')) {
pause();
return true;
}
return which_ == Pong ? pongKey(e) : which_ == Snake ? snakeKey(e) : tetrisKey(e);
}
bool GamesApp::pongKey(const KeyEvent& e) {
if (e.key != Key::Up && e.key != Key::Down) return true;
tapMove_ = e.key == Key::Up ? -1 : 1;
tapUntilMs_ = millis() + kTapMs;
return true;
}
bool GamesApp::snakeKey(const KeyEvent& e) {
switch (e.key) {
case Key::Up: snake_->turn(Snake::Dir::Up); break;
case Key::Down: snake_->turn(Snake::Dir::Down); break;
case Key::Left: snake_->turn(Snake::Dir::Left); break;
case Key::Right: snake_->turn(Snake::Dir::Right); break;
default: break;
}
return true;
}
bool GamesApp::tetrisKey(const KeyEvent& e) {
switch (e.key) {
case Key::Up: tapped_.rotate = true; break;
case Key::Left: tapped_.left = true; break;
case Key::Right: tapped_.right = true; break;
case Key::Down: tapped_.soft = true; break;
case Key::Char:
if (e.ch == 'z' || e.ch == 'Z') tapped_.rotateBack = true;
else if (e.ch == '1') tapped_.hardDrop = true; // far from the arrows: not hit by mistake
break;
default: break;
}
return true;
}
void GamesApp::update(uint32_t nowMs) {
uint32_t ms = std::min<uint32_t>(nowMs - lastMs_, 100);
if (which_ == Menu || state_[which_] != State::Playing) {
lastMs_ = nowMs;
return;
}
if (ms == 0) return;
lastMs_ = nowMs;
if (which_ == Pong) {
int move = keys_.held(Key::Up) ? -1 : keys_.held(Key::Down) ? 1 : 0;
if (!move && static_cast<int32_t>(tapUntilMs_ - nowMs) > 0) move = tapMove_;
uint8_t events = pong_->step(ms / 1000.0f, move);
if (events & Pong::Over) ended(pong_->longestRally());
else if (events & Pong::Point) beep(330, 120);
else if (events & Pong::Hit) beep(880, 15);
else if (events & Pong::Wall) beep(440, 10);
} else if (which_ == Snake) {
uint8_t events = snake_->step(ms);
if (events & Snake::Died) ended(snake_->score());
else if (events & Snake::Ate) beep(990, 25);
} else {
TetrisInput in = tapped_;
tapped_ = TetrisInput();
in.left |= keys_.held(Key::Left);
in.right |= keys_.held(Key::Right);
in.soft |= keys_.held(Key::Down);
uint8_t events = tetris_->step(ms, in);
if (events & Tetris::Over) ended(tetris_->score());
else if (events & Tetris::LevelUp) beep(1320, 120);
else if (events & Tetris::Lines) beep(tetris_->lastCleared() == 4 ? 1320 : 880, 60);
else if (events & Tetris::Locked) beep(220, 12);
Note note{};
uint32_t length = 0;
tune_.setEighthMs(korobeiniki::eighthMsAt(tetris_->level())); // it hurries as the game does
if (state_[Tetris] == State::Playing && musicOn() && settings_.getBool(Setting::Sound) && tune_.step(ms, note, length) && note.hz) {
loud(true);
M5Cardputer.Speaker.tone(note.hz, length - kBreathMs, kTuneChannel, true);
}
}
if (nowMs - lastDrawMs_ >= kFrameMs) {
lastDrawMs_ = nowMs;
requestRedraw();
}
}
void GamesApp::help(std::vector<KeyHelp>& out) const {
switch (which_) {
case Pong: return keys::add(out, keys::kGamesPong);
case Snake: return keys::add(out, keys::kGamesSnake);
case Tetris: return keys::add(out, keys::kGamesTetris);
default: return keys::add(out, keys::kGames);
}
}
const char* GamesApp::helpTitle() const {
return which_ == Pong ? "Games: Pong" : which_ == Snake ? "Games: Snake" : which_ == Tetris ? "Games: Tetris" : nullptr;
}
// A box in the middle of the field: where the Game stands when it is not running.
void GamesApp::drawBanner(Canvas& c, const std::string& title, const std::string& detail) {
const auto& area = theme::kContent;
const int w = 150, h = detail.empty() ? 24 : 40, x = (area.w - w) / 2, y = area.y + (area.h - h) / 2;
c.fillRect(x, y, w, h, theme::kBackground);
c.drawRect(x, y, w, h, theme::kAccent);
c.setTextDatum(top_center);
c.setFont(&fonts::bold);
c.setTextColor(theme::kText);
c.drawString(title.c_str(), area.w / 2, y + 5);
if (!detail.empty()) {
c.setFont(&fonts::body);
c.setTextColor(theme::kMuted);
c.drawString(detail.c_str(), area.w / 2, y + 21);
}
c.setTextDatum(top_left);
}
// The three Games as the Launcher shows Apps: an icon each, the chosen one on a cyan tile, its
// name under the row. A Game waiting paused has the Launcher's orange dot.
void GamesApp::drawMenu(Canvas& c) {
using namespace theme;
static const uint8_t* const kIcons[3] = {icons::pong, icons::snake, icons::tetris};
const int tileW = 60, tileH = 36, x0 = (kContent.w - 3 * tileW) / 2, y = kContent.y + 24;
for (int i = 0; i < 3; i++) {
int x = x0 + i * tileW, ix = x + (tileW - icons::kSize) / 2, iy = y + (tileH - icons::kSize) / 2;
bool chosen = i == chosen_;
if (chosen) {
int tx = ix - 6, tw = icons::kSize + 12;
c.fillRect(tx + 4, y, tw - 8, tileH, kAccent);
c.fillRect(tx + 2, y + 2, tw - 4, tileH - 4, kAccent);
c.fillRect(tx, y + 4, tw, tileH - 8, kAccent);
}
c.drawBitmap(ix, iy, kIcons[i], icons::kSize, icons::kSize, chosen ? kFaint : kAccent);
if (state_[i] == State::Paused) {
c.fillRect(ix + icons::kSize - 6, iy - 1, 10, 10, chosen ? kAccent : kBackground);
c.fillRect(ix + icons::kSize - 4, iy + 1, 6, 6, kWarning);
}
}
auto w = static_cast<Which>(chosen_);
c.setTextDatum(top_center);
c.setFont(&fonts::bold);
c.setTextColor(kText);
c.drawString(kNames[chosen_], kContent.w / 2, y + tileH + 8);
int b = best(w);
std::string under = state_[chosen_] == State::Paused ? "paused" : "";
if (b > 0) under += (under.empty() ? "" : ", ") + std::string(w == Pong ? "longest rally " : "best ") + std::to_string(b);
c.setFont(&fonts::body);
c.setTextColor(kMuted);
c.drawString(under.c_str(), kContent.w / 2, y + tileH + 8 + kLineHeight + 2);
c.setTextDatum(top_left);
}
void GamesApp::drawPong(Canvas& c) {
const auto& area = theme::kContent;
State state = state_[Pong];
const int top = area.y;
for (int y = top + 2; y < top + static_cast<int>(Pong::kHeight); y += 8) c.drawFastVLine(area.w / 2, y, 4, theme::kFaint);
if (pong_) {
char score[8];
c.setFont(&fonts::large);
c.setTextColor(theme::kMuted);
c.setTextDatum(top_center);
std::snprintf(score, sizeof score, "%d", pong_->playerScore());
c.drawString(score, area.w / 2 - 24, top + 3);
std::snprintf(score, sizeof score, "%d", pong_->deviceScore());
c.drawString(score, area.w / 2 + 24, top + 3);
c.setTextDatum(top_left);
const int ph = static_cast<int>(Pong::kPaddleH), pw = static_cast<int>(Pong::kPaddleW), m = static_cast<int>(Pong::kMargin);
c.fillRect(m, top + static_cast<int>(pong_->playerY()) - ph / 2, pw, ph, theme::kAccent);
c.fillRect(area.w - m - pw, top + static_cast<int>(pong_->deviceY()) - ph / 2, pw, ph, theme::kWarning);
const int b = static_cast<int>(Pong::kBall);
c.fillRect(static_cast<int>(pong_->ballX()) - b / 2, top + static_cast<int>(pong_->ballY()) - b / 2, b, b, theme::kText);
}
int b = best(Pong);
std::string bestLine = b > 0 ? "longest rally " + std::to_string(b) : "first to 7";
if (state == State::Ready) drawBanner(c, "Pong", bestLine);
else if (state == State::Paused) drawBanner(c, "Paused", "");
else if (state == State::Over)
drawBanner(c, pong_->playerWon() ? "You win" : "The device wins",
newBest_ ? "a new longest rally: " + std::to_string(b) : "rally " + std::to_string(pong_->longestRally()) + ", best " + std::to_string(b));
}
void GamesApp::drawSnake(Canvas& c) {
const auto& area = theme::kContent;
State state = state_[Snake];
const int w = Snake::kCols * kCell, h = Snake::kRows * kCell, x0 = (area.w - w) / 2, y0 = area.y + area.h - h - 1;
c.drawRect(x0 - 1, y0 - 1, w + 2, h + 2, theme::kFaint);
c.setFont(&fonts::small);
c.setTextColor(theme::kMuted);
char line[32];
std::snprintf(line, sizeof line, "%d", snake_ ? snake_->score() : 0);
c.drawString(line, x0, area.y);
std::snprintf(line, sizeof line, "best %d", best(Snake));
c.setTextDatum(top_right);
c.drawString(line, x0 + w, area.y);
c.setTextDatum(top_left);
if (snake_) {
auto f = snake_->food();
c.fillRect(x0 + f.col * kCell + 1, y0 + f.row * kCell + 1, kCell - 2, kCell - 2, theme::kWarning);
bool head = true;
for (auto& cell : snake_->body()) {
c.fillRect(x0 + cell.col * kCell, y0 + cell.row * kCell, kCell - 1, kCell - 1, head ? theme::kText : theme::kGood);
head = false;
}
}
if (state == State::Ready) drawBanner(c, "Snake", "the walls bite");
else if (state == State::Paused) drawBanner(c, "Paused", "");
else if (state == State::Over)
drawBanner(c, "Game over", newBest_ ? "a new best: " + std::to_string(snake_->score()) : std::to_string(snake_->score()) + ", best " + std::to_string(best(Snake)));
}
void GamesApp::drawTetris(Canvas& c) {
const auto& area = theme::kContent;
State state = state_[Tetris];
const int w = Tetris::kCols * kCell, h = Tetris::kRows * kCell, x0 = (area.w - w) / 2, y0 = area.y + 1;
c.drawFastVLine(x0 - 1, y0, h + 1, theme::kMuted);
c.drawFastVLine(x0 + w, y0, h + 1, theme::kMuted);
c.drawFastHLine(x0 - 1, y0 + h, w + 2, theme::kMuted);
auto block = [&](int x, int y, uint8_t kind) { c.fillRect(x, y, kCell - 1, kCell - 1, pieceColor(kind)); };
c.setFont(&fonts::body);
const int right = x0 + w + 12, left = 6;
auto readout = [&](int x, int y, const char* label, int value) {
c.setTextColor(theme::kMuted);
c.drawString(label, x, y);
c.setTextColor(theme::kText);
c.drawString(std::to_string(value).c_str(), x, y + theme::kLineHeight);
};
readout(left, area.y + 4, "Score", tetris_ ? tetris_->score() : 0);
readout(left, area.y + 38, "Best", best(Tetris));
readout(left, area.y + 72, "Lines", tetris_ ? tetris_->lines() : 0);
c.setTextColor(theme::kMuted);
c.drawString("Next", right, area.y + 4);
readout(right, area.y + 72, "Level", tetris_ ? tetris_->level() + 1 : 1);
c.setFont(&fonts::small);
c.setTextColor(theme::kMuted);
char music[20];
std::snprintf(music, sizeof music, "music %d/%d", volume(), kLoudest);
c.drawString(!settings_.getBool(Setting::Sound) ? "sound off" : musicOn() ? music : "music off", right, area.y + area.h - 10);
c.setFont(&fonts::body);
if (tetris_) {
for (int row = 0; row < Tetris::kRows; row++)
for (int col = 0; col < Tetris::kCols; col++)
if (uint8_t kind = tetris_->cell(col, row)) block(x0 + col * kCell, y0 + row * kCell, kind);
if (state != State::Over)
for (auto& cell : Tetris::cells(tetris_->piece()))
if (cell.row >= 0) block(x0 + cell.col * kCell, y0 + cell.row * kCell, tetris_->piece().kind);
Tetris::Piece next{tetris_->next(), 0, 0, 0};
for (auto& cell : Tetris::cells(next)) block(right + cell.col * kCell, area.y + 22 + cell.row * kCell, next.kind);
}
if (state == State::Ready) drawBanner(c, "Tetris", "");
else if (state == State::Paused) drawBanner(c, "Paused", "");
else if (state == State::Over)
drawBanner(c, "Game over", newBest_ ? "a new best: " + std::to_string(tetris_->score()) : std::to_string(tetris_->lines()) + " lines");
}
void GamesApp::draw(Canvas& c) {
c.setTextDatum(top_left);
lastDrawMs_ = millis();
switch (which_) {
case Pong: drawPong(c); break;
case Snake: drawSnake(c); break;
case Tetris: drawTetris(c); break;
default: drawMenu(c); break;
}
}
} // namespace roro
+79
View File
@@ -0,0 +1,79 @@
#pragma once
#include <memory>
#include <string>
#include "app.h"
#include "key_mapper.h"
#include "key_value_store.h"
#include "pong.h"
#include "settings.h"
#include "snake.h"
#include "tetris.h"
#include "tune.h"
namespace roro {
// The Games App (issue #101): three Games as a row of icons, and the one being played. The rules are in
// lib/games; this draws them, gives them the keys and the time, beeps, and keeps the best of each.
// A Game left in the middle, for the list or for another App, is found again paused.
class GamesApp : public App {
public:
GamesApp(const KeyMapper& keys, const Settings& settings, KeyValueStore& store) : keys_(keys), settings_(settings), store_(store) {}
void onEnter() override;
void onExit() override;
bool onKey(const KeyEvent& e) override;
void update(uint32_t nowMs) override;
void draw(Canvas& c) override;
void help(std::vector<KeyHelp>& out) const override;
const char* helpTitle() const override;
// A Game being played is the device in use, key or no key: the screen stays lit.
bool keepsScreenOn() const override { return which_ != Menu && state_[which_] == State::Playing; }
private:
enum Which { Pong, Snake, Tetris, Menu }; // the first three index the arrays
enum class State : uint8_t { Ready, Playing, Paused, Over };
void begin(); // a new game of the one chosen
void pause();
void ended(int score);
void beep(int hz, int ms);
bool musicOn();
int volume(); // of the tune, 1 to kLoudest
void setVolume(int level);
void quiet(); // the tune stops where it is
void loud(bool on); // the speaker turned up for the tune, or back as it was
int best(Which w);
bool pongKey(const KeyEvent& e);
bool snakeKey(const KeyEvent& e);
bool tetrisKey(const KeyEvent& e);
void drawMenu(Canvas& c);
void drawPong(Canvas& c);
void drawSnake(Canvas& c);
void drawTetris(Canvas& c);
void drawBanner(Canvas& c, const std::string& title, const std::string& detail);
const KeyMapper& keys_;
const Settings& settings_;
KeyValueStore& store_;
int chosen_ = 0; // in the row of three
Which which_ = Menu;
State state_[3] = {State::Ready, State::Ready, State::Ready};
int best_[3] = {-1, -1, -1}; // -1: not read from the store yet
bool newBest_ = false;
std::unique_ptr<games::Pong> pong_;
std::unique_ptr<games::Snake> snake_;
std::unique_ptr<games::Tetris> tetris_;
uint32_t lastMs_ = 0, lastDrawMs_ = 0;
// Keys that came as presses since the last step: a tap moves too, held or not.
int tapMove_ = 0;
uint32_t tapUntilMs_ = 0;
games::TetrisInput tapped_;
// Tetris's tune (Korobeiniki), switched with 0 and remembered.
games::TunePlayer tune_{games::korobeiniki::at, games::korobeiniki::kCount, games::korobeiniki::kEighthMs};
int music_ = -1, volume_ = -1; // -1: not read from the store yet
int masterWas_ = -1; // the speaker's own volume, while the tune has it turned up
};
} // namespace roro
+10
View File
@@ -18,6 +18,7 @@
#include "services/ssh_service.h"
#include "services/vpn_service.h"
#include "services/web_share.h"
#include "apps/games_app.h"
#include "apps/gemini_app.h"
#include "apps/gnss_app.h"
#include "auto_update.h"
@@ -261,6 +262,7 @@ void setup() {
netTools.ntpServer = []() { return settings.getString(Setting::Ntp1); };
// A long note being rewritten (issue #47): the editor waits for the card, so it draws from there.
NoteEditor::onProgress = [](const std::string& name, int percent) { screen.renderUpdate("Saving", name, percent); };
apps->registerApp({"games", "Games", false, new GamesApp(keyMapper, settings, nvs), icons::games});
apps->registerApp({"system", "System", false,
new SystemApp(*wifi, *battery, *storageService, *radioService, *gnssService, nvs), icons::system});
apps->registerApp({"settings", "Settings", false,
@@ -1611,6 +1613,14 @@ static void loopPass() {
bus.dispatch();
notifier->update(now);
apps->update(now);
// An App in use with no key pressed (a Game being played): the screen stays lit, and it is
// not the quiet moment an update waits for.
static uint32_t keptOnMs = 0;
if (apps->foreground().keepsScreenOn() && now - keptOnMs >= 1000) {
keptOnMs = now;
lastKeyMs = now;
power->onKey(now);
}
autoUpdateStep(now);
if (now - lastLog > 10000) {
+140
View File
@@ -3,6 +3,41 @@
namespace roro::icons {
const uint8_t games[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x03, 0xff, 0xff, 0xc0, // ......####################......
0x03, 0xff, 0xff, 0xc0, // ......####################......
0x0c, 0x00, 0x00, 0x30, // ....##....................##....
0x0c, 0x00, 0x00, 0x30, // ....##....................##....
0x30, 0x00, 0x00, 0x0c, // ..##........................##..
0x30, 0x00, 0x00, 0x0c, // ..##........................##..
0x30, 0xc0, 0x03, 0x0c, // ..##....##............##....##..
0x30, 0xc0, 0x03, 0x0c, // ..##....##............##....##..
0x33, 0xf0, 0x0c, 0xcc, // ..##..######........##..##..##..
0x33, 0xf0, 0x0c, 0xcc, // ..##..######........##..##..##..
0x30, 0xc0, 0x03, 0x0c, // ..##....##............##....##..
0x30, 0xc0, 0x03, 0x0c, // ..##....##............##....##..
0x30, 0x00, 0x00, 0x0c, // ..##........................##..
0x30, 0x00, 0x00, 0x0c, // ..##........................##..
0x30, 0x0f, 0xf0, 0x0c, // ..##........########........##..
0x30, 0x0f, 0xf0, 0x0c, // ..##........########........##..
0x30, 0x30, 0x0c, 0x0c, // ..##......##........##......##..
0x30, 0x30, 0x0c, 0x0c, // ..##......##........##......##..
0x0f, 0xc0, 0x03, 0xf0, // ....######............######....
0x0f, 0xc0, 0x03, 0xf0, // ....######............######....
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t gemini[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
@@ -178,6 +213,41 @@ const uint8_t notes[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t pong[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x03, 0x00, 0x00, // ..............##................
0x00, 0x03, 0x00, 0x00, // ..............##................
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x3c, 0x03, 0x00, 0x00, // ..####........##................
0x3c, 0x03, 0x00, 0x00, // ..####........##................
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x3c, 0x03, 0x0f, 0x00, // ..####........##....####........
0x3c, 0x03, 0x0f, 0x00, // ..####........##....####........
0x3c, 0x00, 0x0f, 0x0f, // ..####..............####....####
0x3c, 0x00, 0x0f, 0x0f, // ..####..............####....####
0x00, 0x03, 0x00, 0x0f, // ..............##............####
0x00, 0x03, 0x00, 0x0f, // ..............##............####
0x00, 0x00, 0x00, 0x0f, // ............................####
0x00, 0x00, 0x00, 0x0f, // ............................####
0x00, 0x03, 0x00, 0x0f, // ..............##............####
0x00, 0x03, 0x00, 0x0f, // ..............##............####
0x00, 0x00, 0x00, 0x0f, // ............................####
0x00, 0x00, 0x00, 0x0f, // ............................####
0x00, 0x03, 0x00, 0x00, // ..............##................
0x00, 0x03, 0x00, 0x00, // ..............##................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t settings[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
@@ -248,6 +318,41 @@ const uint8_t shell[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t snake[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x0f, 0xf0, // ....................########....
0x00, 0x00, 0x0f, 0xf0, // ....................########....
0x00, 0x00, 0x0c, 0xf0, // ....................##..####....
0x00, 0x00, 0x0c, 0xf0, // ....................##..####....
0x00, 0x00, 0x0f, 0xf0, // ....................########....
0x00, 0x00, 0x0f, 0xf0, // ....................########....
0x00, 0x00, 0x03, 0xc0, // ......................####......
0x00, 0x00, 0x03, 0xc0, // ......................####......
0x03, 0xff, 0xff, 0x00, // ......##################........
0x03, 0xff, 0xff, 0x00, // ......##################........
0x03, 0xc0, 0x00, 0x00, // ......####......................
0x03, 0xc0, 0x00, 0x00, // ......####......................
0x03, 0xc0, 0x00, 0x00, // ......####......................
0x03, 0xc0, 0x00, 0x00, // ......####......................
0x03, 0xff, 0xff, 0x00, // ......##################........
0x03, 0xff, 0xff, 0x00, // ......##################........
0x00, 0x00, 0x0f, 0x00, // ....................####........
0x00, 0x00, 0x0f, 0x00, // ....................####........
0x00, 0x00, 0x0f, 0x00, // ....................####........
0x00, 0x00, 0x0f, 0x00, // ....................####........
0x3f, 0xff, 0xfc, 0x00, // ..####################..........
0x3f, 0xff, 0xfc, 0x00, // ..####################..........
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x3c, 0x00, 0x00, 0x00, // ..####..........................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t ssh[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
@@ -353,6 +458,41 @@ const uint8_t system[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t tetris[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x0f, 0xcf, 0xcf, 0xc0, // ....######..######..######......
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x0f, 0xc0, 0x00, // ............######..............
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x0f, 0xc0, 0x0f, 0xc0, // ....######..........######......
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
0x3f, 0xff, 0xff, 0xf0, // ..##########################....
0x3f, 0xff, 0xff, 0xf0, // ..##########################....
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
};
const uint8_t wifi_tools[128] = {
0x00, 0x00, 0x00, 0x00, // ................................
0x00, 0x00, 0x00, 0x00, // ................................
+4
View File
@@ -8,16 +8,20 @@ namespace roro::icons {
// One bit a pixel, the leftmost first, a row after another: what Canvas::drawBitmap takes.
constexpr int kSize = 32;
extern const uint8_t games[128]; // games.png
extern const uint8_t gemini[128]; // gemini.png
extern const uint8_t gnss[128]; // gnss.png
extern const uint8_t irc[128]; // irc.png
extern const uint8_t lora[128]; // lora.png
extern const uint8_t notes[128]; // notes.png
extern const uint8_t pong[128]; // pong.png
extern const uint8_t settings[128]; // settings.png
extern const uint8_t shell[128]; // shell.png
extern const uint8_t snake[128]; // snake.png
extern const uint8_t ssh[128]; // ssh.png
extern const uint8_t storage[128]; // storage.png
extern const uint8_t system[128]; // system.png
extern const uint8_t tetris[128]; // tetris.png
extern const uint8_t wifi_tools[128]; // wifi-tools.png
} // namespace roro::icons