package me.desht.chesscraft.chess.pieces;
import chesspresso.Chess;
import me.desht.chesscraft.chess.BoardStyle;
import me.desht.chesscraft.chess.BoardView;
import me.desht.chesscraft.exceptions.ChessException;
import me.desht.dhutils.Debugger;
import me.desht.dhutils.block.MaterialWithData;
import me.desht.dhutils.cuboid.Cuboid;
import me.desht.dhutils.cuboid.Cuboid.CuboidDirection;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
public class PieceDesigner {
private final BoardView view;
private final UUID playerId;
private String setName; // name of the set currently being designed
private BlockChessSet chessSet; // the set currently being designed
public PieceDesigner(BoardView view, String setName, UUID playerId) throws ChessException {
if (view.isDesigning()) {
throw new ChessException("This board is already in design mode.");
}
this.view = view;
this.setName = setName;
this.playerId = playerId;
}
public String getSetName() {
return setName;
}
public UUID getPlayerId() {
return playerId;
}
public void setSetName(String setName) {
this.setName = setName;
}
public BlockChessSet getChessSet() {
return chessSet;
}
/**
* Scan the board and initialise a chess set based on the contents of squares A1-E1 & A2-E2.
*
* @throws ChessException if there is any kind of problem initialising the set
*/
public void scan() throws ChessException {
MaterialMap[] materialMaps = new MaterialMap[2];
materialMaps[Chess.WHITE] = new MaterialMap();
ChessPieceTemplate[][] templates = new ChessPieceTemplate[2][];
templates[Chess.WHITE] = new ChessPieceTemplate[Chess.MAX_PIECE + 1];
templates[Chess.BLACK] = null;
World world = view.getA1Square().getWorld();
for (int colour = Chess.WHITE; colour <= Chess.BLACK; colour++) {
int rotation = rotationNeeded(colour);
Debugger.getInstance().debug("Designer: rotate templates by " + rotation + " degrees for colour " + colour + " & board orientation " + view.getRotation());
// reverse mapping of character to material name
Map<String,Character> reverseMap = new HashMap<String, Character>();
char nextChar = 'A';
for (int p = Chess.MIN_PIECE + 1; p <= Chess.MAX_PIECE; p++) {
// get the bounding box for the materials in this square
Cuboid c = getPieceBox(p, colour);
Debugger.getInstance().debug("Designer: scan: piece " + Chess.pieceToChar(p) + ", colour " + colour + " = cuboid: " + c);
ChessPieceTemplate template = createTemplate(c, rotation);
// scan the cuboid and use the contents to populate the new template
for (int x = 0; x < template.getSizeX(); x++) {
for (int y = 0; y < template.getSizeY(); y++) {
for (int z = 0; z < template.getSizeZ(); z++) {
Point rotatedPoint = rotate(x, z, template.getSizeZ(), template.getSizeX(), rotation);
Block b = c.getRelativeBlock(world, rotatedPoint.x, y, rotatedPoint.z);
short data = b.getType() == Material.AIR ? 0 : b.getData();
MaterialWithData mat = MaterialWithData.get(b.getTypeId(), data).rotate(rotation);
String materialName = mat.toString();
if (!reverseMap.containsKey(materialName)) {
// not seen this material yet
reverseMap.put(materialName, nextChar);
materialMaps[colour].put(nextChar, mat);
Debugger.getInstance().debug(2, "Designer: add material mapping: " + nextChar + "->" + materialName);
nextChar = getNextChar(nextChar);
}
template.put(x, y, z, reverseMap.get(materialName));
}
}
}
templates[colour][p] = template;
}
if (colour == Chess.WHITE) {
materialMaps[Chess.BLACK] = initBlackMaterialMap(materialMaps[Chess.WHITE]);
if (materialMaps[Chess.BLACK] != null) {
// no need to scan black pieces - we're using the same templates for white & black
break;
} else {
templates[Chess.BLACK] = new ChessPieceTemplate[Chess.MAX_PIECE + 1];
materialMaps[Chess.BLACK] = new MaterialMap();
}
}
}
chessSet = new BlockChessSet(setName, templates, materialMaps, "Created in ChessCraft piece designer by " + getPlayerId());
}
private char getNextChar(char c) throws ChessException {
if (c == 'Z') {
return 'a';
} else if (c == 'z') {
return '0';
} else if (c == '9') {
throw new ChessException("material limit exceeded (maximum 62 different materials)");
} else {
return ++c;
}
}
private ChessPieceTemplate createTemplate(Cuboid c, int rotation) {
if (rotation == 0 || rotation == 180) {
return new ChessPieceTemplate(c.getSizeX(), c.getSizeY(), c.getSizeZ());
} else if (rotation == 90 || rotation == 270) {
return new ChessPieceTemplate(c.getSizeZ(), c.getSizeY(), c.getSizeX());
} else {
return null;
}
}
private Point rotate(int x, int z, int sizeX, int sizeZ, int rotation) {
switch (rotation % 360) {
case 0:
return new Point(x, z);
case 90:
return new Point(z, sizeZ - x - 1);
case 180:
return new Point(sizeZ - x - 1, sizeX - z - 1);
case 270:
return new Point(sizeX - z - 1, x);
default:
return null;
}
}
/**
* Attempt to initialise the black material map from blocks in squares B2-E2 inclusive.
*
* @param whiteMap the existing white material map
* @return the black material map, or null if no valid mappings found in B2-E2
*/
private MaterialMap initBlackMaterialMap(MaterialMap whiteMap) {
Map<String,Character> reverseMap = new HashMap<String,Character>();
MaterialMap blackMap = new MaterialMap();
for (Entry<Character, MaterialWithData> e : whiteMap.getMap().entrySet()) {
blackMap.put(e.getKey(), e.getValue());
reverseMap.put(e.getValue().toString(), e.getKey());
}
boolean different = false;
// scan just above squares B2-E2 inclusive
// any block found with a different block on top is of interest
for (int col = 1; col < 5; col++) {
Cuboid c = view.getChessBoard().getSquare(1, col).shift(CuboidDirection.Up, 1);
for (Block b : c) {
Block b2 = b.getRelative(BlockFace.UP);
if (b.getType() == b2.getType() && b.getData() == b2.getData()) {
continue;
}
MaterialWithData mat = MaterialWithData.get(b);
if (reverseMap.containsKey(mat.toString())) {
MaterialWithData mat2 = MaterialWithData.get(b2);
Debugger.getInstance().debug("Designer: insert mapping " + mat.toString() + " -> " + reverseMap.get(mat.toString()) + " -> " + mat2.toString());
blackMap.put(reverseMap.get(mat.toString()), mat2);
different = true;
}
}
}
return different ? blackMap : null;
}
/**
* Load the current design's set data onto the board.
*
* @throws ChessException if the set doesn't exist or doesn't fit the board
*/
public void load() throws ChessException {
ChessSet newChessSet = ChessSetFactory.getChessSet(setName);
if (!(newChessSet instanceof BlockChessSet)) {
throw new ChessException("Set '" + newChessSet.getName() + "' is not a block chess set!");
}
BoardStyle boardStyle = view.getChessBoard().getBoardStyle();
// ensure the new chess set actually fits this board
if (newChessSet.getMaxWidth() > boardStyle.getSquareSize() || newChessSet.getMaxHeight() > boardStyle.getHeight()) {
throw new ChessException("Set '" + newChessSet.getName() + "' is too large for this board!");
}
chessSet = (BlockChessSet) newChessSet;
for (int colour = Chess.WHITE; colour <= Chess.BLACK; colour++) {
for (int p = Chess.MIN_PIECE + 1; p <= Chess.MAX_PIECE; p++) {
int sqi = getSqi(p, colour);
BlockChessStone stone = (BlockChessStone) chessSet.getStone(Chess.pieceToStone(p, colour), view.getRotation());
Debugger.getInstance().debug("Designer: load: stone " + stone.getStone() + " " + stone.getWidth() + " x " + stone.getSizeY());
view.getChessBoard().paintChessPiece(Chess.sqiToRow(sqi), Chess.sqiToCol(sqi), stone.getStone());
}
if (!chessSet.differentBlackTemplates()) {
break;
}
}
if (!chessSet.differentBlackTemplates()) {
addMapBlocks(chessSet.getWhiteToBlack());
}
}
private void addMapBlocks(Map<String, String> whiteToBlack) {
Iterator<String> iter = whiteToBlack.keySet().iterator();
for (int col = 1; col < 5; col++) {
Cuboid c = view.getChessBoard().getSquare(1, col).shift(CuboidDirection.Up, 1);
c.expand(CuboidDirection.Up, 1).fill(0, (byte)0);
int n = 0;
for (Block b : c) {
if (!iter.hasNext())
break;
if (n++ % 2 == 1)
continue; // skip alternate squares
String whiteMat = iter.next();
String blackMat = whiteToBlack.get(whiteMat);
MaterialWithData.get(whiteMat).applyToBlock(b);
MaterialWithData.get(blackMat).applyToBlock(b.getRelative(BlockFace.UP));
}
}
}
/**
* Save the current design to a piece style file.
*
* @throws ChessException if the file can't be written
*/
public void save() throws ChessException {
if (chessSet != null) {
chessSet.save(setName);
// force the new set to be re-cached
ChessSetFactory.getChessSet(setName);
}
}
/**
* Clear all pieces in the design squares (A1-E1 & A2-E2)
*/
public void clear() {
for (int row = 0; row < 2; row++) {
for (int col = 0; col < 5; col++) {
view.getChessBoard().paintChessPiece(row, col, Chess.NO_STONE);
}
}
chessSet = null;
}
private int getSqi(int p, int colour) {
switch (Chess.pieceToStone(p, colour)) {
case Chess.WHITE_PAWN:
return Chess.A2;
case Chess.WHITE_KNIGHT:
return Chess.B1;
case Chess.WHITE_BISHOP:
return Chess.C1;
case Chess.WHITE_ROOK:
return Chess.A1;
case Chess.WHITE_QUEEN:
return Chess.D1;
case Chess.WHITE_KING:
return Chess.E1;
case Chess.BLACK_PAWN:
return Chess.A7;
case Chess.BLACK_KNIGHT:
return Chess.B8;
case Chess.BLACK_BISHOP:
return Chess.C8;
case Chess.BLACK_ROOK:
return Chess.A8;
case Chess.BLACK_QUEEN:
return Chess.D8;
case Chess.BLACK_KING:
return Chess.E8;
default:
throw new IllegalArgumentException("Invalid chess piece " + p);
}
}
private Cuboid getPieceBox(int p, int colour) {
int sqi = getSqi(p, colour);
// get the smallest Cuboid which fully contains the piece (with no external air)
return view.getChessBoard().getPieceRegion(Chess.sqiToRow(sqi), Chess.sqiToCol(sqi)).contract();
}
private int rotationNeeded(int colour) {
int rot;
switch (view.getRotation()) {
case NORTH: rot = 0; break;
case EAST: rot = 270; break;
case SOUTH: rot = 180; break;
case WEST: rot = 90; break;
default: rot = 0; break;
}
if (colour == Chess.BLACK){
rot = (rot + 180) % 360;
}
return rot;
}
private class Point {
public final int x, z;
public Point(int x, int z) {
this.x = x; this.z = z;
}
}
}