mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
craptrix
This commit is contained in:
@@ -19,22 +19,30 @@ public:
|
||||
}
|
||||
}
|
||||
bool get_pixel(int x, int y) {
|
||||
// if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
// assert(false);
|
||||
if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
assert(false);
|
||||
assert(false); // Not implemented
|
||||
return true;
|
||||
// return disp_frame[y][x];
|
||||
}
|
||||
void reset_pixel(int x, int y) {
|
||||
// if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
// assert(false);
|
||||
if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
assert(false);
|
||||
SMD::get().set_pixel(x, y, false);
|
||||
}
|
||||
void set_pixel(int x, int y) {
|
||||
// if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
// assert(false);
|
||||
//
|
||||
if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT)
|
||||
assert(false);
|
||||
|
||||
SMD::get().set_pixel(x, y, true);
|
||||
}
|
||||
void set_pixel(int x, int y, bool on) {
|
||||
if (on) {
|
||||
set_pixel(x, y);
|
||||
} else {
|
||||
reset_pixel(x, y);
|
||||
}
|
||||
}
|
||||
void draw_to_display();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,166 +1,690 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
// 400x240 1-bit Tetris — centered board, HUD, correct polarity, upright font.
|
||||
// Logical ON = BLACK, OFF = WHITE. No hidden inversion anywhere.
|
||||
|
||||
#include <buttons.hpp>
|
||||
#include <cstdint>
|
||||
#include <disp_tools.hpp>
|
||||
#include <disp_tty.hpp>
|
||||
#include <esp_pm.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <power_helper.hpp>
|
||||
#include <shutdowner.hpp>
|
||||
#include <spi_global.hpp>
|
||||
#include "display.hpp"
|
||||
|
||||
#include "bat_mon.hpp"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "i2c_global.hpp"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <memory>
|
||||
#include <power_helper.hpp>
|
||||
#include <shutdowner.hpp>
|
||||
#include <spi_global.hpp>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include <thread>
|
||||
namespace cfg {
|
||||
constexpr int BoardW = 10;
|
||||
constexpr int BoardH = 20;
|
||||
constexpr int CellPx = 12; // 10x20 -> 120x240
|
||||
constexpr int FrameW = 400;
|
||||
constexpr int FrameH = 240;
|
||||
|
||||
#include "GridWindow.hpp"
|
||||
#include "TextWindow.hpp"
|
||||
#include "display.hpp"
|
||||
// game feel
|
||||
constexpr int DropMsStart = 650;
|
||||
constexpr int DropMsMin = 90;
|
||||
constexpr int DropFastMs = 20;
|
||||
constexpr int LevelStepClr = 10;
|
||||
constexpr int LockDelayMs = 380;
|
||||
|
||||
FbTty tty;
|
||||
// input repeat (snappy)
|
||||
constexpr int DAS_ms = 90;
|
||||
constexpr int ARR_ms = 15;
|
||||
|
||||
// frame pacing
|
||||
constexpr int FrameMs = 12;
|
||||
} // namespace cfg
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Interfaces
|
||||
struct IFramebuffer {
|
||||
virtual ~IFramebuffer() = default;
|
||||
virtual void drawPixel(int x, int y, bool on) = 0; // on=true => black
|
||||
virtual void clear(bool on) = 0; // fill full screen to on/off
|
||||
virtual int width() const = 0;
|
||||
virtual int height() const = 0;
|
||||
};
|
||||
|
||||
struct InputState {
|
||||
bool left = false, right = false, down = false, rotate = false, back = false;
|
||||
};
|
||||
struct IInput {
|
||||
virtual ~IInput() = default;
|
||||
virtual InputState readState() = 0;
|
||||
};
|
||||
|
||||
struct IClock {
|
||||
virtual ~IClock() = default;
|
||||
virtual uint32_t millis() = 0;
|
||||
virtual void sleep_ms(uint32_t ms) = 0;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ESP-IDF Adapters (NO inversion here)
|
||||
struct PlatformFramebuffer final : IFramebuffer {
|
||||
int width() const override { return cfg::FrameW; }
|
||||
int height() const override { return cfg::FrameH; }
|
||||
|
||||
void drawPixel(int x, int y, bool on) override {
|
||||
if (x < 0 || y < 0 || x >= width() || y >= height())
|
||||
return;
|
||||
// Logical ON must be BLACK on panel:
|
||||
DispTools::get().set_pixel(x, y, on); // on=true -> black; on=false -> white
|
||||
}
|
||||
|
||||
void clear(bool on) override {
|
||||
// Explicit fill to guarantee polarity: no DispTools::clear() shortcuts.
|
||||
for (int y = 0; y < height(); ++y)
|
||||
for (int x = 0; x < width(); ++x)
|
||||
DispTools::get().set_pixel(x, y, on);
|
||||
}
|
||||
};
|
||||
|
||||
struct PlatformInput final : IInput {
|
||||
InputState readState() override {
|
||||
InputState s{};
|
||||
const uint8_t p = Buttons::get().get_pressed();
|
||||
if (p & BTN_LEFT)
|
||||
s.left = true;
|
||||
if (p & BTN_RIGHT)
|
||||
s.right = true;
|
||||
if (p & BTN_DOWN)
|
||||
s.down = true;
|
||||
if (p & BTN_A)
|
||||
s.rotate = true; // rotate
|
||||
if (p & BTN_B)
|
||||
s.back = true; // pause/back
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
struct PlatformClock final : IClock {
|
||||
uint32_t millis() override {
|
||||
TickType_t t = xTaskGetTickCount();
|
||||
return (uint32_t) ((uint64_t) t * 1000ULL / configTICK_RATE_HZ);
|
||||
}
|
||||
void sleep_ms(uint32_t ms) override {
|
||||
if (ms)
|
||||
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||
}
|
||||
};
|
||||
|
||||
static inline uint32_t elapsed_ms(uint32_t a, uint32_t b) { return b - a; }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Pieces
|
||||
using RotGrid = std::array<char, 16>;
|
||||
using PieceR = std::array<RotGrid, 4>;
|
||||
constexpr char _ = '.', X = '#';
|
||||
|
||||
static const std::array<PieceR, 7> TET = {{
|
||||
PieceR{{RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, _, X, _, _, _, X, _, _, _, X, _, _, _, X, _},
|
||||
RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, _, X, _, _, _, X, _, _, _, X, _, _}}},
|
||||
PieceR{{RotGrid{X, _, _, _, X, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, X, _, _, X, _, _, _, X, _, _, _, _, _, _},
|
||||
RotGrid{_, _, _, _, X, X, X, _, _, _, X, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, _, X, _, _, X, _, _, _, _, _, _, _}}},
|
||||
PieceR{{RotGrid{_, _, X, _, X, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, _, X, _, _, _, X, X, _, _, _, _, _},
|
||||
RotGrid{_, _, _, _, X, X, X, _, X, _, _, _, _, _, _, _},
|
||||
RotGrid{X, X, _, _, _, X, _, _, _, X, _, _, _, _, _, _}}},
|
||||
PieceR{{RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}}},
|
||||
PieceR{{RotGrid{_, X, X, _, X, X, _, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, _, X, X, _, _, _, X, _, _, _, _, _},
|
||||
RotGrid{_, _, _, _, _, X, X, _, X, X, _, _, _, _, _, _},
|
||||
RotGrid{X, _, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}},
|
||||
PieceR{{RotGrid{_, X, _, _, X, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, _, X, X, _, _, X, _, _, _, _, _, _},
|
||||
RotGrid{_, _, _, _, X, X, X, _, _, X, _, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}},
|
||||
PieceR{{RotGrid{X, X, _, _, _, X, X, _, _, _, _, _, _, _, _, _},
|
||||
RotGrid{_, _, X, _, _, X, X, _, _, X, _, _, _, _, _, _},
|
||||
RotGrid{_, _, _, _, X, X, _, _, _, X, X, _, _, _, _, _},
|
||||
RotGrid{_, X, _, _, X, X, _, _, X, _, _, _, _, _, _, _}}},
|
||||
}};
|
||||
|
||||
static inline bool cell_of(const PieceR& p, int rot, int x, int y) { return p[rot][y * 4 + x] == X; }
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Board
|
||||
class Board {
|
||||
public:
|
||||
Board() { clear(); }
|
||||
void clear() { a.fill(0); }
|
||||
uint8_t get(int x, int y) const { return a[y * cfg::BoardW + x]; }
|
||||
void set(int x, int y, uint8_t v) { a[y * cfg::BoardW + x] = v; }
|
||||
|
||||
bool collides(int px, int py, int rot, int pidx) const {
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[pidx], rot, xx, yy)) {
|
||||
int x = px + xx, y = py + yy;
|
||||
if (x < 0 || x >= cfg::BoardW || y >= cfg::BoardH)
|
||||
return true;
|
||||
if (y >= 0 && get(x, y))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void lock(int px, int py, int rot, int pidx) {
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[pidx], rot, xx, yy)) {
|
||||
int x = px + xx, y = py + yy;
|
||||
if (x >= 0 && x < cfg::BoardW && y >= 0 && y < cfg::BoardH)
|
||||
set(x, y, 1);
|
||||
}
|
||||
}
|
||||
int clearLines() {
|
||||
int cleared = 0;
|
||||
for (int y = cfg::BoardH - 1; y >= 0; --y) {
|
||||
bool full = true;
|
||||
for (int x = 0; x < cfg::BoardW; ++x) {
|
||||
if (!get(x, y)) {
|
||||
full = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (full) {
|
||||
++cleared;
|
||||
for (int yy = y; yy > 0; --yy)
|
||||
for (int x = 0; x < cfg::BoardW; ++x)
|
||||
set(x, yy, get(x, yy - 1));
|
||||
for (int x = 0; x < cfg::BoardW; ++x)
|
||||
set(x, 0, 0);
|
||||
++y;
|
||||
}
|
||||
}
|
||||
return cleared;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, cfg::BoardW * cfg::BoardH> a{};
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// 5x7 HUD font — stored with **LSB = top row** so y increases downward.
|
||||
// (This orientation fixes the upside-down issue.)
|
||||
struct Font5x7 {
|
||||
static uint8_t glyph(char c) {
|
||||
switch (c) {
|
||||
case '0':
|
||||
return 0;
|
||||
case '1':
|
||||
return 1;
|
||||
case '2':
|
||||
return 2;
|
||||
case '3':
|
||||
return 3;
|
||||
case '4':
|
||||
return 4;
|
||||
case '5':
|
||||
return 5;
|
||||
case '6':
|
||||
return 6;
|
||||
case '7':
|
||||
return 7;
|
||||
case '8':
|
||||
return 8;
|
||||
case '9':
|
||||
return 9;
|
||||
case 'S':
|
||||
return 10;
|
||||
case 'C':
|
||||
return 11;
|
||||
case 'O':
|
||||
return 12;
|
||||
case 'R':
|
||||
return 13;
|
||||
case 'E':
|
||||
return 14;
|
||||
case 'L':
|
||||
return 15;
|
||||
case 'I':
|
||||
return 16;
|
||||
case 'N':
|
||||
return 17;
|
||||
case 'X':
|
||||
return 18;
|
||||
case 'T':
|
||||
return 19;
|
||||
case 'V':
|
||||
return 20;
|
||||
case 'A':
|
||||
return 21;
|
||||
default:
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
// columns × 7 rows (bit0 = top, bit6 = bottom)
|
||||
static const uint8_t data[22][5];
|
||||
};
|
||||
const uint8_t Font5x7::data[22][5] = {
|
||||
/*0*/ {0b0111110, 0b1000001, 0b1000001, 0b1000001, 0b0111110},
|
||||
/*1*/ {0b0000000, 0b1000010, 0b1111111, 0b1000000, 0b0000000},
|
||||
/*2*/ {0b11100010, 0b10010001, 0b10001001, 0b10001001, 0b10000110},
|
||||
/*3*/ {0b01000010, 0b10000001, 0b10001001, 0b10001001, 0b01110110},
|
||||
/*4*/ {0b00011100, 0b00010010, 0b00010001, 0b11111111, 0b00010000},
|
||||
/*5*/ {0b01001111, 0b10001001, 0b10001001, 0b10001001, 0b01110001},
|
||||
/*6*/ {0b01111110, 0b10001001, 0b10001001, 0b10001001, 0b01110010},
|
||||
/*7*/ {0b00000001, 0b11100001, 0b00010001, 0b00001001, 0b00000111},
|
||||
/*8*/ {0b01110110, 0b10001001, 0b10001001, 0b10001001, 0b01110110},
|
||||
/*9*/ {0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01111110},
|
||||
/*S*/ {0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01110010},
|
||||
/*C*/ {0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01000010},
|
||||
/*O*/ {0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01111110},
|
||||
/*R*/ {0b11111111, 0b00001001, 0b00011001, 0b01101001, 0b10000110},
|
||||
/*E*/ {0b11111111, 0b10001001, 0b10001001, 0b10001001, 0b10000001},
|
||||
/*L*/ {0b11111111, 0b10000000, 0b10000000, 0b10000000, 0b10000000},
|
||||
/*I*/ {0b10000001, 0b10000001, 0b11111111, 0b10000001, 0b10000001},
|
||||
/*N*/ {0b11111111, 0b00000110, 0b00011000, 0b01100000, 0b11111111},
|
||||
/*X*/ {0b11000011, 0b00100100, 0b00011000, 0b00100100, 0b11000011},
|
||||
/*T*/ {0b00000001, 0b00000001, 0b11111111, 0b00000001, 0b00000001},
|
||||
/*V*/ {0b00000111, 0b00111000, 0b11000000, 0b00111000, 0b00000111},
|
||||
/*A*/ {0b11111110, 0b00010001, 0b00010001, 0b00010001, 0b11111110},
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Renderer (centered board + HUD)
|
||||
class Renderer {
|
||||
public:
|
||||
Renderer(IFramebuffer& fb) : fb(fb) {
|
||||
bw = cfg::BoardW * cfg::CellPx; // 120
|
||||
bh = cfg::BoardH * cfg::CellPx; // 240
|
||||
ox = (fb.width() - bw) / 2; // 140
|
||||
oy = (fb.height() - bh) / 2; // 0
|
||||
}
|
||||
|
||||
void render(const Board& b, int px, int py, int prot, int pidx, int score, int level, int lines, int nextIdx,
|
||||
bool ghost) {
|
||||
fb.clear(false); // white bg (off)
|
||||
|
||||
// playfield frame
|
||||
rect(ox - 2, oy - 2, bw + 4, bh + 4, true);
|
||||
|
||||
// settled
|
||||
for (int y = 0; y < cfg::BoardH; ++y)
|
||||
for (int x = 0; x < cfg::BoardW; ++x)
|
||||
if (b.get(x, y))
|
||||
drawCell(x, y, true);
|
||||
|
||||
// ghost
|
||||
if (ghost)
|
||||
drawGhost(b, px, py, prot, pidx);
|
||||
|
||||
// active
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[pidx], prot, xx, yy)) {
|
||||
int gx = px + xx, gy = py + yy;
|
||||
if (gx >= 0 && gx < cfg::BoardW && gy >= 0 && gy < cfg::BoardH)
|
||||
drawCell(gx, gy, true);
|
||||
}
|
||||
|
||||
// HUD (right side)
|
||||
int hudX = ox + bw + 16;
|
||||
int y = oy + 9; // +1 padding to avoid 1px visual cutoff
|
||||
drawLabel(hudX, y, "SCORE");
|
||||
y += 12;
|
||||
drawNumber(hudX, y, score);
|
||||
y += 19;
|
||||
drawLabel(hudX, y, "LEVEL");
|
||||
y += 12;
|
||||
drawNumber(hudX, y, level);
|
||||
y += 19;
|
||||
drawLabel(hudX, y, "LINES");
|
||||
y += 12;
|
||||
drawNumber(hudX, y, lines);
|
||||
y += 19;
|
||||
drawLabel(hudX, y, "NEXT");
|
||||
y += 7;
|
||||
|
||||
const int p = cfg::CellPx;
|
||||
const int nx = hudX, ny = y + 4;
|
||||
rect(nx - 2, ny - 2, 4 * p + 4, 4 * p + 4, true);
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[nextIdx], 0, xx, yy))
|
||||
fillRect(nx + xx * p, ny + yy * p, p, p, true);
|
||||
|
||||
// removed surrounding HUD border rectangle that caused panel outline
|
||||
// rect(hudX - 8, oy, cfg::FrameW - (hudX - 8) - 8, cfg::FrameH - oy * 2, true);
|
||||
|
||||
DispTools::get().draw_to_display();
|
||||
}
|
||||
|
||||
private:
|
||||
IFramebuffer& fb;
|
||||
int ox = 0, oy = 0, bw = 0, bh = 0;
|
||||
|
||||
void putPixel(int x, int y, bool on) { fb.drawPixel(x, y, on); }
|
||||
void hline(int x, int y, int w, bool on) {
|
||||
for (int i = 0; i < w; ++i)
|
||||
putPixel(x + i, y, on);
|
||||
}
|
||||
void vline(int x, int y, int h, bool on) {
|
||||
for (int i = 0; i < h; ++i)
|
||||
putPixel(x, y + i, on);
|
||||
}
|
||||
void rect(int x, int y, int w, int h, bool on) {
|
||||
hline(x, y, w, on);
|
||||
hline(x, y + h - 1, w, on);
|
||||
vline(x, y, h, on);
|
||||
vline(x + w - 1, y, h, on);
|
||||
}
|
||||
void fillRect(int x, int y, int w, int h, bool on) {
|
||||
for (int yy = 0; yy < h; ++yy)
|
||||
for (int xx = 0; xx < w; ++xx)
|
||||
putPixel(x + xx, y + yy, on);
|
||||
}
|
||||
|
||||
void drawCell(int cx, int cy, bool on) {
|
||||
int x0 = ox + cx * cfg::CellPx;
|
||||
int y0 = oy + cy * cfg::CellPx;
|
||||
fillRect(x0, y0, cfg::CellPx, cfg::CellPx, on);
|
||||
}
|
||||
|
||||
void drawGhost(const Board& b, int px, int py, int prot, int pidx) {
|
||||
int gy = py;
|
||||
while (true) {
|
||||
bool col = false;
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[pidx], prot, xx, yy)) {
|
||||
int nx = px + xx, ny = gy + yy + 1;
|
||||
if (ny >= cfg::BoardH || (ny >= 0 && nx >= 0 && nx < cfg::BoardW && b.get(nx, ny)))
|
||||
col = true;
|
||||
}
|
||||
if (col)
|
||||
break;
|
||||
++gy;
|
||||
}
|
||||
for (int yy = 0; yy < 4; ++yy)
|
||||
for (int xx = 0; xx < 4; ++xx)
|
||||
if (cell_of(TET[pidx], prot, xx, yy)) {
|
||||
int gx = px + xx, gy2 = gy + yy;
|
||||
if (gx < 0 || gx >= cfg::BoardW || gy2 < 0 || gy2 >= cfg::BoardH)
|
||||
continue;
|
||||
int x0 = ox + gx * cfg::CellPx, y0 = oy + gy2 * cfg::CellPx;
|
||||
rect(x0, y0, cfg::CellPx, cfg::CellPx, true);
|
||||
}
|
||||
}
|
||||
|
||||
// 5x7 text — LSB = top, so rows draw y..y+6. No baseline offset needed.
|
||||
void drawChar5x7(int x, int y, char c, bool on) {
|
||||
uint8_t gi = Font5x7::glyph(c);
|
||||
if (gi == 255)
|
||||
return;
|
||||
for (int col = 0; col < 5; ++col) {
|
||||
uint8_t bits = Font5x7::data[gi][col];
|
||||
for (int row = 0; row < 8; row++) {
|
||||
if (bits & (1u << row)) {
|
||||
putPixel(x + col, y + row, on);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void drawLabel(int x, int y, const char* s) {
|
||||
for (int i = 0; s[i]; ++i)
|
||||
drawChar5x7(x + i * 6, y, s[i], true);
|
||||
}
|
||||
void drawNumber(int x, int y, int n) {
|
||||
char buf[16];
|
||||
int len = snprintf(buf, sizeof(buf), "%d", n);
|
||||
for (int i = 0; i < len; ++i)
|
||||
drawChar5x7(x + i * 6, y, buf[i], true);
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Bag
|
||||
class Bag {
|
||||
public:
|
||||
Bag() : rng(std::random_device{}()) { refill(); }
|
||||
int next() {
|
||||
if (bag.empty())
|
||||
refill();
|
||||
int t = bag.back();
|
||||
bag.pop_back();
|
||||
return t;
|
||||
}
|
||||
|
||||
private:
|
||||
void refill() {
|
||||
bag = {0, 1, 2, 3, 4, 5, 6};
|
||||
std::shuffle(bag.begin(), bag.end(), rng);
|
||||
}
|
||||
std::vector<int> bag;
|
||||
std::mt19937 rng;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Game
|
||||
struct ScoreState {
|
||||
int level = 0, score = 0, lines = 0, dropMs = cfg::DropMsStart;
|
||||
};
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game(IFramebuffer& fb, IInput& in, IClock& clk) : fb(fb), input(in), clock(clk), renderer(fb) {
|
||||
nextPiece = bag.next();
|
||||
spawn();
|
||||
lastFall = clock.millis();
|
||||
touchTime = lastFall;
|
||||
}
|
||||
|
||||
void step() {
|
||||
if (!running) {
|
||||
renderEndOnce();
|
||||
return;
|
||||
}
|
||||
const uint32_t now = clock.millis();
|
||||
InputState st = input.readState();
|
||||
|
||||
if (st.back && !backPrev)
|
||||
paused = !paused;
|
||||
backPrev = st.back;
|
||||
if (paused) {
|
||||
paintHUD();
|
||||
return;
|
||||
}
|
||||
|
||||
if (st.rotate && !rotPrev)
|
||||
tryRotate(+1);
|
||||
rotPrev = st.rotate;
|
||||
|
||||
handleHorizontal(st, now);
|
||||
|
||||
const int g = st.down ? cfg::DropFastMs : score.dropMs;
|
||||
if (elapsed_ms(lastFall, now) >= (uint32_t) g) {
|
||||
lastFall = now;
|
||||
if (!tryMove(0, 1)) {
|
||||
if (!touchingGround) {
|
||||
touchingGround = true;
|
||||
touchTime = now;
|
||||
} else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) {
|
||||
lockAndAdvance();
|
||||
touchingGround = false;
|
||||
}
|
||||
} else
|
||||
touchingGround = false;
|
||||
}
|
||||
|
||||
paintHUD();
|
||||
}
|
||||
|
||||
private:
|
||||
IFramebuffer& fb;
|
||||
IInput& input;
|
||||
IClock& clock;
|
||||
Renderer renderer;
|
||||
Board board;
|
||||
Bag bag;
|
||||
ScoreState score;
|
||||
bool running = true, paused = false, touchingGround = false;
|
||||
bool rotPrev = false, lHeld = false, rHeld = false, backPrev = false;
|
||||
uint32_t lHoldStart = 0, rHoldStart = 0, lLastRep = 0, rLastRep = 0, lastFall = 0, touchTime = 0;
|
||||
int current = 0, nextPiece = 0, px = 3, py = -2, rot = 0;
|
||||
|
||||
void paintHUD() {
|
||||
renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true);
|
||||
}
|
||||
|
||||
void renderEndOnce() {
|
||||
fb.clear(true);
|
||||
DispTools::get().draw_to_display();
|
||||
clock.sleep_ms(120);
|
||||
fb.clear(false);
|
||||
DispTools::get().draw_to_display();
|
||||
clock.sleep_ms(120);
|
||||
paintHUD();
|
||||
}
|
||||
|
||||
bool tryMove(int dx, int dy) {
|
||||
if (!board.collides(px + dx, py + dy, rot, current)) {
|
||||
px += dx;
|
||||
py += dy;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool tryRotate(int d) {
|
||||
int nr = (rot + d + 4) % 4;
|
||||
static const int kicks[][2] = {{0, 0}, {-1, 0}, {1, 0}, {-2, 0}, {2, 0}, {0, -1}};
|
||||
for (auto& k: kicks)
|
||||
if (!board.collides(px + k[0], py + k[1], nr, current)) {
|
||||
px += k[0];
|
||||
py += k[1];
|
||||
rot = nr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void handleHorizontal(const InputState& st, uint32_t now) {
|
||||
if (st.left && st.right) {
|
||||
lHeld = rHeld = false;
|
||||
return;
|
||||
}
|
||||
if (st.left) {
|
||||
if (!lHeld) {
|
||||
lHeld = true;
|
||||
rHeld = false;
|
||||
tryMove(-1, 0);
|
||||
lHoldStart = now;
|
||||
lLastRep = now;
|
||||
} else {
|
||||
uint32_t s = elapsed_ms(lHoldStart, now), r = elapsed_ms(lLastRep, now);
|
||||
if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) {
|
||||
(void) tryMove(-1, 0);
|
||||
lLastRep = now;
|
||||
}
|
||||
}
|
||||
} else
|
||||
lHeld = false;
|
||||
if (st.right) {
|
||||
if (!rHeld) {
|
||||
rHeld = true;
|
||||
lHeld = false;
|
||||
tryMove(+1, 0);
|
||||
rHoldStart = now;
|
||||
rLastRep = now;
|
||||
} else {
|
||||
uint32_t s = elapsed_ms(rHoldStart, now), r = elapsed_ms(rLastRep, now);
|
||||
if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) {
|
||||
(void) tryMove(+1, 0);
|
||||
rLastRep = now;
|
||||
}
|
||||
}
|
||||
} else
|
||||
rHeld = false;
|
||||
}
|
||||
void lockAndAdvance() {
|
||||
board.lock(px, py, rot, current);
|
||||
int c = board.clearLines();
|
||||
if (c) {
|
||||
static const int pts[5] = {0, 100, 300, 500, 800};
|
||||
score.lines += c;
|
||||
score.score += pts[c] * (score.level + 1);
|
||||
int nl = score.lines / cfg::LevelStepClr;
|
||||
if (nl != score.level) {
|
||||
score.level = nl;
|
||||
score.dropMs = std::max(cfg::DropMsMin, cfg::DropMsStart - score.level * 50);
|
||||
}
|
||||
}
|
||||
spawn();
|
||||
}
|
||||
void spawn() {
|
||||
current = nextPiece;
|
||||
nextPiece = bag.next();
|
||||
px = 3;
|
||||
py = -2;
|
||||
rot = 0;
|
||||
if (board.collides(px, py, rot, current))
|
||||
running = false;
|
||||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// App
|
||||
class App {
|
||||
public:
|
||||
App(IFramebuffer& fb, IInput& in, IClock& clk) : fb(fb), input(in), clock(clk) {
|
||||
game = new Game(fb, input, clock);
|
||||
}
|
||||
~App() { delete game; }
|
||||
void runForever() {
|
||||
uint32_t last = clock.millis();
|
||||
while (true) {
|
||||
game->step();
|
||||
uint32_t now = clock.millis();
|
||||
uint32_t dt = elapsed_ms(last, now);
|
||||
if (dt < (uint32_t) cfg::FrameMs)
|
||||
clock.sleep_ms((uint32_t) cfg::FrameMs - dt);
|
||||
last = clock.millis();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
IFramebuffer& fb;
|
||||
IInput& input;
|
||||
IClock& clock;
|
||||
Game* game = nullptr;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Entry
|
||||
extern "C" void app_main() {
|
||||
esp_pm_config_t pm_config = {
|
||||
.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, .min_freq_mhz = 16, .light_sleep_enable = true};
|
||||
// ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
|
||||
printf("Hello world!\n");
|
||||
// TODO: Where to put that?
|
||||
ESP_ERROR_CHECK(esp_sleep_enable_gpio_wakeup());
|
||||
// For some reason, calling it here hangs on startup, sometimes
|
||||
// ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
PowerHelper::get();
|
||||
Shutdowner::get();
|
||||
Buttons::get();
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
gpio_install_isr_service(0);
|
||||
Shutdowner::get().install_isr();
|
||||
PowerHelper::get().install_isr();
|
||||
Buttons::get().install_isr();
|
||||
I2cGlobal::get();
|
||||
BatMon::get();
|
||||
SpiGlobal::get();
|
||||
SMD::get();
|
||||
SMD::get().clear();
|
||||
DispTools::get().clear();
|
||||
// DispTools::get().draw_line(0, 0, 399, 239);
|
||||
// DispTools::get().draw_circle(100, 100, 20);
|
||||
DispTools::get().draw_to_display();
|
||||
tty.putstr("Hello\nworld!");
|
||||
DispTools::get().draw_to_display();
|
||||
|
||||
int rx = 30, ry = 30;
|
||||
static PlatformFramebuffer fb;
|
||||
static PlatformInput input;
|
||||
static PlatformClock clock;
|
||||
|
||||
int lastmove = 0;
|
||||
|
||||
EventLoop loop;
|
||||
SMDSurface surface(&loop);
|
||||
|
||||
surface.set_window<GridWindow<SMDSurface, 2, 2>>();
|
||||
|
||||
GridWindow<SMDSurface, 2, 2>* window =
|
||||
static_cast<GridWindow<SMDSurface, 2, 2>*>(surface.get_window());
|
||||
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(0, 0, &loop, "hello");
|
||||
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(0, 1, &loop, "hello1");
|
||||
window->set_window<GridWindow<SubSurface<SMDSurface>, 2, 2>>(1, 0);
|
||||
GridWindow<SubSurface<SMDSurface>, 2, 2>* window2 =
|
||||
static_cast<GridWindow<SubSurface<SMDSurface>, 2, 2>*>(
|
||||
window->get_subsurface(1, 0).get_window());
|
||||
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(1, 1, &loop, "hello3");
|
||||
|
||||
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
|
||||
0, 0, &loop, "hello2");
|
||||
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
|
||||
0, 1, &loop, "hello4");
|
||||
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
|
||||
1, 0, &loop, "hello5");
|
||||
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
|
||||
1, 1, &loop, "hello6");
|
||||
|
||||
auto* tl_text = static_cast<TextWindow<SubSurface<SMDSurface>, std::string>*>(
|
||||
window->get_subsurface(0, 0).get_window());
|
||||
|
||||
surface.handle(SurfaceResizeEvent{DISP_WIDTH, DISP_HEIGHT});
|
||||
std::thread loop_thread{[&] { loop.run([&] { DispTools::get().draw_to_display(); }); }};
|
||||
|
||||
uint8_t old_pressed = 0;
|
||||
|
||||
while (true) {
|
||||
// SMD::clear();
|
||||
// printf("Voltage: %f\n", BatMon::get_voltage());
|
||||
// DispTools::get().clear();
|
||||
// tty.reset();
|
||||
|
||||
uint8_t pressed = Buttons::get().get_pressed();
|
||||
if ((pressed & BTN_LEFT) && !(old_pressed & BTN_LEFT))
|
||||
surface.push(KeyboardEvent{Key::Left});
|
||||
if ((pressed & BTN_DOWN) && !(old_pressed & BTN_DOWN))
|
||||
surface.push(KeyboardEvent{Key::Down});
|
||||
if ((pressed & BTN_UP) && !(old_pressed & BTN_UP))
|
||||
surface.push(KeyboardEvent{Key::Up});
|
||||
if ((pressed & BTN_RIGHT) && !(old_pressed & BTN_RIGHT))
|
||||
surface.push(KeyboardEvent{Key::Right});
|
||||
if ((pressed & BTN_SELECT) && !(old_pressed & BTN_SELECT))
|
||||
surface.push(KeyboardEvent{Key::Escape});
|
||||
if ((pressed & BTN_START) && !(old_pressed & BTN_START))
|
||||
surface.push(KeyboardEvent{Key::Enter});
|
||||
|
||||
old_pressed = pressed;
|
||||
|
||||
if (pressed == 0 && !PowerHelper::get().is_slow())
|
||||
lastmove++;
|
||||
else if (pressed != 0) {
|
||||
lastmove = 0;
|
||||
PowerHelper::get().set_slow(false);
|
||||
}
|
||||
|
||||
if (lastmove > 20) {
|
||||
lastmove = 0;
|
||||
PowerHelper::get().set_slow(true);
|
||||
}
|
||||
|
||||
bool slow = PowerHelper::get().is_slow();
|
||||
tl_text->push(TextUpdateEvent<std::string>(std::format("{:.1f}mA {:.1f}V {:.1f}mAh {}\n Buttons: {:08b}",
|
||||
BatMon::get().get_current(), BatMon::get().get_voltage(),
|
||||
BatMon::get().get_charge(), slow ? "S" : "", pressed)));
|
||||
|
||||
// if (rx < 30)
|
||||
// rx = 30;
|
||||
// if (rx > 370)
|
||||
// rx = 370;
|
||||
// if (ry < 30)
|
||||
// ry = 30;
|
||||
// if (ry > 210)
|
||||
// ry = 210;
|
||||
// // tty.fmt("Button: {}", pressed);
|
||||
// DispTools::get().draw_circle(rx, ry, 20);
|
||||
// // printf("Restarting in %d seconds...\n", i);
|
||||
// DispTools::get().draw_to_display();
|
||||
PowerHelper::get().delay(10000, 30);
|
||||
}
|
||||
// printf("Restarting now.\n");
|
||||
// fflush(stdout);
|
||||
// esp_restart();
|
||||
loop_thread.join();
|
||||
static App* app = nullptr;
|
||||
app = new App(fb, input, clock);
|
||||
app->runForever();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user