| Bytes | Lang | Time | Link |
|---|---|---|---|
| nan | 130501T191423Z | manatwor | |
| nan | 180625T220841Z | Andrew M | |
| nan | 140306T180730Z | Bigbio20 | |
| nan | 140226T230745Z | bobbel | |
| nan | 130418T192823Z | Henrik M | |
| 2876 | Lua | 130512T141557Z | mniip |
| nan | 130509T055834Z | Vitaliy |
Pascal
Developed in FreePascal 2.6.2, should compile with Turbo Pascal 6.0 too. Only the Crt unit is used, no external resources.
program tetris;
uses
Crt;
const
width = 10; { playing field width }
height = 20; { playing field height }
const
piece: array [1..7, 1..3, 0..1] of ShortInt = ( { piece shapes : piece, element, coordinate }
(( 1, 0), ( 1, 1), ( 0, 1)), { O }
((-1, 0), ( 1, 0), ( 2, 0)), { I }
(( 0, 1), ( 1, 0), ( 2, 0)), { L }
(( 0, 1), (-1, 0), (-2, 0)), { J }
((-1, 0), ( 1, 0), ( 0, 1)), { T }
(( 1, 0), ( 0, 1), (-1, 1)), { S }
((-1, 0), ( 0, 1), ( 1, 1)) { Z }
);
color: array [1..7, 0..1] of Byte = ( { piece colors : foreground, background }
(Yellow, Brown), { O }
(LightCyan, Cyan), { I }
(LightBlue, Blue), { L }
(White, LightGray), { J }
(LightMagenta, Magenta), { T }
(LightGreen, Green), { S }
(LightRed, Red) { Z }
);
var
area: array [-1..width + 2, -1..height + 2] of Byte; { playing field }
coord: array [0..3, 0..1] of ShortInt; { precalculated element coordinates }
played, { played pieces count }
removed, { completed lines count }
level, { current level }
score: LongInt; { accumulated score }
time, { current level delay }
wait, { current piece delay }
made, { completed lines in current level }
multi, { multiline count }
screen: Word; { original screen size }
i, j, j2: Integer; { counters }
current, { current piece }
next, { next piece }
x, { horizontal coordinate }
y, { vertical coordinate }
position, { rotation position }
k: Byte; { pressed key }
ok: Boolean; { aggregated condition }
{ precalculates the give piece's elements coordinates }
procedure coordinate(current, x, y, position: Byte);
begin
coord[0, 0] := x;
coord[0, 1] := y;
for i := 1 to 3 do begin
coord[i, 0] := x + piece[current, i, position mod 2] * (Ord(position in [0, 1]) * 2 - 1);
coord[i, 1] := y + piece[current, i, 1 - position mod 2] * (Ord(position in [0, 3]) * 2 - 1);
end;
end;
{ draws a piece }
procedure draw(current, x, y, position: Byte; visible: Boolean);
begin
coordinate(current, x, y, position);
for i := 0 to 3 do begin
GotoXY(coord[i, 0] * 2 + 1, coord[i, 1]);
if visible then begin
TextColor(color[current, 0]);
TextBackground(color[current, 1]);
Write('[]');
end else begin
TextBackground(Black);
Write(' ');
end;
end;
end;
{ check whether a piece can be placed in given position }
function check(x2, y2, position2: Byte): Boolean;
begin
coordinate(current, x2, y2, position2);
ok := True;
for i := 0 to 3 do if area[coord[i, 0], coord[i, 1]] <> 0 then ok := False;
if ok then begin
x := x2;
y := y2;
position := position2;
end;
check := ok;
end;
begin
Randomize;
TextColor(LightGray);
TextBackground(Black);
ClrScr;
screen := WindMax;
for i := -1 to width + 2 do for j := -1 to height + 2 do area[i, j] := 9 * Ord(not ((i in [1..width]) and (j in [1..height])));
Window(1, 1, width * 2 + 4, height + 2);
for i := 1 to (width + 2) * (height + 1) do Write('##');
Window(3, 1, width * 2 + 2, height);
ClrScr;
Window(1, 1, Lo(screen), Hi(screen));
Window(width * 2 + 7, 1, width * 2 + 25, 10);
Writeln('Next');
Writeln;
Writeln;
Writeln('Piece');
Writeln('Line');
Writeln('Level');
Writeln('Score');
Window(1, 1, Lo(screen), Hi(screen));
played := 0;
removed := 0;
level := 1;
score := 0;
current := 9;
next := 9;
made := 0;
repeat
if current = 9 then begin
if next = 9 then next := Random(7) + 1 else draw(next, width + 9, 1, 0, False);
current := next;
next := Random(7) + 1;
x := width div 2;
y := 1;
position := 0;
time := 1100 - level * 100;
wait := time;
Inc(played);
Inc(score);
if made = 25 then begin
Inc(level);
made := 0;
end;
draw(next, width + 9, 1, 0, True);
Window(width * 2 + 15, 4, width * 2 + 25, 10);
TextColor(LightGray);
TextBackground(Black);
Writeln(played:5);
Writeln(removed:5);
Writeln(level:5);
Writeln(score:5);
Window(1, 1, Lo(screen), Hi(screen));
if not check(x, y, position) then begin
GotoXY(width * 2 + 7, 10);
Write('Game Over');
Break;
end;
end;
draw(current, x, y, position, True);
GotoXY(1, 1);
repeat Delay(1);
Dec(wait);
until KeyPressed or (wait = 0);
draw(current, x, y, position, False);
if KeyPressed then begin
k := Ord(ReadKey);
case k of
75, 77: check(x + Ord(k = 77) * 2 - 1, y, position);
72, 80: check(x, y, (position + Ord(k = 80) * 2 + 1) mod 4);
32: begin
time := 1;
Inc(score);
end;
end;
end;
if wait = 0 then begin
if not check(x, y + 1, position) then begin
draw(current, x, y, position, True);
for i := 0 to 3 do area[coord[i, 0], coord[i, 1]] := current;
multi := 0;
for j := 1 to height do begin
ok := True;
for i := 1 to width do if area[i, j] = 0 then ok := False;
if ok then begin
for j2 := j downto 2 do for i := 1 to width do area[i, j2] := area[i, j2 - 1];
for i := 1 to width do area[i, 1] := 0;
Inc(score, 10 + multi * 2);
Inc(removed);
Inc(multi);
Window(3, 1, width * 2 + 2, height);
TextBackground(Black);
GotoXY(1, j);
DelLine;
GotoXY(1, 1);
InsLine;
Window(1, 1, Lo(screen), Hi(screen));
end;
end;
if multi <> 0 then Inc(made);
current := 9;
end;
wait := time;
end;
until k = 27;
ReadKey;
TextColor(LightGray);
TextBackground(Black);
ClrScr;
end.
Screenshot

(On Linux, in XTerm window.)
Control
- Left – move left
- Right – move right
- Up – rotate counterclockwise
- Down – rotate clockwise
- Space – drop down
- Esc – exit
Scoring
- played piece – 1 point
- dropped piece – 1 point
- completed line – 10 point
- multiple lines – multiplier * 2 point
Level starts at 1 and increases after each 25 line completing. (Multiple lines completed at once count as 1.)
Measurement
bash-4.2$ sed '
s/{[^{}]*}//g # remove comments
s/^ *\| *$//g # trim leading and trailing spaces
/^$/d # remove empty lines
s/ *\([:=<>,;+*-]\+\) */\1/g # no space around operators
' tetris.pas | wc -c
3697
Thanks
- Ulrich for spotting out a range overflow error
I built a javascript version https://marchingband.github.io/tetris/
<!DOCTYPE html>
<html>
<body>
<p>q and w rotate</p>
<p id='score'>0</p>
<div id='board' style='position:absolute;top:80px' ></div>
</body>
</html>
<script>
var pieceIndex=0
nextPiece=()=>{
pieceIndex=pieceIndex<pieces.length-1?pieceIndex+1:0
return pieces[pieceIndex]
}
const pieces=[
[
{x:0,y:-1},
{x:0,y:0},
{x:1,y:0},
{x:-1,y:0},
],
[
{x:0,y:-2},
{x:0,y:-1},
{x:0,y:0},
{x:0,y:1},
],
[
{x:1,y:0},
{x:0,y:0},
{x:2,y:0},
{x:0,y:-1},
],
[
{x:0,y:0},
{x:1,y:0},
{x:1,y:1},
{x:0,y:1}
],
[
{x:0,y:0},
{x:-1,y:0},
{x:0,y:1},
{x:1,y:1}
],
]
const speeds =[1000,800,600,400,300,250,200,150,100,90,80,70,60,50,40,30,20]
const boardWidth = 10
const boardHeight = 20
const pixelSize = 10
const pixelBorder = 0
const boardBorder = 5
var score = 0
var level = 0
var fallingBits = []
const b = document.getElementById('board')
piece = {
position:{
x:boardWidth/2,
y:1,
},
bits:[
{x:0,y:-1},
{x:0,y:0},
{x:1,y:0},
{x:-1,y:0},
],
reset(){
piece.position={x:boardWidth/2,y:1}
piece.bits=nextPiece()
!piece.coordinates().every((b)=>dom[b.x][b.y]?dom[b.x][b.y].style.background=='red':true) && endGame()
},
coordinates(){
return piece.bits.map((b)=>{
return {x:b.x+piece.position.x,y:b.y+piece.position.y}
})
}
}
const dom = []
for(var x=0;x<boardWidth;x++){
var row = []
for(var y=0;y<boardHeight;y++){
var cell = document.createElement('div')
cell.style.cssText='outline:thin solid;height:'+pixelSize+'px;width:'+pixelSize+'px;background:red;position:absolute'
cell.style.left= (x*(pixelSize+pixelBorder)+boardBorder) + 'px'
cell.style.top = (y*(pixelSize+pixelBorder)+boardBorder) + 'px'
row.push(cell)
b.appendChild(cell)
}
dom.push(row)
}
renderPiece=(color)=>{
piece.coordinates().forEach((b)=>dom[b.x][b.y] && (dom[b.x][b.y].style.background=color))
}
drop=()=>{
piece.coordinates().every((p)=>p.y+1<boardHeight) && piece.coordinates().every((p)=>dom[p.x][p.y+1].style.background!='blue') ?
movePiece(0,1) : freeze()
}
strafe=(d)=>{
piece.coordinates().every((p)=>p.x+d<boardWidth && p.x+d >=0) && piece.coordinates().every((p)=>dom[p.x+d][p.y].style.background!='blue') ?
movePiece(d,0) : null
}
freeze=()=>{
piece.coordinates().forEach((p)=>dom[p.x][p.y].style.background='blue')
lookForRows()
piece.reset()
}
lookForRows=()=>{
[...dom[0].keys()].filter((i)=>dom.every((c)=>c[i].style.background=='blue')).forEach((r)=>{
removeLine(r)
compressAbove(r)
addPoint()
})
}
compressAbove=(r)=>{
// dom.reduce((acc,col,x)=>{return acc.concat(col.map((b,y)=>{return {x:x,y:y}}).filter((b,y)=>y<r && dom[b.x][b.y].style.background=='blue'))},[]).forEach((b)=>dom[b.x][b.y].style.background='red').forEach((b)=>dom[b.x][b.y+1].style.background='blue')
var bits = []
dom.forEach((x,i)=>{
x.forEach((y,j)=>{
if(j<=r && dom[i][j].style.background=='blue'){bits.push({x:i,y:j})}
})
})
bits.forEach((b)=>{
dom[b.x][b.y].style.background='red'
})
bits.forEach((b)=>{
dom[b.x][b.y+1].style.background='blue'
})
}
removeLine=(r)=>{
dom.forEach((x)=>x[r].style.background='red')
}
changePiece=(x,y)=>{
piece.position.x += x
piece.position.y += y
}
movePiece=(x,y)=>{
renderPiece('red')
changePiece(x,y)
renderPiece('yellow')
}
turn=(d)=>{
var newBits = []
for(let i=0;i<piece.bits.length;i++){
var x = piece.bits[i].x
var y = piece.bits[i].y
var bit={}
if(d==1){
bit.x=-y
bit.y=x
}else{
bit.x=y
bit.y=-x
}
newBits.push(bit)
}
if(newBits.every((p)=>p.x+piece.position.x<boardWidth && p.x+piece.position.x>=0 && p.y+piece.position.y<boardHeight && dom[p.x+piece.position.x][p.y+piece.position.y].style.background!='blue')){
renderPiece('red')
piece.bits=newBits
renderPiece('yellow')
}
}
addPoint=()=>{
score++
document.getElementById('score').innerText=score
score-level*10 > 10 && levelUp()
}
levelUp=()=>{
clearInterval(timer)
level++
timer = window.setInterval(drop,speeds[level])
}
endGame=()=>{
clearInterval(timer)
dom.forEach((row)=>{
row.forEach((bit)=>{
bit.style.background=='red' && (bit.style.background='black')
})
})
}
document.onkeydown = function(e) {
switch (e.keyCode) {
case 37:strafe(-1);break
case 87:turn(1);break
case 81:turn(-1);break
case 39:strafe(1);break
case 40:drop();break
}
};
var timer = window.setInterval(drop,1000)
</script>
</html>
C
I wrote this yeaaaaars ago while sitting bored in class in high school. Supposedly, the original programmer wrote the first version of Tetris using square brackets as blocks, and it seemed simple enough to try and recreate, no? I didn't know any GUI stuff, so I made a good, old-fashioned console program. This was back before I learned C++ and those pesky proper programming practices, so the code may be a bit messy. I pretty much just winged it.

It meets all of the requirements of the challenge, except the piece only rotates in one direction (clockwise). Use WASD to play, W rotates the piece. The full source code and exe can be found here: http://sourceforge.net/projects/tklone/files/tklone/tklone-v1.0/
tetris.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include "defines.h"
int array[FIELD_H][FIELD_W]; //field array
int pieceCoords[2]; //current piece coordinates
int x, y; //looping variables
int thisPiece, nextPiece; //current pieces
double score; //the players current score
int newpiece=1; //the newpiece flag
int init_h=1; /*starting coordinates of piece*/
int init_w=4; /*these are obsolete variables*/
int h_offset, w_offset; //piece position offsets from start
int rtTop, rtBot, rtLeft, rtRight; //rotation thresholds for collision
int b1h, b1w, b2h, b2w, b3h, b3w; //block draw offsets
int rotation; /*0=normal view
1=90 degrees clockwise
2=upside-down
3=270 degrees clockwise */
clock_t speed; //time before piece falls (in milliseconds)
clock_t gravity; //temp variable for speed
//clock_t fps;
int piececount; //number of pieces that have fallen
int oldpiececount;
int level;
void GameInit(void);
int GameStart(void);
int GameMain(void);
void newPiece(void);
void scrollbar(void); //scrolling marquee bar
void drawField(void); //graphics rendering function
void setPiece(int piece); //this sets the individual piece offsets
void drawPiece(void); //this sets the blocks in the array
void initArray(void); //this initializes the array
void getMoves(char move); //input function
void rotate(int coll); //loops from 1 to 4
int collisionCheck(int side); //checks to see if piece will collide
int loseGame(void); //checks to see if game has ended
void eraseLastMove(void); //clears the array of the last move
void setLastMove(void); //sets the piece in place for the next piece
void lineClear(void); //clears filled lines and tabulates the score
void predict(int undo); //predicts the next piece rotation for collision
//void delay(int duration);
int main()
{
restart:
GameInit();
//some test blocks
/*for(x=FIELD_H;x>3;x--) //quick lose pattern
array[x][4]=1;*/
nextPiece=rand()%7; //generate a piece to use
drawField(); //draw a blank field. should be a title screen
printf(" / Welcome to TETRIS! \\"); //29 spaces and title
getch(); //wait to start the game
while(1) //main game loop
{
if(newpiece==1) //check to see if a new piece is required
newPiece();
//if(gravity<=clock())
gravity=speed+clock(); //get the end time
while(gravity>clock()) //count to the end time
{
if(kbhit())
{
getMoves(getch()); //get input
if(newpiece==1) //if piece reaches bottom...
break; //...skip by redraw to save some cycles
else
{
eraseLastMove(); //clear the last move from the array
drawPiece(); //set the piece in the array
drawField(); //refresh the screen
scrollbar(); //draw the scrollbar
}
}
}
if(newpiece==0) /*minor speed optimization*/
getMoves('s'); //force piece down a line
eraseLastMove(); //clear the last move from the array
drawPiece(); //set the piece in the array
drawField(); //refresh the screen
scrollbar(); //draw the scrollbar
}
return 0;
}
void GameInit()
{
srand((unsigned)time(NULL)); //seed the random number genreator...
initArray(); //...and initialize the array
//reset some variables
score=0;
piececount=-1; //set to -1 because of newpiece loop on start
oldpiececount=0;
speed=1000; //starting speed of piece
level=1;
}
void newPiece()
{
piececount++; //another piece has fallen
setLastMove(); //set the last move in place
lineClear(); //clear any full lines
newpiece=0; //reset the newpiece flag
rotation=0; //reset the piece rotation
h_offset=0; //reset the...
w_offset=0; //...piece coordinates
gravity=0; //reset the gravity
if(piececount-oldpiececount==10) //if 10 more pieces have fallen
if(speed!=200) //200 is the lowest speed it can go
{
oldpiececount=piececount; //set current number of fallen pieces
speed-=100; //make the speed faster
level++; //increment the level counter
}
thisPiece=nextPiece;//get the current piece
nextPiece=rand()%7; //pick the next piece
setPiece(thisPiece);//get piece data
if(loseGame()) //did player lose the game?
{
drawField(); //refresh the screen
printf("You lose! Final score: %.0f. Press any key to begin a new game.",score);
getch();
newpiece=1; //reset the newpiece flag /*add more initialization here*/
//goto restart; //return to start of the program
exit(0); //quit the game for now
}
drawPiece(); //set the piece in the array
drawField(); //refresh the screen
scrollbar(); //draw the scrollbar
}
void scrollbar()
{
//printf("================================================================================");
printf("Score: %.0f\t\t ",score); //print the score (6 spaces)
if(nextPiece==5||nextPiece==6) //print a space if the piece name is short
printf(" ");
printf("Next piece: ");
if(nextPiece==0) //print the name of the next piece
printf("straight");
else if(nextPiece==1)
printf("t-shaped");
else if(nextPiece==2)
printf("s-shaped");
else if(nextPiece==3)
printf("z-shaped");
else if(nextPiece==4)
printf("l-shaped");
else if(nextPiece==5)
printf("back-l");
else if(nextPiece==6)
printf("square");
printf("\t\t\tLevel %i",level); //print the level
}
void drawField()
{
system("cls"); //clear the screen
for(x=0;x<FIELD_H;x++)
{
printf(" |"); //thirty spaces
for(y=0;y<FIELD_W;y++)
if(array[x][y]==1||array[x][y]==2)
printf("[]");
else
printf(" ");
printf("| "); //thirty spaces
}
//bottom message bar
//scrollbar();
}
void drawPiece()
{
/* This sets the initial location of the piece with
movement offset. Coordinates are based on the
center of the piece. */
pieceCoords[0]=init_h+h_offset; //set height position
pieceCoords[1]=init_w+w_offset; //set width position
//mark the coordinates to draw the piece
array[(pieceCoords[0])][(pieceCoords[1])]=2; //center piece
array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]=2; //other...
array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]=2; //...three...
array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]=2; //...pieces
}
void initArray()
{
for(x=0;x<FIELD_H;x++)
for(y=0;y<FIELD_W;y++)
array[x][y]=0;
//array equals: 0 for empty
// 1 for full
// 2 for moving piece
}
void getMoves(char move)
{
switch(move)
{
case 'a':
case 'A':
if(collisionCheck(1)==0) //if the piece doesnt hit something...
w_offset--; //...move the piece
break;
case 'd':
case 'D':
if(collisionCheck(2)==0)
w_offset++;
break;
case 'w':
case 'W':
if(collisionCheck(0)==0)
{
rotate(0); //rotate the piece and...
setPiece(thisPiece); //...get new piece data
}
break;
case 's':
case 'S':
if(collisionCheck(3)==0)
h_offset++;
else //if the piece hits the bottom...
newpiece=1; //...set the newpiece flag
break;
default:
break;
}
}
void rotate(int coll)
{
if(coll==0) //rotate clockwise
{
if(rotation==3)
rotation=0;
else
rotation++;
}
if(coll==1) //rotate counterclockwise
{
if(rotation==0)
rotation=3;
else
rotation--;
}
}
int collisionCheck(int side)
{
if(side==0) //check rotation collision
{
predict(0);
/* For the first part, we check to see if the
rotation will collide with the sides. */
for(x=0;x<rtTop;x++) //check top side collision
{
if(pieceCoords[0]-x==0) //if piece minus top offset hits the top
{
predict(1);
return 1; //collision detected!
}
}
for(x=0;x<rtLeft;x++) //check left side collision
{
if(pieceCoords[1]-x==0)
{
predict(1);
return 1;
}
}
for(x=0;x<rtRight;x++) //check right side collision
{
if(pieceCoords[1]+x==(FIELD_W-1))
{
predict(1);
return 1;
}
}
for(x=0;x<rtBot;x++) //check bottom side collision
{
if(pieceCoords[0]+x==(FIELD_H-1))
{
predict(1);
return 1;
}
}
/* Then we check to see if any of the
blocks will hit other blocks on the field. */
if(array[(pieceCoords[0]+b1h)][(pieceCoords[1]+b1w)]==1
||array[(pieceCoords[0]+b2h)][(pieceCoords[1]+b2w)]==1
||array[(pieceCoords[0]+b3h)][(pieceCoords[1]+b3w)]==1)
{
predict(1);
return 1;
}
predict(1);
return 0;
}
/* Here, we scan the array and check to see whether
any of the blocks in the current piece are
adjacent to the field sides or another block. */
for(x=0;x<FIELD_H;x++) //scan the array
for(y=0;y<FIELD_W;y++)
if(array[x][y]==2) //if array location contains a block
{
if(side==1) //check left side
{
if(y==0||array[x][y-1]==1) //if piece is next to border or block...
return 1; //collision detected!
}
else if(side==2) //check right side
{
if(y==FIELD_W-1||array[x][y+1]==1)
return 1;
}
else if(side==3) //check bottom
{
if(x==FIELD_H-1||array[x+1][y]==1)
return 1;
}
}
return 0;
}
int loseGame() /*need to modify the conditions of losing*/
{
if(array[init_h][init_w]==1
||array[(init_h+b1h)][(init_w+b1w)]==1
||array[(init_h+b2h)][(init_w+b2w)]==1
||array[(init_h+b3h)][(init_w+b3w)]==1)
return 1;
else
return 0;
}
void eraseLastMove() /*need to optimize this*/
{
for(x=0;x<FIELD_H;x++) //scan the array
for(y=0;y<FIELD_W;y++)
if(array[x][y]==2) //if the spot is filled...
array[x][y]=0; //...clear the block
}
void setLastMove() /*also need to optimize this*/
{
for(x=0;x<FIELD_H;x++) //scan the array
for(y=0;y<FIELD_W;y++)
if(array[x][y]==2) //if the spot is filled...
array[x][y]=1; //...set the block
}
void lineClear()
{
int x2, y2; //new looping variables
int empty; //is the row empty?
int lines=0;//number of lines cleared
for(x=0;x<FIELD_H;x++) //scan the rows
{
empty=0; //reset the empty flag on each row
for(y=0;y<FIELD_W;y++) //scan the blocks in each row
if(array[x][y]==0) //if the row isn't full...
{
empty=1; //...set the empty flag and...
break; //...scan the next row
}
if(!empty)
{
for(y=0;y<FIELD_W;y++) //rescan the filled row...
array[x][y]=0; //...and clear the blocks
for(x2=x-1;x2>=0;x2--) //scan rows from the bottom up starting above cleared row
for(y2=0;y2<FIELD_W;y2++) //scan each block in the row
if(array[x2][y2]==1)
{
array[x2][y2]=0; //clear the block
array[x2+1][y2]=1; //fill in the block below it
}
lines++; //a line has been cleared
}
}
//tabulate the scores
if(lines==1)
score+=10;
else if(lines==2)
score+=25;
else if(lines==3)
score+=50;
else if(lines==4) /* TETRIS! */
score+=100;
}
void predict(int undo)
{
if(undo==0)
{
rotate(0); //get next rotation...
setPiece(thisPiece); //...and next piece data
}
if(undo==1)
{
rotate(1); //un-rotate
setPiece(thisPiece); //reset the piece
}
}
/*void delay(int duration)
{
clock_t done;
done = duration + clock();
while(done>clock())
; //sit and wait
}*/
setPiece.c
#include "defines.h"
extern int rtTop, rtBot, rtLeft, rtRight; //rotation thresholds for collision
extern int b1h, b1w, b2h, b2w, b3h, b3w; //block draw offsets
extern int rotation; /*0=normal view
1=90 degrees clockwise
2=upside-down
3=270 degrees clockwise */
void setPiece(int piece)
{
if(piece==0)
{
switch(rotation)
{
case 0: //piece looks identical rotated upside-down
case 2:
/* The rotation thresholds dictate how far away the piece
needs to be from the field edge for the next rotation. */
rtTop=1;
rtBot=2;
rtLeft=0;
rtRight=0;
/* These are the coordinates of the
blocks relative to the center block. */
b1h=-1;
b1w=0;
b2h=1;
b2w=0;
b3h=2;
b3w=0;
break;
case 1: //piece looks identical side to side
case 3:
rtTop=0;
rtBot=0;
rtLeft=1;
rtRight=2;
b1h=0;
b1w=-1;
b2h=0;
b2w=1;
b3h=0;
b3w=2;
break;
}
}
//draw blue straight piece
//snip other pieces
else if(piece==6)
{
switch(rotation)
{
case 0: //a cube looks identical when rotated 90 degrees
case 1:
case 2:
case 3:
rtTop=0; //piece doesn't rotate so no thresholds needed
rtBot=0;
rtLeft=0;
rtRight=0;
b1h=-1;
b1w=0;
b2h=-1;
b2w=1;
b3h=0;
b3w=1;
break;
}
}
//draw square piece
}
Java (Swing)
This is an implementation of the first historical Game Boy edition from Nintendo(c) from 1989.

How to play:
Z = rotate left
X = rotate right
Left = move left
Right = move right
Down = move down (slowly)
Up = rotate left (just for easier use)
R = reset game
I avoided to use more than one class (because the golf aspect comes to my mind). But now it's not golfable anymore in any case... However, I zipped and Base64-encoded one Font-File and one Image-File, so I can use it in the one and only class file.
To run it, copy the Java code into your IDE and start. You don't need any additional libraries or resources.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.zip.ZipInputStream;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;
import javax.xml.bind.DatatypeConverter;
public class JTetris extends javax.swing.JFrame {
static Image IMAGE;
static Font FONT;
static {
ZipInputStream zipImage = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
"UEsDBBQAAAAIABmiWkRZ0oOouwAAAJYKAAAFAAAAYy5ibXBz8p3GxQAGZUCsAcQ3gFgCiBkZWMDiCkB5GBCA0iJyLAwl+3oYYpJsGK58u8NAKjBGAgyUgVG7QHYxKUGBAjDiBJUUQUhQAMQGA3xsFL3MMBMNRrpdEABhQxQLCiDYmHZBxCF2QdhAu8CyEHLk2cWkBJSH" +
"xxcoCgyg5hsbAgWhbBgJUg9Wg24XWDGQHJF2QeOCUruQwEi0S0kRe5qHKCY2zQsgkv0ItAsIKC97EQnecGTYRQUwKNsbNAYAUEsBAj8AFAAAAAgAGaJaRFnSg6i7AAAAlgoAAAUAJAAAAAAAAAAgAAAAAAAAAGMuYm1wCgAgAAAAAAABABgAkJCiSyczzwGQkKJLJzPP" +
"AZCQoksnM88BUEsFBgAAAAABAAEAVwAAAN4AAAAAAA=="
)));
ZipInputStream zipFont = new ZipInputStream(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(
"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")
));
try {
zipImage.getNextEntry();
zipFont.getNextEntry();
IMAGE = ImageIO.read(zipImage);
FONT = Font.createFont(0, zipFont);
FONT = FONT.deriveFont(20.0f);
} catch (Exception e) {
System.exit(1);
}
}
int[] speeds = {887,820,753,686,619,552,468,368,284,184,167,150,133,117,100,100,83,83,66,66,50};
int[] points = {40,100,300,1200};
int[][][] stones = {
{{0,1},{1,1},{2,1},{0,2}}, // L 0
{{0,1},{1,1},{2,1},{3,1}}, // I 1
{{0,1},{1,1},{2,1},{2,2}}, // J 2
{{0,1},{1,1},{2,1},{1,2}}, // T 3
{{0,1},{1,1},{1,2},{2,2}}, // Z 4
{{1,1},{2,1},{0,2},{1,2}}, // S 5
{{1,1},{2,1},{1,2},{2,2}} // O 6
};
Color[] colors = {
new Color(220, 246, 212), // light
new Color(140, 190, 116), // green
new Color(60, 98, 92), // blue
new Color(4, 30, 20) // dark
};
int FREE = 0;
int BORDER = 8;
int DELTA = 20;
int WIDTH = 10;
int HEIGHT = 20;
int SIZE = 24;
int[][] field = new int[WIDTH][HEIGHT];
int[][] stone;
int deltaX;
int deltaY;
int curStone;
int nextStone = new Random().nextInt(7);
int score;
int level;
int lines;
int steps;
int speed = speeds[0];
Timer tickTimer = new Timer();
Image dbImage;
Image staticImage;
Graphics dbg;
int anim = -1;
boolean blink = false;
ArrayList<Integer> removeLinesRows = new ArrayList<Integer>();
boolean keyDown = true;
boolean freezeKeys = false;
private JTetris() {
setDefaultCloseOperation(2);
setResizable(false);
setSize(475, 480);
addPropertyChangeListener("isDirty", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
repaint();
}
});
reset();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
super.windowClosed(e);
tickTimer.cancel();
}
});
addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
keyDown = true;
}
@Override
public void keyPressed(KeyEvent e) {
if (freezeKeys) {
keyDown = false;
return;
}
switch (e.getKeyCode()) {
case 82: reset(); break;
case 37: updateField(-1, 0, false); break;
case 39: updateField(1, 0, false); break;
case 40: if (keyDown) updateField(0, 1, true); break;
case 38:
case 90: rotate(false); break;
case 88: rotate(true);
}
}
});
setVisible(true);
}
private void startTickTimer() {
if (tickTimer != null) {
tickTimer.cancel();
}
freezeKeys = false;
tickTimer = new Timer();
tickTimer.schedule(new TimerTask() {
public void run() {
updateField(0, 1, false);
}
}, 0, speed);
}
private void startRemoveLineTimer() {
if (!removeLinesRows.isEmpty()) {
anim = 7;
}
if (tickTimer != null) {
tickTimer.cancel();
}
tickTimer = new Timer();
tickTimer.schedule(new TimerTask() {
public void run() {
if (anim > 0) {
blink = !blink;
firePropertyChange("isDirty", false, true);
}
if (anim-- < 0) {
blink = false;
tickTimer.cancel();
if (removeLinesRows.size() > 0) {
int delta = 0;
for (int row : removeLinesRows) {
for (int r = row + delta++; r > 2; r--) {
for (int c = 0; c < WIDTH; c++) {
field[c][r] = field[c][r - 1];
}
}
}
score += points[removeLinesRows.size() - 1] * (level + 1);
lines += removeLinesRows.size();
level = lines / 10;
speed = speeds[level > 20 ? 20 : level];
removeLinesRows.clear();
}
createNextStone();
startTickTimer();
}
}
}, 50, 160);
}
public static void main(String[] a) {
new JTetris();
}
@Override
public void paint(Graphics g) {
if (dbImage == null) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
dbg.setFont(FONT);
staticImage = createImage(getWidth(), getHeight());
Graphics sg = staticImage.getGraphics();
sg.setFont(FONT);
sg.setColor(colors[3]);
sg.fillRect(0, 0, getWidth(), getHeight());
sg.setColor(colors[0]);
sg.fillRect(40 + WIDTH * SIZE + SIZE, 70, 170, 58);
sg.setColor(colors[2]);
sg.fillRect(40 + WIDTH * SIZE + SIZE, 73, 170, 52);
sg.setColor(colors[0]);
sg.fillRect(40 + WIDTH * SIZE + SIZE, 95, 170, 27);
sg.setColor(colors[2]);
sg.fillRect(40 + WIDTH * SIZE + SIZE, 98, 170, 3);
sg.setColor(colors[1]);
sg.fillRoundRect(40 + 13 * SIZE - 15, 40 + 13 * SIZE - 15, SIZE * 4 + 30, SIZE * 4 + 30, 20, 20);
sg.setColor(colors[0]);
sg.fillRoundRect(40 + 13 * SIZE - 12, 40 + 13 * SIZE - 12, SIZE * 4 + 24, SIZE * 4 + 24, 15, 15);
sg.fillRoundRect(330 - 6, 60 - 6, 120 + 12, 25 + 12, 15, 15);
sg.fillRoundRect(330 - 6, 155 - 6, 120 + 12, 60 + 12, 15, 15);
sg.fillRoundRect(330 - 6, 235 - 6, 120 + 12, 60 + 12, 15, 15);
sg.setColor(colors[2]);
sg.fillRoundRect(40 + 13 * SIZE - 9, 40 + 13 * SIZE - 9, SIZE * 4 + 18, SIZE * 4 + 18, 15, 15);
sg.fillRoundRect(330 - 3, 60 - 3, 120 + 6, 25 + 6, 15, 15);
sg.fillRoundRect(330 - 3, 155 - 3, 120 + 6, 60 + 6, 15, 15);
sg.fillRoundRect(330 - 3, 235 - 3, 120 + 6, 60 + 6, 15, 15);
sg.setColor(colors[3]);
sg.fillRoundRect(40 + 13 * SIZE - 6, 40 + 13 * SIZE - 6, SIZE * 4 + 12, SIZE * 4 + 12, 15, 15);
sg.setColor(colors[0]);
sg.fillRoundRect(330, 60, 120, 25, 5, 5);
sg.fillRect(40 + WIDTH * SIZE + SIZE, 40, 3, (HEIGHT - 2) * SIZE);
sg.fillRoundRect(40 + 13 * SIZE - 3, 40 + 13 * SIZE - 3, SIZE * 4 + 6, SIZE * 4 + 6, 5, 5);
sg.fillRoundRect(330, 155, 120, 60, 15, 15);
sg.fillRoundRect(330, 235, 120, 60, 15, 15);
sg.setColor(colors[3]);
sg.drawString("SCORE", 340, 80);
sg.drawString("LEVEL", 340, 180);
sg.drawString("LINES", 340, 260);
for (int r = 0; r < (HEIGHT - 2) * 24 / 18; r++) {
sg.drawImage(IMAGE, 16, 40 + r * 18, 16 + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
sg.drawImage(IMAGE, 40 + WIDTH * SIZE, 40 + r * 18, 40 + WIDTH * SIZE + SIZE, 58 + r * 18, 8 * SIZE, 0, 9 * SIZE, 18, this);
}
}
dbg.drawImage(staticImage, 0, 0, this);
dbg.setColor(colors[3]);
dbg.drawString(String.format("%6s", score > 999999 ? 999999 : score), 320, 120);
dbg.drawString(String.format("%4s", level > 20 ? 20 : level), 340, 205);
dbg.drawString(String.format("%4s", lines > 9999 ? 9999 : lines), 340, 285);
for (int x = 0; x < 4; x++) {
drawField(dbg, stones[nextStone][x][0] + 13, stones[nextStone][x][1] + 13, nextStone + 1);
}
for (int r = 2; r < HEIGHT; r++) {
for (int c = 0; c < WIDTH; c++) {
drawField(dbg, c, r - 2, field[c][r]);
}
}
if (blink) {
if (anim == 0) {
dbg.setColor(colors[0]);
} else {
dbg.setColor(colors[1]);
}
for (int row : removeLinesRows) {
dbg.fillRect(40, 40 + (row - 2) * SIZE, SIZE * WIDTH, SIZE);
}
}
g.drawImage(dbImage, 0, 0, this);
}
private void drawField(Graphics g, int x, int y, int idx) {
if (idx > DELTA) {
idx -= DELTA;
}
g.drawImage(IMAGE, 40 + x * SIZE, 40 + y * SIZE, SIZE + 40 + x * SIZE, SIZE + 40 + y * SIZE, idx * SIZE, 0, (idx + 1) * SIZE, SIZE, this);
}
private void createNextStone() {
curStone = nextStone;
stone = stones[curStone];
deltaX = (WIDTH + 1) / 2 - 2;
deltaY = 1;
if (!isMoveable(0, 0)) {
tickTimer.cancel();
JOptionPane.showMessageDialog(this, "Game over! Press OK for a new game.");
reset();
return;
}
updateField(0, 0, false);
nextStone = new Random().nextInt(7);
}
private void reset() {
score = 0;
steps = 0;
lines = 0;
level = 0;
speed = speeds[0];
for (int r = 0; r < HEIGHT; r++) {
for (int c = 0; c < WIDTH; c++) {
field[c][r] = FREE;
}
}
createNextStone();
startTickTimer();
}
private void updateField(int hor, int ver, boolean player) {
if (isMoveable(hor, ver)) {
if (player) {
steps++;
}
for (int x = 0; x < 4; x++) {
field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
}
deltaX += hor;
deltaY += ver;
for (int x = 0; x < 4; x++) {
field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
}
firePropertyChange("isDirty", false, true);
} else if (ver == 1) {
freezeKeys = true;
fixStone();
removeLines();
}
}
private void removeLines() {
outer:
for (int r = HEIGHT - 1; r >= 0; r--) {
for (int c = 0; c < WIDTH; c++) {
if (field[c][r] == FREE) {
continue outer;
}
}
removeLinesRows.add(r);
}
startRemoveLineTimer();
}
private void fixStone() {
score += steps;
steps = 0;
for (int x = 0; x < 4; x++) {
field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1 + DELTA;
}
}
private boolean isMoveable(int hor, int ver) {
for (int x = 0; x < 4; x++) {
int c = stone[x][0] + deltaX + hor;
int r = stone[x][1] + deltaY + ver;
if (c < 0 || c >= WIDTH || r < 0 || r >= HEIGHT) {
return false;
}
if (field[c][r] > DELTA) {
return false;
}
}
return true;
}
private void rotate(boolean r) {
if (curStone == 6) { // O
return;
}
synchronized (field) {
int[][] o = new int[4][2];
for (int x = 0; x < 4; x++) {
o[x] = new int[] { r ? 3 - stone[x][1] : stone[x][1], r ? stone[x][0] : 3 - stone[x][0] };
}
if (canRotate(o)) {
for (int x = 0; x < 4; x++) {
field[stone[x][0] + deltaX][stone[x][1] + deltaY] = FREE;
}
stone = o;
for (int x = 0; x < 4; x++) {
field[stone[x][0] + deltaX][stone[x][1] + deltaY] = curStone + 1;
}
firePropertyChange("isDirty", false, true);
}
}
}
private boolean canRotate(int[][] o) {
for (int x = 0; x < 4; x++) {
if (o[x][0] + deltaX < 0 ||
o[x][0] + deltaX >= WIDTH ||
o[x][1] + deltaY >= HEIGHT ||
field[o[x][0] + deltaX][o[x][1] + deltaY] > DELTA) {
return false;
}
}
return true;
}
}
TODOs:
- faster stones on higher level (done!)
- original remove line animation (done!)
- correct points for lines (done!)
- interrupt moving down on new stone (done!)
- highscore screen
- original rotation behavior
- menu for choosing level
- game-over screen
- audio
Comments are welcome :)
Try: http://tetris.muehe.org
Update There's a global high score. Enjoy beating it or - alternatively - hacking it :-)

CoffeeScript and HTML version, should fulfill the requirements to the best of my knowledge (and I have never really played Tetris).
$ make stats
4095
Github https://github.com/henrik-muehe/tetris
Features
- Exponential scoring
- Next piece indicator
- Fair randomness
- Rotation, move and drop down
- Game over indication
- Highscore with server backend that should not be that easily fakeable.
Lua - 2876
Tetris in a terminal, works on most unix systems, pure lua, no additional libs needed.
Controls are: wasd or hjkl, w/k to drop, s/j to rotate, ad/hl to move
Speed increases with score, whenever multiple lines are removed, you get the square of amount of destroyed lines
This is not the most possibly golfed solution, but i decided to golf it a little anyway. The newlines are just to fit text in 80 cols, i didn't include them in character count.
math.randomseed(os.time())if arg[1]then local function s(e)local e=io.popen(
"sleep "..e)e:read"*a"return e:close()end local l={{0,1,1,0,1,1},{-1,1,-1,0,1,0}
,{-1,0,1,0,1,1},{-1,0,0,1,1,0},{-1,1,0,1,1,0},{-1,0,0,1,1,1},{-1,0,1,0,2,0}}
function T(t,e,o)if o>1 then return T(-e,t,o-1)end return t,e end local t
function fit(i,n,r,o)if n<1 or n>10 or r<1 or t[r][n]~=0 then return end for e=1
,6,2 do local e,o=T(l[i][e],l[i][e+1],o)e,o=e+n,o+r if e<1 or e>10 or o<1 or((t[
o]or{})[e]or 0)~=0 then return end end return i end function put(e,r,i,n)t[i][r]
=e for o=1,6,2 do local n,o=T(l[e][o],l[e][o+1],n)n,o=n+r,o+i;(t[o]or{})[n]=e
end end while 1 do os.execute"clear"io.write("\27[0;37m+----------++----+\r\n"..
("| || |\r\n"):rep(2)..
"| |+----+\r\n| |Score:\r\n"..("| |\r\n"):rep(16)..
"+----------+\27[20F\27[C")t={}for e=1,20 do t[e]={}for o=1,10 do t[e][o]=0 end
end local e,o,f,r,a,c,n=20,5,0,math.random(7),math.random(7),0,1 while 1 do f=f+
1 s(.1)local d=io.open(arg[1],"rb")or os.exit(0,io.write"\27[49;39m",io.stdout:
flush(),os.execute"clear")local i=d:read(1)while i do if i=="a"or i=="h"then if
fit(r,o-1,e,n)then o=o-1 end elseif i=="d"or i=="l"then if fit(r,o+1,e,n)then o=
o+1 end elseif i=="w"or i=="k"then while fit(r,o,e-1,n)do e=e-1 end elseif i==
"s"or i=="j"then if fit(r,o,e,n+1)then n=n%4+1 end end i=d:read(1)end d:close()d
=io.open(arg[1],"w")d:write()d:close()if f%(math.max(1,10-math.floor(c/20)))==0
then if fit(r,o,e-1,n)then e=e-1 else put(r,o,e,n)r,e,o,a,n=a,20,5,math.random(7
),1 if not fit(r,o,e,n)then break end local e=1 local o=0 while e<21 do local n=
1 for o=1,10 do n=n*t[e][o]end if n~=0 then table.remove(t,e)local e={}for o=1,
10 do e[o]=0 end table.insert(t,e)o=o+1 else e=e+1 end end c=c+o*o end end put(r
,o,e,n)for e=20,1,-1 do for o=1,10 do io.write("\27[3"..t[e][o].."m"..(t[e][o]
==0 and" "or"#"))end io.write"\27[E\27[C"end io.write("\27[20F\27[13C\27[3",a,
"m")for e=1,0,-1 do for n=-1,2 do local o for t=1,6,2 do if l[a][t]==n and l[a][
t+1]==e then io.write"#"o=e end end if e==0 and n==0 then io.write"#"o=e end if
not o then io.write" "end end io.write"\27[4D\27[B"end io.write(
"\27[2B\27[D\27[49;37m",c,"\27[4F\27[C")t[e][o]=0 for i=1,6,2 do local r,n=T(l[r
][i],l[r][i+1],n)r,n=r+o,n+e;(t[n]or{})[r]=0 end end io.write"\27[49;39m"io.
stdout:flush()os.execute"clear"io.write
"\aGame over. Will automatically start new game in 5 seconds. Use ^C to exit"s(5
)io.stdout:flush()end else local e=os.tmpname()local o=io.open(e,"w")local n=
newproxy(true)getmetatable(n).__gc=function()os.execute"stty -raw echo"end os.
execute"stty raw -echo"local t=""i=0 while arg[i]do t=arg[i].." "..t i=i-1 end
os.execute(t..e.." &")while 1 do o:close()local t=io.read(1)o=io.open(e,"a")o:
write(t)if t=="\3"then o:close()os.remove(e)os.execute"stty -raw echo"
getmetatable(n).__gc=nil os.execute"sleep 0.1"os.exit()end end end

Mathematica
This code was written in Mathematica by Xiangdong Wen and can actually be played in a web browser here: Shape Descender (click on graphics to initiate arrows keys). Below are the screen shot and full code - which is pretty shor for a complete web app of this game.
Code
allBlocks = {{{{1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0,
0}}, {{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0,
0}}, {{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0,
0}}, {{0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0,
0}}}, {{{0, 2, 0, 0}, {2, 2, 2, 0}, {0, 0, 0, 0}, {0, 0, 0,
0}}, {{0, 2, 0, 0}, {0, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0,
0}}, {{0, 0, 0, 0}, {2, 2, 2, 0}, {0, 2, 0, 0}, {0, 0, 0,
0}}, {{0, 2, 0, 0}, {2, 2, 0, 0}, {0, 2, 0, 0}, {0, 0, 0,
0}}}, {{{0, 0, 3, 0}, {3, 3, 3, 0}, {0, 0, 0, 0}, {0, 0, 0,
0}}, {{0, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 3, 0}, {0, 0, 0,
0}}, {{0, 0, 0, 0}, {3, 3, 3, 0}, {3, 0, 0, 0}, {0, 0, 0,
0}}, {{3, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 0}}},
{{{0, 0, 0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0,
0, 0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0,
0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0,
0}, {0, 4, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}}, {{{0, 0, 0,
0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5,
0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}, {{0, 0, 0,
0}, {5, 5, 5, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 5,
0}, {0, 0, 5, 0}, {0, 0, 5, 0}, {0, 0, 5, 0}}}, {{{6, 6, 0,
0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6,
0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}, {{6, 6, 0,
0}, {0, 6, 6, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 6,
0}, {0, 6, 6, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}}, {{{0, 7, 7,
0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0,
0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}, {{0, 7, 7,
0}, {7, 7, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 0,
0}, {0, 7, 7, 0}, {0, 0, 7, 0}, {0, 0, 0, 0}}}};
smallBoard =
Table[If[i == 1 || j == 1 || i == 6 || j == 6, 9, 0], {i, 1, 6}, {j,
1, 6}];
color[v_] :=
Switch[v, 0, Black, 1, Yellow, 2, Blue, 3, Magenta, 4, Cyan, 5, Red,
6, Orange, 7, Green, 9, Gray, _, Gray];
showSquare[{i_, j_}, v_] := {color[v],
Rectangle[{i + 0.05, j + 0.05}, {i + 0.95, j + 0.95}]};
showHintBlock[n_, p_, x_, y_] :=
Table[showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], {i, 1,
4}, {j, 1, 4}];
initBoard[
Hold[board_, num_]] := {board =
Table[If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 9, 0], {i, 1,
16}, {j, 1, 28}],
Table[board[[i, 24]] = 0; board[[i, 25]] = 0;
board[[i, 26]] = 0, {i, 7, 10}];
Table[board[[i, 25]] = 0, {i, 1, 16}]; board[[6, 25]] = 9,
board[[11, 25]] = 9,
Table[If[Mod[j + i, 2] == 0, board[[j, i + 3]] = 9], {i, 1,
num}, {j, 4, 13}]};
bMoveTo[n_, p_, x_, y_, Hold[board_]] :=
Total[Table[
allBlocks[[n, p, i, j]]* board[[i + x, j + y]], {i, 1, 4}, {j, 1,
4}], 2] == 0;
bGameOver[n_, p_, Hold[board_]] :=
Not[bMoveTo[n, p, 6, 22, Hold[board]]];
bLeft[n_, p_, x_, y_, Hold[board_]] :=
bMoveTo[n, p, x - 1, y, Hold[board]];
bRight[n_, p_, x_, y_, Hold[board_]] :=
bMoveTo[n, p, x + 1, y, Hold[board]];
bDown[n_, p_, x_, y_, Hold[board_]] :=
bMoveTo[n, p, x, y - 1, Hold[board]];
bRotateAnticlock[n_, p_, x_, y_, Hold[board_]] :=
Module[{tp}, tp = p - 1; If[tp == 0, tp = 4];
bMoveTo[n, tp, x, y, Hold[board]]];
bRotateClockwise[n_, p_, x_, y_, Hold[board_]] :=
Module[{tp}, tp = p + 1; If[tp == 5, tp = 1];
bMoveTo[n, tp, x, y, Hold[board]]];
showBoard[Hold[board_]] :=
Table[showSquare[{i, j}, board[[i, j]]], {i, 3, 14}, {j, 3, 25}];
showDropBlock[n_, p_, x_, y_] :=
Table[If[allBlocks[[n, p, i, j]] != 0,
showSquare[{i + x, j + y}, allBlocks[[n, p, i, j]]], Black], {i,
1, 4}, {j, 1, 4}];
updateBoard[n_, p_, x_, y_,
Hold[board_, score_]] := {Table[
board[[i + x, j + y]] += allBlocks[[n, p, i, j]], {i, 1, 4}, {j,
1, 4}]; score +=
10 + 100*(2^
Sum[updateLine[i, Hold[board]], {i, y + 4, y + 1, -1}] -
1)};
updateLine[line_, Hold[board_]] :=
If[line <= 23 &&
line >= 4 && (Apply[And,
Table[board[[i, line]] != 0, {i, 4, 13}]]) == True,
Table[board[[i, j]] = board[[i, j + 1]], {i, 4, 13}, {j, line,
22}]; Table[board[[i, 23]] = 0, {i, 4, 13}]; 1, 0];
newGame[Hold[n_, p_, x_, y_, board_, nextItem_, gameOver_, pause_,
num_]] := {initBoard[
Hold[board, num]]; {n, p, x, y} = {nextItem, 2, 6, 22};
nextItem = RandomInteger[6] + 1; gameOver = False; pause = False};
newBlock[Hold[n_, p_, x_, y_, board_, score_, gameOver_, nextItem_,
pause_]] := {If[! pause && ! gameOver,
updateBoard[n, p, x, y, Hold[board, score]];
gameOver = bGameOver[nextItem, 2, Hold[board]];
If[gameOver == False, {n, p, x, y} = {nextItem, 2, 6, 22};
nextItem = RandomInteger[6] + 1]]};
shapeDescend[Hold[level_, state_, num_]] :=
DynamicModule[{score = 0, gameOver = False, pause = False,
board = Table[
If[i <= 3 || j <= 3 || i >= 14 || j >= 24, 0, 0], {i, 1,
16}, {j, 1, 28}], n, p, x, y, nextItem},
SeedRandom[level*10 + num]; nextItem = RandomInteger[6] + 1;
newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause, num]];
EventHandler[
Graphics[{Text[Style["Level", Blue, Italic, 24], {18, 12}],
Text[Style["Score", Blue, Italic, 24], {18, 9}],
Table[showSquare[{i + 16, j + 13}, smallBoard[[i, j]]], {i, 1,
6}, {j, 1, 6}],
Dynamic[Refresh[
Switch[state, 1,
newGame[Hold[n, p, x, y, board, nextItem, gameOver, pause,
num]]; state = 3,
2, pause = True,
3, pause = False], UpdateInterval -> Infinity,
TrackedSymbols -> {state}];
Refresh[If[! pause && ! gameOver &&
bDown[n, p, x, y, Hold[board]], y--,
newBlock[
Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
UpdateInterval -> 3./level, TrackedSymbols -> {}];
Join[{{Text[
Style[ToString[level], Green, Italic, 24], {20, 11}],
Text[Style[ToString[score], Green, Italic, 24], {20, 8}]}},
showBoard[Hold[board]], showDropBlock[n, p, x, y],
showHintBlock[nextItem, 2, 17, 14]],
TrackedSymbols -> {x, y, n, p, level}]}, Background -> Black,
ImageSize -> {400, 400}], {"LeftArrowKeyDown" :>
If[! pause && ! gameOver && bLeft[n, p, x, y, Hold[board]], x--],
"RightArrowKeyDown" :>
If[! pause && ! gameOver && bRight[n, p, x, y, Hold[board]],
x++], "DownArrowKeyDown" :> (If[! pause && ! gameOver &&
bRotateClockwise[n, p, x, y, Hold[board]], p++;
If[p == 5, p = 1]]),
"UpArrowKeyDown" :> (If[! pause && ! gameOver &&
bRotateAnticlock[n, p, x, y, Hold[board]], p--;
If[p == 0, p = 4]]), {"KeyDown", " "} :>
If[! pause && ! gameOver && bDown[n, p, x, y, Hold[board]], y--,
newBlock[
Hold[n, p, x, y, board, score, gameOver, nextItem, pause]]],
"EscapeKeyDown" :> {newGame[
Hold[n, p, x, y, board, nextItem, gameOver, pause, num]]}}]];
Manipulate[
shapeDescend[Hold[level, state, initLine]],
{{level, 5}, 1, 9, 1},
{{initLine, 3, "height of bottom level"}, 0, 9, 1},
{{state, 3, ""}, {1 -> "new game", 2 -> "pause", 3 -> "continue"}},
ContentSize -> {480, 450}, SynchronousUpdating -> False,
SaveDefinitions -> True, Alignment -> Center, TrackedSymbols -> {}]

