What tricks can Java implement to achieve greedy snake? Experience the desktop icon version

Get into the habit of writing together! This is the 7th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

foreword

If I ask you, what else can a desktop icon do other than double-click to start a program?

Of course when the game is played, so you will see this picture below.

875na-3i19d.gif

Download address: houxinlin.com:8080/Game.rar

Note that the server at this address is implemented using assembly in the previous chapters. I want to see if it can be easily supported in reality. If it is not accessible, please leave a message.

Here's how to do it.

Implementation principle

This article needs to know most of the things, just look at the official. If you want to know more, you need to understand Windows development first. This article will not describe each knowledge point in detail.

There are two problems that java can't implement, that is, registering hotkeys and moving desktop icons, so this part is implemented with c, and the following are some of the defined jni interfaces.

public class GameJNI {

    public native void registerHotkeyCallback(HotkeyCallback callback);

    public native  int getDesktopIcon();

    public native void moveDesktopIcon(int index,int x,int y);
}
复制代码

If you don’t understand the Windows mechanism, you may not be able to understand how to move the desktop icons. In short, Windows is based on the message mechanism. The message tells a window what happened. For example, when you register a hotkey, the hotkey is called Press it, then you will get a response in the window function of Windows, the window function will get the identifier of the specific message, the identifier of the hot key is WM_HOTKEY, and there are other parameters passed by the system together, such as wParam, lParam.

Moving an icon, on the other hand, sends a message to the window where the icon is located, indicating that someone wants to reposition an icon.

First look at the implementation of registerHotkeyCallback.


void RegisterHotkey(int id,int vkCode) {
	if (RegisterHotKey(NULL, id, 0, vkCode) == 0) {
            printf("fail %d\n", vkCode);
	}
}
JNIEXPORT void JNICALL Java_com_h_game_jni_GameJNI_registerHotkeyCallback
(JNIEnv *env, jobject thisObj, jobject callbackObject) {

	RegisterHotkey(LEFT_ID,37);
	RegisterHotkey(UP_ID,38);
	RegisterHotkey(RIGHT_ID,39);
	RegisterHotkey(DOWN_ID,40);
	fflush(stdout);

	MSG lpMsg = {0};
	while (GetMessage(&lpMsg, NULL, 0, 0)!=0){
		jclass thisClass = (*env)->GetObjectClass(env, callbackObject);
		jmethodID hotKeyCallbackMethodId = (*env)->GetMethodID(env, thisClass, "hotkey", "(I)V");
		if (NULL == hotKeyCallbackMethodId) return;
		if (lpMsg.message== WM_HOTKEY){
			jstring result = (jstring)(*env)->CallObjectMethod(env, callbackObject, hotKeyCallbackMethodId, lpMsg.wParam);
		}
	
	}

}
复制代码

This part of the code is the most. First, register 4 hotkeys through the RegisterHotKey function, namely ←↑→↓, and give them an id of 1, 2, 3, and 4, and then continuously obtain messages from the message queue through GetMessage, GetMessage is usually used in form applications, and at this time, there is no form for him, so the second parameter is NULL, and the first parameter is the message information arriving in the queue. When the message is WM_HOTKEY, it means We press the defined hotkey, get the callback address through the interactive API provided by java, and make a call.

Looking at the implementation of getDesktopIcon, which is used to get the number of desktop icons, the desktop can also be used as a window, and the list of icons on the desktop is a ListView. When the ListView receives the LVM_GETITEMCOUNT message, it means that someone wants to get the number of his icons. , and then he will return to us, and Windows provides the SendMessage function, which is used to send a message to the specified window, and has a return value, and a function PostMessage for no return value, which will be described below.

int GetDesktopIconCount() {
	return  SendMessage(GetWindowHwnd(), LVM_GETITEMCOUNT, 0, 0);
}
JNIEXPORT jint JNICALL Java_com_h_game_jni_GameJNI_getDesktopIcon
(JNIEnv *env, jobject thisObj) {
	return GetDesktopIconCount();
}
复制代码

In order to get the handle of the ListView in the desktop, you need to do this, but I am not sure whether this code can run normally on Win11, because the desktop structure in each system may be different, Win7 and Win10 are different, if Win11 Without changing this structure, this code will run fine.

int GetWindowHwnd() {
	HWND hwndWorkerW = { 0 };
	hwndWorkerW = FindWindow(NULL,TEXT("Program Manager"));
	hwndWorkerW = FindWindowEx(hwndWorkerW, 0, TEXT("SHELLDLL_DefView"), NULL);
	hwndWorkerW = FindWindowEx(hwndWorkerW, 0,TEXT("SysListView32"), TEXT("FolderView"));
	return hwndWorkerW;
}

复制代码

There are a lot of knowledge points involved here, handle and search handle. The handle is an integer used to identify a unique application window. The search handle can be searched according to the class name or window title name, and this will include the child-parent relationship. For details, you need to understand the CreateWindow function.

The following is the implementation of the mobile icon. When the ListView receives the LVM_SETITEMPOSITION message, it will set the position according to other parameters. Here, PostMessage is used to deliver the message because we do not need a return value. If SendMessage is used, it will be much slower.

void MoveDesktopIcon(int iconIndex,int x,int y) {
	PostMessage(GetWindowHwnd(), LVM_SETITEMPOSITION, iconIndex, ConversionXY(x, y));
}
int ConversionXY(int  x, int  y) {
	return  y * 65536 + x;
}
JNIEXPORT void JNICALL Java_com_h_game_jni_GameJNI_moveDesktopIcon
(JNIEnv *env, jobject thisObj, jint index, jint x, jint y) {
	MoveDesktopIcon(index, x, y);
}


复制代码

Well, the following is the java code implementation.

public class Main {
    static {
        System.load("Game.dll");
    }

    public static void main(String[] args) {
        new SnakeGame();
    }
}
复制代码

This part is relatively simple, there is nothing to say, the basic operation of the icon has been provided by c, and it can be called directly here.

Note that here we take the icon index 0 as the head, and the last index as the food, for example, there are 10 icons, 0 is the head, 9 is the food, 1-8 is the body, whenever you move the head according to the direction, first record the movement before position, when the head moves, pass his recorded position to the first icon, and then pass the original position of index 1 to the second one, and so on, forming a greedy snake.

public class SnakeGame extends JFrame implements HotkeyCallback {
    private static final int WINDOW_WIDTH = 300;
    private static final int WINDOW_HEIGHT = 200;
    private GameJNI gameJNI = new GameJNI();
    private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
    private Direction direction = Direction.RIGHT;
    private Point snakePoint = new Point(0, 0);
    private static final int MOVE_SIZE = 84;
    private List<Point> snakeBodyPoint = new ArrayList<>();
    private Point foodPoint = new Point(0, 0);
    private List<Point> allPoint = generatorPoints();
    private ScheduledFuture<?> scheduledFuture = null;

    public SnakeGame() {
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        this.setLocation(screenSize.width / 2 - WINDOW_WIDTH / 2, screenSize.height / 2 - WINDOW_HEIGHT / 2);
        this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        init();
        this.setVisible(true);
    }

    private void startGame() {
        this.setVisible(false);
        if (scheduledFuture == null) {
            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
                Point oldPoint = snakePoint.getLocation();
                if (direction == Direction.LEFT) snakePoint.x -= MOVE_SIZE;
                if (direction == Direction.UP) snakePoint.y -= MOVE_SIZE;
                if (direction == Direction.RIGHT) snakePoint.x += MOVE_SIZE;
                if (direction == Direction.DOWN) snakePoint.y += MOVE_SIZE;
                moveSnakeHeader();
                moveSnakeBody(oldPoint);
                isCollision();
            }, 0, 200, TimeUnit.MILLISECONDS);
        }

    }


    private void isCollision() {
        if (snakeBodyPoint.stream().anyMatch(point -> point.equals(snakePoint))) {
            scheduledFuture.cancel(true);
            scheduledFuture = null;
            this.setVisible(true);
        }
    }

    private void moveSnakeHeader() {
        gameJNI.moveDesktopIcon(0, snakePoint.x, snakePoint.y);
        if (foodPoint.equals(snakePoint)) resetBodyLocation();
    }

    private List<Point> generatorPoints() {
        List<Point> all = new ArrayList<>();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        for (int i = 0; i < screenSize.width; i += MOVE_SIZE) {
            for (int j = 0; j < screenSize.height; j += MOVE_SIZE) {
                all.add(new Point(i, j));
            }
        }
        return all;
    }

    private void resetBodyLocation() {
        List<Point> newPoint = allPoint.stream().filter(point -> !has(point)).collect(Collectors.toList());
        Collections.shuffle(newPoint);
        Point point = newPoint.get(0);
        int desktopIcon = gameJNI.getDesktopIcon();
        foodPoint.setLocation(point.x, point.y);
        gameJNI.moveDesktopIcon(desktopIcon - 1, point.x, point.y);
    }

    private boolean has(Point hasPoint) {
        return snakeBodyPoint.stream().anyMatch(point -> hasPoint.equals(point));
    }

    private void moveSnakeBody(Point oldPoint) {
        for (int i = 1; i < snakeBodyPoint.size() - 1; i++) {
            Point itemPoint = snakeBodyPoint.get(i);
            gameJNI.moveDesktopIcon(i, oldPoint.x, oldPoint.y);
            snakeBodyPoint.set(i, oldPoint.getLocation());
            oldPoint = itemPoint;
        }
    }


    private void init() {
        this.setLayout(new BorderLayout());
        String str ="<html>首先右击桌面,查看>取消自动排列图片、将网格与图片对齐。方向键为← ↑ → ↓</html>";
        JLabel jLabel = new JLabel(str);
        jLabel.setFont(new Font("黑体",0,18));
        add(jLabel, BorderLayout.NORTH);
        add(createButton(), BorderLayout.SOUTH);
        registerHotkey();
        reset();
    }

    private void reset() {
        snakeBodyPoint.clear();
        direction = Direction.RIGHT;
        snakePoint.setLocation(0, 0);
        int desktopIcon = gameJNI.getDesktopIcon();
        int offsetX = -MOVE_SIZE;
        for (int i = 0; i < desktopIcon; i++) {
            snakeBodyPoint.add(new Point(offsetX, 0));
            gameJNI.moveDesktopIcon(i, offsetX, 0);
            offsetX -= MOVE_SIZE;
        }
        resetBodyLocation();
    }

    private JButton createButton() {


        JButton jButton = new JButton("开始");
        jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                reset();
                startGame();
            }
        });
        return jButton;
    }

    @Override
    public void hotkey(int key) {
        if (key == 1) direction = Direction.LEFT;
        if (key == 2) direction = Direction.UP;
        if (key == 3) direction = Direction.RIGHT;
        if (key == 4) direction = Direction.DOWN;

    }

    public void registerHotkey() {
        new Thread(() -> gameJNI.registerHotkeyCallback(this)).start();
    }

    enum Direction {
        LEFT, UP, RIGHT, DOWN
    }
}
复制代码

Guess you like

Origin juejin.im/post/7084865137496031263