Skip to content

Commit

Permalink
Standardize on rank for tracks
Browse files Browse the repository at this point in the history
Maintains backwards compatability with old `level` and `difficulty` terms.
  • Loading branch information
cwegrzyn committed May 20, 2024
1 parent 21561f7 commit 2554961
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 41 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,19 +265,19 @@ Box/tick amounts can be given either as a single `from` argument, or with
- `from-boxes` (optional): the starting value of the progress track, in boxes.
- `from-ticks` (optional): the starting value of the progress track, in ticks
filled into the last unfilled box.
- `level` - the difficulty level of the progress track (e.g. `"formidable"`, `"epic"`, etc).
- `rank` - the challenge rank of the progress track (e.g. `"formidable"`, `"epic"`, etc).
- `steps` (optional, default: 1) - number of times to mark progress.

##### Example

```kdl
progress "My Background Vow" from-boxes=3 from-ticks=2 level="formidable" steps=2
progress "My Background Vow" from-boxes=3 from-ticks=2 rank="formidable" steps=2
```

#### `track`

Marks progress on a progress track. Can be used interchangeably with
`progress`, but doesn't encode the track difficulty level or the number of
`progress`, but doesn't encode the track challenge rank or the number of
times progress was marked. Most often, this node would be used for moves that
say something like "erase two ticks from TKTK".

Expand Down
2 changes: 1 addition & 1 deletion src/characters/lens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ describe("Special Tracks", () => {
progress: 4,
unbounded: true,
complete: false,
difficulty: ChallengeRanks.Epic,
rank: ChallengeRanks.Epic,
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/characters/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function legacyTrack(
lens: {
get(source) {
return ProgressTrack.create_({
difficulty: ChallengeRanks.Epic,
rank: ChallengeRanks.Epic,
// SAFE: a validated character will satisfy the above schema
progress: source[progressKey] as number,
complete: false,
Expand Down
2 changes: 1 addition & 1 deletion src/mechanics/css/dlist-progress.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
}
}

&.level {
&.rank {
text-transform: capitalize;
color: var(--text-faint);
&:before {
Expand Down
6 changes: 3 additions & 3 deletions src/mechanics/mechanics-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export class MechanicsRenderer {
node.properties.from ??
((node.properties["from-boxes"] as number) ?? 0) * 4 +
((node.properties["from-ticks"] as number) ?? 0),
difficulty: node.properties.level,
rank: node.properties.rank ?? node.properties.level,
unbounded: (node.properties.unbounded ?? false) as boolean,
complete: false,
});
Expand All @@ -327,7 +327,7 @@ export class MechanicsRenderer {
const startTrack = result.value;

const [fromBoxes, fromTicks] = startTrack.boxesAndTicks();
const level = startTrack.difficulty;
const rank = startTrack.rank;
const steps = (node.properties.steps ?? node.values[3] ?? 1) as number;

const endTrack = startTrack.advanced(steps);
Expand All @@ -338,7 +338,7 @@ export class MechanicsRenderer {
cls: "steps " + (steps < 0 ? "negative" : "positive"),
value: steps,
},
Level: { cls: "level", value: level },
Rank: { cls: "rank", value: rank },
"From Boxes": { cls: "from-boxes", value: fromBoxes },
"From Ticks": { cls: "from-ticks", value: fromTicks },
"To Boxes": { cls: "to-boxes", value: toBoxes },
Expand Down
2 changes: 1 addition & 1 deletion src/mechanics/node-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function createProgressNode(
properties: {
name: `[[${trackContext.location}|${trackContext.name}]]`,
from: trackContext.track.progress,
level: trackContext.track.difficulty,
rank: trackContext.track.rank,
steps,
},
});
Expand Down
10 changes: 5 additions & 5 deletions src/tracks/progress-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function generateTrackName(name: string): string {

export class ProgressTrackCreateModal extends Modal {
public result = {
difficulty: ChallengeRanks.Dangerous,
rank: ChallengeRanks.Dangerous,
progress: 0,
name: "",
tracktype: "",
Expand Down Expand Up @@ -114,13 +114,13 @@ export class ProgressTrackCreateModal extends Modal {

// TODO: since the string value equals the display string, i don't actually know if this
// is working as intended with the options
new Setting(contentEl).setName("Difficulty").addDropdown((dropdown) =>
new Setting(contentEl).setName("Rank").addDropdown((dropdown) =>
dropdown
.addOptions(ChallengeRanks)
.onChange((value) => {
this.result.difficulty = value as ChallengeRanks;
this.result.rank = value as ChallengeRanks;
})
.setValue(this.result.difficulty),
.setValue(this.result.rank),
);

new Setting(contentEl).setName("Type").addSearch((search) => {
Expand Down Expand Up @@ -166,7 +166,7 @@ export class ProgressTrackCreateModal extends Modal {
tracktype: this.result.tracktype,
fileName: this.result.fileName,
track: ProgressTrack.create_({
difficulty: this.result.difficulty,
rank: this.result.rank,
progress: this.result.progress,
complete: false,
unbounded: false,
Expand Down
37 changes: 21 additions & 16 deletions src/tracks/progress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {

describe("ProgressTrack", () => {
const TEST_DATA: ProgressTrackSchema = {
difficulty: ChallengeRanks.Dangerous,
rank: ChallengeRanks.Dangerous,
progress: 10,
complete: false,
unbounded: false,
Expand All @@ -27,22 +27,22 @@ describe("ProgressTrack", () => {
expect(result.isRight()).toBeTruthy();
const track = result.unwrap();
expect(track).toMatchObject<ProgressTrackSchema>({
difficulty: ChallengeRanks.Dangerous,
rank: ChallengeRanks.Dangerous,
progress: 10,
complete: false,
unbounded: false,
});
});

it("interprets the difficulty case insensitively", () => {
it("interprets the rank case insensitively", () => {
const result = ProgressTrack.create({
...TEST_DATA,
difficulty: "DANGERous",
rank: "DANGERous",
});
expect(result.isRight()).toBeTruthy();
const track = result.unwrap();
expect(track).toMatchObject<ProgressTrackSchema>({
difficulty: ChallengeRanks.Dangerous,
rank: ChallengeRanks.Dangerous,
progress: 10,
complete: false,
unbounded: false,
Expand All @@ -61,7 +61,7 @@ describe("ProgressTrack", () => {
{ rank: ChallengeRanks.Extreme, steps: 1 },
{ rank: ChallengeRanks.Extreme, steps: 2 },
])("advances $rank track by $steps steps", ({ rank, steps }) => {
const start = make({ difficulty: rank });
const start = make({ rank: rank });
const startingProgress = start.progress;
expect(start.advanced(steps).progress).toBe(
startingProgress + CHALLENGE_STEPS[rank] * steps,
Expand Down Expand Up @@ -106,26 +106,24 @@ describe("ProgressTrack", () => {
])(
"#stepsRemaining calculates $steps steps from $ticks ticks for $rank",
({ ticks, steps, rank }) => {
expect(make({ progress: ticks, difficulty: rank }).stepsRemaining).toBe(
steps,
);
expect(make({ progress: ticks, rank: rank }).stepsRemaining).toBe(steps);
},
);
});

describe("ProgressTrackFileAdapter", () => {
const TEST_DATA = {
Name: "Test",
Difficulty: "Dangerous",
rank: "Dangerous",
Progress: 10,
tags: "incomplete",
TrackImage: "[[progress-track-10.svg]]",
tracktype: "Vow",
};

function make(
overrides: Omit<Partial<ProgressTrackerInputSchema>, "Difficulty"> & {
Difficulty?: ChallengeRanks | string;
overrides: Omit<Partial<ProgressTrackerInputSchema>, "rank"> & {
rank?: ChallengeRanks | string;
} = {},
): Either<ZodError, ProgressTrackFileAdapter> {
return ProgressTrackFileAdapter.create(
Expand All @@ -141,8 +139,8 @@ describe("ProgressTrackFileAdapter", () => {
}

function make_(
overrides: Omit<Partial<ProgressTrackerInputSchema>, "Difficulty"> & {
Difficulty?: ChallengeRanks | string;
overrides: Omit<Partial<ProgressTrackerInputSchema>, "rank"> & {
Rank?: ChallengeRanks | string;
} = {},
): ProgressTrackFileAdapter {
return make(overrides).unwrap();
Expand All @@ -151,14 +149,21 @@ describe("ProgressTrackFileAdapter", () => {
it("#track extracts the progress track data", () => {
expect(make_().track).toEqual(
ProgressTrack.create_({
difficulty: ChallengeRanks.Dangerous,
rank: ChallengeRanks.Dangerous,
progress: 10,
complete: false,
unbounded: false,
}),
);
});

it("parses a track with Difficulty instead of rank", () => {
expect(
make_({ rank: undefined, Difficulty: ChallengeRanks.Troublesome }).track
.rank,
).toEqual(ChallengeRanks.Troublesome);
});

it("requires a completion tag", () => {
expect(make({ tags: ["missing_completion"] })).toEqual(
Left.create(
Expand Down Expand Up @@ -255,7 +260,7 @@ describe("legacyTrackXpEarned", () => {
expect(
legacyTrackXpEarned(
ProgressTrack.create_({
difficulty: ChallengeRanks.Epic,
rank: ChallengeRanks.Epic,
progress: boxes * 4,
complete: false,
unbounded: true,
Expand Down
35 changes: 25 additions & 10 deletions src/tracks/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export const challengeRankSchema = z.preprocess(
z.nativeEnum(ChallengeRanks),
);

export const progressTrackerSchema = z
/** Schema for progress track files. */
export const baseProgressTrackerSchema = z
.object({
Name: z.string(),
Difficulty: challengeRankSchema,
rank: challengeRankSchema,
Progress: z.number().int().nonnegative().default(0),
tags: z
.union([z.string().transform((arg) => [arg]), z.array(z.string())])
Expand All @@ -61,12 +62,26 @@ export const progressTrackerSchema = z
})
.passthrough();

export const progressTrackerSchema = z.union([
baseProgressTrackerSchema,
baseProgressTrackerSchema
.omit({ rank: true })
.merge(
z.object({
Difficulty: baseProgressTrackerSchema.shape.rank,
}),
)
.passthrough()
.transform(({ Difficulty, ...rest }) => ({ ...rest, rank: Difficulty })),
]);

export type ProgressTrackerInputSchema = z.input<typeof progressTrackerSchema>;
export type ProgressTrackerSchema = z.infer<typeof progressTrackerSchema>;

/** Validation for progress track domain model object. */
export const progressTrackSchema = z
.object({
difficulty: challengeRankSchema,
rank: challengeRankSchema,
progress: z.number().int().nonnegative(),
complete: z.boolean(),
unbounded: z.boolean(),
Expand All @@ -82,7 +97,7 @@ export class ProgressTrack {
/**
* Challenge rank for this track
*/
readonly difficulty: ChallengeRanks;
readonly rank: ChallengeRanks;

/**
* Current progress (in ticks)
Expand Down Expand Up @@ -117,14 +132,14 @@ export class ProgressTrack {
}

private constructor(data: ProgressTrackSchema) {
this.difficulty = data.difficulty;
this.rank = data.rank;
this.progress = data.progress;
this.complete = data.complete;
this.unbounded = data.unbounded;
}

get ticksPerStep(): number {
return CHALLENGE_STEPS[this.difficulty];
return CHALLENGE_STEPS[this.rank];
}

get boxesFilled(): number {
Expand Down Expand Up @@ -173,7 +188,7 @@ export class ProgressTrack {
return (
this.progress === other.progress &&
this.complete === other.complete &&
this.difficulty === other.difficulty &&
this.rank === other.rank &&
this.unbounded === other.unbounded
);
}
Expand Down Expand Up @@ -218,7 +233,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo {
return this.create(
{
Name: name,
Difficulty: track.difficulty,
rank: track.rank,
Progress: track.progress,
tags: track.complete ? ["complete"] : ["incomplete"],
TrackImage: settings.generateTrackImage(track),
Expand All @@ -237,7 +252,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo {
if (result.success) {
const raw = result.data;
return ProgressTrack.create({
difficulty: raw.Difficulty,
rank: raw.rank,
progress: raw.Progress,
complete: raw.tags.includes("complete"),
unbounded: false,
Expand All @@ -259,7 +274,7 @@ export class ProgressTrackFileAdapter implements ProgressTrackInfo {
produce(this.raw, (data) => {
data.Progress = other.progress;
data.TrackImage = this.settings.generateTrackImage(other);
data.Difficulty = other.difficulty;
data.rank = other.rank;
const [tagToRemove, tagToAdd] = other.complete
? ["incomplete", "complete"]
: ["complete", "incomplete"];
Expand Down

0 comments on commit 2554961

Please sign in to comment.