mirror of
https://github.com/dankamongmen/notcurses.git
synced 2024-11-02 09:40:15 +00:00
Merge branch 'master' of github.com:dankamongmen/notcurses
This commit is contained in:
commit
4e7d918732
@ -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()
|
||||||
|
BIN
data/tetris-background.jpeg
Normal file
BIN
data/tetris-background.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
@ -829,7 +829,7 @@ WARN_LOGFILE =
|
|||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# Note: If this tag is empty the current directory is searched.
|
||||||
|
|
||||||
INPUT = ../src/lib ../include/notcurses.h
|
INPUT = ../src/lib ../include
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# This tag can be used to specify the character encoding of the source files
|
||||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||||
|
@ -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;
|
||||||
@ -295,6 +303,20 @@ namespace ncpp
|
|||||||
return move_above (*above);
|
return move_above (*above);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mergedown (Plane &dst) const
|
||||||
|
{
|
||||||
|
if (plane == dst.plane)
|
||||||
|
throw invalid_argument ("'dst' must refer to a differnt plane than the one this method is called on");
|
||||||
|
return ncplane_mergedown (plane, dst.plane) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mergedown (Plane *dst) const
|
||||||
|
{
|
||||||
|
if (dst == nullptr)
|
||||||
|
throw invalid_argument ("'dst' must be a valid pointer");
|
||||||
|
return mergedown (*dst);
|
||||||
|
}
|
||||||
|
|
||||||
bool cursor_move (int y, int x) const noexcept
|
bool cursor_move (int y, int x) const noexcept
|
||||||
{
|
{
|
||||||
return ncplane_cursor_move_yx (plane, y, x) != -1;
|
return ncplane_cursor_move_yx (plane, y, x) != -1;
|
||||||
@ -860,9 +882,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 +895,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);
|
||||||
|
|
||||||
@ -556,7 +556,7 @@ ncplane_move_below(struct ncplane* n, struct ncplane* below){
|
|||||||
return ncplane_move_below_unsafe(n, below);
|
return ncplane_move_below_unsafe(n, below);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the plane above this one, or NULL if this is at the top.
|
// Return the plane below this one, or NULL if this is at the bottom.
|
||||||
API struct ncplane* ncplane_below(struct ncplane* n);
|
API struct ncplane* ncplane_below(struct ncplane* n);
|
||||||
|
|
||||||
// Rotate the plane pi/2 radians clockwise or counterclockwise. Note that
|
// Rotate the plane pi/2 radians clockwise or counterclockwise. Note that
|
||||||
@ -958,7 +958,8 @@ API int ncplane_stain(struct ncplane* n, int ystop, int xstop, uint64_t ul,
|
|||||||
// rendering region." Merging is independent of the position of 'src' viz 'dst'
|
// rendering region." Merging is independent of the position of 'src' viz 'dst'
|
||||||
// on the z-axis. If 'src' does not intersect with 'dst', 'dst' will not be
|
// on the z-axis. If 'src' does not intersect with 'dst', 'dst' will not be
|
||||||
// changed, but it is not an error. The source plane still exists following
|
// changed, but it is not an error. The source plane still exists following
|
||||||
// this operation. Do not supply the same plane for both 'src' and 'dst'.
|
// this operation. If 'dst' is NULL, it will be interpreted as the standard
|
||||||
|
// plane. Do not supply the same plane for both 'src' and 'dst'.
|
||||||
API int ncplane_mergedown(struct ncplane* RESTRICT src, struct ncplane* RESTRICT dst);
|
API int ncplane_mergedown(struct ncplane* RESTRICT src, struct ncplane* RESTRICT dst);
|
||||||
|
|
||||||
// Erase every cell in the ncplane, resetting all attributes to normal, all
|
// Erase every cell in the ncplane, resetting all attributes to normal, all
|
||||||
@ -1975,13 +1976,13 @@ ncvisual_simple_streamer(struct notcurses* nc, struct ncvisual* ncv, void* curry
|
|||||||
|
|
||||||
// Stream the entirety of the media, according to its own timing. Blocking,
|
// Stream the entirety of the media, according to its own timing. Blocking,
|
||||||
// obviously. streamer may be NULL; it is otherwise called for each frame, and
|
// obviously. streamer may be NULL; it is otherwise called for each frame, and
|
||||||
// its return value handled as outlined for stream cb. Pretty raw; beware.
|
// its return value handled as outlined for stream cb. If streamer() returns
|
||||||
// If streamer() returns non-zero, the stream is aborted, and that value is
|
// non-zero, the stream is aborted, and that value is returned. By convention,
|
||||||
// returned. By convention, return a positive number to indicate intentional
|
// return a positive number to indicate intentional abort from within
|
||||||
// abort from within streamer(). 'timescale' allows the frame duration time to
|
// streamer(). 'timescale' allows the frame duration time to be scaled. For a
|
||||||
// be scaled. For a visual naturally running at 30FPS, a 'timescale' of 0.1
|
// visual naturally running at 30FPS, a 'timescale' of 0.1 will result in
|
||||||
// will result in 300FPS, and a 'timescale' of 10 will result in 3FPS. It is an
|
// 300FPS, and a 'timescale' of 10 will result in 3FPS. It is an error to
|
||||||
// error to supply 'timescale' less than or equal to 0.
|
// supply 'timescale' less than or equal to 0.
|
||||||
API int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv,
|
API int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv,
|
||||||
int* averr, float timescale, streamcb streamer,
|
int* averr, float timescale, streamcb streamer,
|
||||||
void* curry);
|
void* curry);
|
||||||
|
@ -57,11 +57,13 @@ ncplane_polyfill_recurse(ncplane* n, int y, int x, const cell* c){
|
|||||||
// at the initial step only, invalid y, x is an error, so explicitly check.
|
// at the initial step only, invalid y, x is an error, so explicitly check.
|
||||||
int ncplane_polyfill_yx(ncplane* n, int y, int x, const cell* c){
|
int ncplane_polyfill_yx(ncplane* n, int y, int x, const cell* c){
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
|
if(c->gcluster){ // can't polyfill with a null EGC
|
||||||
if(y < n->leny && x < n->lenx){
|
if(y < n->leny && x < n->lenx){
|
||||||
if(y >= 0 && x >= 0){
|
if(y >= 0 && x >= 0){
|
||||||
ret = ncplane_polyfill_recurse(n, y, x, c);
|
ret = ncplane_polyfill_recurse(n, y, x, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,7 +436,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"
|
||||||
|
|
||||||
@ -545,6 +544,20 @@ cell_debug(const egcpool* p, const cell* c){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
plane_debug(const ncplane* n){
|
||||||
|
int dimy, dimx;
|
||||||
|
ncplane_dim_yx(n, &dimy, &dimx);
|
||||||
|
fprintf(stderr, "p: %p dim: %d/%d poolsize: %d\n", n, dimy, dimx, n->pool.poolsize);
|
||||||
|
for(int y = 0 ; y < 1 ; ++y){
|
||||||
|
for(int x = 0 ; x < 10 ; ++x){
|
||||||
|
const cell* c = &n->fb[fbcellidx(y, dimx, x)];
|
||||||
|
fprintf(stderr, "[%03d/%03d] ", y, x);
|
||||||
|
cell_debug(&n->pool, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// True if the cell does not generate background pixels. Only the FULL BLOCK
|
// True if the cell does not generate background pixels. Only the FULL BLOCK
|
||||||
// glyph has this property, AFAIK.
|
// glyph has this property, AFAIK.
|
||||||
// FIXME set a bit, doing this at load time
|
// FIXME set a bit, doing this at load time
|
||||||
@ -572,7 +585,6 @@ cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell
|
|||||||
return !!c->gcluster;
|
return !!c->gcluster;
|
||||||
}
|
}
|
||||||
size_t ulen = strlen(extended_gcluster(splane, c));
|
size_t ulen = strlen(extended_gcluster(splane, c));
|
||||||
//fprintf(stderr, "[%s] (%zu)\n", egcpool_extended_gcluster(&splane->pool, c), strlen(egcpool_extended_gcluster(&splane->pool, c)));
|
|
||||||
int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen);
|
int eoffset = egcpool_stash(tpool, extended_gcluster(splane, c), ulen);
|
||||||
if(eoffset < 0){
|
if(eoffset < 0){
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -203,19 +203,21 @@ lock_in_highcontrast(cell* targc, struct crender* crender){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paints a single ncplane into the provided framebuffer 'fb'. Whenever a cell
|
// Paints a single ncplane into the provided scratch framebuffer 'fb', and
|
||||||
// is locked in, it is compared against the last frame. If it is different, the
|
// ultimately 'lastframe' (we can't always write directly into 'lastframe',
|
||||||
// 'damagevec' bitmap is updated with a 1. 'pool' is typically nc->pool, but can
|
// because we need build state to solve certain cells, and need compare their
|
||||||
|
// solved result to the last frame). Whenever a cell is locked in, it is
|
||||||
|
// compared against the last frame. If it is different, the 'rvec' bitmap is updated with a 1. 'pool' is typically nc->pool, but can
|
||||||
// be whatever's backing fb.
|
// be whatever's backing fb.
|
||||||
static int
|
static int
|
||||||
paint(notcurses* nc, ncplane* p, cell* lastframe, struct crender* rvec, cell* fb, egcpool* pool){
|
paint(ncplane* p, cell* lastframe, struct crender* rvec,
|
||||||
|
cell* fb, egcpool* pool, int dstleny, int dstlenx,
|
||||||
|
int dstabsy, int dstabsx, int lfdimx){
|
||||||
int y, x, dimy, dimx, offy, offx;
|
int y, x, dimy, dimx, offy, offx;
|
||||||
// don't use ncplane_dim_yx()/ncplane_yx() here, lest we deadlock
|
ncplane_dim_yx(p, &dimy, &dimx);
|
||||||
dimy = p->leny;
|
offy = p->absy - dstabsy;
|
||||||
dimx = p->lenx;
|
offx = p->absx - dstabsx;
|
||||||
offy = p->absy - nc->stdscr->absy;
|
//fprintf(stderr, "PLANE %p %d %d %d %d %d %d\n", p, dimy, dimx, offy, offx, dstleny, dstlenx);
|
||||||
offx = p->absx - nc->stdscr->absx;
|
|
||||||
//fprintf(stderr, "PLANE %p %d %d %d %d %d %d\n", p, dimy, dimx, offy, offx, nc->stdscr->leny, nc->stdscr->lenx);
|
|
||||||
// skip content above or to the left of the physical screen
|
// skip content above or to the left of the physical screen
|
||||||
int starty, startx;
|
int starty, startx;
|
||||||
if(offy < 0){
|
if(offy < 0){
|
||||||
@ -231,19 +233,19 @@ paint(notcurses* nc, ncplane* p, cell* lastframe, struct crender* rvec, cell* fb
|
|||||||
for(y = starty ; y < dimy ; ++y){
|
for(y = starty ; y < dimy ; ++y){
|
||||||
const int absy = y + offy;
|
const int absy = y + offy;
|
||||||
// once we've passed the physical screen's bottom, we're done
|
// once we've passed the physical screen's bottom, we're done
|
||||||
if(absy >= nc->stdscr->leny){
|
if(absy >= dstleny){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for(x = startx ; x < dimx ; ++x){
|
for(x = startx ; x < dimx ; ++x){
|
||||||
const int absx = x + offx;
|
const int absx = x + offx;
|
||||||
if(absx >= nc->stdscr->lenx){
|
if(absx >= dstlenx){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cell* targc = &fb[fbcellidx(absy, nc->stdscr->lenx, absx)];
|
cell* targc = &fb[fbcellidx(absy, dstlenx, absx)];
|
||||||
if(cell_locked_p(targc)){
|
if(cell_locked_p(targc)){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
struct crender* crender = &rvec[fbcellidx(absy, nc->stdscr->lenx, absx)];
|
struct crender* crender = &rvec[fbcellidx(absy, dstlenx, absx)];
|
||||||
const cell* vis = &p->fb[nfbcellidx(p, y, x)];
|
const cell* vis = &p->fb[nfbcellidx(p, y, x)];
|
||||||
// if we never loaded any content into the cell (or obliterated it by
|
// if we never loaded any content into the cell (or obliterated it by
|
||||||
// writing in a zero), use the plane's base cell.
|
// writing in a zero), use the plane's base cell.
|
||||||
@ -263,7 +265,7 @@ paint(notcurses* nc, ncplane* p, cell* lastframe, struct crender* rvec, cell* fb
|
|||||||
// screen, nor if we're bisected by a higher plane.
|
// screen, nor if we're bisected by a higher plane.
|
||||||
if(cell_double_wide_p(vis)){
|
if(cell_double_wide_p(vis)){
|
||||||
// are we on the last column of the real screen? if so, 0x20 us
|
// are we on the last column of the real screen? if so, 0x20 us
|
||||||
if(absx >= nc->stdscr->lenx - 1){
|
if(absx >= dstlenx - 1){
|
||||||
targc->gcluster = ' ';
|
targc->gcluster = ' ';
|
||||||
// is the next cell occupied? if so, 0x20 us
|
// is the next cell occupied? if so, 0x20 us
|
||||||
}else if(targc[1].gcluster){
|
}else if(targc[1].gcluster){
|
||||||
@ -313,13 +315,14 @@ paint(notcurses* nc, ncplane* p, cell* lastframe, struct crender* rvec, cell* fb
|
|||||||
// which were already locked in were skipped at the top of the loop)?
|
// which were already locked in were skipped at the top of the loop)?
|
||||||
if(cell_locked_p(targc)){
|
if(cell_locked_p(targc)){
|
||||||
lock_in_highcontrast(targc, crender);
|
lock_in_highcontrast(targc, crender);
|
||||||
cell* prevcell = &lastframe[fbcellidx(absy, nc->lfdimx, absx)];
|
cell* prevcell = &lastframe[fbcellidx(absy, lfdimx, absx)];
|
||||||
/*if(cell_simple_p(targc)){
|
/*if(cell_simple_p(targc)){
|
||||||
fprintf(stderr, "WROTE %u [%c] to %d/%d (%d/%d)\n", targc->gcluster, prevcell->gcluster, y, x, absy, absx);
|
fprintf(stderr, "WROTE %u [%c] to %d/%d (%d/%d)\n", targc->gcluster, prevcell->gcluster, y, x, absy, absx);
|
||||||
}else{
|
}else{
|
||||||
fprintf(stderr, "WROTE %u [%s] to %d/%d (%d/%d)\n", targc->gcluster, extended_gcluster(crender->p, targc), y, x, absy, absx);
|
fprintf(stderr, "WROTE %u [%s] to %d/%d (%d/%d)\n", targc->gcluster, extended_gcluster(crender->p, targc), y, x, absy, absx);
|
||||||
}
|
}
|
||||||
fprintf(stderr, "POOL: %p NC: %p SRC: %p\n", nc->pool.pool, nc, crender->p);*/
|
fprintf(stderr, "POOL: %p NC: %p SRC: %p\n", pool->pool, nc, crender->p);
|
||||||
|
}*/
|
||||||
if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc)){
|
if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc)){
|
||||||
crender->damaged = true;
|
crender->damaged = true;
|
||||||
if(cell_double_wide_p(targc)){
|
if(cell_double_wide_p(targc)){
|
||||||
@ -382,21 +385,35 @@ 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* tmpfb = malloc(sizeof(*tmpfb) * dimy * dimx);
|
||||||
|
cell* rendfb = malloc(sizeof(*rendfb) * dimy * dimx);
|
||||||
const size_t crenderlen = sizeof(struct crender) * dimy * dimx;
|
const size_t crenderlen = sizeof(struct crender) * dimy * dimx;
|
||||||
struct crender* rvec = malloc(crenderlen);
|
struct crender* rvec = malloc(crenderlen);
|
||||||
memset(rvec, 0, crenderlen);
|
memset(rvec, 0, crenderlen);
|
||||||
init_fb(fb, dimy, dimx);
|
init_fb(tmpfb, dimy, dimx);
|
||||||
if(paint(nc, src, dst->fb, rvec, fb, &dst->pool) || paint(nc, dst, dst->fb, rvec, fb, &dst->pool)){
|
init_fb(rendfb, dimy, dimx);
|
||||||
|
if(paint(src, rendfb, rvec, tmpfb, &dst->pool, dst->leny, dst->lenx,
|
||||||
|
dst->absy, dst->absx, dst->lenx)){
|
||||||
free(rvec);
|
free(rvec);
|
||||||
free(fb);
|
free(rendfb);
|
||||||
|
free(tmpfb);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
postpaint(fb, dst->fb, dimy, dimx, rvec, &dst->pool);
|
if(paint(dst, rendfb, rvec, tmpfb, &dst->pool, dst->leny, dst->lenx,
|
||||||
|
dst->absy, dst->absx, dst->lenx)){
|
||||||
|
free(rvec);
|
||||||
|
free(rendfb);
|
||||||
|
free(tmpfb);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
postpaint(tmpfb, rendfb, dimy, dimx, rvec, &dst->pool);
|
||||||
free(dst->fb);
|
free(dst->fb);
|
||||||
dst->fb = fb;
|
dst->fb = rendfb;
|
||||||
free(rvec);
|
free(rvec);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -417,7 +434,9 @@ notcurses_render_internal(notcurses* nc, struct crender* rvec){
|
|||||||
init_fb(fb, dimy, dimx);
|
init_fb(fb, dimy, dimx);
|
||||||
ncplane* p = nc->top;
|
ncplane* p = nc->top;
|
||||||
while(p){
|
while(p){
|
||||||
if(paint(nc, p, nc->lastframe, rvec, fb, &nc->pool)){
|
if(paint(p, nc->lastframe, rvec, fb, &nc->pool,
|
||||||
|
nc->stdscr->leny, nc->stdscr->lenx,
|
||||||
|
nc->stdscr->absy, nc->stdscr->absx, nc->lfdimx)){
|
||||||
free(fb);
|
free(fb);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -787,9 +806,9 @@ update_palette(notcurses* nc, FILE* out){
|
|||||||
// * refresh -- write the stream to the emulator
|
// * refresh -- write the stream to the emulator
|
||||||
|
|
||||||
// Takes a rendered frame (a flat framebuffer, where each cell has the desired
|
// Takes a rendered frame (a flat framebuffer, where each cell has the desired
|
||||||
// EGC, attribute, and channels) and the previously-rendered frame, and spits
|
// EGC, attribute, and channels), which has been written to nc->lastframe, and
|
||||||
// out an optimal sequence of terminal-appropriate escapes and EGCs. There
|
// spits out an optimal sequence of terminal-appropriate escapes and EGCs. There
|
||||||
// should be an rvec entry for each cell; only the 'damaged' field is used.
|
// should be an rvec entry for each cell, but only the 'damaged' field is used.
|
||||||
static int
|
static int
|
||||||
notcurses_rasterize(notcurses* nc, const struct crender* rvec){
|
notcurses_rasterize(notcurses* nc, const struct crender* rvec){
|
||||||
FILE* out = nc->rstate.mstreamfp;
|
FILE* out = nc->rstate.mstreamfp;
|
||||||
|
330
src/tetris/main.cpp
Normal file
330
src/tetris/main.cpp
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <clocale>
|
||||||
|
#include <ncpp/NotCurses.hh>
|
||||||
|
|
||||||
|
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()
|
||||||
|
: std::runtime_error(s) {
|
||||||
|
}
|
||||||
|
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, std::atomic_bool& gameover) :
|
||||||
|
nc_(nc),
|
||||||
|
score_(0),
|
||||||
|
msdelay_(100ms),
|
||||||
|
curpiece_(nullptr),
|
||||||
|
board_(nullptr),
|
||||||
|
backg_(nullptr),
|
||||||
|
stdplane_(nc_.get_stdplane()),
|
||||||
|
gameover_(gameover)
|
||||||
|
{
|
||||||
|
DrawBoard();
|
||||||
|
curpiece_ = NewPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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_;
|
||||||
|
|
||||||
|
// Returns true if there's a current piece which can be moved
|
||||||
|
bool PrepForMove(int* y, int* x) {
|
||||||
|
if(!curpiece_){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
curpiece_->get_yx(y, x);
|
||||||
|
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, 0, 0)){
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if(setlocale(LC_ALL, "") == nullptr){
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
std::atomic_bool gameover = false;
|
||||||
|
notcurses_options ncopts{};
|
||||||
|
ncpp::NotCurses nc(ncopts);
|
||||||
|
Tetris t{nc, gameover};
|
||||||
|
std::thread tid(&Tetris::Ticker, &t);
|
||||||
|
ncpp::Plane* stdplane = nc.get_stdplane();
|
||||||
|
char32_t input = 0;
|
||||||
|
ncinput ni;
|
||||||
|
while(!gameover && (input = nc.getc(true, &ni)) != (char32_t)-1){
|
||||||
|
if(input == 'q'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(input){
|
||||||
|
case NCKEY_LEFT: t.MoveLeft(); break;
|
||||||
|
case NCKEY_RIGHT: t.MoveRight(); break;
|
||||||
|
case 'z': t.RotateCcw(); break;
|
||||||
|
case 'x': t.RotateCw(); break;
|
||||||
|
default:
|
||||||
|
stdplane->cursor_move(0, 0);
|
||||||
|
stdplane->printf("Got unknown input U+%06x", input);
|
||||||
|
nc.render();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(gameover || input == 'q'){
|
||||||
|
gameover = true;
|
||||||
|
tid.join();
|
||||||
|
}else{
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return nc.stop() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
@ -17,6 +17,14 @@ TEST_CASE("Fills") {
|
|||||||
struct ncplane* n_ = notcurses_stdplane(nc_);
|
struct ncplane* n_ = notcurses_stdplane(nc_);
|
||||||
REQUIRE(n_);
|
REQUIRE(n_);
|
||||||
|
|
||||||
|
// can't polyfill with a null glyph
|
||||||
|
SUBCASE("PolyfillNullGlyph") {
|
||||||
|
int dimx, dimy;
|
||||||
|
ncplane_dim_yx(n_, &dimy, &dimx);
|
||||||
|
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
CHECK(0 > ncplane_polyfill_yx(n_, dimy, dimx, &c));
|
||||||
|
}
|
||||||
|
|
||||||
// trying to polyfill an invalid cell ought be an error
|
// trying to polyfill an invalid cell ought be an error
|
||||||
SUBCASE("PolyfillOffplane") {
|
SUBCASE("PolyfillOffplane") {
|
||||||
int dimx, dimy;
|
int dimx, dimy;
|
||||||
@ -40,11 +48,17 @@ TEST_CASE("Fills") {
|
|||||||
CHECK(0 == ncplane_destroy(pfn));
|
CHECK(0 == ncplane_destroy(pfn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SUBCASE("PolyfillStandardPlane") {
|
||||||
|
cell c = CELL_SIMPLE_INITIALIZER('-');
|
||||||
|
CHECK(0 < ncplane_polyfill_yx(n_, 0, 0, &c));
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
}
|
||||||
|
|
||||||
SUBCASE("PolyfillEmptyPlane") {
|
SUBCASE("PolyfillEmptyPlane") {
|
||||||
cell c = CELL_SIMPLE_INITIALIZER('+');
|
cell c = CELL_SIMPLE_INITIALIZER('+');
|
||||||
struct ncplane* pfn = ncplane_new(nc_, 4, 4, 0, 0, nullptr);
|
struct ncplane* pfn = ncplane_new(nc_, 20, 20, 0, 0, nullptr);
|
||||||
REQUIRE(nullptr != pfn);
|
REQUIRE(nullptr != pfn);
|
||||||
CHECK(16 == ncplane_polyfill_yx(pfn, 0, 0, &c));
|
CHECK(400 == ncplane_polyfill_yx(pfn, 0, 0, &c));
|
||||||
CHECK(0 == notcurses_render(nc_));
|
CHECK(0 == notcurses_render(nc_));
|
||||||
CHECK(0 == ncplane_destroy(pfn));
|
CHECK(0 == ncplane_destroy(pfn));
|
||||||
}
|
}
|
||||||
@ -311,7 +325,7 @@ TEST_CASE("Fills") {
|
|||||||
CHECK(0 == notcurses_render(nc_));
|
CHECK(0 == notcurses_render(nc_));
|
||||||
}
|
}
|
||||||
|
|
||||||
SUBCASE("MergeDown") {
|
SUBCASE("MergeDownASCII") {
|
||||||
auto p1 = ncplane_new(nc_, 1, 10, 0, 0, nullptr);
|
auto p1 = ncplane_new(nc_, 1, 10, 0, 0, nullptr);
|
||||||
REQUIRE(p1);
|
REQUIRE(p1);
|
||||||
// make sure glyphs replace nulls
|
// make sure glyphs replace nulls
|
||||||
@ -345,6 +359,83 @@ TEST_CASE("Fills") {
|
|||||||
ncplane_destroy(p1);
|
ncplane_destroy(p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SUBCASE("MergeDownUni") {
|
||||||
|
auto p1 = ncplane_new(nc_, 1, 10, 0, 0, nullptr);
|
||||||
|
REQUIRE(p1);
|
||||||
|
// make sure glyphs replace nulls
|
||||||
|
CHECK(0 < ncplane_putstr(p1, "█▀▄▌▐🞵🞶🞷🞸🞹"));
|
||||||
|
CHECK(0 == ncplane_mergedown(p1, n_));
|
||||||
|
cell cbase = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
cell cp = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
for(int i = 0 ; i < 10 ; ++i){
|
||||||
|
CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase));
|
||||||
|
CHECK(0 < ncplane_at_yx(p1, 0, i, &cp));
|
||||||
|
CHECK(0 == cellcmp(n_, &cbase, p1, &cp));
|
||||||
|
}
|
||||||
|
ncplane_destroy(p1);
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
auto p3 = ncplane_new(nc_, 1, 10, 0, 0, nullptr);
|
||||||
|
CHECK(0 == ncplane_cursor_move_yx(p3, 0, 0));
|
||||||
|
// make sure glyphs replace glyps
|
||||||
|
CHECK(0 < ncplane_putstr(p3, "🞵🞶🞷🞸🞹█▀▄▌▐"));
|
||||||
|
CHECK(0 == ncplane_mergedown(p3, NULL));
|
||||||
|
cell c3 = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
for(int i = 0 ; i < 10 ; ++i){
|
||||||
|
CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase));
|
||||||
|
CHECK(0 < ncplane_at_yx(p3, 0, i, &c3));
|
||||||
|
CHECK(0 == cellcmp(n_, &cbase, p3, &c3));
|
||||||
|
}
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
// make sure nulls do not replace glyphs
|
||||||
|
auto p2 = ncplane_new(nc_, 1, 10, 0, 0, nullptr);
|
||||||
|
CHECK(0 == ncplane_mergedown(p2, NULL));
|
||||||
|
ncplane_destroy(p2);
|
||||||
|
for(int i = 0 ; i < 10 ; ++i){
|
||||||
|
CHECK(0 < ncplane_at_yx(n_, 0, i, &cbase));
|
||||||
|
CHECK(0 < ncplane_at_yx(p3, 0, i, &c3));
|
||||||
|
CHECK(0 == cellcmp(n_, &cbase, p3, &c3));
|
||||||
|
}
|
||||||
|
ncplane_destroy(p3);
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test merging down one plane to another plane which is smaller than the
|
||||||
|
// standard plane
|
||||||
|
SUBCASE("MergeDownSmallPlane") {
|
||||||
|
constexpr int DIMX = 10;
|
||||||
|
constexpr int DIMY = 10;
|
||||||
|
auto p1 = ncplane_new(nc_, DIMY, DIMX, 2, 2, nullptr);
|
||||||
|
REQUIRE(p1);
|
||||||
|
cell c1 = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
CHECK(0 < cell_load(p1, &c1, "█"));
|
||||||
|
CHECK(0 == cell_set_bg(&c1, 0x00ff00));
|
||||||
|
CHECK(0 == cell_set_fg(&c1, 0x0000ff));
|
||||||
|
ncplane_polyfill_yx(p1, 0, 0, &c1);
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
auto p2 = ncplane_new(nc_, DIMY / 2, DIMX / 2, 3, 3, nullptr);
|
||||||
|
REQUIRE(p2);
|
||||||
|
cell c2 = CELL_TRIVIAL_INITIALIZER;
|
||||||
|
CHECK(0 < cell_load(p2, &c2, "🞶"));
|
||||||
|
CHECK(0 == cell_set_bg(&c2, 0x00ffff));
|
||||||
|
CHECK(0 == cell_set_fg(&c2, 0xff00ff));
|
||||||
|
ncplane_polyfill_yx(p2, 0, 0, &c2);
|
||||||
|
CHECK(0 == ncplane_mergedown(p2, p1));
|
||||||
|
CHECK(0 == notcurses_render(nc_));
|
||||||
|
for(int y = 0 ; y < DIMY ; ++y){
|
||||||
|
for(int x = 0 ; x < DIMX ; ++x){
|
||||||
|
CHECK(0 < ncplane_at_yx(p1, y, x, &c1));
|
||||||
|
if(y < 1 || y > 5 || x < 1 || x > 5){
|
||||||
|
CHECK(0 == strcmp(extended_gcluster(p1, &c1), "█"));
|
||||||
|
}else{
|
||||||
|
CHECK(0 < ncplane_at_yx(p2, y - 1, x - 1, &c2));
|
||||||
|
CHECK(0 == cellcmp(p1, &c1, p2, &c2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ncplane_destroy(p1);
|
||||||
|
ncplane_destroy(p2);
|
||||||
|
}
|
||||||
|
|
||||||
CHECK(0 == notcurses_stop(nc_));
|
CHECK(0 == notcurses_stop(nc_));
|
||||||
CHECK(0 == fclose(outfp_));
|
CHECK(0 == fclose(outfp_));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user