forked from SteamWar/SteamWar
Add initial MultiTileArea
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.bausystem.region.dynamic;
|
||||||
|
|
||||||
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
|
import de.steamwar.bausystem.region.Point;
|
||||||
|
import de.steamwar.bausystem.region.Region;
|
||||||
|
import de.steamwar.bausystem.shared.Pair;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public abstract class MultiTileArea implements Region.Area {
|
||||||
|
|
||||||
|
private final Set<Tile> tiles = new HashSet<>();
|
||||||
|
|
||||||
|
public boolean addTile(Tile tile) {
|
||||||
|
boolean result = tiles.add(tile);
|
||||||
|
quantize();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeTile(Tile tile) {
|
||||||
|
boolean result = tiles.remove(tile);
|
||||||
|
quantize();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quantize() {
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
Quantizer quantizer = new Quantizer(tiles);
|
||||||
|
List<Pair<Tile, Tile>> results = new ArrayList<>();
|
||||||
|
while (true) {
|
||||||
|
Pair<Tile, Tile> pair = quantizer.next();
|
||||||
|
if (pair == null) break;
|
||||||
|
results.add(pair);
|
||||||
|
}
|
||||||
|
// [{"min_tile_x":0,"min_tile_z":0,"max_tile_x":0,"max_tile_z":0}]
|
||||||
|
time = System.currentTimeMillis() - time;
|
||||||
|
System.out.println(tiles.size() + ": " + results.size() + " in " + time + "ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Point getMinPoint(boolean extension) {
|
||||||
|
return Point.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Point getMaxPoint(boolean extension) {
|
||||||
|
return Point.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Point getCopyPoint() {
|
||||||
|
return Point.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRegion(Location location, boolean extension) {
|
||||||
|
return inRegion(location.getBlockX(), location.getBlockZ(), extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean inRegion(int x, int z, boolean extension) {
|
||||||
|
Tile tile = Tile.fromXZ(x, z).orElse(null);
|
||||||
|
return tiles.contains(tile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Clipboard copy(boolean extension) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChunkOutside(int chunkX, int chunkZ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Quantizer {
|
||||||
|
|
||||||
|
private Map<Tile, Integer> tileToNeighbourCount = new HashMap<>();
|
||||||
|
|
||||||
|
public Quantizer(Set<Tile> tiles) {
|
||||||
|
for (Tile tile : tiles) {
|
||||||
|
int count = (int) tile.neighboursRing()
|
||||||
|
.filter(tiles::contains)
|
||||||
|
.count();
|
||||||
|
tileToNeighbourCount.put(tile, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Tile, Tile> next() {
|
||||||
|
Map.Entry<Tile, Integer> entry = tileToNeighbourCount.entrySet()
|
||||||
|
.stream()
|
||||||
|
.max(Map.Entry.comparingByValue())
|
||||||
|
.orElse(null);
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
Tile minTile = entry.getKey();
|
||||||
|
Tile maxTile = entry.getKey();
|
||||||
|
tileToNeighbourCount.remove(entry.getKey());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Pair<Set<Tile>, Integer> neg_x = tryExpand(minTile, maxTile, -1, 0);
|
||||||
|
Pair<Set<Tile>, Integer> pos_x = tryExpand(minTile, maxTile, 1, 0);
|
||||||
|
Pair<Set<Tile>, Integer> neg_z = tryExpand(minTile, maxTile, 0, -1);
|
||||||
|
Pair<Set<Tile>, Integer> pos_z = tryExpand(minTile, maxTile, 0, 1);
|
||||||
|
|
||||||
|
if (neg_x.getValue() == 0 && pos_x.getValue() == 0 && neg_z.getValue() == 0 && pos_z.getValue() == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Tile> remove;
|
||||||
|
if (neg_x.getValue() >= pos_x.getValue() && neg_x.getValue() >= neg_z.getValue() && neg_x.getValue() >= pos_z.getValue()) {
|
||||||
|
minTile = minTile.add(-1, 0).orElseThrow();
|
||||||
|
remove = neg_x.getKey();
|
||||||
|
} else if (pos_x.getValue() >= neg_x.getValue() && pos_x.getValue() >= neg_z.getValue() && pos_x.getValue() >= pos_z.getValue()) {
|
||||||
|
maxTile = maxTile.add(1, 0).orElseThrow();
|
||||||
|
remove = pos_x.getKey();
|
||||||
|
} else if (neg_z.getValue() >= neg_x.getValue() && neg_z.getValue() >= pos_x.getValue() && neg_z.getValue() >= pos_z.getValue()) {
|
||||||
|
minTile = minTile.add(0, -1).orElseThrow();
|
||||||
|
remove = neg_z.getKey();
|
||||||
|
} else {
|
||||||
|
maxTile = maxTile.add(0, 1).orElseThrow();
|
||||||
|
remove = pos_z.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove.forEach(tileToNeighbourCount::remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pair<>(minTile, minTile.equals(maxTile) ? null : maxTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pair<Set<Tile>, Integer> EMPTY = new Pair<>(Collections.emptySet(), 0);
|
||||||
|
|
||||||
|
private Pair<Set<Tile>, Integer> tryExpand(Tile minTile, Tile maxTile, int dx, int dz) {
|
||||||
|
Set<Tile> expaned = new HashSet<>();
|
||||||
|
if (dx == 0) {
|
||||||
|
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
|
||||||
|
int z = (dz < 0 ? minTile.getTileZ() : maxTile.getTileZ()) + dz;
|
||||||
|
Tile tile = Tile.fromTile(x, z).orElse(null);
|
||||||
|
if (tile == null) return EMPTY;
|
||||||
|
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
|
||||||
|
expaned.add(tile);
|
||||||
|
}
|
||||||
|
} else if (dz == 0) {
|
||||||
|
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
|
||||||
|
int x = (dx < 0 ? minTile.getTileX() : maxTile.getTileX()) + dx;
|
||||||
|
Tile tile = Tile.fromTile(x, z).orElse(null);
|
||||||
|
if (tile == null) return EMPTY;
|
||||||
|
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
|
||||||
|
expaned.add(tile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = expaned.stream()
|
||||||
|
.mapToInt(tileToNeighbourCount::get)
|
||||||
|
.sum();
|
||||||
|
return new Pair<>(expaned, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user