【JavaFx】使用JavaFx实现拼图游戏 实现键盘操作

  • 参考博客:https://blog.csdn.net/qq_42370146/article/details/84842168#commentBox
  • 在原博主的拼图游戏上添加了键盘控制系统,即通过方向键上下左右进行交换。
  • 思路很简单,就是通过GridPane获取坐标的功能计算出上下左右的坐标,然后通过已经实现的交换方法进行交换,但是交换方法必须获得两个源,而:
  • GridPane不支持通过坐标访问源,所以算出坐标后并不能直接访问来进行交换,所以如何通过坐标来联系上在那个位置的那张图片的源就是难点。
  • 解决方法:n[]这个随机数组中值虽然是随机的,但是n[1],n[2]却是依次排列的,比如imageview[n[5]]就代表第5张图,这样就可以通过以n[5]的值作为ImageView的索引来实现访问第几张图了。然后就可以进行交换了。
  • 但是交换过去后有一个很严重的问题,比如在向上交换后再向下交换,就是把第九张图和第六张图交换之后想再换回去时,现在空白方块在地址5,通过计算下面的方块地址为8,应该和n[8]交换,但是n[8]其实还是刚刚的第九张图,因此就会一直和自己交换,程序看上去就一动不动。
  • 其实我们交换的是图片的源,和n[]并没有关系,所以n[]还是原来的样子,并没有跟着一起交换,这样就导致算出来的新坐标对应的还是老的索引,因此我们应该把n[]也跟着一起交换,达到一致。而n[]中只有8个元素,我们要交换的话,需要九个元素都在,因此要创建一个新数组,前八个元素一样,把缺了的元素m补在最后。
  • 实现:使用一个int[] ncopy = new int[9]来拓展随机数组n[8]。通过遍历将前八个元素复制,并将m添加到ncopy最后。在源图片交换后,将数组里的元素也交换。

  • 键盘操作部分代码:
switch (arg0.getCode()) {
     
			case DOWN:
				// 计算出需要交换的对象图片的位置nindex
				int nrow = mrow + 1;
				int ncol = mcol;
				int nindex = nrow * 3 + ncol;
				if (nindex >= 0 && nindex <= 8) {
     
					// 由于n[]中的索引是和位置一一对应的,再将其值作为imageViews的索引
					// 只要算出nindex,就能对应上相应的图片。将其交换
					// ncopy拓展了n,将m作为第九个元素加入数组,否则就访问不到最后一张图片。
					myevent1.swapimg(imageViews[m], imageViews[ncopy[nindex]]);
					// 图片交换后,将数组值也交换,否则多次操作之后数据会混乱,导致胡乱交换。
					int temp2 = ncopy[mindex];
					ncopy[mindex] = ncopy[nindex];
					ncopy[nindex] = temp2;
				}
				break;

  • Game_1:
package application;

import java.util.Random;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.Arrays;

public class Game_1 extends Application {
     
	public int m; // m是不在随机数组的那个数字
	ImageView[] imageViews = new ImageView[9];
	Image image2 = new Image("application/test.jpg", 800, 800, false, false);
	public int partImagewidth = (int) image2.getHeight() / 3;
	public int[] ncopy;

	@Override
	public void start(Stage primaryStage) {
     
		init(primaryStage);
	}

	// 绘制拼图界面和按钮
	public void init(Stage primaryStage) {
     
		// 父布局
		VBox vBox = new VBox();

		// 3*3九宫格布局,作为上方的选项嵌套入父布局
		GridPane gridPane = new GridPane();

		// 水平布局,作为下方的选项嵌套入父布局
		HBox hbox = new HBox();
		hbox.setPadding(new Insets(10)); // 填充
		hbox.setAlignment(Pos.CENTER); // 居中

		// 添加3个点击
		Button home = new Button("返回首页");
		home.setOnAction(e -> {
     
			Start start = new Start();
			start.start(new Stage());
			primaryStage.close();
		});

		Button again = new Button("重新游戏");
		again.setOnAction(e -> {
     
			Game_1 game = new Game_1();
			game.start(new Stage());
			primaryStage.close();
		});

		Button look = new Button("查看原图");
		look.setOnAction(e -> {
     
			OnImage_1 OnImage = new OnImage_1();
			OnImage.start(new Stage());
		});

		// 设置按钮大小并居中
		home.setPrefSize(200, 50);
		again.setPrefSize(200, 50);
		look.setPrefSize(200, 50);
		// 设置控件之间的距离
		hbox.setSpacing(100);

		hbox.getChildren().addAll(home, again, look);

		// 游戏部分
		/*
		 * 自定义的函数,产生逆序数为偶数的不重复数组,依次作为图片数组的下标,例如: 2 0 1 3 4 5 6 7 ------>,此时m=8,变成如下即为正确:
		 * 0 1 2 3 4 5 4 7
		 */

		// 定义逆序数为偶数的随机数组
		int[] n = random();
		// 找出那个不在随机数组里面的数字
		m = findnum(n);

		// 定义一个二维数组来存放随机数和地址 在键盘操作中用到
		ncopy = new int[9];
		for (int i = 0; i < 9; i++) {
     
			if (i == 8) {
     
				ncopy[8] = m;
			} else {
     
				ncopy[i] = n[i];
			}
		}

		for (int i = 0; i < 9; i++) {
     
			imageViews[i] = new ImageView(image2); // 初始化数组
			imageViews[i].setOnMouseClicked(new myevent()); // 设置点击事件
			imageViews[i].setOnKeyPressed(new myKeyEvent());// 设置键盘事件
		}

		// 切割图片分配给图片数组
		for (int i = 0, k = 0; i <= 2; i++) {
     
			for (int j = 0; j <= 2; j++, k++) {
     
				imageViews[k].setViewport(
						new Rectangle2D(partImagewidth * j, partImagewidth * i, partImagewidth, partImagewidth));
			}
		}

		// 按照产生的随机数将imageView数组加入面板
		gridPane.add(imageViews[n[0]], 0, 0);
		gridPane.add(imageViews[n[1]], 1, 0);
		gridPane.add(imageViews[n[2]], 2, 0);
		gridPane.add(imageViews[n[3]], 0, 1);
		gridPane.add(imageViews[n[4]], 1, 1);
		gridPane.add(imageViews[n[5]], 2, 1);
		gridPane.add(imageViews[n[6]], 0, 2);
		gridPane.add(imageViews[n[7]], 1, 2);

		// 设定空白区域
		Image image1 = new Image("file:C:\\Users\\CharlesChen\\Desktop\\empty.png"); // 3.png为一个透明图,放在空格子中
		imageViews[m].setImage(image1);
		gridPane.add(imageViews[m], 2, 2);
		gridPane.setGridLinesVisible(true);
		gridPane.setPrefWidth(800);
		gridPane.setPrefHeight(800);

		vBox.getChildren().add(gridPane);
		// 布局嵌套,选项部分
		vBox.getChildren().add(hbox);

		Scene scene = new Scene(vBox, 800, 850);
		scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
		primaryStage.setTitle("智障拼图");
		primaryStage.setScene(scene);
		primaryStage.setResizable(true);
		primaryStage.show();

		imageViews[m].requestFocus();
	}

	// 生成8个不重复的逆序数为偶数的数字,这样拼图才有解
	public int[] random() {
     
		int[] ran = new int[8];
		while (iso(ran) == false) {
     
			ran = random_num();
		}
		return ran;
	}

	// 生成8个不重复数
	public int[] random_num() {
     
		int r[] = new int[8];
		Random random = new Random();
		for (int i = 0; i < 8; ++i) {
     
			r[i] = random.nextInt(9);
			for (int j = 0; j < i; ++j) {
     
				while (r[i] == r[j]) {
     
					i--;
					break;
				}
			}
		}
		return r;
	}

	// 判断逆序数是否为偶数
	public boolean iso(int[] num) {
     
		int sum = 0;
		for (int i = 0; i <= 6; ++i) {
     
			for (int j = i; j <= 7; j++) {
     
				if (num[i] > num[j]) {
     
					sum++;
				}
			}
		}
		if ((sum % 2) == 0 && sum != 0) {
     
			return true;
		}

		return false;

	}

	// 点击事件的实现
	class myevent implements EventHandler<MouseEvent> {
     

		@Override
		public void handle(MouseEvent arg0) {
     

			// 取得鼠标点击位置处图片的位置
			ImageView img = (ImageView) arg0.getSource();
			double sx = img.getLayoutX();
			double sy = img.getLayoutY();
			// 计算与空白图片位置的差值
			double dispx = sx - imageViews[m].getLayoutX();
			double dispy = sy - imageViews[m].getLayoutY();

			if ((dispx == -partImagewidth) && (dispy == 0)) {
      // 点击的空格左边的格子
				swapimg(img, imageViews[m]); // 交换imageView
				if (issucc(imageViews)) {
      // 判断是否拼成功
					Alert alert = new Alert(AlertType.WARNING, "恭喜你,拼图成功!");
					alert.show();
				}
			} else if ((dispx == 0) && (dispy == -partImagewidth)) {
      // 上面的格子
				swapimg(img, imageViews[m]);
				if (issucc(imageViews)) {
     
					Alert alert = new Alert(AlertType.WARNING, "恭喜你,拼图成功!");
					alert.show();
				}
			} else if ((dispx == partImagewidth) && (dispy == 0)) {
      // 右边的格子
				swapimg(img, imageViews[m]);
				if (issucc(imageViews)) {
     
					Alert alert = new Alert(AlertType.WARNING, "恭喜你,拼图成功!");
					alert.show();
				}
			} else if ((dispx == 0) && (dispy == partImagewidth)) {
      // 下面的格子
				swapimg(img, imageViews[m]);
				if (issucc(imageViews)) {
     
					Alert alert = new Alert(AlertType.WARNING, "恭喜你,拼图成功!");
					alert.show();
				}
			}
		}

		// 交换两个imageView的实现
		public void swapimg(ImageView i1, ImageView i2) {
     
			int row1 = GridPane.getRowIndex(i1);
			int colu1 = GridPane.getColumnIndex(i1);
			int row2 = GridPane.getRowIndex(i2);
			int colu2 = GridPane.getColumnIndex(i2);
			GridPane.setRowIndex(i1, row2);
			GridPane.setColumnIndex(i1, colu2);
			GridPane.setRowIndex(i2, row1);
			GridPane.setColumnIndex(i2, colu1);
		}
	}

	// 键盘操作的实现
	class myKeyEvent implements EventHandler<KeyEvent> {
     

		@Override
		public void handle(KeyEvent arg0) {
     

			myevent myevent1 = new myevent();
			int mrow = GridPane.getRowIndex(imageViews[m]);
			int mcol = GridPane.getColumnIndex(imageViews[m]);
			int mindex = mrow * 3 + mcol;

			// 针对不同的按键给出不同的反应
			switch (arg0.getCode()) {
     
			case DOWN:
				// 计算出需要交换的对象图片的位置nindex
				int nrow = mrow + 1;
				int ncol = mcol;
				int nindex = nrow * 3 + ncol;
				if (nindex >= 0 && nindex <= 8) {
     
					// 由于n[]中的索引是和位置一一对应的,再将其值作为imageViews的索引
					// 只要算出nindex,就能对应上相应的图片。将其交换
					// ncopy拓展了n,将m作为第九个元素加入数组,否则就访问不到最后一张图片。
					myevent1.swapimg(imageViews[m], imageViews[ncopy[nindex]]);
					// 图片交换后,将数组值也交换,否则多次操作之后数据会混乱,导致胡乱交换。
					int temp2 = ncopy[mindex];
					ncopy[mindex] = ncopy[nindex];
					ncopy[nindex] = temp2;
				}
				break;
			case UP:
				nrow = mrow - 1;
				ncol = mcol;
				nindex = nrow * 3 + ncol;
				if (nindex >= 0 && nindex <= 8) {
     
					myevent1.swapimg(imageViews[m], imageViews[ncopy[nindex]]);
					// 图片交换后,将第一行中的值也交换,否则多次操作之后数据会混乱,导致胡乱交换。第二行不变(代表地址)。
					int temp2 = ncopy[mindex];
					ncopy[mindex] = ncopy[nindex];
					ncopy[nindex] = temp2;
				}
				break;
			case LEFT:
				nrow = mrow;
				ncol = mcol - 1;
				nindex = nrow * 3 + ncol;
				if (nindex >= 0 && nindex <= 8 && nindex != 2 && nindex != 5) {
     
					myevent1.swapimg(imageViews[m], imageViews[ncopy[nindex]]);
					// 图片交换后,将第一行中的值也交换,否则多次操作之后数据会混乱,导致胡乱交换。第二行不变(代表地址)。
					int temp2 = ncopy[mindex];
					ncopy[mindex] = ncopy[nindex];
					ncopy[nindex] = temp2;
				}
				break;
			case RIGHT:
				nrow = mrow;
				ncol = mcol + 1;
				nindex = nrow * 3 + ncol;
				if (nindex >= 0 && nindex <= 8 && nindex != 3 && nindex != 6) {
     
					myevent1.swapimg(imageViews[m], imageViews[ncopy[nindex]]);
					// 图片交换后,将第一行中的值也交换,否则多次操作之后数据会混乱,导致胡乱交换。第二行不变(代表地址)。
					int temp2 = ncopy[mindex];
					ncopy[mindex] = ncopy[nindex];
					ncopy[nindex] = temp2;
				}
				break;
			default:
				break;
			}
		}
	}

	// 判断是否拼图成功
	public boolean issucc(ImageView[] imageViews) {
     
		for (int i = 0; i <= 8; ++i) {
     
			if (i != 3 * GridPane.getRowIndex(imageViews[i]) + GridPane.getColumnIndex(imageViews[i])) {
     
				return false;
			}
		}
		return true;
	}

	// 找出m
	public int findnum(int[] n) {
     
		for (int j = 0; j <= 8; ++j) {
     
			if ((j == n[0]) || (j == n[1]) || (j == n[2]) || (j == n[3]) || (j == n[4]) || (j == n[5]) || (j == n[6])
					|| (j == n[7])) {
     
			} else {
     
				return j;
			}
		}
		return -1;
	}
}

  • Start:
package application;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;

/**
 * 开始菜单主界面 作者:Mr. Yu 2018年12月5日,下午5:40:51
 */
public class Start extends Application {
     

	@Override
	public void start(Stage primaryStage) {
     
		try {
     
			// 父容器
			VBox vBox = new VBox();

			// 子容器,分别是按钮和输出文本
			HBox hBox_1 = new HBox();
			HBox hBox_2 = new HBox();

			// 显示难度选择按钮
			Button start_1 = new Button("简单");
			start_1.setOnAction(e -> {
     
				Game_4 game_4 = new Game_4();
				game_4.start(new Stage());
//				Game_1 game_1 = new Game_1();
//				game_1.start(new Stage());
				// 关闭开始界面
				primaryStage.close();
			});

			Button start_2 = new Button("困难");
			start_2.setOnAction(e -> {
     
				// 打开另一个窗口,即游戏窗口
				Game_2 game_2 = new Game_2();
				game_2.start(new Stage());
				// 关闭开始界面
				primaryStage.close();
			});

			Button start_3 = new Button("地狱");
			start_3.setOnAction(e -> {
     
				// 打开另一个窗口,即游戏窗口
				Game_3 game_3 = new Game_3();
				game_3.start(new Stage());
				// 关闭开始界面
				primaryStage.close();
			});

			Label label = new Label("@余氏出品,必属渣品");
			// 设置文本样式大小和颜色
			label.setFont(new Font("Arial", 30));
			label.setTextFill(Color.web("black"));

			hBox_1.getChildren().addAll(start_1, start_2, start_3);
			hBox_2.getChildren().add(label);
			vBox.getChildren().addAll(hBox_1, hBox_2);

			// 设置按钮大小并调节位置
			start_1.setPrefSize(100, 60);
			start_2.setPrefSize(100, 60);
			start_3.setPrefSize(100, 60);
			hBox_1.setPadding(new Insets(650, 100, 0, 175));
			hBox_2.setPadding(new Insets(50, 50, 500, 260));
			// 设置控件之间的距离
			hBox_1.setSpacing(100);

			// 标题栏图标
			// Image image = new Image("application/4.jpg");

			Scene scene = new Scene(vBox, 800, 800);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			// primaryStage.getIcons().add(image);
			primaryStage.setTitle("智障拼图");
			primaryStage.setScene(scene);
			primaryStage.setResizable(false);
			primaryStage.show();
		} catch (Exception e) {
     
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
     
		launch(args);
	}
}

你可能感兴趣的:(Java,Java,JavaFx)