mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
multiple player/opponent choose - fixed that game ask players in random order instead APNAP (closes #12532);
game: fixed wrong player restore for TestPlayer and SimulatedPlayer2;
This commit is contained in:
parent
eee1462eba
commit
1e2d179410
12 changed files with 404 additions and 100 deletions
|
|
@ -158,31 +158,42 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
|
||||
Player getPlayerOrPlaneswalkerController(UUID playerId);
|
||||
|
||||
/**
|
||||
* Static players list from start of the game. Use it to find player by ID.
|
||||
*/
|
||||
Players getPlayers();
|
||||
|
||||
/**
|
||||
* Static players list from start of the game. Use it to interate by starting turn order.
|
||||
* WARNING, it's ignore range and leaved players, so use it game engine only
|
||||
*/
|
||||
PlayerList getPlayerList();
|
||||
|
||||
/**
|
||||
* Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
|
||||
* Warning, it will return dead players until end of turn.
|
||||
*/
|
||||
default Set<UUID> getOpponents(UUID playerId) {
|
||||
return getOpponents(playerId, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Set of opponents in range for the given playerId
|
||||
* Returns opponents list in range for the given playerId. Use it to interate by starting turn order.
|
||||
* Warning, it will return dead players until end of turn.
|
||||
*
|
||||
* @param playerId
|
||||
* @param excludeDeadPlayers Determines if players who have lost are excluded from the list
|
||||
* @return
|
||||
* @param excludeDeadPlayers exclude dead player immediately without waiting range update on next turn
|
||||
*/
|
||||
default Set<UUID> getOpponents(UUID playerId, boolean excludeDeadPlayers) {
|
||||
Player player = getPlayer(playerId);
|
||||
if (player == null) {
|
||||
return new HashSet<>();
|
||||
return new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
return player.getInRange().stream()
|
||||
return this.getPlayerList().stream()
|
||||
.filter(opponentId -> !opponentId.equals(playerId))
|
||||
.filter(player::hasPlayerInRange)
|
||||
.filter(opponentId -> !excludeDeadPlayers || !getPlayer(opponentId).hasLost())
|
||||
.collect(Collectors.toSet());
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
default boolean isActivePlayer(UUID playerId) {
|
||||
|
|
|
|||
|
|
@ -3183,8 +3183,6 @@ public abstract class GameImpl implements Game {
|
|||
|
||||
/**
|
||||
* Return a list of all players ignoring the range of visible players
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public PlayerList getPlayerList() {
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
// warning, do not use another keys with same starting text cause copy code search and clean all related values
|
||||
public static final String COPIED_CARD_KEY = "CopiedCard";
|
||||
|
||||
private final Players players;
|
||||
private final PlayerList playerList;
|
||||
private final Players players; // full players by ID (static list, table added order)
|
||||
private final PlayerList playerList; // full players (static list, turn order e.g. apnap)
|
||||
private UUID choosingPlayerId; // player that makes a choice at game start
|
||||
|
||||
// revealed cards <Name, <Cards>>, will be reset if all players pass priority
|
||||
|
|
@ -719,8 +719,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
/**
|
||||
* Returns a list of all players of the game ignoring range or if a player
|
||||
* has lost or left the game.
|
||||
*
|
||||
* @return playerList
|
||||
*/
|
||||
public PlayerList getPlayerList() {
|
||||
return playerList;
|
||||
|
|
@ -761,8 +759,9 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
PlayerList newPlayerList = new PlayerList();
|
||||
Player currentPlayer = game.getPlayer(playerId);
|
||||
if (currentPlayer != null) {
|
||||
// must fill PlayerList by table added order (same as main game)
|
||||
for (Player player : players.values()) {
|
||||
if (player.isInGame() && currentPlayer.getInRange().contains(player.getId())) {
|
||||
if (player.isInGame() && currentPlayer.hasPlayerInRange(player.getId())) {
|
||||
newPlayerList.add(player.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,11 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
|
||||
ManaPool getManaPool();
|
||||
|
||||
Set<UUID> getInRange();
|
||||
/**
|
||||
* Is checking player in range of current player.
|
||||
* Warning, range list updates on start of the turn due rules.
|
||||
*/
|
||||
boolean hasPlayerInRange(UUID checkingPlayerId);
|
||||
|
||||
boolean isTopCardRevealed();
|
||||
|
||||
|
|
@ -1250,4 +1254,10 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
}
|
||||
|
||||
public UserData getControllingPlayersUserData(Game game);
|
||||
|
||||
/**
|
||||
* Some player implementations (TestPlayer, Simulated) uses facade structure with hidden player object,
|
||||
* so that's method helps to find real player that used by a game (in most use cases it's a PlayerImpl)
|
||||
*/
|
||||
Player getRealPlayer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
protected boolean idleTimeout;
|
||||
|
||||
protected RangeOfInfluence range;
|
||||
protected Set<UUID> inRange = new HashSet<>(); // players list in current range of influence (updates each turn)
|
||||
protected Set<UUID> inRange = new HashSet<>(); // players list in current range of influence (updates each turn due rules)
|
||||
|
||||
protected boolean isTestMode = false;
|
||||
protected boolean canGainLife = true;
|
||||
|
|
@ -309,6 +309,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
*/
|
||||
@Override
|
||||
public void restore(Player player) {
|
||||
if (!(player instanceof PlayerImpl)) {
|
||||
throw new IllegalArgumentException("Wrong code usage: can't restore from player class " + player.getClass().getName());
|
||||
}
|
||||
|
||||
this.name = player.getName();
|
||||
this.human = player.isHuman();
|
||||
this.life = player.getLife();
|
||||
|
|
@ -357,7 +361,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.attachments.addAll(player.getAttachments());
|
||||
|
||||
this.inRange.clear();
|
||||
this.inRange.addAll(player.getInRange());
|
||||
this.inRange.addAll(((PlayerImpl) player).inRange);
|
||||
this.payLifeCostLevel = player.getPayLifeCostLevel();
|
||||
this.sacrificeCostFilter = player.getSacrificeCostFilter() != null
|
||||
? player.getSacrificeCostFilter().copy() : null;
|
||||
|
|
@ -579,16 +583,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getInRange() {
|
||||
public boolean hasPlayerInRange(UUID checkingPlayerId) {
|
||||
if (inRange.isEmpty()) {
|
||||
// runtime check: inRange filled on beginTurn, but unit tests adds cards by cheat engine before game starting,
|
||||
// so inRange will be empty and some ETB effects can be broken (example: Spark Double puts direct to battlefield).
|
||||
// Cheat engine already have a workaround, so that error must not be visible in normal situation.
|
||||
// TODO: that's error possible on GameView call before real game start (too laggy players on starting???)
|
||||
throw new IllegalStateException("Wrong code usage (game is not started, but you call getInRange in some effects).");
|
||||
throw new IllegalStateException("Wrong code usage (game is not started, but you call hasPlayerInRange in some effects).");
|
||||
}
|
||||
|
||||
return inRange;
|
||||
return inRange.contains(checkingPlayerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -5130,7 +5134,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
public boolean hasOpponent(UUID playerToCheckId, Game game) {
|
||||
return !this.getId().equals(playerToCheckId)
|
||||
&& game.isOpponent(this, playerToCheckId)
|
||||
&& getInRange().contains(playerToCheckId);
|
||||
&& hasPlayerInRange(playerToCheckId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -5470,4 +5474,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
public String toString() {
|
||||
return getName() + " (" + super.getClass().getSimpleName() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getRealPlayer() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public class PlayerList extends CircularList<UUID> {
|
|||
UUID currentPlayerBefore = this.get();
|
||||
UUID nextPlayerId = game.isTurnOrderReversed() ? super.getPrevious() : super.getNext();
|
||||
do {
|
||||
if (basePlayer.getInRange().contains(nextPlayerId)) {
|
||||
if (basePlayer.hasPlayerInRange(nextPlayerId)) {
|
||||
return game.getPlayer(nextPlayerId);
|
||||
}
|
||||
nextPlayerId = game.isTurnOrderReversed() ? super.getPrevious() : super.getNext();
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
|
||||
package mage.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* a thread-safe circular list
|
||||
* Component: a thread-safe circular list, used for players list
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @param <E>
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
||||
//TODO: might have to make E extend Copyable
|
||||
|
|
@ -42,17 +35,17 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Inserts an element into the current position
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
* Insert new element at the current position (make new element as current)
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
list.add(this.index, e);
|
||||
public boolean add(E element) {
|
||||
add(this.index, element);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new element at selected position (keep old element as current)
|
||||
*/
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
lock.lock();
|
||||
|
|
@ -65,35 +58,36 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set current position to an element
|
||||
*
|
||||
* @param e the element to set as current
|
||||
* @return true if element e exists and index was set
|
||||
* @return false on unknown element
|
||||
*/
|
||||
public boolean setCurrent(E e) {
|
||||
if (list.contains(e)) {
|
||||
this.index = list.indexOf(e);
|
||||
public boolean setCurrent(E element) {
|
||||
if (list.contains(element)) {
|
||||
this.index = list.indexOf(element);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the element at the current position
|
||||
*
|
||||
* @return
|
||||
* Find current element
|
||||
*/
|
||||
public E get() {
|
||||
return get(this.index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find element at searching position
|
||||
*/
|
||||
@Override
|
||||
public E get(int index) {
|
||||
if (list.size() > this.index) {
|
||||
return list.get(this.index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next element in the list. Will loop around to the beginning
|
||||
* of the list if the current element is the last.
|
||||
|
|
@ -117,18 +111,23 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
/**
|
||||
* Removes the current element from the list
|
||||
*
|
||||
* @return <tt>true</tt> is the item was successfully removed
|
||||
* @return true on successfully removed
|
||||
*/
|
||||
public boolean remove() {
|
||||
return this.remove(get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes element from searching position
|
||||
*
|
||||
* @return true on successfully removed
|
||||
*/
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
lock.lock();
|
||||
try {
|
||||
E ret = list.remove(index);
|
||||
checkPointer();
|
||||
fixInvalidPointer();
|
||||
modCount++;
|
||||
return ret;
|
||||
} finally {
|
||||
|
|
@ -141,7 +140,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
lock.lock();
|
||||
try {
|
||||
boolean ret = list.remove(o);
|
||||
checkPointer();
|
||||
fixInvalidPointer();
|
||||
modCount++;
|
||||
return ret;
|
||||
} finally {
|
||||
|
|
@ -152,50 +151,49 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
private int incrementPointer() {
|
||||
lock.lock();
|
||||
try {
|
||||
index = incrementListPointer(index);
|
||||
index = findNextListPointer(index);
|
||||
return index;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private int incrementListPointer(int index) {
|
||||
index++;
|
||||
if (index >= list.size()) {
|
||||
index = 0;
|
||||
private int findNextListPointer(int currentIndex) {
|
||||
currentIndex++;
|
||||
if (currentIndex >= list.size()) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
return index;
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
private int decrementPointer() {
|
||||
lock.lock();
|
||||
try {
|
||||
index = decrementListPointer(index);
|
||||
index = findPrevListPointer(index);
|
||||
return index;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private int decrementListPointer(int index) {
|
||||
index--;
|
||||
if (index < 0) {
|
||||
index = list.size() - 1;
|
||||
private int findPrevListPointer(int currentIndex) {
|
||||
currentIndex--;
|
||||
if (currentIndex < 0) {
|
||||
currentIndex = list.size() - 1;
|
||||
}
|
||||
return index;
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should only be called from a locked method thus it is not
|
||||
* necessary to lock from this method
|
||||
*/
|
||||
private int checkPointer() {
|
||||
private void fixInvalidPointer() {
|
||||
if (index > list.size()) {
|
||||
index = list.size() - 1;
|
||||
} else if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -215,16 +213,22 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
return toArray(new UUID[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
T[] res = list.toArray(a);
|
||||
|
||||
public List<E> toList() {
|
||||
return list;
|
||||
// sort due default order
|
||||
Iterator<E> iter = this.iterator();
|
||||
int insertIndex = 0;
|
||||
while (iter.hasNext()) {
|
||||
res[insertIndex] = (T) iter.next();
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -254,7 +258,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
try {
|
||||
boolean ret = list.removeAll(c);
|
||||
modCount++;
|
||||
checkPointer();
|
||||
fixInvalidPointer();
|
||||
return ret;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
|
@ -267,7 +271,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
try {
|
||||
boolean ret = list.retainAll(c);
|
||||
modCount++;
|
||||
checkPointer();
|
||||
fixInvalidPointer();
|
||||
return ret;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
|
@ -286,6 +290,10 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
public E set(E element) {
|
||||
return this.set(this.index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
lock.lock();
|
||||
|
|
@ -297,10 +305,6 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
public E set(E element) {
|
||||
return this.set(this.index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
|
|
@ -361,7 +365,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
throw new ConcurrentModificationException();
|
||||
}
|
||||
E data = (E) list.get(cursor);
|
||||
cursor = incrementListPointer(cursor);
|
||||
cursor = findNextListPointer(cursor);
|
||||
hasMoved = true;
|
||||
return data;
|
||||
}
|
||||
|
|
@ -409,7 +413,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
throw new ConcurrentModificationException();
|
||||
}
|
||||
E data = (E) list.get(cursor);
|
||||
cursor = incrementListPointer(cursor);
|
||||
cursor = findNextListPointer(cursor);
|
||||
hasMoved = true;
|
||||
return data;
|
||||
}
|
||||
|
|
@ -430,7 +434,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
if (curModCount != modCount) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
cursor = decrementListPointer(cursor);
|
||||
cursor = findPrevListPointer(cursor);
|
||||
hasMoved = true;
|
||||
return (E) list.get(cursor);
|
||||
}
|
||||
|
|
@ -438,7 +442,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
@Override
|
||||
public int nextIndex() {
|
||||
if (this.hasNext()) {
|
||||
return incrementListPointer(cursor);
|
||||
return findNextListPointer(cursor);
|
||||
}
|
||||
return list.size();
|
||||
}
|
||||
|
|
@ -446,7 +450,7 @@ public class CircularList<E> implements List<E>, Iterable<E>, Serializable {
|
|||
@Override
|
||||
public int previousIndex() {
|
||||
if (this.hasPrevious()) {
|
||||
return decrementListPointer(cursor);
|
||||
return findPrevListPointer(cursor);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue