三. 数据模型3 - Map类
1. Map类 - 地图
注意: 一个地图下面有多个图层, 层层遮盖
重要属性:
floorHeight: 层高 Integer (有几张图层, 与游戏难度相关)
list: 存放图层数据 List<Layer>
生成 get 和 set 方法
/**
* 地图的构建
* 一个地图下面有多个图层, 层层遮盖
*/
public class Map {
private Integer floorHeight; //层高, 有几张图层, 游戏难度是相关的
private List<Layer> list = new ArrayList<>();
// get和set方法
}
2. 测试地图 - TestBuildMap.class
/**
* 测试创建一个地图实例 层高3
*/
public class TestBuildMap {
public static void main(String[] args) {
Map map = new Map();
map.setFloorHeight(3); // 设置层高为3层
// 创建第一层图层
Layer layer1 = LayerUtil.build(3, 3);
// 创建第二层图层
Layer layer2 = LayerUtil.build(6, 6);
// 创建第三层图层
Layer layer3 = LayerUtil.build(9, 9);
// 将创建好的图层加入到地图中
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
// 因为List是集合, 所以可以遍历
// 通过map.getList() 获取到list集合
List<Layer> list = map.getList();
for (int i=0;i<list.size();i++){
// 打印当前图层
System.out.println("第"+i+"个图层");
// list.get(i) : 获取到当前图层
// 当前图层有 showCells()方法, 查看每一个图层的内容
list.get(i).showCells();
}
}
}
3. 代码重构 - MapUtil.class
public class MapUtil {
// 有一个build方法
public static Map build(Integer floorHeight){
Map map = new Map();
map.setFloorHeight(3);
Layer layer1 = LayerUtil.build(6, 6);
Layer layer2 = LayerUtil.build(6, 6);
Layer layer3 = LayerUtil.build(6, 6);
map.getList().add(layer1); // 结论 在绝对布局中 同样位置 先加入的组件展示最上层
map.getList().add(layer2); // 和现实当中 后面加入的会再上层是不同的
map.getList().add(layer3);
// 把 map 创建好返回
return map;
}
4. 测试类,渲染map - TestRenderMap.class
public class TestRenderMap extends JFrame{
// 通过MapUtil的build方法 拿到一个Map
// 然后 拿到图层的List , 进行遍历
public static Map map = MapUtil.build(3);
public TestRenderMap() throws FileNotFoundException, JavaLayerException {
// 初始化窗口的基本信息
init();
// 2.渲染图层
List<Layer> list = map.getList();
for (int i=0;i<list.size();i++){
// 调用renderLayer方法, 将当前的图层对象传入
renderLayer(list.get(i));
}
// 3.自动刷新
autoRefresh();
}
// 定义renderLayer方法,传入图层对象
private void renderLayer(Layer layer){
//通过图层获取到二维数组
Cell[][] cells = layer.getCells();
// 通过遍历将一个一个图层刷新到界面上
for(int row=0;row<cells.length;row++){
for (int col=0;col<cells[row].length;col++){
Brand brands1 = cells[row][col].getBrand();
int x = col * 59;
int y = row * 66;
brands1.setBounds(x,y,59,66);
this.getContentPane().add(brands1);
}
System.out.println();
}
}
public static void main(String[] args) throws FileNotFoundException, JavaLayerException {
new TestRenderMap();
}
}
注意
测试时:创建地图都创建成 3*3 - MapUtil.java中修改代码
**5. 修改bug: 图层的偏移 **
绘制 3*3 的图层, 但是完全被覆盖住了
而游戏当中会有 部分遮盖的效果, 可以看到下一层牌的一部分, 从而猜测是什么牌
这样增加了游戏的可玩性 - 设置图层偏移量
以下为测试代码
Layer.class - 先测试
public class Layer {
private Integer offsetX; // 偏移量
private Integer offsetY; // 偏移量
// 给到 get 和 set 方法
// 在构造方法赋值
public Layer(Integer rowNum, Integer colNum) throws Exception {
this.offsetX = 0;
this.offsetY = 0;
}
}
MapUtil.class
public class MapUtil {
// 有一个build方法
public static Map build(Integer floorHeight){
Map map = new Map();
map.setFloorHeight(3);
layer1.setOffsetX(60);
layer2.setOffsetX(30);
layer3.setOffsetX(20);
Layer layer1 = LayerUtil.build(6, 6);
Layer layer2 = LayerUtil.build(6, 6);
Layer layer3 = LayerUtil.build(6, 6);
TestBuildMap.class
private void renderLayer(Layer layer){
Cell[][] cells = layer.getCells();
// 打印图层测试
layer.showCells();
for(int row=0;row<cells.length;row++){
for (int col=0;col<cells[row].length;col++){
Brand brands1 = cells[row][col].getBrand();
// 设置随机数
int x = col * 59 + layer.getOffsetX();
int y = row * 66;
brands1.setBounds(x,y,59,66);
this.getContentPane().add(brands1);
}
System.out.println();
}
}
x轴和y轴都设置
Layer.java
public class Layer {
private Integer offsetX; // 偏移量
private Integer offsetY; // 偏移量
// 设置对应的get 和 set 方法
// 构造方法中赋随机数
public Layer(Integer rowNum, Integer colNum) throws Exception {
this.offsetX = new Random().nextInt(100);
this.offsetY = new Random().nextInt(100); // 设置0-100的随机数
}
}
TestRenderMap.java
四. 图层的遮盖判定算法思路
问题1: 假定我们有 9层 图层, 最顶层称为1层, 最下层我们称为9层, 那么此时第六层当中有一张牌, 他应该是正常还是 显示灰色状态呢?
那么 第一层 的牌呢? 最后一层第9层的 牌呢?
思路:
- 判定 6层的某一张牌, 和他上层的 5层的所有牌进行一个比较, 是否有交集
– 如果有: 则说明盖住了, 显示灰色; 不用再判断6层的这张牌 和上层其他牌的遮盖问题
– 如果没有, 则说明5层的牌并没有盖住它(6层的这张牌); 则继续跟4层比较,如果还没有, 继续比较更高层的牌, 直到 最高层 1层, 全部比较完毕- 特殊情况1: 第一层的牌 没有遮盖问题, 都是显示正常
- 特殊情况2: 消除的游戏, 所以牌在减少, 某一层牌为0了, 或者某个单元格为空 - 消除过程中进行考虑判断
具有优化的可能, 目前先暂时实现基本功能, 在考虑优化
1.图层遮盖算法的实现-1
图层建立时, 同时也建立链式结构
- Layer当中增加parentLayer属性 也就是当前图层的上层
实现compare函数, 比较一张牌和一个图层的遮盖问题- 递归实现: 如果和当前图层没有遮盖, 则比较上上层 , 直到顶层
Layer.class
public class Layer {
private Layer parent; // 保存上一层图层对象
// 提供get set 方法
}
在MapUtil中构建图层的链式关系
public class MapUtil {
// 有一个build方法
public static Map build(Integer floorHeight){
Map map = new Map();
map.setFloorHeight(3);
Layer layer1 = LayerUtil.build(6, 6);
Layer layer2 = LayerUtil.build(6, 6);
Layer layer3 = LayerUtil.build(6, 6);
// 构建图层的 链式关系
layer3.setParent(layer2);
layer2.setParent(layer1);
layer1.setParent(null);
// 强调: parent=null 说明当前这一层已经是顶层,
// 这是循环 或者递归结束的的重要条件
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
return map;
}
// 定义compare方法 判断是否遮盖
// 函数的作用: 判断当前牌和某一图层内的所有牌是否有 矩阵交集
// true : 有交集, 当前牌显示灰色
// false: 没有交集, 当前牌显示亮色
public static boolean compare(Brand brand, Layer layer){
// 获得当前层的所有 cell 单元格
Cell[][] cells = layer.getCells();
// 遍历cells
for(int row=0;row<cells.length;row++){
for (int col=0;col<cells[row].length;col++){
// 如果当前的 单元格 是空, 不用比较;
//获取某一个cell对象
Cell cell = cells[row][col];
if (cell.getState()==1){
// 当前单元格有牌, 才能跟传入进来的牌进行比较
Rectangle temp = cell.getBrand().getBounds();// 获取单元格当中牌对应的矩阵
// 获取传入进来的牌的 矩阵
// 目标显示对象.getBounds(参照显示对象)-返回一个矩形显示对象区域A(x,y,width,height)-jdk提供
Rectangle rect = brand.getBounds();
// 比较矩阵和矩阵之间是否有交集的方法 - intersects
boolean result = rect.intersects(temp);
if (result){ // true
//有交集 说明被上面的 牌被盖住了 判断结束
return result;
}
}
}
}
// 与当前图层的所有牌都比较完毕 没有交集
// 此时需要和更上层的牌进行比较判断
//getParent() 获取到当前图层的上一层
// 假设当前有9层, 正在比较的是6层当中的某个牌 正在和5层进行比较
// layer.getParent() 是获取到4层的所有数据
if (layer.getParent()!=null){
return compare(brand, layer.getParent());
}else{
//如果 parent==null.说明已经到顶层了
return false;
}
}
}
2.实现遮盖后不能点击且是灰色的
把map当中所有的牌都调用一次compare函数 , 从而判断是否置灰
Map.java
public class Map {
/**
* 判断当前map中所有的牌是否置灰
*
* 循环太多层, 牌越多 性能越差
* 这个函数什么时候调用 - 除了以下两种情况, compare函数不执行
* 1. 游戏开始的时候调用一次
* 2. 牌点击之后需要调用
*/
public void compareAll(){
// 测试代码 - 看方法是否执行
System.out.println("Map.compareAll");
// 先遍历当前 list
// 索引号: i=0 是最顶层, 不需要判断 所以直接i=1开始判断
for (int i=1;i<list.size();i++){
// 获取到图层对象
Layer layer = list.get(i);
// 获取到图层对象的二维数组
Cell[][] cells = layer.getCells();
// 对二维数组进行循环遍历, 获取二维数组中的每一张牌
for(int row=0;row<cells.length;row++){
for (int col=0;col<cells[row].length;col++){
// 获取单元格对象
Cell cell = cells[row][col];
// 判断当前单元格是否等于1
if (cell.getState()==1){
// 如果有牌, 获取当前的单元格中的牌
Brand brand = cell.getBrand();
// 调用compare方法, 将当前牌 以及 当前牌的上一个图层传入进去
boolean result = MapUtil.compare(brand, layer.getParent());
// result: true 或 false;
brand.setGray(result);
}
}
System.out.println();
}
}
}
}
Brand.java-修改
TestRenderMap.class
3.问题:点击完上面的牌之后, 下面的牌没有变为正常颜色 - Brand.class
compareAll() 函数在游戏开始时调用了一次, 但是在牌点击消失后没有调用
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Brand.mouseClicked");
Brand brand =(Brand) e.getSource();// 获取当前的组件
if (brand.getGray()){
// 灰色 不能被点击
return;
}else{
// 每消除一个调用compareAll()方法
// 2. 但是通过 parent.remove() 只是在页面的UI树中删掉了 brand对象
// 但是 单元格 cell当中状态 state 和 brand对象 并没有删除
// 在我们自己构建的数据结构中还存在 : state并没有变为0 , 而且单元格中的Brand还在
// 解决: 把UI中的牌删除 同时 删除数据模型中的数据 和对应状态
//brand.getParent().remove(brand); // 通过父容器删除自己 - 一般树形结构使用
cell.setState(0);
cell.setBrand(null);
// 以上cell 会报错, 因为在Brand对象中, 获取不到cell对象
// 解决办法: 一个图层有很多的单元格对象, 一个地图有更多 - 采用从上往下找的办法会特别麻烦
// 采用链式的办法 : 定义单元格对象的时候 , 把单元格对象传进来
// 在Brand中定义 cell对象 private Cell cell; 提供get和set 方法
// 在MapUtil中创建调用LayerUtil.build: 创建牌对象, 一个一个放到 cell中
//--将牌 给到单元格对象 cell.setBrand(brands1);
//--让牌 反向找到单元格对象 brands1.setCell(cell);
//-----------------------------------------------------
// 也需要判断 整个map地图 所有牌哪些是置灰
// 1. 需要调用 map.compareAll() - 当前Brand对象并没有Map对象,
// 问题: map对象的共享问题
// 在 TestRenderMap.java中, 将map对象设置为 static, 通过类名访问 -- 不是特别好
// 但是 调用map.compareAll() 还是没有显示为正常颜色 - 解决
// 原因: 与conpareAll的比较逻辑有关系
// 逻辑是: 找到map中的单元格, 判断单元格里面的状态是否为1,如果是1比较, 否则不比较
TestRenderMap.map.compareAll();
}
五. 消除区域实现-ElimiateBox.class
Brand.java
public class Brand extends Component{
private String name; //牌的名称
ElimiateBox elimiateBox = new ElimiateBox();
}
消除区域代码逻辑
/**
* 消除区域
*/
public class ElimiateBox {
// 1. 定义列表存放点击牌的数据
private static List<Brand> slot = new ArrayList<>();
//2. 定义方法: 消除牌的逻辑
public void addSlot(Brand brand){
slot.add(brand);
// 牌的排序 - 调用sort方法
slot.sort(Comparator.comparing(Brand::getName));
// 根据牌的名称进行消除
// 获取牌的名称
// 快捷键:slot.stream().collect(Collectors.groupingBy(Brand::getName));
Map<String, List<Brand>> map = slot.stream().collect(Collectors.groupingBy(Brand::getName));
// 使用keySet() 方法拿到键
Set<String> key = map.keySet();
//快捷键: key.for
for (String s:key){
List<Brand> brands = map.get(s);
if (brands.size()==3){
// 我们使用的是增强for循环, 不能 一边添加 一边删除
// 所以使用迭代器进行清空集合
// 创建方法用来写迭代器清空集合方法 - deleteByBrandName()
deleteByBrandName(s);
break;
}
}
// 4. 调用paint方法
paint();
// 5. 返回到Brand类中,将 //brand.getParent().remove(brand);
// 改为 elimiateBox.addSlot(brand);
over(brand);
}
// 3. 绘制到消除框
void paint(){
for (int i=0;i<slot.size();i++){
Brand brand = slot.get(i);
int x = i*brand.getWidth()+20;
brand.setBounds(x,640,59,66);
}
}
// 设置游戏失败的方法
void over(Brand brand){
if(slot.size()>=7){
JOptionPane.showMessageDialog(brand, "游戏结束!");
System.exit(0);
}
}
// 迭代器清空集合
void deleteByBrandName(String name){
// 获取迭代器
Iterator<Brand> iterator = slot.iterator();
while(iterator.hasNext()){
Brand next = iterator.next();
if(next.getName().equals(name)){
next.getParent().remove(next);
iterator.remove();
}
}
}
}
六. 添加音乐
- 创建根目录music, 引入音乐
- 创建lib目录,存放jar包
M\usic.class
/**
* 添加音乐: 引入jar包
*/
public class Music {
// 构造方法
public void music() throws FileNotFoundException, JavaLayerException {
// 通过缓冲流的方式, 获取音乐
// System.getProperty("user.dir") -- 获取根目录
String str = System.getProperty("user.dir")+"/music/sheep.mp3";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(str));
// 通过 获取播放器, 传入音乐地址
Player player = new Player(bufferedInputStream);
player.play();
}
}
最后在 TestRenderMap中调用
七.添加背景图片