该项目是跟着这个b站视频一步一步写出来的,初学java有些地方我看不是很明白,但是讲解很仔细,大家可以看原视频,我没有添加背景音乐和背景图片,做出来的效果也勉勉强强。
代码已经上传到github上了,大家可以去github上直接下载代码,附上链接:点击进入github源码链接
嫌麻烦进不去github的,我直接附上HTTPS链接和SSH,自己git clone就行
HTTPS: git clone https://github.com/19138060480/Genshin-elimination-game.git
SSH: git clone git@github.com:19138060480/Genshin-elimination-game.git
先上效果图:
因为懒得找原图,手边又刚好有原神的七元素图片,所以就用七元素做成了简易版的《原了个原》
当元素在放满七个后会判断游戏失败,每次图片是随机的(但是可以保证能消除完)后面灰色的图片是被遮盖的图片,无法点击(指点击后不会消失),当前面覆盖的图片被点击消失后才会恢复彩色图片(即可点击状态)。
效果图就先到这里,在上源码之前先看一下我创建的包和类,不想修改乱七八糟的东西的宝子们可以跟我设置相同的包名和类名,省的麻烦。(亲测,修改后可能会出现近百个错误);
特别注意的是,imgs里面的图片只要命名正确就可以了,不需要原图(指可以百度),后面加Gray的图片是灰色的,我用ps一个一个把图片拖进去变成灰色你知道有多累吗?(其实也可以通过代码.......)
源码来咯:
首先是BeginGame包里面的Play:
package BeginGame;
import Model.Brand;
import Model.Cell;
import Model.Layer;
import Model.Map;
import Tool.MapUtil;
import javax.swing.*;
import java.util.List;
//测试渲染一个地图
public class Play extends JFrame {
public static Map map= MapUtil.build(3);
public Play(){
/*初始化窗口基本信息*/
inti();
/*渲染图层*/
List<Layer> list=map.getList();
for (int i = 0; i < list.size(); i++) {
renderLayer(list.get(i));
}
map.compareAll(); //判定游戏开始时,所有牌是灰色还是彩色
/*自动刷新*/
autoRefresh(); //自动刷新线程
}
private void renderLayer(Layer layer){
/*渲染图层
默认情况下 brand牌的左上角坐标是0,0 需要改变牌的坐标
设置布局方式,默认swing 添加组件 提供了多种布局方式 网格、流线、、、 绝对布局
*/
Cell[][] cells=layer.getCells();
layer.showCell();
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*50+layer.getOffsetx(); //加上了偏移量
int y=row*50+layer.getOffsety(); //设置xy坐标
brands1.setBounds(x,y,50,50);
this.getContentPane().add(brands1);
}
System.out.println();
}
}
private void autoRefresh(){ //自动刷新方法
JFrame main=this;
new Thread(new Runnable() {
@Override
public void run() { //一直执行刷新页面
while(true){
main.repaint();
try {
Thread.sleep(40); //40毫秒刷新一次
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
private void inti(){
this.setTitle("羊了个羊"); //标题
this.setSize(450,800); //设置窗口大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭窗口的同时也关闭进程
/*设置绝对布局*/
this.setLayout(null);
this.setBounds(0,0,450,800);
this.setLocationRelativeTo(null); //窗口居中
this.setVisible(true); //窗口显示(默认关闭)
}
public static void main(String[] args) {
new Play();
}
}
然后是Model包
Brand:
package Model;
import BeginGame.Play;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/*
游戏中的牌
*/
public class Brand extends Component { //牌类
/*牌基础信息*/
private String name; //名称
private boolean isGray; //判断当前的牌是否是在牌堆下
private Image image; //正常图片
private Image grayimage; //牌堆下灰色图片
/*坐标*/
private Integer x; //代表在渲染的时候左上角的坐标
private Integer y;
/*宽高度*/
private Integer width;
private Integer height;
/**/
private Cell cell;
EliminateBox eliminateBox =new EliminateBox();
public Brand(String name){ //含name参数构造
this.name=name;
/*通过name的值 对应图片目录下的图片前缀*/
this.image = Toolkit.getDefaultToolkit().getImage("imgs\\"+name+".png"); //通过name找到图片
this.grayimage=Toolkit.getDefaultToolkit().getImage("imgs\\"+name+"Gray.png"); //通过name找到灰色图片
this.isGray=false; //默认不置灰
/*设置宽高*/
this.width=50;
this.height=50;
/*设置默认坐标*/
this.x=0;
this.y=0;
this.addMouseListener(new MouseAdapter() { //鼠标监听器
@Override
public void mouseClicked(MouseEvent e) { //点击鼠标
System.out.println("点击鼠标");
Brand brand=(Brand) e.getSource(); //点击后获取当前组件 强制下转型(默认是object)
if (brand.getGray()){
//灰色
return;
}else {
/*只在页面中删除了brand对象,但是cell状态中的state和brand没有删除*/
//brand.getParent().remove(brand); //调用上层容器删除自己 一般树形结构使用这样的方式
eliminateBox.addBox(brand);
/*解决问题关键是纪要删除UI当中的组件,还要删除数据模型中的数据和对应状态*/
cell.setState(0);
cell.setBrand(null);
Play.map.compareAll(); //涉及到map对象的共享 处理:把map对象设为静态变量
}
}
});
}
@Override
public void paint(Graphics g) { //重写Component的paint方法(绘制函数)
if(this.isGray==true){ //通过控制isGray来控制图片的灰色和彩色
//绘制灰色图片
g.drawImage(this.grayimage,this.x,this.y,null);
}else {
//绘制正常图片
g.drawImage(this.image,this.x,this.y,null);
}
}
/* set/get */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isGray() {
return isGray;
}
public void setGray(boolean gray) {
isGray = gray;
}
public boolean getGray(){
return isGray;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public Image getGrayimage() {
return grayimage;
}
public void setGrayimage(Image grayimage) {
this.grayimage = grayimage;
}
public Cell getCell() {
return cell;
}
public void setCell(Cell cell) {
this.cell = cell;
}
Cell:
package Model;
/*
单元格类
有两种状态, 0:无牌,1:有牌;
*/
public class Cell {
private Integer state; //0、1
private Brand brand;
/* get/set方法 */
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
}
**EliminateBox:**
package Model;
import javax.swing.*;
import java.util.*;
import java.util.Map;
import java.util.stream.Collectors;
public class EliminateBox { //消除区域
private static List<Brand> Box=new ArrayList<>(); //存放消除牌的数据
/*迭代器清空集合方法*/
void deleteByBrandName(String name){
Iterator<Brand> iterator = Box.iterator();
while (iterator.hasNext()){ //如果有下一个值
Brand next=iterator.next(); //获取下一个值
if (next.getName().equals(name)){
next.getParent().remove(next);
iterator.remove();
}
}
}
public void addBox(Brand brand){ //添加到消除区方法
Box.add(brand);
/*牌的排序(根据名称)*/
Box.sort(Comparator.comparing(Brand::getName));
/*消除算法*/
Map<String, List<Brand>> map= Box.stream().collect(Collectors.groupingBy(Brand::getName));//获取牌的名称
Set<String> key=map.keySet();
for (String s:key){
List<Brand> brands=map.get(s);
if (brands.size()==3){
deleteByBrandName(s); //调用迭代器消除方法
break;
}
}
paint(); //调用方法 绘制到消除区
over(brand);//调用方法 判断游戏结束
}
void paint(){ //绘制到消除区
for (int i = 0; i < Box.size(); i++) {
Brand brand = Box.get(i);
int x=i*brand.getWidth()+10;
brand.setBounds(x,600,50,50);
}
}
/*消除方法*/
void over(Brand brand){
if (Box.size()>=7){ //判断游戏结束
JOptionPane.showMessageDialog(brand,"游戏失败");
System.exit(0);
}
}
}
Layer:
package Model;
import java.util.Random;
/*
图层类
二维表格
*/
public class Layer {
private Integer offsetx; /*设置偏移量*/ //x轴
private Integer offsety; //偏移量 y轴
private Integer rowNum; //有多少行 (行数)
private Integer colNum; //有多少列 (列数)
private Integer capacity; //当前图层能最多容纳的牌数量 最大容量
private Integer size; //图层 目前有多少牌 当牌添加的时候,需要改变值、当牌减少的时候,也需要改变值
private Layer parent; //上一层图层对象
private Cell[][] cells =null;
public Layer(Integer rowNum, Integer colNum) throws Exception{ //构造函数,传递参数为行号和列号(因为其他属性都可以通过行号列号知道)
this.rowNum = rowNum;
this.colNum = colNum;
this.capacity= this.rowNum * this.colNum; //容量为行数×列数
if(this.capacity%3!=0){
throw new Exception("容量不是3的倍数");
}
this.cells=new Cell[this.rowNum][this.colNum]; //因为传递的是行号和列号,所以可以通过行号和列号创建对象.
this.size=0; //默认为零
this.offsetx=new Random().nextInt(100);
this.offsety=new Random().nextInt(100); //用随机数设置偏移量
}
public void showCell(){
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
Brand brands1= cells[row][col].getBrand();
System.out.print(brands1.getName()+"-");
}
System.out.println();
}
}
/* get/set方法 */
public Layer getParent() {
return parent;
}
public void setParent(Layer parent) {
this.parent = parent;
}
public Integer getOffsety() {
return offsety;
}
public void setOffsety(Integer offsety) {
this.offsety = offsety;
}
public Integer getOffsetx() {
return offsetx;
}
public void setOffsetx(Integer offsetx) {
this.offsetx = offsetx;
}
public Integer getRowNum() {
return rowNum;
}
public void setRowNum(Integer rowNum) {
this.rowNum = rowNum;
}
public Integer getColNum() {
return colNum;
}
public void setColNum(Integer colNum) {
this.colNum = colNum;
}
public Integer getCapacity() {
return capacity;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public Cell[][] getCells() {
return cells;
}
public void setCells(Cell[][] cells) {
this.cells = cells;
}
}
Map:
package Model;
import Tool.MapUtil;
import java.util.ArrayList;
import java.util.List;
//图层
public class Map {
private Integer floorHeight; //层高 有几个图层
private List<Layer> list=new ArrayList<>(); //存放图层数据
/* set/get */
public Integer getFloorHeight() {
return floorHeight;
}
public void setFloorHeight(Integer floorHeight) {
this.floorHeight = floorHeight;
}
public List<Layer> getList() {
return list;
}
public void setList(List<Layer> list) {
this.list = list;
}
/*判断当前map中所有牌是否置灰*/
public void compareAll(){
System.out.println("map.compareAll");
//i=0是最顶层layer,不需要判断
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]; //先拿到单元格对象
if (cell.getState()==1){
Brand brand = cell.getBrand(); //有牌取牌
boolean result=MapUtil.compare(brand,layer.getParent());
brand.setGray(result);
}
}
}
}
}
}
然后是Tool工具包:
BrandUtil:
package Tool;
import Model.Brand;
import java.util.Random;
/*
工具类
提供 创建牌相关的一些公共方法
*/
public class BrandUtil {
public static Random random =new Random();
public static String[] brandNames= {"风","岩","雷","草","水","火","冰"}; //牌名数组
public static String getBrandName(){ //随机获取一个牌的名称
int a=random.nextInt(brandNames.length);
return brandNames[a];
}
//创建随机牌
public static Brand[] buildBrands(Integer capcity){ //需要参数(数组大小)
Brand brands[]=new Brand[capcity];
for (int i = 0; i < brands.length; i=i+3) {
String randomBrandName=getBrandName(); //每次循环获取一个随机牌名称
Brand brand1=new Brand(randomBrandName); //名称传递,创建新的对象
Brand brand2=new Brand(randomBrandName);
Brand brand3=new Brand(randomBrandName); //创建三个相同的对象,方便删除牌
brands[i]=brand1;
brands[i+1]=brand2;
brands[i+2]=brand3;
}
for (int i = 0; i < brands.length; i++) {
//当前位置A的变量拿到
Brand brandA = brands[i];
//随机交换位置
int randomIndex=random.nextInt(brands.length);
Brand brandB=brands[randomIndex];
Brand temp=brandA;
brands[i]= brandB;
brands[randomIndex]=temp; //交换
}
return brands;
}
}
LayerUtil:
package Tool;
import Model.Brand;
import Model.Cell;
import Model.Layer;
public class LayerUtil {
public static Layer build(Integer rowNum, Integer colNum) {
Layer layer = null;
try {
layer = new Layer(rowNum, colNum); //容量需要为3的倍数,不然下面for循环因为i=i+3时,数组越界会报错
} catch (Exception e) {
e.printStackTrace();
}
Brand[] brands=BrandUtil.buildBrands(layer.getCapacity());
Cell cells[][] = layer.getCells();
int flag=0;
for (int row = 0; row < cells.length; row++) {
for (int col = 0; col < cells[row].length; col++) {
// System.out.println(row+"-"+col);
Brand brands1=brands[flag++];
Cell cell = new Cell(); //初始化单元格对象
cell.setState(1);
cell.setBrand(brands1); //单元格对象找到我们牌
brands1.setCell(cell); //牌反向找到单元格对象, 互相链式关系
cells[row][col] = cell; //把之前空的图层设置了值
}
}
return layer;
}
}
MapUtil:
package Tool;
import Model.Brand;
import Model.Cell;
import Model.Layer;
import Model.Map;
import java.awt.*;
public class MapUtil {
public static Map build(Integer floorHeight){
Map map=new Map();
map.setFloorHeight(floorHeight);
Layer layer1= LayerUtil.build(3,5);
Layer layer2= LayerUtil.build(9,4);
Layer layer3= LayerUtil.build(6,4);
Layer layer4= LayerUtil.build(6,9);
Layer layer5= LayerUtil.build(3,3);
Layer layer6= LayerUtil.build(10,9);
Layer layer7= LayerUtil.build(3,8);
Layer layer8= LayerUtil.build(9,6);
Layer layer9= LayerUtil.build(6,9);
Layer layer10= LayerUtil.build(6,12);
Layer layer11= LayerUtil.build(6,5);
Layer layer12= LayerUtil.build(4,9);
Layer layer13= LayerUtil.build(5,9);
Layer layer14= LayerUtil.build(7,9);
Layer layer15= LayerUtil.build(9,9);
Layer layer16= LayerUtil.build(2,9);
Layer layer17= LayerUtil.build(5,9);
Layer layer18= LayerUtil.build(4,9);
layer1.setParent(null); //parent为null时已经是最后一层了 是循环递归结束的条件
layer2.setParent(layer1);
layer3.setParent(layer2); //用链式关系把图层锁起来
layer4.setParent(layer3);
layer5.setParent(layer4);
layer6.setParent(layer5);
layer7.setParent(layer6);
layer8.setParent(layer7);
layer9.setParent(layer8);
layer10.setParent(layer9);
layer11.setParent(layer10);
layer12.setParent(layer11);
layer13.setParent(layer12);
layer14.setParent(layer13);
layer15.setParent(layer14);
layer16.setParent(layer15);
layer17.setParent(layer16);
layer18.setParent(layer17);
map.getList().add(layer1);
map.getList().add(layer2);
map.getList().add(layer3);
map.getList().add(layer4);
map.getList().add(layer5);
map.getList().add(layer6);
map.getList().add(layer7);
map.getList().add(layer8);
map.getList().add(layer9);
map.getList().add(layer10);
map.getList().add(layer11);
map.getList().add(layer12);
map.getList().add(layer13);
map.getList().add(layer14);
map.getList().add(layer15);
map.getList().add(layer16);
map.getList().add(layer17);
map.getList().add(layer18);
return map;
}
public static boolean compare(Brand brand, Layer layer){ //判断当前牌和某一图层所有牌是否有矩阵交集,ture表示有交集,显示灰色,false表示没有交集,显示正常牌
Cell cells[][]=layer.getCells();
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();
Rectangle rect=brand.getBounds();
boolean result=rect.intersects(temp); //布尔类型判断是否有交集
if (result){
//有交集说明被上层牌盖住了
return result; //判定结束,结束方法
}
}
}
System.out.println();
}
/*如果跳出了上面的循环,说明都没有交集,需要和更上层进行对比*/
if (layer.getParent()!=null){
return compare(brand,layer.getParent()); //递归判定
}else{ //如果getparent等于null,说明已经到最顶层了
return false;
}
}
}
好了只用这些就能完整的运行啦,细心的人可能发现了我没有发Test包里的东西 ,因为那只是用来做测试的!!!不过如果想要的话私聊我,我看看能不能把整个压缩包发给你.......
我这上面注释写的应该还算清楚,应该不难理解吧,不过为了以防万一,我还是说一下一些关键可修改的地方
首先是MapUtil类里面的,这个包是不是看到很多重复的代码块(不是为了凑代码行数!!)
这是为了创建不同的图层,这个游戏可以理解为有很多图层叠在一起,然后这里就是设置图层的个数,每创建一组就多一层图层。
这一行是设置图层的长rowNum和宽colNum的 (注意长和宽的乘积一定要是3的倍数,最简单的方法就是其中一个是3或者3的倍数,不然会报错,因为在LayerUtil类里设置了捕获异常,捕获的是BrandUtil类里,因为Brand对象是三个三个创建的,如果不是三的倍数会出现数组越界)
这一行是为了让后一个图层把上一层图层联系起来,层与层之间互相关联,比如这个图就是layer3与layer2锁在一起,让layer3的父层为layer2,如果要新添加图层的话,新添加的图层也需要把上一层设置为父层。
这一条代码是为了让图层显示出来,如果没有这一行,你的图层将不会渲染到游戏里,add括号内写哪一层就渲染哪一层。
这三条语句可以看为一个整体,如果要添加或者删除图层的话,只需要修改对应的这三条语句就可以了。
然后是BrandUtil类里
brandNames数组里是你的图片名称,你可以随意修改(前提是找得到图片),图片在imgs文件夹下,和Total_class是同级的,注意你也要创建一个imgs文件夹来存放图片,不然就进入brand类里面,把这个imgs改成你文件的名字
特别说一下,设置的图层偏移量是在Play类里面,写的有注释,可以自行查找也可以不改,还有就是每个图层的长宽比不要太大,不然会出现你不想看到的结果(指bug)。
其他的有注释,能看懂的应该都能看懂,看不懂的那我也没办法,照抄就好了。
版权归原作者 心朝乂安 所有, 如有侵权,请联系我们删除。