A battle game between two wizards developed in Java, where players control characters who cast spells, collect power-ups, and compete to defeat their opponent.
Wizard Battle is a local multiplayer game where two players compete in a divided arena. Each player controls a wizard character who can:
- Move within their half of the arena
- Cast spells at the opponent
- Collect power-ups that appear randomly
- Temporarily boost their abilities through buffs
The goal is to reduce your opponent's life to zero before they do the same to you.
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE APLICAÇÃO │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────┐
│ App │
└────┬─────┘
│
┌──────────────┴──────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────┐
│ GameController │ │ HomeScreen │
│ - startAction │ │ - background │
│ + startGame() │ │ + hide() │
└─────────────────────┘ └──────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE GERENCIAMENTO │
└─────────────────────────────────────────────────────────────────────────┘
│
┌───────────┼───────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
│GameState │ │CollisionManager│ │PowerUpHandler │
│Manager │ │- characters │ │+ handleCollection│
│+ triggerGame │ │- powerUps │ │+ applyEffect │
│ Over() │ │+ checkCollision│ └──────────────────┘
└──────────────┘ └────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE PERSONAGENS │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────┐
│ Character │◄─────────────┐
│ (abstract) │ │
├───────────────┤ │
│# position │ │
│# characterHead│ │
│# healthBar │ │
│# playerNumber │ │
├───────────────┤ │
│+ moveUp() │ │
│+ moveDown() │ │
│+ moveLeft() │ │
│+ moveRight() │ │
│+ castSpell() │ │
│+ takeDamage() │ │
└───────┬───────┘ │
│ │
┌──────────┴──────────┐ │
│ │ │
▼ ▼ │
┌──────────────────┐ ┌──────────────────┐│
│PlayerOneCharacter│ │PlayerTwoCharacter││
│- opponent: P2 │ │- opponent: P1 ││
└──────────────────┘ └──────────────────┘│
│
┌─────────────────────┘
│
┌───────┴────────┐
│ BuffManager │
│+ applyTempBuff │
└────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE ELEMENTOS DE JOGO │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────┴────┐
│ │
▼ ▼
┌────────┐ ┌─────────┐
│ Spell │ │ PowerUp │◄──────┐
│ │ │ │ │
├────────┤ ├─────────┤ │
│- damage│ │- col │ │
│- speed │ │- row │ │
│- player│ └─────────┘ │
├────────┤ △ │
│+ move()│ │ │
└────────┘ │ │
┌────┴────┬────────┴─────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌───────────────┐
│PowerUp │ │PowerUp │ │PowerUpSpell │
│Health │ │Damage │ │Speed │
└──────────┘ └──────────┘ └───────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE INTERFACE │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────┴────────────┐
│ │
▼ ▼
┌────────┐ ┌──────────┐
│ Grid │ │HealthBar │
│ │ │ │
├────────┤ ├──────────┤
│- cols │ │- lives[] │
│- rows │ │+ remove │
│- canvas│ │ LifePoint│
├────────┤ └──────────┘
│+ init()│
└────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ CAMADA DE CONTROLES │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────┴────┐
│ │
▼ ▼
┌─────────────┐ ┌──────────┐
│AppKeyboard │ │ Controls │
│- keyboard │ │ │
│- character │ ├──────────┤
│+ keyPressed │ │- moveUp │
└─────────────┘ │- moveDown│
│- attack │
└──────────┘
WizzardBattle/
├── src/
│ ├── App.java # Application entry point
│ ├── game/ # Main game logic
│ │ ├── GameController.java # Central game controller
│ │ ├── GameStateManager.java # State management
│ │ ├── Player.java # Player wrapper class
│ │ ├── PlayerEnum.java # Player enumeration
│ │ ├── characters/ # Playable characters
│ │ │ ├── Character.java # Abstract base class
│ │ │ ├── PlayerOneCharacter.java # Player 1 implementation
│ │ │ ├── PlayerTwoCharacter.java # Player 2 implementation
│ │ │ └── BuffManager.java # Temporary buff manager
│ │ ├── spells/ # Spell system
│ │ │ └── Spell.java # Spell projectile
│ │ └── powerUps/ # Power-up system
│ │ ├── PowerUp.java # Base power-up class
│ │ ├── PowerUpHandler.java # Centralized handler
│ │ ├── PowerUpHealth.java # Health power-up
│ │ ├── PowerUpDamage.java # Damage power-up
│ │ └── PowerUpSpellSpeed.java # Spell speed power-up
│ ├── collisionManager/ # Collision system
│ │ └── CollisionManager.java # Collision detection
│ ├── keyboard/ # Input system
│ │ ├── AppKeyboard.java # Keyboard manager
│ │ └── Controls.java # Key mapping
│ ├── ui/ # User interface
│ │ ├── character/ # Character rendering
│ │ │ └── CharacterUI.java # Character UI
│ │ ├── grid/ # Grid system
│ │ │ ├── Grid.java # Main game grid
│ │ │ └── GameArea.java # Playable area
│ │ ├── healthBar/ # Health system
│ │ │ └── HealthBar.java # Health bar
│ │ ├── faceCard/ # Player avatars
│ │ │ └── PlayerFaceCard.java # Player face card
│ │ ├── screens/ # Game screens
│ │ │ ├── HomeScreen.java # Home screen
│ │ │ └── GameOverScreen.java # Game over screen
│ │ └── position/ # Positioning system
│ │ └── Position.java # Logical coordinates
│ └── utils/ # Utilities
│ └── AppColor.java # Color palette
├── resources/ # Graphic resources
│ ├── Characters/ # Character sprites
│ ├── Faces/ # Player avatars
│ ├── PowerUps/ # Power-up sprites
│ ├── Spells/ # Spell sprites
│ └── *.png # Backgrounds and UI
├── lib/ # External libraries
└── build.xml # Ant build script
Purpose: Main entry point of the application.
Responsibilities:
- Initialize the home screen (HomeScreen)
- Set up the game start callback
- Create the grid and player characters
- Initialize the collision system
Main Methods:
main(String[] args)- Application entry methodstartGame()- Starts a new match, creating grid and characters
Execution Flow:
- Creates and displays HomeScreen
- Waits for SPACE key to start
- Creates Grid (128x72 cells, 1920x1080 pixels)
- Positions players in their halves of the arena
- Instantiates PlayerOneCharacter and PlayerTwoCharacter
Purpose: Central controller that coordinates the start of the game.
Responsibilities:
- Store the start game action
- Provide an entry point to start matches
Methods:
setStartAction(Runnable action)- Sets the action to execute on startstartGame()- Executes the registered start action
Usage: Decouples screens (HomeScreen, GameOverScreen) from App initialization logic.
Purpose: Centralizes game state transitions (mainly game-over).
Responsibilities:
- Manage game over
- Clear all visual and logical elements
- Display victory screen
Methods:
triggerGameOver(PlayerEnum winner)- Triggers the game-over sequence
Game-Over Process:
- Clears all health bars
- Clears CollisionManager (characters, power-ups)
- Clears Grid and visual elements
- Waits 100ms for threads to finish
- Displays GameOverScreen with the winner
Propósito: Sistema de detecção de colisões entre todos os elementos do jogo.
Responsabilidades:
- Registrar personagens e power-ups ativos
- Detectar colisões entre feitiços e personagens
- Verificar coleta de power-ups
- Validar movimentos dentro da área de jogo
Estruturas de Dados:
List<Character> registeredCharacters- Personagens ativosList<PowerUp> registeredPowerUps- Power-ups disponíveisDEBUG_COLLISIONS- Flag de debug (atualmente true)
Métodos Principais:
Registro:
registerCharacter(Character)- Adiciona personagem ao sistemaregisterPowerUp(PowerUp)- Adiciona power-up ao sistemaunregisterPowerUp(PowerUp)- Remove power-up coletado
Detecção de Power-Ups:
getPowerUpAt(int col, int row)- Busca power-up em posição específica (raio de 1 célula)getPowerUpAlongPath(fromCol, fromRow, toCol, toRow)- Detecta power-ups ao longo de movimentogetPowerUpOverlappingCharacter(Character)- Detecção por sobreposição de pixels
Detecção de Feitiços:
getCollidingCharacter(Spell)- Verifica colisão simples feitiço-personagemgetCollidingCharacterAlongPath(Spell, fromCol, toCol)- Detecção avançada considerando trajetória
Validação de Movimento:
checkGameAreaCollision(newCol, newRow)- Verifica se movimento é válido- Considera limites lógicos (colunas/linhas)
- Considera limites físicos (pixels)
- Aplica padding extra para hitbox dos personagens
- Respeita divisão de arena por jogador
Limpeza:
clearAll()- Remove todos os elementos registradosdumpState()- Método de debug (vazio após remoção de logs)
Algoritmos de Colisão:
-
Colisão Feitiço-Personagem:
- Calcula área varrida pelo feitiço (swept rectangle)
- Usa posição anterior e atual do feitiço
- Adiciona padding de hitbox (8 pixels para feitiço, 12 para personagem)
- Adiciona padding vertical de meia célula
-
Colisão Power-Up:
- Busca em raio de células configurável (POWER_UP_PICKUP_RADIUS_CELLS)
- Fallback para detecção por sobreposição de pixels
- Suporta movimento multi-célula (quando velocidade > 1)
Propósito: Centraliza toda a lógica de coleta e aplicação de power-ups.
Responsabilidades:
- Verificar coleta de power-ups durante movimento
- Aplicar efeitos apropriados ao personagem
- Remover power-up do jogo após coleta
Métodos:
handlePowerUpCollection(Character, fromCol, fromRow, toCol, toRow)- Verifica e processa coletaapplyPowerUpEffect(Character, PowerUp)- Aplica efeito do power-up
Efeitos por Tipo:
- PowerUpHealth:
character.addLifePoints() - PowerUpDamage:
character.applyDamageBuff(1, 10s) - PowerUpSpellSpeed:
character.applySpeedBuff(1, 10s)+applyMovementBuff(1, 10s)
Benefício: Elimina duplicação de código (antes: 8 lugares, agora: 1 lugar).
Propósito: Classe base que contém toda a lógica comum aos personagens.
Atributos Protegidos:
protected CharacterUI characterHead; // Sprite visual
protected Position position; // Posição lógica (col, row)
protected PlayerEnum playerNumber; // Identificador do jogador
protected AppKeyboard appKeyboard; // Sistema de input
protected CollisionManager collisionManager; // Gerenciador de colisões
protected HealthBar healthBar; // Barra de vidaAtributos de Buffs:
private int spellDamageModifier = 0; // Buff de dano (+1 = 2 de dano total)
private int spellSpeedModifier = 0; // Buff de velocidade de feitiço
private int movementSpeedModifier = 0; // Buff de velocidade de movimentoMétodos Abstratos (devem ser implementados pelas subclasses):
Position getPosition()- Retorna posição atualint getPixelX/Y/Width/Height()- Retorna bounds em pixelsPlayerEnum getOpponentPlayer()- Retorna enum do oponentevoid takeDamage(int damage)- Processa dano recebidovoid addLifePoints()- Adiciona vida ao personagem
Métodos Concretos de Movimento:
Todos os métodos de movimento seguem o mesmo padrão:
public void moveUp/Down/Left/Right() {
// 1. Calcula células a mover (1 + buff de velocidade)
int moveCells = 1 + Math.max(0, getMovementSpeedModifier());
// 2. Calcula nova posição
int newRow/Col = position + (±moveCells);
// 3. Valida movimento
if (collisionManager.checkGameAreaCollision(newCol, newRow)) {
// 4. Move sprite visual
characterHead.move(deltaX, deltaY);
// 5. Verifica coleta de power-ups
PowerUpHandler.handlePowerUpCollection(...);
// 6. Atualiza posição lógica
position.setRow/Col(newValue);
}
}Método de Feitiço:
public void castSpell() {
Spell s = new Spell(position.getRow(), position.getCol(), playerNumber);
s.setDamage(s.getDamage() + getSpellDamageModifier());
s.setSpeed(s.getSpeed() + getSpellSpeedModifier());
}Sistema de Buffs:
applyDamageBuff(extraDamage, durationSeconds)- Aumenta dano temporariamenteapplySpeedBuff(extraSpeed, durationSeconds)- Aumenta velocidade de feitiçoapplyMovementBuff(extraMovement, durationSeconds)- Aumenta velocidade de movimento
Todos os buffs usam BuffManager.applyTemporaryBuff() internamente.
Outros Métodos:
cleanupOnGameOver()- Hook para limpeza no fim do jogohideCharacter()- Esconde sprite do personagem
Propósito: Implementações concretas para cada jogador.
Diferenças:
- Sprite:
character.pngvscharacter2.png - Controles: WASD+T vs IJKL+P
- Oponente: Player_2 vs Player_1
Construtor:
public PlayerOneCharacter(Grid grid, int column, int row) {
playerNumber = PlayerEnum.Player_1;
position = new Position(column, row);
characterHead = new CharacterUI(column, row, "resources/Characters/character.png");
appKeyboard = new AppKeyboard(PlayerEnum.Player_1, this);
collisionManager = new CollisionManager(this, grid);
healthBar = new HealthBar(PlayerEnum.Player_1);
}Implementação de takeDamage:
public void takeDamage(int damage) {
healthBar.removeLifePoints(damage);
if (!healthBar.isAlive()) {
hideCharacter();
GameStateManager.triggerGameOver(getOpponentPlayer());
}
}Métricas: Cada classe tem apenas ~65-70 linhas (redução de 74% após refatoração).
Propósito: Gerenciador genérico de buffs temporários.
Interface ModifiableValue:
public interface ModifiableValue {
void modify(int delta); // delta pode ser positivo (aplicar) ou negativo (remover)
}Método Principal:
public static void applyTemporaryBuff(
ModifiableValue target, // O que modificar
int extraValue, // Quanto adicionar
int durationSeconds // Por quanto tempo
)Funcionamento:
- Aplica buff imediatamente:
target.modify(+extraValue) - Cria thread que aguarda duração
- Remove buff:
target.modify(-extraValue)
Uso Típico:
applyDamageBuff(1, 10) {
BuffManager.applyTemporaryBuff(delta -> {
synchronized (this) {
spellDamageModifier = Math.max(0, spellDamageModifier + delta);
}
}, 1, 10);
}Benefício: Elimina duplicação de 3 métodos similares em Character.
Propósito: Projétil de feitiço que se move horizontalmente e causa dano.
Atributos:
private Picture spell; // Sprite visual
private Position position; // Posição lógica
private int prevX; // Posição X anterior (para swept collision)
private int speed; // Células por tick (padrão: 2)
private int damage; // Dano causado (padrão: 1)
private PlayerEnum playerEnum; // Dono do feitiçoGerenciamento Global:
private static final List<Spell> ACTIVE; // Todos os feitiços ativos
public static void cleanupAll(); // Remove todos ao fim do jogoConstrutor:
public Spell(int row, int col, PlayerEnum playerEnum)Cálculo de Posição Inicial:
- Calcula posição base da célula
- Adiciona offset vertical (SPELL_VERTICAL_OFFSET_BASE ou _P2)
- Ajusta horizontalmente para simular disparo da mão:
- Player 1: dispara da mão direita (+handOffset)
- Player 2: dispara da mão esquerda (-handOffset)
Thread de Movimento:
O feitiço cria uma thread que:
-
Loop Principal:
while (true) { // Calcula próxima posição int desiredNext = currentCol + (dir * speed); // Verifica limites da arena if (fora_dos_limites) { // Move até a borda // Aguarda 40ms // Remove feitiço break; } // Verifica colisão com personagem Character hit = CollisionManager.getCollidingCharacterAlongPath(...); if (hit != null) { // Move até o alvo hit.takeDamage(damage); // Remove feitiço break; } // Move normalmente position.setCol(desiredNext); translate(dir * CELL_SIZE * speed, 0); // Aguarda 60ms antes do próximo tick Thread.sleep(60); }
-
Direção:
- Player 1:
dir = 1(direita →) - Player 2:
dir = -1(esquerda ←)
- Player 1:
-
Velocidade: 2 células a cada 60ms = ~33 células/segundo
Métodos Auxiliares:
translate(col, row)- Move sprite e atualiza prevXsafeDelete()- Remove sprite com tratamento de errogetX/Y/Width/Height()- Getters para detecção de colisãogetPrevX()- Posição anterior para swept collision
Propósito: Classe base para todos os power-ups coletáveis.
Atributos:
private final int col; // Coluna lógica
private final int row; // Linha lógica
private Picture powerUpSquare; // Sprite visual
private static final List<PowerUp> ACTIVE; // Todos power-ups ativosConstrutor:
public PowerUp(int col, int row, String imagePath) {
this.row = row;
this.col = col;
// Calcula posição centralizada na célula
int x = Grid.PADDING + col * Grid.CELL_SIZE + (Grid.CELL_SIZE - size) / 2;
int y = Grid.PADDING + row * Grid.CELL_SIZE + (Grid.CELL_SIZE - size) / 2;
powerUpSquare = new Picture(x, y, imagePath);
powerUpSquare.draw();
CollisionManager.registerPowerUp(this);
ACTIVE.add(this);
}Métodos:
removeFromGame()- Remove power-up após coleta- Deleta sprite
- Remove do CollisionManager
- Notifica Grid
getPixelX/Y/Width/Height()- Bounds para detecção de colisãocleanupAll()- Remove todos power-ups (fim de jogo)
Subclasses:
-
PowerUpHealth (
resources/PowerUps/health.png)- Efeito: +1 ponto de vida
-
PowerUpDamage (
resources/PowerUps/damage.png)- Efeito: +1 dano por 10 segundos
-
PowerUpSpellSpeed (
resources/PowerUps/spellSpeed.png)- Efeito: +1 velocidade de feitiço e movimento por 10 segundos
Sistema de Spawn:
- Power-ups são criados pelo Grid
- Delay entre spawns:
POWER_UP_SPAWN_DELAY_SECONDS = 8 - Duração de buffs:
POWER_UP_BUFF_DURATION_SECONDS = 10 - Raio de coleta:
POWER_UP_PICKUP_RADIUS_CELLS = 1
Propósito: Sistema central de renderização e coordenação da arena.
Constantes Importantes:
public static final int PADDING = 10;
public static final int DEFAULT_CELL_SIZE = 5;
public static int CELL_SIZE; // Definido em runtime
// Power-Ups
public static final int POWER_UP_SPAWN_DELAY_SECONDS = 8;
public static final int POWER_UP_BUFF_DURATION_SECONDS = 10;
public static final int POWER_UP_PICKUP_RADIUS_CELLS = 1;
// Hitboxes
public static final int EXTRA_HIT_BOX_PADDING_CHAR_PIXELS = 12;
public static final int EXTRA_HIT_BOX_PADDING_SPELL_PIXELS = 8;
// Alinhamento de Feitiços
public static final int SPELL_VERTICAL_OFFSET_BASE = 35;
public static final int SPELL_VERTICAL_OFFSET_P2 = 40;
public static final int SPELL_HAND_TUNING = 6;Atributos:
private static int cols; // Colunas totais
private static int rows; // Linhas totais
private int cellSize; // Tamanho de cada célula
private static Picture canvas; // Background
private GameArea gameArea; // Área jogável central
private static PowerUp leftPowerUp; // Power-up esquerdo ativo
private static PowerUp rightPowerUp; // Power-up direito ativo
private PlayerFaceCard card1, card2; // Avatares dos jogadoresConstrutor e Inicialização:
// Construtor calcula tamanho de célula baseado na resolução alvo
public Grid(int cols, int rows, int targetWidth, int targetHeight) {
Grid.cols = cols;
Grid.rows = rows;
this.targetWidth = targetWidth - 2 * PADDING;
this.targetHeight = targetHeight - 2 * PADDING;
// Calcula maior célula que cabe
int sizeByWidth = this.targetWidth / cols;
int sizeByHeight = this.targetHeight / rows;
this.cellSize = Math.min(sizeByWidth, sizeByHeight);
}
// Inicialização cria todos os elementos visuais
public void init() {
CELL_SIZE = cellSize;
// 1. Canvas de fundo
canvas = new Picture(PADDING, PADDING, "resources/backgroun2.png");
canvas.draw();
// 2. GameArea (área jogável)
gameArea = new GameArea("resources/GameArea.png", ...);
// 3. Avatares dos jogadores
card1 = new PlayerFaceCard(...);
card2 = new PlayerFaceCard(...);
// 4. Inicia spawn de power-ups
startPowerUpSpawning();
}Sistema de GameArea:
A GameArea é a região central onde os personagens podem se mover.
// Cálculo da área de jogo
public int getGameAreaRows() {
int areaHeight = gameArea.getAreaHeight();
return Math.max(0, areaHeight / cellSize);
}
public int getGameAreaTopRow() {
int totalRows = rows;
int gameRows = getGameAreaRows();
return (totalRows - gameRows) / 2; // Centralizado verticalmente
}
public int getGameAreaBottomRow() {
return getGameAreaTopRow() + getMaxRowsPerPlayer() - 1;
}
// Cada jogador tem metade das colunas
public int getMaxColsPerPlayer() {
return Math.max(1, cols / 2);
}
// Cada jogador tem todas as linhas da game area
public int getMaxRowsPerPlayer() {
return getGameAreaRows();
}Sistema de Power-Ups:
private void startPowerUpSpawning() {
powerUpSpawningEnabled = true;
new Thread(() -> {
while (powerUpSpawningEnabled) {
try {
Thread.sleep(POWER_UP_SPAWN_DELAY_SECONDS * 1000);
if (powerUpSpawningEnabled) {
spawnRandomPowerUps();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}).start();
}
private void spawnRandomPowerUps() {
// Escolhe tipo aleatório
PowerUp[] types = {
new PowerUpHealth(...),
new PowerUpDamage(...),
new PowerUpSpellSpeed(...)
};
// Spawn em posições aleatórias
// - leftPowerUp: lado esquerdo da arena
// - rightPowerUp: lado direito da arena
}Métodos Úteis:
getCols/Rows()- Dimensões da gridgetWidth/Height()- Tamanho em pixels do canvasgetActiveGrid()- Retorna instância ativa da gridclearAll()- Remove todos elementos visuaisonPowerUpCollected(PowerUp)- Callback quando power-up é coletado
Propósito: Sistema de visualização de pontos de vida.
Estrutura:
public class HealthBar {
private Life[] lifeCounter; // Array de ícones de vida
private PlayerEnum playerNumber; // Dono da barra
private int numberOfLives = 10; // Vidas totais (padrão)
// Lista global para limpeza
private static List<HealthBar> INSTANCES;
// Classe interna para cada ícone de vida
class Life {
private Picture life;
public Life(int index, boolean playerOne) { ... }
public void remove() { ... }
}
}Layout:
Player 1 (esquerda): Player 2 (direita):
❤❤❤❤❤❤❤❤❤❤ ❤❤❤❤❤❤❤❤❤❤
- Player 1: Vidas alinhadas da esquerda para direita
- Player 2: Vidas alinhadas da direita para esquerda
Construtor:
public HealthBar(PlayerEnum playerNumber) {
this.playerNumber = playerNumber;
lifeCounter = new Life[numberOfLives];
// Cria todos os ícones de vida
for (int i = 0; i < numberOfLives; i++) {
lifeCounter[i] = new Life(i, playerNumber == PlayerEnum.Player_1);
}
INSTANCES.add(this);
}Métodos Principais:
-
removeLifePoints(int pointsToRemove)
- Player 1: Remove do fim para o início (direita → esquerda)
- Player 2: Remove do início para o fim (esquerda → direita)
-
isAlive()
- Retorna true se ainda há algum ícone de vida
-
addLifePoints()
- Algoritmo inteligente de preenchimento:
- Se vazio, adiciona na primeira/última posição
- Player 1: Tenta adicionar após o último preenchido
- Player 2: Tenta adicionar antes do primeiro preenchido
- Fallback: Preenche primeiro slot vazio encontrado
- Algoritmo inteligente de preenchimento:
Limpeza:
cleanup()- Remove todos os ícones de uma barracleanupAll()- Remove todas as barras (chamado em game-over)
HomeScreen.java:
public class HomeScreen {
private Picture background;
private Picture title;
private Text startHint;
private boolean visible = true;
public HomeScreen() {
// Carrega background
background = new Picture(0, 0, "resources/backgroun2.png");
background.draw();
// Carrega título
title = new Picture(x, y, "resources/WizardBattle.png");
title.draw();
// Texto de instrução
startHint = new Text(x, y, "Press SPACE to start");
startHint.setColor(Color.WHITE);
startHint.draw();
}
public void hide() {
if (visible) {
background.delete();
title.delete();
startHint.delete();
visible = false;
}
}
}GameOverScreen.java:
public class GameOverScreen implements KeyboardHandler {
private Picture background;
private Picture winnerFace;
private Text winnerText;
private Text hintText;
private boolean triggered = false;
public GameOverScreen(PlayerEnum winner) {
// 1. Background
background = new Picture(0, 0, "resources/backgroun2.png");
background.draw();
// 2. Face do vencedor (ampliada)
String facePath = Grid.getPlayerFacePath(winner);
winnerFace = new Picture(centerX, centerY, facePath);
winnerFace.draw();
winnerFace.grow(120, 120);
// 3. Texto de vitória
winnerText = new Text(x, y, playerName + " WINS!");
winnerText.setColor(Color.YELLOW);
winnerText.draw();
// 4. Hint de restart
hintText = new Text(x, y, "Press SPACE to restart");
hintText.draw();
// 5. Escuta SPACE para reiniciar
setupKeyboardListener();
}
public void keyPressed(KeyboardEvent event) {
if (!triggered && event.getKey() == KeyboardEvent.KEY_SPACE) {
triggered = true;
cleanup();
GameController.startGame();
}
}
}Propósito: Gerenciador de entrada de teclado por personagem.
Atributos:
private Keyboard keyboard; // Listener do SimpleGraphics
private Controls playerControls; // Mapeamento de teclas
private Character controlledCharacter; // Personagem controladoSistema Global de SPACE:
private static Keyboard globalKeyboard;
private static boolean armedForStart = false;
private static HomeScreen armedHomeScreen = null;
private static Runnable armedAction = null;Construtor:
public AppKeyboard(PlayerEnum playerNumber, Character character) {
keyboard = new Keyboard(this);
playerControls = new Controls(playerNumber);
this.controlledCharacter = character;
// Registra eventos
keyboard.addEventListener(playerControls.getMoveUpEvent());
keyboard.addEventListener(playerControls.getMoveDownEvent());
keyboard.addEventListener(playerControls.getMoveLeftEvent());
keyboard.addEventListener(playerControls.getMoveRightEvent());
keyboard.addEventListener(playerControls.getAttackEvent());
}Handler de Eventos:
public void keyPressed(KeyboardEvent event) {
int key = event.getKey();
if (key == playerControls.getMoveRightEvent().getKey()) {
controlledCharacter.moveRight();
} else if (key == playerControls.getMoveLeftEvent().getKey()) {
controlledCharacter.moveLeft();
} else if (key == playerControls.getMoveUpEvent().getKey()) {
controlledCharacter.moveUp();
} else if (key == playerControls.getMoveDownEvent().getKey()) {
controlledCharacter.moveDown();
} else if (key == playerControls.getAttackEvent().getKey()) {
controlledCharacter.castSpell();
}
}Sistema de Start:
public static void addStartListener(HomeScreen screen, Runnable action) {
// Inicializa listener global de SPACE uma única vez
// Permite "armar" o listener para start/restart
armedHomeScreen = screen;
armedAction = action;
armedForStart = true;
}Propósito: Configuração de mapeamento de teclas por jogador.
Estrutura de Configuração:
private static class ControlConfig {
int up, down, left, right, attack;
ControlConfig(int up, int down, int left, int right, int attack) {
this.up = up;
this.down = down;
this.left = left;
this.right = right;
this.attack = attack;
}
}Configurações Pré-Definidas:
private static final ControlConfig PLAYER1_CONFIG = new ControlConfig(
KeyboardEvent.KEY_W, // Cima
KeyboardEvent.KEY_S, // Baixo
KeyboardEvent.KEY_A, // Esquerda
KeyboardEvent.KEY_D, // Direita
KeyboardEvent.KEY_T // Ataque
);
private static final ControlConfig PLAYER2_CONFIG = new ControlConfig(
KeyboardEvent.KEY_I, // Cima
KeyboardEvent.KEY_K, // Baixo
KeyboardEvent.KEY_J, // Esquerda
KeyboardEvent.KEY_L, // Direita
KeyboardEvent.KEY_P // Ataque
);Construtor:
public Controls(PlayerEnum playerNumber) {
// Seleciona configuração apropriada
ControlConfig config = playerNumber.equals(PlayerEnum.Player_1)
? PLAYER1_CONFIG
: PLAYER2_CONFIG;
// Configura todos os eventos
setupKeyEvent(moveUp, config.up);
setupKeyEvent(moveDown, config.down);
setupKeyEvent(moveLeft, config.left);
setupKeyEvent(moveRight, config.right);
setupKeyEvent(attack, config.attack);
}
private void setupKeyEvent(KeyboardEvent event, int key) {
event.setKey(key);
event.setKeyboardEventType(KeyboardEventType.KEY_PRESSED);
}Vantagens da Abordagem:
- Fácil adicionar novos jogadores
- Configuração centralizada
- Sem duplicação de código
- Fácil modificar esquema de controles
Reduce your opponent's life to zero before they do the same to you!
- W - Move up
- S - Move down
- A - Move left
- D - Move right
- T - Cast spell
- I - Move up
- K - Move down
- J - Move left
- L - Move right
- P - Cast spell
- SPACE - Start game / Restart after game-over
Power-ups appear randomly in the arena every 8 seconds:
- ❤️ Health (Green): Adds +1 life point
- ⚔️ Damage (Red): Increases spell damage for 10 seconds
- ⚡ Speed (Blue): Increases movement and spell speed for 10 seconds
- Movement: Characters can only move within their half of the arena
- Spells: Travel in a straight line and deal 1 damage (2 with buff)
- Power-Ups: Appear on both sides - be quick to collect them
- Buffs: Stack! Collecting multiple increases effects
- Positioning: Keep your distance when casting spells
- Java JDK 8+
- Apache Ant (for build)
- SimpleGraphics library (included in
lib/)
# Compile only
ant compile
# Compile and create JAR
ant jarfile
# Clean build
ant clean# Run from compiled classes
java -cp "build/classes;lib/*" App
# Or run the JAR (after ant jarfile)
java -jar "build/Wizard Battle.jar"The build.xml file defines:
- init: Creates build directories
- prepare: Prepares structure
- copy-resources: Copies images and resources to build/classes
- compile: Compiles Java code
- jarfile: Creates executable JAR
This project has undergone significant refactoring to improve code quality:
-
Elimination of Duplication
- Reduced 350+ lines of duplicated code
- PlayerOneCharacter: 254 → 68 lines (-73%)
- PlayerTwoCharacter: 248 → 65 lines (-74%)
-
New Utility Classes
PowerUpHandler- Centralizes power-up logicBuffManager- Generic temporary buff systemGameStateManager- Game state management
-
Logic Consolidation
- Common movement in the
Characterclass - Power-ups processed in a single place
- Centralized game-over logic
- Common movement in the
-
Data-Driven Configuration
- Controls configured by data objects
- Easy to add new players or control schemes
Para contribuir com o projeto:
- Faça fork do repositório
- Crie uma branch para sua feature (
git checkout -b feature/NovaFeature) - Commit suas mudanças (
git commit -m 'Adiciona NovaFeature') - Push para a branch (
git push origin feature/NovaFeature) - Abra um Pull Request
- Mantenha o código limpo e bem documentado
- Siga os padrões de código existentes
- Teste suas mudanças antes de commitar
- Atualize a documentação conforme necessário
Este projeto é livre para uso educacional e pessoal.
Desenvolvido como projeto acadêmico para aprendizado de:
- Programação Orientada a Objetos
- Padrões de Design
- Desenvolvimento de Jogos 2D
Divirte-te🧙♂️⚡🧙♀️