俄罗斯方块(c语言)

简介: 俄罗斯方块(c语言)


说明

本程序是在 项目: https://github.com/pzibang/tinytetris

上修改得到的。

  • Linux版本
    在原基础上添加了注释
  • windwos 版本
    在原基础上做了部分修改
Linux 版本
#include <ctime>
#include <curses.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int 
     x = 431424,   // 方块x轴坐标
     y = 598356,   // 方块y轴坐标
     r = 427089,   // 某一种方块的具体那种形式 ( 同一个类别的方块可以经过旋转)
     px = 247872,  // 记录上一次的x 信息
     py = 799248,  // 记录上一次的y 信息  
   pr,           // 记录上一次的r 信息
     c = 348480,   // 中间变量
     p = 615696,   // 记录方块的类别, 也记录了每种方块的颜色信息
     tick,         // 控制帧率  控制下落的速度
     board[20][10],// 整个地图(画面) 有无方块、方块的颜色信息
   //{h-1,w-1}{x0,y0}{x1,y1}{x2,y2}{x3,y3} (two bits each)
     block[7][4] = {  {x, y, x, y},    // 打表  总共有7种类别的方块  每种类别可以经过旋转
                      {r, p, r, p},    // 每种方块 由四个子块组成
                      {c, c, c, c},    // 宽度-1 宽度-1, 高度-1 , 四个子方块的相对位置   
              {599636, 431376, 598336, 432192}, // 每个数字 仅仅低22位有效
                      {411985, 610832, 415808, 595540},
                      {px, py, px, py},
                      {614928, 399424, 615744, 428369}},
    score = 0;    //得分
                  
/*****************************
@param  x    某种类型方块的具体形式
@param  y    要取的两位为y+1 和 y+2
return  int  某两位的值
说明:  p决定是那种方块
******************************/
int NUM(int x, int y) { return 3 & block[p][x] >> y; }
/*******************************
@func:  产生一个新的方块
*******************************/
void new_piece() {
  y = py = 0;          // 新的块的y是0
  p = rand() % 7;      // 块的类别是7种的随机一种                                                       
  r = pr = rand() % 4; // 旋转类型也是四种的随机一种
  x = px = rand() % (10 - NUM(r, 16)); // 17,18位   x坐标是屏幕宽度-块的宽度之间的随机数
}
/*****************************
@func遍历 board 数组,在相应的位置画出对应颜色的块
*****************************/
void frame() {
  for (int i = 0; i < 20; i++) {
    move(1 + i, 1); // 到新的一行
    for (int j = 0; j < 10; j++) {
      board[i][j] && attron(262176 | board[i][j] << 8); 
    // 如果board[i][j]非0 则进行后面的语句
    // board[i][j] << 8 等价于 COLOR_PAIR(board[i][j]) 
    // 262176实际上应为 262144 为 A_REVERSE  将前景色和背景色对换
      printw("  "); 
    // 上一句设置输出的颜色属性,下一句关闭设置的的颜色,为下次绘制做准备
      attroff(262176 | board[i][j] << 8);
    }
  }
  move(21, 1); 
  printw("Score: %d", score);
  refresh();    
}
// set the value fo the board for a particular (x,y,r) piece
/******************************
@func: 给board数组(地图) 赋值, 在地图上表示出某个方块的位置信息
@param  x  要绘制的x坐标
@param  y  要绘制的y坐标
@param  r  旋转的类型
@param  v  具体的值, 非0 表示方块的颜色信息
                        0  表示擦除这个方块
******************************/
void set_piece(int x, int y, int r, int v) {
  for (int i = 0; i < 8; i += 2) { // 每个数据由2位表示,所以步进为2
    board[NUM(r, i * 2) + y][NUM(r, (i * 2) + 2) + x] = v;
  }
}
/********************************
@func: 擦除旧的方块在新的位置绘制
********************************/
int update_piece() {
  set_piece(px, py, pr, 0);                // 擦除原位置,在判断是否能够放下的时候要把自己影响消除,置0
  set_piece(px = x, py = y, pr = r, p + 1);// 块的类别加1为颜色信息, x,y为当前的新位置
}
/********************************
@func: 判断一行是否已经满了, 如果满了清除满的行,并得分
********************************/
void remove_line() {
  for (int row = y; row <= y + NUM(r, 18); row++) {  // 遍历的范围会当前方块的顶部到底部
    c = 1;
    for (int i = 0; i < 10; i++) { // 遍历一行
      c *= board[row][i];
    }
    if (!c) { // 为 0 表示一行中出现了0,没有满
      continue;
    }
    for (int i = row - 1; i > 0; i--) {
      memcpy(&board[i + 1][0], &board[i][0], 40); //如果满了,就将 上一行,依次向下一行移
    }
    memset(&board[0][0], 0, 10); //将最顶行清空
    score++; // 得分 
  }
}
/*********************************
@func: 判断将方块放置在x,y位置是否会冲突
*********************************/
int check_hit(int x, int y, int r) {
  if (y + NUM(r, 18) > 19) { // 方块的底部已经超出了屏幕
    return 1;
  }
  set_piece(px, py, pr, 0);// 清空当前方块,清除自己的影响
  c = 0;
  for (int i = 0; i < 8; i += 2) {
    board[y + NUM(r, i * 2)][x + NUM(r, (i * 2) + 2)] && c++; 
  // 判断本方块要放置的四个位置 是否已经有了别的方块
  }
  set_piece(px, py, pr, p + 1);// 在原位置重新放置
  return c; // 如果c == 0 表示可以放置
}
/*********************************
func:  控制方块下落的速度
*********************************/
int do_tick() {
  if (++tick > 30) {
    tick = 0;
    if (check_hit(x, y + 1, r)) {   
    // 不能放置到下一行
      if (!y) {  // y == 0 游戏结束
        return 0;
      }
      remove_line(); // 判断是否已经满了
      new_piece(); // 在开头创建新的块
    } else {
    // 可以放置到下一行
      y++;  
      update_piece();
    }
  }
  return 1;
}
/********************************
@func: 游戏主循环,  wasd 按键处理   w: 旋转
********************************/
void runloop() {
  while (do_tick()){
    usleep(10000);
    if ((c = getch()) == 'a' && x > 0 && !check_hit(x - 1, y, r)) {
    // 左
      x--;
    }
    if (c == 'd' && x + NUM(r, 16) < 9 && !check_hit(x + 1, y, r)) {
      // 右
      x++;
    }
    if (c == 's') {
      while (!check_hit(x, y + 1, r)) { // 向下没有冲突就一直向下
        y++;
        update_piece();
      }
      remove_line();
      new_piece();
    }
    if (c == 'w') {
      ++r %= 4; // 旋转
      while (x + NUM(r, 16) > 9) {  // 横坐标超出了边界
        x--;
      }
      if (check_hit(x, y, r)) { // 如果变型后发生了碰撞,则不能变形,x还是原来的x, r还是原来的r
        x = px;
        r = pr;
      }
    }
    if (c == 'q') {
      return;
    }
    update_piece();
    frame();
  }
}
/*******************
@func main function  初始化
*******************/
int main() {
  srand(time(0));   // 初始化随机数种子
  initscr();        // 进入 curses模式
  start_color();    // 初始化颜色
  // 方块的类别就代表它们的颜色信息
  for (int i = 1; i < 8; i++) {
    init_pair(i, i, 0);    // 设置颜色对,共7个
  }
  new_piece();
  resizeterm(22, 22);      // 设置窗口大小
  noecho();                // 不回显
  timeout(0);              
  curs_set(0);             // 不显示光标
  box(stdscr, 0, 0);       //  绘制边界
  runloop();
  endwin();                // 退出curses模式
}
Window 版本
#include <time.h>
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
int 
     x = 431424,   // 方块x轴坐标
     y = 598356,   // 方块y轴坐标
     r = 427089,   // 某一种方块的具体那种形式 ( 同一个类别的方块可以经过旋转)
     px = 247872,  // 记录上一次的x 信息
     py = 799248,  // 记录上一次的y 信息  
   pr,           // 记录上一次的r 信息
     c = 348480,   // 中间变量
     p = 615696,   // 记录方块的类别, 也记录了每种方块的颜色信息
     tick,         // 控制帧率  控制下落的速度
     board[20][10],// 整个地图(画面) 有无方块、方块的颜色信息
   //{h-1,w-1}{x0,y0}{x1,y1}{x2,y2}{x3,y3} (two bits each)
     block[7][4] = {  {x, y, x, y},    // 打表  总共有7种类别的方块  每种类别可以经过旋转
                      {r, p, r, p},    // 每种方块 由四个子块组成
                      {c, c, c, c},    // 宽度-1 宽度-1, 高度-1 , 四个子方块的相对位置   
              {599636, 431376, 598336, 432192}, // 每个数字 仅仅低22位有效
                      {411985, 610832, 415808, 595540},
                      {px, py, px, py},
                      {614928, 399424, 615744, 428369}},
    score = 0;    //得分
    
/* 黑色 0   深蓝 1   深绿 2    深蓝绿 3   深红 4    紫 5     暗绿 6     
   白 7     灰 8     亮蓝 9    亮绿 10    亮蓝绿 11  红12    粉   13
   黄 14    亮白 15 
*/
/****************************
@func  设置控制台输出的颜色
@param bk 背景色
@param fg 前景色 
如果 bk == 0 and fg == 0 则为颜色恢复 
****************************/
int setcolor(int bk, int fg) 
{
  if(bk+fg == 0){
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED |
                  FOREGROUND_GREEN |
                  FOREGROUND_BLUE);
  }else{
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),(bk&0x0f)<<4+(fg&0x0f));
  }
  
  return 1;
}
void close_cur(){
  HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);//获得输出句柄 
  CONSOLE_CURSOR_INFO CursorInfo;
  GetConsoleCursorInfo(handle,&CursorInfo);//获取控制台光标信息
  CursorInfo.bVisible=false;//隐藏控制台光标
  SetConsoleCursorInfo(handle,&CursorInfo);//设置控制台光标状态 
}
      
/****************
@func: 设置光标的位置
@param y 
@param x 
****************/
void move(int y,int x) 
{
  COORD pos;
  pos.X=x;
  pos.Y=y;
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}
/*******************
@func:  绘制边界 
*******************/
void border(){
  int i=0;
  move(0,0);
  for(i = 1 ;i<22;i++) printf("-");
  move(21,0); 
  for(i = 1 ;i<22;i++) printf("-");
  for(i =0;i<22;i++){
    move(i,0);
    printf("|");
    move(i,21);
    printf("|");
  }
}   
              
/*****************************
@param  x    某种类型方块的具体形式
@param  y    要取的两位为y+1 和 y+2
return  int  某两位的值
说明:  p决定是那种方块
******************************/
int NUM(int x, int y) { return 3 & block[p][x] >> y; }
/*******************************
@func:  产生一个新的方块
*******************************/
void new_piece() {
  y = py = 0;          // 新的块的y是0
  p = rand() % 7;      // 块的类别是7种的随机一种                                                       
  r = pr = rand() % 4; // 旋转类型也是四种的随机一种
  x = px = rand() % (10 - NUM(r, 16)); // 17,18位   x坐标是屏幕宽度-块的宽度之间的随机数
}
/*****************************
@func遍历 board 数组,在相应的位置画出对应颜色的块
*****************************/
void frame() {
  for (int i = 0; i < 20; i++) {
    move(1 + i, 1); // 到新的一行
    for (int j = 0; j < 10; j++) {
      if(board[i][j]){ 
      // 如果board[i][j]非0 则进行后面的语句
        setcolor(board[i][j],0);
    printf("  ");  
    }else{
      setcolor(0,0);
      printf("  ");
    }
    }
  }
  move(22, 1); 
  setcolor(0,0);
  printf("Score: %d", score);    
}
/******************************
@func: 给board数组(地图) 赋值, 在地图上表示出某个方块的位置信息
@param  x  要绘制的x坐标
@param  y  要绘制的y坐标
@param  r  旋转的类型
@param  v  具体的值, 非0 表示方块的颜色信息
                        0  表示擦除这个方块
******************************/
void set_piece(int x, int y, int r, int v) {
  for (int i = 0; i < 8; i += 2) { // 每个数据由2位表示,所以步进为2
    board[NUM(r, i * 2) + y][NUM(r, (i * 2) + 2) + x] = v;
  }
}
/********************************
@func: 擦除旧的方块在新的位置绘制
********************************/
int update_piece() {
  set_piece(px, py, pr, 0);                // 擦除原位置,在判断是否能够放下的时候要把自己影响消除,置0
  set_piece(px = x, py = y, pr = r, p + 1);// 块的类别加1为颜色信息, x,y为当前的新位置
}
/********************************
@func: 判断一行是否已经满了, 如果满了清除满的行,并得分
********************************/
void remove_line() {
  for (int row = y; row <= y + NUM(r, 18); row++) {  // 遍历的范围会当前方块的顶部到底部
    c = 1;
    for (int i = 0; i < 10; i++) { // 遍历一行
      c *= board[row][i];
    }
    if (!c) { // 为 0 表示一行中出现了0,没有满
      continue;
    }
    for (int i = row - 1; i > 0; i--) {
      memcpy(&board[i + 1][0], &board[i][0], 40); //如果满了,就将 上一行,依次向下一行移
    }
    memset(&board[0][0], 0, 10); //将最顶行清空
    score++; // 得分 
  }
}
/*********************************
@func: 判断将方块放置在x,y位置是否会冲突
*********************************/
int check_hit(int x, int y, int r) {
  if (y + NUM(r, 18) > 19) { // 方块的底部已经超出了屏幕
    return 1;
  }
  set_piece(px, py, pr, 0);// 清空当前方块,清除自己的影响
  c = 0;
  for (int i = 0; i < 8; i += 2) {
    board[y + NUM(r, i * 2)][x + NUM(r, (i * 2) + 2)] && c++; 
  // 判断本方块要放置的四个位置 是否已经有了别的方块
  }
  set_piece(px, py, pr, p + 1);// 在原位置重新放置
  return c; // 如果c == 0 表示可以放置
}
/*********************************
func:  控制方块下落的速度
*********************************/
int do_tick() {
  if (++tick > 30) {
    Sleep(10);
    tick = 0;
    if (check_hit(x, y + 1, r)) {   
    // 不能放置到下一行
      if (!y) {  // y == 0 游戏结束
        return 0;
      }
      remove_line(); // 判断是否已经满了
      new_piece(); // 在开头创建新的块
    } else {
    // 可以放置到下一行
      y++;  
      update_piece();
    }
  }
  return 1;
}
/********************************
@func: 游戏主循环,  wasd 按键处理   w: 旋转
********************************/
void runloop() {
  while (do_tick()){
    if(kbhit()){
      if ((c = getch()) == 'a' && x > 0 && !check_hit(x - 1, y, r)) {
      // 左
        x--;
      }
      if (c == 'd' && x + NUM(r, 16) < 9 && !check_hit(x + 1, y, r)) {
        // 右
        x++;
      }
      if (c == 's') {
        while (!check_hit(x, y + 1, r)) { // 向下没有冲突就一直向下
          y++;
          update_piece();
        }
        remove_line();
        new_piece();
      }
      if (c == 'w') {
        ++r %= 4; // 旋转
        while (x + NUM(r, 16) > 9) {  // 横坐标超出了边界
          x--;
        }
        if (check_hit(x, y, r)) { // 如果变型后发生了碰撞,则不能变形,x还是原来的x, r还是原来的r
          x = px;
          r = pr;
        }
      }
      if (c == 'q') {
        return;
      }
  }
    update_piece();
    frame();
  }
}
/*******************
@func main function  初始化
*******************/
int main() {
  srand(time(0));   // 初始化随机数种子
  SetConsoleTitle("Tetris");
  border();
  new_piece();
  close_cur();                // 关闭光标 
  runloop();
  return 0; 
}
相关文章
|
1月前
|
C语言
俄罗斯方块-----C语言
俄罗斯方块-----C语言
16 0
俄罗斯方块-----C语言
|
5月前
|
存储 C语言
C语言实战 | “俄罗斯方块”游戏重构
摘要(Markdown格式): 在之前的游戏中,全局变量的过度使用导致存储浪费和低代码通用性。以“贪吃蛇”为例,显示功能依赖全局变量,限制了函数的复用。通过参数传递代替全局变量,如在“俄罗斯方块”等游戏中控制物体运动的函数,可提升代码重用性和模块化。重构过程中,即使小到变量命名和代码精简的改进,也能逐步带来程序质量的显著提升。
31 0
|
6月前
|
存储 定位技术 API
c语言——俄罗斯方块
c语言——俄罗斯方块
123 0
|
C语言
C语言小游戏------俄罗斯方块
C语言写的俄罗斯方块小游戏
262 0
|
27天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
31 3
|
18天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
31 10
|
11天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
16天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
42 7
|
16天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
25 4