aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java
blob: 9e9d20f698539b391dd69199dd245188bb0bb67b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package de.hysky.skyblocker.skyblock.dungeon.puzzle;

import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.tictactoe.BoardIndex;
import de.hysky.skyblocker.utils.tictactoe.TicTacToeUtils;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.decoration.ItemFrameEntity;
import net.minecraft.item.map.MapState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Thanks to Danker for a reference implementation!
 */
public class TicTacToe extends DungeonPuzzle {
	private static final Logger LOGGER = LoggerFactory.getLogger(TicTacToe.class);
	private static final float[] RED_COLOR_COMPONENTS = { 1.0F, 0.0F, 0.0F };
	private static final float[] GREEN_COLOR_COMPONENTS = { 0.0F, 1.0F, 0.0F };
	@SuppressWarnings("unused")
	private static final TicTacToe INSTANCE = new TicTacToe();
	private static Box nextBestMoveToMake = null;

	private TicTacToe() {
		super("tic-tac-toe", "tic-tac-toe-1");
	}

	public static void init() {
	}

	@Override
	public void tick(MinecraftClient client) {
		if (!shouldSolve()) {
			return;
		}

		nextBestMoveToMake = null;

		if (client.world == null || client.player == null || !Utils.isInDungeons()) return;

		//Search within 21 blocks for item frames that contain maps
		Box searchBox = new Box(client.player.getX() - 21, client.player.getY() - 21, client.player.getZ() - 21, client.player.getX() + 21, client.player.getY() + 21, client.player.getZ() + 21);
		List<ItemFrameEntity> itemFramesThatHoldMaps = client.world.getEntitiesByClass(ItemFrameEntity.class, searchBox, ItemFrameEntity::containsMap);

		try {
			//Only attempt to solve if the puzzle wasn't just completed and if its the player's turn
			//The low bit will always be set to 1 on odd numbers
			if (itemFramesThatHoldMaps.size() != 9 && (itemFramesThatHoldMaps.size() & 1) == 1) {
				char[][] board = new char[3][3];

				for (ItemFrameEntity itemFrame : itemFramesThatHoldMaps) {
					MapState mapState = client.world.getMapState(itemFrame.getMapId(itemFrame.getHeldItemStack()));

					if (mapState == null) continue;

					//Surely if we pass shouldSolve then the room should be matched right
					BlockPos relative = DungeonManager.getCurrentRoom().actualToRelative(itemFrame.getBlockPos());

					//Determine the row -- 72 = top, 71 = middle, 70 = bottom
					int y = relative.getY();
					int row = switch (y) {
						case 72 -> 0;
						case 71 -> 1;
						case 70 -> 2;

						default -> -1;
					};

					//Determine the column - 17 = first, 16 = second, 15 = third
					int z = relative.getZ();
					int column = switch (z) {
						case 17 -> 0;
						case 16 -> 1;
						case 15 -> 2;

						default -> -1;
					};

					if (row == -1 || column == -1) continue;

					//Get the color of the middle pixel of the map which determines whether its X or O
					int middleColor = mapState.colors[8256] & 0xFF;

					if (middleColor == 114) {
						board[row][column] = 'X';
					} else if (middleColor == 33) {
						board[row][column] = 'O';
					}
				}

				BoardIndex bestMove = TicTacToeUtils.getBestMove(board);

				double nextX = 8;
				double nextY = 72 - bestMove.row();
				double nextZ = 17 - bestMove.column();

				BlockPos nextPos = DungeonManager.getCurrentRoom().relativeToActual(BlockPos.ofFloored(nextX, nextY, nextZ));
				nextBestMoveToMake = RenderHelper.getBlockBoundingBox(client.world, nextPos);
			}
		} catch (Exception e) {
			LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while determining a tic tac toe solution!", e);
		}
	}

	@Override
	public void render(WorldRenderContext context) {
		try {
			if (SkyblockerConfigManager.get().dungeons.puzzleSolvers.solveTicTacToe && nextBestMoveToMake != null) {
				RenderHelper.renderFilled(context, nextBestMoveToMake, GREEN_COLOR_COMPONENTS, 0.5f, false);
			}
		} catch (Exception e) {
			LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while rendering the tic tac toe solution!", e);
		}
	}
}