package lect78_impl.model; import lect78_impl.view.Sprite; import utils.NotPossibleException; import utils.DomainConstraint; /** * @overview An armored vehicle used in the game * * @attributes * id Integer Unique identifier * hitPoint Integer Maximum health points * currentHP Integer Current health points * damage Integer Base damage dealt * armor Integer Damage reduction value * price Integer Cost of the tank * position Position Current position on board * angle Integer Current facing angle (degrees) * owner Player The player who owns this tank * sprite Sprite Visual representation * range Integer Attack range in cells */ public class Tank { private static int nextId = 1; private int id; @DomainConstraint(type="Integer", mutable=true, optional=false, min=1) private int hitPoint; private int currentHP; @DomainConstraint(type="Integer", mutable=true, optional=false, min=1) private int damage; @DomainConstraint(type="Integer", mutable=true, optional=false, min=0) private int armor; @DomainConstraint(type="Integer", mutable=false, optional=false, min=1) private int price; private Position position; private int angle; private Player owner; private String spritePath; // Store just the path, not the sprite object private int range; private boolean destroyed; // Movement/action cooldowns private long lastMoveTime = 0; private long lastRotateTime = 0; private long lastFireTime = 0; // Constants private static final long MOVE_DELAY = 200; // 200ms delay between moves private static final long ROTATE_DELAY = 150; // 150ms delay between rotations private static final long FIRE_DELAY = 1000; // 1s delay between shots /** * @effects Initialize this as a new Tank with specified parameters */ public Tank(int hp, int damage, int armor, int price, String spritePath) throws NotPossibleException { if (hp < 1) { throw new NotPossibleException("Tank.init: Invalid HP: " + hp); } if (damage < 1) { throw new NotPossibleException("Tank.init: Invalid damage: " + damage); } if (armor < 0) { throw new NotPossibleException("Tank.init: Invalid armor: " + armor); } if (price < 1) { throw new NotPossibleException("Tank.init: Invalid price: " + price); } this.id = nextId++; this.hitPoint = hp; this.currentHP = hp; this.damage = damage; this.armor = armor; this.price = price; this.angle = 0; this.range = 3; // Default range this.destroyed = false; this.spritePath = spritePath; // Initialize with default position this.position = new Position(0, 0); } /** * @effects Gets the unique ID of this tank */ public int getId() { return id; } /** * @effects Gets the maximum hit points of this tank */ public int getHitPoint() { return hitPoint; } /** * @effects Gets the current hit points of this tank */ public int getCurrentHP() { return currentHP; } /** * @effects Gets the damage value of this tank */ public int getDamage() { return damage; } /** * @effects Gets the armor value of this tank */ public int getArmor() { return armor; } /** * @effects Gets the price of this tank */ public int getPrice() { return price; } /** * @effects Gets the range of this tank */ public int getRange() { return range; } /** * @effects Sets the range of this tank */ public void setRange(int range) { if (range > 0) { this.range = range; } } /** * @effects Gets the position of this tank */ public Position getPosition() { return position; } /** * @effects Sets the position of this tank */ public void setPosition(Position position) { this.position = position; } /** * @effects Gets the angle of this tank */ public int getAngle() { return angle; } /** * @effects Gets the owner of this tank */ public Player getOwner() { return owner; } /** * @effects Sets the owner of this tank */ public void setOwner(Player owner) { this.owner = owner; } /** * @effects Gets the sprite path for this tank */ public String getSpritePath() { return spritePath; } /** * @effects Checks if the tank can rotate now (cooldown check) */ public boolean canRotate() { long currentTime = System.currentTimeMillis(); if (currentTime - lastRotateTime >= ROTATE_DELAY) { lastRotateTime = currentTime; return true; } return false; } /** * @effects Rotates the tank by 45 degrees clockwise or counterclockwise */ public void rotate(boolean clockwise) { // Add or subtract 45 degrees, keeping angle in range [0, 315] if (clockwise) { angle = (angle + 45) % 360; } else { angle = (angle - 45 + 360) % 360; } } /** * @effects Checks if the tank can move now (cooldown check) */ public boolean canMove() { long currentTime = System.currentTimeMillis(); if (currentTime - lastMoveTime >= MOVE_DELAY) { lastMoveTime = currentTime; return true; } return false; } /** * @effects Checks if the tank can fire now (cooldown check) */ public boolean canFire() { long currentTime = System.currentTimeMillis(); if (currentTime - lastFireTime >= 0) { // FIRE_DELAY) { lastFireTime = currentTime; return true; } return false; } /** * @effects Take damage from an attack */ public void takeDamage(int amount) { currentHP -= amount; if (currentHP <= 0) { destroyed = true; } } /** * @effects Checks if this tank is destroyed */ public boolean isDestroyed() { return destroyed; } /** * @effects Fires a projectile of the specified type */ public Projectile fireProjectile(String type) { if (!canFire() || isDestroyed()) { return null; } // Record last fire time lastFireTime = System.currentTimeMillis(); Projectile projectile; switch (type) { case "AP": projectile = new ProjectileAP(damage, angle, this); break; case "HE": projectile = new ProjectileHE(damage, angle, this); break; case "Standard": projectile = new ProjectileStandard(damage, angle, this); break; default: return null; } // Set projectile's initial position to same as tank Position initialPos = new Position(position.getX(), position.getY()); projectile.setPosition(initialPos); return projectile; } /** * @effects Determines the movement direction based on angle * @return New position after movement, or null if invalid */ public Position calculateMovementPosition(boolean forward) { int x = position.getX(); int y = position.getY(); int direction = forward ? 1 : -1; // Calculate movement based on angle switch (angle) { case 0: // Right x += direction; break; case 45: // Right-Down x += direction; y += direction; break; case 90: // Down y += direction; break; case 135: // Left-Down x -= direction; y += direction; break; case 180: // Left x -= direction; break; case 225: // Left-Up x -= direction; y -= direction; break; case 270: // Up y -= direction; break; case 315: // Right-Up x += direction; y -= direction; break; } return new Position(x, y); } /** * @effects String representation of this tank */ @Override public String toString() { return String.format("Tank(id=%d, hp=%d/%d, damage=%d, armor=%d, position=%s)", id, currentHP, hitPoint, damage, armor, position); } }