1
0
Fork 0

Added LootGenerator

This commit is contained in:
Michaël Lemaire 2015-01-14 01:00:00 +01:00 committed by Michaël Lemaire
parent 7d62ee42e2
commit 0df8eabe08
9 changed files with 361 additions and 79 deletions

View 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);
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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
View 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);
}
}
}
}

View file

@ -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);

View 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);
});
});
}

View 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);
});
});
}

View file

@ -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);
});
});
}

View 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);
});
});
}