《俄羅斯方塊C語言程序設(shè)計(jì)報(bào)告.doc》由會(huì)員分享,可在線閱讀,更多相關(guān)《俄羅斯方塊C語言程序設(shè)計(jì)報(bào)告.doc(18頁珍藏版)》請(qǐng)?jiān)谘b配圖網(wǎng)上搜索。
C語言課程設(shè)計(jì)報(bào)告
俄羅斯方塊程序設(shè)計(jì)報(bào)告
一、 問題描述
俄羅斯方塊(Tetris, 俄文:Тетрис)是一款電視游戲機(jī)和掌上游戲機(jī)游戲,它由俄羅斯人阿列克謝帕基特諾夫發(fā)明,故得此名。俄羅斯方塊的基本規(guī)則是移動(dòng)、旋轉(zhuǎn)和擺放游戲自動(dòng)輸出的各種方塊,使之排列成完整的一行或多行并且消除得分。
在本次設(shè)計(jì)中,要求支持鍵盤操作和若干種不同類型方塊的旋轉(zhuǎn)變換,并且界面上顯示下一個(gè)方塊的提示以及當(dāng)前的玩家的得分,隨著游戲的進(jìn)行,等級(jí)越高,游戲難度越大,即方塊的下落速度越快,相應(yīng)的等級(jí),等級(jí)越高,為玩家提供了不同的選擇。
二、功能分析
I、俄羅斯方塊游戲需要解決的問題包括:
⑴、隨機(jī)產(chǎn)生方塊并自動(dòng)下移
⑵、用Esc鍵退出游戲
⑶、用 鍵變體
⑷、用 鍵和 鍵左右移動(dòng)方塊
⑸、用空格鍵使游戲暫停
⑹、能正確判斷滿行并消行、計(jì)分、定級(jí)別
⑺、設(shè)定游戲?yàn)椴煌?jí)別,級(jí)別越高難度越大
II、俄羅斯方塊游戲需要設(shè)計(jì)的功能函數(shù)包括:
⑴、聲明俄羅斯方塊的結(jié)構(gòu)體
⑵、函數(shù)原型聲明
⑶、制作游戲窗口
⑷、制作俄羅斯方塊
⑸、判斷是否可動(dòng)
⑹、隨機(jī)產(chǎn)生俄羅斯方塊類型的序號(hào)
⑺、打印俄羅斯方塊
⑻、清除俄羅斯方塊的痕跡
⑼、判斷是否滿行并刪除滿行的俄羅斯方塊
三、程序設(shè)計(jì)
1、程序總體結(jié)構(gòu)設(shè)計(jì)
(1)、游戲方塊預(yù)覽功能。在游戲過程中,游戲界面右側(cè)會(huì)有預(yù)覽區(qū)。由于在此游戲中存在多種不同的游戲方塊,所以在游戲方塊預(yù)覽區(qū)域中顯示隨機(jī)生成的游戲方塊有利于游戲玩家控制游戲的策略。
(2)、游戲方塊控制功能。通過各種條件的判斷,實(shí)現(xiàn)對(duì)游戲方塊的左移、右移、自由下落、旋轉(zhuǎn)功能,以及行滿消除行的功能。
(3)、游戲數(shù)據(jù)顯示功能。在游戲玩家進(jìn)行游戲過程中,需要按照一定的游戲規(guī)則給玩家計(jì)算游戲分?jǐn)?shù)。例如,消除一行加100分,游戲分?jǐn)?shù)達(dá)到一定數(shù)量之后,需要給游戲者進(jìn)行等級(jí)的上升,每上升一個(gè)等級(jí),游戲方塊的下落速度將加快,游戲的難度將增加。以上游戲數(shù)據(jù)均會(huì)在游戲界面右側(cè)顯示以提示玩家。
(4)、游戲信息提示功能。玩家進(jìn)入游戲后,將有對(duì)本游戲如何操作的友情提示。
(5)、游戲結(jié)束退出功能。判斷游戲結(jié)束條件,通過Esc鍵進(jìn)行退出。
打開程序,運(yùn)行,進(jìn)入界面
開始游戲
游戲數(shù)據(jù)顯示功能
游戲信息提示功能
游戲結(jié)束退出功能
游戲方塊預(yù)覽功能
游戲方塊控制功能
否
游戲是否結(jié)束
是
關(guān)閉游戲界面返回程序
游戲執(zhí)行主流程圖
2、界面設(shè)計(jì)
分為左右兩個(gè)部分:
*左邊為游戲面板
*右邊有三部分:游戲數(shù)據(jù)提示框、下一個(gè)方塊提示框和功能提示框
3、重要功能函數(shù)設(shè)計(jì)
1)、聲明俄羅斯方塊的結(jié)構(gòu)體
struct Tetris
{
int x; //中心方塊的x軸坐標(biāo)
int y; //中心方塊的y軸坐標(biāo)
int flag; //標(biāo)記方塊類型的序號(hào)
int next; //下一個(gè)俄羅斯方塊類型的序號(hào)
int speed; //俄羅斯方塊移動(dòng)的速度
int count; //產(chǎn)生俄羅斯方塊的個(gè)數(shù)
int score; //游戲的分?jǐn)?shù)
int level; //游戲的等級(jí)
};
2)、函數(shù)原型聲明
//光標(biāo)移到指定位置
void gotoxy(HANDLE hOut, int x, int y);
//制作游戲窗口
void make_frame();
//隨機(jī)產(chǎn)生方塊類型的序號(hào)
void get_flag(struct Tetris *);
//制作俄羅斯方塊
void make_tetris(struct Tetris *);
//打印俄羅斯方塊
void print_tetris(HANDLE hOut,struct Tetris *);
//清除俄羅斯方塊的痕跡
void clear_tetris(HANDLE hOut,struct Tetris *);
//判斷是否能移動(dòng),返回值為1,能移動(dòng),否則,不動(dòng)
int if_moveable(struct Tetris *);
//判斷是否滿行,并刪除滿行的俄羅斯方塊
void del_full(HANDLE hOut,struct Tetris *);
//開始游戲
void start_game();
3)、制作游戲窗口
void make_frame()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定義顯示器句柄變量
gotoxy(hOut,FrameX+Frame_width-5,FrameY-2); //打印游戲名稱
printf("俄羅斯方塊");
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+7); //打印選擇菜單
printf("**********下一個(gè)方塊:");
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+13);
printf("**********");
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+17);
printf("↑鍵:變體");
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+19);
printf("空格:暫停游戲");
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+15);
printf("Esc :退出游戲");
gotoxy(hOut,FrameX,FrameY); //打印框角并記住該處已有圖案
printf("╔");
gotoxy(hOut,FrameX+2*Frame_width-2,FrameY);
printf("╗");
gotoxy(hOut,FrameX,FrameY+Frame_height);
printf("╚");
gotoxy(hOut,FrameX+2*Frame_width-2,FrameY+Frame_height);
printf("╝");
a[FrameX][FrameY+Frame_height]=2;
a[FrameX+2*Frame_width-2][FrameY+Frame_height]=2;
for(i=2;i<2*Frame_width-2;i+=2)
{
gotoxy(hOut,FrameX+i,FrameY);
printf("═"); //打印上橫框
}
for(i=2;i<2*Frame_width-2;i+=2)
{
gotoxy(hOut,FrameX+i,FrameY+Frame_height);
printf("═"); //打印下橫框
a[FrameX+i][FrameY+Frame_height]=2; //記住下橫框有圖案
}
for(i=1;i
x][tetris->y]=b[0]; //中心方塊位置的圖形狀態(tài):1-有,0-無
switch(tetris->flag) //共6大類,19種類型
{
case 1: //田字方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x+2][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 2: //直線方塊:----
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x+2][tetris->y]=b[2];
a[tetris->x+4][tetris->y]=b[3];
break;
}
case 3: //直線方塊: |
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y-2]=b[2];
a[tetris->x][tetris->y+1]=b[3];
break;
}
case 4: //T字方塊
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x+2][tetris->y]=b[2];
a[tetris->x][tetris->y+1]=b[3];
break;
}
case 5: //T字順時(shí)針轉(zhuǎn)90度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y+1]=b[2];
a[tetris->x-2][tetris->y]=b[3];
break;
}
case 6: //T字順時(shí)針轉(zhuǎn)180度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x-2][tetris->y]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 7: //T字順時(shí)針轉(zhuǎn)270度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y+1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 8: //Z字方塊
{
a[tetris->x][tetris->y+1]=b[1];
a[tetris->x-2][tetris->y]=b[2];
a[tetris->x+2][tetris->y+1]=b[3];
break;
}
case 9: //Z字順時(shí)針轉(zhuǎn)90度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x-2][tetris->y]=b[2];
a[tetris->x-2][tetris->y+1]=b[3];
break;
}
case 10: //Z字順時(shí)針轉(zhuǎn)180度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x-2][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 11: //Z字順時(shí)針轉(zhuǎn)270度方塊
{
a[tetris->x][tetris->y+1]=b[1];
a[tetris->x+2][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 12: //7字方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y+1]=b[2];
a[tetris->x-2][tetris->y-1]=b[3];
break;
}
case 13: //7字順時(shí)針轉(zhuǎn)90度方塊
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x-2][tetris->y+1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 14: //7字順時(shí)針轉(zhuǎn)180度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y+1]=b[2];
a[tetris->x+2][tetris->y+1]=b[3];
break;
}
case 15: //7字順時(shí)針轉(zhuǎn)270度方塊
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x+2][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 16: //倒7字方塊
{
a[tetris->x][tetris->y+1]=b[1];
a[tetris->x][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y-1]=b[3];
break;
}
case 17: //倒7字順指針轉(zhuǎn)90度方塊
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x-2][tetris->y-1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
case 18: //倒7字順時(shí)針轉(zhuǎn)180度方塊
{
a[tetris->x][tetris->y-1]=b[1];
a[tetris->x][tetris->y+1]=b[2];
a[tetris->x-2][tetris->y+1]=b[3];
break;
}
case 19: //倒7字順時(shí)針轉(zhuǎn)270度方塊
{
a[tetris->x-2][tetris->y]=b[1];
a[tetris->x+2][tetris->y+1]=b[2];
a[tetris->x+2][tetris->y]=b[3];
break;
}
}
5)、判斷是否可動(dòng)
int if_moveable(struct Tetris *tetris)
{
if(a[tetris->x][tetris->y]!=0)//當(dāng)中心方塊位置上有圖案時(shí),返回值為0,即不可移動(dòng)
{
return 0;
}
else
{
if( //當(dāng)為田字方塊且除中心方塊位置外,其他"口"字方塊位置上無圖案時(shí),返回值為1,即可移動(dòng)
( tetris->flag==1 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x+2][tetris->y-1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
//或?yàn)橹本€方塊且除中心方塊位置外,其他"口"字方塊位置上無圖案時(shí),返回值為1,即可移動(dòng)
( tetris->flag==2 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x+2][tetris->y]==0 && a[tetris->x+4][tetris->y]==0 ) ) ||
( tetris->flag==3 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y-2]==0 && a[tetris->x][tetris->y+1]==0 ) ) ||
( tetris->flag==4 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x+2][tetris->y]==0 && a[tetris->x][tetris->y+1]==0 ) ) ||
( tetris->flag==5 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y+1]==0 && a[tetris->x-2][tetris->y]==0 ) ) ||
( tetris->flag==6 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x-2][tetris->y]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==7 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y+1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==8 && ( a[tetris->x][tetris->y+1]==0 &&
a[tetris->x-2][tetris->y]==0 && a[tetris->x+2][tetris->y+1]==0 ) ) ||
( tetris->flag==9 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x-2][tetris->y]==0 && a[tetris->x-2][tetris->y+1]==0 ) ) ||
( tetris->flag==10 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x-2][tetris->y-1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==11 && ( a[tetris->x][tetris->y+1]==0 &&
a[tetris->x+2][tetris->y-1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==12 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y+1]==0 && a[tetris->x-2][tetris->y-1]==0 ) ) ||
( tetris->flag==13 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x-2][tetris->y+1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==14 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y+1]==0 && a[tetris->x+2][tetris->y+1]==0 ) ) ||
( tetris->flag==15 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x+2][tetris->y-1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==16 && ( a[tetris->x][tetris->y+1]==0 &&
a[tetris->x][tetris->y-1]==0 && a[tetris->x+2][tetris->y-1]==0 ) ) ||
( tetris->flag==17 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x-2][tetris->y-1]==0 && a[tetris->x+2][tetris->y]==0 ) ) ||
( tetris->flag==18 && ( a[tetris->x][tetris->y-1]==0 &&
a[tetris->x][tetris->y+1]==0 && a[tetris->x-2][tetris->y+1]==0 ) ) ||
( tetris->flag==19 && ( a[tetris->x-2][tetris->y]==0 &&
a[tetris->x+2][tetris->y+1]==0 && a[tetris->x+2][tetris->y]==0 ) ) )
{
return 1;
}
}
return 0;
}
6)、隨機(jī)產(chǎn)生俄羅斯方塊類型的序號(hào)
void get_flag(struct Tetris *tetris)
{
tetris->count++; //記住產(chǎn)生方塊的個(gè)數(shù)
srand((unsigned)time(NULL)); //初始化隨機(jī)數(shù)
if(tetris->count==1)
{
tetris->flag = rand()%19+1; //記住第一個(gè)方塊的序號(hào)
}
tetris->next = rand()%19+1; //記住下一個(gè)方塊的序號(hào)
}
7)、打印俄羅斯方塊
void print_tetris(HANDLE hOut,struct Tetris *tetris)
{
for(i=0;i<4;i++)
{
b[i]=1; //數(shù)組b[4]的每個(gè)元素的值都為1
}
make_tetris(tetris); //制作俄羅斯方塊
for( i=tetris->x-2; i<=tetris->x+4; i+=2 )
{
for(j=tetris->y-2;j<=tetris->y+1;j++)
{
if( a[i][j]==1 && j>FrameY )
{
gotoxy(hOut,i,j);
printf("□"); //打印邊框內(nèi)的方塊
}
}
}
//打印菜單信息
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+1);
printf("level : %d",tetris->level);
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+3);
printf("score : %d",tetris->score);
gotoxy(hOut,FrameX+2*Frame_width+3,FrameY+5);
printf("speed : %dms",tetris->speed);
}
8)、清除俄羅斯方塊的痕跡
void clear_tetris(HANDLE hOut,struct Tetris *tetris)
{
for(i=0;i<4;i++)
{
b[i]=0; //數(shù)組b[4]的每個(gè)元素的值都為0
}
make_tetris(tetris); //制作俄羅斯方塊
for( i=tetris->x-2; i<=tetris->x+4; i+=2 )
{
for(j=tetris->y-2;j<=tetris->y+1;j++)
{
if( a[i][j]==0 && j>FrameY )
{
gotoxy(hOut,i,j);
printf(" "); //清除方塊
}
}
}
}
9)、判斷是否滿行并刪除滿行的俄羅斯方塊
void del_full(HANDLE hOut,struct Tetris *tetris)
{ //當(dāng)某行有Frame_width-2個(gè)方塊時(shí),則滿行
int k,del_count=0; //分別用于記錄某行方塊的個(gè)數(shù)和刪除方塊的行數(shù)的變量
for(j=FrameY+Frame_height-1;j>=FrameY+1;j--)
{
k=0;
for(i=FrameX+2;iFrameY;k--)
{ //如果刪除行以上的位置有方塊,則先清除,再將方塊下移一個(gè)位置
for(i=FrameX+2;iscore+=100*del_count; //每刪除一行,得100分
if( del_count>0 && ( tetris->score%1000==0 || tetris->score/1000>tetris->level-1 ) )
{ //如果得1000分即累計(jì)刪除10行,速度加快20ms并升一級(jí)
tetris->speed-=20;
tetris->level++;
}
}
10)、開始游戲
void start_game()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); //定義顯示器句柄變量
struct Tetris t,*tetris=&t; //定義結(jié)構(gòu)體的指針并指向結(jié)構(gòu)體變量
unsigned char ch; //定義接收鍵盤輸入的變量
tetris->count=0; //初始化俄羅斯方塊數(shù)為0個(gè)
tetris->speed=300; //初始移動(dòng)速度為300ms
tetris->score=0; //初始游戲的分?jǐn)?shù)為0分
tetris->level=1; //初始游戲?yàn)榈?關(guān)
while(1)
{//循環(huán)產(chǎn)生方塊,直至游戲結(jié)束
get_flag(tetris); //得到產(chǎn)生俄羅斯方塊類型的序號(hào)
temp=tetris->flag; //記住當(dāng)前俄羅斯方塊序號(hào)
//打印下一個(gè)俄羅斯方塊的圖形(右邊窗口)
tetris->x=FrameX+2*Frame_width+6;
tetris->y=FrameY+10;
tetris->flag = tetris->next;
print_tetris(hOut,tetris);
tetris->x=FrameX+Frame_width; //初始中心方塊x坐標(biāo)
tetris->y=FrameY-1; //初始中心方塊y坐標(biāo)
tetris->flag=temp; //取出當(dāng)前的俄羅斯方塊序號(hào)
while(1)
{//控制方塊方向,直至方塊不再下移
label:print_tetris(hOut,tetris);//打印俄羅斯方塊
Sleep(tetris->speed); //延緩時(shí)間
clear_tetris(hOut,tetris); //清除痕跡
temp1=tetris->x; //記住中心方塊橫坐標(biāo)的值
temp2=tetris->flag; //記住當(dāng)前俄羅斯方塊序號(hào)
if(kbhit())
{ //判斷是否有鍵盤輸入,有則用ch↓接收
ch=getch();
if(ch==75) //按←鍵則向左動(dòng),中心橫坐標(biāo)減2
{
tetris->x-=2;
}
if(ch==77) //按→鍵則向右動(dòng),中心橫坐標(biāo)加2
{
tetris->x+=2;
}
if(ch==72) //按↑鍵則變體即當(dāng)前方塊順時(shí)針轉(zhuǎn)90度
{
if( tetris->flag>=2 && tetris->flag<=3 )
{
tetris->flag++;
tetris->flag%=2;
tetris->flag+=2;
}
if( tetris->flag>=4 && tetris->flag<=7 )
{
tetris->flag++;
tetris->flag%=4;
tetris->flag+=4;
}
if( tetris->flag>=8 && tetris->flag<=11 )
{
tetris->flag++;
tetris->flag%=4;
tetris->flag+=8;
}
if( tetris->flag>=12 && tetris->flag<=15 )
{
tetris->flag++;
tetris->flag%=4;
tetris->flag+=12;
}
if( tetris->flag>=16 && tetris->flag<=19 )
{
tetris->flag++;
tetris->flag%=4;
tetris->flag+=16;
}
}
if(ch==32) //按空格鍵,暫停
{
print_tetris(hOut,tetris);
while(1)
{
if(kbhit()) //再按空格鍵,繼續(xù)游戲
{
ch=getch();
if(ch==32)
{
goto label;
}
}
}
}
if(if_moveable(tetris)==0) //如果不可動(dòng),上面操作無效
{
tetris->x=temp1;
tetris->flag=temp2;
}
else //如果可動(dòng),執(zhí)行操作
{
goto label;
}
}
tetris->y++; //如果沒有操作指令,方塊向下移動(dòng)
if(if_moveable(tetris)==0) //如果向下移動(dòng)且不可動(dòng),方塊放在此處
{
tetris->y--;
print_tetris(hOut,tetris);
del_full(hOut,tetris);
break;
}
}
for(i=tetris->y-2;iy+2;i++)
{//游戲結(jié)束條件:方塊觸到框頂位置
if(i==FrameY)
{
j=0; //如果游戲結(jié)束,j=0 }
}
if(j==0)
{
system("cls");
getch();
break;
}
//清除下一個(gè)俄羅斯方塊的圖形(右邊窗口)
tetris->flag = tetris->next;
tetris->x=FrameX+2*Frame_width+6;
tetris->y=FrameY+10;
clear_tetris(hOut,tetris);
} }
4、函數(shù)設(shè)計(jì)流程
進(jìn)入俄羅斯方塊程序
、
聲明俄羅斯方塊的結(jié)構(gòu)體
struct Tetris
定義全局變量
函數(shù)原型聲明
//制作游戲窗口make_frame();
//開始游戲start_game();
定義主函數(shù)
void main()
制作俄羅斯方塊
判斷是否可動(dòng)
開始游戲
具體設(shè)計(jì)運(yùn)行游戲所需要的各種功能
隨機(jī)產(chǎn)生俄羅斯
方塊類型的序號(hào)
打印俄羅斯方塊
清除俄羅斯方塊的痕跡
判斷是否滿行并刪除滿行的俄羅斯方塊
鏈接地址:http://ioszen.com/p-9108399.html