Resolve #129: Allow liking plays during revealing.
This commit is contained in:
@@ -29,6 +29,7 @@ class LikeActions extends Actions.Implementation<
|
||||
if (
|
||||
lobby.game.round.verifyStage<Round.Revealing | Round.Judging>(
|
||||
action,
|
||||
"Revealing",
|
||||
"Judging"
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as Play from "../../games/cards/play";
|
||||
import * as StartRevealing from "./start-revealing";
|
||||
import * as Card from "../../games/cards/card";
|
||||
|
||||
/**
|
||||
* Indicates the czar has finished revealing the plays and is now picking a winner.
|
||||
*/
|
||||
export interface StartJudging {
|
||||
export interface StartJudging extends StartRevealing.AfterPlaying {
|
||||
event: "StartJudging";
|
||||
/**
|
||||
* The plays that are to be judged. If the revealing stage was played, this won't be included as the data will have
|
||||
@@ -12,7 +14,13 @@ export interface StartJudging {
|
||||
plays?: Play.Revealed[];
|
||||
}
|
||||
|
||||
export const of = (plays?: Play.Revealed[]): StartJudging => ({
|
||||
export const of = (
|
||||
plays?: Play.Revealed[],
|
||||
played?: Play.Id,
|
||||
drawn?: Card.Response[]
|
||||
): StartJudging => ({
|
||||
event: "StartJudging",
|
||||
plays,
|
||||
played,
|
||||
drawn,
|
||||
});
|
||||
|
||||
@@ -5,17 +5,32 @@ import * as Card from "../../games/cards/card";
|
||||
* Indicates players have finished playing into the round and now the czar
|
||||
* should reveal the plays.
|
||||
*/
|
||||
export interface StartRevealing {
|
||||
export interface StartRevealing extends AfterPlaying {
|
||||
event: "StartRevealing";
|
||||
plays: Play.Id[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Details in an event after finishing playing.
|
||||
*/
|
||||
export interface AfterPlaying {
|
||||
/**
|
||||
* The id of the play the user receiving this event played, if they did play one.
|
||||
*/
|
||||
played?: Play.Id;
|
||||
/**
|
||||
* The cards drawn by the player receiving this event.
|
||||
*/
|
||||
drawn?: Card.Response[];
|
||||
}
|
||||
|
||||
export const of = (
|
||||
plays: Play.Id[],
|
||||
played?: Play.Id,
|
||||
drawn?: Card.Response[]
|
||||
): StartRevealing => ({
|
||||
event: "StartRevealing",
|
||||
plays,
|
||||
...(drawn !== undefined ? { drawn } : {}),
|
||||
played,
|
||||
drawn,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as Card from "../../games/cards/card";
|
||||
import { Hand } from "../../games/cards/hand";
|
||||
import * as Lobby from "../../lobby";
|
||||
import { LikeDetail } from "../../games/game/round/public";
|
||||
|
||||
/**
|
||||
* Synchronise the game state.
|
||||
@@ -10,17 +11,20 @@ export interface Sync {
|
||||
state: Lobby.Public;
|
||||
hand?: Hand;
|
||||
play?: Card.Id[];
|
||||
likeDetail?: LikeDetail;
|
||||
gameTime: number;
|
||||
}
|
||||
|
||||
export const of = (
|
||||
state: Lobby.Public,
|
||||
hand?: Hand,
|
||||
play?: Card.Id[]
|
||||
play?: Card.Id[],
|
||||
likeDetail?: LikeDetail
|
||||
): Sync => ({
|
||||
event: "Sync",
|
||||
state,
|
||||
...(hand !== undefined ? { hand } : {}),
|
||||
...(play !== undefined ? { play } : {}),
|
||||
...(likeDetail !== undefined ? { likeDetail } : {}),
|
||||
gameTime: Date.now(),
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@ import * as RoundStageTimerDone from "../../timeout/round-stage-timer-done";
|
||||
import * as Timeout from "../../timeout";
|
||||
import * as Event from "../../event";
|
||||
import * as StartJudging from "../../events/game-event/start-judging";
|
||||
import * as StartRevealing from "../../events/game-event/start-revealing";
|
||||
import * as Rules from "../rules";
|
||||
import * as Game from "../game";
|
||||
|
||||
export type Round = Playing | Revealing | Judging | Complete;
|
||||
|
||||
@@ -45,12 +47,13 @@ export abstract class Base<TStage extends Stage> {
|
||||
*/
|
||||
public verifyStage<TRound extends Round>(
|
||||
action: Action,
|
||||
expected: TRound["stage"]
|
||||
...expected: TRound["stage"][]
|
||||
): this is TRound {
|
||||
if (this.stage !== expected) {
|
||||
throw new IncorrectRoundStageError(action, this.stage, expected);
|
||||
if (expected.some((n) => n == this.stage)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new IncorrectRoundStageError(action, this.stage, ...expected);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +185,8 @@ export class Judging extends Base<"Judging"> implements Timed {
|
||||
|
||||
public start(
|
||||
rules: Rules.Rules,
|
||||
previouslyRevealed: boolean
|
||||
previouslyRevealed: boolean,
|
||||
newCardsAndPlayedByPlayer: Map<User.Id, StartRevealing.AfterPlaying>
|
||||
): {
|
||||
timeouts?: Iterable<Timeout.After>;
|
||||
events?: Iterable<Event.Distributor>;
|
||||
@@ -191,7 +195,15 @@ export class Judging extends Base<"Judging"> implements Timed {
|
||||
const plays = previouslyRevealed
|
||||
? undefined
|
||||
: Array.from(this.revealedPlays());
|
||||
const event = Event.targetAll(StartJudging.of(plays));
|
||||
if (previouslyRevealed) {
|
||||
for (const [_, v] of newCardsAndPlayedByPlayer) {
|
||||
delete v.played;
|
||||
}
|
||||
}
|
||||
const event = Event.additionally(
|
||||
StartJudging.of(plays),
|
||||
newCardsAndPlayedByPlayer
|
||||
);
|
||||
return {
|
||||
timeouts: Util.asOptionalIterable(timeout),
|
||||
events: Util.asOptionalIterable(event),
|
||||
@@ -271,9 +283,30 @@ export class Revealing extends Base<"Revealing"> implements Timed {
|
||||
this.timedOut = timedOut;
|
||||
}
|
||||
|
||||
public start(
|
||||
game: Game.Game
|
||||
): {
|
||||
events?: Iterable<Event.Distributor>;
|
||||
timeouts?: Iterable<Timeout.After>;
|
||||
} {
|
||||
const playsToBeRevealed = Array.from(
|
||||
wu(game.round.plays).map((play) => play.id)
|
||||
);
|
||||
const events = Util.asOptionalIterable(
|
||||
Event.additionally(
|
||||
StartRevealing.of(playsToBeRevealed),
|
||||
this.getAfterPlayingDetails(game)
|
||||
)
|
||||
);
|
||||
const timeouts = Util.asOptionalIterable(
|
||||
RoundStageTimerDone.ifEnabled(game.round, game.rules.stages)
|
||||
);
|
||||
return { events, timeouts };
|
||||
}
|
||||
|
||||
public advance(
|
||||
rules: Rules.Rules,
|
||||
previouslyRevealed = true
|
||||
game: Game.Game,
|
||||
previouslyRevealed: boolean
|
||||
):
|
||||
| {
|
||||
round: Judging;
|
||||
@@ -283,7 +316,11 @@ export class Revealing extends Base<"Revealing"> implements Timed {
|
||||
| undefined {
|
||||
if (StoredPlay.allRevealed(this)) {
|
||||
const judging = new Judging(this.id, this.czar, this.call, this.plays);
|
||||
const start = judging.start(rules, previouslyRevealed);
|
||||
const start = judging.start(
|
||||
game.rules,
|
||||
previouslyRevealed,
|
||||
this.getAfterPlayingDetails(game)
|
||||
);
|
||||
return {
|
||||
round: judging,
|
||||
...start,
|
||||
@@ -293,6 +330,36 @@ export class Revealing extends Base<"Revealing"> implements Timed {
|
||||
}
|
||||
}
|
||||
|
||||
private getAfterPlayingDetails(
|
||||
game: Game.Game
|
||||
): Map<User.Id, StartRevealing.AfterPlaying> {
|
||||
const slotCount = Card.slotCount(game.round.call);
|
||||
const extraCards =
|
||||
slotCount > 2 ||
|
||||
(slotCount === 2 && game.rules.houseRules.packingHeat !== undefined)
|
||||
? slotCount - 1
|
||||
: 0;
|
||||
const newCardsAndPlayedByPlayer = new Map<
|
||||
User.Id,
|
||||
StartRevealing.AfterPlaying
|
||||
>();
|
||||
for (const play of game.round.plays) {
|
||||
const idSet = new Set(play.play.map((c) => c.id));
|
||||
const player = game.players[play.playedBy];
|
||||
if (player !== undefined) {
|
||||
player.hand = player.hand.filter((card) => !idSet.has(card.id));
|
||||
const toDraw = play.play.length - extraCards;
|
||||
const drawn = game.decks.responses.draw(toDraw);
|
||||
newCardsAndPlayedByPlayer.set(play.playedBy, {
|
||||
drawn,
|
||||
played: play.id,
|
||||
});
|
||||
player.hand.push(...drawn);
|
||||
}
|
||||
}
|
||||
return newCardsAndPlayedByPlayer;
|
||||
}
|
||||
|
||||
public waitingFor(): Set<User.Id> | null {
|
||||
return new Set(this.czar);
|
||||
}
|
||||
@@ -363,31 +430,42 @@ export class Playing extends Base<"Playing"> implements Timed {
|
||||
this.timedOut = timedOut;
|
||||
}
|
||||
|
||||
public advance(): Revealing {
|
||||
return new Revealing(
|
||||
public advance(
|
||||
game: Game.Game,
|
||||
doNotStart = false
|
||||
): {
|
||||
round: Revealing;
|
||||
events?: Iterable<Event.Distributor>;
|
||||
timeouts?: Iterable<Timeout.After>;
|
||||
} {
|
||||
const revealing = new Revealing(
|
||||
this.id,
|
||||
this.czar,
|
||||
this.call,
|
||||
Util.shuffled(this.plays)
|
||||
);
|
||||
return {
|
||||
round: revealing,
|
||||
...(doNotStart ? {} : revealing.start(game)),
|
||||
};
|
||||
}
|
||||
|
||||
public skipToJudging(
|
||||
rules: Rules.Rules
|
||||
game: Game.Game
|
||||
): {
|
||||
round: Judging;
|
||||
timeouts?: Iterable<Timeout.After>;
|
||||
events?: Iterable<Event.Distributor>;
|
||||
} {
|
||||
const revealing = this.advance();
|
||||
for (const play of revealing.plays) {
|
||||
const advanceRevealing = this.advance(game, true);
|
||||
for (const play of advanceRevealing.round.plays) {
|
||||
play.revealed = true;
|
||||
}
|
||||
const advance = revealing.advance(rules, false);
|
||||
if (advance === undefined) {
|
||||
const advanceJudging = advanceRevealing.round.advance(game, false);
|
||||
if (advanceJudging === undefined) {
|
||||
throw new Error("All plays should have been revealed automatically.");
|
||||
}
|
||||
return advance;
|
||||
return advanceJudging;
|
||||
}
|
||||
|
||||
public waitingFor(): Set<User.Id> | null {
|
||||
|
||||
@@ -23,6 +23,11 @@ export interface Playing extends Base, Timed {
|
||||
played: User.Id[];
|
||||
}
|
||||
|
||||
export interface LikeDetail {
|
||||
liked: Play.Id[];
|
||||
played?: Play.Id;
|
||||
}
|
||||
|
||||
export interface Revealing extends Base, Timed {
|
||||
stage: "Revealing";
|
||||
plays: Play.PotentiallyRevealed[];
|
||||
|
||||
@@ -139,6 +139,7 @@ export class SocketManager {
|
||||
await Change.apply(server, auth.gc, (lobby) => {
|
||||
let hand = undefined;
|
||||
let play = undefined;
|
||||
let likeDetail = undefined;
|
||||
if (lobby.game !== undefined) {
|
||||
const player = lobby.game.players[uid];
|
||||
if (player !== undefined) {
|
||||
@@ -150,6 +151,16 @@ export class SocketManager {
|
||||
if (potentialPlay !== undefined) {
|
||||
play = potentialPlay.play.map((card) => card.id);
|
||||
}
|
||||
const round = lobby.game.round;
|
||||
const stage = round.stage;
|
||||
if (stage === "Revealing" || stage === "Judging") {
|
||||
const liked = round.plays
|
||||
.filter((p) => p.likes.some((l) => l === uid))
|
||||
.map((p) => p.id);
|
||||
const played = round.plays.find((p) => p.playedBy === uid)
|
||||
?.id;
|
||||
likeDetail = { played, liked };
|
||||
}
|
||||
}
|
||||
|
||||
const user = lobby.users[uid];
|
||||
@@ -158,7 +169,7 @@ export class SocketManager {
|
||||
lobby,
|
||||
events: [
|
||||
Event.targetOnly(
|
||||
Sync.of(Lobby.censor(lobby), hand, play),
|
||||
Sync.of(Lobby.censor(lobby), hand, play, likeDetail),
|
||||
uid
|
||||
),
|
||||
],
|
||||
|
||||
@@ -7,6 +7,8 @@ import * as Timeout from "../timeout";
|
||||
import * as Util from "../util";
|
||||
import * as RoundStageTimerDone from "./round-stage-timer-done";
|
||||
import * as Rules from "../games/rules";
|
||||
import * as User from "../user";
|
||||
import * as Game from "../games/game";
|
||||
/**
|
||||
* Indicates that the round should start the revealing phase if it is appropriate
|
||||
* to do so.
|
||||
@@ -49,59 +51,18 @@ export const handle: Timeout.Handler<FinishedPlaying> = (
|
||||
|
||||
// We discard the plays first so if the deck runs out, all the cards get
|
||||
// rotated in.
|
||||
const responses = game.decks.responses;
|
||||
for (const play of game.round.plays) {
|
||||
responses.discard(play.play);
|
||||
game.decks.responses.discard(play.play);
|
||||
}
|
||||
|
||||
const slotCount = Card.slotCount(game.round.call);
|
||||
const extraCards =
|
||||
slotCount > 2 ||
|
||||
(slotCount === 2 && game.rules.houseRules.packingHeat !== undefined)
|
||||
? slotCount - 1
|
||||
: 0;
|
||||
const newCardsByPlayer = new Map();
|
||||
for (const play of game.round.plays) {
|
||||
const idSet = new Set(play.play.map((c) => c.id));
|
||||
const player = game.players[play.playedBy];
|
||||
if (player !== undefined) {
|
||||
player.hand = player.hand.filter((card) => !idSet.has(card.id));
|
||||
const toDraw = play.play.length - extraCards;
|
||||
const drawn = responses.draw(toDraw);
|
||||
newCardsByPlayer.set(play.playedBy, { drawn });
|
||||
player.hand.push(...drawn);
|
||||
}
|
||||
}
|
||||
|
||||
if (game.rules.stages.revealing === undefined) {
|
||||
const { round, events, timeouts } = game.round.skipToJudging(game.rules);
|
||||
game.round = round;
|
||||
return {
|
||||
lobby,
|
||||
events,
|
||||
timeouts,
|
||||
};
|
||||
} else {
|
||||
game.round = game.round.advance();
|
||||
|
||||
const playsToBeRevealed = Array.from(
|
||||
wu(game.round.plays).map((play) => play.id)
|
||||
);
|
||||
const events = [
|
||||
Event.additionally(
|
||||
StartRevealing.of(playsToBeRevealed),
|
||||
newCardsByPlayer
|
||||
),
|
||||
];
|
||||
|
||||
const timeouts = Util.asOptionalIterable(
|
||||
RoundStageTimerDone.ifEnabled(game.round, game.rules.stages)
|
||||
);
|
||||
|
||||
return {
|
||||
lobby,
|
||||
events: events,
|
||||
timeouts: timeouts,
|
||||
};
|
||||
}
|
||||
const { round, events, timeouts } =
|
||||
game.rules.stages.revealing === undefined
|
||||
? game.round.skipToJudging(game)
|
||||
: game.round.advance(game);
|
||||
game.round = round;
|
||||
return {
|
||||
lobby,
|
||||
events,
|
||||
timeouts,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ export const handle: Timeout.Handler<FinishedRevealing> = (
|
||||
if (round.stage !== "Revealing") {
|
||||
return {};
|
||||
}
|
||||
const advanced = round.advance(game.rules);
|
||||
const advanced = round.advance(game, true);
|
||||
if (advanced === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user