初始化提交

This commit is contained in:
王立帮
2024-07-20 22:09:06 +08:00
commit c247dd07a6
6876 changed files with 2743096 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
MD_MAX72XX::fontType_t _Fixed_5x3[] PROGMEM = {
'F', 1, 32, 127, 5,
2, 0, 0, // 32 - 'Space'
1, 23, // 33 - '!'
3, 3, 0, 3, // 34 - '"'
3, 31, 10, 31, // 35 - '#'
3, 22, 31, 13, // 36 - '$'
3, 9, 4, 18, // 37 - '%'
3, 10, 21, 26, // 38 - '&'
1, 3, // 39
2, 14, 17, // 40 - '('
2, 17, 14, // 41 - ')'
3, 10, 4, 10, // 42 - '*'
3, 4, 14, 4, // 43 - '+'
2, 16, 8, // 44 - ','
3, 4, 4, 4, // 45 - '-'
1, 16, // 46 - '.'
3, 8, 4, 2, // 47 - '/'
3, 31, 17, 31, // 48 - '0'
2, 0, 31, // 49 - '1'
3, 29, 21, 23, // 50 - '2'
3, 17, 21, 31, // 51 - '3'
3, 7, 4, 31, // 52 - '4'
3, 23, 21, 29, // 53 - '5'
3, 31, 21, 29, // 54 - '6'
3, 1, 1, 31, // 55 - '7'
3, 31, 21, 31, // 56 - '8'
3, 23, 21, 31, // 57 - '9'
1, 10, // 58 - ':'
2, 16, 10, // 59 - ';'
3, 4, 10, 17, // 60 - '<'
3, 10, 10, 10, // 61 - '='
3, 17, 10, 4, // 62 - '>'
3, 1, 21, 3, // 63 - '?'
3, 14, 21, 22, // 64 - '@'
3, 30, 5, 30, // 65 - 'A'
3, 31, 21, 10, // 66 - 'B'
3, 14, 17, 17, // 67 - 'C'
3, 31, 17, 14, // 68 - 'D'
3, 31, 21, 17, // 69 - 'E'
3, 31, 5, 1, // 70 - 'F'
3, 14, 17, 29, // 71 - 'G'
3, 31, 4, 31, // 72 - 'H'
3, 17, 31, 17, // 73 - 'I'
3, 8, 16, 15, // 74 - 'J'
3, 31, 4, 27, // 75 - 'K'
3, 31, 16, 16, // 76 - 'L'
3, 31, 2, 31, // 77 - 'M'
3, 31, 14, 31, // 78 - 'N'
3, 14, 17, 14, // 79 - 'O'
3, 31, 5, 2, // 80 - 'P'
3, 14, 25, 30, // 81 - 'Q'
3, 31, 5, 26, // 82 - 'R'
3, 18, 21, 9, // 83 - 'S'
3, 1, 31, 1, // 84 - 'T'
3, 15, 16, 15, // 85 - 'U'
3, 7, 24, 7, // 86 - 'V'
3, 15, 28, 15, // 87 - 'W'
3, 27, 4, 27, // 88 - 'X'
3, 3, 28, 3, // 89 - 'Y'
3, 25, 21, 19, // 90 - 'Z'
2, 31, 17, // 91 - '['
3, 2, 4, 8, // 92 - '\'
2, 17, 31, // 93 - ']'
3, 2, 1, 2, // 94 - '^'
3, 16, 16, 16, // 95 - '_'
2, 1, 2, // 96 - '`'
3, 12, 18, 28, // 97 - 'a'
3, 31, 18, 12, // 98 - 'b'
3, 12, 18, 18, // 99 - 'c'
3, 12, 18, 31, // 100 - 'd'
3, 12, 26, 20, // 101 - 'e'
3, 4, 31, 5, // 102 - 'f'
3, 20, 26, 12, // 103 - 'g'
3, 31, 2, 28, // 104 - 'h'
1, 29, // 105 - 'i'
2, 16, 13, // 106 - 'j'
3, 31, 8, 20, // 107 - 'k'
1, 31, // 108 - 'l'
3, 30, 6, 30, // 109 - 'm'
3, 30, 2, 28, // 110 - 'n'
3, 12, 18, 12, // 111 - 'o'
3, 30, 10, 4, // 112 - 'p'
3, 4, 10, 30, // 113 - 'q'
2, 30, 4, // 114 - 'r'
3, 20, 30, 10, // 115 - 's'
3, 4, 30, 4, // 116 - 't'
3, 14, 16, 30, // 117 - 'u'
3, 14, 16, 14, // 118 - 'v'
3, 14, 24, 14, // 119 - 'w'
3, 18, 12, 18, // 120 - 'x'
3, 22, 24, 14, // 121 - 'y'
3, 26, 30, 22, // 122 - 'z'
3, 4, 27, 17, // 123 - '{'
1, 27, // 124 - '|'
3, 17, 27, 4, // 125 - '}'
3, 6, 2, 3, // 126 - '~'
3, 31, 31, 31, // 127 - 'Full Block'
};

View File

@@ -0,0 +1,694 @@
// Implements the game of Tetris using MD_MAXPanel library
// Tips for coding this taken from https://www.youtube.com/watch?v=8OK8_tHeCIA
//
// Hardware used
// =============
//
// LEFT_PIN - move left switch, INPUT_PULLUP
// RIGHT_PIN - move right switch, INPUT_PULLUP
// UP_PIN - unused
// DOWN_PIN - drop the piece switch, INPUT_PULLUP
// SELECT_PIN - rotate the piece switch, INPUT_PULLUP
// ENTER_PIN - unused
// BEEPER_PIN - piezo speaker
// CLK_PIN, DATA_PIN, CS_PIN - LED matrix display connections
//
// Libraries used
// ==============
// MD_MAX72XX available from https://github.com/MajicDesigns/MD_MAX72XX
//
// Rules of the Game
// =================
// Game pieces are shaped like tetrominoes, geometric shapes composed of four
// square blocks each. A random sequence of tetrominoes fall down the playing
// field (a rectangular vertical shaft, called the "well" or "matrix"). The
// objective of the game is to manipulate these tetrominoes, by moving each one
// sideways (if the player feels the need) and rotating it by 90 degree units,
// with the aim of creating a horizontal line of ten units without gaps. When
// such a line is created, it gets destroyed, and any block above the deleted
// line will fall. When a certain number of lines are cleared, the game enters
// a new level. As the game progresses, each level causes the tetrominoes to fall
// faster, and the game ends when the stack of tetrominoes reaches the top of
// the playing field and no new tetrominos are able to enter.
#include <MD_MAXPanel.h>
#include "Font5x3.h"
#include "score.h"
#include "sound.h"
#include "randomseed.h"
// Turn on debug statements to the serial output
#define DEBUG 0
#if DEBUG
#define PRINT(s, x) { Serial.print(F(s)); Serial.print(x); }
#define PRINTS(x) { Serial.print(F(x)); }
#define PRINTD(x) { Serial.print(x, DEC); }
#define PRINTXY(s,x,y) { Serial.print(F(s)); Serial.print(F("(")); Serial.print(x); Serial.print(F(",")); Serial.print(y); Serial.print(")"); }
#define PRINTSTATE(s) { Serial.print("\n++>"); Serial.print(s); }
#else
#define PRINT(s, x)
#define PRINTS(x)
#define PRINTD(x)
#define PRINTXY(s,x,y)
#define PRINTSTATE(s)
#endif
// Hardware pin definitions.
// All momentary on switches are initialized PULLUP
const uint8_t UP_PIN = 2;
const uint8_t RIGHT_PIN = 3;
const uint8_t DOWN_PIN = 4;
const uint8_t LEFT_PIN = 5;
const uint8_t SELECT_PIN = 6;
const uint8_t ENTER_PIN = 7;
const uint8_t BEEPER_PIN = 9;
// Define the number of devices in the chain and the SPI hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
const MD_MAX72XX::moduleType_t HARDWARE_TYPE = MD_MAX72XX::FC16_HW;
const uint8_t X_DEVICES = 4;
const uint8_t Y_DEVICES = 5;
const uint8_t CLK_PIN = 12; // or SCK
const uint8_t DATA_PIN = 11; // or MOSI
const uint8_t CS_PIN = 10; // or SS
// SPI hardware interface
MD_MAXPanel mp = MD_MAXPanel(HARDWARE_TYPE, CS_PIN, X_DEVICES, Y_DEVICES);
// Arbitrary pins
// MD_MAXPanel mx = MD_MAXPanel(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, X_DEVICES, Y_DEVICES);
const uint16_t FIELD_WIDTH = 10; // playable area width inside 'bucket'
const uint16_t FIELD_HEIGHT = 21; // playable area depth inside 'bucket'
const uint16_t FIELD_BOTTOM = 3; // y coord bottom wall
const uint16_t FIELD_LEFT = 5; // x coord left wall
const uint16_t FIELD_TOP = FIELD_BOTTOM + FIELD_HEIGHT; // y coord top opening of the 'bucket'
const uint16_t FIELD_RIGHT = FIELD_LEFT + FIELD_WIDTH + 1; // x coord right wall
const char TITLE_TEXT[] = "TETRIS";
const uint16_t SPLASH_DELAY = 3000; // in milliseconds
const char GAME_TEXT[] = "GAME";
const char OVER_TEXT[] = "OVER";
const uint16_t GAME_OVER_DELAY = 3000; // in milliseconds
const uint8_t FONT_NUM_WIDTH = 3;
const uint16_t MAX_SCORE = 60000;
const uint16_t PIECE_SCORE = 5;
const uint16_t LINE_SCORE = 20;
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
// A class to encapsulate the snake direction switches
// Can move up, down, rotate, drop
class cMoveSW
{
private:
uint8_t _pinLeft;
uint8_t _pinRight;
uint8_t _pinRotate;
uint8_t _pinDrop;
uint16_t _timeDelay; // the delay between switch detection
uint32_t _timeLastCheck; // the millis() value for the last time we checked
public:
enum moveType_t { MOVE_NONE, MOVE_LEFT, MOVE_RIGHT, MOVE_DROP, MOVE_ROTATE };
void begin(uint8_t pinL, uint8_t pinR, uint8_t pinRot, uint8_t pinD)
{
_timeDelay = 100;
_pinLeft = pinL;
_pinRight = pinR;
_pinRotate = pinRot;
_pinDrop = pinD;
_timeLastCheck = 0;
pinMode(_pinLeft, INPUT_PULLUP);
pinMode(_pinRight, INPUT_PULLUP);
pinMode(_pinRotate, INPUT_PULLUP);
pinMode(_pinDrop, INPUT_PULLUP);
}
bool anyKey(void) { return (move() != MOVE_NONE);}
moveType_t move(void)
{
if (millis() - _timeLastCheck < _timeDelay)
return(MOVE_NONE);
_timeLastCheck = millis();
if (digitalRead(_pinLeft) == LOW)
return(MOVE_LEFT);
else if (digitalRead(_pinRight) == LOW)
return(MOVE_RIGHT);
else if (digitalRead(_pinRotate) == LOW)
return(MOVE_ROTATE);
else if (digitalRead(_pinDrop) == LOW)
return(MOVE_DROP);
return(MOVE_NONE);
}
};
// A class to encapsulate the Tetris field
class cTetris
{
private:
const uint8_t OMINO_SIZE = 4; // 4x4 field flattened out into 16 bits
const uint8_t MAX_ROTATE = 4; // maximum number of rotations
uint16_t _tetromino[7]; // tetronimoes, values initialized in the constructor
bool _field[FIELD_WIDTH][FIELD_HEIGHT];
uint32_t _timeLastMove; // last time the snake was moved
uint16_t _moveDelay; // the delay between moves in milliseconds
bool _run; // game is playing when true
int8_t _pieceCount; // number of pieces completed
uint8_t _curOmino; // current tetronimo
uint8_t _nxtOmino; // the next tetronimo
uint8_t _curRotation; // current rotated orientation
uint16_t _x, _y; // _field coordinates for the piece
cScore *_pScore; // for keeping score
cSound *_pSound; // for making noise
void dumpOmino(uint8_t omino, uint8_t rot)
// for debugging only
{
PRINT("\n -- DUMPOMINO ", omino); PRINT(" R:", rot);
for (int8_t j = 0; j < OMINO_SIZE; j++)
{
PRINT("\n L", j); PRINTS(": ");
for (int8_t i = 0; i < OMINO_SIZE; i++)
{
// Get index into piece
int8_t idx = rotate(i, j, rot);
// draw the point
if ((_tetromino[omino] >> idx) & 1) { PRINTS(" 1") }
else { PRINTS(" 0") }
}
}
}
void showOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y, bool b)
// show the tetronimo on the actual display
// x and y need to be display coordinates
{
//PRINT("\n-- SHOW ", omino);
//PRINTXY(" @", x, y);
//PRINT(" b=", b);
mp.update(false);
//dumpOmino(omino, rot);
for (int8_t j = 0; j < OMINO_SIZE; j++)
for (int8_t i = 0; i < OMINO_SIZE; i++)
{
// Get index into piece
int8_t idx = rotate(i, j, rot);
// draw the point
//PRINTXY(" ", FIELD_LEFT + x + i, FIELD_TOP - y - j);
//PRINT(" i", idx);
//PRINT(" v", (_tetromino[omino] >> idx) & 1);
if ((_tetromino[omino] >> idx) & 1)
mp.setPoint(x + i, y - j, b);
}
mp.update(true);
}
void drawNxtOmino(void) { showOmino(_nxtOmino, 0, FIELD_RIGHT + OMINO_SIZE, FIELD_TOP - (2*OMINO_SIZE), true); }
void eraseNxtOmino(void) { showOmino(_nxtOmino, 0, FIELD_RIGHT + OMINO_SIZE, FIELD_TOP - (2*OMINO_SIZE), false); }
void drawOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y) { showOmino(omino, rot, FIELD_LEFT + x + 1, FIELD_TOP - y, true); }
void eraseOmino(uint8_t omino, uint8_t rot, int16_t x, int16_t y) { showOmino(omino, rot, FIELD_LEFT + x + 1, FIELD_TOP - y, false); }
void omino2Field(uint8_t omino, uint8_t rot, int16_t x, int16_t y)
// copy the tetronimo to the field, leaving the rest untouched
{
for (int8_t j = 0; j < OMINO_SIZE; j++)
{
for (int8_t i = 0; i < OMINO_SIZE; i++)
{
// Get index into piece
int8_t idx = rotate(i, j, rot);
// set in field if set in tetronimo
if ((_tetromino[omino] >> idx) & 1)
_field[x + i][y + j] = true;
}
}
}
void displayField(void)
// display the field, mindful of offsets in displayable field
{
mp.update(false);
for (uint8_t j = 0; j < FIELD_HEIGHT; j++)
for (uint8_t i = 0; i < FIELD_WIDTH; i++)
mp.setPoint(FIELD_LEFT+i+1, FIELD_TOP-j, _field[i][j]);
mp.update(true);
}
void clearField(void)
// clear the playing field
{
memset(_field, 0, sizeof(bool)*FIELD_HEIGHT*FIELD_WIDTH);
displayField();
}
uint8_t rotate(uint8_t x, uint8_t y, uint8_t r)
// return the linear index for from the x, y coords and the
// current rotation
{
uint8_t idx = 0;
switch (r) // Rotation effect
{
case 0: // 0 degrees // 0 1 2 3
idx = y * 4 + x; // 4 5 6 7
break; // 8 9 10 11
//12 13 14 15
case 1: // 90 degrees //12 8 4 0
idx = 12 + y - (x * 4); //13 9 5 1
break; //14 10 6 2
//15 11 7 3
case 2: // 180 degrees //15 14 13 12
idx = 15 - (y * 4) - x; //11 10 9 8
break; // 7 6 5 4
// 3 2 1 0
case 3: // 270 degrees // 3 7 11 15
idx = 3 - y + (x * 4); // 2 6 10 14
break; // 1 5 9 13
} // 0 4 8 12
return(idx);
}
bool checkPieceFit(uint8_t omino, uint8_t rot, int16_t x, int16_t y)
// does the piece fit in the _field array?
{
/*
PRINTXY("\n-- CHKFIT @", x, y);
PRINT(" T:", omino);
PRINT(" R:", rot);
PRINT(" H:", FIELD_HEIGHT);
PRINT(" W:", FIELD_WIDTH);
*/
for (int8_t j = 0; j < OMINO_SIZE; j++)
{
for (int8_t i = 0; i < OMINO_SIZE; i++)
{
// Get index into piece
int8_t idx = rotate(i, j, rot);
bool bCell = (_tetromino[omino] >> idx) & 1;
if (bCell) // we have an occupied cell in the tetromino
{
// PRINTXY("\n", i, j); PRINTXY("=>", x + i, y + j); PRINT(" i:", idx); PRINT(" cell: ", bCell);
// check it is in bounds
if (x + i < 0 || x + i >= FIELD_WIDTH)
{
//PRINTS(" fail x");
return(false); // out of side bounds
}
else if (y + j < 0 || y + j >= FIELD_HEIGHT)
{
//PRINTS(" fail y");
return(false); // out of bounds
}
else if (_field[x + i][y + j]) // in bounds - do a collision check
{
//PRINTS(" fail field");
return(false); // occupied field cell
}
}
}
}
//PRINTS(" pass");
return(true);
}
public:
enum moveType_t { M_LEFT, M_RIGHT, M_DROP, M_ROTATE };
cTetris(void)
{
// straight block
// 0010
// 0010
// 0010
// 0010
_tetromino[0] = 0x2222;
// T block
// 0010
// 0110
// 0010
// 0000
_tetromino[1] = 0x2620;
// square block
// 0000
// 0110
// 0110
// 0000
_tetromino[2] = 0x0660;
// normal Z block
// 0010
// 0110
// 0100
// 0000
_tetromino[3] = 0x2640;
// reversed Z block
// 0100
// 0110
// 0010
// 0000
_tetromino[4] = 0x4620;
// normal L block
// 0100
// 0100
// 0110
_tetromino[5] = 0x4460;
// reversed L block
// 0010
// 0010
// 0110
_tetromino[6] = 0x2260;
}
uint16_t getDelay(void) { return (_moveDelay); }
void setDelay(uint16_t delay) { if (delay > 10) _moveDelay = delay; }
void start(void) { _run = true; }
void stop(void) { _run = false; }
void begin(cScore *pScore, cSound *pSound)
{
_timeLastMove = 0;
_moveDelay = 1000;
_pieceCount = 0;
_run = false;
// set up the next tetronimo
_curOmino = 0;
_nxtOmino = random(ARRAY_SIZE(_tetromino));
_curRotation = 0;
// save and reset the score
_pScore = pScore;
_pScore->reset();
// save the sound object
_pSound = pSound;
// clear and display the field
clearField();
}
bool nextOmino(void)
{
_x = (FIELD_WIDTH - OMINO_SIZE) / 2;
_y = 0;
_curRotation = 0;
_curOmino = _nxtOmino;
if (!checkPieceFit(_curOmino, _curRotation, _x, _y))
return(false); // can't fit it it, pass the message back!
drawOmino(_curOmino, _curRotation, _x, _y);
_pSound->hit();
// work out and display the next omino
eraseNxtOmino();
_nxtOmino = random(ARRAY_SIZE(_tetromino));
drawNxtOmino();
return(true);
}
bool move(moveType_t moveType)
// user defined action to do something
{
bool b = false;
eraseOmino(_curOmino, _curRotation, _x, _y);
switch (moveType)
{
case M_LEFT:
if (b = checkPieceFit(_curOmino, _curRotation, _x - 1, _y))
_x--;
break;
case M_RIGHT:
if (b = checkPieceFit(_curOmino, _curRotation, _x + 1, _y))
_x++;
break;
case M_ROTATE:
if (b = checkPieceFit(_curOmino, (_curRotation + 1) % MAX_ROTATE, _x, _y))
_curRotation = (_curRotation + 1) % MAX_ROTATE;
break;
case M_DROP:
if (b = checkPieceFit(_curOmino, _curRotation, _x, _y + 1))
_y++;
break;
}
drawOmino(_curOmino, _curRotation, _x, _y);
return(b);
}
bool run(void)
// return false if the game has ended
{
bool bReturn = true;
// if this is the time for a move?
if (_run && millis() - _timeLastMove <= _moveDelay)
return(true);
_timeLastMove = millis();
// check if the piece can be moved down
if (checkPieceFit(_curOmino, _curRotation, _x, _y + 1))
{
// yes - do it and animate the display
eraseOmino(_curOmino, _curRotation, _x, _y);
_y++;
drawOmino(_curOmino, _curRotation, _x, _y);
// now adjust the seed if we have reached the threshold
if (_pieceCount >= 10)
{
// cap how fast we make it!
if (_moveDelay >= 200) _moveDelay -= 50;
_pieceCount = 0;
}
}
else
{
_pieceCount++; // just landed another one
// insert the current piece in the field, show it and update the score
omino2Field(_curOmino, _curRotation, _x, _y);
displayField();
_pScore->increment(PIECE_SCORE);
// Check for lines to delete
// There can only be lines in the span of the last block, so
// check the 4 lines it takes up
uint16_t lines = 0;
for (int16_t y = _y + OMINO_SIZE; (y >= _y) && (y >= 0); y--)
{
int16_t count = 0;
for (int16_t x = 0; x < FIELD_WIDTH; x++)
count += (_field[x][y] ? 1 : 0);
if (count == FIELD_WIDTH)
{
lines++; // keep count of lines completed
// delete the line - blank it out, pause for effect,
// make a sound then collapse the lines!
for (int16_t x = 0; x < FIELD_WIDTH; x++)
_field[x][y] = false;
displayField();
delay(200);
_pSound->bounce();
for (int16_t j = y; j >= 1; j--)
for (int16_t x = 0; x < FIELD_WIDTH; x++)
_field[x][j] = _field[x][j-1];
displayField();
// roll back the index as we have just changed the lines
y++;
}
}
// update the score if deleted lines
if (lines != 0) _pScore->increment((1 << lines)*LINE_SCORE);
// create the next omino and return status
bReturn = nextOmino();
}
return(bReturn);
}
};
// main objects coordinated by the code logic
cScore score;
cMoveSW moveSW;
cSound sound;
cTetris tetris;
void setupField(void)
// Draw the playing field at the start of the game.
{
mp.clear();
mp.drawHLine(FIELD_BOTTOM, FIELD_LEFT, FIELD_RIGHT);
mp.drawVLine(FIELD_LEFT, FIELD_TOP, FIELD_BOTTOM);
mp.drawVLine(FIELD_RIGHT, FIELD_TOP, FIELD_BOTTOM);
score.draw();
}
void setup()
{
#if DEBUG
Serial.begin(57600);
#endif
PRINTS("\n[MD_MAXPanel_Tetris]");
mp.begin();
mp.setFont(_Fixed_5x3);
mp.setIntensity(4);
// mp.setRotation(MD_MAXPanel::ROT_90);
sound.begin(BEEPER_PIN);
score.limit(MAX_SCORE); // so we can use width() below
score.begin(&mp, mp.getXMax() - (score.width() * (FONT_NUM_WIDTH + mp.getCharSpacing())) + mp.getCharSpacing(), mp.getYMax() - 1, MAX_SCORE);
moveSW.begin(LEFT_PIN, RIGHT_PIN, SELECT_PIN, DOWN_PIN);
randomSeed(seedOut(31, RANDOM_SEED_PORT));
}
bool handleUI(void)
{
bool b = true;
switch (moveSW.move())
{
case cMoveSW::MOVE_ROTATE:tetris.move(cTetris::M_ROTATE); break;
case cMoveSW::MOVE_DROP: tetris.move(cTetris::M_DROP); break;
case cMoveSW::MOVE_LEFT: tetris.move(cTetris::M_LEFT); break;
case cMoveSW::MOVE_RIGHT: tetris.move(cTetris::M_RIGHT); break;
case cMoveSW::MOVE_NONE: b = false; break;
}
return(b);
}
void loop(void)
{
static enum { S_SPLASH, S_INIT, S_WAIT_START, S_PLAY, S_GAME_OVER } runState = S_SPLASH;
switch (runState)
{
case S_SPLASH: // show splash screen at start
PRINTSTATE("SPLASH");
{
const uint16_t border = 2;
mp.clear();
mp.drawRectangle(border, border, mp.getXMax() - border, mp.getYMax() - border);
mp.drawLine(0, 0, border, border);
mp.drawLine(0, mp.getYMax(), border, mp.getYMax()-border);
mp.drawLine(mp.getXMax(), 0, mp.getXMax()-border, border);
mp.drawLine(mp.getXMax(), mp.getYMax(), mp.getXMax()-border, mp.getYMax()-border);
mp.drawText((mp.getXMax() - mp.getTextWidth(TITLE_TEXT)) / 2, (mp.getYMax() + mp.getFontHeight())/2 , TITLE_TEXT);
sound.splash();
delay(SPLASH_DELAY);
runState = S_INIT;
}
break;
case S_INIT: // initialize for a new game
PRINTSTATE("INIT");
setupField();
tetris.begin(&score, &sound);
runState = S_WAIT_START;
PRINTSTATE("WAIT_START");
break;
case S_WAIT_START: // waiting for the start of a new game
if (moveSW.anyKey())
{
PRINTS("\n-- Starting Game");
sound.start();
tetris.start();
if (tetris.nextOmino())
{
runState = S_PLAY;
PRINTSTATE("PLAY");
}
else
runState = S_GAME_OVER;
}
break;
case S_PLAY: // playing a point
// handle the switches
handleUI();
// move and finish the game if false
if (!tetris.run())
{
tetris.stop();
runState = S_GAME_OVER;
}
break;
case S_GAME_OVER:
{
uint16_t w, x, y;
PRINTSTATE("GAME_OVER");
w = mp.getTextWidth(GAME_TEXT);
x = ((mp.getXMax() - w) / 2) - 1;
y = (mp.getYMax() / 2) + mp.getFontHeight() + 2;
mp.clear(x, y, x + w + 2, y - mp.getFontHeight() - 2);
mp.drawText(x + 1, y - 1, GAME_TEXT);
w = mp.getTextWidth(OVER_TEXT);
x = ((mp.getXMax() - w) / 2) - 1;
y = (mp.getYMax() / 2) - 1 + 2;
mp.clear(x, y, x + w + 2, y - mp.getFontHeight() - 1);
mp.drawText(x + 1, y - 1, OVER_TEXT);
sound.over();
delay(GAME_OVER_DELAY);
runState = S_INIT;
}
break;
}
}

View File

@@ -0,0 +1,45 @@
#pragma once
// Random seed creation --------------------------
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
const uint8_t RANDOM_SEED_PORT = A3; // port read for random seed
uint16_t bitOut(uint8_t port)
{
static bool firstTime = true;
uint32_t prev = 0;
uint32_t bit1 = 0, bit0 = 0;
uint32_t x = 0, limit = 99;
if (firstTime)
{
firstTime = false;
prev = analogRead(port);
}
while (limit--)
{
x = analogRead(port);
bit1 = (prev != x ? 1 : 0);
prev = x;
x = analogRead(port);
bit0 = (prev != x ? 1 : 0);
prev = x;
if (bit1 != bit0)
break;
}
return(bit1);
}
uint32_t seedOut(uint16_t noOfBits, uint8_t port)
{
// return value with 'noOfBits' random bits set
uint32_t seed = 0;
for (int i = 0; i<noOfBits; ++i)
seed = (seed << 1) | bitOut(port);
return(seed);
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,54 @@
#pragma once
#include <MD_MAXPanel.h>
// A class to encapsulate the score display
class cScore
{
private:
MD_MAXPanel *_mp; // the MAXPanel object for drawing the score
uint16_t _score; // the score
uint16_t _x, _y; // coordinate of top left for display
uint8_t _width; // number of digits wide
uint16_t _limit; // maximum value allowed
public:
void begin(MD_MAXPanel *mp, uint16_t x, uint16_t y, uint16_t maxScore) { _mp = mp; _x = x, _y = y; limit(maxScore); reset(); }
void reset(void) { erase(); _score = 0; draw(); }
void set(uint16_t s) { if (s <= _limit) { erase(); _score = s; draw(); } }
void increment(uint16_t inc = 1) { if (_score + inc <= _limit) { erase(); _score += inc; draw(); } }
void decrement(uint16_t dec = 1) { if (_score >= dec) { erase(); _score -= dec; draw(); } }
uint16_t score(void) { return(_score); }
void erase(void) { draw(false); }
uint16_t width(void) { return(_width); }
void limit(uint16_t m)
{
erase(); // width may change, so delete with the curret parameters
_limit = m;
// work out how many digits this is
_width = 0;
do
{
_width++;
m /= 10;
} while (m != 0);
}
void draw(bool state = true)
{
char sz[_width + 1];
uint16_t s = _score;
// PRINT("\n-- SCORE: ", _score);
sz[_width] = '\0';
for (int i = _width - 1; i >= 0; --i)
{
sz[i] = (s % 10) + '0';
s /= 10;
}
_mp->drawText(_x, _y, sz, MD_MAXPanel::ROT_0, state);
}
};

View File

@@ -0,0 +1,47 @@
#pragma once
// A class to encapsulate primitive sound effects
class cSound
{
private:
const uint16_t EOD = 0; // End Of Data marker
uint8_t _pinBeep; // the pin to use for beeping
// Sound data - frequency followed by duration in pairs.
// Data ends in End Of Data marker EOD.
const uint16_t soundSplash[1] PROGMEM = { EOD };
const uint16_t soundHit[3] PROGMEM = { 1000, 50, EOD };
const uint16_t soundBounce[3] PROGMEM = { 500, 50, EOD };
const uint16_t soundPoint[3] PROGMEM = { 150, 150, EOD };
const uint16_t soundStart[7] PROGMEM = { 250, 100, 500, 100, 1000, 100, EOD };
const uint16_t soundOver[7] PROGMEM = { 1000, 100, 500, 100, 250, 100, EOD };
void playSound(const uint16_t *table)
// Play sound table data. Data table must end in EOD marker.
{
uint8_t idx = 0;
//PRINTS("\nTone Data ");
while (table[idx] != EOD)
{
uint16_t t = table[idx++];
uint16_t d = table[idx++];
//PRINTXY("-", t, d);
tone(_pinBeep, t);
delay(d);
}
//PRINTS("-EOD");
noTone(_pinBeep); // be quiet now!
}
public:
void begin(uint8_t pinBeep) { _pinBeep = pinBeep; }
void splash(void) { playSound(soundSplash); }
void start(void) { playSound(soundStart); }
void hit(void) { playSound(soundHit); }
void bounce(void) { playSound(soundBounce); }
void point(void) { playSound(soundPoint); }
void over(void) { playSound(soundOver); }
};