Prevent choosing stages that don't support the number of players in the game

This commit is contained in:
Andrio Celos
2024-02-22 15:44:58 +11:00
parent 125531cfbe
commit a70b5a9700
8 changed files with 50 additions and 16 deletions

View File

@@ -75,7 +75,7 @@ function showStageSelectionForm(prompt: StageSelectionPrompt | null, isReady: bo
let i = -1;
for (const button of stageButtons.buttons) {
const originalClass = i < 0 ? 'stageRandom' : 'stage';
if (prompt.bannedStages?.includes(i)) {
if (i >= 0 && (currentGame!.game.maxPlayers > stageDatabase.stages![i].maxPlayers || prompt.bannedStages?.includes(i))) {
button.buttonElement.className = `${originalClass} banned`;
button.enabled = false;
} else if (prompt.struckStages?.includes(i)) {

View File

@@ -93,7 +93,7 @@ function stageSwitchButton_click(e: Event) {
let status = button.dataset.status == '0' ? 1 : button.dataset.status == '1' ? 2 : 0;
button.dataset.status = status.toString();
(<HTMLElement>button.getElementsByClassName('stageStatus')[0]).innerText = [ 'Allowed', 'Counterpick only', 'Banned' ][status];
gameSetupSubmitButton.disabled = stageSwitchButtons.every(b => b.dataset.status != '0');
updateCreateRoomButton();
}
maxPlayersBox.addEventListener('change', () => {
@@ -103,8 +103,18 @@ maxPlayersBox.addEventListener('change', () => {
else
maxPlayersBox.value = '2';
}
const maxPlayers = parseInt(maxPlayersBox.value);
for (let i = 0; i < stageDatabase.stages!.length; i++) {
stageSwitchButtons[i].disabled = maxPlayers > stageDatabase.stages![i].maxPlayers;
}
updateCreateRoomButton();
});
function updateCreateRoomButton() {
const maxPlayers = parseInt(maxPlayersBox.value);
gameSetupSubmitButton.disabled = stageSwitchButtons.every((b, i) => b.dataset.status != '0' || maxPlayers > stageDatabase.stages![i].maxPlayers);
}
newGameSetupButton.addEventListener('click', _ => {
gameSetupDialog.showModal();
});
@@ -172,8 +182,9 @@ function createRoom(useOptionsForm: boolean) {
data.append('name', name);
data.append('clientToken', clientToken);
if (useOptionsForm) {
const maxPlayers = parseInt(maxPlayersBox.value);
const settings = <CustomRoomConfig> {
maxPlayers: parseInt(maxPlayersBox.value),
maxPlayers,
turnTimeLimit: turnTimeLimitBox.value ? turnTimeLimitBox.valueAsNumber : null,
goalWinCount: goalWinCountBox.value ? parseInt(goalWinCountBox.value) : null,
allowUpcomingCards: gameSetupAllowUpcomingCardsBox.checked,

View File

@@ -13,6 +13,8 @@ class Stage {
return new Stage(obj.name, obj.grid, obj.startSpaces);
}
get maxPlayers() { return Math.max(...this.startSpaces.map(a => a.length)); }
getStartSpaces(numPlayers: number) {
let list = null as Point[] | null;
for (const list2 of this.startSpaces) {

View File

@@ -75,6 +75,7 @@ class StageButton extends CheckButton {
this.startCells.splice(0);
const startSpaces = this.stage.getStartSpaces(numPlayers);
if (startSpaces == null) return;
for (let i = 0; i < numPlayers; i++) {
const space = startSpaces[i];
const cell = this.cells[space.x][space.y]!;

View File

@@ -115,6 +115,10 @@ public class Game(int maxPlayers) {
return false;
}
} else {
if (stages.Any(i => i >= StageDatabase.Stages.Count)) {
error = new(HttpStatusCode.UnprocessableEntity, "InvalidStage", "Invalid stage selection.");
return false;
}
var rule = this.GetCurrentStageSelectionRule();
if (stages.Intersect(rule.BannedStages).Any()) {
error = new(HttpStatusCode.UnprocessableEntity, "IllegalStage", "A selected stage is banned.");
@@ -125,10 +129,6 @@ public class Game(int maxPlayers) {
return false;
}
}
if (stages.Any(i => i >= StageDatabase.Stages.Count)) {
error = new(HttpStatusCode.UnprocessableEntity, "StageNotFound", "No such stage is known.");
return false;
}
player.selectedStages = stages;
error = default;
return true;
@@ -495,10 +495,10 @@ public class Game(int maxPlayers) {
var stage = StageDatabase.Stages[stageIndex];
this.StageIndex = stageIndex;
this.setStages.Add(stageIndex);
this.Board = (Space[,]) stage.grid.Clone();
this.Board = (Space[,]) stage.Grid.Clone();
// Place starting positions.
var list = stage.startSpaces.Where(s => s.Length >= this.Players.Count).MinBy(s => s.Length) ?? throw new InvalidOperationException("Couldn't find start spaces");
var list = stage.StartSpaces.Where(s => s.Length >= this.Players.Count).MinBy(s => s.Length) ?? throw new InvalidOperationException("Couldn't find start spaces");
this.StartSpaces = list;
for (int i = 0; i < this.Players.Count; i++)
this.Board[list[i].X, list[i].Y] = Space.SpecialInactive1 | (Space) i;

View File

@@ -179,21 +179,21 @@ internal partial class Program {
StageSelectionRules? stageSelectionRuleFirst = null, stageSelectionRuleAfterWin = null, stageSelectionRuleAfterDraw = null;
if (d.TryGetValue("stageSelectionRuleFirst", out var json1)) {
if (!TryParseStageSelectionRule(json1, out stageSelectionRuleFirst) || stageSelectionRuleFirst.Method is StageSelectionMethod.Same or StageSelectionMethod.Counterpick) {
if (!TryParseStageSelectionRule(json1, maxPlayers, out stageSelectionRuleFirst) || stageSelectionRuleFirst.Method is StageSelectionMethod.Same or StageSelectionMethod.Counterpick) {
SetErrorResponse(e.Response, new(HttpStatusCode.UnprocessableEntity, "InvalidGameSettings", "stageSelectionRuleFirst was invalid."));
return;
}
} else
stageSelectionRuleFirst = StageSelectionRules.Default;
if (d.TryGetValue("stageSelectionRuleAfterWin", out var json2)) {
if (!TryParseStageSelectionRule(json2, out stageSelectionRuleAfterWin)) {
if (!TryParseStageSelectionRule(json2, maxPlayers, out stageSelectionRuleAfterWin)) {
SetErrorResponse(e.Response, new(HttpStatusCode.UnprocessableEntity, "InvalidGameSettings", "stageSelectionRuleAfterWin was invalid."));
return;
}
} else
stageSelectionRuleAfterWin = stageSelectionRuleFirst;
if (d.TryGetValue("stageSelectionRuleAfterDraw", out var json3)) {
if (!TryParseStageSelectionRule(json3, out stageSelectionRuleAfterDraw) || stageSelectionRuleAfterDraw.Method == StageSelectionMethod.Counterpick) {
if (!TryParseStageSelectionRule(json3, maxPlayers, out stageSelectionRuleAfterDraw) || stageSelectionRuleAfterDraw.Method == StageSelectionMethod.Counterpick) {
SetErrorResponse(e.Response, new(HttpStatusCode.UnprocessableEntity, "InvalidGameSettings", "stageSelectionRuleAfterDraw was invalid."));
return;
}
@@ -766,10 +766,16 @@ internal partial class Program {
return false;
}
private static bool TryParseStageSelectionRule(string json, [MaybeNullWhen(false)] out StageSelectionRules stageSelectionRule) {
private static bool TryParseStageSelectionRule(string json, int maxPlayers, [MaybeNullWhen(false)] out StageSelectionRules stageSelectionRule) {
try {
stageSelectionRule = JsonUtils.Deserialise<StageSelectionRules>(json);
return stageSelectionRule != null;
if (stageSelectionRule == null) return false;
stageSelectionRule.AddUnavailableStages(maxPlayers);
// Check that at least one stage is allowed.
for (var i = 0; i < StageDatabase.Stages.Count; i++) {
if (!stageSelectionRule.BannedStages.Contains(i)) return true;
}
return false;
} catch (JsonSerializationException) {
stageSelectionRule = null;
return false;

View File

@@ -4,7 +4,7 @@ namespace TableturfBattleServer;
public class Stage(string name, Space[,] grid, Point[][] startSpaces) {
public string Name { get; } = name ?? throw new ArgumentNullException(nameof(name));
[JsonProperty]
internal readonly Space[,] grid = grid ?? throw new ArgumentNullException(nameof(grid));
internal Space[,] Grid = grid ?? throw new ArgumentNullException(nameof(grid));
/// <summary>
/// The lists of starting spaces on this stage.
/// </summary>
@@ -13,5 +13,8 @@ public class Stage(string name, Space[,] grid, Point[][] startSpaces) {
/// For example, if there is a list of 3 and a list of 4, the list of 3 will be used for 2 or 3 players, and the list of 4 will be used for 4 players.
/// </remarks>
[JsonProperty]
internal readonly Point[][] startSpaces = startSpaces ?? throw new ArgumentNullException(nameof(startSpaces));
internal Point[][] StartSpaces = startSpaces ?? throw new ArgumentNullException(nameof(startSpaces));
[JsonIgnore]
public int MaxPlayers => this.StartSpaces.Max(a => a.Length);
}

View File

@@ -5,6 +5,17 @@ public class StageSelectionRules(StageSelectionMethod method, int[]? bannedStage
public int[] BannedStages { get; set; } = bannedStages ?? Array.Empty<int>();
public static StageSelectionRules Default { get; } = new(StageSelectionMethod.Vote, Array.Empty<int>());
public void AddUnavailableStages(int maxPlayers) {
if (maxPlayers == 2) return;
var list = new List<int>(this.BannedStages);
for (var i = 0; i < StageDatabase.Stages.Count; i++) {
if (maxPlayers > StageDatabase.Stages[i].MaxPlayers)
list.Add(i);
}
this.BannedStages = list.ToArray();
}
}
public enum StageSelectionMethod {