📄 ntetris.c
字号:
/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is NanoTetris.
*
* The Initial Developer of the Original Code is Alex Holden.
* Portions created by Alex Holden are Copyright (C) 2000
* Alex Holden <alex@linuxhacker.org>. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms
* of the GNU General Public license (the "[GNU] License"), in which case the
* provisions of [GNU] License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the [GNU] License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the [GNU] License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the [GNU] License.
*/
/*
* A Nano-X Tetris clone by Alex Holden.
*
* The objective is to keep placing new pieces for as long as possible. When a
* horizontal line is filled with blocks, it will vanish, and everything above
* it will drop down a line. It quickly gets difficult because the speed
* increases with the score. Unlike with some Tetris clones, no bonus points
* are awarded for matching colours, completing more than one line at a time,
* or for using the "drop shape to bottom" function.
*
* The box in the top left of the game window is the score box. The top score
* is the highest score you have achieved since last resetting the high score
* counter. The counter is stored when the game exits in the file specified by
* the HISCORE_FILE parameter ("/usr/games/nanotetris.hiscore" by default).
* Note that no attempt is made to encrypt the file, so anybody with write
* access to the file can alter the contents of it using a text editor.
*
* The box below the score box is the next shape box. This contains a "preview"
* of the next shape to appear, so that you can plan ahead as you are building
* up the blocks.
*
* The game functions can be controlled using either the mouse (or a touch pad,
* touch screen, trackball, etc.) and the buttons below the next shape box, or
* with the following keyboard keys:
*
* Q = quit game
* N = new game
* P = pause game
* C = continue game
* D = rotate shape anticlockwise
* F = rotate shape clockwise
* J = move shape left
* K = move shape right
* Space Bar = drop shape to bottom.
*
* The reason for the unconventional use of D, F, J, and K keys is that they
* are all in the "home" position of a QWERTY keyboard, which makes them very
* easy to press if you are used to touch typing.
*
* I'll leave it to you to figure out which mouse operated movement button does
* what (it's pretty obvious).
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <sys/time.h>
#ifdef __ECOS
#define random rand
#define srandom srand
#endif
#define MWINCLUDECOLORS
#include <nano-X.h>
#include "ntetris.h"
static void *my_malloc(size_t size)
{
void *ret;
if(!(ret = malloc(size))) {
fprintf(stderr, "Out of memory\n");
exit(1);
}
return ret;
}
#ifdef HAVE_USLEEP
static void msleep(long ms)
{
usleep(ms * 1000);
}
#else
static void msleep(long ms)
{
struct timespec req, rem;
req.tv_sec = ms / 1000000;
req.tv_nsec = (ms % 1000000) * 1000000;
while(nanosleep(&req, &rem) == -1) {
if(errno == EINTR) {
req.tv_sec = rem.tv_sec;
req.tv_nsec = rem.tv_nsec;
continue;
} else {
perror("nanosleep() failed");
return;
}
}
}
#endif
#ifdef USE_HISCORE_FILE
static void read_hiscore(nstate *state)
{
FILE *f;
int i, n;
if(!(f = fopen(HISCORE_FILE, "r"))) {
if(errno != ENOENT)
perror("Couldn't open high score file for reading");
state->hiscore = state->fhiscore = 0;
return;
}
i = fscanf(f, "%d", &n);
fclose(f);
if(i != 1) {
fprintf(stderr, "Couldn't read high score file\n");
n = 0;
}
state->hiscore = state->fhiscore = n;
}
static void write_hiscore(nstate *state)
{
FILE *f;
if(state->score > state->hiscore) state->hiscore = state->score;
if(state->hiscore <= state->fhiscore) return;
if(!(f = fopen(HISCORE_FILE, "w"))) {
perror("Couldn't open high score file for writing");
return;
}
if((fprintf(f, "%d", state->hiscore)) == -1) {
perror("Couldn't write to high score file");
}
fclose(f);
}
#else
static void read_hiscore(nstate *state)
{
state->hiscore = 0;
}
static void write_hiscore(nstate *state) {}
#endif
static int will_collide(nstate *state, int x, int y, int orientation)
{
int r, c, xx, yy;
char ch = 0;
draw_shape(state, state->current_shape.x, state->current_shape.y, 1);
for(r = 0; ch < 3; r++) {
ch = 0;
for(c = 0; ch < 2; c++) {
ch = shapes[state->current_shape.type]
[orientation][r][c];
if(ch == 1) {
yy = y + r;
xx = x + c;
if((yy == WELL_HEIGHT) || (xx == WELL_WIDTH) ||
(state->blocks[0][yy][xx])) {
draw_shape(state,
state->current_shape.x,
state->current_shape.y, 0);
return 1;
}
}
}
}
draw_shape(state, state->current_shape.x, state->current_shape.y, 0);
return 0;
}
static void draw_shape(nstate *state, GR_COORD x, GR_COORD y, int erase)
{
int r, c, yy, xx;
GR_COLOR col;
char ch = 0;
if(erase) col = 0;
else col = state->current_shape.colour;
for(r = 0; ch < 3; r++) {
ch = 0;
for(c = 0; ch < 2; c++) {
ch = shapes[state->current_shape.type]
[state->current_shape.orientation][r][c];
if(ch == 1) {
yy = y + r;
xx = x + c;
state->blocks[0][yy][xx] = col;
}
}
}
}
static void draw_well(nstate *state, int forcedraw)
{
int x, y;
for(y = WELL_NOTVISIBLE; y < WELL_HEIGHT; y++) {
for(x = 0; x < WELL_WIDTH; x++) {
if(forcedraw || (state->blocks[0][y][x] !=
state->blocks[1][y][x])) {
state->blocks[1][y][x] = state->blocks[0][y][x];
GrSetGCForeground(state->wellgc,
state->blocks[0][y][x]);
GrFillRect(state->well_window, state->wellgc,
(BLOCK_SIZE * x),
(BLOCK_SIZE * (y - WELL_NOTVISIBLE)),
BLOCK_SIZE, BLOCK_SIZE);
}
}
}
GrFlush();
}
static void draw_score(nstate *state)
{
char buf[32];
GrFillRect(state->score_window, state->scoregcb, 0, 0,
SCORE_WINDOW_WIDTH, SCORE_WINDOW_HEIGHT);
sprintf(buf, "%d", state->score);
GrText(state->score_window, state->scoregcf, TEXT_X_POSITION,
TEXT2_Y_POSITION, buf, strlen(buf), 0);
sprintf(buf, "%d", state->hiscore);
GrText(state->score_window, state->scoregcf, TEXT_X_POSITION,
TEXT_Y_POSITION, buf, strlen(buf), 0);
}
static void draw_next_shape(nstate *state)
{
int r, c, startx, starty, x, y;
char ch = 0;
GrFillRect(state->next_shape_window, state->nextshapegcb, 0, 0,
NEXT_SHAPE_WINDOW_WIDTH, NEXT_SHAPE_WINDOW_HEIGHT);
GrSetGCForeground(state->nextshapegcf, state->next_shape.colour);
startx = (BLOCK_SIZE * ((NEXT_SHAPE_WINDOW_SIZE / 2) -
(shape_sizes[state->next_shape.type]
[state->next_shape.orientation][0] / 2)));
starty = (BLOCK_SIZE * ((NEXT_SHAPE_WINDOW_SIZE / 2) -
(shape_sizes[state->next_shape.type]
[state->next_shape.orientation][1] / 2)));
for(r = 0; ch < 3; r++) {
ch = 0;
for(c = 0; ch < 2; c++) {
ch = shapes[state->next_shape.type]
[state->next_shape.orientation][r][c];
if(ch == 1) {
x = startx + (c * BLOCK_SIZE);
y = starty + (r * BLOCK_SIZE);
GrFillRect(state->next_shape_window,
state->nextshapegcf, x, y,
BLOCK_SIZE, BLOCK_SIZE);
}
}
}
}
static void draw_new_game_button(nstate *state)
{
GrFillRect(state->new_game_button, state->buttongcb, 0, 0,
NEW_GAME_BUTTON_WIDTH, NEW_GAME_BUTTON_HEIGHT);
GrText(state->new_game_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, "New Game", 8, 0);
}
static void draw_anticlockwise_button(nstate *state)
{
if(!state->running_buttons_mapped) return;
GrFillRect(state->anticlockwise_button, state->buttongcb, 0, 0,
ANTICLOCKWISE_BUTTON_WIDTH, ANTICLOCKWISE_BUTTON_HEIGHT);
GrText(state->anticlockwise_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, " /", 4, 0);
}
static void draw_clockwise_button(nstate *state)
{
if(!state->running_buttons_mapped) return;
GrFillRect(state->clockwise_button, state->buttongcb, 0, 0,
CLOCKWISE_BUTTON_WIDTH, CLOCKWISE_BUTTON_HEIGHT);
GrText(state->clockwise_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, " \\", 4, 0);
}
static void draw_left_button(nstate *state)
{
if(!state->running_buttons_mapped) return;
GrFillRect(state->left_button, state->buttongcb, 0, 0,
LEFT_BUTTON_WIDTH, LEFT_BUTTON_HEIGHT);
GrText(state->left_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, " <", 3, 0);
}
static void draw_right_button(nstate *state)
{
if(!state->running_buttons_mapped) return;
GrFillRect(state->right_button, state->buttongcb, 0, 0,
RIGHT_BUTTON_WIDTH, RIGHT_BUTTON_HEIGHT);
GrText(state->right_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, " >", 4, 0);
}
static void draw_drop_button(nstate *state)
{
if(!state->running_buttons_mapped) return;
GrFillRect(state->drop_button, state->buttongcb, 0, 0,
DROP_BUTTON_WIDTH, DROP_BUTTON_HEIGHT);
GrText(state->drop_button, state->buttongcf, TEXT_X_POSITION,
TEXT_Y_POSITION, " Drop", 8, 0);
}
static void draw_pause_continue_button(nstate *state)
{
if((state->running_buttons_mapped) && (state->state == STATE_STOPPED)) {
GrUnmapWindow(state->pause_continue_button);
GrUnmapWindow(state->anticlockwise_button);
GrUnmapWindow(state->clockwise_button);
GrUnmapWindow(state->left_button);
GrUnmapWindow(state->right_button);
GrUnmapWindow(state->drop_button);
state->running_buttons_mapped = 0;
return;
}
if((!state->running_buttons_mapped) && (state->state == STATE_RUNNING)){
GrMapWindow(state->pause_continue_button);
GrMapWindow(state->anticlockwise_button);
GrMapWindow(state->clockwise_button);
GrMapWindow(state->left_button);
GrMapWindow(state->right_button);
GrMapWindow(state->drop_button);
state->running_buttons_mapped = 1;
return;
}
if(!state->running_buttons_mapped) return;
GrFillRect(state->pause_continue_button, state->buttongcb, 0, 0,
PAUSE_CONTINUE_BUTTON_WIDTH, PAUSE_CONTINUE_BUTTON_HEIGHT);
if(state->state == STATE_PAUSED) {
GrText(state->pause_continue_button, state->buttongcf,
TEXT_X_POSITION, TEXT_Y_POSITION, " Continue", 9, 0);
} else {
GrText(state->pause_continue_button, state->buttongcf,
TEXT_X_POSITION, TEXT_Y_POSITION, " Pause", 8, 0);
}
}
static int block_is_all_in_well(nstate *state)
{
if(state->current_shape.y >= WELL_NOTVISIBLE)
return 1;
return 0;
}
static void delete_line(nstate *state, int line)
{
int x, y;
if(line < WELL_NOTVISIBLE) return;
for(y = line - 1; y; y--)
for(x = WELL_WIDTH; x; x--)
state->blocks[0][y + 1][x] = state->blocks[0][y][x];
draw_well(state, 0);
}
static void block_reached_bottom(nstate *state)
{
int x, y;
if(!block_is_all_in_well(state)) {
state->state = STATE_STOPPED;
return;
}
for(y = WELL_HEIGHT - 1; y; y--) {
for(x = 0; x < WELL_WIDTH; x++)
if(!state->blocks[0][y][x]) goto nr;
msleep(DELETE_LINE_DELAY);
delete_line(state, y);
state->score += SCORE_INCREMENT;
if((LEVELS > (state->level + 1)) && (((state->level + 1) *
LEVEL_DIVISOR) <= state->score))
state->level++;
draw_score(state);
y++;
nr: ;
}
choose_new_shape(state);
draw_next_shape(state);
}
static void move_block(nstate *state, int direction)
{
if(direction == 0) {
if(!state->current_shape.x) return;
else {
if(!will_collide(state, (state->current_shape.x - 1),
state->current_shape.y,
state->current_shape.orientation)) {
draw_shape(state, state->current_shape.x,
state->current_shape.y, 1);
state->current_shape.x--;
draw_shape(state, state->current_shape.x,
state->current_shape.y, 0);
draw_well(state, 0);
}
}
} else {
if(!will_collide(state, (state->current_shape.x + 1),
state->current_shape.y,
state->current_shape.orientation)) {
draw_shape(state, state->current_shape.x,
state->current_shape.y, 1);
state->current_shape.x++;
draw_shape(state, state->current_shape.x,
state->current_shape.y, 0);
draw_well(state, 0);
}
}
}
static void rotate_block(nstate *state, int direction)
{
int neworientation = 0;
if(direction == 0) {
if(!state->current_shape.orientation)
neworientation = MAXORIENTATIONS - 1;
else neworientation = state->current_shape.orientation - 1;
} else {
neworientation = state->current_shape.orientation + 1;
if(neworientation == MAXORIENTATIONS) neworientation = 0;
}
if(!will_collide(state, state->current_shape.x, state->current_shape.y,
neworientation)) {
draw_shape(state, state->current_shape.x,
state->current_shape.y, 1);
state->current_shape.orientation = neworientation;
draw_shape(state, state->current_shape.x,
state->current_shape.y, 0);
draw_well(state, 0);
}
}
static int drop_block_1(nstate *state)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -