Make writing a habit together! This is the 7th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

preface

If you ask yourself, what can a desktop icon do other than double-click to launch an application?

When the game is played, of course, which is why you see the picture below.

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

Note that this address of the server is the previous several chapters using assembly implementation, would like to see if it can be simply supported in reality, if not access, please leave a message.

Here’s how to do it.

Realize the principle of

This article needs to understand the majority of things, see a look at the line, if you need to understand in detail, you need to understand Windows development, this article will not detail each knowledge point.

There are two problems that Java cannot implement, namely registration hotkeys and mobile desktop ICONS, so this part is implemented in C. Here are some JNI interfaces defined.

public class GameJNI {

    public native void registerHotkeyCallback(HotkeyCallback callback);

    public native  int getDesktopIcon(a);

    public native void moveDesktopIcon(int index,int x,int y);
}
Copy the code

If you don’t understand Windows, you probably don’t understand how to move desktop ICONS, but just to be brief, Windows is based on messages that tell you what’s going on in a window, so for example, when you register a hotkey, and the hotkey is pressed, it gets a response in Windows window functions, The window function will get the identifier of the specific message, the identifier of the hotkey is WM_HOTKEY, and other parameters such as wParam and lParam passed through the system together.

To move an icon is to send 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); }}}Copy the code

This part of the code is the most. First, register 4 hotkeys through RegisterHotKey function, namely ←↑→↓, and give them an ID of 1, 2, 3 and 4 respectively. Then continuously obtain messages from the message queue through GetMessage, which is usually used in the form application. For him, there is no window, so the second parameter is NULL, and the first parameter is the message message that arrives in the queue. When the message is WM_HOTKEY, it means that we press the defined hotkey and get the callback address through the interaction API provided by Java.

If LVM_GETITEMCOUNT is sent to the ListView, it means that someone wants to get the number of LVM_GETITEMCOUNT ICONS. It will then return it to us, and Windows provides a function called SendMessage, which sends a message to a specified window and has a return value, and a function called PostMessage, which has no return value, as described below.

int GetDesktopIconCount(a) {
	return  SendMessage(GetWindowHwnd(), LVM_GETITEMCOUNT, 0.0);
}
JNIEXPORT jint JNICALL Java_com_h_game_jni_GameJNI_getDesktopIcon
(JNIEnv *env, jobject thisObj) {
	return GetDesktopIconCount();
}
Copy the code

Win11, Win7, Win10 is not the same, if Win11 did not change the structure, this code will work properly.

int GetWindowHwnd(a) {
	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;
}

Copy the code

A handle is an integer that identifies a unique application window. You can find a handle based on the class name or the window title name, and there are child and parent relationships. See CreateWindow for more details.

When the ListView receives LVM_SETITEMPOSITION, it sets the position based on other parameters. PostMessage is used because we don’t need to return any value. SendMessage is 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);
}


Copy the code

Well, here’s the Java code implementation.

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

    public static void main(String[] args) {
        newSnakeGame(); }}Copy the code

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

Note here, we took the icon index 0 as head, index last as food, such as 10 ICONS, 0 is head, 9 is food, 1-8 is the body, every time according to the direction of head movement, to record the location before moving, pledge after moving, passed to the position of the he recorded the first icon, and in their original position of index 1 to 2, And so on, you get greedy snakes.

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();
    privateScheduledFuture<? > scheduledFuture =null;

    public SnakeGame(a) {
        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(a) {
        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(a) {
        if (snakeBodyPoint.stream().anyMatch(point -> point.equals(snakePoint))) {
            scheduledFuture.cancel(true);
            scheduledFuture = null;
            this.setVisible(true); }}private void moveSnakeHeader(a) {
        gameJNI.moveDesktopIcon(0, snakePoint.x, snakePoint.y);
        if (foodPoint.equals(snakePoint)) resetBodyLocation();
    }

    private List<Point> generatorPoints(a) {
        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(newPoint(i, j)); }}return all;
    }

    private void resetBodyLocation(a) { 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(a) {
        this.setLayout(new BorderLayout());
        String str ="< HTML > First right click on the desktop and view > unarrange the pictures automatically and align the grid with the pictures. Arrow keys ← ↑ → ↓
      ";
        JLabel jLabel = new JLabel(str);
        jLabel.setFont(new Font("Black".0.18));
        add(jLabel, BorderLayout.NORTH);
        add(createButton(), BorderLayout.SOUTH);
        registerHotkey();
        reset();
    }

    private void reset(a) {
        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(a) {


        JButton jButton = new JButton("Start");
        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(a) {
        new Thread(() -> gameJNI.registerHotkeyCallback(this)).start();
    }

    enum Direction {
        LEFT, UP, RIGHT, DOWN
    }
}
Copy the code