fi.iki.tsnorri.gonia.logic.Tetromino.java Source code

Java tutorial

Introduction

Here is the source code for fi.iki.tsnorri.gonia.logic.Tetromino.java

Source

/*
 * Copyright (c) 2012, 2015 Tuukka Norri, tsnorri@iki.fi.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 */
package fi.iki.tsnorri.gonia.logic;

import java.awt.Color;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.apache.commons.lang3.StringUtils;
import org.javatuples.Pair;

/**
 * Tetromino.
 *
 * @author tsnorri
 */
public class Tetromino {
    /**
     * Tetromino type.
     */
    public enum Type {
        C, I, S, Z, L, J, O, T1, T2, Y
    };

    private static final float SATURATION = 1.0f;
    private static final float BRIGHTNESS = 0.4f;

    MutableHexPoint[] points;
    MutableHexPoint[] shapePoints;
    // Location along orthogonal axes.
    int w = 0;
    int h = 0;
    String name;
    Color color;

    /**
     * Constructor.
     *
     * @param name The name of the tetromino, e.g. ?T1?.
     * @param color The colour of the tetromino.
     * @param points The points occupied by the tetromino.
     */
    public Tetromino(String name, Color color, HexPoint... points) {
        if (0 == points.length)
            throw new IllegalArgumentException("No points given.");

        this.points = new MutableHexPoint[points.length];
        this.shapePoints = new MutableHexPoint[points.length];
        for (int i = 0; i < points.length; i++) {
            this.points[i] = points[i].mutableClone();
            this.shapePoints[i] = points[i].mutableClone();
        }

        this.name = name;
        this.color = color;
    }

    /**
     * Create a tetromino of the given type.
     *
     * @param type The type.
     * @return Tetromino.
     */
    public static Tetromino tetrominoWithType(Type type) {
        Tetromino retval = null;
        switch (type) {
        case C:
            retval = new Tetromino("C", tetrominoColor(184.0f), new HexPoint(1, -1, 0), new HexPoint(0, -1, 1),
                    new HexPoint(-1, 0, 1), new HexPoint(-1, 1, 0));
            break;
        case I:
            retval = new Tetromino("I", tetrominoColor(220.0f), new HexPoint(-2, 0, 2), new HexPoint(-1, 0, 1),
                    new HexPoint(0, 0, 0), new HexPoint(1, 0, -1));
            break;

        case S:
            retval = new Tetromino("S", tetrominoColor(256.0f), new HexPoint(-1, 0, 1), new HexPoint(0, 0, 0),
                    new HexPoint(0, 1, -1), new HexPoint(1, 1, -2));
            break;

        case Z:
            retval = new Tetromino("Z", tetrominoColor(292.0f), new HexPoint(1, 0, -1), new HexPoint(0, 0, 0),
                    new HexPoint(-1, 1, 0), new HexPoint(-2, 1, 1));
            break;

        case L:
            retval = new Tetromino("L", tetrominoColor(328.0f), new HexPoint(-2, 1, 1), new HexPoint(-1, 0, 1),
                    new HexPoint(0, 0, 0), new HexPoint(1, 0, -1));
            break;

        case J:
            retval = new Tetromino("J", tetrominoColor(4.0f), new HexPoint(1, 1, -2), new HexPoint(1, 0, -1),
                    new HexPoint(0, 0, 0), new HexPoint(-1, 0, 1));
            break;

        case O:
            retval = new Tetromino("O", tetrominoColor(40.0f), new HexPoint(-1, 0, 1), new HexPoint(-1, 1, 0),
                    new HexPoint(0, 0, 0), new HexPoint(0, 1, -1));
            break;

        case T1:
            retval = new Tetromino("T1", tetrominoColor(76.0f), new HexPoint(-1, 0, 1), new HexPoint(0, 0, 0),
                    new HexPoint(1, 0, -1), new HexPoint(-1, 1, 0));
            break;

        case T2:
            retval = new Tetromino("T2", tetrominoColor(112.0f), new HexPoint(-1, 0, 1), new HexPoint(0, 0, 0),
                    new HexPoint(1, 0, -1), new HexPoint(0, 1, -1));
            break;

        case Y:
            retval = new Tetromino("Y", tetrominoColor(148.0f), new HexPoint(0, 0, 0), new HexPoint(1, -1, 0),
                    new HexPoint(-1, 0, 1), new HexPoint(0, 1, -1));
            break;
        }
        return retval;
    }

    private static Color tetrominoColor(float angle) {
        int rgb = Color.HSBtoRGB(angle / 360.0f, SATURATION, BRIGHTNESS);
        return new Color(rgb);
    }

    /**
     * The points occupied by the tetromino.
     *
     * @return The points.
     */
    public Collection<? extends HexPoint> getPoints() {
        return Collections.unmodifiableList(Arrays.asList(points));
    }

    /**
     * The colour.
     *
     * @return A colour.
     */
    public Color getColor() {
        return this.color;
    }

    /**
     * The name.
     *
     * @return A string.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Move the tetromino along the orthogonal axes.
     *
     * @param w Horizontal co-ordinate.
     * @param h Vertical co-ordinate.
     */
    public void moveTo(int w, int h) {
        int dw = w - this.w;
        int dh = h - this.h;

        this.applyTranslation(dw, dh);
    }

    /**
     * Move the tetromino left.
     *
     * @param gb The game board to check.
     */
    public void moveLeft(GameBoard gb) {
        if (gb.hasSpaceLeft(Arrays.asList(this.points)))
            this.applyTranslation(-1, 0);
    }

    /**
     * Move the tetromino right.
     *
     * @param gb The game board to check.
     */
    public void moveRight(GameBoard gb) {
        if (gb.hasSpaceRight(Arrays.asList(this.points)))
            this.applyTranslation(1, 0);
    }

    /**
     * Rotate the tetromino clockwise.
     *
     * @param gb The game board to check.
     */
    public void rotateCW(GameBoard gb) {
        int[][] transform = { { 0, -1, 0, 0 }, { 0, 0, -1, 0 }, { -1, 0, 0, 0 }, { 0, 0, 0, 1 }, };
        this.checkAvailabilityAndTransform(gb, transform);
    }

    /**
     * Rotate the tetromino counterclockwise.
     *
     * @param gb The game board to check.
     */
    public void rotateCCW(GameBoard gb) {
        int[][] transform = { { 0, 0, -1, 0 }, { -1, 0, 0, 0 }, { 0, -1, 0, 0 }, { 0, 0, 0, 1 }, };
        this.checkAvailabilityAndTransform(gb, transform);
    }

    /**
     * Move the tetromino down on a trajectory.
     *
     * @param trajectory The trajectory.
     * @param gb The game board to check.
     * @return -1 if the tetromino can still move, the number of lines removed
     * if the tetromino occupied space on the game board.
     */
    public int dropOne(Trajectory trajectory, GameBoard gb) {
        int retval = -1;
        Collection<HexPoint> nextPoints = trajectory.nextPoints(Arrays.asList(points), gb);

        if (null == nextPoints)
            retval = gb.occupySpace(points, color);
        else {
            int i = 0;
            for (HexPoint point : nextPoints) {
                points[i].copyFrom(point);
                i++;
            }
            this.h--;
        }
        return retval;
    }

    /**
     * Drop the tetromino.
     *
     * @param trajectory The trajectory.
     * @param gb The game board to check.
     * @return A pair: the dropped distance and how many lines were removed.
     */
    public Pair<Integer, Integer> drop(Trajectory trajectory, GameBoard gb) {
        int distance = 0;
        int lines = 0;
        while (-1 == (lines = this.dropOne(trajectory, gb)))
            distance++;
        return new Pair<Integer, Integer>(distance, lines);
    }

    /**
     * Transform if possible.
     *
     * @param gb The game board to check.
     * @param transform The transformation.
     */
    protected void checkAvailabilityAndTransform(GameBoard gb, int[][] transform) {
        MutableHexPoint newShapePoints[] = new MutableHexPoint[this.shapePoints.length];
        MutableHexPoint newPoints[] = new MutableHexPoint[this.shapePoints.length];
        int minY = 0;
        for (int i = 0; i < this.shapePoints.length; i++) {
            newShapePoints[i] = this.shapePoints[i].mutableClone();
            newShapePoints[i].transform(transform);
            minY = Math.min(minY, newShapePoints[i].getY());
        }

        for (int i = 0; i < this.shapePoints.length; i++)
            newPoints[i] = newShapePoints[i].mutableClone();
        HexPoint.applyOrthogonalTranslation(Arrays.asList(newPoints), this.w, this.h - minY);

        if (gb.areAllValidAndVacant(Arrays.asList(newPoints))) {
            for (int i = 0; i < this.shapePoints.length; i++) {
                this.shapePoints[i].copyFrom(newShapePoints[i]);
                this.points[i].copyFrom(newPoints[i]);
            }
        }
    }

    /**
     * Apply a transformation along orthogonal axes.
     *
     * @param dw Horizontal distance.
     * @param dh vertical distance.
     */
    protected void applyTranslation(int dw, int dh) {
        HexPoint.applyOrthogonalTranslation(points, dw, dh);
        this.w += dw;
        this.h += dh;
    }

    @Override
    public String toString() {
        return String.format("Tetromino %s w: %d h: %d points: (%s)", this.name, this.w, this.h,
                StringUtils.join(points, ", "));
    }
}