game logic

main
Jonah Bauer 3 years ago
parent 640443abbc
commit 7947e54872

@ -3,7 +3,7 @@ package eu.jonahbauer.wizard.client.libgdx.actions;
import com.badlogic.gdx.scenes.scene2d.Action; import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group; import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.actions.*;
public class MyActions extends Actions { public class MyActions extends Actions {
public static ChangeParentAction changeParent(Group parent) { public static ChangeParentAction changeParent(Group parent) {
@ -12,19 +12,30 @@ public class MyActions extends Actions {
return action; return action;
} }
public static <T extends Action> T target(T action, Actor target) { public static SilentlyRemoveActorAction removeActorSilently () {
action.setTarget(target);
return action;
}
static public SilentlyRemoveActorAction removeActorSilently () {
return action(SilentlyRemoveActorAction.class); return action(SilentlyRemoveActorAction.class);
} }
static public SilentlyRemoveActorAction removeActorSilently(Actor removeActor) { public static WaitAction delay(Actor actor) {
var action = action(SilentlyRemoveActorAction.class); var action = action(WaitAction.class);
action.setTarget(removeActor); action.setTarget(actor);
return action; return action;
} }
public static void finish(Action action) {
if (action instanceof ParallelAction parallel) {
var subactions = parallel.getActions();
for (int i = 0; i < subactions.size; i++) {
finish(subactions.get(i));
}
} else if (action instanceof DelayAction delay) {
delay.finish();
finish(delay.getAction());
} else if (action instanceof DelegateAction delegate) {
var subaction = delegate.getAction();
finish(subaction);
} else if (action instanceof TemporalAction temporal) {
temporal.finish();
}
}
} }

@ -0,0 +1,10 @@
package eu.jonahbauer.wizard.client.libgdx.actions;
import com.badlogic.gdx.scenes.scene2d.Action;
public class WaitAction extends Action {
@Override
public boolean act(float delta) {
return getTarget().getActions().size == 0;
}
}

@ -12,6 +12,7 @@ import lombok.Setter;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
@Getter @Getter
@Setter @Setter
@ -126,6 +127,7 @@ public class CardActor extends Actor {
public void setCard(Card card) { public void setCard(Card card) {
this.card = card; this.card = card;
this.background = atlas.findRegion(ATLAS_PATHS.get(card)); this.background = atlas.findRegion(ATLAS_PATHS.get(card));
if (this.background == null) throw new NoSuchElementException("Could not find texture for card " + card + ".");
} }
public void setCard(Card.Suit suit) { public void setCard(Card.Suit suit) {
@ -137,6 +139,7 @@ public class CardActor extends Actor {
case BLUE -> GameAtlas.CARDS_BLUE; case BLUE -> GameAtlas.CARDS_BLUE;
case RED -> GameAtlas.CARDS_RED; case RED -> GameAtlas.CARDS_RED;
}); });
if (this.background == null) throw new NoSuchElementException("Could not find texture for suit " + suit + ".");
} }
@Override @Override

@ -126,7 +126,7 @@ public class CardStack extends Group {
if (action != null) actor.removeAction(action); if (action != null) actor.removeAction(action);
action = parallel( action = parallel(
moveTo(seat.getCardX() - actor.getWidth() / 2, seat.getCardY() - actor.getHeight() / 2, EXPAND_DURATION), moveTo(seat.getFrontX() - actor.getWidth() / 2, seat.getFrontY() - actor.getHeight() / 2, EXPAND_DURATION),
rotateTo(expandedRotation, EXPAND_DURATION) rotateTo(expandedRotation, EXPAND_DURATION)
); );

@ -17,7 +17,7 @@ public class PadOfTruth extends Table {
private static final float EXTENDED_WIDTH = 636; private static final float EXTENDED_WIDTH = 636;
private static final float EXTENDED_HEIGHT = 824; private static final float EXTENDED_HEIGHT = 824;
private static final float COLLAPSE_DURATION = 0.25f; private static final float COLLAPSE_DURATION = 0.25f;
private static final float COLLAPSED_SCALE = 0.2f; private static final float COLLAPSED_SCALE = CardActor.PREF_HEIGHT / EXTENDED_HEIGHT;
private final Label[] names = new Label[6]; private final Label[] names = new Label[6];
private final Label[][] predictions = new Label[20][]; private final Label[][] predictions = new Label[20][];

@ -7,9 +7,10 @@ import eu.jonahbauer.wizard.common.model.Card;
public class ChangePredictionOverlay extends MakePredictionOverlay { public class ChangePredictionOverlay extends MakePredictionOverlay {
public ChangePredictionOverlay(GameScreen gameScreen, int round, int oldPrediction) { public ChangePredictionOverlay(GameScreen gameScreen, long timeout, int round, int oldPrediction) {
super( super(
gameScreen, gameScreen,
timeout,
oldPrediction == 0 ? new int[] {oldPrediction + 1} oldPrediction == 0 ? new int[] {oldPrediction + 1}
: oldPrediction == round + 1 ? new int[] {oldPrediction - 1} : oldPrediction == round + 1 ? new int[] {oldPrediction - 1}
: new int[] {oldPrediction - 1, oldPrediction + 1} : new int[] {oldPrediction - 1, oldPrediction + 1}

@ -0,0 +1,4 @@
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
public interface InteractionOverlay {
}

@ -1,26 +1,27 @@
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay; package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup; import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup; import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen; import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.common.messages.player.PredictMessage;
import java.util.stream.IntStream; import java.util.stream.IntStream;
public class MakePredictionOverlay extends Overlay { public class MakePredictionOverlay extends Overlay implements InteractionOverlay {
private final int[] values; private final int[] values;
private final TextButton[] buttons; private final TextButton[] buttons;
public MakePredictionOverlay(GameScreen gameScreen, int round) { public MakePredictionOverlay(GameScreen gameScreen, long timeout, int round) {
this(gameScreen, IntStream.range(0, round + 2).toArray()); this(gameScreen, timeout, IntStream.range(0, round + 2).toArray());
} }
protected MakePredictionOverlay(GameScreen gameScreen, int[] values) { protected MakePredictionOverlay(GameScreen gameScreen, long timeout, int[] values) {
super(gameScreen); super(gameScreen, timeout);
this.values = values; this.values = values;
this.buttons = new TextButton[values.length]; this.buttons = new TextButton[values.length];
} }
@ -32,30 +33,23 @@ public class MakePredictionOverlay extends Overlay {
var prompt = new Label("Please make your prediction:", data.skin); var prompt = new Label("Please make your prediction:", data.skin);
var buttonGroup = new HorizontalGroup().space(20); var buttonGroup = new HorizontalGroup().space(20);
for (int i = 0; i < values.length; i++) { var listener = new ChangeListener() {
buttons[i] = new TextButton(String.valueOf(i), data.skin);
buttonGroup.addActor(buttons[i]);
}
buttonGroup.addListener(new ClickListener() {
private boolean finished = false;
@Override @Override
public void clicked(InputEvent event, float x, float y) { public void changed(ChangeEvent event, Actor actor) {
if (finished) return;
finished = true;
var target = event.getTarget();
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
if (buttons[i] == target) { if (actor == buttons[i]) {
System.out.println(values[i]); screen.send(new PredictMessage(values[i]));
break; break;
} }
} }
getRoot().remove();
} }
}); };
for (int i = 0; i < values.length; i++) {
buttons[i] = new TextButton(String.valueOf(i), data.skin);
buttons[i].addListener(listener);
buttonGroup.addActor(buttons[i]);
}
root.addActor(prompt); root.addActor(prompt);
root.addActor(buttonGroup); root.addActor(buttonGroup);

@ -1,26 +1,52 @@
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay; package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Container; import com.badlogic.gdx.scenes.scene2d.ui.Container;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame; import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen; import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
public abstract class Overlay { import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
public abstract class Overlay extends Action {
protected static final float OVERLAY_TIME = 5.0f;
protected final GameScreen screen;
protected final WizardGame.Data data; protected final WizardGame.Data data;
protected final TextureAtlas atlas; protected final TextureAtlas atlas;
private long timeout;
private Container<?> root; private Container<?> root;
private final Drawable background; private boolean started;
private boolean finished;
public Overlay(GameScreen gameScreen) { public Overlay(GameScreen gameScreen, long timeout) {
this.screen = gameScreen;
this.data = gameScreen.getData(); this.data = gameScreen.getData();
this.atlas = gameScreen.getAtlas(); this.atlas = gameScreen.getAtlas();
this.background = gameScreen.getOverlay(); this.timeout = timeout;
}
@Override
public boolean act(float delta) {
if (!started) {
if (System.currentTimeMillis() > timeout) {
return true;
}
started = true;
show((Group) getActor());
}
if (System.currentTimeMillis() > timeout) {
finishInternal();
}
return finished;
} }
protected abstract Actor createContent(); protected abstract Actor createContent();
@ -28,7 +54,7 @@ public abstract class Overlay {
protected Container<?> getRoot() { protected Container<?> getRoot() {
if (root == null) { if (root == null) {
root = new Container<>(createContent()); root = new Container<>(createContent());
root.setBackground(background); root.setBackground(new TextureRegionDrawable(data.uiskinAtlas.findRegion(UiskinAtlas.WHITE)).tint(new Color(0, 0, 0, 0.5f)));
root.setSize(WizardGame.WIDTH, WizardGame.HEIGHT); root.setSize(WizardGame.WIDTH, WizardGame.HEIGHT);
root.setTouchable(Touchable.enabled); root.setTouchable(Touchable.enabled);
} }
@ -36,7 +62,25 @@ public abstract class Overlay {
return root; return root;
} }
public void show(Stage stage) { protected void show(Group parent) {
stage.addActor(getRoot()); parent.addActor(getRoot());
}
protected void finishInternal() {
if (!finished) {
finished = true;
getRoot().remove();
}
}
public void finish() {
getRoot().addAction(sequence(
targeting(root, alpha(0.0f, .1f, Interpolation.pow2Out)),
run(this::finishInternal)
));
}
public void timeout() {
timeout = 0;
} }
} }

@ -8,18 +8,19 @@ import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor; import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen; import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.common.messages.player.PickTrumpMessage;
import eu.jonahbauer.wizard.common.model.Card; import eu.jonahbauer.wizard.common.model.Card;
import java.util.EnumMap; import java.util.EnumMap;
public class PickTrumpOverlay extends Overlay { public class PickTrumpOverlay extends Overlay implements InteractionOverlay {
private final boolean allowNone; private final boolean allowNone;
private final EnumMap<Card.Suit, CardActor> cards = new EnumMap<>(Card.Suit.class); private final EnumMap<Card.Suit, CardActor> cards = new EnumMap<>(Card.Suit.class);
public PickTrumpOverlay(GameScreen gameScreen, boolean allowNone) { public PickTrumpOverlay(GameScreen gameScreen, long timeout, boolean allowNone) {
super(gameScreen); super(gameScreen, timeout);
this.allowNone = allowNone; this.allowNone = allowNone;
} }
@ -41,23 +42,15 @@ public class PickTrumpOverlay extends Overlay {
cards.values().forEach(cardGroup::addActor); cards.values().forEach(cardGroup::addActor);
cardGroup.addListener(new ClickListener() { cardGroup.addListener(new ClickListener() {
private boolean finished = false;
@Override @Override
public void clicked(InputEvent event, float x, float y) { public void clicked(InputEvent event, float x, float y) {
if (finished) return;
finished = true;
var target = event.getTarget(); var target = event.getTarget();
for (Card.Suit suit : Card.Suit.values()) { for (Card.Suit suit : Card.Suit.values()) {
if (cards.get(suit) == target) { if (cards.get(suit) == target) {
// TODO screen.send(new PickTrumpMessage(suit));
System.out.println(suit);
break; break;
} }
} }
getRoot().remove();
} }
}); });

@ -2,20 +2,20 @@ package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup; import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import eu.jonahbauer.wizard.client.libgdx.actions.MyActions;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen; import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
public class StartRoundOverlay extends Overlay { public class StartRoundOverlay extends Overlay {
private final int round; private final int round;
public StartRoundOverlay(GameScreen gameScreen, int round) { public StartRoundOverlay(GameScreen gameScreen, int round) {
super(gameScreen); super(gameScreen, Long.MAX_VALUE);
this.round = round; this.round = round;
} }
@ -31,13 +31,19 @@ public class StartRoundOverlay extends Overlay {
} }
@Override @Override
public void show(Stage stage) { public void show(Group parent) {
super.show(parent);
var root = getRoot(); var root = getRoot();
stage.addActor(root); root.addAction(sequence(
stage.addAction(sequence( delay(OVERLAY_TIME),
delay(2.0f), targeting(root, alpha(0.0f, .5f, Interpolation.pow2Out)),
target(alpha(0.0f, .1f, Interpolation.pow2Out), root), run(this::finishInternal)
removeActor(root)
)); ));
} }
@Override
public void finish() {
MyActions.finish(getRoot().getActions().get(0));
}
} }

@ -1,31 +1,96 @@
package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay; package eu.jonahbauer.wizard.client.libgdx.actors.game.overlay;
import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup;
import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup; import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import eu.jonahbauer.wizard.client.libgdx.actions.MyActions;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor; import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen; import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.common.model.Card; import eu.jonahbauer.wizard.common.model.Card;
import java.util.Map;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*; import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
public class TrumpOverlay extends Overlay { public class TrumpOverlay extends Overlay {
@SuppressWarnings("RedundantTypeArguments")
private static final Map<Card, Card.Suit> DEFAULT_SUITES = Map.<Card, Card.Suit>ofEntries(
Map.entry(Card.BLUE_1, Card.Suit.BLUE),
Map.entry(Card.BLUE_2, Card.Suit.BLUE),
Map.entry(Card.BLUE_3, Card.Suit.BLUE),
Map.entry(Card.BLUE_4, Card.Suit.BLUE),
Map.entry(Card.BLUE_5, Card.Suit.BLUE),
Map.entry(Card.BLUE_6, Card.Suit.BLUE),
Map.entry(Card.BLUE_7, Card.Suit.BLUE),
Map.entry(Card.BLUE_8, Card.Suit.BLUE),
Map.entry(Card.BLUE_9, Card.Suit.BLUE),
Map.entry(Card.BLUE_10, Card.Suit.BLUE),
Map.entry(Card.BLUE_11, Card.Suit.BLUE),
Map.entry(Card.BLUE_12, Card.Suit.BLUE),
Map.entry(Card.BLUE_13, Card.Suit.BLUE),
Map.entry(Card.RED_1, Card.Suit.RED),
Map.entry(Card.RED_2, Card.Suit.RED),
Map.entry(Card.RED_3, Card.Suit.RED),
Map.entry(Card.RED_4, Card.Suit.RED),
Map.entry(Card.RED_5, Card.Suit.RED),
Map.entry(Card.RED_6, Card.Suit.RED),
Map.entry(Card.RED_7, Card.Suit.RED),
Map.entry(Card.RED_8, Card.Suit.RED),
Map.entry(Card.RED_9, Card.Suit.RED),
Map.entry(Card.RED_10, Card.Suit.RED),
Map.entry(Card.RED_11, Card.Suit.RED),
Map.entry(Card.RED_12, Card.Suit.RED),
Map.entry(Card.RED_13, Card.Suit.RED),
Map.entry(Card.GREEN_1, Card.Suit.GREEN),
Map.entry(Card.GREEN_2, Card.Suit.GREEN),
Map.entry(Card.GREEN_3, Card.Suit.GREEN),
Map.entry(Card.GREEN_4, Card.Suit.GREEN),
Map.entry(Card.GREEN_5, Card.Suit.GREEN),
Map.entry(Card.GREEN_6, Card.Suit.GREEN),
Map.entry(Card.GREEN_7, Card.Suit.GREEN),
Map.entry(Card.GREEN_8, Card.Suit.GREEN),
Map.entry(Card.GREEN_9, Card.Suit.GREEN),
Map.entry(Card.GREEN_10, Card.Suit.GREEN),
Map.entry(Card.GREEN_11, Card.Suit.GREEN),
Map.entry(Card.GREEN_12, Card.Suit.GREEN),
Map.entry(Card.GREEN_13, Card.Suit.GREEN),
Map.entry(Card.YELLOW_1, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_2, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_3, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_4, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_5, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_6, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_7, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_8, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_9, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_10, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_11, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_12, Card.Suit.YELLOW),
Map.entry(Card.YELLOW_13, Card.Suit.YELLOW)
);
private final String player; private final String player;
private final Card card; private final Card card;
private final Card.Suit suit; private final Card.Suit suit;
private final CardActor trumpCardActor; private final CardActor trumpCardActor;
private final CardActor trumpSuitActor;
private boolean animateCard = true;
public TrumpOverlay(GameScreen gameScreen, String player, Card card, Card.Suit suit) { public TrumpOverlay(GameScreen gameScreen, String player, Card card, Card.Suit suit) {
super(gameScreen); super(gameScreen, Long.MAX_VALUE);
this.player = player; this.player = player;
this.card = card; this.card = card;
this.suit = suit; this.suit = suit;
this.trumpCardActor = gameScreen.getTrumpCardActor(); this.trumpCardActor = gameScreen.getTrumpCardActor();
this.trumpSuitActor = gameScreen.getTrumpSuitActor();
} }
@Override @Override
@ -35,53 +100,91 @@ public class TrumpOverlay extends Overlay {
String text; String text;
if (player == null) { if (player == null) {
text = switch (suit) { text = suit != null ? switch (suit) {
case YELLOW -> "The trump suit is [#ffff00]yellow[#ffffff]."; case YELLOW -> "The trump suit is [#ffff00]yellow[#ffffff].";
case GREEN -> "The trump suit is [#00ff00]green[#ffffff]."; case GREEN -> "The trump suit is [#00ff00]green[#ffffff].";
case BLUE -> "The trump suit is [#0000ff]blue[#ffffff]."; case BLUE -> "The trump suit is [#0000ff]blue[#ffffff].";
case RED -> "The trump suit is [#ff0000]red[#ffffff]."; case RED -> "The trump suit is [#ff0000]red[#ffffff].";
default -> "There is no trump suit."; default -> "There is no trump suit.";
}; } : "To be determined.";
} else { } else {
text = player + switch (suit) { text = player + (suit != null ? switch (suit) {
case YELLOW -> " chose the trump suit [#ffff00]yellow[#ffffff]."; case YELLOW -> " chose the trump suit [#ffff00]yellow[#ffffff].";
case GREEN -> " chose the trump suit [#00ff00]green[#ffffff]."; case GREEN -> " chose the trump suit [#00ff00]green[#ffffff].";
case BLUE -> " chose the trump suit [#0000ff]blue[#ffffff]."; case BLUE -> " chose the trump suit [#0000ff]blue[#ffffff].";
case RED -> " chose the trump suit [#ff0000]red[#ffffff]."; case RED -> " chose the trump suit [#ff0000]red[#ffffff].";
default -> " has decided there will be no trump suit this round."; default -> " has decided there will be no trump suit this round.";
}; } : " to be determined.");
} }
var label = new Label(text, data.skin); var label = new Label(text, data.skin);
label.getStyle().font.getData().markupEnabled = true; label.getStyle().font.getData().markupEnabled = true;
label.setFontScale(1.5f); label.setFontScale(1.5f);
root.addActor(label); root.addActor(label);
root.addActor(trumpCardActor);
var cardGroup = new HorizontalGroup().space(20);
root.addActor(cardGroup);
if (trumpCardActor.getParent() != null && trumpCardActor.getCard() == card && suit != null) {
// if card actor is already correct then dont change it
animateCard = false;
} else {
trumpCardActor.remove();
cardGroup.addActor(trumpCardActor);
trumpCardActor.setCard(card);
}
trumpSuitActor.remove();
if (suit != DEFAULT_SUITES.get(card)) {
trumpSuitActor.setRotation(0);
trumpSuitActor.setOrigin(0, 0);
cardGroup.addActor(trumpSuitActor);
trumpSuitActor.setCard(suit);
}
return root; return root;
} }
@Override @Override
public void show(Stage stage) { public void show(Group parent) {
var root = getRoot(); super.show(parent);
ParallelAction cardAnimation = parallel();
if (animateCard) {
cardAnimation.addAction(sequence(
targeting(trumpCardActor, removeActorSilently()),
targeting(trumpCardActor, changeParent(parent)),
targeting(trumpCardActor, moveTo(10, 10, .3f))
));
}
trumpCardActor.setCard(card); if (suit != null) {
cardAnimation.addAction(sequence(
targeting(trumpSuitActor, removeActorSilently()),
targeting(trumpSuitActor, changeParent(parent)),
run(trumpCardActor::toFront),
parallel(
targeting(trumpSuitActor, rotateTo(-90, .3f)),
targeting(trumpSuitActor,
moveTo(10, 10 + (trumpSuitActor.getHeight() + trumpSuitActor.getWidth()) / 2, .3f)
)
)
));
}
stage.addActor(root); var root = getRoot();
stage.addAction(sequence( root.addAction(sequence(
delay(2.0f), delay(OVERLAY_TIME),
parallel( parallel(
sequence( targeting(root, alpha(0.0f, .3f, Interpolation.pow2Out)),
target(alpha(0.0f, .1f, Interpolation.pow2Out), root), cardAnimation
removeActor(root) ),
), run(this::finishInternal)
sequence(
removeActorSilently(trumpCardActor),
target(changeParent(stage.getRoot()), trumpCardActor),
target(moveTo(10, 10, .25f), trumpCardActor)
)
)
)); ));
} }
@Override
public void finish() {
MyActions.finish(getRoot().getActions().get(0));
}
} }

@ -3,105 +3,90 @@ package eu.jonahbauer.wizard.client.libgdx.screens;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Touchable; import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Container; import com.badlogic.gdx.scenes.scene2d.ui.Container;
import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup; import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Align;
import eu.jonahbauer.wizard.client.libgdx.GameAtlas; import eu.jonahbauer.wizard.client.libgdx.GameAtlas;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame; import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor; import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardStack; import eu.jonahbauer.wizard.client.libgdx.actors.game.CardStack;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardsGroup; import eu.jonahbauer.wizard.client.libgdx.actors.game.CardsGroup;
import eu.jonahbauer.wizard.client.libgdx.actors.game.PadOfTruth; import eu.jonahbauer.wizard.client.libgdx.actors.game.PadOfTruth;
import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.*; import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.*;
import eu.jonahbauer.wizard.client.libgdx.state.Game;
import eu.jonahbauer.wizard.client.libgdx.util.Pair; import eu.jonahbauer.wizard.client.libgdx.util.Pair;
import eu.jonahbauer.wizard.common.messages.observer.*; import eu.jonahbauer.wizard.common.messages.client.InteractionMessage;
import eu.jonahbauer.wizard.common.messages.server.GameMessage; import eu.jonahbauer.wizard.common.messages.observer.UserInputMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage; import eu.jonahbauer.wizard.common.messages.player.ContinueMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayCardMessage;
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
import eu.jonahbauer.wizard.common.model.Card; import eu.jonahbauer.wizard.common.model.Card;
import lombok.Getter; import lombok.Getter;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*; import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;
import static eu.jonahbauer.wizard.common.messages.observer.UserInputMessage.Action.*;
public class GameScreen extends MenuScreen { public class GameScreen extends MenuScreen {
private final WizardGame game;
@Getter @Getter
private TextureAtlas atlas; private TextureAtlas atlas;
@Getter
private Drawable overlay;
private final List<UUID> players; private Label.LabelStyle labelStyleDefault;
private Label.LabelStyle labelStyleActive;
private final UUID self; private final Game state;
private final UUID session;
private final Map<UUID, String> names;
private final Map<Integer, Map<UUID, Integer>> scores = new HashMap<>();
private int round = -1; private final List<UUID> players;
private final Map<UUID, List<Card>> hands = new HashMap<>();
private final Map<UUID, Integer> predictions = new HashMap<>();
private final Map<UUID, List<List<Card>>> tricks = new HashMap<>();
private Card.Suit trumpSuit;
private Card trumpCard;
private int trick = -1;
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
private Pair<UUID, UserInputMessage.Action> activePlayer; private Pair<UUID, UserInputMessage.Action> activePlayer;
private CardsGroup handCards; private CardsGroup handCards;
private CardStack cardStack; private CardStack cardStack;
private PadOfTruth padOfTruth; private PadOfTruth padOfTruth;
@Getter @Getter
private CardActor trumpCardActor; private CardActor trumpCardActor;
@Getter
private CardActor trumpSuitActor;
private VerticalGroup messages; private VerticalGroup messages;
private Label persistentMessage;
private Action currentAction;
private final Queue<Action> pendingActions = new LinkedList<>();
private final Map<UUID, Seat> seats = new HashMap<>(); private final Map<UUID, Seat> seats = new HashMap<>();
private final Map<UUID, Label> nameLabels = new HashMap<>(); private final Map<UUID, Label> nameLabels = new HashMap<>();
private final AtomicBoolean sending = new AtomicBoolean();
private final AtomicBoolean pendingSync = new AtomicBoolean();
public GameScreen(WizardGame game) { public GameScreen(WizardGame game) {
super(game); super(game);
this.game = game; this.state = (Game) game.getClient().getState();
this.players = new ArrayList<>(state.getPlayers().keySet());
//<editor-fold desc="Sample Data" defaultstate="collapsed">
this.session = UUID.randomUUID();
this.names = new HashMap<>();
int playerCount = 6;
var players = new UUID[playerCount];
for (int i = 0; i < playerCount; i++) {
players[i] = UUID.randomUUID();
names.put(players[i], "Player " + i);
}
this.self = players[0];
this.players = Stream.of(players).sorted().toList();
//</editor-fold>
} }
@Override @Override
public void show() { public void show() {
super.show(); super.show();
atlas = new TextureAtlas(Gdx.files.internal(GameAtlas.$PATH)); atlas = new TextureAtlas(Gdx.files.internal(GameAtlas.$PATH));
overlay = new TextureRegionDrawable(game.data.uiskinAtlas.findRegion(UiskinAtlas.WHITE)).tint(new Color(0,0,0,0.5f)); // overlay = new TextureRegionDrawable(game.data.uiskinAtlas.findRegion(UiskinAtlas.WHITE)).tint(new Color(0,0,0,0.5f));
labelStyleDefault = game.data.skin.get(Label.LabelStyle.class);
labelStyleActive = new Label.LabelStyle(labelStyleDefault);
labelStyleActive.fontColor = Color.RED;
seat(); seat();
prepareLabels(); prepareLabels();
handCards = new CardsGroup(Collections.emptyList(), atlas); handCards = new CardsGroup(Collections.emptyList(), atlas);
handCards.setOnClickListener(card -> playCard(self, card.getCard())); handCards.setOnClickListener(card -> send(new PlayCardMessage(card.getCard())));
var container = new Container<>(handCards); var container = new Container<>(handCards);
container.setPosition(360, 75); container.setPosition(360, 75);
container.setSize(1200, CardActor.PREF_HEIGHT); container.setSize(1200, CardActor.PREF_HEIGHT);
@ -126,121 +111,6 @@ public class GameScreen extends MenuScreen {
game.data.stage.addActor(cardStack); game.data.stage.addActor(cardStack);
game.data.stage.addActor(padOfTruth); game.data.stage.addActor(padOfTruth);
game.data.stage.addActor(messages); game.data.stage.addActor(messages);
game.data.stage.addActor(debugButtons());
}
private Group debugButtons() {
var trump = new TextButton("Trump", game.data.skin, "simple");
trump.align(Align.left);
trump.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onMessage(new GameMessage(new TrumpMessage(
Card.values()[(int)(Math.random() * Card.values().length)],
Card.Suit.values()[(int)(Math.random() * Card.Suit.values().length)]
)));
}
});
var playCard = new TextButton("Play Card", game.data.skin, "simple");
playCard.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
var hand = hands.get(self);
if (hand != null && hand.size() > 0) {
onMessage(new GameMessage(new CardMessage(
self,
hand.get((int) (Math.random() * hand.size()))
)));
}
}
});
var deal = new TextButton("Deal", game.data.skin, "simple");
deal.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
var hand = Stream.of(Card.values()).limit(20).toList();
onMessage(new GameMessage(new HandMessage(self, hand)));
}
});
var round = new TextButton("Round", game.data.skin, "simple");
round.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onMessage(new GameMessage(new StateMessage("starting_round")));
round.setText("Round " + GameScreen.this.round);
}
});
var scores = new TextButton("Scores", game.data.skin, "simple");
scores.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
var scores = players.stream().collect(Collectors.toMap(p -> p, p -> (int) (Math.random() * 5) * 10));
onMessage(new GameMessage(new ScoreMessage(scores)));
}
});
var predictions = new TextButton("Predictions", game.data.skin, "simple");
predictions.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
players.forEach(player -> onMessage(new GameMessage(new PredictionMessage(player, (int) (Math.random() * 5)))));
}
});
var playOtherCard = new TextButton("Play Other Card", game.data.skin, "simple");
playOtherCard.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
var card = Card.values()[(int) (Math.random() * Card.values().length)];
UUID player;
do {
player = players.get((int) (Math.random() * players.size()));
} while (self.equals(player));
onMessage(new GameMessage(new CardMessage(player, card)));
}
});
var makePrediction = new TextButton("Make Prediction", game.data.skin, "simple");
makePrediction.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.MAKE_PREDICTION, Long.MAX_VALUE)));
}
});
var changePrediction = new TextButton("Change Prediction", game.data.skin, "simple");
changePrediction.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.CHANGE_PREDICTION, Long.MAX_VALUE)));
}
});
var chooseTrump = new TextButton("Choose Trump", game.data.skin, "simple");
chooseTrump.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.PICK_TRUMP, Long.MAX_VALUE)));
}
});
var buttons = new VerticalGroup();
buttons.top().left().columnLeft().setPosition(0, WizardGame.HEIGHT - buttons.getHeight());
buttons.addActor(round);
buttons.addActor(deal);
buttons.addActor(trump);
buttons.addActor(predictions);
buttons.addActor(playCard);
buttons.addActor(playOtherCard);
buttons.addActor(scores);
buttons.addActor(chooseTrump);
buttons.addActor(makePrediction);
buttons.addActor(changePrediction);
return buttons;
} }
@Override @Override
@ -261,7 +131,35 @@ public class GameScreen extends MenuScreen {
} }
@Override @Override
protected void renderForeground(float delta) {} protected void renderForeground(float delta) {
if (pendingActions.size() > 0) {
var actions = game.data.stage.getRoot().getActions();
boolean running = false;
if (currentAction != null) {
for (int i = 0; i < actions.size; i++) {
if (actions.get(i) == currentAction) {
running = true;
break;
}
}
}
if (!running) {
currentAction = pendingActions.poll();
game.data.stage.addAction(currentAction);
}
} else if (pendingSync.getAndSet(false)) {
send(new ContinueMessage());
}
}
private void execute(Action action) {
pendingActions.add(action);
}
private void execute(Runnable runnable) {
pendingActions.add(run(runnable));
}
private void seat() { private void seat() {
var count = players.size(); var count = players.size();
@ -272,17 +170,18 @@ public class GameScreen extends MenuScreen {
case 6 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP, Seat.TOP_RIGHT, Seat.RIGHT}; case 6 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP, Seat.TOP_RIGHT, Seat.RIGHT};
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
int index = players.indexOf(self); int index = players.indexOf(state.getSelf());
for (int i = 1; i < count; i++) { for (int i = 1; i < count; i++) {
var player = players.get((index + i) % count); var player = players.get((index + i) % count);
var seat = seats[i - 1]; var seat = seats[i - 1];
this.seats.put(player, seat); this.seats.put(player, seat);
} }
this.seats.put(state.getSelf(), Seat.BOTTOM);
} }
private void prepareLabels() { private void prepareLabels() {
for (UUID player : players) { for (UUID player : players) {
if (self.equals(player)) continue; if (state.getSelf().equals(player)) continue;
var label = new Label("", game.data.skin); var label = new Label("", game.data.skin);
var seat = seats.get(player); var seat = seats.get(player);
label.setX(seat.getLabelX()); label.setX(seat.getLabelX());
@ -296,185 +195,243 @@ public class GameScreen extends MenuScreen {
private void setNames() { private void setNames() {
for (int i = 0; i < players.size(); i++) { for (int i = 0; i < players.size(); i++) {
var player = players.get(i); var player = players.get(i);
var name = names.get(player); var name = state.getPlayers().get(player);
padOfTruth.setName(i, name); padOfTruth.setName(i, name);
if (!self.equals(player)) { if (!state.getSelf().equals(player)) {
nameLabels.get(player).setText(name); nameLabels.get(player).setText(name);
} }
} }
} }
private void startRound() { public void startRound(int round) {
round++; execute(parallel(
tricks.clear(); run(() -> {
predictions.clear(); if (trumpCardActor != null) {
trumpCardActor.remove();
trumpSuit = null; }
trumpCard = null;
if (trumpCardActor != null) {
trumpCardActor.remove();
}
trick = -1;
handCards.clearChildren();
hands.clear();
cardStack.clearChildren(); if (trumpSuitActor != null) {
stack.clear(); trumpSuitActor.remove();
}
new StartRoundOverlay(this, round).show(game.data.stage); handCards.clearChildren();
cardStack.clearChildren();
}),
new StartRoundOverlay(this, round)
));
} }
private void startTrick() { public void startTrick() {
trick++; setActivePlayer(null, null, 0);
execute(() -> cardStack.clearChildren());
setActivePlayer(null, null);
cardStack.clearChildren();
stack.clear();
} }
private void finishTrick(UUID player, List<Card> cards) { public void finishTrick(UUID player, List<Card> cards) {
stack.clear(); var seat = seats.get(player);
tricks.computeIfAbsent(player, p -> new ArrayList<>()).add(cards);
// TODO var action = parallel();
execute(sequence(
run(() -> cardStack.removeAll().forEach(card -> action.addAction(sequence(
targeting(card, changeParent(game.data.stage.getRoot())),
parallel(
targeting(card, rotateTo(0, 0.1f)),
targeting(card, moveTo(
seat.getFrontX() - card.getWidth() / 2,
seat.getFrontY() - card.getHeight() / 2,
0.25f
))
),
targeting(card, alpha(0, 0.5f)),
removeActor(card)
)))),
action
));
} }
private void setHand(UUID player, List<Card> cards) { public void setHand(UUID player, List<Card> cards) {
var hand = hands.computeIfAbsent(player, p -> new ArrayList<>()); if (state.getSelf().equals(player)) {
hand.clear(); execute(() -> handCards.update(cards));
hand.addAll(cards);
if (self.equals(player)) {
handCards.update(hand);
} }
} }
private void setTrump(Card trumpCard, Card.Suit trumpSuit) { public void setTrump(Card trumpCard, Card.Suit trumpSuit) {
this.trumpCard = trumpCard; if (trumpCardActor == null) {
this.trumpSuit = trumpSuit;
if (trumpCardActor != null) {
trumpCardActor.remove();
} else {
trumpCardActor = new CardActor(Card.HIDDEN, atlas); trumpCardActor = new CardActor(Card.HIDDEN, atlas);
} }
new TrumpOverlay(this, null, trumpCard, trumpSuit).show(game.data.stage); if (trumpSuitActor == null) {
} trumpSuitActor = new CardActor(Card.HIDDEN, atlas);
}
String player = null;
if (activePlayer != null && activePlayer.second() == PICK_TRUMP) {
player = state.getPlayers().get(activePlayer.first());
setActivePlayer(null, null, 0);
}
private void addPrediction(UUID player, int prediction) { execute(new TrumpOverlay(this, player, trumpCard, trumpSuit));
addMessage(names.get(player) + " predicted " + prediction + ".");
predictions.put(player, prediction);
padOfTruth.setPrediction(players.indexOf(player), round, prediction);
} }
private void playCard(UUID player, Card card) { public void addPrediction(int round, UUID player, int prediction) {
addMessage(names.get(player) + " played a card."); if (activePlayer != null && activePlayer.first().equals(player) && (activePlayer.second() == CHANGE_PREDICTION || activePlayer.second() == MAKE_PREDICTION)) {
setActivePlayer(null, null, 0);
}
var handCard = switch (card) { addMessage(state.getPlayers().get(player) + " predicted " + prediction + ".");
case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING; execute(() -> padOfTruth.setPrediction(players.indexOf(player), round, prediction));
case CLOUD_BLUE, CLOUD_RED, CLOUD_GREEN, CLOUD_YELLOW -> Card.CLOUD; }
case JUGGLER_BLUE, JUGGLER_RED, JUGGLER_GREEN, JUGGLER_YELLOW -> Card.JUGGLER;
default -> card;
};
if (hands.containsKey(player)) { public void playCard(UUID player, Card card) {
hands.get(player).remove(handCard); if (activePlayer != null && activePlayer.first().equals(player) && activePlayer.second() == PLAY_CARD) {
setActivePlayer(null, null, 0);
} }
stack.add(Pair.of(player, card));
CardActor actor; addMessage(state.getPlayers().get(player) + " played a card.");
Seat seat;
if (self.equals(player)) {
actor = handCards.remove(handCard);
seat = Seat.BOTTOM;
} else {
actor = new CardActor(card, atlas);
seat = seats.get(player);
actor.setPosition(seat.getX() - actor.getWidth() / 2, seat.getY() - actor.getHeight() / 2);
actor.setRotation(seat.getAngle());
}
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
cardStack.add(seat, actor);
}
private void addScores(Map<UUID, Integer> scores) { Seat seat = seats.get(player);
this.scores.put(round, scores);
for (int i = 0; i < players.size(); i++) { var sequence = sequence();
UUID player = players.get(i); sequence.addAction(run(() -> {
padOfTruth.setScore(i, round, scores.get(player)); CardActor actor;
} if (state.getSelf().equals(player)) {
actor = handCards.remove(card);
} else {
actor = new CardActor(card, atlas);
actor.setPosition(seat.getHandX() - actor.getWidth() / 2, seat.getHandY() - actor.getHeight() / 2);
actor.setRotation(seat.getHandAngle());
}
actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
cardStack.add(seat, actor);
sequence.addAction(delay(actor));
sequence.addAction(delay(0.5f));
}));
execute(sequence);
}
public void addScores(int round, Map<UUID, Integer> scores) {
execute(() -> {
for (int i = 0; i < players.size(); i++) {
UUID player = players.get(i);
padOfTruth.setScore(i, round, scores.get(player));
}
});
} }
private void setActivePlayer(UUID player, UserInputMessage.Action action) { public void setActivePlayer(UUID player, UserInputMessage.Action action, long timeout) {
if (activePlayer != null && nameLabels.containsKey(activePlayer.getKey())) { if (activePlayer != null && nameLabels.containsKey(activePlayer.getKey())) {
nameLabels.get(activePlayer.getKey()).getStyle().fontColor = Color.WHITE; var label = nameLabels.get(activePlayer.getKey());
execute(() -> label.setStyle(labelStyleDefault));
} }
if (player == null && action == null) { if (player == null && action == null) {
activePlayer = null; activePlayer = null;
} else { setPersistentMessage(null);
activePlayer = Pair.of(player, action); return;
if (nameLabels.containsKey(player)) { }
nameLabels.get(player).getStyle().fontColor = Color.RED;
activePlayer = Pair.of(player, action);
if (nameLabels.containsKey(player)) {
var label = nameLabels.get(player);
execute(() -> label.setStyle(labelStyleActive));
}
if (state.getSelf().equals(player)) {
setPersistentMessage(null);
switch (action) {
case PICK_TRUMP -> execute(new PickTrumpOverlay(this, timeout, false));
case MAKE_PREDICTION -> execute(new MakePredictionOverlay(this, timeout, state.getRound()));
case CHANGE_PREDICTION -> execute(new ChangePredictionOverlay(this, timeout, state.getRound(), state.getPredictions().get(state.getSelf())));
case PLAY_CARD -> setPersistentMessage("It is your turn to play a card.");
} }
// TODO do something
} else {
var name = state.getPlayers().get(player);
setPersistentMessage("It is " + name + "'s turn to " + switch (action) {
case CHANGE_PREDICTION -> "change their prediction";
case JUGGLE_CARD -> "juggle a card";
case PLAY_CARD -> "play a card";
case PICK_TRUMP -> "pick the trump suit";
case MAKE_PREDICTION -> "make a prediction";
default -> throw new AssertionError();
});
} }
} }
private void addMessage(String text) { public void addMessage(String text) {
addMessage(text, false);
}
public void addMessage(String text, boolean immediate) {
var label = new Label(text, game.data.skin); var label = new Label(text, game.data.skin);
label.addAction(sequence( label.addAction(sequence(
delay(1.5f), delay(1.5f),
alpha(0, 0.25f), alpha(0, 0.25f),
removeActor() removeActor()
)); ));
messages.addActor(label);
}
@Override Runnable runnable = () -> {
public void dispose() { if (persistentMessage != null && persistentMessage.getParent() == messages) {
super.dispose(); messages.addActorBefore(persistentMessage, label);
if (atlas != null) atlas.dispose(); } else {
messages.addActor(label);
}
};
if (immediate) {
runnable.run();
} else {
execute(runnable);
}
} }
public void onMessage(ServerMessage serverMessage) { public void setPersistentMessage(String text) {
if (serverMessage instanceof GameMessage gameMessage) { execute(() -> {
var observerMessage = gameMessage.getObserverMessage(); if (text != null) {
if (observerMessage instanceof CardMessage card) { if (persistentMessage == null) {
playCard(card.getPlayer(), card.getCard()); persistentMessage = new Label(text, getData().skin);
} else if (observerMessage instanceof TrickMessage trick) { } else {
finishTrick(trick.getPlayer(), trick.getCards()); persistentMessage.setText(text);
} else if (observerMessage instanceof HandMessage hand) {
setHand(hand.getPlayer(), hand.getHand());
} else if (observerMessage instanceof ScoreMessage score) {
addScores(score.getPoints());
} else if (observerMessage instanceof TrumpMessage trump) {
setTrump(trump.getCard(), trump.getSuit());
} else if (observerMessage instanceof PredictionMessage prediction) {
addPrediction(prediction.getPlayer(), prediction.getPrediction());
} else if (observerMessage instanceof UserInputMessage userInput) {
setActivePlayer(userInput.getPlayer(), userInput.getAction());
if (self.equals(userInput.getPlayer())) {
switch (userInput.getAction()) {
case PICK_TRUMP -> new PickTrumpOverlay(this, false).show(game.data.stage);
case MAKE_PREDICTION -> new MakePredictionOverlay(this, round).show(game.data.stage);
case CHANGE_PREDICTION -> new ChangePredictionOverlay(this, round, predictions.get(self)).show(game.data.stage);
}
// TODO do something
}
} else if (observerMessage instanceof StateMessage state) {
switch (state.getState()) {
case "starting_round" -> startRound();
case "starting_trick" -> startTrick();
case "finished", "error" -> {
// TODO do something
}
} }
messages.addActor(persistentMessage);
} else if (persistentMessage != null) {
persistentMessage.remove();
} }
});
}
public void send(PlayerMessage message) {
if (!sending.getAndSet(true) || (message instanceof ContinueMessage)) {
game.getClient().send(new InteractionMessage(message));
} else {
addMessage("Please slow down.", true);
} }
} }
public void ready(boolean success) {
if (!pendingSync.get()) {
sending.set(false);
}
if (success && currentAction instanceof Overlay overlay && overlay instanceof InteractionOverlay) {
overlay.finish();
}
}
public void timeout() {
addMessage("Timed out.");
ready(true);
}
public void sync() {
pendingSync.set(true);
sending.set(true);
}
@Override
public void dispose() {
if (atlas != null) atlas.dispose();
}
public WizardGame.Data getData() { public WizardGame.Data getData() {
return game.data; return game.data;
} }
@ -488,27 +445,31 @@ public class GameScreen extends MenuScreen {
TOP(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 200), TOP(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 200),
TOP_RIGHT(WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 200); TOP_RIGHT(WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 200);
private final float x; // position of the hand, should be offscreen
private final float y; private final float handX;
private final float handY;
private final float handAngle;
// position of the label
private final float labelX; private final float labelX;
private final float labelY; private final float labelY;
private final int labelAlign; private final int labelAlign;
private final float angle;
private final float cardX;
private final float cardY;
Seat(float x, float y, float labelX, float labelY, int labelAlign, float cardX, float cardY) { private final float frontX;
this.x = x; private final float frontY;
this.y = y;
Seat(float handX, float handY, float labelX, float labelY, int labelAlign, float frontX, float frontY) {
this.handX = handX;
this.handY = handY;
this.labelX = labelX; this.labelX = labelX;
this.labelY = labelY; this.labelY = labelY;
this.labelAlign = labelAlign; this.labelAlign = labelAlign;
this.cardX = cardX; this.frontX = frontX;
this.cardY = cardY; this.frontY = frontY;
var deltaX = WizardGame.WIDTH * 0.5f - x; var deltaX = WizardGame.WIDTH * 0.5f - handX;
var deltaY = WizardGame.HEIGHT * 0.5f - y; var deltaY = WizardGame.HEIGHT * 0.5f - handY;
this.angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX) + Math.PI / 2); this.handAngle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX) + Math.PI / 2);
} }
} }
} }

@ -0,0 +1,245 @@
package eu.jonahbauer.wizard.client.libgdx.state;
import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.data.SessionData;
import eu.jonahbauer.wizard.common.messages.observer.*;
import eu.jonahbauer.wizard.common.messages.server.AckMessage;
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
import eu.jonahbauer.wizard.common.messages.server.NackMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
@Log4j2
@Getter
public final class Game extends BaseState {
private final UUID self;
private final UUID session;
private final String sessionName;
private final String secret;
private final Map<UUID, String> players;
private final Map<UUID, Integer> scores = new HashMap<>();
private int round = -1;
private final Map<UUID, Integer> predictions = new HashMap<>();
private final Map<UUID, List<Card>> hands = new HashMap<>();
private final Map<UUID, List<List<Card>>> tricks = new HashMap<>();
private int trick = -1;
private final List<Pair<UUID, Card>> stack = new ArrayList<>();
private Card trumpCard;
private Card.Suit trumpSuit;
private GameScreen gameScreen;
public Game(UUID self, UUID session, String sessionName, String secret, Map<UUID, String> players) {
this.self = self;
this.session = session;
this.sessionName = sessionName;
this.secret = secret;
this.players = players;
}
@Override
public Optional<ClientState> onEnter(Client client) {
gameScreen = new GameScreen(client.getGame());
client.getGame().setScreen(gameScreen);
return super.onEnter(client);
}
@Override
public Optional<ClientState> onMessage(Client client, ServerMessage message) {
log(message);
if (message instanceof GameMessage game) {
var observerMessage = game.getObserverMessage();
if (observerMessage instanceof StateMessage state) {
switch (state.getState()) {
case "starting_round" -> {
round ++;
predictions.clear();
tricks.clear();
trumpSuit = null;
trumpCard = null;
stack.clear();
trick = -1;
gameScreen.startRound(round);
}
case "starting_trick" -> {
trick ++;
stack.clear();
gameScreen.startTrick();
}
case "finished", "error" -> {
return returnToSession();
}
}
} else if (observerMessage instanceof HandMessage hand) {
hands.put(hand.getPlayer(), hand.getHand());
gameScreen.setHand(hand.getPlayer(), hand.getHand());
} else if (observerMessage instanceof PredictionMessage prediction) {
predictions.put(prediction.getPlayer(), prediction.getPrediction());
gameScreen.addPrediction(round, prediction.getPlayer(), prediction.getPrediction());
} else if (observerMessage instanceof TrumpMessage trump) {
trumpCard = trump.getCard();
trumpSuit = trump.getSuit();
gameScreen.setTrump(trumpCard, trumpSuit);
} else if (observerMessage instanceof TrickMessage trick) {
this.stack.clear();
this.tricks.computeIfAbsent(trick.getPlayer(), player -> new ArrayList<>())
.add(trick.getCards());
gameScreen.finishTrick(trick.getPlayer(), trick.getCards());
} else if (observerMessage instanceof CardMessage card) {
this.stack.add(Pair.of(card.getPlayer(), card.getCard()));
var handCard = switch (card.getCard()) {
case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING;
case JUGGLER_BLUE, JUGGLER_GREEN, JUGGLER_RED, JUGGLER_YELLOW -> Card.JUGGLER;
case CLOUD_BLUE, CLOUD_GREEN, CLOUD_RED, CLOUD_YELLOW -> Card.CLOUD;
default -> card.getCard();
};
var hand = this.hands.get(card.getPlayer());
if (hand != null) {
hand.remove(handCard);
}
gameScreen.playCard(card.getPlayer(), handCard);
} else if (observerMessage instanceof ScoreMessage score) {
score.getPoints().forEach((player, points) -> scores.merge(player, points, Integer::sum));
gameScreen.addScores(round, score.getPoints());
} else if (observerMessage instanceof UserInputMessage input) {
if (input.getAction() == UserInputMessage.Action.SYNC) {
gameScreen.sync();
} else {
gameScreen.setActivePlayer(input.getPlayer(), input.getAction(), input.getTimeout());
}
} else if (observerMessage instanceof TimeoutMessage) {
gameScreen.timeout();
} else {
return Optional.of(new Menu());
}
return Optional.empty();
} else if (message instanceof NackMessage nack) {
int code = nack.getCode();
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
gameScreen.addMessage(nack.getMessage());
gameScreen.ready(false);
return Optional.empty();
} else {
return unexpectedMessage(client, message);
}
} else if (message instanceof AckMessage) {
gameScreen.ready(true);
return Optional.empty();
} else {
return unexpectedMessage(client, message);
}
}
private Optional<ClientState> returnToSession() {
return Optional.of(new Session(
new SessionData(session, sessionName, -1, null),
players.entrySet().stream()
.map(entry -> new PlayerData(entry.getKey(), entry.getValue(), false))
.toList(),
self, secret
));
}
private void log(ServerMessage message) {
if (message instanceof GameMessage gameMessage) {
var observerMessage = gameMessage.getObserverMessage();
if (observerMessage instanceof StateMessage state) {
switch (state.getState()) {
case "starting_round" -> log.info("Round {} is starting...", round + 1);
case "starting_trick" -> log.info("Trick {} is starting...", trick + 1);
case "finished" -> log.info("The game has finished.");
case "error" -> log.info("The game has finished with an error.");
}
} else if (observerMessage instanceof HandMessage hand) {
if (hand.getPlayer().equals(self)) {
log.info("Your hand cards are: {}", hand.getHand());
} else {
log.info("{}'s hand cards are: {}", nameOf(hand.getPlayer()), hand.getHand());
}
} else if (observerMessage instanceof PredictionMessage prediction) {
if (prediction.getPlayer().equals(self)) {
log.info("You predicted: {}%n", prediction.getPrediction());
} else {
log.info("{} predicted: {}%n", nameOf(prediction.getPlayer()), prediction.getPrediction());
}
} else if (observerMessage instanceof TrumpMessage trump) {
trumpCard = trump.getCard();
trumpSuit = trump.getSuit();
if (trumpCard == null) {
log.info("There is no trump in this round.");
} else {
log.info("The trump suit is {} ({}).", trumpSuit, trumpCard);
}
} else if (observerMessage instanceof TrickMessage trick) {
log.info("This trick {} goes to {}.", trick.getCards(), nameOf(trick.getPlayer()));
} else if (observerMessage instanceof CardMessage card) {
if (card.getPlayer().equals(self)) {
log.info("You played {}.", card.getCard());
} else {
log.info("{} played {}.", nameOf(card.getPlayer()), card.getCard());
}
} else if (observerMessage instanceof ScoreMessage score) {
log.info("The scores are as follows: " + score.getPoints());
} else if (observerMessage instanceof UserInputMessage input) {
if (input.getAction() != UserInputMessage.Action.SYNC) {
if (self.equals(input.getPlayer())) {
log.info("It is your turn to {}. You have time until {}.", switch (input.getAction()) {
case CHANGE_PREDICTION -> "change your prediction";
case JUGGLE_CARD -> "juggle a card";
case PLAY_CARD -> "play a card";
case PICK_TRUMP -> "pick the trump suit";
case MAKE_PREDICTION -> "make a prediction";
default -> throw new AssertionError();
}, LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()), ZoneId.systemDefault()));
} else {
log.info(
"Waiting for input {} from {}. (times out at {})",
input.getAction(),
nameOf(input.getPlayer()),
LocalDateTime.ofInstant(Instant.ofEpochMilli(input.getTimeout()), ZoneId.systemDefault())
);
}
}
} else if (observerMessage instanceof TimeoutMessage) {
log.info("The previous interaction timed out.");
} else {
log.fatal("Unknown observer message type {}.", observerMessage.getClass());
}
} else if (message instanceof NackMessage nack) {
int code = nack.getCode();
if (code == NackMessage.ILLEGAL_ARGUMENT || code == NackMessage.ILLEGAL_STATE) {
log.error(nack.getMessage());
} else {
log.fatal("Unexpected message: {}", message);
}
} else if (message instanceof AckMessage) {
log.info("OK");
}
}
private String nameOf(UUID player) {
if (player == null) {
return "all players";
} else {
return players.get(player);
}
}
}

@ -74,11 +74,10 @@ public final class Session extends BaseState {
return Optional.empty(); return Optional.empty();
} else if (message instanceof StartingGameMessage) { } else if (message instanceof StartingGameMessage) {
return Optional.empty(); return Optional.of(new Game(
// return Optional.of(new Game( self, session, sessionName, secret,
// self, session, sessionName, secret, players.stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName))
// players.stream().collect(Collectors.toMap(PlayerData::getUuid, PlayerData::getName)) ));
// ));
} else if (sending && message instanceof NackMessage nack) { } else if (sending && message instanceof NackMessage nack) {
// TODO display error // TODO display error
log.error(nack.getMessage()); log.error(nack.getMessage());

Loading…
Cancel
Save