/**
* Copyright 2010 Philippe Beaudoin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.puzzlebazar.shared.puzzle.heyawake.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import com.googlecode.objectify.annotation.Serialized;
import com.puzzlebazar.shared.InvalidObjectException;
import com.puzzlebazar.shared.ObjectAlreadyInitializedException;
import com.puzzlebazar.shared.puzzle.model.PuzzleImpl;
import com.puzzlebazar.shared.puzzle.squaregrid.model.CellArray;
import com.puzzlebazar.shared.util.Has2DSize;
import com.puzzlebazar.shared.util.KeyNotFoundException;
import com.puzzlebazar.shared.util.PuzzleMessage;
import com.puzzlebazar.shared.util.Recti;
import com.puzzlebazar.shared.util.Vec2i;
/**
* @author Philippe Beaudoin
*/
public class HeyawakePuzzle extends PuzzleImpl<HeyawakePuzzle, HeyawakePuzzleDetails>
implements Has2DSize, CellArray<HeyawakeCellState>, Serializable {
private static final long serialVersionUID = -7747407683017419004L;
private static final HeyawakeCellState defaultState = new HeyawakeCellState(HeyawakeCellState.UNKNOWN);
@Serialized private ArrayList<HeyawakeRoom> rooms;
@Serialized private HeyawakeCellState[][] stateArray;
@Override
public void attachToPuzzleDetails(HeyawakePuzzleDetails puzzleDetails)
throws InvalidObjectException, ObjectAlreadyInitializedException {
super.attachToPuzzleDetails(puzzleDetails);
if (getWidth() <= 0 || getHeight() <= 0) {
throw new InvalidObjectException("HeyawakePuzzleDetails has an invalid width or height.");
}
if (rooms == null) {
rooms = new ArrayList<HeyawakeRoom>();
}
if (stateArray == null) {
stateArray = new HeyawakeCellState[getWidth()][getHeight()];
} else if (stateArray.length != getWidth() ||
stateArray[0].length != getWidth()) {
throw new InvalidObjectException("Width or height of HeyawakePuzzle doesn't match that of HeyawakePuzzleDetails");
}
}
@Override
public int getHeight() {
if (puzzleDetails == null) {
return UNKNOWN_SIZE;
}
return puzzleDetails.getHeight();
}
@Override
public int getWidth() {
if (puzzleDetails == null) {
return UNKNOWN_SIZE;
}
return puzzleDetails.getWidth();
}
@Override
public void clearCellStates() {
for (int i = 0; i < getWidth(); ++i) {
for (int j = 0; j < getWidth(); ++j) {
stateArray[i][j] = null;
}
}
}
@Override
public HeyawakeCellState getCellState(Vec2i loc) {
HeyawakeCellState result = stateArray[loc.x][loc.y];
if (result == null) {
return defaultState;
}
return result;
}
@Override
public void setCellState(Vec2i loc, HeyawakeCellState state) {
stateArray[loc.x][loc.y] = state;
}
/**
* Ensures that no room overlap within the heyawake puzzle. This
* does not verify that the current state is legal, for this
* call {@link #checkSolved()}.
*
* @return {@code true} if the puzzle integrity is verified, that is, no room overlap. {@code false} otherwise.
*/
public boolean checkIntegrity() {
int roomsPerCell[][] = countRoomsPerCell();
for (int x = 0; x < getWidth(); ++x) {
for (int y = 0; y < getHeight(); ++y) {
if (roomsPerCell[x][y] > 1) {
return false;
}
}
}
return true;
}
/**
* Ensures that this heyawake puzzle is legally solved.
*
* @return {@code null} if all the cells of the desired type form a
* connected group, otherwise return a {@link java.util.List} of cell
* locations that form a disconnected group.
*/
public PuzzleMessage checkSolved() {
PuzzleMessage result = null;
int roomsPerCell[][] = countRoomsPerCell();
for (int x = 0; x < getWidth(); ++x) {
for (int y = 0; y < getHeight(); ++y) {
if (roomsPerCell[x][y] != 1) {
if (result == null) {
result = new PuzzleMessage(true, "Some cells are covered by more than one room.");
}
result.addErrorLocation(new Vec2i(x,y));
}
}
}
if (result != null) {
return result;
}
return new PuzzleMessage(false);
}
/**
* Ensures that the {@link HeyawakeRoom} can be added to the puzzle.
* This is only possible if the room does not overlap any existing
* room.
*
* @param room The {@link HeyawakeRoom} to add to the puzzle.
* @return {@code true} if the room can be added, {@code false} otherwise.
*/
public boolean canAddRoom(HeyawakeRoom room) {
Recti rect = room.getRoomRect();
for (HeyawakeRoom actualRoom : rooms) {
if (actualRoom.getRoomRect().cellsOverlap(rect)) {
return false;
}
}
return true;
}
/**
* Adds a new room to the heyawake puzzle.
* This version does not ensure that the move is valid,
* for validation call {@link #canAddRoom(HeyawakeRoom)}
* prior to calling this.
*
* @param room Information on ths {@link HeyawakeRoom} to add.
*/
public void addRoom(HeyawakeRoom room) {
rooms.add(room);
}
/**
* Deletes a room from the heyawake puzzle.
*
* @param cell00 The location of the top-left cell of the room to remove, a {@link Vec2i}.
* @throws KeyNotFoundException Thrown if the location was not found and the room was not removed.
*/
public void deleteRoom(Vec2i cell00) throws KeyNotFoundException {
int indexToRemove = 0;
for (HeyawakeRoom room : rooms) {
if (room.getRoomRect().getCell00().equals(cell00)) {
break;
}
indexToRemove++;
}
if (indexToRemove == rooms.size()) {
throw new KeyNotFoundException("The room at postion " + cell00 + " was not found.");
}
rooms.remove(indexToRemove);
}
/**
* Counts the number of rooms on each cell. The integrity of a puzzle is verified if this
* count is 0 or 1 for every cell. The puzzle is complete if this count is 1 for every cell.
*
* @return An array of integer of size {@link #getWidth} times {@link #getHeight()} containing
* the number of rooms overlapping each cell.
*/
private int[][] countRoomsPerCell() {
int[][] result = new int[getWidth()][getHeight()];
for (int x = 0; x < getWidth(); ++x) {
Arrays.fill(result[x], 0);
}
for (HeyawakeRoom room : rooms) {
Recti rect = room.getRoomRect();
for (int x = rect.x; x < rect.x + rect.w; ++x) {
for (int y = rect.y; y < rect.y + rect.h; ++y) {
result[x][y]++;
}
}
}
return result;
}
}