mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
* tetris man page * tetris basic skeleton * tetris: Ticker() * README: fix up some obsolete terminology * tetris: draw the game board * tetris: add NewPiece() * tetris: draw tetriminos * tetris: check for stuck piece, move it down * Accept NULL dst in ncplane_translate() #408
This commit is contained in:
parent
fbd222b04a
commit
52bdbc6724
@ -448,6 +448,27 @@ target_compile_definitions(notcurses-ncreel
|
|||||||
FORTIFY_SOURCE=2
|
FORTIFY_SOURCE=2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp)
|
||||||
|
add_executable(notcurses-tetris ${TETRISSRC})
|
||||||
|
target_include_directories(notcurses-tetris
|
||||||
|
PRIVATE
|
||||||
|
include
|
||||||
|
"${PROJECT_BINARY_DIR}/include"
|
||||||
|
)
|
||||||
|
target_link_libraries(notcurses-tetris
|
||||||
|
PRIVATE
|
||||||
|
Threads::Threads
|
||||||
|
notcurses++
|
||||||
|
)
|
||||||
|
target_compile_options(notcurses-tetris
|
||||||
|
PRIVATE
|
||||||
|
-Wall -Wextra -W -Wshadow ${DEBUG_OPTIONS}
|
||||||
|
)
|
||||||
|
target_compile_definitions(notcurses-tetris
|
||||||
|
PRIVATE
|
||||||
|
FORTIFY_SOURCE=2
|
||||||
|
)
|
||||||
|
|
||||||
# notcurses-view
|
# notcurses-view
|
||||||
file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp)
|
file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp)
|
||||||
if(${USE_FFMPEG})
|
if(${USE_FFMPEG})
|
||||||
@ -682,6 +703,7 @@ install(TARGETS notcurses-demo DESTINATION bin)
|
|||||||
install(TARGETS notcurses-input DESTINATION bin)
|
install(TARGETS notcurses-input DESTINATION bin)
|
||||||
install(TARGETS notcurses-ncreel DESTINATION bin)
|
install(TARGETS notcurses-ncreel DESTINATION bin)
|
||||||
install(TARGETS notcurses-tester DESTINATION bin)
|
install(TARGETS notcurses-tester DESTINATION bin)
|
||||||
|
install(TARGETS notcurses-tetris DESTINATION bin)
|
||||||
if(${USE_FFMPEG})
|
if(${USE_FFMPEG})
|
||||||
install(TARGETS notcurses-view DESTINATION bin)
|
install(TARGETS notcurses-view DESTINATION bin)
|
||||||
endif()
|
endif()
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
<a href="notcurses-ncreel.1.html">notcurses-ncreel</a>—experiments with ncreels<br/>
|
<a href="notcurses-ncreel.1.html">notcurses-ncreel</a>—experiments with ncreels<br/>
|
||||||
<a href="notcurses-pydemo.1.html">notcurses-pydemo</a>—validates the Python wrappers<br/>
|
<a href="notcurses-pydemo.1.html">notcurses-pydemo</a>—validates the Python wrappers<br/>
|
||||||
<a href="notcurses-tester.1.html">notcurses-tester</a>—unit test driver<br/>
|
<a href="notcurses-tester.1.html">notcurses-tester</a>—unit test driver<br/>
|
||||||
|
<a href="notcurses-tetris.1.html">notcurses-tetris</a>—Tetris in the terminal<br/>
|
||||||
<a href="notcurses-view.1.html">notcurses-view</a>—renders images and video to the terminal<br/>
|
<a href="notcurses-view.1.html">notcurses-view</a>—renders images and video to the terminal<br/>
|
||||||
<h2>C library (section 3)</h2>
|
<h2>C library (section 3)</h2>
|
||||||
<a href="notcurses_cell.3.html">notcurses_cell</a>—operations on <tt>cell</tt> objects<br/>
|
<a href="notcurses_cell.3.html">notcurses_cell</a>—operations on <tt>cell</tt> objects<br/>
|
||||||
|
32
doc/man/man1/notcurses-tetris.1.md
Normal file
32
doc/man/man1/notcurses-tetris.1.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
% notcurses-tetris(1)
|
||||||
|
% nick black <nickblack@linux.com>
|
||||||
|
% v1.2.3
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
notcurses-tetris - Render images and video to the console
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
**notcurses-tetris** [**-h|--help**] [**-l loglevel**]
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
**notcurses-tetris** implements Tetris using notcurses.
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
**-h**: Show help and exit.
|
||||||
|
|
||||||
|
**-l loglevel**: Log everything (high log level) or nothing (log level 0) to stderr.
|
||||||
|
|
||||||
|
# NOTES
|
||||||
|
|
||||||
|
Optimal display requires a terminal advertising the **rgb** terminfo(5)
|
||||||
|
capability, or that the environment variable **COLORTERM** is defined to
|
||||||
|
**24bit** (and that the terminal honors this variable), along with a
|
||||||
|
fixed-width font with good coverage of the Unicode Block Drawing Characters.
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
**notcurses(3)**
|
@ -151,6 +151,14 @@ It is an error for two threads to concurrently access a single ncplane. So long
|
|||||||
as rendering is not taking place, however, multiple threads may safely output
|
as rendering is not taking place, however, multiple threads may safely output
|
||||||
to multiple ncplanes.
|
to multiple ncplanes.
|
||||||
|
|
||||||
|
**ncplane_translate** translates coordinates expressed relative to the plane
|
||||||
|
**src**, and writes the coordinates of that cell relative to **dst**. The cell
|
||||||
|
need not intersect with **dst**, though this will yield coordinates which are
|
||||||
|
invalid for writing or reading on **dst**. If **dst** is **NULL**, it is taken
|
||||||
|
to refer to the standard plane. **ncplane_translate_abs** takes coordinates
|
||||||
|
expressed relative to the standard plane, and returns coordinates relative to
|
||||||
|
**dst**, returning **false** if the coordinates are invalid for **dst**.
|
||||||
|
|
||||||
**ncplane_mergedown** writes to **dst** the frame that would be rendered if only
|
**ncplane_mergedown** writes to **dst** the frame that would be rendered if only
|
||||||
**src** and **dst** existed on the z-axis, ad **dst** represented the entirety
|
**src** and **dst** existed on the z-axis, ad **dst** represented the entirety
|
||||||
of the rendering region. Only those cells where **src** intersects with **dst**
|
of the rendering region. Only those cells where **src** intersects with **dst**
|
||||||
|
@ -121,7 +121,7 @@ namespace ncpp
|
|||||||
return cell_simple_p (&_cell);
|
return cell_simple_p (&_cell);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t get_edc_idx () const noexcept
|
uint32_t get_egc_idx () const noexcept
|
||||||
{
|
{
|
||||||
return cell_egc_idx (&_cell);
|
return cell_egc_idx (&_cell);
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,14 @@ namespace ncpp
|
|||||||
return ncplane_pulse (plane, ts, fader, curry) != -1;
|
return ncplane_pulse (plane, ts, fader, curry) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mergedown (Plane* dst = nullptr) {
|
||||||
|
return ncplane_mergedown(*this, dst ? dst->plane : nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mergedown (Plane& dst) {
|
||||||
|
return mergedown(&dst);
|
||||||
|
}
|
||||||
|
|
||||||
bool gradient (const char* egc, uint32_t attrword, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ystop, int xstop) const noexcept
|
bool gradient (const char* egc, uint32_t attrword, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, int ystop, int xstop) const noexcept
|
||||||
{
|
{
|
||||||
return ncplane_gradient (plane, egc, attrword, ul, ur, ll, lr, ystop, xstop) != -1;
|
return ncplane_gradient (plane, egc, attrword, ul, ur, ll, lr, ystop, xstop) != -1;
|
||||||
@ -860,9 +868,7 @@ namespace ncpp
|
|||||||
|
|
||||||
void translate (const Plane *dst, int *y = nullptr, int *x = nullptr) const
|
void translate (const Plane *dst, int *y = nullptr, int *x = nullptr) const
|
||||||
{
|
{
|
||||||
if (dst == nullptr)
|
ncplane_translate(*this, dst ? dst->plane: nullptr, y, x);
|
||||||
throw invalid_argument ("'dst' must be a valid pointer");
|
|
||||||
translate (*this, *dst, y, x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void translate (const Plane &dst, int *y = nullptr, int *x = nullptr) noexcept
|
void translate (const Plane &dst, int *y = nullptr, int *x = nullptr) noexcept
|
||||||
@ -875,10 +881,7 @@ namespace ncpp
|
|||||||
if (src == nullptr)
|
if (src == nullptr)
|
||||||
throw invalid_argument ("'src' must be a valid pointer");
|
throw invalid_argument ("'src' must be a valid pointer");
|
||||||
|
|
||||||
if (dst == nullptr)
|
ncplane_translate(*src, dst ? dst->plane : nullptr, y, x);
|
||||||
throw invalid_argument ("'dst' must be a valid pointer");
|
|
||||||
|
|
||||||
translate (*src, *dst, y, x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void translate (const Plane &src, const Plane &dst, int *y = nullptr, int *x = nullptr) noexcept
|
static void translate (const Plane &src, const Plane &dst, int *y = nullptr, int *x = nullptr) noexcept
|
||||||
|
@ -410,7 +410,7 @@ API struct ncplane* ncplane_dup(struct ncplane* n, void* opaque);
|
|||||||
|
|
||||||
// provided a coordinate relative to the origin of 'src', map it to the same
|
// provided a coordinate relative to the origin of 'src', map it to the same
|
||||||
// absolute coordinate relative to thte origin of 'dst'. either or both of 'y'
|
// absolute coordinate relative to thte origin of 'dst'. either or both of 'y'
|
||||||
// and 'x' may be NULL.
|
// and 'x' may be NULL. if 'dst' is NULL, it is taken to be the standard plane.
|
||||||
API void ncplane_translate(const struct ncplane* src, const struct ncplane* dst,
|
API void ncplane_translate(const struct ncplane* src, const struct ncplane* dst,
|
||||||
int* RESTRICT y, int* RESTRICT x);
|
int* RESTRICT y, int* RESTRICT x);
|
||||||
|
|
||||||
|
@ -434,7 +434,7 @@ rotate_output(ncplane* dst, uint32_t tchan, uint32_t bchan){
|
|||||||
//
|
//
|
||||||
// Ideally, rotation through 360 degrees will restore the original 2x1 squre.
|
// Ideally, rotation through 360 degrees will restore the original 2x1 squre.
|
||||||
// Unfortunately, the case where a half block occupies a cell having the same
|
// Unfortunately, the case where a half block occupies a cell having the same
|
||||||
// fore- and background will see it roated into a single full block. In
|
// fore- and background will see it rotated into a single full block. In
|
||||||
// addition, lower blocks eventually become upper blocks with their channels
|
// addition, lower blocks eventually become upper blocks with their channels
|
||||||
// reversed. In general:
|
// reversed. In general:
|
||||||
//
|
//
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <wctype.h>
|
#include <wctype.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <pthread.h>
|
|
||||||
#include "notcurses/notcurses.h"
|
#include "notcurses/notcurses.h"
|
||||||
#include "egcpool.h"
|
#include "egcpool.h"
|
||||||
|
|
||||||
|
@ -1952,6 +1952,9 @@ bool ncplane_translate_abs(const ncplane* n, int* restrict y, int* restrict x){
|
|||||||
|
|
||||||
void ncplane_translate(const ncplane* src, const ncplane* dst,
|
void ncplane_translate(const ncplane* src, const ncplane* dst,
|
||||||
int* restrict y, int* restrict x){
|
int* restrict y, int* restrict x){
|
||||||
|
if(dst == NULL){
|
||||||
|
dst = ncplane_stdplane_const(src);
|
||||||
|
}
|
||||||
if(y){
|
if(y){
|
||||||
*y = src->absy - dst->absy + *y;
|
*y = src->absy - dst->absy + *y;
|
||||||
}
|
}
|
||||||
|
@ -382,6 +382,9 @@ postpaint(cell* fb, cell* lastframe, int dimy, int dimx,
|
|||||||
// paint within the real viewport currently.
|
// paint within the real viewport currently.
|
||||||
int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst){
|
int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst){
|
||||||
notcurses* nc = src->nc;
|
notcurses* nc = src->nc;
|
||||||
|
if(dst == NULL){
|
||||||
|
dst = nc->stdscr;
|
||||||
|
}
|
||||||
int dimy, dimx;
|
int dimy, dimx;
|
||||||
ncplane_dim_yx(dst, &dimy, &dimx);
|
ncplane_dim_yx(dst, &dimy, &dimx);
|
||||||
cell* fb = malloc(sizeof(*fb) * dimy * dimx);
|
cell* fb = malloc(sizeof(*fb) * dimy * dimx);
|
||||||
|
189
src/tetris/main.cpp
Normal file
189
src/tetris/main.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <clocale>
|
||||||
|
#include <ncpp/NotCurses.hh>
|
||||||
|
|
||||||
|
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(char const* const message) throw()
|
||||||
|
: std::runtime_error(message) {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual char const* what() const throw(){
|
||||||
|
return exception::what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Tetris {
|
||||||
|
public:
|
||||||
|
Tetris(ncpp::NotCurses& nc) :
|
||||||
|
nc_(nc),
|
||||||
|
score_(0),
|
||||||
|
msdelay_(10ms),
|
||||||
|
curpiece_(nullptr),
|
||||||
|
stdplane_(nc_.get_stdplane())
|
||||||
|
{
|
||||||
|
curpiece_ = NewPiece();
|
||||||
|
DrawBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0.5 cell aspect: One board height == one row. One 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);
|
||||||
|
mtx_.lock();
|
||||||
|
if(curpiece_){
|
||||||
|
int y, x;
|
||||||
|
curpiece_->get_yx(&y, &x);
|
||||||
|
++y;
|
||||||
|
if(PieceStuck()){
|
||||||
|
// FIXME lock it into place, get next piece
|
||||||
|
}else{
|
||||||
|
if(!curpiece_->move(y, x) || !nc_.render()){
|
||||||
|
// FIXME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}while(ms != std::chrono::milliseconds::zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop(){
|
||||||
|
mtx_.lock();
|
||||||
|
msdelay_ = std::chrono::milliseconds::zero(); // FIXME wake it up?
|
||||||
|
mtx_.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ncpp::NotCurses& nc_;
|
||||||
|
uint64_t score_;
|
||||||
|
std::mutex mtx_;
|
||||||
|
std::chrono::milliseconds msdelay_;
|
||||||
|
std::unique_ptr<ncpp::Plane> curpiece_;
|
||||||
|
ncpp::Plane* stdplane_;
|
||||||
|
|
||||||
|
void DrawBoard(){
|
||||||
|
int y, x;
|
||||||
|
stdplane_->get_dim(&y, &x);
|
||||||
|
uint64_t channels = 0;
|
||||||
|
channels_set_fg(&channels, 0x00b040);
|
||||||
|
if(!stdplane_->cursor_move(y - (BOARD_HEIGHT + 2), x / 2 - (BOARD_WIDTH + 1))){
|
||||||
|
throw TetrisNotcursesErr("cursor_move()");
|
||||||
|
}
|
||||||
|
if(!stdplane_->rounded_box(0, channels, y - 1, x / 2 + BOARD_WIDTH + 1, NCBOXMASK_TOP)){
|
||||||
|
throw TetrisNotcursesErr("rounded_box()");
|
||||||
|
}
|
||||||
|
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 absolute coordinates via translation
|
||||||
|
curpiece_->translate(nullptr, &cmpy, &cmpx);
|
||||||
|
ncpp::Cell c;
|
||||||
|
auto egc = nc_.get_at(cmpy, cmpx, c);
|
||||||
|
if(!egc){
|
||||||
|
return false; // FIXME is this not indicative of an error?
|
||||||
|
}
|
||||||
|
if(*egc && *egc != ' '){
|
||||||
|
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 - 3);
|
||||||
|
const int yoff = y - (BOARD_HEIGHT + 4);
|
||||||
|
std::unique_ptr<ncpp::Plane> n = std::make_unique<ncpp::Plane>(2, cols, yoff, 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_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(void){
|
||||||
|
if(setlocale(LC_ALL, "") == nullptr){
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
notcurses_options ncopts{};
|
||||||
|
ncpp::NotCurses nc(ncopts);
|
||||||
|
Tetris t{nc};
|
||||||
|
std::thread tid(&Tetris::Ticker, &t);
|
||||||
|
char32_t input;
|
||||||
|
ncinput ni;
|
||||||
|
while((input = nc.getc(true, &ni)) != (char32_t)-1){
|
||||||
|
if(input == 'q'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(input){
|
||||||
|
case NCKEY_LEFT: break;
|
||||||
|
case NCKEY_RIGHT: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(input == 'q'){
|
||||||
|
t.Stop();
|
||||||
|
tid.join();
|
||||||
|
}else{
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return nc.stop() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user