Initial commit of Mesos Java project
22
src/main/java/Server/Automaton/ActionResult.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package Server.Automaton;
|
||||
|
||||
public class ActionResult {
|
||||
private final boolean success;
|
||||
private final String errorMessage; // null se success
|
||||
|
||||
private ActionResult(boolean success, String errorMessage) {
|
||||
this.success = success;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public static ActionResult ok() {
|
||||
return new ActionResult(true, null);
|
||||
}
|
||||
|
||||
public static ActionResult failure(String message) {
|
||||
return new ActionResult(false, message);
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public String getErrorMessage(){ return errorMessage; }
|
||||
}
|
||||
53
src/main/java/Server/Automaton/Automaton.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package Server.Automaton;
|
||||
|
||||
public class Automaton {
|
||||
|
||||
private GameState state = GameState.SETUP;
|
||||
|
||||
public GameState getState(){
|
||||
return state;
|
||||
}
|
||||
|
||||
private Boolean canEvolve(int val){
|
||||
int currOrd = state.ordinal();
|
||||
int setupOrd = GameState.SETUP.ordinal();
|
||||
|
||||
if (currOrd == setupOrd && val > 5) {
|
||||
System.out.println("Invalid number of player");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public Boolean evolve(int i) {
|
||||
if (!canEvolve(i))
|
||||
return false;
|
||||
|
||||
int currOrd = state.ordinal();
|
||||
int lastOrd = GameState.GAME_OVER.ordinal();
|
||||
|
||||
if (currOrd < lastOrd) {
|
||||
state = state.next(i);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Boolean evolveTo(GameState toState, int i){
|
||||
if (!canEvolve(i))
|
||||
return false;
|
||||
|
||||
int toOrd = toState.ordinal();
|
||||
int currOrd = state.ordinal();
|
||||
|
||||
if (toOrd>currOrd) {
|
||||
state = toState;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
789
src/main/java/Server/Automaton/Game.java
Normal file
@@ -0,0 +1,789 @@
|
||||
package Server.Automaton;
|
||||
|
||||
import Server.*;
|
||||
import Server.Cards.*;
|
||||
import Server.Utils.LoadingCardsException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Central game controller for MESOS.
|
||||
*
|
||||
* Owns the full game lifecycle:
|
||||
*
|
||||
* SETUP → newGame()
|
||||
* TOTEM_PLACEMENT → placeTotem() — one call per player, in TurnTile order
|
||||
* ACTION_RESOLUTION → resolveCardAction() — one call per pending action
|
||||
* EXTRA_DRAW → extraDrawAction() / skipExtraDraw() [only if a player owns the building]
|
||||
* EVENT_RESOLUTION → handled automatically at end of round
|
||||
* GAME_OVER → endGame()
|
||||
*
|
||||
* All public methods that advance state return a boolean:
|
||||
* true = action accepted, state may have changed
|
||||
* false = action rejected, state unchanged (caller should log / notify client)
|
||||
*
|
||||
* NOTE: GameState must have EXTRA_DRAW added to the enum for this class to compile:
|
||||
* UNKNOWN, SETUP, TOTEM_PLACEMENT, ACTION_RESOLUTION, EXTRA_DRAW, EVENT_RESOLUTION, GAME_OVER
|
||||
*
|
||||
* NOTE: Tribe.endPoints() already computes end-game building bonuses internally via
|
||||
* buildingAbilitiesEndPoints(). Do NOT also call BuildingManager.endgameForSix() etc.
|
||||
* in endGame() — that would count those bonuses twice.
|
||||
*/
|
||||
public class Game {
|
||||
private static final Logger logger = LogManager.getLogger(Game.class);
|
||||
// -------------------------------------------------------------------------
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static final int TOTAL_ROUNDS = 10;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Core state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private final List<Player> players;
|
||||
private GameBoard gameBoard;
|
||||
private int round;
|
||||
private GameState state;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Phase tracking
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** How many players have placed their totem this round. */
|
||||
private int totemPlacedCount;
|
||||
|
||||
/** Index of the offering tile (central row) currently being resolved (left to right). */
|
||||
private int currentOfferingTileIndex;
|
||||
|
||||
/** Actions the current player still has to resolve. */
|
||||
private List<Symbol> pendingActions;
|
||||
|
||||
/**
|
||||
* Queue of players who own the EXTRA_DRAW building and still need to
|
||||
* make their extra-draw decision this round. Ordered by turn order.
|
||||
*/
|
||||
//TODO to check it should be a single player
|
||||
private final List<Player> extraDrawQueue;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public Game(List<Player> players) {
|
||||
this.players = new ArrayList<>(players);
|
||||
this.round = 0;
|
||||
this.state = GameState.SETUP;
|
||||
this.pendingActions = new ArrayList<>();
|
||||
this.extraDrawQueue = new ArrayList<>();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// SETUP
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Initialises a new game: loads the deck, builds the board, deals initial
|
||||
* food, and randomises the first turn order.
|
||||
*
|
||||
* Initial food (rulebook setup step 10):
|
||||
* Slot 1 -> 2 food, slots 2-3 -> 3 food, slots 4-5 -> 4 food.
|
||||
*/
|
||||
public void newGame(String cardsFilePath) {
|
||||
try {
|
||||
logger.info("STARTING NEW GAME : ", cardsFilePath);
|
||||
|
||||
CardDeck deck = new CardDeck();
|
||||
deck.setForNPlayer(cardsFilePath, players.size());
|
||||
|
||||
gameBoard = new GameBoard(Era.I, deck, players.size());
|
||||
gameBoard.initOfferingTiles(players.size());
|
||||
gameBoard.setupInitialRows(players.size());
|
||||
|
||||
gameBoard.getTurnTile().setInitialOrder(players);
|
||||
dealInitialFood();
|
||||
|
||||
round = 1;
|
||||
totemPlacedCount = 0;
|
||||
state = GameState.TOTEM_PLACEMENT;
|
||||
|
||||
logger.info("Game started! Round 1.");
|
||||
logTurnOrder();
|
||||
|
||||
} catch (LoadingCardsException e) {
|
||||
System.err.println("Fatal: could not load cards — " + e.getMessage());
|
||||
state = GameState.GAME_OVER;
|
||||
}
|
||||
}
|
||||
|
||||
private void dealInitialFood() {
|
||||
Player[] order = gameBoard.getTurnTile().getPositions();
|
||||
for (int i = 0; i < order.length; i++) {
|
||||
int food = (i == 0) ? 2 : (i <= 2) ? 3 : 4;
|
||||
order[i].addFood(food);
|
||||
logger.info(" " + order[i].getNickname() + " starts with " + food + " food.");
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PHASE 1 — TOTEM PLACEMENT
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Places the player's totem on an offering tile.
|
||||
*
|
||||
* Players must place in the order defined by the TurnTile (top to bottom).
|
||||
* If the wrong player calls this, it is rejected.
|
||||
*
|
||||
* @param player the player attempting to place
|
||||
* @param tileIndex 0-based index into the offering tile list
|
||||
* @return true if placement was accepted
|
||||
*/
|
||||
public boolean placeTotem(Player player, int tileIndex) {
|
||||
if (state != GameState.TOTEM_PLACEMENT) {
|
||||
logger.info("Error: not the totem-placement phase.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enforce turn order
|
||||
Player expected = gameBoard.getTurnTile().getPositions()[totemPlacedCount];
|
||||
if (expected != player) {
|
||||
logger.info("Error: it is " + expected.getNickname()
|
||||
+ "'s turn to place, not " + player.getNickname() + ".");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<OfferingTile> tiles = gameBoard.getOfferingTiles();
|
||||
if (tileIndex < 0 || tileIndex >= tiles.size()) {
|
||||
logger.info("Error: tile index " + tileIndex + " out of range.");
|
||||
return false;
|
||||
}
|
||||
|
||||
OfferingTile chosen = tiles.get(tileIndex);
|
||||
if (!gameBoard.placeTotem(player, chosen, gameBoard.getTurnTile())) {
|
||||
logger.info("Tile " + chosen.getLetter() + " is already occupied.");
|
||||
return false;
|
||||
}
|
||||
|
||||
totemPlacedCount++;
|
||||
logger.info("player {} TOTEM PLACED ON TILE {}", player.getNickname() , chosen.getLetter());
|
||||
|
||||
// + " (" + totemPlacedCount + "/" + players.size() + ").");
|
||||
|
||||
if (totemPlacedCount == players.size()) {
|
||||
logger.info("START ACTION RESOLUTION");
|
||||
startActionResolution();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PHASE 2 — ACTION RESOLUTION
|
||||
// =========================================================================
|
||||
|
||||
private void startActionResolution() {
|
||||
state = GameState.ACTION_RESOLUTION;
|
||||
currentOfferingTileIndex = 0;
|
||||
logger.info("All totems placed — resolving actions left to right.");
|
||||
loadNextPlayerActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans offering tiles left to right for the next occupied one, loads that
|
||||
* player's actions, and either resolves them automatically (food tile) or
|
||||
* waits for player input (card tiles).
|
||||
*
|
||||
* Calls onAllActionsResolved() when every tile has been processed.
|
||||
*/
|
||||
private void loadNextPlayerActions() {
|
||||
List<OfferingTile> tiles = gameBoard.getOfferingTiles();
|
||||
|
||||
while (currentOfferingTileIndex < tiles.size()) {
|
||||
OfferingTile tile = tiles.get(currentOfferingTileIndex);
|
||||
|
||||
if (tile.isEmpty()) {
|
||||
currentOfferingTileIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Player player = tile.getOccupant();
|
||||
pendingActions = new ArrayList<>(tile.getActions());
|
||||
logger.info(" PLAYER {} PENDING_ACTIONS {} ", player.getNickname() , pendingActions );
|
||||
// --- Food tile: resolve entirely without player input ---
|
||||
if (pendingActions.get(0) == Symbol.FOOD) {
|
||||
|
||||
int food = pendingActions.size();
|
||||
player.addFood(food);
|
||||
BuildingManager.bonusEndTurn(player, food); // BONUS_FOOD_ENDTURN building effect
|
||||
logger.info(player.getNickname() + " receives " + food
|
||||
+ " food from tile " + tile.getLetter() + ".");
|
||||
pendingActions.clear();
|
||||
finishPlayerTurn(player);
|
||||
currentOfferingTileIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Card tile: strip impossible actions first ---
|
||||
filterImpossibleActions(player);
|
||||
|
||||
if (pendingActions.isEmpty()) {
|
||||
// All actions were impossible (both rows empty)
|
||||
finishPlayerTurn(player);
|
||||
currentOfferingTileIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for the player to call resolveCardAction()
|
||||
logger.info("player {} pending ACTIONS {}" , player.getNickname() , pendingActions);
|
||||
// logger.info("It is " + player.getNickname() + "'s turn — actions: " + pendingActions);
|
||||
return;
|
||||
}
|
||||
|
||||
// Every tile has been processed
|
||||
onAllActionsResolved();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips actions that cannot be executed because the required row is empty.
|
||||
* Pure filter — no side effects beyond mutating pendingActions.
|
||||
*/
|
||||
private void filterImpossibleActions(Player player) {
|
||||
|
||||
Card ctop = gameBoard.getTopRow().stream().filter(s -> s instanceof CharacterCard || s instanceof BuildingCard).findFirst().orElse(null);
|
||||
Card cdown = gameBoard.getBottomRow().stream().filter(s -> s instanceof CharacterCard || s instanceof BuildingCard).findFirst().orElse(null);
|
||||
|
||||
boolean topEmpty = ctop == null;
|
||||
boolean bottomEmpty = cdown == null;
|
||||
|
||||
if (topEmpty) {
|
||||
long removed = pendingActions.stream().filter(s -> s == Symbol.UP).count();
|
||||
pendingActions.removeIf(s -> s == Symbol.UP);
|
||||
if (removed > 0)
|
||||
logger.info("{} LOSES ACTION UP" ,player.getNickname(), removed);
|
||||
|
||||
}
|
||||
if (bottomEmpty) {
|
||||
long removed = pendingActions.stream().filter(s -> s == Symbol.DOWN).count();
|
||||
pendingActions.removeIf(s -> s == Symbol.DOWN);
|
||||
if (removed > 0)
|
||||
logger.info("{} LOSES ACTION DOWN" ,player.getNickname(), removed);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves one card-pick action for the current player.
|
||||
* Works for both CharacterCards and BuildingCards.
|
||||
*
|
||||
* For BuildingCards: builder discount is applied and the player must have
|
||||
* enough food. If not, the card is put back and the call is rejected — the
|
||||
* player must choose a different card.
|
||||
*
|
||||
* @param player the acting player (must match the current tile occupant)
|
||||
* @param fromTop true = pick from the top row; false = from the bottom row
|
||||
* @param cardId the ID of the card to take
|
||||
* @return true if the action was accepted and consumed
|
||||
*/
|
||||
public ActionResult resolveCardAction(Player player, boolean fromTop, int cardId) {
|
||||
if (state != GameState.ACTION_RESOLUTION) {
|
||||
logger.info("Error: not in ACTION_RESOLUTION phase.");
|
||||
return ActionResult.failure("Error: not in ACTION_RESOLUTION phase.");
|
||||
|
||||
}
|
||||
|
||||
OfferingTile currentTile = gameBoard.getOfferingTiles().get(currentOfferingTileIndex);
|
||||
if (currentTile.getOccupant() != player) {
|
||||
logger.info("Error: it is not " + player.getNickname() + "'s turn to act.");
|
||||
return ActionResult.failure("Error: it is not " + player.getNickname() + "'s turn to act.");
|
||||
}
|
||||
|
||||
Symbol required = fromTop ? Symbol.UP : Symbol.DOWN;
|
||||
if (!pendingActions.contains(required)) {
|
||||
logger.info("Error: no " + required + " action available for "
|
||||
+ player.getNickname() + ".");
|
||||
return ActionResult.failure("Error: no " + required + " action available for "
|
||||
+ player.getNickname() + ".");
|
||||
}
|
||||
|
||||
// Find the card in the correct row
|
||||
Card card = fromTop
|
||||
? gameBoard.takeFromTopRow(cardId)
|
||||
: gameBoard.takeFromBottomRow(cardId);
|
||||
|
||||
if (card == null) {
|
||||
logger.info("Error: card " + cardId + " not found in the "
|
||||
+ (fromTop ? "top" : "bottom") + " row.");
|
||||
return ActionResult.failure("Error: card " + cardId + " not found in the "
|
||||
+ (fromTop ? "top" : "bottom") + " row.");
|
||||
}
|
||||
|
||||
// Event cards can never be taken by players
|
||||
if (card instanceof EventCard) {
|
||||
putCardBack(card, fromTop);
|
||||
logger.info("Error: Event cards cannot be taken.");
|
||||
return ActionResult.failure("Error: Event cards cannot be taken.");
|
||||
}
|
||||
|
||||
// Resolve the card
|
||||
if (card instanceof BuildingCard) {
|
||||
ActionResult result =resolveBuildingCard(player, (BuildingCard) card);
|
||||
if (!result.isSuccess()) {
|
||||
putCardBack(card, fromTop);
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
resolveCharacterCard(player, (CharacterCard) card);
|
||||
}
|
||||
|
||||
// Consume the action
|
||||
pendingActions.remove(required);
|
||||
logger.info("player {} TOOK CARD {} --> PEDNING ACTIONS {}",player.getNickname(), cardId, pendingActions);
|
||||
|
||||
|
||||
// A row may have become empty after this draw — re-check impossible actions
|
||||
if (!pendingActions.isEmpty()) {
|
||||
filterImpossibleActions(player);
|
||||
}
|
||||
|
||||
// If no more actions remain, finish this player's turn
|
||||
if (pendingActions.isEmpty()) {
|
||||
finishPlayerTurn(player);
|
||||
currentOfferingTileIndex++;
|
||||
loadNextPlayerActions();
|
||||
}
|
||||
|
||||
return ActionResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles building card acquisition.
|
||||
* Applies builder discount, checks affordability, deducts food, and
|
||||
* registers the building with BuildingManager.
|
||||
*
|
||||
* @return true if the acquisition succeeded
|
||||
*/
|
||||
private ActionResult resolveBuildingCard(Player player, BuildingCard building) {
|
||||
int discount = player.getPlayerTribe().buildersDiscount();
|
||||
int actualCost = Math.max(0, building.getCost() - discount);
|
||||
|
||||
if (player.getFoodTokens() < actualCost) {
|
||||
logger.info(player.getNickname() + " cannot afford building "
|
||||
+ building.getCardId() + " (cost after discount: " + actualCost
|
||||
+ ", has: " + player.getFoodTokens() + " food).");
|
||||
return ActionResult.failure(player.getNickname() + " cannot afford building "
|
||||
+ building.getCardId() + " (cost after discount: " + actualCost
|
||||
+ ", has: " + player.getFoodTokens() + " food).");
|
||||
}
|
||||
|
||||
player.removeFood(actualCost);
|
||||
player.getPlayerTribe().addBuilding(building);
|
||||
gameBoard.getBuildingManager().addActiveBuilding(building, player);
|
||||
|
||||
logger.info(player.getNickname() + " bought building "
|
||||
+ building.getCardId() + " for " + actualCost + " food.");
|
||||
return ActionResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles character card acquisition and triggers all immediate effects:
|
||||
*
|
||||
* Hunter with leg icon (iconValue > 0):
|
||||
* Take 1 food per Hunter currently in tribe (including this one).
|
||||
*
|
||||
* FOOD_FOR_SIX building:
|
||||
* Take 6 food if this draw completes a full set of all 6 character types.
|
||||
*
|
||||
* FOOD_PER_INVENTORS building:
|
||||
* Take 3 food if this Inventor forms a matching pair.
|
||||
*
|
||||
* The card is added to the tribe BEFORE effects fire so all counts include it.
|
||||
*/
|
||||
private void resolveCharacterCard(Player player, CharacterCard character) {
|
||||
player.getPlayerTribe().addCharacter(character);
|
||||
|
||||
// Hunter with leg icon: immediate food reward
|
||||
if (character.getCharacterType() == CharacterType.HUNTER
|
||||
&& character.getIconValue() > 0) {
|
||||
int food = player.getPlayerTribe().huntersNumber() * character.getIconValue();
|
||||
player.addFood(food);
|
||||
logger.info(player.getNickname() + "'s hunter (leg icon) grants +"
|
||||
+ food + " food.");
|
||||
}
|
||||
|
||||
// Building-triggered effects on character draw
|
||||
gameBoard.getBuildingManager().foodForSix(player, character);
|
||||
if (character.getCharacterType() == CharacterType.INVENTOR) {
|
||||
gameBoard.getBuildingManager().foodPerInventors(player, character);
|
||||
}
|
||||
|
||||
logger.info(player.getNickname() + " drew "
|
||||
+ character.getCharacterType() + " (id " + character.getCardId() + ").");
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current player as done with their tile actions.
|
||||
* Calls TurnTile.returnTotem(), which places them in the next available
|
||||
* slot and immediately applies the position food reward or penalty.
|
||||
*/
|
||||
private void finishPlayerTurn(Player player) {
|
||||
int slot = gameBoard.getTurnTile().returnTotem(player);
|
||||
OfferingTile off = gameBoard.getOfferingTile(player);
|
||||
logger.info("player {} leaving offering {} " ,player.getNickname(), off);
|
||||
if(off!=null) off.setOccupant(null);
|
||||
logger.info("player {} left offering {} slot {} " ,player.getNickname(), off, slot);
|
||||
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PHASE 2b — EXTRA DRAW (EXTRA_DRAW building effect)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Called when every offering tile has been resolved.
|
||||
* Checks if any player owns the EXTRA_DRAW building and, if so, enters
|
||||
* the EXTRA_DRAW phase. Otherwise proceeds directly to end of round.
|
||||
*
|
||||
* Rulebook appendix: "After resolving all actions (once all totems are back
|
||||
* on the TurnTile) and before the end-of-round phase, you may take 1
|
||||
* Character or Building card (paying its cost) from the top row."
|
||||
*/
|
||||
private void onAllActionsResolved() {
|
||||
logger.info("All actions resolved.");
|
||||
|
||||
extraDrawQueue.clear();
|
||||
// Iterate in turn order so extra draws happen in a consistent sequence
|
||||
for (Player p : gameBoard.getTurnTile().getPositions()) {
|
||||
if (p != null && gameBoard.getBuildingManager().extraDraw(p)) {
|
||||
extraDrawQueue.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (!extraDrawQueue.isEmpty()) {
|
||||
state = GameState.EXTRA_DRAW;
|
||||
System.out.println("Extra draw phase: "
|
||||
+ extraDrawQueue.get(0).getNickname() + " may draw first.");
|
||||
} else {
|
||||
endRound();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The player uses their EXTRA_DRAW building to take one card from the top row.
|
||||
* Building cards may also be taken, paying the cost after builder discounts.
|
||||
*
|
||||
* @param player the player taking the extra draw
|
||||
* @param cardId the ID of the card they want from the top row
|
||||
* @return true if the action was accepted
|
||||
*/
|
||||
public ActionResult extraDrawAction(Player player, int cardId) {
|
||||
if (state != GameState.EXTRA_DRAW) {
|
||||
System.out.println("Error: not in EXTRA_DRAW phase.");
|
||||
return ActionResult.failure("Error: not in EXTRA_DRAW phase.");
|
||||
}
|
||||
if (extraDrawQueue.isEmpty() || extraDrawQueue.get(0) != player) {
|
||||
System.out.println("Error: it is not " + player.getNickname()
|
||||
+ "'s extra draw turn.");
|
||||
return ActionResult.failure("Error: it is not " + player.getNickname()
|
||||
+ "'s extra draw turn.");
|
||||
}
|
||||
|
||||
Card card = gameBoard.takeFromTopRow(cardId);
|
||||
if (card == null) {
|
||||
System.out.println("Error: card " + cardId + " not found in top row.");
|
||||
return ActionResult.failure("Error: card " + cardId + " not found in top row.");
|
||||
}
|
||||
if (card instanceof EventCard) {
|
||||
gameBoard.getTopRow().add(card);
|
||||
System.out.println("Error: cannot take Event cards.");
|
||||
return ActionResult.failure("Error: cannot take Event cards.");
|
||||
}
|
||||
|
||||
if (card instanceof BuildingCard) {
|
||||
ActionResult result = resolveBuildingCard(player, (BuildingCard) card);
|
||||
if (!result.isSuccess()) {
|
||||
gameBoard.getTopRow().add(card);
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
resolveCharacterCard(player, (CharacterCard) card);
|
||||
}
|
||||
|
||||
advanceExtraDrawQueue();
|
||||
return ActionResult.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* The player declines to use their EXTRA_DRAW building this round.
|
||||
*
|
||||
* @return true if accepted
|
||||
*/
|
||||
public boolean skipExtraDraw(Player player) {
|
||||
if (state != GameState.EXTRA_DRAW) return false;
|
||||
if (extraDrawQueue.isEmpty() || extraDrawQueue.get(0) != player) return false;
|
||||
|
||||
System.out.println(player.getNickname() + " skips extra draw.");
|
||||
advanceExtraDrawQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void advanceExtraDrawQueue() {
|
||||
extraDrawQueue.remove(0);
|
||||
if (extraDrawQueue.isEmpty()) {
|
||||
endRound();
|
||||
} else {
|
||||
System.out.println("Next extra draw: " + extraDrawQueue.get(0).getNickname());
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// END OF ROUND
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Runs all end-of-round steps in rulebook order:
|
||||
*
|
||||
* Step 1 — Resolve Event cards.
|
||||
* Normal rounds: only bottom row events.
|
||||
* Round 10: events from BOTH rows; then GAME_OVER.
|
||||
* Step 2 — Advance rows: discard bottom non-buildings, shift top
|
||||
* non-buildings down, draw (numPlayers + 4) new cards.
|
||||
* Step 3 — If a new Era card was revealed in step 2, trigger
|
||||
* the Era transition (building row swap).
|
||||
* Step 4 — Start the next round.
|
||||
*/
|
||||
private void endRound() {
|
||||
state = GameState.EVENT_RESOLUTION;
|
||||
//notify( GameState.EVENT_RESOLUTION);
|
||||
|
||||
logger.info("--- End of round " + round + " ---");
|
||||
|
||||
boolean isFinalRound = (round == TOTAL_ROUNDS);
|
||||
|
||||
// Step 1: resolve events
|
||||
resolveVisibleEvents(isFinalRound);
|
||||
|
||||
if (isFinalRound) {
|
||||
state = GameState.GAME_OVER;
|
||||
notify( GameState.GAME_OVER, endGame());
|
||||
logger.info("Round 10 complete — game over!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: advance rows; returns true if a new Era card was revealed
|
||||
boolean eraChangeTriggered = gameBoard.advanceRows(players.size());
|
||||
|
||||
// Step 3: handle Era transition if needed
|
||||
if (eraChangeTriggered) {
|
||||
gameBoard.triggerEraChange(players.size());
|
||||
}
|
||||
|
||||
// Step 4: start next round
|
||||
startNewRound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves all visible Event cards in the correct order.
|
||||
*
|
||||
* Rules:
|
||||
* - Multiple events of different types: any order, Sustainment always last.
|
||||
* - Two events of the same type in the same round: resolve in Era order.
|
||||
* - Round 10: events from both rows must be resolved.
|
||||
*
|
||||
* @param bothRows true means events from both rows are included (round 10)
|
||||
*/
|
||||
private void resolveVisibleEvents(boolean bothRows) {
|
||||
List<EventCard> events = bothRows
|
||||
? gameBoard.getAllVisibleEvents() // already sorted by Era ordinal
|
||||
: gameBoard.getVisibleEvents(); // already sorted by Era ordinal
|
||||
|
||||
if (events.isEmpty()) {
|
||||
logger.info("No events to resolve this round.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sustainment must always be resolved last
|
||||
List<EventCard> others = new ArrayList<>();
|
||||
List<EventCard> sustainments = new ArrayList<>();
|
||||
for (EventCard e : events) {
|
||||
if (e.getEvent() == Event.SUSTAINMENT) sustainments.add(e);
|
||||
else others.add(e);
|
||||
}
|
||||
|
||||
for (EventCard e : others) resolveOneEvent(e);
|
||||
for (EventCard e : sustainments) resolveOneEvent(e);
|
||||
}
|
||||
|
||||
private void resolveOneEvent(EventCard event) {
|
||||
notify( GameState.EVENT_RESOLUTION, event.toString());
|
||||
logger.info("EVENT Resolving: " + event.getEvent() + " (Era " + event.getEra() + ")");
|
||||
EventsSolver.solveEvents(Collections.singletonList(event), players);
|
||||
}
|
||||
|
||||
private void startNewRound() {
|
||||
|
||||
round++;
|
||||
|
||||
notify( GameState.NEXT_ROUND, "Round="+ round);
|
||||
logger.info("startNewRound: {}", round );
|
||||
totemPlacedCount = 0;
|
||||
currentOfferingTileIndex = 0;
|
||||
pendingActions.clear();
|
||||
|
||||
gameBoard.clearOfferingTiles();
|
||||
gameBoard.getTurnTile().resetTrack();
|
||||
|
||||
// notify( GameState.TOTEM_PLACEMENT);
|
||||
state = GameState.TOTEM_PLACEMENT;
|
||||
logger.info("=== NEW Round " + round + " ===");
|
||||
logTurnOrder();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// END GAME
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Computes the final leaderboard and returns it as a formatted string.
|
||||
*
|
||||
* Final score = prestige points accumulated during play
|
||||
* + Tribe.endPoints() which covers: builder PP, inventor PP,
|
||||
* artist PP, building base PP, and building end-game abilities.
|
||||
*
|
||||
* IMPORTANT: do NOT call BuildingManager.endgameForSix() /
|
||||
* endgameBonusCharacter() / endgameBonusPoints() here.
|
||||
* Tribe.endPoints() already computes those bonuses. Calling both would
|
||||
* count them twice.
|
||||
*
|
||||
* Tie-breaking rule: most food wins. Further ties share the victory.
|
||||
*/
|
||||
public String endGame() {
|
||||
state = GameState.GAME_OVER;
|
||||
|
||||
List<Player> ranked = new ArrayList<>(players);
|
||||
ranked.sort((a, b) -> {
|
||||
int diff = totalScore(b) - totalScore(a);
|
||||
if (diff != 0) return diff;
|
||||
return b.getFoodTokens() - a.getFoodTokens();
|
||||
});
|
||||
|
||||
StringBuilder sb = new StringBuilder("=== FINAL LEADERBOARD ===\n");
|
||||
for (int i = 0; i < ranked.size(); i++) {
|
||||
Player p = ranked.get(i);
|
||||
sb.append(i + 1).append(". ")
|
||||
.append(p.getNickname())
|
||||
.append(" — ").append(totalScore(p)).append(" PP")
|
||||
.append(" (food: ").append(p.getFoodTokens()).append(")\n");
|
||||
}
|
||||
|
||||
Player winner = ranked.get(0);
|
||||
sb.append("\nWINNER: ")
|
||||
.append(winner.getNickname().toUpperCase())
|
||||
.append(" with ").append(totalScore(winner)).append(" PP!");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Prestige points accumulated during play + final scoring from the Tribe engine. */
|
||||
private int totalScore(Player p) {
|
||||
return p.getPrestigePoints() + p.getPlayerTribe().endPoints();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HELPERS
|
||||
// =========================================================================
|
||||
|
||||
private void putCardBack(Card card, boolean wasTopRow) {
|
||||
if (wasTopRow) gameBoard.getTopRow().add(card);
|
||||
else gameBoard.getBottomRow().add(card);
|
||||
}
|
||||
|
||||
private void logTurnOrder() {
|
||||
Player[] order = gameBoard.getTurnTile().getPositions();
|
||||
StringBuilder sb = new StringBuilder("Turn order: ");
|
||||
for (int i = 0; i < order.length; i++) {
|
||||
if (order[i] != null)
|
||||
sb.append(i + 1).append(". ").append(order[i].getNickname()).append(" ");
|
||||
}
|
||||
logger.info("logTurnOrder: " + sb);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GETTERS
|
||||
// =========================================================================
|
||||
|
||||
public List<Player> getPlayers() { return Collections.unmodifiableList(players); }
|
||||
public GameBoard getGameBoard() { return gameBoard; }
|
||||
public int getRound() { return round; }
|
||||
public GameState getState() { return state; }
|
||||
public void setState(GameState s) { this.state = s; }
|
||||
public List<Symbol> getPendingActions() { return Collections.unmodifiableList(pendingActions); }
|
||||
|
||||
/**
|
||||
* Returns the player currently expected to act:
|
||||
* TOTEM_PLACEMENT — the next player who should place their totem
|
||||
* ACTION_RESOLUTION — the player currently resolving tile actions
|
||||
* EXTRA_DRAW — the next player to make their extra-draw decision
|
||||
*/
|
||||
public Player getCurrentPlayer() {
|
||||
switch (state) {
|
||||
case TOTEM_PLACEMENT:
|
||||
if (totemPlacedCount < players.size())
|
||||
return gameBoard.getTurnTile().getPositions()[totemPlacedCount];
|
||||
return null;
|
||||
case ACTION_RESOLUTION:
|
||||
List<OfferingTile> tiles = gameBoard.getOfferingTiles();
|
||||
if (currentOfferingTileIndex < tiles.size())
|
||||
return tiles.get(currentOfferingTileIndex).getOccupant();
|
||||
return null;
|
||||
case EXTRA_DRAW:
|
||||
return extraDrawQueue.isEmpty() ? null : extraDrawQueue.get(0);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Game{" +
|
||||
" state=" + state +
|
||||
",\n round=" + round +
|
||||
",\n totemPlacedCount=" + totemPlacedCount +
|
||||
",\n currentOfferingTileIndex=" + currentOfferingTileIndex +
|
||||
",\n pendingActions=" + pendingActions +
|
||||
",\n extraDrawQueue=" + extraDrawQueue +
|
||||
" \n GameBoard=" + gameBoard +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
public record GameEventNotification(GameState event, String message) {}
|
||||
|
||||
public interface GameEventListener {
|
||||
void onEvent(GameEventNotification notification);
|
||||
}
|
||||
|
||||
// Campo privato
|
||||
private GameEventListener eventListener;
|
||||
|
||||
// Setter per il controller FX
|
||||
public void setEventListener(GameEventListener listener) {
|
||||
this.eventListener = listener;
|
||||
}
|
||||
|
||||
// Metodi privati di notifica
|
||||
private void notify(GameState event, String message) {
|
||||
if (eventListener != null)
|
||||
eventListener.onEvent(new GameEventNotification(event, message));
|
||||
}
|
||||
|
||||
private void notify(GameState event) {
|
||||
notify(event, null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
68
src/main/java/Server/Automaton/GameState.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package Server.Automaton;
|
||||
|
||||
public enum GameState implements Comparable<GameState> {
|
||||
UNKNOWN,
|
||||
SETUP,
|
||||
NEXT_ROUND,
|
||||
TOTEM_PLACEMENT,
|
||||
ACTION_RESOLUTION,
|
||||
END_ACTION,
|
||||
EXTRA_DRAW,
|
||||
EVENT_RESOLUTION,
|
||||
GAME_OVER;
|
||||
|
||||
GameState next(int turn){
|
||||
switch (this) {
|
||||
case SETUP:
|
||||
return SETUP;
|
||||
case NEXT_ROUND:
|
||||
return NEXT_ROUND;
|
||||
case TOTEM_PLACEMENT:
|
||||
return ACTION_RESOLUTION;
|
||||
case ACTION_RESOLUTION:
|
||||
return END_ACTION;
|
||||
case EXTRA_DRAW:
|
||||
return EXTRA_DRAW;
|
||||
case END_ACTION:
|
||||
if(turn != 10) return GAME_OVER;
|
||||
return TOTEM_PLACEMENT;
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static GameState fromString(String s){
|
||||
return switch (s.toUpperCase().charAt(0)) {
|
||||
case 'S' -> SETUP;
|
||||
case 'T' -> TOTEM_PLACEMENT;
|
||||
case 'A' -> ACTION_RESOLUTION;
|
||||
case 'E' -> END_ACTION;
|
||||
case 'X' -> EXTRA_DRAW;
|
||||
case 'G' -> GAME_OVER;
|
||||
case 'N' -> NEXT_ROUND;
|
||||
default -> UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (this) {
|
||||
case SETUP:
|
||||
return "SETUP";
|
||||
case TOTEM_PLACEMENT:
|
||||
return "TOTEM_PLACEMENT";
|
||||
case ACTION_RESOLUTION:
|
||||
return "ACTION_RESOLUTION";
|
||||
case END_ACTION:
|
||||
return "END_ACTION";
|
||||
case EVENT_RESOLUTION:
|
||||
return "EVENT_RESOLUTION";
|
||||
case NEXT_ROUND:
|
||||
return "NEXT_ROUND";
|
||||
case EXTRA_DRAW:
|
||||
return "EXTRA_DRAW";
|
||||
case GAME_OVER:
|
||||
return "GAME_OVER";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/main/java/Server/BuildingManager.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package Server;
|
||||
|
||||
import Server.Cards.BuildingCard;
|
||||
import Server.Cards.CharacterCard;
|
||||
import Server.Cards.CharacterType;
|
||||
import Server.Cards.Trigger;
|
||||
import Server.Utils.EventsManagerException;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class BuildingManager {
|
||||
private static List<BuildingCard> activeBuildings;
|
||||
|
||||
public BuildingManager() {
|
||||
activeBuildings = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addActiveBuilding (BuildingCard buildingCard, Player player) {
|
||||
buildingCard.setOwner(player);
|
||||
activeBuildings.add(buildingCard);
|
||||
}
|
||||
|
||||
public static BuildingCard playerHasBuilding(Player player, Trigger trigger) {
|
||||
|
||||
for (BuildingCard buildingCard : activeBuildings) {
|
||||
if(buildingCard.getAbilityTrigger() == trigger && buildingCard.getOwner() == player) {
|
||||
return buildingCard;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int numberOfTargets(Player player, List<BuildingCard> buildingCards) {
|
||||
int numCard = 0;
|
||||
for (BuildingCard buildingCard : buildingCards) {
|
||||
|
||||
List<CharacterCard> playerCharacters = player.getPlayerTribe().getCharacters();
|
||||
|
||||
for (CharacterCard card : playerCharacters) {
|
||||
if (card.getCharacterType() == buildingCard.getEffectTarget()) {
|
||||
numCard++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return numCard;
|
||||
}
|
||||
|
||||
public static List<BuildingCard> playerHasVariousBuildings(Player player, Trigger trigger) {
|
||||
|
||||
List<BuildingCard> buildingCards = new ArrayList<>();
|
||||
|
||||
for (BuildingCard buildingCard : activeBuildings) {
|
||||
if(buildingCard.getAbilityTrigger() == trigger && buildingCard.getOwner() == player) {
|
||||
buildingCards.add(buildingCard);
|
||||
}
|
||||
}
|
||||
return buildingCards;
|
||||
}
|
||||
|
||||
//When a player draw a characterCard, if said card complete a set of 6
|
||||
//different character type, give the player 6 food
|
||||
|
||||
public static void foodForSix(Player player, CharacterCard characterCard) {
|
||||
if(playerHasBuilding(player, Trigger.FOOD_FOR_SIX) == null){
|
||||
return;
|
||||
}
|
||||
|
||||
List<CharacterCard> playerTribe = player.getPlayerTribe().getCharacters();
|
||||
int [] preDraw = new int[CharacterType.values().length];
|
||||
int [] postDraw = new int[CharacterType.values().length];
|
||||
|
||||
for(CharacterCard card: playerTribe){
|
||||
preDraw[card.getCharacterType().ordinal()]++;
|
||||
postDraw[card.getCharacterType().ordinal()]++;
|
||||
}
|
||||
|
||||
postDraw[characterCard.getCharacterType().ordinal()]++;
|
||||
|
||||
int minPre = Arrays.stream(preDraw).min().getAsInt();
|
||||
int minPost = Arrays.stream(postDraw).min().getAsInt();
|
||||
|
||||
if(minPre < minPost){
|
||||
player.addFood(6);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//the method return the discount received for the player based on the cards he owns
|
||||
//the card providing the discount are indicated on the BuildingCard
|
||||
public static int sustainDiscount(Player player) {
|
||||
List<BuildingCard> buildingCards = playerHasVariousBuildings(player, Trigger.SUSTAIN_DISCOUNT);
|
||||
if(buildingCards.isEmpty()){
|
||||
return 0;
|
||||
}
|
||||
return numberOfTargets(player, buildingCards);
|
||||
}
|
||||
|
||||
//if the player is going to lose during the shamanicRitual, the loss are nullif
|
||||
public static boolean shamanNoLoss(Player player) {
|
||||
return playerHasBuilding(player, Trigger.SHAMAN_NO_LOSS) != null;
|
||||
}
|
||||
|
||||
//check if the player has a SHAMAN_DOUBLE_POINTS card
|
||||
public static boolean shamanDoublePoints(Player player) {
|
||||
return playerHasBuilding(player, Trigger.SHAMAN_DOUBLE_POINTS) != null;
|
||||
}
|
||||
|
||||
//if the player has a BONUS_END_TURN buildingCard the player gain additional food
|
||||
//after placing his totem on a tile whit food
|
||||
public static int bonusEndTurn(Player player, int positionReward) {
|
||||
if (playerHasBuilding(player, Trigger.BONUS_FOOD_ENDTURN) == null) return -1;
|
||||
|
||||
if (positionReward >= 1){
|
||||
player.addFood(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//if the player has a BuildingCard whit FOOD_PER_INVENTORS, and he
|
||||
//draws a copy of an inventor that is already in his tribe he gains 3 food
|
||||
public static void foodPerInventors(Player player, CharacterCard characterCard) {
|
||||
if(playerHasBuilding(player, Trigger.FOOD_PER_INVENTORS) == null){
|
||||
return;
|
||||
}
|
||||
|
||||
if(characterCard.getCharacterType()!= CharacterType.INVENTOR){
|
||||
throw new EventsManagerException("Invalid character type");
|
||||
}
|
||||
|
||||
List<CharacterCard> playerTribe = player.getPlayerTribe().getCharacters();
|
||||
int numberOfInventors = 0;
|
||||
|
||||
for(CharacterCard card: playerTribe){
|
||||
if(card.getIconValue() == characterCard.getIconValue() && card.getCharacterType() == CharacterType.INVENTOR){
|
||||
numberOfInventors++;
|
||||
}
|
||||
}
|
||||
|
||||
if(numberOfInventors%2 == 1)
|
||||
player.addFood(3);
|
||||
}
|
||||
|
||||
//if the player has a SHAMAN_BONUS building the function return 3, else return 0
|
||||
public static int shamanBonus(Player player) {
|
||||
if (BuildingManager.playerHasBuilding(player, Trigger.SHAMAN_BONUS) == null) return 0;
|
||||
return 3;
|
||||
}
|
||||
|
||||
//return if HUNT_BONUS is active for the player
|
||||
public static boolean hunterBonus(Player player) {
|
||||
return playerHasBuilding(player, Trigger.HUNT_BONUS) != null;
|
||||
}
|
||||
|
||||
//return if ENDGAME_BUILDER_BONUS is active for the player
|
||||
public static boolean endgameBuilderBonus(Player player) {
|
||||
return playerHasBuilding(player, Trigger.ENDGAME_BUILDER_BONUS) != null;
|
||||
}
|
||||
|
||||
//return the amount of food gained if the player has a BuildingCard with
|
||||
//PAINTING_FOOD_BONUS, a unit for each ARTIST in his tribe
|
||||
public static void paintingFoodBonus(Player player) {
|
||||
if(playerHasBuilding(player, Trigger.PAINTING_FOOD_BONUS) == null) return;
|
||||
|
||||
player.addFood(player.getPlayerTribe().artistsNumber());
|
||||
}
|
||||
|
||||
//return the amount of buildingPoint if the player has a BuildingCard with
|
||||
//ENDGAME_FOR_SIX, a unit for each set of 6 character of different Type
|
||||
public static void endgameForSix(Player player) {
|
||||
if(playerHasBuilding(player, Trigger.ENDGAME_FOR_SIX) == null) return;
|
||||
|
||||
List<CharacterCard> playerTribe = player.getPlayerTribe().getCharacters();
|
||||
|
||||
int [] typeDuplicates = new int[CharacterType.values().length];
|
||||
|
||||
for(CharacterCard card: playerTribe){
|
||||
typeDuplicates[card.getCharacterType().ordinal()]++;
|
||||
}
|
||||
|
||||
int minCharacterType = Arrays.stream(typeDuplicates).min().getAsInt();
|
||||
|
||||
player.addPrestigePoints(minCharacterType*6);
|
||||
}
|
||||
|
||||
//add the bonus prestigePoints due to ENDGAME_BONUS_CHARACTER, the amount is based
|
||||
//on the CharacterType indicated on the BuildingCards, 3 for each character of the same type
|
||||
//A plater can own different BuildingCards with the same effect
|
||||
public static void endgameBonusCharacter(Player player) {
|
||||
List<BuildingCard> buildingCards = playerHasVariousBuildings(player, Trigger.ENDGAME_BONUS_CHARACTER);
|
||||
if(buildingCards.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
player.addPrestigePoints(numberOfTargets(player, buildingCards)* 3);
|
||||
}
|
||||
|
||||
//return if EXTRA_DRAW is active for the player
|
||||
public static boolean extraDraw(Player player) {
|
||||
return playerHasBuilding(player, Trigger.EXTRA_DRAW) != null;
|
||||
}
|
||||
|
||||
//return if the player has a BuildingCard with
|
||||
//ENDGAME_BONUS_POINTS, then add 25 points if he has
|
||||
public static void endgameBonusPoints(Player player) {
|
||||
if(playerHasBuilding(player, Trigger.ENDGAME_BONUS_POINTS) == null) return;
|
||||
|
||||
player.addPrestigePoints(25);
|
||||
}
|
||||
}
|
||||
48
src/main/java/Server/BuildingRules.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package Server;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class BuildingRules {
|
||||
private static final Map<Integer, Map<Era, Integer>> RULES = new HashMap<>();
|
||||
static {
|
||||
// 2 players
|
||||
Map<Era, Integer> p2 = new EnumMap<>(Era.class);
|
||||
p2.put(Era.I, 1);
|
||||
p2.put(Era.II, 2);
|
||||
p2.put(Era.III, 3);
|
||||
|
||||
// 3 players
|
||||
Map<Era, Integer> p3 = new EnumMap<>(Era.class);
|
||||
p3.put(Era.I, 2);
|
||||
p3.put(Era.II, 2);
|
||||
p3.put(Era.III, 4);
|
||||
|
||||
// 4 players
|
||||
Map<Era, Integer> p4 = new EnumMap<>(Era.class);
|
||||
p4.put(Era.I, 2);
|
||||
p4.put(Era.II, 3);
|
||||
p4.put(Era.III, 4);
|
||||
|
||||
// 5 players
|
||||
Map<Era, Integer> p5 = new EnumMap<>(Era.class);
|
||||
p4.put(Era.I, 2);
|
||||
p4.put(Era.II, 3);
|
||||
p4.put(Era.III, 5);
|
||||
|
||||
// insert into main map
|
||||
RULES.put(2, p2);
|
||||
RULES.put(3, p3);
|
||||
RULES.put(4, p4);
|
||||
RULES.put(5, p5);
|
||||
}
|
||||
|
||||
public static int getBuildingCards(int players, Era era) {
|
||||
Map<Era, Integer> eraMap = RULES.get(players);
|
||||
if (eraMap!=null){
|
||||
Integer value = eraMap.get(era);
|
||||
if (value!=null) return value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
84
src/main/java/Server/Cards/BuildingCard.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package Server.Cards;
|
||||
|
||||
import Server.Era;
|
||||
import Server.Player;
|
||||
|
||||
public class BuildingCard extends Card{
|
||||
private final int cost;
|
||||
private final int endPP;
|
||||
private final Trigger abilityTrigger;
|
||||
private final CharacterType effectTarget;
|
||||
private Player owner;
|
||||
|
||||
public BuildingCard(int cardId, int forMinPlayer, Era era, int cost, int endPP, Trigger abilityTrigger,CharacterType characterType, Player owner) {
|
||||
super(cardId, forMinPlayer, era);
|
||||
this.cost = cost;
|
||||
this.endPP = endPP;
|
||||
this.abilityTrigger = abilityTrigger;
|
||||
this.effectTarget = characterType;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public BuildingCard(int cardId, int forMinPlayer, Era era, int cost, int endPP, Trigger abilityTrigger, CharacterType characterType) {
|
||||
super(cardId, forMinPlayer, era);
|
||||
this.cost = cost;
|
||||
this.endPP = endPP;
|
||||
this.abilityTrigger = abilityTrigger;
|
||||
this.effectTarget = characterType;
|
||||
this.owner = null;
|
||||
}
|
||||
|
||||
public BuildingCard(int cardId, int forMinPlayer, Era era, int cost, int endPP, Trigger abilityTrigger) {
|
||||
super(cardId, forMinPlayer, era);
|
||||
this.cost = cost;
|
||||
this.endPP = endPP;
|
||||
this.abilityTrigger = abilityTrigger;
|
||||
this.effectTarget = null;
|
||||
this.owner = null;
|
||||
}
|
||||
|
||||
public void setOwner(Player owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public int getEndPP() {
|
||||
return endPP;
|
||||
}
|
||||
|
||||
public Trigger getAbilityTrigger() {
|
||||
return abilityTrigger;
|
||||
}
|
||||
|
||||
public Player getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public CharacterType getEffectTarget() {
|
||||
return effectTarget;
|
||||
}
|
||||
|
||||
public static BuildingCard parsRow(String row) {
|
||||
String cleanRow = row.trim();
|
||||
String[] values = cleanRow.split(";");
|
||||
|
||||
int cardId = Integer.parseInt(values[1]);
|
||||
int forMinPlayer = Integer.parseInt(values[2]);
|
||||
Era era = Era.valueOf(values[3]);
|
||||
int cost = Integer.parseInt(values[4]);
|
||||
int endPP = Integer.parseInt(values[5]);
|
||||
Trigger abilityTrigger = Trigger.valueOf(values[6]);
|
||||
/* MANCA LA COLONNA 8 nel CSV
|
||||
if(abilityTrigger == Trigger.SUSTAIN_DISCOUNT || abilityTrigger == Trigger.ENDGAME_BONUS_CHARACTER){
|
||||
CharacterType effectTarget = CharacterType.valueOf(values[7]);
|
||||
return new BuildingCard(cardId, forMinPlayer, era, cost, endPP, abilityTrigger, effectTarget);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
return new BuildingCard(cardId, forMinPlayer, era, cost, endPP, abilityTrigger);
|
||||
}
|
||||
}
|
||||
35
src/main/java/Server/Cards/Card.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package Server.Cards;
|
||||
|
||||
import Server.Era;
|
||||
|
||||
public abstract class Card {
|
||||
private final int cardId;
|
||||
private final int forMinPlayer;
|
||||
private final Era era;
|
||||
|
||||
public Card(int cardId, int forMinPlayer, Era era) {
|
||||
this.cardId = cardId;
|
||||
this.forMinPlayer = forMinPlayer;
|
||||
this.era = era;
|
||||
}
|
||||
|
||||
public int getCardId() {
|
||||
return cardId;
|
||||
}
|
||||
|
||||
public Era getEra() {
|
||||
return era;
|
||||
}
|
||||
|
||||
public int getForMinPlayer() {
|
||||
return forMinPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Card{" +
|
||||
"cardId=" + cardId +
|
||||
", era=" + era +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
131
src/main/java/Server/Cards/CardDeck.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package Server.Cards;
|
||||
|
||||
import Server.Automaton.Game;
|
||||
import Server.Era;
|
||||
import Server.Utils.LoadingCardsException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CardDeck {
|
||||
private List<Card> tribeDeck;
|
||||
private Map<Era, List<Card>> buildingDeck;
|
||||
private static final Logger logger = LogManager.getLogger(CardDeck.class);
|
||||
|
||||
public List<Card> getTribeDeck() {
|
||||
return tribeDeck;
|
||||
}
|
||||
|
||||
public List<Card> getBuildingDeck(Era era) {
|
||||
return buildingDeck.get(era);
|
||||
}
|
||||
|
||||
public void setForNPlayer(String path, int n) throws LoadingCardsException {
|
||||
|
||||
List<Card> tribe = new ArrayList<>();
|
||||
List<Card> building = new ArrayList<>();
|
||||
|
||||
try {
|
||||
List<String> rows = Files.readAllLines(Path.of(path));
|
||||
int p=0;
|
||||
for (String row : rows) {
|
||||
String cleanRow = row.trim();
|
||||
String[] fields = cleanRow.split(";");
|
||||
logger.info((p++) + " ROW " +row);
|
||||
switch (fields[0]) {
|
||||
case "C":
|
||||
tribe.add(CharacterCard.parsRow(row));
|
||||
break;
|
||||
case "E":
|
||||
tribe.add(EventCard.parsRow(row));
|
||||
break;
|
||||
case "B":
|
||||
building.add(BuildingCard.parsRow(row));
|
||||
break;
|
||||
default:
|
||||
throw new LoadingCardsException("Content not supported");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error(e);
|
||||
throw new LoadingCardsException("file not found");
|
||||
}
|
||||
|
||||
building = CardDeck.shuffle(building);
|
||||
|
||||
this.tribeDeck = new ArrayList<>();
|
||||
|
||||
for(Card card : tribe) {
|
||||
if (card.getForMinPlayer() <= n)
|
||||
this.tribeDeck.add(card);
|
||||
}
|
||||
this.tribeDeck = CardDeck.shuffle(this.tribeDeck);
|
||||
|
||||
// groups the building cards by era
|
||||
this.buildingDeck = building.stream().collect(Collectors.groupingBy(Card::getEra));
|
||||
}
|
||||
|
||||
public List<Card> drawTribe(int n) {
|
||||
List<Card> cards = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if(tribeDeck.isEmpty()) break;
|
||||
cards.add(tribeDeck.getFirst());
|
||||
tribeDeck.remove(tribeDeck.getFirst());
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
public List<Card> drawBuilding(int n, Era era) {
|
||||
List<Card> cards = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
if(buildingDeck.isEmpty()) break;
|
||||
Card card = buildingDeck.get(era).getFirst();
|
||||
cards.add(card);
|
||||
buildingDeck.remove(card);
|
||||
}
|
||||
return cards;
|
||||
}
|
||||
|
||||
|
||||
public static List<Card> shuffle(List<Card> cards) {
|
||||
List<Card> shuffled = new ArrayList<>();
|
||||
|
||||
for(Era e: Era.values()) {
|
||||
List<Card> cardsToShuffle = new ArrayList<>();
|
||||
for(Card card : cards) {
|
||||
if(card.getEra() == e){
|
||||
cardsToShuffle.add(card);
|
||||
}
|
||||
}
|
||||
Collections.shuffle(cardsToShuffle);
|
||||
shuffled.addAll(cardsToShuffle);
|
||||
}
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
public Card getFirstCardForCover() {
|
||||
if(tribeDeck.isEmpty()) return null;
|
||||
Card card = tribeDeck.getFirst();
|
||||
return card;
|
||||
}
|
||||
|
||||
public Card drawTribeOne() {
|
||||
if(tribeDeck.isEmpty()) return null;
|
||||
Card card = tribeDeck.getFirst();
|
||||
tribeDeck.remove(card);
|
||||
return card;
|
||||
}
|
||||
|
||||
public Card drawBuildingOne(Era era) {
|
||||
if(buildingDeck.isEmpty()) return null;
|
||||
Card card = buildingDeck.get(era).getFirst();
|
||||
buildingDeck.get(era).remove(card);
|
||||
return card;
|
||||
}
|
||||
}
|
||||
58
src/main/java/Server/Cards/CharacterCard.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package Server.Cards;
|
||||
|
||||
import Server.Era;
|
||||
import Server.Utils.LoadingCardsException;
|
||||
|
||||
public class CharacterCard extends Card{
|
||||
private final CharacterType characterType;
|
||||
private final int iconValue;
|
||||
private final int prestigePoints;
|
||||
|
||||
public CharacterCard(int cardId, int forMinPlayer, Era era, CharacterType characterType, int iconValue, int prestigePoints) {
|
||||
super(cardId, forMinPlayer, era);
|
||||
this.characterType = characterType;
|
||||
this.iconValue = iconValue;
|
||||
this.prestigePoints = prestigePoints;
|
||||
}
|
||||
|
||||
public CharacterType getCharacterType() {
|
||||
return characterType;
|
||||
}
|
||||
|
||||
public int getIconValue() {
|
||||
return iconValue;
|
||||
}
|
||||
|
||||
public int getPrestigePoints() {
|
||||
return prestigePoints;
|
||||
}
|
||||
|
||||
public static CharacterCard parsRow(String row){
|
||||
|
||||
String cleanRow = row.trim();
|
||||
String[] values = cleanRow.split(";");
|
||||
|
||||
if(!values[0].equals("C")){
|
||||
throw new LoadingCardsException("Not a character card");
|
||||
}
|
||||
|
||||
int cardId = Integer.parseInt(values[1]);
|
||||
int forMinPlayer = Integer.parseInt(values[2]);
|
||||
Era era = Era.valueOf(values[3]);
|
||||
CharacterType characterType = CharacterType.valueOf(values[4]);
|
||||
int iconValue = Integer.parseInt(values[5]);
|
||||
int prestigePoints = Integer.parseInt(values[6]);
|
||||
|
||||
return new CharacterCard(cardId, forMinPlayer, era, characterType, iconValue, prestigePoints);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CharacterCard{" +
|
||||
"characterType=" + characterType +
|
||||
", value=" + iconValue +
|
||||
", points=" + prestigePoints +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
10
src/main/java/Server/Cards/CharacterType.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package Server.Cards;
|
||||
|
||||
public enum CharacterType {
|
||||
INVENTOR,
|
||||
HUNTER,
|
||||
GATHERER,
|
||||
SHAMAN,
|
||||
ARTIST,
|
||||
BUILDER
|
||||
}
|
||||
8
src/main/java/Server/Cards/Event.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package Server.Cards;
|
||||
|
||||
public enum Event {
|
||||
SUSTAINMENT,
|
||||
HUNT,
|
||||
SHAMANIC_RITUAL,
|
||||
CAVE_PAINTINGS
|
||||
}
|
||||
55
src/main/java/Server/Cards/EventCard.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package Server.Cards;
|
||||
|
||||
import Server.Era;
|
||||
import Server.Utils.LoadingCardsException;
|
||||
|
||||
public class EventCard extends Card {
|
||||
private final Event event;
|
||||
private final int firstValue;
|
||||
private final int secondValue;
|
||||
|
||||
public EventCard(int cardId, int forMinPlayer, Era era, Event event, int firstValue, int secondValue) {
|
||||
super(cardId, forMinPlayer, era);
|
||||
this.event = event;
|
||||
this.firstValue = firstValue;
|
||||
this.secondValue = secondValue;
|
||||
}
|
||||
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
public int getFirstValue() {
|
||||
return firstValue;
|
||||
}
|
||||
|
||||
public int getSecondValue() {
|
||||
return secondValue;
|
||||
}
|
||||
|
||||
public static EventCard parsRow(String row){
|
||||
String cleanRow = row.trim();
|
||||
String[] values = cleanRow.split(";");
|
||||
|
||||
if (!values[0].equals("E")) {
|
||||
throw new LoadingCardsException("Not an EventCard");
|
||||
}
|
||||
|
||||
int cardId = Integer.parseInt(values[1]);
|
||||
int forMinPlayer = Integer.parseInt(values[2]);
|
||||
Era era = Era.valueOf(values[3]);
|
||||
Event event = Event.valueOf(values[4]);
|
||||
int firstValue = Integer.parseInt(values[5]);
|
||||
int secondValue = Integer.parseInt(values[6]);
|
||||
|
||||
return new EventCard(cardId, forMinPlayer, era, event, firstValue, secondValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventCard{" +
|
||||
"event=" + event +
|
||||
", firstValue=" + firstValue +
|
||||
", secondValue=" + secondValue +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
18
src/main/java/Server/Cards/Trigger.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package Server.Cards;
|
||||
|
||||
public enum Trigger {
|
||||
FOOD_FOR_SIX,
|
||||
SUSTAIN_DISCOUNT,
|
||||
SHAMAN_NO_LOSS,
|
||||
BONUS_FOOD_ENDTURN,
|
||||
FOOD_PER_INVENTORS,
|
||||
SHAMAN_BONUS,
|
||||
SHAMAN_DOUBLE_POINTS,
|
||||
HUNT_BONUS,
|
||||
ENDGAME_BUILDER_BONUS,
|
||||
PAINTING_FOOD_BONUS,
|
||||
ENDGAME_FOR_SIX,
|
||||
ENDGAME_BONUS_CHARACTER,
|
||||
EXTRA_DRAW,
|
||||
ENDGAME_BONUS_POINTS
|
||||
}
|
||||
684
src/main/java/Server/DeckGridAppFX.java
Normal file
@@ -0,0 +1,684 @@
|
||||
package Server;
|
||||
|
||||
import Server.Automaton.ActionResult;
|
||||
import Server.Automaton.Game;
|
||||
import Server.Cards.*;
|
||||
import Server.Utils.GameException;
|
||||
import javafx.animation.*;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DeckGridAppFX extends Application {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(DeckGridAppFX.class);
|
||||
private static final int IMG_HEIGHT=200;
|
||||
private PDDocument documentFront;
|
||||
private PDDocument documentBack;
|
||||
private PDFRenderer pdfRendererFront;
|
||||
private PDFRenderer pdfRendererBack;
|
||||
private int totalCards = 0;
|
||||
|
||||
// Contenitori Layout
|
||||
private HBox topMenu;
|
||||
private HBox topRow;
|
||||
private HBox centerRow;
|
||||
private HBox bottomRow;
|
||||
private HBox playersArea; // NUOVO: Area per i giocatori
|
||||
private Button btnRefresh;
|
||||
private Button btnTop;
|
||||
private Button btnBottom;
|
||||
private Button btnChooseOffering;
|
||||
|
||||
private Button btnMsg;
|
||||
// --------------------------------
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
|
||||
List<Player> players = new ArrayList<>();
|
||||
//players.add(new Player("Yellow", TotemColor.YELLOW));
|
||||
players.add(new Player("Blue", TotemColor.BLUE));
|
||||
//players.add(new Player("Purple", TotemColor.PURPLE));
|
||||
players.add(new Player("Red", TotemColor.RED));
|
||||
|
||||
//players.add(new Player("Green", TotemColor.GREEN));
|
||||
|
||||
Game game = new Game(players);
|
||||
String fileCards="/home/lorenzo/dev/Mesos2/src/main/resources/files/cards.csv";
|
||||
String fileCardsImgFront="/home/lorenzo/dev/Mesos2/src/main/resources/files/Cards_total_front_PROMO.pdf";
|
||||
String fileCardsImgCover="/home/lorenzo/dev/Mesos2/src/main/resources/files/Cards_total_back_PROMO.pdf";
|
||||
|
||||
try {
|
||||
|
||||
File fileFront = new File(fileCardsImgFront);
|
||||
File fileBack = new File(fileCardsImgCover);
|
||||
|
||||
documentFront = PDDocument.load(fileFront);
|
||||
pdfRendererFront = new PDFRenderer(documentFront);
|
||||
documentBack = PDDocument.load(fileBack);
|
||||
pdfRendererBack = new PDFRenderer(documentBack);
|
||||
|
||||
} catch ( IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
game.newGame(fileCards);
|
||||
|
||||
game.setEventListener(notification -> {
|
||||
Platform.runLater(() -> {
|
||||
drawGameState(game);
|
||||
switch (notification.event()) {
|
||||
case TOTEM_PLACEMENT-> showPopup("TOTEM PLACEMENT\n" + (notification.message()!=null?notification.message():""));
|
||||
case SETUP -> showPopup("SETUP \n" + (notification.message()!=null?notification.message():""));
|
||||
case ACTION_RESOLUTION -> showPopup("ACTION_RESOLUTION \n" + (notification.message()!=null?notification.message():"" ));
|
||||
case END_ACTION -> showPopup("END_ACTION \n" + (notification.message()!=null?notification.message():"" ));
|
||||
case EVENT_RESOLUTION -> showPopup("EVENT_RESOLUTION \n" + (notification.message()!=null?notification.message():"" ));
|
||||
case EXTRA_DRAW -> showPopup("EXTRA_DRAW \n" + (notification.message()!=null?notification.message():"" ));
|
||||
case GAME_OVER -> showPopup("GAME OVER");
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
primaryStage.setTitle("Tavolo da Gioco - Board & Players");
|
||||
|
||||
btnTop = new Button("Pick Top");
|
||||
btnBottom = new Button("Pick Bottom");
|
||||
btnMsg = new Button("Game State");
|
||||
btnRefresh= new Button("Refresh");
|
||||
btnChooseOffering= new Button("Choose Offering");
|
||||
//btnNext.setDisable(true);
|
||||
topMenu = new HBox(15, btnRefresh, btnMsg);
|
||||
topMenu.setAlignment(Pos.CENTER);
|
||||
topMenu.setPadding(new Insets(10));
|
||||
|
||||
Label stateLabel = new Label("Game State = " + game.getState() );
|
||||
stateLabel.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
stateLabel.setTextFill(Color.DARKBLUE);
|
||||
|
||||
//showPopup(topMenu, "STARTING GAME");
|
||||
|
||||
btnMsg.setOnAction(e -> {
|
||||
new Alert(Alert.AlertType.INFORMATION,
|
||||
game.toString()
|
||||
).showAndWait();
|
||||
});
|
||||
btnRefresh.setOnAction(e -> {
|
||||
drawGameState(game);
|
||||
});
|
||||
|
||||
topRow = createRowContainer();
|
||||
centerRow = createRowContainer();
|
||||
bottomRow = createRowContainer();
|
||||
|
||||
// NUOVO: Inizializza l'area giocatori
|
||||
playersArea = new HBox(30);
|
||||
playersArea.setAlignment(Pos.CENTER);
|
||||
playersArea.setPadding(new Insets(20));
|
||||
|
||||
|
||||
|
||||
|
||||
VBox tableArea = new VBox(20,
|
||||
new Label(" "), topRow,
|
||||
new Label(" "), centerRow,
|
||||
new Label(" "), bottomRow,
|
||||
new Label("--- ASSET PLAYERS ---"), playersArea
|
||||
);
|
||||
tableArea.setAlignment(Pos.CENTER);
|
||||
tableArea.setPadding(new Insets(20));
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane(tableArea);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(topMenu);
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
Scene scene = new Scene(root, 1300, 900);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
|
||||
drawGameState(game);
|
||||
}
|
||||
|
||||
private HBox createRowContainer() {
|
||||
HBox row = new HBox(10);
|
||||
row.setAlignment(Pos.CENTER);
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
private void drawGameState(Game game) {
|
||||
if (documentFront == null) return;
|
||||
|
||||
|
||||
topMenu.getChildren().clear();
|
||||
topRow.getChildren().clear();
|
||||
centerRow.getChildren().clear();
|
||||
bottomRow.getChildren().clear();
|
||||
playersArea.getChildren().clear();
|
||||
|
||||
drawTopMenu(topMenu, game);
|
||||
drawTopRow(topRow, game);
|
||||
drawTurnTileAndOffering(centerRow, game.getPlayers().size(), game);
|
||||
drawBottomRow(bottomRow, game );
|
||||
|
||||
|
||||
List<Player> players =game.getPlayers();
|
||||
renderPlayers(players, game);
|
||||
logger.info(game.getCurrentPlayer());
|
||||
|
||||
}
|
||||
|
||||
private void pickTopCardAction(Player player, GameBoard board, int cardid){
|
||||
Card card = board.getTopRow().stream()
|
||||
.filter(CharacterCard.class::isInstance)
|
||||
.map(CharacterCard.class::cast)
|
||||
.filter(c -> c.getCardId() == cardid)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (card != null) {
|
||||
board.getTopRow().remove(card);
|
||||
CharacterCard charCard = (CharacterCard)card;
|
||||
player.addCharacterToTribe((CharacterCard) card);
|
||||
}
|
||||
}
|
||||
|
||||
private void pickBottomCardAction(Player player, GameBoard board, int cardid){
|
||||
Card card = board.getBottomRow().stream()
|
||||
.filter(CharacterCard.class::isInstance)
|
||||
.map(CharacterCard.class::cast)
|
||||
.filter(c -> c.getCardId() == cardid)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (card != null) {
|
||||
board.getBottomRow().remove(card);
|
||||
player.addCharacterToTribe((CharacterCard) card);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void drawBottomRow(HBox row, Game game) {
|
||||
for (Card c : game.getGameBoard().getBottomRow()) {
|
||||
ImageView cardImage = createCardImageFromPdf(c.getCardId(), IMG_HEIGHT, true);
|
||||
|
||||
if (cardImage != null) {
|
||||
// 1. Create a Label for your debugging text
|
||||
Label debugLabel = new Label("ID: " + c.getCardId());
|
||||
|
||||
// 2. Style the label so it's readable over any card background
|
||||
// (White text with a semi-transparent black background)
|
||||
debugLabel.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7); " +
|
||||
"-fx-text-fill: white; " +
|
||||
"-fx-padding: 3px; " +
|
||||
"-fx-font-weight: bold;");
|
||||
|
||||
// 3. Create a StackPane and add both the image and the text
|
||||
StackPane cardContainer = new StackPane();
|
||||
cardContainer.getChildren().addAll(cardImage, debugLabel);
|
||||
|
||||
// 4. Align the text wherever you want (e.g., Top-Left corner of the card)
|
||||
StackPane.setAlignment(debugLabel, Pos.TOP_LEFT);
|
||||
// You can also use Pos.CENTER, Pos.BOTTOM_RIGHT, etc.
|
||||
cardImage.setOnMouseClicked(event -> {
|
||||
logger.info("Bottom Card clicked");
|
||||
Player p = game.getCurrentPlayer();
|
||||
//pickBottomCardAction(p, game.getGameBoard(), c.getCardId());
|
||||
ActionResult result = game.resolveCardAction(p, false, c.getCardId());
|
||||
if (!result.isSuccess()) {
|
||||
new Alert(Alert.AlertType.ERROR,
|
||||
result.getErrorMessage()
|
||||
).showAndWait();
|
||||
}
|
||||
drawGameState(game);
|
||||
});
|
||||
// 5. Add the StackPane (which now holds both) to the row
|
||||
row.getChildren().add(cardContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void drawTopMenu(HBox row, Game game) {
|
||||
//topMenu = new HBox(15, btnChooseOffering, btnAction, btnTop, btnBottom, btnMsg);
|
||||
//topMenu.setAlignment(Pos.CENTER);
|
||||
//topMenu.setPadding(new Insets(10));
|
||||
|
||||
Label stateLabel = new Label("Game State = " + game.getState() );
|
||||
stateLabel.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
stateLabel.setTextFill(Color.DARKBLUE);
|
||||
row.getChildren().add(stateLabel);
|
||||
row.getChildren().add(btnRefresh);
|
||||
row.getChildren().add(btnMsg);
|
||||
|
||||
|
||||
|
||||
}
|
||||
private void drawTopRow(HBox row, Game game) {
|
||||
for (Card c : game.getGameBoard().getTopRow()) {
|
||||
ImageView cardImage = createCardImageFromPdf(c.getCardId(), IMG_HEIGHT, true);
|
||||
|
||||
if (cardImage != null) {
|
||||
// 1. Create a Label for your debugging text
|
||||
Label debugLabel = new Label("ID: " + c.getCardId());
|
||||
|
||||
// 2. Style the label so it's readable over any card background
|
||||
// (White text with a semi-transparent black background)
|
||||
debugLabel.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7); " +
|
||||
"-fx-text-fill: white; " +
|
||||
"-fx-padding: 3px; " +
|
||||
"-fx-font-weight: bold;");
|
||||
|
||||
// 3. Create a StackPane and add both the image and the text
|
||||
StackPane cardContainer = new StackPane();
|
||||
cardContainer.getChildren().addAll(cardImage, debugLabel);
|
||||
|
||||
// 4. Align the text wherever you want (e.g., Top-Left corner of the card)
|
||||
StackPane.setAlignment(debugLabel, Pos.TOP_LEFT);
|
||||
// You can also use Pos.CENTER, Pos.BOTTOM_RIGHT, etc.
|
||||
cardImage.setOnMouseClicked(event -> {
|
||||
logger.info("Card clicked");
|
||||
Player p = game.getCurrentPlayer();
|
||||
//pickTopCardAction(p, game.getGameBoard(), c.getCardId());
|
||||
//game.resolveCardAction(p, true, c.getCardId());
|
||||
|
||||
ActionResult result = game.resolveCardAction(p, true, c.getCardId());
|
||||
if (!result.isSuccess()) {
|
||||
new Alert(Alert.AlertType.ERROR,
|
||||
result.getErrorMessage()
|
||||
).showAndWait();
|
||||
}
|
||||
|
||||
drawGameState(game);
|
||||
});
|
||||
// 5. Add the StackPane (which now holds both) to the row
|
||||
row.getChildren().add(cardContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StackPane drawTurnTileCardImageWithPlayers(String imagePath, int height, Player[] players) {
|
||||
try {
|
||||
Image fxImage = new Image("file:" + imagePath);
|
||||
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
|
||||
StackPane stack = new StackPane(imageView);
|
||||
|
||||
VBox playerBox = new VBox(5);
|
||||
playerBox.setPadding(new Insets(5));
|
||||
playerBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
// 🔥 DEBUG background (to SEE the box)
|
||||
playerBox.setStyle("-fx-background-color: rgba(255,0,0,0.3);");
|
||||
|
||||
for (Player p : players) {
|
||||
if (p==null) continue;
|
||||
Rectangle rect = new Rectangle(15,15);
|
||||
rect.setFill(p.getTotemColor().getFxColor());
|
||||
rect.setStroke(Color.WHITE);
|
||||
rect.setStrokeWidth(1);
|
||||
|
||||
playerBox.getChildren().add(rect);
|
||||
}
|
||||
playerBox.setPadding(new Insets(22));
|
||||
// 🔥 IMPORTANT: bring to front
|
||||
playerBox.toFront();
|
||||
|
||||
// position it clearly
|
||||
StackPane.setAlignment(playerBox, Pos.CENTER);
|
||||
|
||||
stack.getChildren().add(playerBox);
|
||||
|
||||
return stack;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Errore caricamento immagine {}", imagePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void drawTurnTileAndOffering(HBox row, int n, Game game) {
|
||||
|
||||
// Clear row if needed (optional but recommended)
|
||||
row.getChildren().clear();
|
||||
|
||||
// --- LEFT BLOCK (Turn + initial card) ---
|
||||
VBox turnBox = new VBox(5);
|
||||
turnBox.setAlignment(Pos.CENTER);
|
||||
|
||||
// Turn label
|
||||
int round = game.getRound(); // adjust if different
|
||||
Label turnLabel = new Label("Round = " + round);
|
||||
turnLabel.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
turnLabel.setTextFill(Color.DARKBLUE);
|
||||
|
||||
Card first = game.getGameBoard().getCardDeck().getFirstCardForCover();
|
||||
if (first!=null){
|
||||
ImageView imgCover = createCardImageFromPdf(first.getCardId(), 120, false);
|
||||
turnBox.getChildren().addAll(turnLabel, imgCover);
|
||||
} else turnBox.getChildren().addAll(turnLabel);
|
||||
|
||||
// Add FIRST
|
||||
row.getChildren().add(turnBox);
|
||||
|
||||
// --- TURN TILE ---
|
||||
StackPane pane = drawTurnTileCardImageWithPlayers(
|
||||
"/home/lorenzo/dev/Mesos2/src/main/resources/files/Start_" + n + "P.png",
|
||||
IMG_HEIGHT,
|
||||
game.getGameBoard().getTurnTile().getPositions()
|
||||
);
|
||||
|
||||
row.getChildren().add(pane);
|
||||
|
||||
// --- OFFERINGS ---
|
||||
for (OfferingTile offering : game.getGameBoard().getOfferingTiles()) {
|
||||
Player occupant = offering.getOccupant();
|
||||
StackPane offPane =drawOfferingCardImageWithTotem(
|
||||
"/home/lorenzo/dev/Mesos2/src/main/resources/files/offering" + offering.getLetter() + ".png",
|
||||
IMG_HEIGHT,
|
||||
occupant, game, offering);
|
||||
|
||||
if (offPane != null) {
|
||||
row.getChildren().add(offPane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- NUOVO: RENDERIZZAZIONE GIOCATORI E RAGGRUPPAMENTO (JAVA 8) ---
|
||||
private void renderPlayers(List<Player> players, Game game) {
|
||||
playersArea.getChildren().clear();
|
||||
for (Player player : players) {
|
||||
boolean active = player.equals(game.getCurrentPlayer());
|
||||
|
||||
VBox playerBox = new VBox(10);
|
||||
playerBox.setPadding(new Insets(15));
|
||||
playerBox.setStyle("-fx-border-color: #555; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-color: #f9f9f9; -fx-background-radius: 10;");
|
||||
|
||||
if (active) {
|
||||
// Strong highlight (gold border + soft background)
|
||||
playerBox.setStyle(
|
||||
"-fx-border-color: gold;" +
|
||||
"-fx-border-width: 3;" +
|
||||
"-fx-border-radius: 10;" +
|
||||
"-fx-background-color: linear-gradient(to bottom, #fffbe6, #f9f9f9);" +
|
||||
"-fx-background-radius: 10;"
|
||||
);
|
||||
|
||||
// Slight scale-up
|
||||
playerBox.setScaleX(1.05);
|
||||
playerBox.setScaleY(1.05);
|
||||
|
||||
// Glow effect
|
||||
DropShadow glow = new DropShadow();
|
||||
glow.setColor(Color.GOLD);
|
||||
glow.setRadius(20);
|
||||
playerBox.setEffect(glow);
|
||||
|
||||
// Smooth pulse animation (NOT blinking)
|
||||
Timeline pulse = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(glow.radiusProperty(), 10)
|
||||
),
|
||||
new KeyFrame(Duration.seconds(1.2),
|
||||
new KeyValue(glow.radiusProperty(), 25)
|
||||
)
|
||||
);
|
||||
pulse.setAutoReverse(true);
|
||||
pulse.setCycleCount(Animation.INDEFINITE);
|
||||
pulse.play();
|
||||
}
|
||||
|
||||
HBox nameRow = new HBox(8);
|
||||
nameRow.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Rectangle rect = new Rectangle(12, 12);
|
||||
|
||||
TotemColor color = player.getTotemColor();
|
||||
rect.setFill(color.getFxColor());
|
||||
|
||||
|
||||
rect.setStroke(Color.BLACK);
|
||||
rect.setStrokeWidth(1);
|
||||
|
||||
Label nameLbl = new Label(player.getNickname());
|
||||
nameLbl.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
|
||||
if (active) {
|
||||
Label turnLbl = new Label(" ▶ Your Turn");
|
||||
turnLbl.setTextFill(Color.GOLDENROD);
|
||||
turnLbl.setFont(Font.font("System", FontWeight.BOLD, 14));
|
||||
nameRow.getChildren().addAll(rect, nameLbl, turnLbl);
|
||||
} else {
|
||||
nameRow.getChildren().addAll(rect, nameLbl);
|
||||
}
|
||||
|
||||
// Risorse Cibo e Soldi
|
||||
Label statsLbl = new Label("🍖 Food: " + player.getFoodTokens() + " | 💰 Points: " + player.getPrestigePoints());
|
||||
statsLbl.setTextFill(Color.DARKRED);
|
||||
statsLbl.setFont(Font.font("System", FontWeight.BOLD, 14));
|
||||
|
||||
playerBox.getChildren().addAll(nameRow, statsLbl);
|
||||
|
||||
Map<CharacterType, List<CharacterCard>> groupedCards = player.getPlayerTribe().getCharacters().stream()
|
||||
.collect(Collectors.groupingBy(CharacterCard::getCharacterType));
|
||||
|
||||
// Itera sui gruppi creati e genera la grafica
|
||||
groupedCards.forEach((type, cardsOfType) -> {
|
||||
Label typeLbl = new Label("Tipo: " + type.name() + " (" + cardsOfType.size() + ")");
|
||||
typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12));
|
||||
|
||||
HBox cardImagesRow = new HBox(5);
|
||||
for (Card c : cardsOfType) {
|
||||
// Carte più piccole (90px) per l'area giocatore
|
||||
ImageView img = createCardImageFromPdf(c.getCardId(), 90, true);
|
||||
if (img != null) cardImagesRow.getChildren().add(img);
|
||||
}
|
||||
|
||||
playerBox.getChildren().addAll(typeLbl, cardImagesRow);
|
||||
});
|
||||
|
||||
Map<Era, List<BuildingCard>> groupedBuildCards = player.getPlayerTribe().getBuildingCard().stream()
|
||||
.collect(Collectors.groupingBy(BuildingCard::getEra));
|
||||
|
||||
groupedBuildCards.forEach((type, cardsOfType) -> {
|
||||
Label typeLbl = new Label("Build Tipo: " + type.name() + " (" + cardsOfType.size() + ")");
|
||||
typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12));
|
||||
|
||||
HBox cardImagesRow = new HBox(5);
|
||||
for (Card c : cardsOfType) {
|
||||
// Carte più piccole (90px) per l'area giocatore
|
||||
ImageView img = createCardImageFromPdf(c.getCardId(), 90, true);
|
||||
if (img != null) cardImagesRow.getChildren().add(img);
|
||||
}
|
||||
|
||||
playerBox.getChildren().addAll(typeLbl, cardImagesRow);
|
||||
//playerBox.getChildren().addAll(nameRow, statsLbl);
|
||||
});
|
||||
playersArea.getChildren().add(playerBox);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageView createCardImageFromPdf(int pageIndex, int height, boolean front) {
|
||||
try {
|
||||
BufferedImage bim =null;
|
||||
if (front) bim = pdfRendererFront.renderImageWithDPI(pageIndex-1, 100);
|
||||
else bim = pdfRendererBack.renderImageWithDPI(pageIndex-1, 100);
|
||||
|
||||
Image fxImage = SwingFXUtils.toFXImage(bim, null);
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
return imageView;
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore rendering pagina {}", pageIndex, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private StackPane drawOfferingCardImageWithTotem(String imagePath, int height, Player occupant, Game game, OfferingTile offering) {
|
||||
try {
|
||||
Image fxImage = new Image("file:" + imagePath);
|
||||
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
|
||||
imageView.setOnMouseClicked(event -> {
|
||||
int idx = game.getGameBoard().getOfferingTiles().indexOf(offering);
|
||||
game.placeTotem(game.getCurrentPlayer(), idx);
|
||||
logger.info(" PLAYER {} choose {} ", game.getCurrentPlayer().getNickname() , offering );
|
||||
drawGameState(game);
|
||||
});
|
||||
|
||||
StackPane stack = new StackPane(imageView);
|
||||
if(occupant!=null){
|
||||
Rectangle rect = new Rectangle(25, 25);
|
||||
rect.setFill(occupant.getTotemColor().getFxColor());
|
||||
rect.setStroke(Color.WHITE);
|
||||
rect.setStrokeWidth(1);
|
||||
|
||||
StackPane.setAlignment(rect, Pos.TOP_CENTER);
|
||||
rect.setTranslateY(20);
|
||||
StackPane.setMargin(rect, new Insets(5));
|
||||
|
||||
stack.getChildren().add(rect);
|
||||
}
|
||||
|
||||
return stack;
|
||||
} catch (Exception e) {
|
||||
logger.error("Errore caricamento immagine {}", imagePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ImageView createCardImage(String imagePath, int height) {
|
||||
try {
|
||||
Image fxImage = new Image("file:" + imagePath);
|
||||
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
|
||||
return imageView;
|
||||
} catch (Exception e) {
|
||||
logger.error("Errore caricamento immagine {}", imagePath, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private void showPopup(String message) {
|
||||
Platform.runLater(() -> {
|
||||
|
||||
Label label = new Label("⚡ " + message); // icon improves readability
|
||||
label.setWrapText(true);
|
||||
label.setMaxWidth(400);
|
||||
|
||||
label.setStyle(
|
||||
"-fx-background-color: rgba(20,20,20,0.9);" +
|
||||
"-fx-text-fill: white;" +
|
||||
"-fx-font-size: 20px;" +
|
||||
"-fx-font-weight: bold;" +
|
||||
"-fx-padding: 15 25 15 25;" +
|
||||
"-fx-background-radius: 12;" +
|
||||
"-fx-border-radius: 12;" +
|
||||
"-fx-border-color: #00c3ff;"
|
||||
);
|
||||
|
||||
label.setOpacity(0); // start invisible
|
||||
|
||||
topMenu.getChildren().add(label);
|
||||
|
||||
// Fade IN
|
||||
FadeTransition fadeIn = new FadeTransition(Duration.millis(250), label);
|
||||
fadeIn.setFromValue(0);
|
||||
fadeIn.setToValue(1);
|
||||
|
||||
// Stay visible
|
||||
PauseTransition pause = new PauseTransition(Duration.seconds(4));
|
||||
|
||||
// Fade OUT
|
||||
FadeTransition fadeOut = new FadeTransition(Duration.millis(400), label);
|
||||
fadeOut.setFromValue(1.0);
|
||||
fadeOut.setToValue(0.0);
|
||||
|
||||
fadeOut.setOnFinished(f -> topMenu.getChildren().remove(label));
|
||||
|
||||
fadeIn.play();
|
||||
fadeIn.setOnFinished(e -> pause.play());
|
||||
pause.setOnFinished(e -> fadeOut.play());
|
||||
});
|
||||
}
|
||||
private void showPopupNo(String message) {
|
||||
Platform.runLater(() -> {
|
||||
Label label = new Label(message);
|
||||
label.setStyle(
|
||||
"-fx-background-color: rgba(0,0,0,0.75);" +
|
||||
"-fx-text-fill: white;" +
|
||||
"-fx-font-size: 28px;" +
|
||||
"-fx-font-weight: bold;" +
|
||||
"-fx-padding: 20 40 20 40;" +
|
||||
"-fx-background-radius: 12;"
|
||||
);
|
||||
label.setMouseTransparent(true);
|
||||
|
||||
topMenu.getChildren().add(label);
|
||||
|
||||
PauseTransition pause = new PauseTransition(Duration.seconds(5));
|
||||
pause.setOnFinished(e -> {
|
||||
FadeTransition fade = new FadeTransition(Duration.millis(400), label);
|
||||
fade.setFromValue(1.0);
|
||||
fade.setToValue(0.0);
|
||||
fade.setOnFinished(f -> topMenu.getChildren().remove(label));
|
||||
fade.play();
|
||||
});
|
||||
pause.play();
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (documentFront != null) documentFront.close();
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
19
src/main/java/Server/Era.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package Server;
|
||||
|
||||
public enum Era {
|
||||
I,
|
||||
II,
|
||||
III,
|
||||
FINAL;
|
||||
|
||||
|
||||
public Era next() {
|
||||
Era[] eras = values();
|
||||
// Calcola l'indice del prossimo elemento
|
||||
int nextOrdinal = this.ordinal() + 1;
|
||||
if (nextOrdinal < eras.length) {
|
||||
return eras[nextOrdinal];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
136
src/main/java/Server/EventsSolver.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package Server;
|
||||
|
||||
import Server.Cards.Event;
|
||||
import Server.Cards.EventCard;
|
||||
import Server.Cards.Trigger;
|
||||
import Server.Utils.EventsManagerException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EventsSolver {
|
||||
|
||||
public static boolean solveEvents(List<EventCard> events, List<Player> players){
|
||||
|
||||
for(EventCard event: events){
|
||||
switch (event.getEvent()){
|
||||
case Event.SUSTAINMENT:
|
||||
EventsSolver.sustainment(event, players);
|
||||
break;
|
||||
case Event.HUNT:
|
||||
EventsSolver.hunt(event, players);
|
||||
break;
|
||||
case Event.SHAMANIC_RITUAL:
|
||||
EventsSolver.shamanicRitual(event, players);
|
||||
break;
|
||||
case Event.CAVE_PAINTINGS:
|
||||
EventsSolver.cavePaintings(event, players);
|
||||
break;
|
||||
default:
|
||||
throw new EventsManagerException("Unknown event type");
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<Integer> sustainment(EventCard event, List<Player> players){
|
||||
|
||||
if(event.getEvent() != Event.SUSTAINMENT){throw new EventsManagerException("Not a sustainment card");}
|
||||
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for(Player p: players){
|
||||
int subpoints = p.getPlayerTribe().getCharacters().size();
|
||||
int discount = p.getPlayerTribe().gathererDiscount() + BuildingManager.sustainDiscount(p);
|
||||
|
||||
if(subpoints <= discount){subpoints = 0;}else{
|
||||
subpoints -= discount;
|
||||
}
|
||||
|
||||
|
||||
if(p.getFoodTokens() >= subpoints){
|
||||
p.removeFood(subpoints);
|
||||
}else{
|
||||
subpoints -= p.getFoodTokens();
|
||||
p.removeFood(p.getFoodTokens());
|
||||
p.removePrestigePoints(subpoints*event.getFirstValue());
|
||||
}
|
||||
|
||||
result.add(p.getFoodTokens());
|
||||
result.add(p.getPrestigePoints());
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Integer> hunt(EventCard event, List<Player> players){
|
||||
if(event.getEvent() != Event.HUNT){throw new EventsManagerException("Not a hunt card");}
|
||||
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
for(Player p: players){
|
||||
p.addFood(p.getPlayerTribe().huntersNumber());
|
||||
p.addPrestigePoints(p.getPlayerTribe().huntersNumber()*event.getFirstValue());
|
||||
|
||||
if(BuildingManager.hunterBonus(p)){
|
||||
p.addFood(p.getPlayerTribe().huntersNumber());
|
||||
p.addPrestigePoints(p.getPlayerTribe().huntersNumber());
|
||||
}
|
||||
|
||||
result.add(p.getFoodTokens());
|
||||
result.add(p.getPrestigePoints());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Integer> shamanicRitual(EventCard event, List<Player> players){
|
||||
if(event.getEvent() != Event.SHAMANIC_RITUAL){throw new EventsManagerException("Not a shamanic ritual card");}
|
||||
List<Integer> result = new ArrayList<>();
|
||||
int maxSymbols = 0;
|
||||
int minSymbols = 999;
|
||||
|
||||
for(Player p: players){
|
||||
int symbols = p.getPlayerTribe().shamansIcons() + BuildingManager.shamanBonus(p);
|
||||
if (symbols > maxSymbols){maxSymbols = p.getPlayerTribe().shamansIcons();}
|
||||
if (symbols < minSymbols){minSymbols = p.getPlayerTribe().shamansIcons();}
|
||||
}
|
||||
|
||||
for(Player p: players){
|
||||
if(p.getPlayerTribe().shamansIcons() == maxSymbols){
|
||||
p.addPrestigePoints(event.getFirstValue());
|
||||
|
||||
//activating building shamanDoublePoints card effect
|
||||
if(BuildingManager.shamanDoublePoints(p))
|
||||
p.addPrestigePoints(event.getFirstValue());
|
||||
}
|
||||
if(p.getPlayerTribe().shamansIcons() == minSymbols){
|
||||
|
||||
//activating building shamanNoLoss card effect
|
||||
if(!BuildingManager.shamanNoLoss(p))
|
||||
p.removePrestigePoints(event.getSecondValue());
|
||||
}
|
||||
result.add(p.getPrestigePoints());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Integer> cavePaintings(EventCard event, List<Player> players){
|
||||
|
||||
if(event.getEvent() != Event.CAVE_PAINTINGS){throw new EventsManagerException("Not a cave painting card");}
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for(Player p: players){
|
||||
BuildingManager.paintingFoodBonus(p);
|
||||
if(p.getPlayerTribe().artistsNumber() >= event.getFirstValue()){
|
||||
p.addPrestigePoints(p.getPlayerTribe().artistsNumber()*event.getSecondValue());
|
||||
}else{
|
||||
p.removePrestigePoints(event.getSecondValue());
|
||||
}
|
||||
|
||||
result.add(p.getPrestigePoints());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
395
src/main/java/Server/GameBoard.java
Normal file
@@ -0,0 +1,395 @@
|
||||
package Server;
|
||||
|
||||
import Server.Automaton.Game;
|
||||
import Server.Cards.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents the physical game board: the two card rows, the offering tiles,
|
||||
* the turn-order tile, and the card deck.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Owning and mutating the top and bottom card rows.
|
||||
* - Owning the OfferingTiles and the TurnTile.
|
||||
* - Performing end-of-round row transitions and era-change building swaps.
|
||||
* - Exposing visible EventCards to Game for resolution.
|
||||
*
|
||||
* NOT responsible for:
|
||||
* - Resolving events (that is EventsSolver's job, called by Game).
|
||||
* - Applying player rewards/penalties (that is Game's job).
|
||||
* - Holding a reference to the player list.
|
||||
*/
|
||||
public class GameBoard {
|
||||
private static final Logger logger = LogManager.getLogger(GameBoard.class);
|
||||
// -------------------------------------------------------------------------
|
||||
// Fields
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private Era era;
|
||||
|
||||
private final CardDeck cardDeck;
|
||||
private final List<Card> topRow;
|
||||
private final List<Card> bottomRow;
|
||||
private final List<OfferingTile> offeringTiles;
|
||||
private final TurnTile turnTile;
|
||||
private final BuildingManager buildingManager;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates the board for a new game.
|
||||
* Call {@link #setupInitialRows(int)} and {@link #initOfferingTiles(int)}
|
||||
* separately after construction (mirrors the rulebook setup steps).
|
||||
*/
|
||||
public GameBoard(Era startingEra, CardDeck cardDeck, int numPlayers) {
|
||||
this.era = startingEra;
|
||||
this.cardDeck = cardDeck;
|
||||
this.topRow = new ArrayList<>();
|
||||
this.bottomRow = new ArrayList<>();
|
||||
this.offeringTiles = new ArrayList<>();
|
||||
this.turnTile = new TurnTile(numPlayers);
|
||||
this.buildingManager = new BuildingManager();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates and places the correct OfferingTiles for the given player count.
|
||||
*
|
||||
* Tile layout (from the rulebook):
|
||||
* orderId 0 → tile A: 3 food (5-player only)
|
||||
* orderId 1 → tile B: 1 DOWN (all)
|
||||
* orderId 2 → tile C: 1 UP (all)
|
||||
* orderId 3 → tile D: 2 DOWN (3+ players)
|
||||
* orderId 4 → tile E: 1 DOWN 1 UP (all)
|
||||
* orderId 5 → tile F: 2 UP (all)
|
||||
* orderId 6 → tile G: 1 DOWN 2 UP (4+ players)
|
||||
*
|
||||
* With n players, exactly n tiles are used, starting from tile A if
|
||||
* 5 players, otherwise starting from tile B.
|
||||
*/
|
||||
public void initOfferingTiles(int numPlayers) {
|
||||
offeringTiles.clear();
|
||||
switch (numPlayers) {
|
||||
case 2:
|
||||
offeringTiles.add(new OfferingTile(1));
|
||||
offeringTiles.add(new OfferingTile(2));
|
||||
offeringTiles.add(new OfferingTile(4));
|
||||
offeringTiles.add(new OfferingTile(5));
|
||||
break;
|
||||
case 3:
|
||||
offeringTiles.add(new OfferingTile(1));
|
||||
offeringTiles.add(new OfferingTile(2));
|
||||
offeringTiles.add(new OfferingTile(3));
|
||||
offeringTiles.add(new OfferingTile(4));
|
||||
offeringTiles.add(new OfferingTile(5));
|
||||
break;
|
||||
case 4:
|
||||
offeringTiles.add(new OfferingTile(1));
|
||||
offeringTiles.add(new OfferingTile(2));
|
||||
offeringTiles.add(new OfferingTile(3));
|
||||
offeringTiles.add(new OfferingTile(4));
|
||||
offeringTiles.add(new OfferingTile(5));
|
||||
offeringTiles.add(new OfferingTile(6));
|
||||
break;
|
||||
case 5:
|
||||
offeringTiles.add(new OfferingTile(0));
|
||||
offeringTiles.add(new OfferingTile(1));
|
||||
offeringTiles.add(new OfferingTile(2));
|
||||
offeringTiles.add(new OfferingTile(3));
|
||||
offeringTiles.add(new OfferingTile(4));
|
||||
offeringTiles.add(new OfferingTile(5));
|
||||
offeringTiles.add(new OfferingTile(6));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Find the oddertingTile selected by Player
|
||||
public OfferingTile getOfferingTile(Player p){
|
||||
OfferingTile offering = offeringTiles.stream().filter(o -> p.equals(o.getOccupant())).findFirst().orElse(null);
|
||||
return offering;
|
||||
}
|
||||
/**
|
||||
* Draws the initial two rows of cards according to rulebook setup steps 4-5:
|
||||
*
|
||||
* Bottom row: draw cards one at a time until (numPlayers + 1) Character cards
|
||||
* have been placed. Any Event card drawn is placed in the top row instead.
|
||||
* Top row: fill to (numPlayers + 4) with additional draws (accounting for
|
||||
* any events already placed there from the bottom-row draw).
|
||||
*/
|
||||
public void setupInitialRows(int numPlayers) {
|
||||
topRow.clear();
|
||||
bottomRow.clear();
|
||||
|
||||
// Draw bottom row — events are bumped to top row
|
||||
while (bottomRow.size() < numPlayers + 1) {
|
||||
Card card = cardDeck.drawTribeOne();
|
||||
if (card instanceof EventCard) {
|
||||
topRow.add(card);
|
||||
logger.info("Setup: Event card " + card.getCardId() + " moved to top row.");
|
||||
} else {
|
||||
bottomRow.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill top row up to numPlayers + 4
|
||||
int needed = (numPlayers + 4) - topRow.size();
|
||||
if (needed > 0) {
|
||||
topRow.addAll(cardDeck.drawTribe(needed));
|
||||
}
|
||||
|
||||
// Add all Building Era.I on top row 2P 1 3-4-5 2
|
||||
topRow.add(cardDeck.drawBuildingOne(Era.I));
|
||||
if (numPlayers > 2){
|
||||
topRow.add(cardDeck.drawBuildingOne(Era.I));
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// End-of-round row management (rulebook "Fine del Round" steps 2-4)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Performs the three row-management steps at the end of every round:
|
||||
*
|
||||
* Step 2 — Discard all Character and Event cards from the bottom row.
|
||||
* Building cards stay.
|
||||
* Step 3 — Move all Character and Event cards from the top row down to
|
||||
* the bottom row. Building cards stay in the top row.
|
||||
* Step 4 — Draw (numPlayers + 4) new cards into the top row.
|
||||
*
|
||||
* @return true if the newly drawn cards contain a card from the next Era,
|
||||
* signalling that {@link #triggerEraChange()} should be called.
|
||||
*/
|
||||
public boolean advanceRows(int numPlayers) {
|
||||
|
||||
Era eraBeforeDraw = this.era;
|
||||
|
||||
// Step 2: discard non-building cards from the bottom row
|
||||
bottomRow.removeIf(
|
||||
c -> !(c instanceof BuildingCard)
|
||||
);
|
||||
|
||||
// Step 3: move non-building cards from top row down to bottom row
|
||||
Iterator<Card> it = topRow.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (!(c instanceof BuildingCard)) {
|
||||
logger.debug("move card from top to bottom " + c);
|
||||
bottomRow.add(c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: draw fresh cards into the top row
|
||||
List<Card> newCards = cardDeck.drawTribe(numPlayers + 4);
|
||||
topRow.addAll(newCards);
|
||||
logger.info("NEW CARDS ON TOP {} " , newCards);
|
||||
return isNewEraRevealed(eraBeforeDraw, newCards);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of the newly drawn cards belongs to an era
|
||||
* that is strictly later than the current one.
|
||||
*/
|
||||
private boolean isNewEraRevealed(Era currentEra, List<Card> newCards) {
|
||||
for (Card c : newCards) {
|
||||
Era cardEra = eraOf(c);
|
||||
if (cardEra != null && cardEra.ordinal() > currentEra.ordinal()) {
|
||||
logger.info("FOUND NEW ERA {} " , cardEra);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Era change (rulebook "Inizio della Nuova Era")
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Advances the era and performs the three building-row transitions:
|
||||
*
|
||||
* Step 1 — (Era III only) Discard all Building cards from the bottom row.
|
||||
* Step 2 — Move all Building cards from the top row to the bottom row
|
||||
* (to the right of the Tribe cards). [Era II and III]
|
||||
* Step 3 — Place the new era's Building cards face-up in the top row.
|
||||
* [Era II and III]
|
||||
*
|
||||
* @return the new Era after the transition
|
||||
*/
|
||||
public Era triggerEraChange(int numPlayers) {
|
||||
era = era.next(); // Era.I → II → III → FINAL
|
||||
logger.info("ERA CHANGED → {} ", era);
|
||||
|
||||
// Step 1 (Era III only): remove old era buildings from the bottom row
|
||||
if (era == Era.III) {
|
||||
bottomRow.removeIf(c -> c instanceof BuildingCard);
|
||||
}
|
||||
|
||||
// Step 2: move buildings from top row to bottom row
|
||||
List<BuildingCard> descending = new ArrayList<>();
|
||||
Iterator<Card> it = topRow.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (c instanceof BuildingCard) {
|
||||
descending.add((BuildingCard) c);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
bottomRow.addAll(descending);
|
||||
logger.info("event=MOVED_BUILING era={} count={} cards={}", era, descending.size(), descending);
|
||||
|
||||
// Step 3: place the new era's building cards in the top row
|
||||
int n = BuildingRules.getBuildingCards(numPlayers, era);
|
||||
List<Card> newEraBuildings = cardDeck.drawBuilding(n , era);
|
||||
topRow.addAll(newEraBuildings);
|
||||
|
||||
logger.info("event=ADD_BUILDINGS era={} count={} cards={}", era, n, newEraBuildings);
|
||||
return era;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Offering tiles
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Attempts to place a player's totem on an offering tile.
|
||||
*
|
||||
* @return true if successful; false if the tile was already occupied
|
||||
*/
|
||||
public boolean placeTotem(Player player, OfferingTile tile, TurnTile turntile) {
|
||||
if (!tile.isEmpty()) return false;
|
||||
tile.setOccupant(player);
|
||||
turntile.leaveTurnTile(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all totems from offering tiles. Call at the start of each new round.
|
||||
*/
|
||||
public void clearOfferingTiles() {
|
||||
for (OfferingTile tile : offeringTiles) {
|
||||
tile.removeOccupant();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Card removal (called by Game when a player takes a card)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Removes a card from the top row by ID and returns it.
|
||||
*
|
||||
* @return the card, or null if no card with that ID exists in the top row
|
||||
*/
|
||||
public Card takeFromTopRow(int cardId) {
|
||||
return takeFromRow(topRow, cardId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a card from the bottom row by ID and returns it.
|
||||
*
|
||||
* @return the card, or null if no card with that ID exists in the bottom row
|
||||
*/
|
||||
public Card takeFromBottomRow(int cardId) {
|
||||
return takeFromRow(bottomRow, cardId);
|
||||
}
|
||||
|
||||
private Card takeFromRow(List<Card> row, int cardId) {
|
||||
Iterator<Card> it = row.iterator();
|
||||
while (it.hasNext()) {
|
||||
Card c = it.next();
|
||||
if (c.getCardId() == cardId) {
|
||||
it.remove();
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Event visibility
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns all EventCards currently in the bottom row, sorted by era
|
||||
* (ascending) so they are resolved in the correct order.
|
||||
* Sustainment is NOT sorted last here — that is Game's responsibility.
|
||||
*/
|
||||
public List<EventCard> getVisibleEvents() {
|
||||
List<EventCard> events = new ArrayList<>();
|
||||
for (Card c : bottomRow) {
|
||||
if (c instanceof EventCard) {
|
||||
events.add((EventCard) c);
|
||||
}
|
||||
}
|
||||
events.sort((a, b) -> a.getEra().ordinal() - b.getEra().ordinal());
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns EventCards from BOTH rows.
|
||||
* Used during the final round when all visible events must be resolved.
|
||||
*/
|
||||
public List<EventCard> getAllVisibleEvents() {
|
||||
List<EventCard> events = new ArrayList<>();
|
||||
for (Card c : bottomRow) {
|
||||
if (c instanceof EventCard) events.add((EventCard) c);
|
||||
}
|
||||
for (Card c : topRow) {
|
||||
if (c instanceof EventCard) events.add((EventCard) c);
|
||||
}
|
||||
events.sort((a, b) -> a.getEra().ordinal() - b.getEra().ordinal());
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Extracts the Era from a card, returning null for building cards. */
|
||||
private Era eraOf(Card c) {
|
||||
if (c instanceof CharacterCard) return ((CharacterCard) c).getEra();
|
||||
if (c instanceof EventCard) return ((EventCard) c).getEra();
|
||||
return null;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Getters
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public Era getEra() { return era; }
|
||||
public List<Card> getTopRow() { return topRow; }
|
||||
public List<Card> getBottomRow() { return bottomRow; }
|
||||
public List<OfferingTile> getOfferingTiles() { return offeringTiles; }
|
||||
public TurnTile getTurnTile() { return turnTile; }
|
||||
public BuildingManager getBuildingManager() { return buildingManager; }
|
||||
public CardDeck getCardDeck() { return cardDeck; }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameBoard{" +
|
||||
"era=" + era +
|
||||
",\n offeringTiles=" + offeringTiles +
|
||||
",\n turnTile=" + turnTile +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
148
src/main/java/Server/OfferingTile.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package Server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Represents a single tile on the offering track.
|
||||
*
|
||||
* Each tile has a fixed letter (A–G) and a fixed list of actions the
|
||||
* occupying player must resolve. The tile is either empty or occupied
|
||||
* by exactly one player's totem at any given time.
|
||||
*
|
||||
* Tile layout (from the rulebook):
|
||||
*
|
||||
* Letter │ orderId │ Actions │ Player count
|
||||
* ───────┼─────────┼──────────────────────┼─────────────
|
||||
* A │ 0 │ FOOD FOOD FOOD │ 5 only
|
||||
* B │ 1 │ BOTTOM │ all
|
||||
* C │ 2 │ UP │ all
|
||||
* D │ 3 │ BOTTOM BOTTOM │ 3+
|
||||
* E │ 4 │ BOTTOM UP │ all
|
||||
* F │ 5 │ UP UP │ all
|
||||
* G │ 6 │ BOTTOM UP UP │ 4+
|
||||
*
|
||||
* Which tiles are included in a game is decided by GameBoard.initOfferingTiles(),
|
||||
* not by this class.
|
||||
*/
|
||||
public class OfferingTile {
|
||||
|
||||
private static final char FIRST_LETTER = 'A';
|
||||
|
||||
private final int orderId;
|
||||
private final char letter;
|
||||
private final List<Symbol> actions; // unmodifiable after construction
|
||||
private Player occupant;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public OfferingTile(int orderId) {
|
||||
this.orderId = orderId;
|
||||
this.letter = (char) (FIRST_LETTER + orderId);
|
||||
this.occupant = null;
|
||||
this.actions = Collections.unmodifiableList(buildActions(orderId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fixed action list for each tile orderId.
|
||||
* Extracted into a private method to keep the constructor clean.
|
||||
*/
|
||||
private static List<Symbol> buildActions(int orderId) {
|
||||
List<Symbol> list = new ArrayList<>();
|
||||
switch (orderId) {
|
||||
case 0: // A — food tile (5p only)
|
||||
list.add(Symbol.FOOD);
|
||||
list.add(Symbol.FOOD);
|
||||
list.add(Symbol.FOOD);
|
||||
break;
|
||||
case 1: // B
|
||||
list.add(Symbol.DOWN);
|
||||
break;
|
||||
case 2: // C
|
||||
list.add(Symbol.UP);
|
||||
break;
|
||||
case 3: // D
|
||||
list.add(Symbol.DOWN);
|
||||
list.add(Symbol.DOWN);
|
||||
break;
|
||||
case 4: // E
|
||||
list.add(Symbol.DOWN);
|
||||
list.add(Symbol.UP);
|
||||
break;
|
||||
case 5: // F
|
||||
list.add(Symbol.UP);
|
||||
list.add(Symbol.UP);
|
||||
break;
|
||||
case 6: // G
|
||||
list.add(Symbol.DOWN);
|
||||
list.add(Symbol.UP);
|
||||
list.add(Symbol.UP);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid OfferingTile orderId: " + orderId);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Occupant management
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** Returns true if no totem is currently placed on this tile. */
|
||||
public boolean isEmpty() {
|
||||
return occupant == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a player's totem on this tile.
|
||||
* Callers should check {@link #isEmpty()} before calling this.
|
||||
*/
|
||||
public void setOccupant(Player player) {
|
||||
this.occupant = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the totem from this tile and returns the player who was on it,
|
||||
* or null if the tile was already empty.
|
||||
*/
|
||||
public Player removeOccupant() {
|
||||
Player previous = this.occupant;
|
||||
this.occupant = null;
|
||||
return previous;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Getters
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** The player currently occupying this tile, or null if empty. */
|
||||
public Player getOccupant() {
|
||||
return occupant;
|
||||
}
|
||||
|
||||
/**
|
||||
* The actions this tile grants to its occupant (e.g. [BOTTOM, UP]).
|
||||
* The returned list is unmodifiable.
|
||||
*/
|
||||
public List<Symbol> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
/** 0-based position index; determines the tile's letter and action set. */
|
||||
public int getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
/** The tile's letter label ('A'–'G'), useful for display and logging. */
|
||||
public char getLetter() {
|
||||
return letter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tile " + letter + " " + actions + (isEmpty() ? " [empty]" : " [" + occupant.getNickname() + "]");
|
||||
}
|
||||
}
|
||||
96
src/main/java/Server/Player.java
Normal file
@@ -0,0 +1,96 @@
|
||||
package Server;
|
||||
|
||||
import Server.Cards.CharacterCard;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Player {
|
||||
|
||||
//Attributes
|
||||
private String nickname;
|
||||
private TotemColor totemColor;
|
||||
private int foodTokens;
|
||||
private int prestigePoints;
|
||||
private Tribe playerTribe;
|
||||
|
||||
|
||||
//Constructor
|
||||
public Player(String nickname, TotemColor totemColor) {
|
||||
this.nickname = nickname;
|
||||
this.totemColor = totemColor;
|
||||
this.foodTokens = 0;
|
||||
this.prestigePoints = 0;
|
||||
this.playerTribe = new Tribe();
|
||||
}
|
||||
|
||||
//Methods
|
||||
public void addFood(int food){
|
||||
this.foodTokens += food;
|
||||
}
|
||||
|
||||
public boolean removeFood(int food){
|
||||
if (this.foodTokens >= food) {
|
||||
this.foodTokens -= food;
|
||||
return true; // Pagamento andato a buon fine
|
||||
}
|
||||
return false; // Il giocatore non ha abbastanza cibo, transazione negata, la quantità di cibo rimane quella di prima.
|
||||
}
|
||||
|
||||
public void addPrestigePoints(int prestige){
|
||||
this.prestigePoints += prestige;
|
||||
}
|
||||
|
||||
public int removePrestigePoints(int prestige){
|
||||
this.prestigePoints -= prestige;
|
||||
return prestigePoints;
|
||||
}
|
||||
|
||||
public void addCharacterToTribe(CharacterCard card) {
|
||||
int foodGained = this.playerTribe.addCharacter(card);
|
||||
this.addFood(foodGained);
|
||||
}
|
||||
|
||||
//Getters
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public int getFoodTokens() {
|
||||
return foodTokens;
|
||||
}
|
||||
|
||||
public int getPrestigePoints() {
|
||||
return prestigePoints;
|
||||
}
|
||||
|
||||
public TotemColor getTotemColor() {
|
||||
return totemColor;
|
||||
}
|
||||
|
||||
public Tribe getPlayerTribe() {
|
||||
return playerTribe;
|
||||
}
|
||||
|
||||
// two Player objects to be considered equal only by nickname
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Player player = (Player) o;
|
||||
return Objects.equals(nickname, player.nickname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(nickname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Player(" +
|
||||
"name='" + nickname + '\'' +
|
||||
", food=" + foodTokens +
|
||||
", pp=" + prestigePoints +
|
||||
')';
|
||||
}
|
||||
}
|
||||
7
src/main/java/Server/Symbol.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Server;
|
||||
|
||||
public enum Symbol {
|
||||
UP,
|
||||
DOWN,
|
||||
FOOD
|
||||
}
|
||||
21
src/main/java/Server/TotemColor.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package Server;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public enum TotemColor {
|
||||
RED(Color.RED),
|
||||
BLUE(Color.BLUE),
|
||||
YELLOW(Color.YELLOW),
|
||||
GREEN(Color.GREEN),
|
||||
PURPLE(Color.PURPLE);
|
||||
|
||||
private final Color fxColor;
|
||||
|
||||
TotemColor(Color fxColor) {
|
||||
this.fxColor = fxColor;
|
||||
}
|
||||
|
||||
public Color getFxColor() {
|
||||
return fxColor;
|
||||
}
|
||||
}
|
||||
239
src/main/java/Server/Tribe.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package Server;
|
||||
|
||||
import Server.Cards.BuildingCard;
|
||||
import Server.Cards.CharacterCard;
|
||||
import Server.Cards.CharacterType;
|
||||
import Server.Cards.Trigger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
public class Tribe {
|
||||
// Attributes
|
||||
private List<CharacterCard> characters;
|
||||
private List<BuildingCard> buildings;
|
||||
|
||||
// Constructor
|
||||
public Tribe() {
|
||||
this.characters = new ArrayList<>();
|
||||
this.buildings = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<CharacterCard> getCharacters() {
|
||||
return characters;
|
||||
}
|
||||
public List<BuildingCard> getBuildingCard() {
|
||||
return buildings;
|
||||
}
|
||||
// METODI
|
||||
|
||||
|
||||
public int addCharacter(CharacterCard card) {
|
||||
this.characters.add(card);
|
||||
if (card.getCharacterType().equals(CharacterType.HUNTER)) {
|
||||
return hunterGetFood(card);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Metodo per aggiungere un nuovo building alla tribù
|
||||
public void addBuilding(BuildingCard card) {
|
||||
this.buildings.add(card);
|
||||
}
|
||||
|
||||
// Metodo per ottenere lo sconto in cibo in base a quanti gatherer abbiamo nella tribù
|
||||
public int gathererDiscount() {
|
||||
int discount = 0;
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == CharacterType.GATHERER) {
|
||||
discount += 3; // i gatherers prendono sempre 3 cibi
|
||||
}
|
||||
}
|
||||
return discount;
|
||||
}
|
||||
|
||||
// Metodo per ottenere lo sconto totale sugli edifici grazie ai builder nella tribù
|
||||
public int buildersDiscount() {
|
||||
int discount = 0;
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == CharacterType.BUILDER) {
|
||||
discount += c.getIconValue(); // con getIconValue intendo lo sconto del costruttore
|
||||
}
|
||||
}
|
||||
return discount;
|
||||
}
|
||||
|
||||
// Metodo che conta quante stelle degli sciamani abbiamo in totale nella tribù
|
||||
public int shamansIcons() {
|
||||
int totalIcons = 0;
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == CharacterType.SHAMAN) {
|
||||
totalIcons += c.getIconValue();
|
||||
}
|
||||
}
|
||||
return totalIcons;
|
||||
}
|
||||
|
||||
// Metodo che restituisce il numero di artisti nella tribù
|
||||
public int artistsNumber() {
|
||||
return countCharactersByType(CharacterType.ARTIST);
|
||||
}
|
||||
|
||||
// Metodo che restituisce il numero di cacciatori nella tribù
|
||||
public int huntersNumber() {
|
||||
return countCharactersByType(CharacterType.HUNTER);
|
||||
}
|
||||
|
||||
// Metodo universale per contare le carte di un certo tipo all'interno della tribù
|
||||
public int countCharactersByType(CharacterType typeToCount) {
|
||||
int count = 0;
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == typeToCount) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Metodo che ritorna il numero totale di cibi ottenuti dopo aver pescato il cacciatore col cosciotto
|
||||
public int hunterGetFood(CharacterCard hunter) {
|
||||
return huntersNumber() * hunter.getIconValue(); // getIconValue = 1 se il cacciatore ha il cosciotto
|
||||
}
|
||||
|
||||
// Metodo che restituisce i punti finali degli inventori
|
||||
public int inventorsEndPoints() {
|
||||
int inventorCount = 0;
|
||||
List<Integer> uniqueInventions = new ArrayList<>();
|
||||
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == CharacterType.INVENTOR) {
|
||||
inventorCount++;
|
||||
|
||||
int inventionId = c.getIconValue(); // Usiamo l'iconValue del file cards.csv
|
||||
|
||||
// Se non abbiamo ancora contato questa invenzione, la aggiungiamo
|
||||
if (!uniqueInventions.contains(inventionId)) {
|
||||
uniqueInventions.add(inventionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return inventorCount * uniqueInventions.size();
|
||||
}
|
||||
|
||||
// Metodo che restituisce i punti finali degli artisti
|
||||
public int artistsEndPoints() {
|
||||
return (artistsNumber() / 2) * 10; // in java 1/2 fa 0
|
||||
}
|
||||
|
||||
// Metodo che restituisce i punti finali dei costruttori
|
||||
public int buildersEndPoints() {
|
||||
int points = 0;
|
||||
for (CharacterCard c : characters) {
|
||||
if (c.getCharacterType() == CharacterType.BUILDER) {
|
||||
points += c.getPrestigePoints();
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
// Metodo che restituisce i punti finali guadagnati grazie agli EFFETTI delle carte building
|
||||
private int buildingAbilitiesEndPoints() {
|
||||
int bonusPoints = 0;
|
||||
|
||||
for (BuildingCard b : buildings) {
|
||||
Trigger trigger = b.getAbilityTrigger(); // leggiamo il trigger della carta edificio
|
||||
|
||||
// Se la carta per qualche motivo non ha trigger, passiamo alla prossima
|
||||
if (trigger == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (trigger) {
|
||||
case ENDGAME_BUILDER_BONUS: // id carta: 107
|
||||
bonusPoints += (buildersEndPoints() * 2);
|
||||
break;
|
||||
|
||||
case ENDGAME_FOR_SIX: // id carta: 109
|
||||
// Prepariamo i contatori per tutti e 6 i tipi di characters
|
||||
int inv = 0, hun = 0, gat = 0, sha = 0, art = 0, bui = 0;
|
||||
|
||||
// contiamo le carte nella tribù
|
||||
for (CharacterCard c : characters) {
|
||||
switch (c.getCharacterType()) {
|
||||
case INVENTOR: inv++; break;
|
||||
case HUNTER: hun++; break;
|
||||
case GATHERER: gat++; break;
|
||||
case SHAMAN: sha++; break;
|
||||
case ARTIST: art++; break;
|
||||
case BUILDER: bui++; break;
|
||||
}
|
||||
}
|
||||
|
||||
// troviamo il numero di set completi
|
||||
int min1 = Math.min(inv, hun);
|
||||
int min2 = Math.min(gat, sha);
|
||||
int min3 = Math.min(art, bui);
|
||||
int completeSets = Math.min(Math.min(min1, min2), min3);
|
||||
|
||||
// aggiungiamo 6 punti prestigio per ogni set completo
|
||||
bonusPoints += (completeSets * 6);
|
||||
break;
|
||||
|
||||
case ENDGAME_BONUS_CHARACTER:
|
||||
|
||||
int id = b.getCardId(); // uso l'id della carta per capire che edificio è
|
||||
|
||||
// il numero id corrisponde al numero di pagina nel pdf
|
||||
if (id == 110) { // edificio dei cacciatori
|
||||
bonusPoints += countCharactersByType(CharacterType.HUNTER) * 3;
|
||||
}
|
||||
else if (id == 111) { // edificio dei gatherer
|
||||
bonusPoints += countCharactersByType(CharacterType.GATHERER) * 4;
|
||||
}
|
||||
else if (id == 112) { // edificio degli sciamani
|
||||
bonusPoints += countCharactersByType(CharacterType.SHAMAN) * 4;
|
||||
}
|
||||
else if (id == 113) { // edificio dei costruttori
|
||||
bonusPoints += countCharactersByType(CharacterType.BUILDER) * 4;
|
||||
}
|
||||
else if (id == 114) { // edificio degli artisti
|
||||
bonusPoints += countCharactersByType(CharacterType.ARTIST) * 4;
|
||||
}
|
||||
else if (id == 115) { // edificio degli inventori
|
||||
bonusPoints += countCharactersByType(CharacterType.INVENTOR) * 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case ENDGAME_BONUS_POINTS: // id carta: 117
|
||||
bonusPoints += 25; // dà 25 punti bonus
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bonusPoints;
|
||||
}
|
||||
|
||||
// Metodo che calcola i punti finali totali
|
||||
public int endPoints() {
|
||||
int total = 0;
|
||||
|
||||
// sommiamo i punti calcolati dai vari personaggi
|
||||
total += inventorsEndPoints();
|
||||
total += artistsEndPoints();
|
||||
total += buildersEndPoints();
|
||||
|
||||
// sommiamo i punti prestigio BASE di tutti gli edifici (EndPP)
|
||||
for (BuildingCard b : buildings) {
|
||||
total += b.getEndPP();
|
||||
}
|
||||
|
||||
// sommiamo gli effetti degli edifici sul punteggio finale, NON quelli durante la partita
|
||||
total += buildingAbilitiesEndPoints();
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
176
src/main/java/Server/TurnTile.java
Normal file
@@ -0,0 +1,176 @@
|
||||
package Server;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the Turn Order tile.
|
||||
*
|
||||
* Lifecycle per round:
|
||||
* 1. At round start, read positions top-to-bottom via nextToPlace() to know
|
||||
* which player places their totem on the offering track first.
|
||||
* 2. After a player resolves all their offering actions, they call returnTotem().
|
||||
* returnTotem() places them in the next free slot (top-to-bottom) and
|
||||
* immediately gives them the position reward/penalty.
|
||||
* 3. The order in which players called returnTotem() becomes the new turn
|
||||
* order for the NEXT round.
|
||||
* 4. resetTrack() is called at the start of each new round to restart the
|
||||
* placement cursor and the return-slot counter, WITHOUT clearing positions
|
||||
* (they already hold the new order from step 2-3).
|
||||
*/
|
||||
public class TurnTile {
|
||||
private static final Logger logger = LogManager.getLogger(TurnTile.class);
|
||||
private final Player[] positions; // current turn order; updated each round via returnTotem()
|
||||
private int nextFreeSlot; // next available slot for a returning totem
|
||||
private int placementCursor; // cursor used during the totem-placement phase
|
||||
|
||||
public TurnTile(int numP) {
|
||||
this.positions = new Player[numP];
|
||||
this.nextFreeSlot = 0;
|
||||
this.placementCursor = 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Setup
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Randomises the initial turn order. Call once during game setup.
|
||||
*/
|
||||
public void setInitialOrder(List<Player> players) {
|
||||
|
||||
List<Player> shuffled = new ArrayList<>(players);
|
||||
Collections.shuffle(shuffled);
|
||||
for (int i = 0; i < shuffled.size(); i++) {
|
||||
positions[i] = shuffled.get(i);
|
||||
}
|
||||
logger.info("setInitialOrder " + this);
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Totem-placement phase (phase 1 of a round)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the next player who must place their totem on the offering track
|
||||
* (following the current turn order, top to bottom).
|
||||
* Returns null when all players have already been served this round.
|
||||
*/
|
||||
public Player nextToPlace() {
|
||||
if (placementCursor < positions.length) {
|
||||
return positions[placementCursor++];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the player who is currently expected to place their totem
|
||||
* (i.e. the last player returned by nextToPlace), or null if none yet.
|
||||
*/
|
||||
public Player getLastPlacedPlayer() {
|
||||
if (placementCursor > 0) {
|
||||
return positions[placementCursor - 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Action-resolution phase (phase 2 of a round)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when a player has finished resolving all their offering actions.
|
||||
* Places the player's totem in the next free slot (determining next-round
|
||||
* order) and applies the food reward or penalty for that slot.
|
||||
*
|
||||
* Reward rules (from the rulebook):
|
||||
* - Slot 0 (first to return): +1 food (2p), +2 food (3-4p), +3 food (5p)
|
||||
* - Slot 1 (second to return, only 4-5 player games): +1 food
|
||||
* - Last slot: pay 1 food; if unable, lose 2 PP instead
|
||||
*
|
||||
* @return the slot index the player was placed in
|
||||
*/
|
||||
public int returnTotem(Player player) {
|
||||
logger.info("returnTotem " + player);
|
||||
int slot = nextFreeSlot;
|
||||
|
||||
// --- Position food reward ---
|
||||
int positionFood = 0;
|
||||
if (slot == 0) {
|
||||
positionFood = (positions.length == 2) ? 1
|
||||
: (positions.length == 5) ? 3
|
||||
: 2; // 3 or 4 players
|
||||
} else if (slot == 1 && positions.length >= 4) {
|
||||
positionFood = 1;
|
||||
}
|
||||
player.addFood(positionFood);
|
||||
|
||||
// --- Activate BONUS_FOOD_ENDTURN building if the player has it ---
|
||||
// (The building gives +1 extra food whenever the player lands on a food slot)
|
||||
BuildingManager.bonusEndTurn(player, positionFood);
|
||||
|
||||
// --- Last-slot penalty ---
|
||||
if (slot == positions.length - 1) {
|
||||
if (!player.removeFood(1)) {
|
||||
player.removePrestigePoints(2);
|
||||
}
|
||||
}
|
||||
|
||||
positions[slot] = player;
|
||||
nextFreeSlot++;
|
||||
|
||||
logger.info(player.getNickname() + " returned totem to slot " + slot
|
||||
+ (positionFood > 0 ? " and received " + positionFood + " food" : ""));
|
||||
return slot;
|
||||
}
|
||||
|
||||
public void leaveTurnTile(Player player) {
|
||||
for(int i=0;i<positions.length;i++)
|
||||
if (positions[i] !=null && positions[i].equals(player)) positions[i]=null;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Round reset
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Resets the cursors for a new round.
|
||||
* Does NOT clear positions: those already contain the new turn order
|
||||
* established during the previous round's returnTotem() calls.
|
||||
*/
|
||||
public void resetTrack() {
|
||||
nextFreeSlot = 0;
|
||||
placementCursor = 0;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Getters
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the full positions array (turn order).
|
||||
* Index 0 = first to act, last index = last to act.
|
||||
*/
|
||||
public Player[] getPositions() {
|
||||
return positions;
|
||||
}
|
||||
|
||||
/** How many players have already returned their totem this round. */
|
||||
public int getReturnedCount() {
|
||||
return nextFreeSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TurnTile{" +
|
||||
"positions=" + Arrays.toString(positions) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
89
src/main/java/Server/TurnTileOld.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package Server;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TurnTileOld {
|
||||
|
||||
private final Player[] positions;
|
||||
private int place;
|
||||
private int currentplayer;
|
||||
|
||||
|
||||
public TurnTileOld(int numP) {
|
||||
|
||||
this.positions = new Player[numP];
|
||||
this.place = 0;
|
||||
this.currentplayer = 0;
|
||||
|
||||
}
|
||||
|
||||
public Player[] startOrder (List<Player> players){
|
||||
|
||||
for (Player p: players){
|
||||
|
||||
positions[place]=p;
|
||||
place++;
|
||||
|
||||
}
|
||||
//Collections.shuffle(Arrays.asList(positions));
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
public Player giveReward (Player player){
|
||||
|
||||
if (player == positions[positions.length-1]){
|
||||
|
||||
if (!player.removeFood(1)){
|
||||
|
||||
player.removePrestigePoints(2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
else if (player == positions[0]){
|
||||
|
||||
if (positions.length==2){
|
||||
|
||||
player.addFood(1);
|
||||
}
|
||||
else if (positions.length == 5){
|
||||
|
||||
player.addFood(3);
|
||||
}
|
||||
else{
|
||||
|
||||
player.addFood(2);
|
||||
}
|
||||
}
|
||||
|
||||
else if (player == positions[1] && positions.length >= 4){
|
||||
|
||||
player.addFood(1);
|
||||
}
|
||||
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public Player nextPlayer() {
|
||||
|
||||
currentplayer++;
|
||||
if (currentplayer== positions.length){
|
||||
currentplayer=0;
|
||||
}
|
||||
|
||||
return positions[currentplayer];
|
||||
}
|
||||
|
||||
public Player addPlayer(Player player){
|
||||
|
||||
positions[currentplayer]=player;
|
||||
|
||||
return positions[currentplayer];
|
||||
|
||||
}
|
||||
}
|
||||
7
src/main/java/Server/Utils/EventsManagerException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Server.Utils;
|
||||
|
||||
public class EventsManagerException extends RuntimeException {
|
||||
public EventsManagerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
7
src/main/java/Server/Utils/GameException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Server.Utils;
|
||||
|
||||
public class GameException extends RuntimeException {
|
||||
public GameException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
7
src/main/java/Server/Utils/LoadingCardsException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Server.Utils;
|
||||
|
||||
public class LoadingCardsException extends RuntimeException {
|
||||
public LoadingCardsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
18
src/main/java/module-info.java
Normal file
@@ -0,0 +1,18 @@
|
||||
module org.example.mesosll07 {
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
|
||||
requires org.controlsfx.controls;
|
||||
requires javafx.swing;
|
||||
requires org.apache.pdfbox;
|
||||
requires org.apache.logging.log4j;
|
||||
|
||||
opens org.example.mesosll07 to javafx.fxml;
|
||||
exports org.example.mesosll07;
|
||||
exports Server;
|
||||
opens Server to javafx.fxml;
|
||||
exports Server.Cards;
|
||||
opens Server.Cards to javafx.fxml;
|
||||
exports Server.Automaton;
|
||||
opens Server.Automaton to javafx.fxml;
|
||||
}
|
||||
208
src/main/java/org/example/mesosll07/DeckGridApp.java
Normal file
@@ -0,0 +1,208 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class DeckGridApp extends Application {
|
||||
|
||||
// Inizializza il Logger
|
||||
private static final Logger logger = LogManager.getLogger(DeckGridApp.class);
|
||||
|
||||
private PDDocument document;
|
||||
private PDFRenderer pdfRenderer;
|
||||
private int totalCards = 0;
|
||||
|
||||
// Contenitori per le tre righe
|
||||
private HBox topRow;
|
||||
private HBox centerRow;
|
||||
private HBox bottomRow;
|
||||
private Button btnShuffle;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
primaryStage.setTitle("Tavolo da Gioco - Carte Casuali");
|
||||
logger.info("Avvio dell'applicazione JavaFX");
|
||||
|
||||
// Pulsanti
|
||||
Button btnLoad = new Button("Carica PDF Mazzo");
|
||||
btnShuffle = new Button("Rimescola Carte");
|
||||
btnShuffle.setDisable(true); // Disabilitato finché non si carica un PDF
|
||||
|
||||
btnLoad.setOnAction(e -> loadPdf(primaryStage));
|
||||
btnShuffle.setOnAction(e -> distributeRandomCards());
|
||||
|
||||
HBox topMenu = new HBox(15, btnLoad, btnShuffle);
|
||||
topMenu.setAlignment(Pos.CENTER);
|
||||
topMenu.setPadding(new Insets(10));
|
||||
|
||||
// Setup delle tre righe (spazio tra le carte impostato a 10px)
|
||||
topRow = createRowContainer();
|
||||
centerRow = createRowContainer();
|
||||
bottomRow = createRowContainer();
|
||||
|
||||
// Contenitore verticale per le righe
|
||||
VBox tableArea = new VBox(30,
|
||||
new Label("TOP (8 Carte):"), topRow,
|
||||
new Label("CENTER (6 Carte):"), centerRow,
|
||||
new Label("BOTTOM (7 Carte):"), bottomRow
|
||||
);
|
||||
tableArea.setAlignment(Pos.CENTER);
|
||||
tableArea.setPadding(new Insets(20));
|
||||
|
||||
// Mettiamo il tavolo in uno ScrollPane nel caso le carte siano troppo grandi per lo schermo
|
||||
ScrollPane scrollPane = new ScrollPane(tableArea);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(topMenu);
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
// Finestra un po' più grande per contenere tutte queste carte
|
||||
Scene scene = new Scene(root, 1200, 800);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
private HBox createRowContainer() {
|
||||
HBox row = new HBox(15); // 15px di spazio tra una carta e l'altra
|
||||
row.setAlignment(Pos.CENTER);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void loadPdf(Stage stage) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Seleziona il PDF del mazzo di carte");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("File PDF", "*.pdf"));
|
||||
File file = fileChooser.showOpenDialog(stage);
|
||||
|
||||
if (file != null) {
|
||||
try {
|
||||
if (document != null) {
|
||||
document.close();
|
||||
}
|
||||
logger.info("Caricamento file PDF: {}", file.getAbsolutePath());
|
||||
document = PDDocument.load(file);
|
||||
pdfRenderer = new PDFRenderer(document);
|
||||
totalCards = document.getNumberOfPages();
|
||||
|
||||
logger.info("PDF caricato. Pagine totali (carte): {}", totalCards);
|
||||
btnShuffle.setDisable(false);
|
||||
|
||||
// Distribuisci le carte per la prima volta
|
||||
distributeRandomCards();
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore nel caricamento del PDF", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void distributeRandomCards() {
|
||||
if (document == null || totalCards == 0) return;
|
||||
|
||||
logger.info("Inizio rimescolamento e distribuzione carte...");
|
||||
|
||||
// Pulisce il tavolo dalle carte precedenti
|
||||
topRow.getChildren().clear();
|
||||
centerRow.getChildren().clear();
|
||||
bottomRow.getChildren().clear();
|
||||
|
||||
// 1. Crea una lista con tutti gli indici (da 0 al numero totale di pagine)
|
||||
List<Integer> deckIndices = new ArrayList<>();
|
||||
for (int i = 0; i < totalCards; i++) {
|
||||
deckIndices.add(i);
|
||||
}
|
||||
|
||||
// 2. Mescola la lista (Simula la mescolata del mazzo)
|
||||
Collections.shuffle(deckIndices);
|
||||
|
||||
// 3. Distribuisci nelle righe (evitando IndexOutOfBounds se il PDF ha meno di 21 pagine)
|
||||
// Riga Top: 8 carte
|
||||
populateRow(topRow, deckIndices, 0, 8);
|
||||
|
||||
// Riga Center: 6 carte
|
||||
populateRow(centerRow, deckIndices, 8, 6);
|
||||
|
||||
// Riga Bottom: 7 carte
|
||||
populateRow(bottomRow, deckIndices, 14, 7);
|
||||
|
||||
logger.info("Distribuzione completata.");
|
||||
}
|
||||
|
||||
private void populateRow(HBox row, List<Integer> deckIndices, int startIndex, int numberOfCards) {
|
||||
for (int i = 0; i < numberOfCards; i++) {
|
||||
int actualIndex = startIndex + i;
|
||||
|
||||
// Se abbiamo esaurito le carte nel PDF, interrompiamo
|
||||
if (actualIndex >= deckIndices.size()) {
|
||||
logger.warn("Il PDF non ha abbastanza carte per riempire questa riga.");
|
||||
break;
|
||||
}
|
||||
|
||||
int pageNumber = deckIndices.get(actualIndex);
|
||||
ImageView cardImage = createCardImageView(pageNumber);
|
||||
|
||||
if (cardImage != null) {
|
||||
row.getChildren().add(cardImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ImageView createCardImageView(int pageIndex) {
|
||||
try {
|
||||
// Renderizza la pagina. Usiamo 100 DPI invece di 150 per risparmiare RAM,
|
||||
// dato che ora stiamo generando 21 immagini contemporaneamente.
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(pageIndex, 100);
|
||||
Image fxImage = SwingFXUtils.toFXImage(bim, null);
|
||||
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
// Impostiamo l'altezza fissa per le carte, in modo che stiano sullo schermo
|
||||
imageView.setFitHeight(180);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 5, 0, 0, 0);");
|
||||
|
||||
return imageView;
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore durante il rendering della pagina {}", pageIndex, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (document != null) {
|
||||
document.close();
|
||||
logger.info("Documento PDF chiuso correttamente.");
|
||||
}
|
||||
logger.info("Applicazione terminata.");
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
263
src/main/java/org/example/mesosll07/DeckGridApp2.java
Normal file
@@ -0,0 +1,263 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DeckGridApp2 extends Application {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(DeckGridApp2.class);
|
||||
|
||||
private PDDocument document;
|
||||
private PDFRenderer pdfRenderer;
|
||||
private int totalCards = 0;
|
||||
|
||||
// Contenitori Layout
|
||||
private HBox topRow;
|
||||
private HBox centerRow;
|
||||
private HBox bottomRow;
|
||||
private HBox playersArea; // NUOVO: Area per i giocatori
|
||||
private Button btnShuffle;
|
||||
|
||||
// --- LOGICA DI GIOCO SIMULATA ---
|
||||
public enum CardType {
|
||||
CACCIA, UTENSILI, RITUALI, COSTRUZIONI
|
||||
}
|
||||
|
||||
public static class Card {
|
||||
int pdfPageIndex;
|
||||
CardType type;
|
||||
|
||||
public Card(int pdfPageIndex, CardType type) {
|
||||
this.pdfPageIndex = pdfPageIndex;
|
||||
this.type = type;
|
||||
}
|
||||
public CardType getType() { return type; }
|
||||
}
|
||||
|
||||
public static class Player {
|
||||
String name;
|
||||
int food;
|
||||
int money;
|
||||
List<Card> hand = new ArrayList<>();
|
||||
|
||||
public Player(String name, int food, int money) {
|
||||
this.name = name;
|
||||
this.food = food;
|
||||
this.money = money;
|
||||
}
|
||||
public List<Card> getHand() { return hand; }
|
||||
}
|
||||
// --------------------------------
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
primaryStage.setTitle("Tavolo da Gioco - Board & Players");
|
||||
|
||||
Button btnLoad = new Button("Carica PDF Mazzo");
|
||||
btnShuffle = new Button("Rimescola & Distribuisci");
|
||||
btnShuffle.setDisable(true);
|
||||
|
||||
btnLoad.setOnAction(e -> loadPdf(primaryStage));
|
||||
btnShuffle.setOnAction(e -> distributeAll());
|
||||
|
||||
HBox topMenu = new HBox(15, btnLoad, btnShuffle);
|
||||
topMenu.setAlignment(Pos.CENTER);
|
||||
topMenu.setPadding(new Insets(10));
|
||||
|
||||
topRow = createRowContainer();
|
||||
centerRow = createRowContainer();
|
||||
bottomRow = createRowContainer();
|
||||
|
||||
// NUOVO: Inizializza l'area giocatori
|
||||
playersArea = new HBox(30);
|
||||
playersArea.setAlignment(Pos.CENTER);
|
||||
playersArea.setPadding(new Insets(20));
|
||||
|
||||
VBox tableArea = new VBox(20,
|
||||
new Label("TOP (8 Carte):"), topRow,
|
||||
new Label("CENTER (6 Carte):"), centerRow,
|
||||
new Label("BOTTOM (7 Carte):"), bottomRow,
|
||||
new Label("--- SITUAZIONE GIOCATORI ---"), playersArea
|
||||
);
|
||||
tableArea.setAlignment(Pos.CENTER);
|
||||
tableArea.setPadding(new Insets(20));
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane(tableArea);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(topMenu);
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
Scene scene = new Scene(root, 1300, 900);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
private HBox createRowContainer() {
|
||||
HBox row = new HBox(10);
|
||||
row.setAlignment(Pos.CENTER);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void loadPdf(Stage stage) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("File PDF", "*.pdf"));
|
||||
File file = fileChooser.showOpenDialog(stage);
|
||||
|
||||
if (file != null) {
|
||||
try {
|
||||
if (document != null) document.close();
|
||||
document = PDDocument.load(file);
|
||||
pdfRenderer = new PDFRenderer(document);
|
||||
totalCards = document.getNumberOfPages();
|
||||
btnShuffle.setDisable(false);
|
||||
|
||||
distributeAll();
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore PDF", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void distributeAll() {
|
||||
if (document == null || totalCards == 0) return;
|
||||
|
||||
topRow.getChildren().clear();
|
||||
centerRow.getChildren().clear();
|
||||
bottomRow.getChildren().clear();
|
||||
playersArea.getChildren().clear();
|
||||
|
||||
List<Integer> deck = new ArrayList<>();
|
||||
for (int i = 0; i < totalCards; i++) deck.add(i);
|
||||
Collections.shuffle(deck);
|
||||
|
||||
// Distribuisce sul tavolo le prime 21 carte
|
||||
int index = 0;
|
||||
index = populateTable(topRow, deck, index, 8);
|
||||
index = populateTable(centerRow, deck, index, 6);
|
||||
index = populateTable(bottomRow, deck, index, 7);
|
||||
|
||||
// NUOVO: Simula 2 giocatori con le carte rimanenti
|
||||
Random rand = new Random();
|
||||
List<Player> players = Arrays.asList(
|
||||
new Player("Giocatore 1 (Tribù Rossa)", rand.nextInt(15), rand.nextInt(50)),
|
||||
new Player("Giocatore 2 (Tribù Blu)", rand.nextInt(15), rand.nextInt(50))
|
||||
);
|
||||
|
||||
// Assegna 5 carte a caso a ogni giocatore e simula il loro Tipo
|
||||
CardType[] types = CardType.values();
|
||||
for (Player p : players) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (index < deck.size()) {
|
||||
CardType randomType = types[rand.nextInt(types.length)];
|
||||
p.getHand().add(new Card(deck.get(index), randomType));
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Renderizza i giocatori nella UI
|
||||
renderPlayers(players);
|
||||
}
|
||||
|
||||
private int populateTable(HBox row, List<Integer> deck, int startIndex, int amount) {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
if (startIndex >= deck.size()) break;
|
||||
ImageView card = createCardImage(deck.get(startIndex), 150); // Altezza 150px
|
||||
if (card != null) row.getChildren().add(card);
|
||||
startIndex++;
|
||||
}
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
// --- NUOVO: RENDERIZZAZIONE GIOCATORI E RAGGRUPPAMENTO (JAVA 8) ---
|
||||
private void renderPlayers(List<Player> players) {
|
||||
for (Player player : players) {
|
||||
VBox playerBox = new VBox(10);
|
||||
playerBox.setPadding(new Insets(15));
|
||||
playerBox.setStyle("-fx-border-color: #555; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-color: #f9f9f9; -fx-background-radius: 10;");
|
||||
|
||||
// Intestazione Giocatore
|
||||
Label nameLbl = new Label(player.name);
|
||||
nameLbl.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
|
||||
// Risorse Cibo e Soldi
|
||||
Label statsLbl = new Label("🍖 Cibo: " + player.food + " | 💰 Money: " + player.money);
|
||||
statsLbl.setTextFill(Color.DARKRED);
|
||||
statsLbl.setFont(Font.font("System", FontWeight.BOLD, 14));
|
||||
|
||||
playerBox.getChildren().addAll(nameLbl, statsLbl);
|
||||
|
||||
// JAVA 8 MAGIA: Raggruppa le carte del giocatore per Tipo!
|
||||
Map<CardType, List<Card>> groupedCards = player.getHand().stream()
|
||||
.collect(Collectors.groupingBy(Card::getType));
|
||||
|
||||
// Itera sui gruppi creati e genera la grafica
|
||||
groupedCards.forEach((type, cardsOfType) -> {
|
||||
Label typeLbl = new Label("Tipo: " + type.name() + " (" + cardsOfType.size() + ")");
|
||||
typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12));
|
||||
|
||||
HBox cardImagesRow = new HBox(5);
|
||||
for (Card c : cardsOfType) {
|
||||
// Carte più piccole (90px) per l'area giocatore
|
||||
ImageView img = createCardImage(c.pdfPageIndex, 90);
|
||||
if (img != null) cardImagesRow.getChildren().add(img);
|
||||
}
|
||||
|
||||
playerBox.getChildren().addAll(typeLbl, cardImagesRow);
|
||||
});
|
||||
|
||||
playersArea.getChildren().add(playerBox);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageView createCardImage(int pageIndex, int height) {
|
||||
try {
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(pageIndex, 100);
|
||||
Image fxImage = SwingFXUtils.toFXImage(bim, null);
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
return imageView;
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore rendering pagina {}", pageIndex, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (document != null) document.close();
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
271
src/main/java/org/example/mesosll07/DeckGridAppFX.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import Server.Automaton.Game;
|
||||
import Server.Era;
|
||||
import Server.Player;
|
||||
import Server.TotemColor;
|
||||
import javafx.application.Application;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import Server.Cards.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
public class DeckGridAppFX extends Application {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(DeckGridAppFX.class);
|
||||
|
||||
private PDDocument documentFront;
|
||||
private PDDocument documentBack;
|
||||
private PDFRenderer pdfRendererFront;
|
||||
private PDFRenderer pdfRendererBack;
|
||||
private int totalCards = 0;
|
||||
|
||||
// Contenitori Layout
|
||||
private HBox topRow;
|
||||
private HBox centerRow;
|
||||
private HBox bottomRow;
|
||||
private HBox playersArea; // NUOVO: Area per i giocatori
|
||||
private Button btnShuffle;
|
||||
|
||||
|
||||
|
||||
// --------------------------------
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
|
||||
List<Player> players = new ArrayList<>();
|
||||
players.add(new Player("Yoshi", TotemColor.YELLOW));
|
||||
players.add(new Player("Pippo", TotemColor.PURPLE));
|
||||
players.add(new Player("John", TotemColor.RED));
|
||||
players.add(new Player("Baggio", TotemColor.BLUE));
|
||||
players.add(new Player("Gino", TotemColor.GREEN));
|
||||
|
||||
Game game = new Game(players);
|
||||
String fileCards="/home/lorenzo/dev/Mesos2/src/main/resources/files/cards.csv";
|
||||
String fileCardsImgFront="/home/lorenzo/dev/Mesos2/src/main/resources/files/Cards_total_front_PROMO.pdf";
|
||||
String fileCardsImgCover="/home/lorenzo/dev/Mesos2/src/main/resources/files/Cards_total_back_PROMO.pdf";
|
||||
|
||||
try {
|
||||
|
||||
File fileFront = new File(fileCardsImgFront);
|
||||
File fileBack = new File(fileCardsImgCover);
|
||||
|
||||
documentFront = PDDocument.load(fileFront);
|
||||
pdfRendererFront = new PDFRenderer(documentFront);
|
||||
documentBack = PDDocument.load(fileBack);
|
||||
pdfRendererBack = new PDFRenderer(documentBack);
|
||||
|
||||
} catch ( IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
game.newGame(fileCards);
|
||||
|
||||
|
||||
|
||||
primaryStage.setTitle("Tavolo da Gioco - Board & Players");
|
||||
|
||||
|
||||
btnShuffle = new Button("Rimescola & Distribuisci");
|
||||
btnShuffle.setDisable(true);
|
||||
|
||||
|
||||
btnShuffle.setOnAction(e -> distributeAll(game));
|
||||
|
||||
HBox topMenu = new HBox(15, btnShuffle);
|
||||
topMenu.setAlignment(Pos.CENTER);
|
||||
topMenu.setPadding(new Insets(10));
|
||||
|
||||
topRow = createRowContainer();
|
||||
centerRow = createRowContainer();
|
||||
bottomRow = createRowContainer();
|
||||
|
||||
|
||||
|
||||
// NUOVO: Inizializza l'area giocatori
|
||||
playersArea = new HBox(30);
|
||||
playersArea.setAlignment(Pos.CENTER);
|
||||
playersArea.setPadding(new Insets(20));
|
||||
|
||||
distributeAll(game);
|
||||
VBox tableArea = new VBox(20,
|
||||
new Label("TOP"), topRow,
|
||||
new Label("CENTER "), centerRow,
|
||||
new Label("DOWN"), bottomRow,
|
||||
new Label("--- SITUAZIONE GIOCATORI ---"), playersArea
|
||||
);
|
||||
tableArea.setAlignment(Pos.CENTER);
|
||||
tableArea.setPadding(new Insets(20));
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane(tableArea);
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(topMenu);
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
Scene scene = new Scene(root, 1300, 900);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
private HBox createRowContainer() {
|
||||
HBox row = new HBox(10);
|
||||
row.setAlignment(Pos.CENTER);
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void distributeAll(Game game) {
|
||||
if (documentFront == null) return;
|
||||
|
||||
topRow.getChildren().clear();
|
||||
centerRow.getChildren().clear();
|
||||
bottomRow.getChildren().clear();
|
||||
playersArea.getChildren().clear();
|
||||
|
||||
List<Integer> deck = new ArrayList<>();
|
||||
for (int i = 0; i < 118; i++) deck.add(i);
|
||||
Collections.shuffle(deck);
|
||||
|
||||
// Distribuisce sul tavolo le prime 21 carte
|
||||
int index = 0;
|
||||
index = populateTable(topRow, deck, index, 8);
|
||||
index = populateTable(centerRow, deck, index, 6);
|
||||
index = populateTable(bottomRow, deck, index, 7);
|
||||
|
||||
// NUOVO: Simula 2 giocatori con le carte rimanenti
|
||||
Random rand = new Random();
|
||||
List<Player> players =game.getPlayers();
|
||||
|
||||
// Assegna 5 carte a caso a ogni giocatore e simula il loro Tipo
|
||||
|
||||
|
||||
|
||||
for (Player p : players) {
|
||||
for (int j=0;j<4;j++){
|
||||
int idx = rand.nextInt(game.getGameBoard().getCardDeck().getTribeDeck().size());
|
||||
Card c = game.getGameBoard().getCardDeck().drawTribeOne();
|
||||
p.getPlayerTribe().addCharacter((CharacterCard) c);
|
||||
}
|
||||
for (int j=0;j<2;j++){
|
||||
int idx = rand.nextInt(game.getGameBoard().getCardDeck().getBuildingDeck(Era.I).size());
|
||||
Card c = game.getGameBoard().getCardDeck().drawBuildingOne(Era.I);
|
||||
p.getPlayerTribe().addBuilding((BuildingCard) c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Renderizza i giocatori nella UI
|
||||
renderPlayers(players);
|
||||
}
|
||||
|
||||
private int populateTable(HBox row, List<Integer> deck, int startIndex, int amount) {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
if (startIndex >= deck.size()) break;
|
||||
ImageView card = createCardImage(deck.get(startIndex), 150, true); // Altezza 150px
|
||||
if (card != null) row.getChildren().add(card);
|
||||
startIndex++;
|
||||
}
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
// --- NUOVO: RENDERIZZAZIONE GIOCATORI E RAGGRUPPAMENTO (JAVA 8) ---
|
||||
private void renderPlayers(List<Player> players) {
|
||||
for (Player player : players) {
|
||||
VBox playerBox = new VBox(10);
|
||||
playerBox.setPadding(new Insets(15));
|
||||
playerBox.setStyle("-fx-border-color: #555; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-color: #f9f9f9; -fx-background-radius: 10;");
|
||||
|
||||
// Intestazione Giocatore
|
||||
Label nameLbl = new Label(player.getNickname());
|
||||
nameLbl.setFont(Font.font("System", FontWeight.BOLD, 16));
|
||||
|
||||
// Risorse Cibo e Soldi
|
||||
Label statsLbl = new Label("🍖 Cibo: " + player.getFoodTokens() + " | 💰 Money: " + player.getPrestigePoints());
|
||||
statsLbl.setTextFill(Color.DARKRED);
|
||||
statsLbl.setFont(Font.font("System", FontWeight.BOLD, 14));
|
||||
|
||||
playerBox.getChildren().addAll(nameLbl, statsLbl);
|
||||
|
||||
// JAVA 8 MAGIA: Raggruppa le carte del giocatore per Tipo!
|
||||
Map<CharacterType, List<CharacterCard>> groupedCards = player.getPlayerTribe().getCharacters().stream()
|
||||
.collect(Collectors.groupingBy(CharacterCard::getCharacterType));
|
||||
|
||||
// Itera sui gruppi creati e genera la grafica
|
||||
groupedCards.forEach((type, cardsOfType) -> {
|
||||
Label typeLbl = new Label("Tipo: " + type.name() + " (" + cardsOfType.size() + ")");
|
||||
typeLbl.setFont(Font.font("System", FontWeight.NORMAL, 12));
|
||||
|
||||
HBox cardImagesRow = new HBox(5);
|
||||
for (Card c : cardsOfType) {
|
||||
// Carte più piccole (90px) per l'area giocatore
|
||||
ImageView img = createCardImage(c.getCardId(), 90, true);
|
||||
if (img != null) cardImagesRow.getChildren().add(img);
|
||||
}
|
||||
|
||||
playerBox.getChildren().addAll(typeLbl, cardImagesRow);
|
||||
});
|
||||
|
||||
playersArea.getChildren().add(playerBox);
|
||||
}
|
||||
}
|
||||
|
||||
private ImageView createCardImage(int pageIndex, int height, boolean front) {
|
||||
try {
|
||||
BufferedImage bim =null;
|
||||
if (front) bim = pdfRendererFront.renderImageWithDPI(pageIndex, 100);
|
||||
else bim = pdfRendererBack.renderImageWithDPI(pageIndex, 100);
|
||||
|
||||
Image fxImage = SwingFXUtils.toFXImage(bim, null);
|
||||
ImageView imageView = new ImageView(fxImage);
|
||||
imageView.setFitHeight(height);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.4), 4, 0, 0, 0);");
|
||||
return imageView;
|
||||
} catch (IOException e) {
|
||||
logger.error("Errore rendering pagina {}", pageIndex, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (documentFront != null) documentFront.close();
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
159
src/main/java/org/example/mesosll07/DeckViewerFX.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import Server.Automaton.Game;
|
||||
import Server.Player;
|
||||
import Server.TotemColor;
|
||||
import javafx.application.Application;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DeckViewerFX extends Application {
|
||||
|
||||
private PDDocument document;
|
||||
private PDFRenderer pdfRenderer;
|
||||
private int currentIndex = 0;
|
||||
private int totalCards = 0;
|
||||
|
||||
private ImageView cardImageView;
|
||||
private Label pageLabel;
|
||||
private Button btnPrev;
|
||||
private Button btnNext;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
|
||||
List<Player> players = new ArrayList<>();
|
||||
players.add(new Player("Yoshi", TotemColor.YELLOW));
|
||||
players.add(new Player("Pippo", TotemColor.PURPLE));
|
||||
players.add(new Player("John", TotemColor.RED));
|
||||
players.add(new Player("Baggio", TotemColor.BLUE));
|
||||
players.add(new Player("Gino", TotemColor.GREEN));
|
||||
|
||||
Game game = new Game(players);
|
||||
game.newGame("/home/lorenzo/dev/Mesos2/src/main/resources/files/cards.csv");
|
||||
|
||||
primaryStage.setTitle("Visualizzatore Mazzo di Carte (PDF)");
|
||||
|
||||
// Componente per mostrare la carta
|
||||
cardImageView = new ImageView();
|
||||
cardImageView.setFitHeight(500); // Altezza fissa, larghezza in proporzione
|
||||
cardImageView.setPreserveRatio(true);
|
||||
cardImageView.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 10, 0, 0, 0);");
|
||||
|
||||
// Controlli UI
|
||||
btnPrev = new Button("⬅ Carta Precedente");
|
||||
btnNext = new Button("Carta Successiva ➡");
|
||||
pageLabel = new Label("Nessun mazzo caricato");
|
||||
Button btnLoad = new Button("Carica PDF Mazzo");
|
||||
|
||||
// Azioni dei pulsanti
|
||||
btnLoad.setOnAction(e -> loadPdf(primaryStage));
|
||||
btnPrev.setOnAction(e -> showCard(currentIndex - 1));
|
||||
btnNext.setOnAction(e -> showCard(currentIndex + 1));
|
||||
|
||||
updateButtons();
|
||||
|
||||
// Layout
|
||||
HBox controls = new HBox(15, btnPrev, pageLabel, btnNext);
|
||||
controls.setAlignment(Pos.CENTER);
|
||||
|
||||
VBox topBox = new VBox(10, btnLoad);
|
||||
topBox.setAlignment(Pos.CENTER);
|
||||
topBox.setPadding(new Insets(10));
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(topBox);
|
||||
root.setCenter(cardImageView);
|
||||
root.setBottom(controls);
|
||||
BorderPane.setMargin(cardImageView, new Insets(20));
|
||||
BorderPane.setMargin(controls, new Insets(20));
|
||||
|
||||
Scene scene = new Scene(root, 600, 700);
|
||||
primaryStage.setScene(scene);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
private void loadPdf(Stage stage) {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Seleziona il PDF del mazzo di carte");
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("File PDF", "*.pdf"));
|
||||
File file = fileChooser.showOpenDialog(stage);
|
||||
|
||||
if (file != null) {
|
||||
try {
|
||||
// Chiude il documento precedente se esiste
|
||||
if (document != null) {
|
||||
document.close();
|
||||
}
|
||||
|
||||
// Carica il nuovo PDF
|
||||
document = PDDocument.load(file);
|
||||
pdfRenderer = new PDFRenderer(document);
|
||||
totalCards = document.getNumberOfPages();
|
||||
|
||||
// Mostra la prima carta
|
||||
showCard(0);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
pageLabel.setText("Errore nel caricamento del file!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showCard(int index) {
|
||||
if (document == null || index < 0 || index >= totalCards) return;
|
||||
|
||||
try {
|
||||
// Renderizza la pagina del PDF in un'immagine BufferedImage (DPI 150 per buona qualità)
|
||||
BufferedImage bim = pdfRenderer.renderImageWithDPI(index, 150);
|
||||
|
||||
// Converte l'immagine di AWT (Swing) in un'immagine JavaFX
|
||||
Image fxImage = SwingFXUtils.toFXImage(bim, null);
|
||||
|
||||
cardImageView.setImage(fxImage);
|
||||
currentIndex = index;
|
||||
pageLabel.setText("Carta " + (currentIndex + 1) + " di " + totalCards);
|
||||
|
||||
updateButtons();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
btnPrev.setDisable(document == null || currentIndex == 0);
|
||||
btnNext.setDisable(document == null || currentIndex == totalCards - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
// Importante: chiudere il documento per liberare la memoria quando l'app si chiude
|
||||
if (document != null) {
|
||||
document.close();
|
||||
}
|
||||
super.stop();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
19
src/main/java/org/example/mesosll07/HelloApplication.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HelloApplication extends Application {
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
|
||||
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
|
||||
stage.setTitle("Hello!");
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
}
|
||||
}
|
||||
14
src/main/java/org/example/mesosll07/HelloController.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
public class HelloController {
|
||||
@FXML
|
||||
private Label welcomeText;
|
||||
|
||||
@FXML
|
||||
protected void onHelloButtonClick() {
|
||||
welcomeText.setText("Welcome to JavaFX Application!");
|
||||
}
|
||||
}
|
||||
9
src/main/java/org/example/mesosll07/Launcher.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package org.example.mesosll07;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
public class Launcher {
|
||||
public static void main(String[] args) {
|
||||
Application.launch(HelloApplication.class, args);
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/files/Cards_total_back_PROMO.pdf
Normal file
BIN
src/main/resources/files/Cards_total_front_PROMO.pdf
Normal file
BIN
src/main/resources/files/Start_2P.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/main/resources/files/Start_3P.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/main/resources/files/Start_4P.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/main/resources/files/Start_5P.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
117
src/main/resources/files/cards.csv
Normal file
@@ -0,0 +1,117 @@
|
||||
C;1;0;I;HUNTER;1;0
|
||||
C;2;0;I;HUNTER;1;0
|
||||
C;3;0;I;HUNTER;0;0
|
||||
C;4;3;I;HUNTER;0;0
|
||||
C;5;3;I;HUNTER;0;0
|
||||
C;6;0;I;BUILDER;1;3
|
||||
C;7;0;I;BUILDER;2;0
|
||||
C;8;5;I;BUILDER;2;1
|
||||
C;9;0;I;BUILDER;1;2
|
||||
C;10;0;I;GATHERER;3;0
|
||||
C;11;0;I;GATHERER;3;0
|
||||
C;12;3;I;GATHERER;3;0
|
||||
C;13;5;I;GATHERER;3;0
|
||||
C;14;0;I;ARTIST;0;0
|
||||
C;15;0;I;ARTIST;0;0
|
||||
C;16;0;I;ARTIST;0;0
|
||||
C;17;3;I;ARTIST;0;0
|
||||
C;18;4;I;ARTIST;0;0
|
||||
C;19;0;I;INVENTOR;8;0
|
||||
C;20;0;I;INVENTOR;0;0
|
||||
C;21;0;I;INVENTOR;1;0
|
||||
C;22;0;I;INVENTOR;9;0
|
||||
C;23;4;I;INVENTOR;5;0
|
||||
C;24;4;I;INVENTOR;7;0
|
||||
C;25;4;I;INVENTOR;4;0
|
||||
C;26;5;I;SHAMAN;2;0
|
||||
C;27;0;I;SHAMAN;2;0
|
||||
C;28;0;I;SHAMAN;1;0
|
||||
C;29;4;I;SHAMAN;1;0
|
||||
C;30;0;II;HUNTER;0;0
|
||||
C;31;0;II;HUNTER;0;0
|
||||
C;32;3;II;HUNTER;1;0
|
||||
C;33;0;II;HUNTER;1;0
|
||||
C;34;4;II;HUNTER;1;0
|
||||
C;35;5;II;HUNTER;0;0
|
||||
C;36;0;II;BUILDER;1;4
|
||||
C;37;0;II;BUILDER;2;1
|
||||
C;38;3;II;BUILDER;1;2
|
||||
C;39;0;II;BUILDER;2;3
|
||||
C;40;0;II;GATHERER;3;0
|
||||
C;41;3;II;GATHERER;3;0
|
||||
C;42;4;II;GATHERER;3;0
|
||||
C;43;5;II;GATHERER;3;0
|
||||
C;44;3;II;ARTIST;0;0
|
||||
C;45;0;II;ARTIST;0;0
|
||||
C;46;0;II;ARTIST;0;0
|
||||
C;47;0;II;ARTIST;0;0
|
||||
C;48;0;II;INVENTOR;0;0
|
||||
C;49;4;II;INVENTOR;0;0
|
||||
C;50;0;II;INVENTOR;0;0
|
||||
C;51;0;II;INVENTOR;0;0
|
||||
C;52;0;II;INVENTOR;0;0
|
||||
C;53;0;II;INVENTOR;0;0
|
||||
C;54;0;II;SHAMAN;2;0
|
||||
C;55;0;II;SHAMAN;2;0
|
||||
C;56;5;II;SHAMAN;1;0
|
||||
C;57;5;II;SHAMAN;2;0
|
||||
C;58;5;III;HUNTER;1;0
|
||||
C;59;0;III;HUNTER;0;0
|
||||
C;60;0;III;HUNTER;0;0
|
||||
C;61;0;III;HUNTER;1;0
|
||||
C;62;0;III;BUILDER;1;5
|
||||
C;63;0;III;BUILDER;2;3
|
||||
C;64;5;III;BUILDER;1;4
|
||||
C;65;0;III;BUILDER;2;2
|
||||
C;66;5;III;GATHERER;3;0
|
||||
C;67;4;III;GATHERER;3;0
|
||||
C;68;0;III;GATHERER;3;0
|
||||
C;69;5;III;ARTIST;0;0
|
||||
C;70;0;III;ARTIST;0;0
|
||||
C;71;0;III;ARTIST;0;0
|
||||
C;72;0;III;ARTIST;0;0
|
||||
C;73;4;III;INVENTOR;0;0
|
||||
C;74;3;III;INVENTOR;0;0
|
||||
C;75;3;III;INVENTOR;0;0
|
||||
C;76;0;III;INVENTOR;0;0
|
||||
C;77;0;III;INVENTOR;0;0
|
||||
C;78;0;III;INVENTOR;0;0
|
||||
C;79;0;III;INVENTOR;0;0
|
||||
C;80;3;III;SHAMAN;2;0
|
||||
C;81;0;III;SHAMAN;3;0
|
||||
C;82;0;III;SHAMAN;2;0
|
||||
C;83;0;III;SHAMAN;3;0
|
||||
C;84;4;III;SHAMAN;2;0
|
||||
E;85;0;I;HUNT;1;1
|
||||
E;86;0;I;SUSTAINMENT;1;1
|
||||
E;87;0;I;SHAMANIC_RITUAL;5;3
|
||||
E;88;0;I;CAVE_PAINTINGS;2;1
|
||||
E;89;0;II;HUNT;1;2
|
||||
E;90;0;II;SUSTAINMENT;1;2
|
||||
E;91;0;II;SHAMANIC_RITUAL;10;5
|
||||
E;92;0;II;CAVE_PAINTINGS;2;2
|
||||
E;93;0;III;HUNT;1;3
|
||||
E;94;0;III;CAVE_PAINTINGS;2;3
|
||||
E;95;0;FINAL;SUSTAINMENT;1;3
|
||||
E;96;0;FINAL;SHAMANIC_RITUAL;15;7
|
||||
B;97;0;I;4;3;FOOD_FOR_SIX
|
||||
B;98;0;I;4;4;SUSTAIN_DISCOUNT
|
||||
B;99;0;I;5;3;SUSTAIN_DISCOUNT
|
||||
B;100;0;I;5;2;SHAMAN_NO_LOSS
|
||||
B;101;0;I;3;3;BONUS_FOOD_ENDTURN
|
||||
B;102;0;I;3;4;FOOD_PER_INVENTORS
|
||||
B;103;0;II;7;0;SHAMAN_DOUBLE_POINTS
|
||||
B;104;0;II;6;4;SHAMAN_BONUS
|
||||
B;105;0;II;7;4;SUSTAIN_DISCOUNT
|
||||
B;106;0;II;7;2;HUNT_BONUS
|
||||
B;107;0;II;6;4;ENDGAME_BUILDER_BONUS
|
||||
B;108;0;II;5;6;PAINTING_FOOD_BONUS
|
||||
B;109;0;II;5;6;ENDGAME_FOR_SIX
|
||||
B;110;0;III;8;8;ENDGAME_BONUS_CHARACTER
|
||||
B;111;0;III;7;6;ENDGAME_BONUS_CHARACTER
|
||||
B;112;0;III;7;4;ENDGAME_BONUS_CHARACTER
|
||||
B;113;0;III;6;3;ENDGAME_BONUS_CHARACTER
|
||||
B;114;0;III;7;4;ENDGAME_BONUS_CHARACTER
|
||||
B;115;0;III;6;6;ENDGAME_BONUS_CHARACTER
|
||||
B;116;0;III;9;3;EXTRA_DRAW
|
||||
B;117;0;III;10;0;ENDGAME_BONUS_POINTS
|
||||
|
BIN
src/main/resources/files/offeringA.png
Normal file
|
After Width: | Height: | Size: 834 KiB |
BIN
src/main/resources/files/offeringB.png
Normal file
|
After Width: | Height: | Size: 858 KiB |
BIN
src/main/resources/files/offeringC.png
Normal file
|
After Width: | Height: | Size: 927 KiB |
BIN
src/main/resources/files/offeringD.png
Normal file
|
After Width: | Height: | Size: 878 KiB |
BIN
src/main/resources/files/offeringE.png
Normal file
|
After Width: | Height: | Size: 876 KiB |
BIN
src/main/resources/files/offeringF.png
Normal file
|
After Width: | Height: | Size: 826 KiB |
BIN
src/main/resources/files/offeringG.png
Normal file
|
After Width: | Height: | Size: 904 KiB |
22
src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<!-- Appender per scrivere sulla Console -->
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
|
||||
<!-- Appender per scrivere su un file chiamato app.log -->
|
||||
<File name="LogFile" fileName="logs/mesos2.log">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</File>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<!-- Configurazione base: registra tutto ciò che è livello INFO o superiore -->
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
<AppenderRef ref="LogFile"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
16
src/main/resources/org/example/mesosll07/hello-view.fxml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.example.mesosll07.HelloController">
|
||||
<padding>
|
||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
|
||||
</padding>
|
||||
|
||||
<Label fx:id="welcomeText"/>
|
||||
<Button text="Hello!" onAction="#onHelloButtonClick"/>
|
||||
</VBox>
|
||||