Added LootGenerator
This commit is contained in:
parent
7d62ee42e2
commit
0df8eabe08
59
src/scripts/game/LootGenerator.ts
Normal file
59
src/scripts/game/LootGenerator.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
module SpaceTac.Game {
|
||||
"use strict";
|
||||
|
||||
// Equipment generator from loot templates
|
||||
export class LootGenerator {
|
||||
// List of available templates
|
||||
templates: LootTemplate[];
|
||||
|
||||
// Random generator that will be used
|
||||
random: RandomGenerator;
|
||||
|
||||
// Construct a basic loot generator
|
||||
// The list of templates will be automatically populated
|
||||
constructor() {
|
||||
this.templates = [];
|
||||
this.random = new RandomGenerator();
|
||||
|
||||
this.populate();
|
||||
}
|
||||
|
||||
// Fill the list of templates
|
||||
populate(): void {
|
||||
|
||||
}
|
||||
|
||||
// Generate a random equipment
|
||||
// If slot is specified, it will generate an equipment for this slot type specifically
|
||||
// If level is specified, it will generate an equipment with level requirement inside this range
|
||||
// If no equipment could be generated from available templates, null is returned
|
||||
generate(level: IntegerRange = null, slot: SlotType = null): Equipment {
|
||||
// Generate equipments matching conditions, with each template
|
||||
var equipments: Equipment[] = [];
|
||||
this.templates.forEach((template: LootTemplate) => {
|
||||
if (slot !== null && slot !== template.slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
var equipment: Equipment;
|
||||
if (level === null) {
|
||||
equipment = template.generate(this.random);
|
||||
} else {
|
||||
equipment = template.generateInLevelRange(level, this.random);
|
||||
}
|
||||
|
||||
if (equipment !== null) {
|
||||
equipments.push(equipment);
|
||||
}
|
||||
});
|
||||
|
||||
// No equipment could be generated with given conditions
|
||||
if (equipments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Pick a random equipment
|
||||
return this.random.choice(equipments);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,6 @@
|
|||
module SpaceTac.Game {
|
||||
"use strict";
|
||||
|
||||
// Range of values
|
||||
export class Range {
|
||||
// Minimal value
|
||||
private min: number;
|
||||
|
||||
// Maximal value
|
||||
private max: number;
|
||||
|
||||
// Create a range of values
|
||||
constructor(min: number, max: number) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
// Get a proportional value (give 0.0-1.0 value to obtain a value in range)
|
||||
getProportional(cursor: number) :number {
|
||||
return (this.max - this.min) * cursor + this.min;
|
||||
}
|
||||
}
|
||||
|
||||
// Template used to generate a loot equipment
|
||||
export class LootTemplate {
|
||||
// Type of slot this equipment will fit in
|
||||
|
@ -39,8 +19,8 @@ module SpaceTac.Game {
|
|||
// Effect area's radius
|
||||
blast: Range;
|
||||
|
||||
// Duration
|
||||
duration: Range;
|
||||
// Duration, in number of turns
|
||||
duration: IntegerRange;
|
||||
|
||||
// Effects
|
||||
|
||||
|
@ -48,7 +28,7 @@ module SpaceTac.Game {
|
|||
ap_usage: Range;
|
||||
|
||||
// Level requirement
|
||||
min_level: Range;
|
||||
min_level: IntegerRange;
|
||||
|
||||
// Create a loot template
|
||||
constructor(slot: SlotType, name: string) {
|
||||
|
@ -56,14 +36,14 @@ module SpaceTac.Game {
|
|||
this.name = name;
|
||||
this.distance = new Range(0, 0);
|
||||
this.blast = new Range(0, 0);
|
||||
this.duration = new Range(0, 0);
|
||||
this.duration = new IntegerRange(0, 0);
|
||||
this.ap_usage = new Range(0, 0);
|
||||
this.min_level = new Range(0, 0);
|
||||
this.min_level = new IntegerRange(0, 0);
|
||||
}
|
||||
|
||||
// Generate a random equipment with this template
|
||||
generate(): Equipment {
|
||||
var random = new RandomGenerator();
|
||||
generate(random: RandomGenerator = null): Equipment {
|
||||
random = random || new RandomGenerator();
|
||||
var power = random.throw();
|
||||
return this.generateFixed(power);
|
||||
}
|
||||
|
@ -75,13 +55,50 @@ module SpaceTac.Game {
|
|||
result.slot = this.slot;
|
||||
result.name = this.name;
|
||||
|
||||
result.distance = Math.floor(this.distance.getProportional(power));
|
||||
result.blast = Math.floor(this.blast.getProportional(power));
|
||||
result.duration = Math.floor(this.duration.getProportional(power));
|
||||
result.ap_usage = Math.floor(this.ap_usage.getProportional(power));
|
||||
result.min_level = Math.floor(this.min_level.getProportional(power));
|
||||
result.distance = this.distance.getProportional(power);
|
||||
result.blast = this.blast.getProportional(power);
|
||||
result.duration = this.duration.getProportional(power);
|
||||
result.ap_usage = this.ap_usage.getProportional(power);
|
||||
result.min_level = this.min_level.getProportional(power);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find the power range that will result in the level range
|
||||
getPowerRangeForLevel(level: IntegerRange): Range {
|
||||
if (level.min > this.min_level.max || level.max < this.min_level.min) {
|
||||
return null;
|
||||
} else {
|
||||
var min: number;
|
||||
var max: number;
|
||||
|
||||
if (level.min <= this.min_level.min) {
|
||||
min = 0.0;
|
||||
} else {
|
||||
min = this.min_level.getReverseProportional(level.min);
|
||||
}
|
||||
if (level.max >= this.min_level.max) {
|
||||
max = 1.0;
|
||||
} else {
|
||||
max = this.min_level.getReverseProportional(level.max);
|
||||
}
|
||||
|
||||
return new Range(min, max);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate an equipment that will have its level requirement in the given range
|
||||
// May return null if level range is not compatible with the template
|
||||
generateInLevelRange(level: IntegerRange, random: RandomGenerator = null): Equipment {
|
||||
random = random || new RandomGenerator();
|
||||
|
||||
var random_range = this.getPowerRangeForLevel(level);
|
||||
if (random_range) {
|
||||
var power = random.throw() * (random_range.max - random_range.min) + random_range.min;
|
||||
return this.generateFixed(power);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,18 @@ module SpaceTac.Game {
|
|||
}
|
||||
}
|
||||
|
||||
// Generate a random integer value in a range
|
||||
throwInt(min: number, max: number): number {
|
||||
var value = this.throw(max - min + 1);
|
||||
return Math.floor(value) + max;
|
||||
}
|
||||
|
||||
// Choose a random item from an array
|
||||
choice(items: any[]): any {
|
||||
var index = this.throwInt(0, items.length - 1);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
// Fake the generator, by forcing the next value
|
||||
// Call it several times to set future successive values
|
||||
// This value will replace the 0.0-1.0 random value, not the final one
|
||||
|
|
75
src/scripts/game/Range.ts
Normal file
75
src/scripts/game/Range.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
module SpaceTac.Game {
|
||||
"use strict";
|
||||
|
||||
// Range of number values
|
||||
export class Range {
|
||||
// Minimal value
|
||||
min: number;
|
||||
|
||||
// Maximal value
|
||||
max: number;
|
||||
|
||||
// Create a range of values
|
||||
constructor(min: number, max: number) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
// Get a proportional value (give 0.0-1.0 value to obtain a value in range)
|
||||
getProportional(cursor: number): number {
|
||||
if (cursor <= 0.0) {
|
||||
return this.min;
|
||||
} else if (cursor >= 1.0) {
|
||||
return this.max;
|
||||
} else {
|
||||
return (this.max - this.min) * cursor + this.min;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the value of the cursor that would give this proportional value (in 0.0-1.0 range)
|
||||
getReverseProportional(expected: number): number {
|
||||
if (expected <= this.min) {
|
||||
return 0;
|
||||
} else if (expected >= this.max) {
|
||||
return 1;
|
||||
} else {
|
||||
return (expected - this.min) / (this.max - this.min);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a value is in the range
|
||||
isInRange(value: number): boolean {
|
||||
return value >= this.min && value <= this.max;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Range of integer values
|
||||
//
|
||||
// This differs from Range in that it adds space in proportional values to include the 'max'.
|
||||
// Typically, using Range for integers will only yield 'max' for exactly 1.0 proportional, not for 0.999999.
|
||||
// This fixes this behavior.
|
||||
//
|
||||
// As this rounds values to integer, the 'reverse' proportional is no longer a bijection.
|
||||
export class IntegerRange extends Range {
|
||||
getProportional(cursor: number): number {
|
||||
if (cursor <= 0.0) {
|
||||
return this.min;
|
||||
} else if (cursor >= 1.0) {
|
||||
return this.max;
|
||||
} else {
|
||||
return Math.floor((this.max - this.min + 1) * cursor + this.min);
|
||||
}
|
||||
}
|
||||
|
||||
getReverseProportional(expected: number): number {
|
||||
if (expected <= this.min) {
|
||||
return 0;
|
||||
} else if (expected > this.max) {
|
||||
return 1;
|
||||
} else {
|
||||
return (expected - this.min) * 1.0 / (this.max - this.min + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,6 +43,9 @@ module SpaceTac.Game {
|
|||
// Number of action points used to make a 1.0 move
|
||||
movement_cost: number;
|
||||
|
||||
// List of slots, able to contain equipment
|
||||
slots: Slot[];
|
||||
|
||||
// Create a new ship inside a fleet
|
||||
constructor(fleet: Fleet, name: string) {
|
||||
this.fleet = fleet;
|
||||
|
@ -52,6 +55,7 @@ module SpaceTac.Game {
|
|||
this.ap_maximal = 20;
|
||||
this.ap_recover = 5;
|
||||
this.movement_cost = 0.1;
|
||||
this.slots = [];
|
||||
|
||||
if (fleet) {
|
||||
fleet.addShip(this);
|
||||
|
|
29
src/scripts/game/specs/LootGenerator.spec.ts
Normal file
29
src/scripts/game/specs/LootGenerator.spec.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/// <reference path="../../definitions/jasmine.d.ts"/>
|
||||
|
||||
module SpaceTac.Game.Specs {
|
||||
"use strict";
|
||||
|
||||
class TestTemplate extends LootTemplate {
|
||||
constructor() {
|
||||
super(SlotType.Shield, "Hexagrid Shield");
|
||||
|
||||
this.min_level = new IntegerRange(2, 100);
|
||||
this.ap_usage = new Range(6, 8);
|
||||
}
|
||||
}
|
||||
|
||||
describe("LootGenerator", () => {
|
||||
it("generates items within a given level range", () => {
|
||||
var generator = new LootGenerator();
|
||||
generator.templates = [new TestTemplate()];
|
||||
generator.random.forceNextValue(0.5);
|
||||
|
||||
var equipment = generator.generate(new IntegerRange(3, 6));
|
||||
|
||||
expect(equipment.slot).toBe(SlotType.Shield);
|
||||
expect(equipment.name).toEqual("Hexagrid Shield");
|
||||
expect(equipment.min_level).toBe(5);
|
||||
expect(equipment.ap_usage).toEqual(7);
|
||||
});
|
||||
});
|
||||
}
|
84
src/scripts/game/specs/LootTemplate.spec.ts
Normal file
84
src/scripts/game/specs/LootTemplate.spec.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/// <reference path="../../definitions/jasmine.d.ts"/>
|
||||
|
||||
module SpaceTac.Game.Specs {
|
||||
"use strict";
|
||||
|
||||
describe("LootTemplate", () => {
|
||||
it("interpolates between weak and strong loot", () => {
|
||||
var template = new LootTemplate(SlotType.Weapon, "Bulletator");
|
||||
|
||||
template.distance = new Range(1, 3);
|
||||
template.blast = new Range(1, 1);
|
||||
template.duration = new IntegerRange(1, 2);
|
||||
template.ap_usage = new Range(4, 12);
|
||||
template.min_level = new IntegerRange(5, 9);
|
||||
|
||||
var equipment = template.generateFixed(0.0);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(1);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(1);
|
||||
expect(equipment.ap_usage).toEqual(4);
|
||||
expect(equipment.min_level).toEqual(5);
|
||||
|
||||
equipment = template.generateFixed(1.0);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(3);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(2);
|
||||
expect(equipment.ap_usage).toEqual(12);
|
||||
expect(equipment.min_level).toEqual(9);
|
||||
|
||||
equipment = template.generateFixed(0.5);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(2);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(2);
|
||||
expect(equipment.ap_usage).toEqual(8);
|
||||
expect(equipment.min_level).toEqual(7);
|
||||
});
|
||||
|
||||
it("restricts power range to stay in a level range", () => {
|
||||
var template = new LootTemplate(SlotType.Weapon, "Bulletator");
|
||||
template.min_level = new IntegerRange(4, 7);
|
||||
|
||||
var result: Range;
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(4, 7));
|
||||
expect(result.min).toBe(0);
|
||||
expect(result.max).toBe(1);
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(1, 10));
|
||||
expect(result.min).toBe(0);
|
||||
expect(result.max).toBe(1);
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(5, 6));
|
||||
expect(result.min).toBeCloseTo(0.25, 0.000001);
|
||||
expect(result.max).toBeCloseTo(0.75, 0.000001);
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(5, 12));
|
||||
expect(result.min).toBeCloseTo(0.25, 0.000001);
|
||||
expect(result.max).toBe(1);
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(3, 6));
|
||||
expect(result.min).toBe(0);
|
||||
expect(result.max).toBeCloseTo(0.75, 0.000001);
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(10, 15));
|
||||
expect(result).toBeNull();
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(1, 3));
|
||||
expect(result).toBeNull();
|
||||
|
||||
result = template.getPowerRangeForLevel(new IntegerRange(5, 5));
|
||||
expect(result.min).toBeCloseTo(0.25, 0.000001);
|
||||
expect(result.max).toBeCloseTo(0.5, 0.000001);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/// <reference path="../../definitions/jasmine.d.ts"/>
|
||||
|
||||
module SpaceTac.Game.Specs {
|
||||
"use strict";
|
||||
|
||||
describe("LootTemplate", () => {
|
||||
it("interpolates between weak and strong loot", () => {
|
||||
var template = new LootTemplate(SlotType.Weapon, "Bulletator");
|
||||
|
||||
template.distance = new Range(1, 3);
|
||||
template.blast = new Range(1, 1);
|
||||
template.duration = new Range(1, 2);
|
||||
template.ap_usage = new Range(4, 12);
|
||||
template.min_level = new Range(5, 9);
|
||||
|
||||
var equipment = template.generateFixed(0.0);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(1);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(1);
|
||||
expect(equipment.ap_usage).toEqual(4);
|
||||
expect(equipment.min_level).toEqual(5);
|
||||
|
||||
var equipment = template.generateFixed(1.0);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(3);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(2);
|
||||
expect(equipment.ap_usage).toEqual(12);
|
||||
expect(equipment.min_level).toEqual(9);
|
||||
|
||||
var equipment = template.generateFixed(0.5);
|
||||
|
||||
expect(equipment.slot).toEqual(SlotType.Weapon);
|
||||
expect(equipment.name).toEqual("Bulletator");
|
||||
expect(equipment.distance).toEqual(2);
|
||||
expect(equipment.blast).toEqual(1);
|
||||
expect(equipment.duration).toEqual(1);
|
||||
expect(equipment.ap_usage).toEqual(8);
|
||||
expect(equipment.min_level).toEqual(7);
|
||||
});
|
||||
});
|
||||
}
|
49
src/scripts/game/specs/Range.spec.ts
Normal file
49
src/scripts/game/specs/Range.spec.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/// <reference path="../../definitions/jasmine.d.ts"/>
|
||||
|
||||
module SpaceTac.Game.Specs {
|
||||
"use strict";
|
||||
|
||||
function checkProportional(range: Range, value1: number, value2: number) {
|
||||
expect(range.getProportional(value1)).toEqual(value2);
|
||||
expect(range.getReverseProportional(value2)).toEqual(value1);
|
||||
}
|
||||
|
||||
describe("Range", () => {
|
||||
it("can work with proportional values", () => {
|
||||
var range = new Range(1, 5);
|
||||
|
||||
checkProportional(range, 0, 1);
|
||||
checkProportional(range, 1, 5);
|
||||
checkProportional(range, 0.5, 3);
|
||||
checkProportional(range, 0.4, 2.6);
|
||||
|
||||
expect(range.getProportional(-0.25)).toEqual(1);
|
||||
expect(range.getProportional(1.8)).toEqual(5);
|
||||
|
||||
expect(range.getReverseProportional(0)).toEqual(0);
|
||||
expect(range.getReverseProportional(6)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("IntegerRange", () => {
|
||||
it("can work with proportional values", () => {
|
||||
var range = new IntegerRange(1, 5);
|
||||
|
||||
expect(range.getProportional(0)).toEqual(1);
|
||||
expect(range.getProportional(0.1)).toEqual(1);
|
||||
expect(range.getProportional(0.2)).toEqual(2);
|
||||
expect(range.getProportional(0.45)).toEqual(3);
|
||||
expect(range.getProportional(0.5)).toEqual(3);
|
||||
expect(range.getProportional(0.75)).toEqual(4);
|
||||
expect(range.getProportional(0.8)).toEqual(5);
|
||||
expect(range.getProportional(0.99)).toEqual(5);
|
||||
expect(range.getProportional(1)).toEqual(5);
|
||||
|
||||
expect(range.getReverseProportional(1)).toEqual(0);
|
||||
expect(range.getReverseProportional(2)).toEqual(0.2);
|
||||
expect(range.getReverseProportional(3)).toEqual(0.4);
|
||||
expect(range.getReverseProportional(4)).toEqual(0.6);
|
||||
expect(range.getReverseProportional(5)).toEqual(0.8);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue