@ -10,18 +10,6 @@ const std::string BackgroundFile = "../data/tetris-background.jpeg";
using namespace std : : chrono_literals ;
// "North-facing" tetrimino forms (the form in which they are released from the
// top) are expressed in terms of two rows having between two and four columns.
// We map each game column to four columns and each game row to two rows.
// Each byte of the texture maps to one 4x4 component block (and wastes 7 bits).
static const struct tetrimino {
unsigned color ;
const char * texture ;
} tetriminos [ ] = { // OITLJSZ
{ 0xcbc900 , " **** " } , { 0x009caa , " **** " } , { 0x952d98 , " * *** " } ,
{ 0xcf7900 , " **** " } , { 0x0065bd , " * *** " } , { 0x69be28 , " **** " } ,
{ 0xbd2939 , " ** ** " } } ;
class TetrisNotcursesErr : public std : : runtime_error {
public :
TetrisNotcursesErr ( const std : : string & s ) throw ( )
@ -40,154 +28,41 @@ public:
Tetris ( ncpp : : NotCurses & nc , std : : atomic_bool & gameover ) :
nc_ ( nc ) ,
score_ ( 0 ) ,
msdelay_ ( 100 ms ) ,
curpiece_ ( nullptr ) ,
board_ ( nullptr ) ,
backg_ ( nullptr ) ,
stdplane_ ( nc_ . get_stdplane ( ) ) ,
gameover_ ( gameover )
gameover_ ( gameover ) ,
level_ ( 0 ) ,
msdelay_ ( Gravity ( level_ ) )
{
DrawBoard ( ) ;
curpiece_ = NewPiece ( ) ;
}
// 0.5 cell aspect: One board height == one row. One board width == two columns.
// 0.5 cell aspect: 1 board height == one row. 1 board width == two columns.
static constexpr auto BOARD_WIDTH = 10 ;
static constexpr auto BOARD_HEIGHT = 20 ;
// FIXME ideally this would be called from constructor :/
void Ticker ( ) {
std : : chrono : : milliseconds ms ;
mtx_ . lock ( ) ;
do {
ms = msdelay_ ;
// FIXME loop and verify we didn't get a spurious wakeup
mtx_ . unlock ( ) ;
std : : this_thread : : sleep_for ( ms ) ;
const std : : lock_guard < std : : mutex > lock ( mtx_ ) ;
if ( curpiece_ ) {
int y , x ;
curpiece_ - > get_yx ( & y , & x ) ;
if ( PieceStuck ( ) ) {
if ( y < = board_top_y_ - 2 ) {
gameover_ = true ;
return ;
}
curpiece_ - > mergedown ( * board_ ) ;
curpiece_ = NewPiece ( ) ;
} else {
+ + y ;
if ( ! curpiece_ - > move ( y , x ) | | ! nc_ . render ( ) ) {
throw TetrisNotcursesErr ( " move() or render ( ) " ) ;
}
}
}
} while ( ! gameover_ ) ;
}
void MoveLeft ( ) {
const std : : lock_guard < std : : mutex > lock ( mtx_ ) ;
int y , x ;
if ( ! PrepForMove ( & y , & x ) ) {
return ;
}
// For each line of the current piece, find the leftmost populated column.
// Check the game area to the immediate left. If something's there, we
// can't make this move.
ncpp : : Cell c ;
for ( int ly = 0 ; ly < curpiece_ - > get_dim_y ( ) ; + + ly ) {
int lx = 0 ;
while ( lx < curpiece_ - > get_dim_x ( ) ) {
if ( curpiece_ - > get_at ( ly , lx , & c ) ) {
if ( c . get ( ) . gcluster & & c . get ( ) . gcluster ! = ' ' ) {
break ;
}
}
+ + lx ;
}
if ( lx < curpiece_ - > get_dim_x ( ) ) { // otherwise, nothing on this row
ncpp : : Cell b ;
int cmpy = ly , cmpx = lx - 1 ;
curpiece_ - > translate ( * board_ , & cmpy , & cmpx ) ;
if ( board_ - > get_at ( cmpy , cmpx , & b ) ) {
if ( b . get ( ) . gcluster & & b . get ( ) . gcluster ! = ' ' ) {
return ; // move is blocked
}
}
}
}
- - x ;
if ( ! curpiece_ - > move ( y , x ) | | ! nc_ . render ( ) ) { // FIXME needs y?
throw TetrisNotcursesErr ( " move() or render ( ) " ) ;
}
}
void MoveRight ( ) {
const std : : lock_guard < std : : mutex > lock ( mtx_ ) ;
int y , x ;
if ( ! PrepForMove ( & y , & x ) ) {
return ;
}
// For each line of the current piece, find the rightmost populated column.
// Check the game area to the immediate right. If something's there, we
// can't make this move.
ncpp : : Cell c ;
for ( int ly = 0 ; ly < curpiece_ - > get_dim_y ( ) ; + + ly ) {
int lx = curpiece_ - > get_dim_x ( ) - 1 ;
while ( lx > = 0 ) {
if ( curpiece_ - > get_at ( ly , lx , & c ) ) {
if ( c . get ( ) . gcluster & & c . get ( ) . gcluster ! = ' ' ) {
break ;
}
}
- - lx ;
}
if ( lx > = 0 ) { // otherwise, nothing on this row
ncpp : : Cell b ;
int cmpy = ly , cmpx = lx + 1 ;
curpiece_ - > translate ( * board_ , & cmpy , & cmpx ) ;
if ( board_ - > get_at ( cmpy , cmpx , & b ) ) {
if ( b . get ( ) . gcluster & & b . get ( ) . gcluster ! = ' ' ) {
return ; // move is blocked
}
}
}
}
+ + x ;
if ( ! curpiece_ - > move ( y , x ) | | ! nc_ . render ( ) ) { // FIXME needs y?
throw TetrisNotcursesErr ( " move() or render ( ) " ) ;
}
}
void RotateCcw ( ) {
const std : : lock_guard < std : : mutex > lock ( mtx_ ) ;
int y , x ;
if ( ! PrepForMove ( & y , & x ) ) {
return ;
}
// FIXME rotate that fucker ccw
}
void RotateCw ( ) {
const std : : lock_guard < std : : mutex > lock ( mtx_ ) ;
int y , x ;
if ( ! PrepForMove ( & y , & x ) ) {
return ;
}
// FIXME rotate that fucker cw
}
# include "gravity.h"
# include "ticker.h"
# include "movedown.h"
# include "moveleft.h"
# include "moveright.h"
# include "rotate.h"
private :
ncpp : : NotCurses & nc_ ;
uint64_t score_ ;
std : : mutex mtx_ ;
std : : chrono : : milliseconds msdelay_ ;
std : : unique_ptr < ncpp : : Plane > curpiece_ ;
std : : unique_ptr < ncpp : : Plane > board_ ;
std : : unique_ptr < ncpp : : Visual > backg_ ;
ncpp : : Plane * stdplane_ ;
std : : atomic_bool & gameover_ ;
int board_top_y_ ;
int level_ ;
std : : chrono : : milliseconds msdelay_ ;
// Returns true if there's a current piece which can be moved
bool PrepForMove ( int * y , int * x ) {
@ -198,97 +73,9 @@ private:
return true ;
}
// background is drawn to the standard plane, at the bottom.
void DrawBackground ( const std : : string & s ) {
int averr ;
try {
backg_ = std : : make_unique < ncpp : : Visual > ( s . c_str ( ) , & averr , 0 , 0 , ncpp : : NCScale : : Stretch ) ;
} catch ( std : : exception & e ) {
throw TetrisNotcursesErr ( " visual() : " + s + " : " + e.what()) ;
}
if ( ! backg_ - > decode ( & averr ) ) {
throw TetrisNotcursesErr ( " decode() : " + s) ;
}
if ( ! backg_ - > render ( 0 , 0 , - 1 , - 1 ) ) {
throw TetrisNotcursesErr ( " render() : " + s) ;
}
}
// draw the background on the standard plane, then create a new plane for
// the play area.
void DrawBoard ( ) {
DrawBackground ( BackgroundFile ) ;
int y , x ;
stdplane_ - > get_dim ( & y , & x ) ;
board_top_y_ = y - ( BOARD_HEIGHT + 2 ) ;
board_ = std : : make_unique < ncpp : : Plane > ( BOARD_HEIGHT , BOARD_WIDTH * 2 ,
board_top_y_ , x / 2 - ( BOARD_WIDTH + 1 ) ) ;
uint64_t channels = 0 ;
channels_set_fg ( & channels , 0x00b040 ) ;
channels_set_bg_alpha ( & channels , CELL_ALPHA_TRANSPARENT ) ;
if ( ! board_ - > rounded_box ( 0 , channels , BOARD_HEIGHT - 1 , BOARD_WIDTH * 2 - 1 , NCBOXMASK_TOP ) ) {
throw TetrisNotcursesErr ( " rounded_box() " ) ;
}
channels_set_fg_alpha ( & channels , CELL_ALPHA_TRANSPARENT ) ;
board_ - > set_base ( channels , 0 , " " ) ;
if ( ! nc_ . render ( ) ) {
throw TetrisNotcursesErr ( " render() " ) ;
}
}
bool PieceStuck ( ) {
if ( ! curpiece_ ) {
return false ;
}
// check for impact. iterate over bottom row of piece's plane, checking for
// presence of glyph. if there, check row below. if row below is occupied,
// we're stuck.
int y , x ;
curpiece_ - > get_dim ( & y , & x ) ;
- - y ;
while ( x - - ) {
int cmpy = y + 1 , cmpx = x ; // need game area coordinates via translation
curpiece_ - > translate ( * board_ , & cmpy , & cmpx ) ;
ncpp : : Cell c ;
if ( board_ - > get_at ( cmpy , cmpx , & c ) < 0 ) {
throw TetrisNotcursesErr ( " get_at() " ) ;
}
if ( c . get ( ) . gcluster & & c . get ( ) . gcluster ! = ' ' ) {
return true ;
}
}
return false ;
}
// tidx is an index into tetriminos. yoff and xoff are relative to the
// terminal's origin. returns colored north-facing tetrimino on a plane.
std : : unique_ptr < ncpp : : Plane > NewPiece ( ) {
const int tidx = random ( ) % 7 ;
const struct tetrimino * t = & tetriminos [ tidx ] ;
const size_t cols = strlen ( t - > texture ) ;
int y , x ;
stdplane_ - > get_dim ( & y , & x ) ;
const int xoff = x / 2 - BOARD_WIDTH + ( random ( ) % BOARD_WIDTH - 1 ) ;
std : : unique_ptr < ncpp : : Plane > n = std : : make_unique < ncpp : : Plane > ( 2 , cols , board_top_y_ - 2 , xoff , nullptr ) ;
if ( n ) {
uint64_t channels = 0 ;
channels_set_bg_alpha ( & channels , CELL_ALPHA_TRANSPARENT ) ;
channels_set_fg_alpha ( & channels , CELL_ALPHA_TRANSPARENT ) ;
n - > set_fg ( t - > color ) ;
n - > set_bg_alpha ( CELL_ALPHA_TRANSPARENT ) ;
n - > set_base ( channels , 0 , " " ) ;
y = 0 ;
for ( size_t i = 0 ; i < strlen ( t - > texture ) ; + + i ) {
if ( t - > texture [ i ] = = ' * ' ) {
if ( n - > putstr ( y , x , " ██ " ) < 0 ) {
return NULL ;
}
}
y + = ( ( x = ( ( x + 2 ) % cols ) ) = = 0 ) ;
}
}
return n ;
}
# include "background.h"
# include "stuck.h"
# include "newpiece.h"
} ;
@ -296,6 +83,7 @@ int main(void) {
if ( setlocale ( LC_ALL , " " ) = = nullptr ) {
return EXIT_FAILURE ;
}
srand ( time ( NULL ) ) ;
std : : atomic_bool gameover = false ;
notcurses_options ncopts { } ;
ncpp : : NotCurses nc ( ncopts ) ;
@ -309,8 +97,9 @@ int main(void) {
break ;
}
switch ( input ) {
case NCKEY_LEFT : t . MoveLeft ( ) ; break ;
case NCKEY_RIGHT : t . MoveRight ( ) ; break ;
case NCKEY_LEFT : case ' h ' : t . MoveLeft ( ) ; break ;
case NCKEY_RIGHT : case ' l ' : t . MoveRight ( ) ; break ;
case NCKEY_DOWN : case ' j ' : t . MoveDown ( ) ; break ;
case ' z ' : t . RotateCcw ( ) ; break ;
case ' x ' : t . RotateCw ( ) ; break ;
default :
@ -320,7 +109,7 @@ int main(void) {
break ;
}
}
if ( gameover | | input = = ' q ' ) {
if ( gameover | | input = = ' q ' ) { // FIXME signal it on 'q'
gameover = true ;
tid . join ( ) ;
} else {