Monkey Fighter Monkey Fighter
Games Help Help Search Search Shop Shop
Tetris


Name: Tetris
Author Robert Walsh
Platform: Amiga
Language: C
Lincense: Free

Screen Shots

Opening Screen
Waiting to Play
Level One
Level Four
Game Over

Source

Tetris.c
Tetris.h

Executable

Tetris




Tetris

Overview:
In 1991 I wrote my first clone to prove that a playable version could be written in Basic for a stock Amiga 1000. After eight hour of coding, I created a perfect Tetris clone using Basic. Several years later, I wrote another Tetris clone. This time I used Lattice C and added my own twists to the game.


Game Design:
If you need a design description of Tetris, you probably live under a rock! For that reason, I am not including a description of the game. Instead, I will describe the deviations that I made from the classic Tetris.

To make programming the game a bit more interesting, I added several visual effects. When the game is not being played, a sprite with the Blue-Blood logo bounces on the bottom of the screen. Also, a simulated star field effect occupies the play area. To make the buttons more interesting, I framed them with a rotating lights effect.

Game play is changed in several way, too. Instead of removing completed rows instantly, they flash for a short while before disappearing. After ten rows disappear, the game goes to the next level. Each new level starts with all blocks cleared except for the prepositioned bricks which define the level. After ten levels (or is it eleven...), the game cycles back to the first level but the speed which the bricks drop is increased. If you complete another ten levels at this speed, you need professional help - tetris is not that fun.


Development:

The game board is a two dimesional array. It looks like I used a 13x15 matrix. However, one may be inclined to use a 15x16 matrix and fill the edges with nonremovable blocks. In the former case, one would need to check if the user moves the block out of the array bound [i.e. (x < 0)||(x > 12) ]. In the later, bound checking is performed when checking for collisions with previously dropped blocks.

All seven blocks can be represented in 2 two dimensional arrays - one for the X positions and one for the Y positions. The arrays I used are 4x28. The first index represents the block rotations. The second index manages the subblock positions. That is, each of the seven block have exactly four subblocks. The positions are offsets from a common point. So, the four offsets for the first block are first four values of the second index and the positions for the other blocks start at 4x. I know, a three dimensional array would allow a separate index for rotation, block and subblock offset but I prefer not to use them.

Now all there is to do is track the X, Y and rotational position of the falling block while checking for collisions. If a collision occurs in the Y dimension it lands. So, update the game board array accordingly. Then, check for completed rows an start the next block falling. Simple!


#include <intuition/intuition.h>
#include <exec/memory.h>
#include <devices/gameport.h>
#include <devices/inputevent.h>
#include <devices/audio.h>
#include <pragmas/graphics_pragmas.h>
#include <stdio.h>
#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <graphics/gfxmacros.h>
#include <graphics/sprite.h>
#include <graphics/gfxbase.h>
#include <mffp.h>
#include <proto/all.h>
#include "tetris.h
									

Except for the last include, these are all Amiga kernel routines that are needed for this project. The last one is a data file that I created to hold in game data. I could have loaded the game data from an external file during runtime but I didn't because I wanted this program to be a single file. That was common practice for Amiga developers.


extern struct Custom far custom
									

Custom is a struct in custom.h that we need to access some of the Amiga's custom hardware like the Copper chip. Lattice C requires that it be declared exactly like this.


BOOL UnitOpened=FALSE, DeviceOpened=FALSE
									

Variables that tell if the gameport (joystick) was acquired.


int X=155,Y=-36,LastX=0,LastY=0,JHorz=0,ButDown=0,Rock=0,NRock=4,
    Down=0,move=0,Angle=0,Score=0,Boxb[350],Play=-1,Gone=0,Lit=0,
    Clock=0,Level=1,LevDel=0,Keys=0,

    BBs   =30000,
    lands =3860,
    lines =3044,
    taps  =5160,
    rights=10690,
    boings=4180
									

I used a lot of global ints. They are pretty self explainatory. I would explain them all but then this tutorial would be very large. But, since you are wondering, the six variables at the end store sound sample sizes. Most people would declare these as constants but I don't like constants.


struct IntuitionBase *IntuitionBase;
struct GfxBase       *GfxBase;
struct Screen        *screen;
struct Window        *Win;
struct SimpleSprite   sprite1,sprite2,sprite3,sprite4,sprite5;
struct ViewPort      *viewport;
struct InputEvent     GPEvent;
struct IOStdReq      *GIOMsg=NULL;
struct MsgPort       *GMsgP=NULL,
                     *port,*port1,*port2,*port3;
struct IntuiMessage  *mess;
struct IOAudio       *AIOptr,*AIOptr1,*AIOptr2,*AIOptr3;
struct Message       *msg
									

Here are even more global variables. They all look like pointers to Kernal libraries to me.


ULONG device,device1,device2,device3;
UBYTE chan1[]={1,2,4,8}
									

Audio device variables. Need I say more?


char *Intro[]=
{  "TETRIS by BLUE-BLOOD SOFTWARE","",
   "Use joystick or arrow keys to control rock:",
   "UP-DOWN......for twisting the chunk around.",
   "LEFT-RIGHT...for moving the chunk sideways.",
   "Hold fire button for precise movements",
   "Complete five lines for a new level.","",
   "This version of Tetris replaces the old unplayable version.  How do",
   "know if you have an old version?  The new version opens and says",
   "Blue-Blood and the old version cheers All-Right.  If you have the old",
   "version then throw it away because you now have a better one. New",
   "features include keyboard controls, fast rock dropping ability, and",
   "game speed changes. :-) Space bar starts new game!!!","",
   "Send comments or contributions to:","",
   "Blue-Blood","574 County Road 69","Bovey, MN 55709","",
   "REMEMBER: THIS PROGRAM IS FREEWARE!!!"   }
									

The instructions stored as an char array. Please, don't send anything to that address. I haven't lived there for a million years. I you want to write to me, use the email icon at the top of this page. I you want to help this site financially, use the donate button located on the help page (the link is at the top of this page).


PLANEPTR BMap[3],BoxB[350]
									

The Amiga used bit plane graphics. Three bit maps would get you an eight color video display.


struct TextAttr Font=
{ (STRPTR)"topaz.font",TOPAZ_EIGHTY,FS_NORMAL,FPF_ROMFONT };

struct IntuiText
QuitT ={ 5,0,JAM2,16,7,&Font,(UBYTE *)"QUIT",NULL },
NextT ={ 5,0,JAM2,54,55,&Font,(UBYTE *)"NEXT",NULL },
ReseT ={ 5,0,JAM2,12,7,&Font,(UBYTE *)"RESET",NULL },
ScorT ={ 5,0,JAM2,0,2,&Font,(UBYTE *)"SCORE:",NULL },
LeveT ={ 5,0,JAM2,0,12,&Font,(UBYTE *)"LEVEL:",NULL }
									

Create the font and text strings that are used in the game.


struct Gadget Reset=
{  NULL,520,80,64,20,
   GADGHBOX,RELVERIFY,BOOLGADGET,
   NULL,NULL,&ReseT,NULL,NULL,2,NULL },

Quit=
{  &Reset,520,50,64,20,
   GADGHBOX,RELVERIFY,BOOLGADGET,
   NULL,NULL,&QuitT,NULL,NULL,1,NULL }
									

Two in game buttons that we can click to reset or quit the game.


struct NewScreen newscreen=
{  0,0,640,200,3,0,1,
   HIRES|SPRITES,CUSTOMSCREEN|SCREENQUIET,&Font,
   NULL,NULL,NULL
};

struct NewWindow newindow=
{  0,0,640,200,0,1,
   RAWKEY|GADGETUP, ACTIVATE|BORDERLESS|SIMPLE_REFRESH|RMBTRAP,
   NULL,NULL, NULL,NULL,NULL,
   640,200,640,200,
   CUSTOMSCREEN
}
									

These structures describe how the OS should open a new screen and a new window.


int PosX[4][28]=
{  0,12,12,24, 0,12,12,24, 0,0,12,12, 0,0,0,0,
   0,0,12,24,  0,0,12,24,  0,12,12,24,
   0,0,12,12,  0,0,12,12,  0,0,12,12, 0,12,24,36,
   0,0,0,12,   0,12,12,12, 0,0,0,12,
   0,12,12,24, 0,12,12,24, 0,0,12,12, 0,0,0,0,
   0,12,24,24, 0,12,24,24, 0,12,12,24,
   0,0,12,12,  0,0,12,12,  0,0,12,12, 0,12,24,36,
   0,12,12,12, 0,0,0,12,   0,12,12,12  },

PosY[4][28]=
{  12,12,24,24, 24,12,24,12, 24,12,24,12, 0,12,24,36,  12,24,24,24,
   12,24,12,12, 24,12,24,24,
   12,24,12,0,  0,12,12,24,  24,12,24,12, 12,12,12,12, 0,12,24,0,
   0,0,12,24,   0,12,24,12,
   12,12,24,24, 24,12,24,12, 24,12,24,12, 0,12,24,36,  12,12,12,24,
   24,24,24,12, 12,12,24,12,
   12,24,12,0,  0,12,12,24,  24,12,24,12, 24,24,24,24, 24,0,12,24,
   0,12,24,24,  12,0,12,24 },

R[]={ 0,0,0,0,4,9,5,3,9,9,9,9,9,9,9,9,9,9,7,6,9,0,0,0,0,0,0,0 },
G[]={ 0,9,5,7,4,0,5,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 },
B[]={ 0,0,0,0,4,0,5,3,0,0,0,0,0,0,0,0,0,9,7,6,0,9,5,7,0,9,5,7 },

Lev[11][20]=
{  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
   0,14,12,14,6,14,6,13,6,12,0,0,0,0,0,0,0,0,0,0,
   0,12,0,13,12,12,12,13,0,0,0,0,0,0,0,0,0,0,0,0,
   5,13,6,13,7,13,6,12,6,14,0,0,0,0,0,0,0,0,0,0,
   0,12,1,12,12,12,11,12,0,0,0,0,0,0,0,0,0,0,0,0,
   0,14,12,14,3,12,4,12,8,12,9,12,4,11,8,11,0,0,0,0,
   1,12,1,13,1,14,3,14,5,14,7,14,9,14,11,12,11,13,11,14,
   1,12,1,13,2,13,3,11,11,12,11,13,10,13,9,11,0,0,0,0,
   0,10,3,12,5,14,6,14,7,14,6,13,9,12,12,10,0,0,0,0,
   0,11,1,11,2,11,3,10,9,10,10,11,11,11,12,11,0,0,0,0,
   1,13,2,12,3,11,6,14,6,12,6,10,9,11,10,12,11,13,0,0
}
									

All seven rocks have exactly four subrocks. PosX[][] and PosY[][] are the position offsets for each subrock, for each rock, for each rotation. R[]G[]B[] are the color values for the indexed color palette. The Amiga had 4096 colors. So, the maximum value here would be 15! Lev[][] describes how the start of a new level should look. New levels just started with blocks already in the pit.


struct GamePortTrigger Fire=
{ GPTF_UPKEYS+GPTF_DOWNKEYS,1,1,1 }
									

Hummm, could it be the joystick button...


struct Star
{  int   X,Y,Dist;
   float Dir;       };

struct Star Stars[35];

struct Line
{
   int Rand,Row,Frame,Delay;
   struct Line *Next;
};

struct Line Head, Tail
									

Star is a struct for a starfield effect that is displayed in the game area when the game is not being played.


USHORT chip CubeData[]=
{
   0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7E00,0x7F00,
   0x7FFF,0x7F00,  0x7FFF,0x7F00,  0x7FFF,0x7F00,  0x7FFF,0x7F00,
   0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x0000,0x0100,

   0x8000,0x0000,  0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFDFF,0xFE00,
   0xFDFF,0xFE00,  0xFDFF,0xFE00,  0xFDFF,0xFE00,  0xFDFF,0xFE00,
   0xFD00,0x7E00,  0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00
},

BCubeData[]=
{
   0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,
   0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,
   0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x7FFF,0xFF00,  0x0000,0x0100,

   0x8000,0x0000,  0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00,
   0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00,
   0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00,  0xFFFF,0xFE00
},

BlankData[]=
{
   0x8000,0x0000,  0x8000,0x0000,  0x8000,0x0000,  0x8000,0x0000,
   0x8000,0x0000,  0x8000,0x0000,  0x8000,0x0000,  0x8000,0x0000,
   0x8000,0x0000,  0x8000,0x0000,  0x8000,0x0000,  0xFFFF,0xFF00,

   0xFFFF,0xFF00,  0x8000,0x0100,  0x8000,0x0100,  0x8000,0x0100,
   0x8000,0x0100,  0x8000,0x0100,  0x8000,0x0100,  0x8000,0x0100,
   0x8000,0x0100,  0x8000,0x0100,  0x8000,0x0100,  0xFFFF,0xFF00,

   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,
   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,
   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00
},

SkullData[]=
{
   0x8000,0x0000,  0x8000,0x0000,  0x883E,0x0800,  0x8200,0x8400,
   0x8008,0x2000,  0x8008,0x8000,  0x8001,0x0000,  0x8000,0x4000,
   0x8084,0x0400,  0x8804,0x0800,  0x8000,0x0000,  0xFFFF,0xFF00,

   0xFFFF,0xFF00,  0x8000,0x0100,  0x987E,0x1900,  0xA200,0xC500,
   0x843E,0x2100,  0x811C,0x8100,  0x8081,0x0100,  0x8200,0x4100,
   0xA0A5,0x0500,  0x981C,0x1900,  0x8000,0x0100,  0xFFFF,0xFF00,

   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,
   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,
   0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00,  0xFFFF,0xFF00
}
									

Graphic data associated with the blocks.


zer[]={ 0x07E0,0x1830,0x3018,0x3018,0x3018,0x3018,0x1830,0x0FE0},
one[]={ 0x1C00,0x0E00,0x0600,0x0600,0x0600,0x0600,0x0700,0x0380},
two[]={ 0x1FE0,0x3030,0x1818,0x0018,0x0070,0x03C0,0x0E00,0x3FFC},
thr[]={ 0x0FF0,0x1818,0x0030,0x01E0,0x0070,0x0018,0x1818,0x0FF0},
fou[]={ 0x1818,0x0C30,0x1860,0x3060,0x3FF8,0x0060,0x0060,0x0060},
fiv[]={ 0x07F8,0x0C00,0x1800,0x3FE0,0x0030,0x0018,0x3018,0x1FF0},
six[]={ 0x00F0,0x0380,0x0E00,0x1800,0x1FF8,0x180C,0x180C,0x0FF8},
sev[]={ 0x3FF8,0x0018,0x0060,0x00C0,0x0180,0x0300,0x0600,0x0600},
eig[]={ 0x0FF0,0x1818,0x1C38,0x0FF0,0x1C38,0x1818,0x1C38,0x0FF0},
nin[]={ 0x07F0,0x1818,0x1818,0x0FF8,0x0018,0x0018,0x0018,0x0018},
ten[]={ 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}
									

A custom font for displaying the score and such.


UWORD chip
Blood[]=
{  0,0,0xE55C,0x0000,0x9550,0x0000,0x9550,0x0000,0xE55C,0x0000,
   0x9550,0x0000,0x9550,0x0000,0xE5DC,0x0000,0x0000,0x0000,
   0xE4A6,0x0000,0x9555,0x0000,0x9555,0x0000,0xE555,0x0000,
   0x9555,0x0000,0x9555,0x0000,0xE4A6,0x0000,0,0  },

SprD2[]=
{  0,0,0x7FF0,0x8000,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x7070,0xFFE0,
   0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,
   0x7F70,0xE0E0,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x0010,0xFFE0,0,0  },

SprD3[]=
{  0,0,0x7FF0,0x8000,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x7070,0xFFE0,
   0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,
   0x7F70,0xE0E0,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x0010,0xFFE0,0,0  },

SprD4[]=
{  0,0,0x7FF0,0x8000,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x7070,0xFFE0,
   0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,
   0x7F70,0xE0E0,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x0010,0xFFE0,0,0  },

SprD5[]=
{  0,0,0x7FF0,0x8000,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x7070,0xFFE0,
   0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,0x7F70,0xEFE0,
   0x7F70,0xE0E0,0x7FF0,0xFFE0,0x7FF0,0xFFE0,0x0010,0xFFE0,0,0  }
									

Sprite data. The first one is the Blue Blood logo that bounces around the screen. The second is the subrocks. The falling rock is made up of four sprites. When it lands, the rock is drawn as bitmaped graphics to the screen data.


struct Image
Cube  ={ 0,0,24,12,2,&CubeData[0],3,0,NULL  },
BCube ={ 0,0,24,12,2,&BCubeData[0],3,0,NULL },
Skull ={ 0,0,24,12,3,&SkullData[0],7,0,NULL },
Blank ={ 0,0,24,12,3,&BlankData[0],7,0,NULL },
Num[] ={ 0,0,16,8,1,&zer[0],1,0,NULL,
         0,0,16,8,1,&one[0],1,0,NULL,
         0,0,16,8,1,&two[0],1,0,NULL,
         0,0,16,8,1,&thr[0],1,0,NULL,
         0,0,16,8,1,&fou[0],1,0,NULL,
         0,0,16,8,1,&fiv[0],1,0,NULL,
         0,0,16,8,1,&six[0],1,0,NULL,
         0,0,16,8,1,&sev[0],1,0,NULL,
         0,0,16,8,1,&eig[0],1,0,NULL,
         0,0,16,8,1,&nin[0],1,0,NULL }
									

Binding the image data to the image struture.


BOOL SetJoyType(type)
BYTE type;
{  BOOL success=FALSE;
   BYTE CType=0;

   Forbid();
   GIOMsg->io_Command=GPD_ASKCTYPE;
   GIOMsg->io_Length=1;
   GIOMsg->io_Flags=IOF_QUICK;
   GIOMsg->io_Data=(APTR)&CType;
   DoIO(GIOMsg);
   if(CType==GPCT_NOCONTROLLER)
   {
      GIOMsg->io_Command=GPD_SETCTYPE;
      GIOMsg->io_Flags=IOF_QUICK;
      GIOMsg->io_Length=1;
      GIOMsg->io_Data=(APTR)&type;
      DoIO(GIOMsg);
      success=TRUE;
      UnitOpened=TRUE;
   }
   Permit();
   return(success);

									

Try to acquire game port.


void SetFire(struct GamePortTrigger *gpt)
{  GIOMsg->io_Command=GPD_SETTRIGGER;
   GIOMsg->io_Length=(LONG)sizeof(struct GamePortTrigger);
   GIOMsg->io_Data=(APTR)gpt;
   DoIO(GIOMsg);

									

Setup required for the joystick fire button.


void Number(n,b,x,y)
register int n,x,y;
{  register int t,d;

   SetAPen(Win->RPort,2);
   SetDrMd(Win->RPort,0xC0);
   for(t=b;t>=1;t/=10)
   {  d=n/t; n-=(d*t);
         DrawImage(Win->RPort,&Num[d],x,y);
      x+=16;
   }
   SetAPen(Win->RPort,0);

									

This routine draws a number, n, to screen bitmap at location x, y. The number, n, can be any number between 0 and (10^n)-1.


void CloseUp()
{  BYTE type=GPCT_NOCONTROLLER;
   OFF_SPRITE;
   FreeSprite(2);
   FreeSprite(3);
   FreeSprite(4);
   FreeSprite(5);
   FreeSprite(1);
   if(UnitOpened)
   {  GIOMsg->io_Command=GPD_SETCTYPE;
      GIOMsg->io_Length=1;
      GIOMsg->io_Flags=IOF_QUICK;
      GIOMsg->io_Data=(APTR)&type;
      DoIO(GIOMsg);
   }
   if(port)          DeletePort(port);
   if(device==0)     CloseDevice((struct IORequest *)AIOptr);
   if(AIOptr)        FreeMem(AIOptr,sizeof(struct IOAudio));
   if(port1)          DeletePort(port1);
   if(device1==0)     CloseDevice((struct IORequest *)AIOptr1);
   if(AIOptr1)        FreeMem(AIOptr1,sizeof(struct IOAudio));
   if(port2)          DeletePort(port2);
   if(device2==0)     CloseDevice((struct IORequest *)AIOptr2);
   if(AIOptr2)        FreeMem(AIOptr2,sizeof(struct IOAudio));
   if(port3)          DeletePort(port3);
   if(device3==0)     CloseDevice((struct IORequest *)AIOptr3);
   if(AIOptr3)        FreeMem(AIOptr3,sizeof(struct IOAudio));
   if(DeviceOpened)  CloseDevice(GIOMsg);
   if(GIOMsg)        DeleteStdIO(GIOMsg);
   if(GMsgP)         DeletePort(GMsgP);
   if(Win)           CloseWindow(Win);
   if(screen)        CloseScreen(screen);
   if(IntuitionBase) CloseLibrary(IntuitionBase);
   if(GfxBase)       CloseLibrary((struct Library *)GfxBase);
   exit(1);

									

To prevent memory loss and resource locks, all the memory and resources allocated by this program must be freed. And, that is what this routine does.


void ShowNext()
{ register int t;

  RectFill(Win->RPort,22,70,118,118);
  for(t=0;t<=3;t++)
   DrawImage(Win->RPort,&Cube,22+(PosX[1][NRock+t]*2),70+PosY[1][NRock+t]);

									

Tetris allows the player a preview of the next rock that will fall. This routine displays that rock.


void SetBox(x,y,a)
register int x,y,a;
{  int b;
   PLANEPTR B;

   B=BMap[0]+(x>>3)+(y*80);
   b=128>>(x%8);
   BoxB[a]=B;
   Boxb[a]=b;

									

The box drawn around the reset and quit buttons have a walking light effect. This routine seems to draw them dotted line boxes.


void InitStars()
{  register int t;
   for(t=0;t<=35;t++)
   {  Stars[t].X=320;
      Stars[t].Y=100;
      Stars[t].Dist=0;
      Stars[t].Dir=RangeRand(360);
   }

									

As previously mentioned, there is a star field effect for when the game in not playing. This routine initializes the stars for that effect.


void Hear1(data,leng,per)
int leng,per;
BYTE *data;
{  AIOptr->ioa_Request.io_Message.mn_ReplyPort=port;
   AIOptr->ioa_Request.io_Command             =ADCMD_FINISH;
   BeginIO((struct IORequest *)AIOptr);
   if(1L<<port->mp_SigBit) msg=GetMsg(port);
   AIOptr->ioa_Request.io_Message.mn_ReplyPort=port;
   AIOptr->ioa_Request.io_Command             =CMD_WRITE;
   AIOptr->ioa_Request.io_Flags               =ADIOF_PERVOL;
   AIOptr->ioa_Data                           =data;
   AIOptr->ioa_Length                         =leng;
   AIOptr->ioa_Period                         =per;
   AIOptr->ioa_Volume                         =64;
   AIOptr->ioa_Cycles                         =1;
   BeginIO((struct IORequest *)AIOptr);
}

void Hear2(data,leng,per)
int leng,per;
BYTE *data;
{  AIOptr1->ioa_Request.io_Message.mn_ReplyPort=port1;
   AIOptr1->ioa_Request.io_Command             =ADCMD_FINISH;
   BeginIO((struct IORequest *)AIOptr1);
   if(1L<<port->mp_SigBit) msg=GetMsg(port);
   AIOptr1->ioa_Request.io_Message.mn_ReplyPort=port1;
   AIOptr1->ioa_Request.io_Command             =CMD_WRITE;
   AIOptr1->ioa_Request.io_Flags               =ADIOF_PERVOL;
   AIOptr1->ioa_Data                           =data;
   AIOptr1->ioa_Length                         =leng;
   AIOptr1->ioa_Period                         =per;
   AIOptr1->ioa_Volume                         =64;
   AIOptr1->ioa_Cycles                         =1;
   BeginIO((struct IORequest *)AIOptr1);
}

void Hear3(data,leng,per)
int leng,per;
BYTE *data;
{  AIOptr2->ioa_Request.io_Message.mn_ReplyPort=port2;
   AIOptr2->ioa_Request.io_Command             =ADCMD_FINISH;
   BeginIO((struct IORequest *)AIOptr2);
   if(1L<<port->mp_SigBit) msg=GetMsg(port);
   AIOptr2->ioa_Request.io_Message.mn_ReplyPort=port2;
   AIOptr2->ioa_Request.io_Command             =CMD_WRITE;
   AIOptr2->ioa_Request.io_Flags               =ADIOF_PERVOL;
   AIOptr2->ioa_Data                           =data;
   AIOptr2->ioa_Length                         =leng;
   AIOptr2->ioa_Period                         =per;
   AIOptr2->ioa_Volume                         =24;
   AIOptr2->ioa_Cycles                         =1;
   BeginIO((struct IORequest *)AIOptr2);
}

void Hear4(data,leng,per)
int leng,per;
BYTE *data;
{  AIOptr3->ioa_Request.io_Message.mn_ReplyPort=port3;
   AIOptr3->ioa_Request.io_Command             =ADCMD_FINISH;
   BeginIO((struct IORequest *)AIOptr3);
   if(1L<<port->mp_SigBit) msg=GetMsg(port);
   AIOptr3->ioa_Request.io_Message.mn_ReplyPort=port3;
   AIOptr3->ioa_Request.io_Command             =CMD_WRITE;
   AIOptr3->ioa_Request.io_Flags               =ADIOF_PERVOL;
   AIOptr3->ioa_Data                           =data;
   AIOptr3->ioa_Length                         =leng;
   AIOptr3->ioa_Period                         =per;
   AIOptr3->ioa_Volume                         =64;
   AIOptr3->ioa_Cycles                         =1;
   BeginIO((struct IORequest *)AIOptr3);

									

These four routine starts sampled data playing through the selected audio channel. Data is the sampled data, length is the length of the sample, and per is the rate (period) at which the sample is played.


void NewGame()
{  struct Line *Temp=Head.Next;
   Down=0;
   MoveSprite(NULL,&sprite2,350,0);
   MoveSprite(NULL,&sprite3,350,0);
   MoveSprite(NULL,&sprite4,350,0);
   MoveSprite(NULL,&sprite5,350,0);
   RectFill(Win->RPort,166,0,477,179);
   while(Temp->Next)
   {  Head.Next=Temp->Next;
      FreeMem(Temp,sizeof(struct Line));
      Temp=Head.Next;
   }
   Rock=RangeRand(7)<<2;
   NRock=RangeRand(7)<<2;
   ShowNext();
   Gone=0; Score=0; Level=1; Y=-35; X=155;
   InitStars();
   Number(Score,10000,56,1);
   Number(Level,10,56,11);

									

NewGame initializes all the variables to start a new game.


void ClearStars()
{  register int t,b;

   for(t=0;t<=35;t++)
   {  b=128>>(Stars[t].X%8);
      *(BMap[0]+(Stars[t].X>>3)+(Stars[t].Y*80))^=b;
} 

ClearStars will erase all the stars from the bitmap.


void Init()
{  int t,n=0;
   SHORT SprNum;

   InitStars();

   if(!(IntuitionBase=(struct IntuitionBase *)
    OpenLibrary("intuition.library",0L)))
     CloseUp();
   if(!(screen=(struct Screen *)OpenScreen(&newscreen)))
    CloseUp();
   newindow.Screen=screen;
   if(!(Win=(struct Window *)OpenWindow(&newindow)))
    CloseUp();
   if(!(GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L)))
      CloseUp();
   if(!(GMsgP=CreatePort(0,0L)))
      CloseUp();
   if(!(GIOMsg=CreateStdIO(GMsgP)))
      CloseUp();
   if(OpenDevice("gameport.device",1,GIOMsg,0))
      CloseUp();
   else DeviceOpened=TRUE;
   if(!(SetJoyType(GPCT_RELJOYSTICK)))
      CloseUp();
   if(!(AIOptr=(struct IOAudio *)
      AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR)))
         CloseUp();
   if(!(port=CreatePort(0,0)))
      CloseUp();
   if(!(AIOptr1=(struct IOAudio *)
      AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR)))
         CloseUp();
   if(!(port1=CreatePort(0,0)))
      CloseUp();
   if(!(AIOptr2=(struct IOAudio *)
      AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR)))
         CloseUp();
   if(!(port2=CreatePort(0,0)))
      CloseUp();
   if(!(AIOptr3=(struct IOAudio *)
      AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR)))
         CloseUp();
   if(!(port3=CreatePort(0,0)))
      CloseUp();

   SetFire(&Fire);

   Head.Next=&Tail;
   Tail.Next=NULL;
   BMap[0]=screen->BitMap.Planes[0];
   BMap[1]=screen->BitMap.Planes[1];
   BMap[2]=screen->BitMap.Planes[2];
   viewport=&(screen->ViewPort);
   for(t=0;t<=27;t++)
      SetRGB4(viewport,t,R[t],G[t],B[t]);
   SprNum=GetSprite(&sprite1,1);
   SprNum=GetSprite(&sprite2,2);
   SprNum=GetSprite(&sprite3,3);
   SprNum=GetSprite(&sprite4,4);
   SprNum=GetSprite(&sprite5,5);
   sprite1.height=15;
   sprite2.height=12;
   sprite3.height=12;
   sprite4.height=12;
   sprite5.height=12;
   ChangeSprite(NULL,&sprite1,(APTR)Blood);
   ChangeSprite(NULL,&sprite2,(APTR)SprD2);
   ChangeSprite(NULL,&sprite3,(APTR)SprD3);
   ChangeSprite(NULL,&sprite4,(APTR)SprD4);
   ChangeSprite(NULL,&sprite5,(APTR)SprD5);
   for(t=520;t<=584;t++)
      { SetBox(t,50,n);  SetBox(t,80,n+164);  n++; }
   for(t=51;t<=70;t++)
      { SetBox(584,t,n); SetBox(584,t+30,n+164); n++; }
   for(t=583;t>=520;t--)
      { SetBox(t,70,n);  SetBox(t,100,n+164);  n++; }
   for(t=69;t>=51;t--)
      { SetBox(520,t,n); SetBox(520,t+30,n+164); n++; }
   BoxB[n]=BoxB[0]; Boxb[n]=Boxb[0];

									

Init begin by opening the needed Amiga kernel libraries and requesting/allocating resources. Then, it initializes the game variables.


void InitAud()
{  AIOptr->ioa_Request.io_Message.mn_ReplyPort  =port;
   AIOptr->ioa_Request.io_Message.mn_Node.ln_Pri=0;
   AIOptr->ioa_Request.io_Command               =ADCMD_ALLOCATE;
   AIOptr->ioa_Request.io_Flags                 =ADIOF_NOWAIT;
   AIOptr->ioa_AllocKey                         =0;
   AIOptr->ioa_Data                             =chan1;
   AIOptr->ioa_Length                           =sizeof(chan1);
   if(device=OpenDevice
       ("audio.device",0L,(struct IORequest *)AIOptr,0L))
         CloseUp();

   AIOptr1->ioa_Request.io_Message.mn_ReplyPort  =port1;
   AIOptr1->ioa_Request.io_Message.mn_Node.ln_Pri=0;
   AIOptr1->ioa_Request.io_Command               =ADCMD_ALLOCATE;
   AIOptr1->ioa_Request.io_Flags                 =ADIOF_NOWAIT;
   AIOptr1->ioa_AllocKey                         =0;
   AIOptr1->ioa_Data                             =chan1;
   AIOptr1->ioa_Length                           =sizeof(chan1);
   if(device1=OpenDevice
       ("audio.device",0L,(struct IORequest *)AIOptr1,0L))
         CloseUp();

   AIOptr2->ioa_Request.io_Message.mn_ReplyPort  =port2;
   AIOptr2->ioa_Request.io_Message.mn_Node.ln_Pri=0;
   AIOptr2->ioa_Request.io_Command               =ADCMD_ALLOCATE;
   AIOptr2->ioa_Request.io_Flags                 =ADIOF_NOWAIT;
   AIOptr2->ioa_AllocKey                         =0;
   AIOptr2->ioa_Data                             =chan1;
   AIOptr2->ioa_Length                           =sizeof(chan1);
   if(device2=OpenDevice
       ("audio.device",0L,(struct IORequest *)AIOptr2,0L))
         CloseUp();

   AIOptr3->ioa_Request.io_Message.mn_ReplyPort  =port3;
   AIOptr3->ioa_Request.io_Message.mn_Node.ln_Pri=0;
   AIOptr3->ioa_Request.io_Command               =ADCMD_ALLOCATE;
   AIOptr3->ioa_Request.io_Flags                 =ADIOF_NOWAIT;
   AIOptr3->ioa_AllocKey                         =0;
   AIOptr3->ioa_Data                             =chan1;
   AIOptr3->ioa_Length                           =sizeof(chan1);
   if(device3=OpenDevice
       ("audio.device",0L,(struct IORequest *)AIOptr3,0L))
         CloseUp();
   Hear1(&BB,BBs,240);
   Hear2(&BB,BBs,240);
   Hear3(&BB,BBs,250);
   Hear4(&BB,BBs,240);

									

This code initializes the audio devices so that some sound effects can be played.


void Board()
{  register int x;

   NewGame();
   for(x=0;x<=15;x++)
   {  DrawImage(Win->RPort,&Cube,142,x*12);
      DrawImage(Win->RPort,&Cube,478,x*12);
   }
   for(x=0;x<=14;x++)
      DrawImage(Win->RPort,&Cube,142+(x*24),180);
   PrintIText(Win->RPort,&NextT,0,0);
   PrintIText(Win->RPort,&ScorT,0,0);
   PrintIText(Win->RPort,&LeveT,0,0);
   ON_SPRITE;

									

Board draws the game board and all the other stuff that you see on the screen when you play this game.


void DrawRock()
{  register int x;

   x=X-1;
   MoveSprite(NULL,&sprite2,x+PosX[Angle][Rock],Y+PosY[Angle][Rock]);
   MoveSprite(NULL,&sprite3,x+PosX[Angle][Rock+1],Y+PosY[Angle][Rock+1]);
   MoveSprite(NULL,&sprite4,x+PosX[Angle][Rock+2],Y+PosY[Angle][Rock+2]);
   MoveSprite(NULL,&sprite5,x+PosX[Angle][Rock+3],Y+PosY[Angle][Rock+3]);

									

Draws the falling rock. We know this because we are moving sprites, not bitmap data.


void Turn()
{  register int a;

   a=Angle++;
   if(Angle>3) Angle=0;
   if(Hit()) Angle=a;
   else Hear3(&tap,taps,500);

									

Turn turns the falling rock to its next rotational positon and checks to make sure that it didn't cause a collision.


void JoyDir()
{  if(!Keys)
   { GIOMsg->io_Command=GPD_READEVENT;
     GIOMsg->io_Length=sizeof(struct InputEvent);
     GIOMsg->io_Data=(APTR)&GPEvent;
     DoIO(GIOMsg);
     GetMsg(GMsgP);
     JHorz=GPEvent.ie_X;
     Down=GPEvent.ie_Y;
     switch(GPEvent.ie_Code)
     {  case IECODE_LBUTTON: ButDown=1;
                             break;
        case (IECODE_LBUTTON|IECODE_UP_PREFIX):
                             ButDown=0;
                             break;
  } }

   if((Down<0)&&!LastY) Turn();
   if(ButDown&&!LastX) move=JHorz*6;
   if(!ButDown) move=JHorz*6;
   LastX=JHorz;
   LastY=Down;

									

Here's where we read the joystick read the joystick and move the rock all in one routine.


int Hit()
{  register int r=0,t,x,y,b;
   PLANEPTR B1=0;

   for(t=0;t<=3;t++)
   {  y=Y+PosY[Angle][Rock+t]+12;
      if(y>0)
      {  x=(X+PosX[Angle][Rock+t])<<1;
         b=128>>(x%8);
         x=(x>>3)+(y*80);
         *B1=*(BMap[0]+x)|*(BMap[1]+x)|*(BMap[2]+x);
         r|=(*B1&b);
   }  }
   return(r);

									

No game is complete without collision detection. This game is no exception. However, collision detection is easy to do in Tetris because it is done merely by checking the game grid coordinates for true.


void Slide()
{  register int x;
   x=X;
   if(Y<-12) move=0;
   if(move<0) X-=1;
   if(move>0) X+=12;
   if(!Hit())
   {  Hear3(&tap,taps,500);
      while(move)
      {  if(move<0)
           { x-=2; move++; }
         if(move>0)
           { x+=2; move--; }
         DrawRock();
   }  }
   else move=0;
   X=x;

									

The falling rock can be moved left or right by the player as long as it doesn't collide with anything.


void CheckLine(int l)
{  register int t,n=0,c;
   register struct Line *Temp;

   for(t=167;t<=455;t+=24)
   {  c=ReadPixel(Win->RPort,t,l*12);
      if(c&&c<4) n++;
   }
   if(n>=13)
   {  Temp=AllocMem(sizeof(struct Line),MEMF_PUBLIC);
      Temp->Next=Head.Next;
      Head.Next=Temp;
      Temp->Row=l;
      Temp->Frame=0;
      Temp->Delay=0;
      Temp->Rand=(RangeRand(15)+10);
} 

If any given row has a subrock in every position, that line is to be removed. However, this Tetris has a twist. Instead of removing the line immediately, it flashes for a random period and then it is removed.


void KillLine()
{  register struct Line *Temp=Head.Next,*Last=&Head,*Renew;
   register int t,row;

   while(Temp->Next)
   {  row=Temp->Row;
      if(Temp->Delay++>=Temp->Rand)
      {  Temp->Delay=0;
         Temp->Frame++;
         if(Temp->Frame<30)
         {  if(Temp->Frame&1)
               for(t=0;t<=12;t++)
                  DrawImage(Win->RPort,&Blank,166+t*24,row*12);
            else
               for(t=0;t<=12;t++)
                  DrawImage(Win->RPort,&Skull,166+t*24,row*12);
         }
       else
       {  ScrollRaster(Win->RPort,0,-12,166,0,477,(row*12)+11);
          Last->Next=Temp->Next;
          FreeMem(Temp,sizeof(struct Line));
          Temp=Last->Next;
          Renew=Head.Next;
          Score+=(16-row)*100;
          Hear2(&lne,lines,400);
          Number(Score,10000,56,1);
          Gone++;
          while(Renew->Next)
          {  if(Renew->Row<row) Renew->Row++;
             Renew=Renew->Next;
      }}}
      if(Temp->Next)
      {  Last=Last->Next;
         Temp=Temp->Next;
}  } 

KillLine removes that line that was flashing for a random time peroid. It even lowers all the rocks above it.


void Dead()
{  int x, y;

   for(y=0;y<179;y+=12)
      for(x=166;x<=477;x+=24)
      {  if(ReadPixel(Win->RPort,x,y))
            DrawImage(Win->RPort,&Skull,x,y);
      }
   X=350; Y=200; Play=-1; LastX=1;

									

Its curtains for the player when a rock cannot drop into the playing pit. In other words, your dead; game over, etc. And, you'll know this happened because all the rocks in the pit turned to skulls!


void Land()
{  register int t,l;

   for(t=0;t<=3;t++)
      DrawImage(Win->RPort,&BCube,(X+PosX[Angle][Rock+t])*2,
                                  Y+PosY[Angle][Rock+t]);
   GIOMsg->io_Command=CMD_CLEAR;
   DoIO(GIOMsg);
   Down=0;
   Rock=NRock;
   NRock=RangeRand(7)*4;
   ShowNext();
   Hear1(&land,lands,650);
   if(Y>0)
   {  for(t=0;t<=3;t++)
      {  l=(Y/12)+t;
         if(l<=14) CheckLine(l);
      }
      Y=-35; X=155; Angle=1;
      Score+=Level;
      Number(Score,10000,56,1);
   }
   else Dead();

									

The falling rock is four sprites but when it lands, the rock is drawn to the screen bitmap. This routine does all that and it even updates the score.


void Bounce()
 { static int BBX=50,BBY=50,BBXA=3,BBYA=0;

   BBX+=BBXA; BBYA+=1; BBY+=BBYA;
   if(BBX>=319||BBX<=1) { BBX-=BBXA; BBXA=-BBXA; }
   if(BBY>190) { BBYA=-BBYA; BBY+=BBYA; Hear3(&boing,boings,500); }
   MoveSprite(NULL,&sprite1,BBX,BBY);

									

Another game effect is the bouncing Blue-Blood software logo. Its a sprite so all we need to move it is to update the location coordinates. But, I added an annoying boing sound effect to liven things up.


void NewMsg()
{  Keys=0;
   if(mess=(struct IntuiMessage *)GetMsg(Win->UserPort))
   {  ReplyMsg((struct message *)mess);
      switch(mess->Class)
      {  case GADGETUP: switch(((struct Gadget *)mess->IAddress)->GadgetID)
                        {  case 1: CloseUp();
                                   break;
                           case 2: ClearStars();
                                   Hear4(&tap,taps,400);
                                   NewGame();
                                   Play=-1;
                                   break;
                        }
                        break;
         case RAWKEY:   switch(mess->Code)
                        {  case 95: if(mess->Qualifier&IEQUALIFIER_LSHIFT)
                                    Gone=6;
                                    break;
                           case 64: MoveSprite(NULL,&sprite1,268,110);
                                    ClearStars();
                                    NewGame();
                                    Hear4(&tap,taps,400);
                                    Play=1;
                                    break;
                           case 79: JHorz=-1;
                                    Keys=1;
                                    break;
                           case 78: JHorz= 1;
                                    Keys=1;
                                    break;
                           case 76: Down=-1;
                                    Keys=1;
                                    break;
                           case 77: Down= 1;
                                    Keys=1;
                                    break;
                        }
                        break;
}  } 

This routine processes any messages that are in the message buffer. As you can see, that would be those two button (reset and quit) and a couple of keys (for those without a joystick).


void Lights()
{  register int l;

   for(l=Lit;l<=328;l+=8)
   {  *BoxB[l]^=Boxb[l];
      *BoxB[l+1]|=Boxb[l+1];
   }
   if(++Lit&8) Lit=0;

									

The box drawn around the reset and quit buttons have a walking light effect. This is the routine that makes them dotted lines move.


void NewStar(register int T)
{  Stars[T].X=320;
   Stars[T].Y=100;
   Stars[T].Dir=T^!Lit;
   Stars[T].Dist=0;

									

Another routine for the star field effect. This one creates a new star by putting it in the center of the display so that it may be moved outward like you were flying past it through space.


void MoveStars()
{  register int t,b;

   for(t=0;t<=35;t++)
   {  b=128>>(Stars[t].X%8);
      *(BMap[0]+(Stars[t].X>>3)+(Stars[t].Y*80))^=b;

      Stars[t].X+=(++Stars[t].Dist)*SPCos(Stars[t].Dir);
      Stars[t].Y+=Stars[t].Dist*SPSin(Stars[t].Dir);
      if(Stars[t].X>477||Stars[t].X<166||
         Stars[t].Y>179||Stars[t].Y<0) NewStar(t);
      else
      {  b=128>>(Stars[t].X%8);
         *(BMap[0]+(Stars[t].X>>3)+(Stars[t].Y*80))^=b;
}  } 

Here is where the new stars created in the last routine are moved.


void NewLev()
{  register int x,y;
   register struct Line *Temp=Head.Next;

   Down=0;
   switch(LevDel)
   {  case 5:   while(Temp->Next)
                {  Head.Next=Temp->Next;
                   FreeMem(Temp,sizeof(struct Line));
                   Temp=Head.Next;
                }
                MoveSprite(NULL,&sprite2,350,0);
                MoveSprite(NULL,&sprite3,350,0);
                MoveSprite(NULL,&sprite4,350,0);
                MoveSprite(NULL,&sprite5,350,0);
                break;
      case 30:  for(y=0;y<=179;y+=12)
                 for(x=166;x<=477;x+=24)
                  DrawImage(Win->RPort,&Cube,x,y);
                break;
      case 60:  Hear1(&right,rights,440);
                RectFill(Win->RPort,166,0,477,179);
                y=Level; if(y>10) y%=10;
                for(x=0;x<=9;x++)
                 if(Lev[y][x*2+1])
                  DrawImage(Win->RPort,&Cube,166+24*Lev[y][x*2],
                                                 12*Lev[y][x*2+1]);
                break;
      case 90:  Y=-36; X=155; Level++; Gone=0; LevDel=0;
                if(Level>99) Level=0;
                Number(Level,10,56,11);
   }
   LevDel++;

									

When the player reaches a new level, the game needs to be updated accordingly. Every level begins with a new pattern of rocks. And, it looks like every 5, 30, 60, and 90 levels has others things that happen as well. That's sad, though. I got plenty bored after 10 or so levels. Who would play long enough to see the level number roll back to zero?


void Print()
{  int x,t;

   MoveSprite(NULL,&sprite1,350,200);
   MoveSprite(NULL,&sprite2,350,200);
   MoveSprite(NULL,&sprite3,350,200);
   MoveSprite(NULL,&sprite4,350,200);
   MoveSprite(NULL,&sprite5,350,200);
   SetAPen(Win->RPort,5);
   for(t=0;t<=21;t++)
   {  x=320-(strlen(Intro[t])*4);
      Move(Win->RPort,x,t*9+10);
      Text(Win->RPort,Intro[t],strlen(Intro[t]));
   }
   InitAud();
   while((mess=(struct IntuiMessage *)GetMsg(Win->UserPort))==NULL);
   ReplyMsg((struct Message *)mess);
   RangeRand(10);
   SetAPen(Win->RPort,0);
   RectFill(Win->RPort,0,0,640,400);
   AddGList(Win,&Quit,0,2,NULL);
   RefreshGList(&Quit,Win,NULL,2);

									

This code prints that message with the bad addresses to the screen.


void main()
{  Init();
   Print();
   Board();
   FOREVER
   {  if(Play>0)
      {  if(Down<1) JoyDir();
         if(Gone>=5) NewLev();
         else
         {  if(Hit()) Land();
            if(move) Slide();
            if(Level<12) Y=Y+Clock;
            else Y++;
            DrawRock();
            KillLine();
      }  }
      else
      {  WaitTOF();
         if(Clock) MoveStars();
         else Bounce();
      }
      if(!Clock) Lights();
      NewMsg();
      Clock^=1;
} 

This is the main routine. It should be self explainatory. If not, keep studying it and you'll figure it out.


Copyright © 2005, Robert Walsh, All Rights reserved.