java实现俄罗斯方块小游戏

 更新时间:2021年01月19日 11:39:07   作者:江湖人称小明  
这篇文章主要为大家详细介绍了java实现俄罗斯方块小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文实例为大家分享了java实现俄罗斯方块的具体代码,供大家参考,具体内容如下

使用一个二维数组保存游戏的地图:

// 游戏地图格子,每个格子保存一个方块,数组纪录方块的状态
private State map[][] = new State[rows][columns];

游戏前先将所有地图中的格子初始化为空:

/* 初始化所有的方块为空 */
for (int i = 0; i < map.length; i++) {
 for (int j = 0; j < map[i].length; j++) {
 map[i][j] = State.EMPTY;
 }
}

玩游戏过程中,我们能够看到界面上的方块,那么就得将地图中所有的方块绘制出来,当然,除了需要绘制方块外,游戏积分和游戏结束的字符串在必要的时候也需要绘制:

/**
 * 绘制窗体内容,包括游戏方块,游戏积分或结束字符串
 */
@Override
public void paint(Graphics g) {
 super.paint(g);
 for (int i = 0; i < rows; i++) {
 for (int j = 0; j < columns; j++) {
  if (map[i][j] == State.ACTIVE) { // 绘制活动块
  g.setColor(activeColor);
  g.fillRoundRect(j * BLOCK_SIZE, i * BLOCK_SIZE + 25,
   BLOCK_SIZE - 1, BLOCK_SIZE - 1, BLOCK_SIZE / 5,
   BLOCK_SIZE / 5);
  } else if (map[i][j] == State.STOPED) { // 绘制静止块
  g.setColor(stopedColor);
  g.fillRoundRect(j * BLOCK_SIZE, i * BLOCK_SIZE + 25,
   BLOCK_SIZE - 1, BLOCK_SIZE - 1, BLOCK_SIZE / 5,
   BLOCK_SIZE / 5);
  }
 }
 }

 /* 打印得分 */
 g.setColor(scoreColor);
 g.setFont(new Font("Times New Roman", Font.BOLD, 30));
 g.drawString("SCORE : " + totalScore, 5, 70);

 // 游戏结束,打印结束字符串
 if (!isGoingOn) {
 g.setColor(Color.RED);
 g.setFont(new Font("Times New Roman", Font.BOLD, 40));
 g.drawString("GAME OVER !", this.getWidth() / 2 - 140,
  this.getHeight() / 2);
 }
}

通过随机数的方式产生方块所组成的几种图形,一般七种图形:条形、田形、正7形、反7形、T形、Z形和反Z形,如生成条形:

map[0][randPos] = map[0][randPos - 1] = map[0][randPos + 1] 
  = map[0][randPos + 2] = State.ACTIVE;

生成图形后,实现下落的操作。如果遇到阻碍,则不能再继续下落:

isFall = true; // 是否能够下落
// 从当前行检查,如果遇到阻碍,则停止下落
for (int i = 0; i < blockRows; i++) {
 for (int j = 0; j < columns; j++) {
 // 遍历到行中块为活动块,而下一行块为静止块,则遇到阻碍
 if (map[rowIndex - i][j] == State.ACTIVE
  && map[rowIndex - i + 1][j] == State.STOPED) {
  isFall = false; // 停止下落
  break;
 }
 }
 if (!isFall)
 break;
}

如果未遇到阻碍,则下落的时候,方块图形整体向下移动一行:

// 图形下落一行
for (int i = 0; i < blockRows; i++) {
 for (int j = 0; j < columns; j++) {
 if (map[rowIndex - i][j] == State.ACTIVE) { // 活动块向下移动一行
  map[rowIndex - i][j] = State.EMPTY; // 原活动块变成空块
  map[rowIndex - i + 1][j] = State.ACTIVE; // 下一行块变成活动块
 }
 }
}

向左、向右方向移动时是类似的操作:

/**
 * 向左走
 */
private void left() {
 // 标记左边是否有阻碍
 boolean hasBlock = false;

 /* 判断是否左边有阻碍 */
 for (int i = 0; i < blockRows; i++) {
 if (map[rowIndex - i][0] == State.ACTIVE) { // 判断左边是否为墙
  hasBlock = true;
  break; // 有阻碍,不用再循环判断行
 } else {
  for (int j = 1; j < columns; j++) { // 判断左边是否有其它块
  if (map[rowIndex - i][j] == State.ACTIVE
   && map[rowIndex - i][j - 1] == State.STOPED) {
   hasBlock = true;
   break; // 有阻碍,不用再循环判断列
  }
  }
  if (hasBlock)
  break; // 有阻碍,不用再循环判断行
 }
 }

 /* 左边没有阻碍,则将图形向左移动一个块的距离 */
 if (!hasBlock) {
 for (int i = 0; i < blockRows; i++) {
  for (int j = 1; j < columns; j++) {
  if (map[rowIndex - i][j] == State.ACTIVE) {
   map[rowIndex - i][j] = State.EMPTY;
   map[rowIndex - i][j - 1] = State.ACTIVE;
  }
  }
 }

 // 重绘
 repaint();
 }
}

向下加速移动时,就是减小每次正常状态下落的时间间隔:

/**
 * 向下直走
 */
private void down() {
 // 标记可以加速下落
 immediate = true;
}

如何变换图形方向,这里仅使用了非常简单的方法来实现方向变换,当然可以有更优的算法实现方向变换操作,大家可以自己研究:

/**
 * 旋转方块图形
 */
private void rotate() {
 try {
 if (shape == 4) { // 方形,旋转前后是同一个形状
  return;
 } else if (shape == 0) { // 条状
  // 临时数组,放置旋转后图形
  State[][] tmp = new State[4][4];
  int startColumn = 0;
  // 找到图形开始的第一个方块位置
  for (int i = 0; i < columns; i++) {
  if (map[rowIndex][i] == State.ACTIVE) {
   startColumn = i;
   break;
  }
  }
  // 查找旋转之后是否有阻碍,如果有阻碍,则不旋转
  for (int i = 0; i < 4; i++) {
  for (int j = 0; j < 4; j++) {
   if (map[rowIndex - 3 + i][j + startColumn] == State.STOPED) {
   return;
   }
  }
  }

  if (map[rowIndex][startColumn + 1] == State.ACTIVE) { // 横向条形,变换为竖立条形
  for (int i = 0; i < 4; i++) {
   tmp[i][0] = State.ACTIVE;
   for (int j = 1; j < 4; j++) {
   tmp[i][j] = State.EMPTY;
   }
  }
  blockRows = 4;
  } else { // 竖立条形,变换为横向条形
  for (int j = 0; j < 4; j++) {
   tmp[3][j] = State.ACTIVE;
   for (int i = 0; i < 3; i++) {
   tmp[i][j] = State.EMPTY;
   }
  }
  blockRows = 1;
  }
  // 将原地图中图形修改为变换后图形
  for (int i = 0; i < 4; i++) {
  for (int j = 0; j < 4; j++) {
   map[rowIndex - 3 + i][startColumn + j] = tmp[i][j];
  }
  }
 } else {
  // 临时数组,放置旋转后图形
  State[][] tmp = new State[3][3];
  int startColumn = columns;
  // 找到图形开始的第一个方块位置
  for (int j = 0; j < 3; j++) {
  for (int i = 0; i < columns; i++) {
   if (map[rowIndex - j][i] == State.ACTIVE) {
   startColumn = i < startColumn ? i : startColumn;
   }
  }
  }
  // 判断变换后是否会遇到阻碍
  for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++) {
   if (map[rowIndex - 2 + j][startColumn + 2 - i] == State.STOPED)
   return;
  }
  }
  // 变换
  for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++) {
   tmp[2 - j][i] = map[rowIndex - 2 + i][startColumn + j];
  }
  }
  // 将原地图中图形修改为变换后图形
  for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++) {
   map[rowIndex - 2 + i][startColumn + j] = tmp[i][j];
  }
  }

  // 重绘
  repaint();
  // 重新修改行指针
  for (int i = 0; i < 3; i++) {
  for (int j = 0; j < 3; j++) {
   if (map[rowIndex - i][startColumn + j] != null
    || map[rowIndex - i][startColumn + j] != State.EMPTY) {
   rowIndex = rowIndex - i;
   blockRows = 3;
   return;
   }
  }
  }
 }
 } catch (Exception e) {
 // 遇到数组下标越界,说明不能变换图形形状,不作任何处理
 }
}

当图形下落遇到阻碍时停止,我们就需要判断这时是否有某一行或几行可以消除掉,这时可以先获取每行中方块的个数,然后再进行判断:

int[] blocksCount = new int[rows]; // 记录每行有方块的列数
int eliminateRows = 0; // 消除的行数
/* 计算每行方块数量 */
for (int i = 0; i < rows; i++) {
 blocksCount[i] = 0;
 for (int j = 0; j < columns; j++) {
 if (map[i][j] == State.STOPED)
  blocksCount[i]++;
 }
}

如果有满行的方块,则消除掉该行方块:

/* 实现有满行的方块消除操作 */
for (int i = 0; i < rows; i++) {
 if (blocksCount[i] == columns) {
 // 清除一行
 for (int m = i; m >= 0; m--) {
  for (int n = 0; n < columns; n++) {
  map[m][n] = (m == 0) ? State.EMPTY : map[m - 1][n];
  }
 }
  eliminateRows++; // 记录消除行数
 }
}

最后我们再重绘显示积分就可以了。

重复以上的生成图形、图形下落、左右下移动、判断消除行的操作,一个简单的俄罗斯方块就完成了。

运行效果:

完整示例代码:俄罗斯方块

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Application.yml的自定义属性的读取方式

    Application.yml的自定义属性的读取方式

    这篇文章主要介绍了Application.yml的自定义属性的读取方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 详解Java读取本地文件并显示在JSP文件中

    详解Java读取本地文件并显示在JSP文件中

    这篇文章主要介绍了详解Java读取本地文件并显示在JSP文件中的相关资料,这里提供实例帮助大家实现这样的功能,希望能帮助到大家,需要的朋友可以参考下
    2017-08-08
  • MyBatis的缓存解析

    MyBatis的缓存解析

    这篇文章主要介绍了MyBatis的缓存解析,一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会缓存,下次查询相同的数据就会从缓存中直接获取,不会从数据重新访问,前提必须是同一个SqlSession对象,并且查询的数据相同,需要的朋友可以参考下
    2023-09-09
  • 基于SpringBoot核心原理(自动配置、事件驱动、Condition)

    基于SpringBoot核心原理(自动配置、事件驱动、Condition)

    这篇文章主要介绍了基于SpringBoot核心原理(自动配置、事件驱动、Condition),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Spring中依赖注入(DI)几种方式解读

    Spring中依赖注入(DI)几种方式解读

    这篇文章主要介绍了Spring中依赖注入(DI)几种方式解读,构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖,需要的朋友可以参考下
    2024-01-01
  • Java日期时间以及日期相互转换

    Java日期时间以及日期相互转换

    这篇文章主要为大家详细介绍了Java日期时间,以及日期相互转换的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • JAVA SFTP文件上传、下载及批量下载实例

    JAVA SFTP文件上传、下载及批量下载实例

    本篇文章主要介绍了JAVA SFTP文件上传、下载及批量下载实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • MyBatis异常java.sql.SQLSyntaxErrorException的问题解决

    MyBatis异常java.sql.SQLSyntaxErrorException的问题解决

    使用mybatis插入数据时出现java.sql.SQLSyntaxErrorException异常,本文就来介绍一下MyBatis异常的问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • 关于@ComponentScan TypeFilter自定义指定扫描bean的规则

    关于@ComponentScan TypeFilter自定义指定扫描bean的规则

    这篇文章主要介绍了关于@ComponentScan TypeFilter自定义指定扫描bean的规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • servlet上传文件实现代码详解(四)

    servlet上传文件实现代码详解(四)

    这篇文章主要为大家详细介绍了servlet上传文件的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09

最新评论