Hi all!
Due to a misconfiguration of my domain's DMARC record, my previous mail
went directly into your spam folders. Now that it's fixed, I'm sending
it again. Hope that doesn't cause too much trouble.
I made some changes to tetris(6) to follow the Tetris guidelines more
closely and to add new controls and features.
Among the (visible) changes:
- The random shape generator now picks a shape from a shuffled "bag"
(a 7 ints array), thus preventing extremely long runs without a
desired shape, and on the other hand to have the same piece 4 times
in a row.
- New controls have been added, a player can now rotate the active shape
counterclockwise, clockwise and by 180�. The player can also soft drop
(i.e. move the shape down), without influencing the score, unlike the
hard drop.
- There's now a "hold" control, which allows the player to swap the active
shape with the one in the hold space. If there's none, the active shape
is moved into the hold space and the next shape comes into the board.
The hold action can only be used once, until the next randomly-chosen
shapes comes. The shape in that space is kept as-is until the player
decides to put it back on the board.
Maybe we should put that thing as a command-line flag and penalizes
the score if enabled, like the preview of the next shape? Let me know.
- The manpage has been modified to add the new controls and features.
A "TODO" section has been created with the features that I'd like
to implement in the future (like a ghost shape and support for arrow
keys in the -k flag).
Now, the internal changes:
- A shape preview function has been created to facilitate.. the preview
of shapes.
- Trailing whitespaces and tabs have been removed. (Actually, I didn't
even do it myself, my editor config just removes them automagically.)
I did my best to follow OpenBSD's code style in the changes I made.
It's my first contribution, hope I didn't make too many mistakes!
Thanks for your time, and have a great day/evening/night!
~ tmtt
Index: screen.c
===================================================================
RCS file: /cvs/src/games/tetris/screen.c,v
retrieving revision 1.19
diff -u -p -u -p -r1.19 screen.c
--- screen.c 28 Jun 2019 13:32:52 -0000 1.19
+++ screen.c 26 Aug 2022 19:35:42 -0000
@@ -138,6 +138,45 @@ put(int c)
#define moveto(r, c) putpad(tgoto(CMstr, c, r))
/*
+ * Shape preview.
+ */
+void
+preview_shape(const struct shape *piece, char *text, int r, int c)
+{
+ int tr, tc, t, i;
+
+ /* clean */
+ putpad(SEstr);
+ moveto(r-1, c-1); putstr(" ");
+ moveto(r, c-1); putstr(" ");
+ moveto(r+1, c-1); putstr(" ");
+ moveto(r+2, c-1); putstr(" ");
+
+ moveto(r-3, c-2);
+ putstr(text);
+
+ /* draw */
+ if (piece != NULL) {
+ if (SOstr)
+ putpad(SOstr);
+
+ moveto(r, 2 * c);
+ putstr(SOstr ? " " : "[]");
+ for (i = 0; i < 3; i++) {
+ t = c + r * B_COLS;
+ t += piece->off[i];
+
+ tr = t / B_COLS;
+ tc = t % B_COLS;
+
+ moveto(tr, 2*tc);
+ putstr(SOstr ? " " : "[]");
+ }
+ putpad(SEstr);
+ }
+}
+
+/*
* Set up from termcap.
*/
void
@@ -165,7 +204,7 @@ scr_init(void)
{"sg", &sgnum},
{ {0}, NULL}
};
-
+
if ((term = getenv("TERM")) == NULL)
stop("you must set the TERM environment variable");
if (tgetent(tbuf, term) <= 0)
@@ -370,6 +409,7 @@ scr_update(void)
int i, ccol, j;
sigset_t sigset, osigset;
static const struct shape *lastshape;
+ static const struct shape *lastholdshape;
sigemptyset(&sigset);
sigaddset(&sigset, SIGTSTP);
@@ -387,41 +427,22 @@ scr_update(void)
curscore = score;
}
+ static int r, c;
+ int tr, tc, t;
+
/* draw preview of next pattern */
if (showpreview && (nextshape != lastshape)) {
- static int r=5, c=2;
- int tr, tc, t;
-
lastshape = nextshape;
+ preview_shape(nextshape, "Next shape:", 12, 2);
+ }
- /* clean */
- putpad(SEstr);
- moveto(r-1, c-1); putstr(" ");
- moveto(r, c-1); putstr(" ");
- moveto(r+1, c-1); putstr(" ");
- moveto(r+2, c-1); putstr(" ");
-
- moveto(r-3, c-2);
- putstr("Next shape:");
-
- /* draw */
- if (SOstr)
- putpad(SOstr);
- moveto(r, 2 * c);
- putstr(SOstr ? " " : "[]");
- for (i = 0; i < 3; i++) {
- t = c + r * B_COLS;
- t += nextshape->off[i];
-
- tr = t / B_COLS;
- tc = t % B_COLS;
-
- moveto(tr, 2*tc);
- putstr(SOstr ? " " : "[]");
- }
- putpad(SEstr);
+ /* draw preview of hold piece */
+ if (lastholdshape == NULL || holdshape != lastholdshape) {
+ lastholdshape = holdshape;
+ preview_shape(holdshape, "Hold shape:", 5, 2);
}
+
bp = &board[D_FIRST * B_COLS];
sp = &curscreen[D_FIRST * B_COLS];
for (j = D_FIRST; j < D_LAST; j++) {
@@ -478,7 +499,7 @@ scr_update(void)
void
scr_msg(char *s, int set)
{
-
+
if (set || CEstr == NULL) {
int l = strlen(s);
Index: tetris.6
===================================================================
RCS file: /cvs/src/games/tetris/tetris.6,v
retrieving revision 1.21
diff -u -p -u -p -r1.21 tetris.6
--- tetris.6 19 Jun 2017 06:04:16 -0000 1.21
+++ tetris.6 26 Aug 2022 19:35:42 -0000
@@ -32,7 +32,7 @@
.\"
.\" @(#)tetris.6 8.1 (Berkeley) 5/31/93
.\"
-.Dd $Mdocdate: June 19 2017 $
+.Dd $Mdocdate: August 26 2022 $
.Dt TETRIS 6
.Os
.Sh NAME
@@ -57,14 +57,24 @@ The default level of play is 2.
The default control keys are as follows:
.Pp
.Bl -tag -width "<space>" -compact -offset indent
-.It j
+.It h
move left
+.It j
+soft drop
.It k
-rotate 1/4 turn counterclockwise
+hold shape
.It l
move right
+
+.It a
+rotate counterclockwise
+.It s
+rotate 180�
+.It d
+rotate clockwise
+
.It <space>
-drop
+hard drop
.It p
pause
.It q
@@ -74,7 +84,7 @@ quit
The options are as follows:
.Bl -tag -width indent
.It Fl c
-Classic tetris mode, in which shapes rotate clockwise and are drawn with
+Classic tetris mode, in which shapes are drawn with
.Dq [] .
.It Fl k Ar keys
The default control keys can be changed using the
@@ -82,16 +92,16 @@ The default control keys can be changed
option.
The
.Ar keys
-argument must have the six keys in order; remember to quote any
+argument must have the ten keys in order; remember to quote any
space or tab characters from the shell.
For example:
.sp
-.Dl "tetris -l 2 -k 'jkl pq'"
+.Dl "tetris -l 2 -k 'hjklasd pq'"
.sp
will play the default game, i.e. level 2 with the default
control keys.
The current key settings are displayed at the bottom of the screen
-during play.
+during play, unless the screen is too small.
.It Fl l Ar level
Select a level of play.
.It Fl p
@@ -112,15 +122,21 @@ When this shape
.Dq "touches down"
on the bottom of the field, another will appear at the top.
.Pp
-You can move shapes to the left or right, rotate them counterclockwise,
-or drop them to the bottom by pressing the appropriate keys.
-As you fit them together, completed horizontal rows vanish,
-and any blocks above fall down to fill in.
+You can move shapes to the left, right and down, hold them, rotate them
+counterclockwise, clockwise and by 180�, or drop them to the bottom
+by pressing the appropriate keys. As you fit them together, completed
+horizontal rows vanish, and any blocks above fall down to fill in.
When the blocks stack up to the top of the screen, the game is over.
+.Pp
+The hold control allows you to swap the active shape with the one in
+the hold space. If there's none, the active shape is moved into the
+hold space and the next shape comes into the board. The shape in the
+hold space is kept as-is until you decide to use it. This feature can
+only be used once until the next randomly-chosen shapes comes.
.Sh SCORING
You get one point for every block you fit into the stack,
and one point for every space a block falls when you hit the drop key.
-(Dropping the blocks is therefore a good way to increase your score.)
+(Hard dropping the blocks is therefore a good way to increase your score.)
Completing a row rewards you with a bonus corresponding to the number
of simultaneous rows completed.
Your total score is the product of the level of play
@@ -168,5 +184,15 @@ and
.Pp
Shape previewing code adapted from code by
.An Hubert Feyrer .
+.Pp
+Tetris' bag-based
+.Dq Random Generator ,
+hold piece and advanced controls made by
+.An Tom MTT .
+.Sh TODO
+A flag that adds a ghost shape at the bottom of the board
+to let the player know where the shape will land.
+
+A way to set arrow keys as controls in the -k flag.
.Sh BUGS
The higher levels are unplayable without a fast terminal connection.
Index: tetris.c
===================================================================
RCS file: /cvs/src/games/tetris/tetris.c,v
retrieving revision 1.35
diff -u -p -u -p -r1.35 tetris.c
--- tetris.c 12 Jul 2021 15:09:18 -0000 1.35
+++ tetris.c 26 Aug 2022 19:35:42 -0000
@@ -53,15 +53,19 @@
#include "screen.h"
#include "tetris.h"
-#define NUMKEYS 6
+#define NUMKEYS 10
cell board[B_SIZE];
int Rows, Cols;
const struct shape *curshape;
const struct shape *nextshape;
+const struct shape *holdshape;
+char holdshape_available;
long fallrate;
int score;
-char key_msg[100];
+int bag[] = { 0, 1, 2, 3, 4, 5, 6 };
+int bag_index = -1;
+char key_msg[110];
char scorepath[PATH_MAX];
int showpreview, classic;
@@ -134,16 +138,42 @@ elide(void)
}
}
+void
+swap(int *a, int *b)
+{
+ int tmp = *a;
+ *a = *b;
+ *b = tmp;
+}
+
+void
+shuffle(int array[], int length)
+{
+ for (int i = length - 1; i > 0; i--) {
+ int j = arc4random_uniform(i + 1);
+ swap(&array[i], &array[j]);
+ }
+}
+
const struct shape *
randshape(void)
{
const struct shape *tmp;
int i, j;
+ int bag_size = sizeof(bag) / sizeof(bag[0]);
+
+ if (bag_index == -1) {
+ shuffle(bag, bag_size);
+ bag_index = bag_size - 1;
+ }
+
+ tmp = &shapes[bag[bag_index]];
+ bag_index--;
- tmp = &shapes[arc4random_uniform(7)];
j = arc4random_uniform(4);
for (i = 0; i < j; i++)
- tmp = &shapes[classic? tmp->rotc : tmp->rot];
+ tmp = &shapes[tmp->rotccw];
+
return (tmp);
}
@@ -170,7 +200,7 @@ main(int argc, char *argv[])
if (pledge("stdio rpath wpath cpath tty unveil", NULL) == -1)
err(1, "pledge");
- keys = "jkl pq";
+ keys = "hjklasd pq";
classic = showpreview = 0;
while ((ch = getopt(argc, argv, "ck:l:ps")) != -1)
@@ -178,8 +208,8 @@ main(int argc, char *argv[])
case 'c':
/*
* this means:
- * - rotate the other way;
* - no reverse video.
+ * - ASCII graphics (instead of ANSI).
*/
classic = 1;
break;
@@ -226,9 +256,9 @@ main(int argc, char *argv[])
}
snprintf(key_msg, sizeof key_msg,
-"%s - left %s - rotate %s - right %s - drop %s - pause %s - quit",
- key_write[0], key_write[1], key_write[2], key_write[3],
- key_write[4], key_write[5]);
+"%s %s %s %s - left, drop, hold, right %s %s %s - rotate CCW, 180�, CW %s
- hard drop %s - pause %s - quit",
+ key_write[0], key_write[1], key_write[2], key_write[3],
key_write[4],
+ key_write[5], key_write[6], key_write[7], key_write[8],
key_write[9]);
(void)signal(SIGINT, onintr);
scr_init();
@@ -247,7 +277,11 @@ main(int argc, char *argv[])
nextshape = randshape();
curshape = randshape();
- scr_msg(key_msg, 1);
+ holdshape = NULL;
+ holdshape_available = 1;
+
+ if (Cols > sizeof(key_msg))
+ scr_msg(key_msg, 1);
for (;;) {
place(curshape, pos, 1);
@@ -272,6 +306,12 @@ main(int argc, char *argv[])
elide();
/*
+ * Make the hold shape available for swapping.
+ */
+
+ holdshape_available = 1;
+
+ /*
* Choose a new shape. If it does not fit,
* the game is over.
*/
@@ -286,23 +326,25 @@ main(int argc, char *argv[])
/*
* Handle command keys.
*/
- if (c == keys[5]) {
+ if (c == keys[9]) {
/* quit */
break;
}
- if (c == keys[4]) {
+ if (c == keys[8]) {
static char msg[] =
"paused - press RETURN to continue";
place(curshape, pos, 1);
do {
scr_update();
- scr_msg(key_msg, 0);
+ if (Cols > sizeof(key_msg))
+ scr_msg(key_msg, 0);
scr_msg(msg, 1);
(void) fflush(stdout);
} while (rwait(NULL) == -1);
scr_msg(msg, 0);
- scr_msg(key_msg, 1);
+ if (Cols > sizeof(key_msg))
+ scr_msg(key_msg, 1);
place(curshape, pos, 0);
continue;
}
@@ -313,22 +355,65 @@ main(int argc, char *argv[])
continue;
}
if (c == keys[1]) {
- /* turn */
- const struct shape *new = &shapes[
- classic? curshape->rotc : curshape->rot];
-
- if (fits_in(new, pos))
- curshape = new;
+ /* (soft) drop */
+ if (fits_in(curshape, pos + B_COLS))
+ pos += B_COLS;
continue;
}
if (c == keys[2]) {
+ /* hold shape */
+ if (holdshape_available == 1) {
+ if (holdshape != NULL) {
+ const struct shape *tmp = curshape;
+ curshape = holdshape;
+ holdshape = tmp;
+
+ } else {
+ holdshape = curshape;
+ curshape = nextshape;
+ nextshape = randshape();
+ }
+
+ holdshape_available = 0;
+
+ pos = A_FIRST*B_COLS + (B_COLS/2)-1;
+ if (!fits_in(curshape, pos))
+ break;
+ }
+ continue;
+ }
+ if (c == keys[3]) {
/* move right */
if (fits_in(curshape, pos + 1))
pos++;
continue;
}
- if (c == keys[3]) {
- /* move to bottom */
+ if (c == keys[4]) {
+ /* rotate counterclockwise */
+ const struct shape *new = &shapes[curshape->rotccw];
+
+ if (fits_in(new, pos))
+ curshape = new;
+ continue;
+ }
+ if (c == keys[5]) {
+ /* rotate 180� */
+ const struct shape *new =
&shapes[shapes[curshape->rotccw].rotccw];
+
+ if (fits_in(new, pos))
+ curshape = new;
+ continue;
+ }
+ if (c == keys[6]) {
+ /* rotate clockwise */
+ const struct shape *new = &shapes[curshape->rotcw];
+
+ if (fits_in(new, pos))
+ curshape = new;
+ continue;
+ }
+ if (c == keys[7]) {
+ /* hard drop */
while (fits_in(curshape, pos + B_COLS)) {
pos += B_COLS;
score++;
@@ -337,7 +422,8 @@ main(int argc, char *argv[])
}
if (c == '\f') {
scr_clear();
- scr_msg(key_msg, 1);
+ if (Cols > 110)
+ scr_msg(key_msg, 1);
}
}
Index: tetris.h
===================================================================
RCS file: /cvs/src/games/tetris/tetris.h,v
retrieving revision 1.13
diff -u -p -u -p -r1.13 tetris.h
--- tetris.h 18 May 2019 19:38:26 -0000 1.13
+++ tetris.h 26 Aug 2022 19:35:42 -0000
@@ -70,8 +70,8 @@ extern cell board[B_SIZE]; /* 1 => occup
/*
* Minimum display size.
*/
-#define MINROWS 23
-#define MINCOLS 40
+#define MINROWS 24
+#define MINCOLS 50
extern int Rows, Cols; /* current screen size */
@@ -124,8 +124,8 @@ extern int Rows, Cols; /* current screen
* rotated forms.
*/
struct shape {
- int rot; /* index of rotated version of this shape */
- int rotc; /* -- " -- in classic version */
+ int rotccw; /* Counterclockwise rotated shape */
+ int rotcw; /* Clockwise rotated shape */
int off[3]; /* offsets to other blots if center is at (0,0) */
};
@@ -133,6 +133,7 @@ extern const struct shape shapes[];
extern const struct shape *curshape;
extern const struct shape *nextshape;
+extern const struct shape *holdshape;
/*
* Shapes fall at a rate faster than once per second.
@@ -170,7 +171,7 @@ extern long fallrate; /* less than 1 bil
extern int score; /* the obvious thing */
-extern char key_msg[100];
+extern char key_msg[110];
extern char scorepath[PATH_MAX];
extern int showpreview;
extern int classic;
--
Tom MTT. https://heimdall.pm/
PGP: https://heimdall.pm/tmtt.pub