mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			864 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			864 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/// <reference path="../DefinitelyTyped/react/react-global.d.ts" />
 | 
						|
 | 
						|
// Fixture taken from https://github.com/RyanCavanaugh/koany/blob/master/koany.tsx
 | 
						|
 | 
						|
interface Garden {
 | 
						|
	colors: Gardens.RockColor[];
 | 
						|
	shapes: Gardens.RockShape[];
 | 
						|
}
 | 
						|
 | 
						|
namespace Gardens {
 | 
						|
	export enum RockShape {
 | 
						|
		Empty,
 | 
						|
		Circle,
 | 
						|
		Triangle,
 | 
						|
		Square,
 | 
						|
		Max
 | 
						|
	}
 | 
						|
	export const RockShapes = [RockShape.Circle, RockShape.Triangle, RockShape.Square];
 | 
						|
	export const RockShapesAndEmpty = RockShapes.concat(RockShape.Empty);
 | 
						|
 | 
						|
	export enum RockColor {
 | 
						|
		Empty,
 | 
						|
		White,
 | 
						|
		Red,
 | 
						|
		Black,
 | 
						|
		Max
 | 
						|
	}
 | 
						|
	export const RockColors = [RockColor.White, RockColor.Red, RockColor.Black];
 | 
						|
	export const RockColorsAndEmpty = RockColors.concat(RockColor.Empty);
 | 
						|
 | 
						|
	export const Size = 9;
 | 
						|
 | 
						|
	// 012
 | 
						|
	// 345
 | 
						|
	// 678
 | 
						|
	export const adjacencies = [
 | 
						|
		[1, 3], [0, 4, 2], [1, 5],
 | 
						|
		[0, 4, 6], [3, 1, 7, 5], [2, 4, 8],
 | 
						|
		[3, 7], [6, 4, 8], [7, 5]
 | 
						|
	];
 | 
						|
}
 | 
						|
 | 
						|
module Koan {
 | 
						|
	export enum DescribeContext {
 | 
						|
		// every "white stone" is ...
 | 
						|
		Singular,
 | 
						|
		// all "white stones" are
 | 
						|
		Plural,
 | 
						|
		// every stone in the top row is "white"
 | 
						|
		Adjectival
 | 
						|
	}
 | 
						|
 | 
						|
	export enum PartType {
 | 
						|
		Selector,
 | 
						|
		Aspect
 | 
						|
	}
 | 
						|
 | 
						|
	export enum StateTestResult {
 | 
						|
		Fail = 0,
 | 
						|
		WeakPass = 1,
 | 
						|
		Pass = 2
 | 
						|
	}
 | 
						|
 | 
						|
	/// A general format for producing a Statement
 | 
						|
	export interface StatementTemplate<T> {
 | 
						|
		holes: PartType[];
 | 
						|
		describe(args: T): string;
 | 
						|
		test(g: Garden, args: T): StateTestResult;
 | 
						|
	}
 | 
						|
 | 
						|
	/// A completed rule that can be used to test a Garden
 | 
						|
	export interface ProducedStatement<T> {
 | 
						|
		test(g: Garden): StateTestResult;
 | 
						|
		description: string;
 | 
						|
		children: T;
 | 
						|
 | 
						|
		hasPassedAndFailed(): boolean;
 | 
						|
	}
 | 
						|
 | 
						|
	function rnd(max: number) {
 | 
						|
		return Math.floor(Math.random() * max);
 | 
						|
	}
 | 
						|
 | 
						|
	function randomColor(): Gardens.RockColor {
 | 
						|
		return Math.floor(Math.random() * (Gardens.RockColor.Max - 1)) + 1
 | 
						|
	}
 | 
						|
 | 
						|
	function randomShape(): Gardens.RockShape {
 | 
						|
		return Math.floor(Math.random() * (Gardens.RockShape.Max - 1)) + 1
 | 
						|
	}
 | 
						|
 | 
						|
	/* New Impl Here */
 | 
						|
	interface SelectorSpec<T> {
 | 
						|
		childTypes?: PartType[];
 | 
						|
		precedence: number;
 | 
						|
		weight: number;
 | 
						|
		test(args: T, g: Garden, index: number): string|number|boolean;
 | 
						|
		describe(args: T, context: DescribeContext): string;
 | 
						|
		isAllValues(values: Array<string>|Array<number>): boolean;
 | 
						|
	}
 | 
						|
 | 
						|
	interface ProducedSelector {
 | 
						|
		test(g: Garden, index: number): string|number|boolean;
 | 
						|
		getDescription(plural: DescribeContext): string;
 | 
						|
		seenAllValues(): boolean;
 | 
						|
	}
 | 
						|
 | 
						|
	export function buildSelector<T>(spec: SelectorSpec<T>, args: T): ProducedSelector {
 | 
						|
		let seenResults: { [s: string]: boolean;} = {};
 | 
						|
		return {
 | 
						|
			test: (g: Garden, index: number) => {
 | 
						|
				var result = spec.test(args, g, index);
 | 
						|
				seenResults[result + ''] = true;
 | 
						|
				return result;
 | 
						|
			},
 | 
						|
			getDescription: (context) => {
 | 
						|
				return spec.describe(args, context);
 | 
						|
			},
 | 
						|
			seenAllValues: () => {
 | 
						|
				return spec.isAllValues(Object.keys(seenResults));
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	export var SelectorTemplates: Array<SelectorSpec<{}>> = [];
 | 
						|
	module LetsMakeSomeSelectors {
 | 
						|
		// Is rock
 | 
						|
		SelectorTemplates.push({
 | 
						|
			test: (args, g, i) => g.colors[i] !== Gardens.RockColor.Empty,
 | 
						|
			describe: (args, context) => {
 | 
						|
				switch(context) {
 | 
						|
					case DescribeContext.Plural:
 | 
						|
						return 'Stones';
 | 
						|
					case DescribeContext.Adjectival:
 | 
						|
						return 'not empty';
 | 
						|
					case DescribeContext.Singular:
 | 
						|
						return 'Stone';
 | 
						|
				}
 | 
						|
			},
 | 
						|
			isAllValues: items => items.length === 2,
 | 
						|
			precedence: 0,
 | 
						|
			weight: 1
 | 
						|
		});
 | 
						|
 | 
						|
		// Is of a certain color and/or shape
 | 
						|
		Gardens.RockColorsAndEmpty.forEach(color => {
 | 
						|
			let colorName = Gardens.RockColor[color];
 | 
						|
			let colorWeight = color === Gardens.RockColor.Empty ? 1 : 0.33;
 | 
						|
			Gardens.RockShapesAndEmpty.forEach(shape => {
 | 
						|
				let shapeName = Gardens.RockShape[shape];
 | 
						|
				let shapeWeight = shape === Gardens.RockShape.Empty ? 1 : 0.33;
 | 
						|
				SelectorTemplates.push({
 | 
						|
					test: (args, g, i) => {
 | 
						|
						if(color === Gardens.RockColor.Empty) {
 | 
						|
							if (shape === Gardens.RockShape.Empty) {
 | 
						|
								return g.colors[i] === Gardens.RockColor.Empty;
 | 
						|
							} else {
 | 
						|
								return g.shapes[i] === shape;
 | 
						|
							}
 | 
						|
						} else {
 | 
						|
							if (shape === Gardens.RockShape.Empty) {
 | 
						|
								return g.colors[i] === color;
 | 
						|
							} else {
 | 
						|
								return g.shapes[i] === shape && g.colors[i] === color;
 | 
						|
							}
 | 
						|
						}
 | 
						|
					},
 | 
						|
					describe: (args, context) => {
 | 
						|
						if(color === Gardens.RockColor.Empty) {
 | 
						|
							if (shape === Gardens.RockShape.Empty) {
 | 
						|
								switch(context) {
 | 
						|
									case DescribeContext.Plural:
 | 
						|
										return 'Empty Cells';
 | 
						|
									case DescribeContext.Adjectival:
 | 
						|
										return 'Empty';
 | 
						|
									case DescribeContext.Singular:
 | 
						|
										return 'Empty Cell';
 | 
						|
								}
 | 
						|
							} else {
 | 
						|
								switch(context) {
 | 
						|
									case DescribeContext.Plural:
 | 
						|
										return shapeName + 's';
 | 
						|
									case DescribeContext.Adjectival:
 | 
						|
										return 'a ' + shapeName;
 | 
						|
									case DescribeContext.Singular:
 | 
						|
										return shapeName;
 | 
						|
								}
 | 
						|
							}
 | 
						|
						} else {
 | 
						|
							if (shape === Gardens.RockShape.Empty) {
 | 
						|
								switch(context) {
 | 
						|
									case DescribeContext.Plural:
 | 
						|
										return colorName + ' Stones';
 | 
						|
									case DescribeContext.Adjectival:
 | 
						|
										return colorName;
 | 
						|
									case DescribeContext.Singular:
 | 
						|
										return colorName + ' Stone';
 | 
						|
								}
 | 
						|
							} else {
 | 
						|
								switch(context) {
 | 
						|
									case DescribeContext.Plural:
 | 
						|
										return colorName + ' ' + shapeName + 's';
 | 
						|
									case DescribeContext.Adjectival:
 | 
						|
										return 'a ' + colorName + ' ' + shapeName;
 | 
						|
									case DescribeContext.Singular:
 | 
						|
										return colorName + ' ' + shapeName;
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
					},
 | 
						|
					isAllValues: items => items.length === 2,
 | 
						|
					precedence: 3,
 | 
						|
					weight: (shapeWeight + colorWeight === 2) ? 0.3 : shapeWeight * colorWeight
 | 
						|
				});
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		// [?] in the [top|middle|bottom] [row|column]
 | 
						|
		[0, 1, 2].forEach(rowCol => {
 | 
						|
			[true, false].forEach(isRow => {
 | 
						|
				var name = (isRow ? ['top', 'middle', 'bottom'] : ['left', 'middle', 'right'])[rowCol] + ' ' + (isRow ? 'row' : 'column');
 | 
						|
				var spec: SelectorSpec<[ProducedSelector]> = {
 | 
						|
					childTypes: [PartType.Selector],
 | 
						|
					test: (args, g, i) => {
 | 
						|
						var c = isRow ? Math.floor(i / 3) : i % 3;
 | 
						|
						if (c === rowCol) {
 | 
						|
							return args[0].test(g, i);
 | 
						|
						} else {
 | 
						|
							return false;
 | 
						|
						}
 | 
						|
					},
 | 
						|
					describe: (args, plural) => args[0].getDescription(plural) + ' in the ' + name,
 | 
						|
					isAllValues: items => items.length === 2,
 | 
						|
					precedence: 4,
 | 
						|
					weight: 1 / 6
 | 
						|
				};
 | 
						|
				SelectorTemplates.push(spec);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		// [?] next to a [?]
 | 
						|
		SelectorTemplates.push({
 | 
						|
			childTypes: [PartType.Selector, PartType.Selector],
 | 
						|
			test: (args, g, i) => {
 | 
						|
				if (args[0].test(g, i)) {
 | 
						|
					return Gardens.adjacencies[i].some(x => !!args[1].test(g, x));
 | 
						|
				} else {
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
			},
 | 
						|
			describe: (args, plural) => {
 | 
						|
				return args[0].getDescription(plural) + ' next to a ' + args[1].getDescription(DescribeContext.Singular);
 | 
						|
			},
 | 
						|
			isAllValues: items => items.length === 2,
 | 
						|
			precedence: 4,
 | 
						|
			weight: 1
 | 
						|
		} as SelectorSpec<[ProducedSelector, ProducedSelector]>);
 | 
						|
	}
 | 
						|
 | 
						|
	export function buildStatement<T>(s: StatementTemplate<T>, args: T): ProducedStatement<T> {
 | 
						|
		let hasPassed = false;
 | 
						|
		let hasFailed = false;
 | 
						|
 | 
						|
		let result: ProducedStatement<T> = {
 | 
						|
			children: args,
 | 
						|
			description: s.describe(args),
 | 
						|
			test: (g) => {
 | 
						|
				let r = s.test(g, args);
 | 
						|
				if (r === StateTestResult.Pass) {
 | 
						|
					hasPassed = true;
 | 
						|
				} else if(r === StateTestResult.Fail) {
 | 
						|
					hasFailed = true;
 | 
						|
				}
 | 
						|
				return r;
 | 
						|
			},
 | 
						|
			hasPassedAndFailed: () => {
 | 
						|
				return hasPassed && hasFailed && (args as any as ProducedSelector[]).every(c => c.seenAllValues());
 | 
						|
			}
 | 
						|
		};
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	export let StatementList: StatementTemplate<any>[] = [];
 | 
						|
	module LetsMakeSomeStatements {
 | 
						|
		// Every [?] is a [?]
 | 
						|
		StatementList.push({
 | 
						|
			holes: [PartType.Selector, PartType.Selector],
 | 
						|
			test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
 | 
						|
				let didAnyTests = false;
 | 
						|
				for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
					if (args[0].test(g, i)) {
 | 
						|
						if(!args[1].test(g, i)) return StateTestResult.Fail;
 | 
						|
						didAnyTests = true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return didAnyTests ? StateTestResult.Pass : StateTestResult.WeakPass;
 | 
						|
			},
 | 
						|
			describe: args => {
 | 
						|
				return 'Every ' + args[0].getDescription(DescribeContext.Singular) + ' is ' + args[1].getDescription(DescribeContext.Adjectival);
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
		// There is exactly 1 [?]
 | 
						|
		StatementList.push({
 | 
						|
			holes: [PartType.Selector],
 | 
						|
			test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
 | 
						|
				var count = 0;
 | 
						|
				for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
					if (args[0].test(g, i)) count++;
 | 
						|
				}
 | 
						|
 | 
						|
				return count === 1 ? StateTestResult.Pass : StateTestResult.Fail;
 | 
						|
			},
 | 
						|
			describe: args => {
 | 
						|
				return 'There is exactly one ' + args[0].description;
 | 
						|
			}
 | 
						|
		});
 | 
						|
 | 
						|
		// There are more [?] than [?]
 | 
						|
		StatementList.push({
 | 
						|
			holes: [PartType.Selector, PartType.Selector],
 | 
						|
			test: (g: Garden, args: [ProducedSelector, ProducedSelector]) => {
 | 
						|
				var p1c = 0, p2c = 0;
 | 
						|
				for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
					if (args[0].test(g, i)) p1c++;
 | 
						|
					if (args[1].test(g, i)) p2c++;
 | 
						|
				}
 | 
						|
				if(p1c > p2c && p2c > 0) {
 | 
						|
					return StateTestResult.Pass;
 | 
						|
				} else if(p1c > p2c) {
 | 
						|
					return StateTestResult.WeakPass;
 | 
						|
				} else {
 | 
						|
					return StateTestResult.Fail;
 | 
						|
				}
 | 
						|
			},
 | 
						|
			describe: args => {
 | 
						|
				return 'There are more ' + args[0].descriptionPlural + ' than ' + args[1].descriptionPlural;
 | 
						|
			}
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	function randomElementOf<T>(arr: T[]): T {
 | 
						|
		if (arr.length === 0) {
 | 
						|
			return undefined;
 | 
						|
		} else {
 | 
						|
			return arr[Math.floor(Math.random() * arr.length)];
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	function randomWeightedElementOf<T extends { weight: number }>(arr: T[]): T {
 | 
						|
		var totalWeight = arr.reduce((acc, v) => acc + v.weight, 0);
 | 
						|
		var rnd = Math.random() * totalWeight;
 | 
						|
		for (var i = 0; i < arr.length; i++) {
 | 
						|
			rnd -= arr[i].weight;
 | 
						|
			if (rnd <= 0) return arr[i];
 | 
						|
		}
 | 
						|
		// Got destroyed by floating error, just try again
 | 
						|
		return randomWeightedElementOf(arr);
 | 
						|
	}
 | 
						|
 | 
						|
	export function buildRandomNewSelector(maxPrecedence = 1000000): ProducedSelector {
 | 
						|
		var choices = SelectorTemplates;
 | 
						|
 | 
						|
		let initial = randomWeightedElementOf(choices.filter(p => p.precedence <= maxPrecedence));
 | 
						|
		// Fill in the holes
 | 
						|
		if (initial.childTypes) {
 | 
						|
			var fills = initial.childTypes.map(h => {
 | 
						|
				if (h === PartType.Selector) {
 | 
						|
					return buildRandomNewSelector(initial.precedence - 1);
 | 
						|
				} else {
 | 
						|
					throw new Error('Only know how to fill Selector holes')
 | 
						|
				}
 | 
						|
			});
 | 
						|
			return buildSelector(initial, fills);
 | 
						|
		} else {
 | 
						|
			return buildSelector(initial, []);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	export function makeEmptyGarden(): Garden {
 | 
						|
		var g = {} as Garden;
 | 
						|
		g.colors = [];
 | 
						|
		g.shapes = [];
 | 
						|
		for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
			g.colors.push(Gardens.RockColor.Empty);
 | 
						|
			g.shapes.push(Gardens.RockShape.Empty);
 | 
						|
		}
 | 
						|
 | 
						|
		return g;
 | 
						|
	}
 | 
						|
 | 
						|
	export function gardenToString(g: Garden): string {
 | 
						|
		return g.colors.join('') + g.shapes.join('');
 | 
						|
	}
 | 
						|
 | 
						|
	export function makeRandomGarden(): Garden {
 | 
						|
		var g = makeEmptyGarden();
 | 
						|
		blitRandomGardenPair(g, g);
 | 
						|
		return g;
 | 
						|
	}
 | 
						|
 | 
						|
	export function cloneGarden(g: Garden): Garden {
 | 
						|
		var result: Garden = {
 | 
						|
			colors: g.colors.slice(0),
 | 
						|
			shapes: g.shapes.slice(0)
 | 
						|
		};
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	export function clearGarden(g: Garden) {
 | 
						|
		for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
			g.colors[i] = Gardens.RockColor.Empty;
 | 
						|
			g.shapes[i] = Gardens.RockShape.Empty;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	export function blitRandomGardenPair(g1: Garden, g2: Garden): void {
 | 
						|
		let placeCount = 0;
 | 
						|
		for (var i = 0; i < Gardens.Size; i++) {
 | 
						|
			if (rnd(7) === 0) {
 | 
						|
				g1.colors[i] = g2.colors[i] = randomColor();
 | 
						|
				g1.shapes[i] = g2.shapes[i] = randomShape();
 | 
						|
			} else {
 | 
						|
				placeCount++;
 | 
						|
				g1.colors[i] = g2.colors[i] = Gardens.RockColor.Empty;
 | 
						|
				g1.shapes[i] = g2.shapes[i] = Gardens.RockShape.Empty;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (placeCount === 0) blitRandomGardenPair(g1, g2);
 | 
						|
	}
 | 
						|
 | 
						|
	export function blitNumberedGarden(g: Garden, stoneCount: number, n: number): void {
 | 
						|
		clearGarden(g);
 | 
						|
 | 
						|
		let cellNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8];
 | 
						|
		for (let i = 0; i < stoneCount; i++) {
 | 
						|
			let cellNum = getValue(cellNumbers.length);
 | 
						|
			let cell = cellNumbers.splice(cellNum, 1)[0];
 | 
						|
			g.colors[cell] = getValue(3) + 1;
 | 
						|
			g.shapes[cell] = getValue(3) + 1;
 | 
						|
		}
 | 
						|
 | 
						|
		function getValue(max: number) {
 | 
						|
			let result = n % max;
 | 
						|
			n = (n - result) / max;
 | 
						|
			return result;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	export function mutateGarden(g: Garden): void {
 | 
						|
		while (true) {
 | 
						|
			var op = rnd(5);
 | 
						|
			let x = rnd(Gardens.Size);
 | 
						|
			let y = rnd(Gardens.Size);
 | 
						|
			switch (op) {
 | 
						|
				case 0: // Swap two non-identical cells
 | 
						|
					if (g.colors[x] !== g.colors[y] || g.shapes[x] !== g.shapes[y]) {
 | 
						|
						var tmp: any = g.colors[x];
 | 
						|
						g.colors[x] = g.colors[y];
 | 
						|
						g.colors[y] = tmp;
 | 
						|
						tmp = g.shapes[x];
 | 
						|
						g.shapes[x] = g.shapes[y];
 | 
						|
						g.shapes[y] = tmp;
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				case 1: // Add a stone
 | 
						|
					if (g.colors[x] === Gardens.RockColor.Empty) {
 | 
						|
						g.colors[x] = randomColor();
 | 
						|
						g.shapes[x] = randomShape();
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				case 2: // Remove a stone
 | 
						|
					if (g.colors.filter(x => x !== Gardens.RockColor.Empty).length === 1) continue;
 | 
						|
 | 
						|
					if (g.colors[x] !== Gardens.RockColor.Empty) {
 | 
						|
						g.colors[x] = Gardens.RockColor.Empty;
 | 
						|
						g.shapes[x] = Gardens.RockShape.Empty;
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				case 3: // Change a color
 | 
						|
					let c = randomColor();
 | 
						|
					if (g.colors[x] !== Gardens.RockColor.Empty && g.colors[x] !== c) {
 | 
						|
						g.colors[x] = c;
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
				case 4: // Change a shape
 | 
						|
					let s = randomShape();
 | 
						|
					if (g.shapes[x] !== Gardens.RockShape.Empty && g.shapes[x] !== s) {
 | 
						|
						g.shapes[x] = s;
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
class Indexion {
 | 
						|
	sizes: number[];
 | 
						|
	constructor(...sizes: number[]) {
 | 
						|
		this.sizes = sizes;
 | 
						|
	}
 | 
						|
 | 
						|
	public getValues(index: number): number[] {
 | 
						|
		let result = new Array<number>(this.sizes.length);
 | 
						|
		this.fillValues(index, result);
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	public fillValues(index: number, result: number[]): void {
 | 
						|
		for (var i = 0; i < this.sizes.length; i++) {
 | 
						|
			result[i] = index % this.sizes[i];
 | 
						|
			index -= result[i];
 | 
						|
			index /= this.sizes[i];
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	public valuesToIndex(values: number[]): number {
 | 
						|
		var result = 0;
 | 
						|
		var factor = 1;
 | 
						|
		for (var i = 0; i < this.sizes.length; i++) {
 | 
						|
			result += values[i] * this.sizes[i] * factor;
 | 
						|
			factor *= this.sizes[i];
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	public getAdjacentIndices(index: number): number[][] {
 | 
						|
		var baseline = this.getValues(index);
 | 
						|
		var results: number[][] = [];
 | 
						|
		for (var i = 0; i < this.sizes.length; i++) {
 | 
						|
			if(baseline[i] > 0) {
 | 
						|
				baseline[i]--;
 | 
						|
				results.push(baseline.slice());
 | 
						|
				baseline[i]++;
 | 
						|
			}
 | 
						|
			if(baseline[i] < this.sizes[i] - 1) {
 | 
						|
				baseline[i]++;
 | 
						|
				results.push(baseline.slice());
 | 
						|
				baseline[i]--;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return results;
 | 
						|
	}
 | 
						|
 | 
						|
	public distance(index1: number, index2: number): number {
 | 
						|
		let delta = 0;
 | 
						|
		for (var i = 0; i < this.sizes.length; i++) {
 | 
						|
			var a = index1 % this.sizes[i];
 | 
						|
			var b = index2 % this.sizes[i];
 | 
						|
			delta += Math.abs(b - a);
 | 
						|
			index1 -= a;
 | 
						|
			index2 -= b;
 | 
						|
			index1 /= this.sizes[i];
 | 
						|
			index2 /= this.sizes[i];
 | 
						|
		}
 | 
						|
		return delta;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
function makeNewExample() {
 | 
						|
	while (true) {
 | 
						|
		var p1 = Koan.buildSelector(Koan.SelectorTemplates[12], []);
 | 
						|
		var p2 = Koan.buildSelector(Koan.SelectorTemplates[14], []);
 | 
						|
		var test = Koan.buildStatement(Koan.StatementList[0], [p1, p2]);
 | 
						|
 | 
						|
		var examples: Garden[] = [];
 | 
						|
 | 
						|
		console.log('Attempt to generate examples for "' + test.description + '"');
 | 
						|
 | 
						|
		var maxGarden = /*(9 * 9) + (9 * 9 * 9 * 8) + */(9 * 9 * 9 * 8 * 9 * 7);
 | 
						|
		let g = Koan.makeEmptyGarden();
 | 
						|
		let passCount = 0, failCount = 0;
 | 
						|
		let resultLookup: boolean[] = [];
 | 
						|
		let lastResult: boolean = undefined;
 | 
						|
		for (var i = 0; i < maxGarden; i++) {
 | 
						|
			Koan.blitNumberedGarden(g, 3, i);
 | 
						|
			let result = test.test(g);
 | 
						|
			if(result === Koan.StateTestResult.Pass) {
 | 
						|
				resultLookup[i] = true;
 | 
						|
				passCount++;
 | 
						|
 | 
						|
				if (lastResult !== true && examples.length < 10) examples.push(Koan.cloneGarden(g));
 | 
						|
				lastResult = true;
 | 
						|
			} else if (result === Koan.StateTestResult.Fail) {
 | 
						|
				resultLookup[i] = false;
 | 
						|
				failCount++;
 | 
						|
 | 
						|
				if (lastResult !== false && examples.length < 10) examples.push(Koan.cloneGarden(g));
 | 
						|
				lastResult = false;
 | 
						|
			}
 | 
						|
 | 
						|
			if (examples.length === 10) break;
 | 
						|
		}
 | 
						|
 | 
						|
		console.log('Rule passes ' + passCount + ' and fails ' + failCount);
 | 
						|
 | 
						|
		/*
 | 
						|
		if (!test.hasPassedAndFailed()) {
 | 
						|
			console.log('Rule has unreachable, contradictory, or tautological clauses');
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (passCount === 0 || failCount === 0) {
 | 
						|
			console.log('Rule is always true or always false');
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		*/
 | 
						|
 | 
						|
		var h = document.createElement('h2');
 | 
						|
		h.innerText = test.description;
 | 
						|
		document.body.appendChild(h);
 | 
						|
 | 
						|
		return { test: test, examples: examples };
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
let list: Garden[] = [];
 | 
						|
let test: Koan.ProducedStatement<any>;
 | 
						|
window.onload = function() {
 | 
						|
	let rule = makeNewExample();
 | 
						|
	let garden = Koan.makeRandomGarden();
 | 
						|
	list = rule.examples;
 | 
						|
	test = rule.test;
 | 
						|
 | 
						|
	function renderList() {
 | 
						|
		function makeGarden(g: Garden, i: number) {
 | 
						|
			return <GardenDisplay
 | 
						|
				garden={g}
 | 
						|
				key={i + Koan.gardenToString(g)}
 | 
						|
				test={test}
 | 
						|
				leftButton='✗'
 | 
						|
				rightButton='✎'
 | 
						|
				onLeftButtonClicked={() => {
 | 
						|
					console.log(list.indexOf(g));
 | 
						|
					list.splice(list.indexOf(g), 1);
 | 
						|
					renderList();
 | 
						|
				}}
 | 
						|
				onRightButtonClicked={() => {
 | 
						|
					garden = g;
 | 
						|
					renderEditor();
 | 
						|
				}}
 | 
						|
			/>;
 | 
						|
		}
 | 
						|
		let gardenList = <div>{list.map(makeGarden)}</div>;
 | 
						|
		React.render(gardenList, document.getElementById('results'));
 | 
						|
	}
 | 
						|
 | 
						|
	let i = 0;
 | 
						|
	function renderEditor() {
 | 
						|
		i++;
 | 
						|
		let editor = <GardenEditor key={i} test={rule.test} garden={garden} onSaveClicked={(garden) => {
 | 
						|
			list.push(garden);
 | 
						|
			renderList();
 | 
						|
		}} />;
 | 
						|
		React.render(editor, document.getElementById('editor'));
 | 
						|
	}
 | 
						|
 | 
						|
	renderList();
 | 
						|
	renderEditor();
 | 
						|
}
 | 
						|
 | 
						|
function classNames(nameMap: any): string {
 | 
						|
	return Object.keys(nameMap).filter(k => nameMap[k]).join(' ');
 | 
						|
}
 | 
						|
 | 
						|
interface GardenCellProps extends React.Props<{}> {
 | 
						|
	color: Gardens.RockColor;
 | 
						|
	shape: Gardens.RockShape;
 | 
						|
	index: number;
 | 
						|
 | 
						|
	movable?: boolean;
 | 
						|
	onEdit?(newColor: Gardens.RockColor, newShape: Gardens.RockShape): void;
 | 
						|
}
 | 
						|
interface GardenCellState {
 | 
						|
	isDragging?: boolean;
 | 
						|
}
 | 
						|
class GardenCell extends React.Component<GardenCellProps, GardenCellState> {
 | 
						|
	state: GardenCellState = {};
 | 
						|
	ignoreNextEdit = false;
 | 
						|
 | 
						|
	render() {
 | 
						|
		var classes = ['cell', 'index_' + this.props.index];
 | 
						|
 | 
						|
		if (this.state.isDragging) {
 | 
						|
			// Render as blank
 | 
						|
		} else {
 | 
						|
			classes.push(Gardens.RockColor[this.props.color], Gardens.RockShape[this.props.shape]);
 | 
						|
		}
 | 
						|
 | 
						|
		if (this.props.movable) classes.push('movable');
 | 
						|
		let events: React.HTMLAttributes = {
 | 
						|
			onDragStart: (e) => {
 | 
						|
				this.ignoreNextEdit = false;
 | 
						|
				e.dataTransfer.dropEffect = 'copyMove';
 | 
						|
				e.dataTransfer.effectAllowed = 'move';
 | 
						|
				e.dataTransfer.setData('shape', this.props.shape.toString());
 | 
						|
				e.dataTransfer.setData('color', this.props.color.toString());
 | 
						|
 | 
						|
				let drag = document.getElementById(getGardenName(this.props.color, this.props.shape));
 | 
						|
				let xfer: any = (e.nativeEvent as DragEvent).dataTransfer;
 | 
						|
				xfer.setDragImage(drag, drag.clientWidth * 0.5, drag.clientHeight * 0.5);
 | 
						|
 | 
						|
				this.setState({ isDragging: true });
 | 
						|
			},
 | 
						|
			onDragEnter: (e) => {
 | 
						|
				e.dataTransfer.dropEffect = 'move';
 | 
						|
				e.preventDefault();
 | 
						|
			},
 | 
						|
			onDragOver: (e) => {
 | 
						|
				e.dataTransfer.dropEffect = 'move';
 | 
						|
				e.preventDefault();
 | 
						|
			},
 | 
						|
			onDragEnd: (e) => {
 | 
						|
				this.setState({ isDragging: false });
 | 
						|
				if (!this.ignoreNextEdit) {
 | 
						|
					this.props.onEdit && this.props.onEdit(undefined, undefined);
 | 
						|
				}
 | 
						|
			},
 | 
						|
			draggable: true
 | 
						|
		}
 | 
						|
 | 
						|
		let handleDrop = (event: React.DragEvent) => {
 | 
						|
			if(this.props.onEdit) {
 | 
						|
				if (this.state.isDragging) {
 | 
						|
					// Dragged to self, don't do anything
 | 
						|
					this.ignoreNextEdit = true;
 | 
						|
				} else {
 | 
						|
					let shape: Gardens.RockShape = +event.dataTransfer.getData('shape');
 | 
						|
					let color: Gardens.RockColor = +event.dataTransfer.getData('color');
 | 
						|
					this.props.onEdit(color, shape);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return <span className={classes.join(' ')} onDrop={handleDrop} {...this.props.movable ? events : {}} />;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
interface GardenDisplayProps extends React.Props<GardenDisplay> {
 | 
						|
	garden?: Garden;
 | 
						|
	test?: Koan.ProducedStatement<any>;
 | 
						|
 | 
						|
	leftButton?: string;
 | 
						|
	rightButton?: string;
 | 
						|
	onLeftButtonClicked?(): void;
 | 
						|
	onRightButtonClicked?(): void;
 | 
						|
 | 
						|
	editable?: boolean;
 | 
						|
	onChanged?(newGarden: Garden): void;
 | 
						|
}
 | 
						|
interface GardenDisplayState {
 | 
						|
	garden?: Garden;
 | 
						|
}
 | 
						|
class GardenDisplay extends React.Component<GardenDisplayProps, GardenDisplayState> {
 | 
						|
	state = {
 | 
						|
		garden: Koan.cloneGarden(this.props.garden)
 | 
						|
	};
 | 
						|
 | 
						|
	leftClicked = () => {
 | 
						|
		this.props.onLeftButtonClicked && this.props.onLeftButtonClicked();
 | 
						|
	};
 | 
						|
 | 
						|
	rightClicked = () => {
 | 
						|
		this.props.onRightButtonClicked && this.props.onRightButtonClicked();
 | 
						|
	};
 | 
						|
 | 
						|
	render() {
 | 
						|
		let g = this.state.garden;
 | 
						|
		let pass = (this.props.test && this.props.test.test(this.state.garden));
 | 
						|
 | 
						|
		let classes = {
 | 
						|
			garden: true,
 | 
						|
			unknown: pass === undefined,
 | 
						|
			pass: pass === Koan.StateTestResult.Pass || pass === Koan.StateTestResult.WeakPass,
 | 
						|
			fail: pass === Koan.StateTestResult.Fail,
 | 
						|
			editable: this.props.editable
 | 
						|
		};
 | 
						|
 | 
						|
		var children = g.colors.map((_, i) => (
 | 
						|
			<GardenCell
 | 
						|
				key={i}
 | 
						|
				color={g.colors[i]}
 | 
						|
				shape={g.shapes[i]}
 | 
						|
				index={i}
 | 
						|
				movable={this.props.editable}
 | 
						|
				onEdit={(newColor, newShape) => {
 | 
						|
					if(this.props.editable) {
 | 
						|
						let newGarden = Koan.cloneGarden(this.state.garden);
 | 
						|
						newGarden.colors[i] = newColor;
 | 
						|
						newGarden.shapes[i] = newShape;
 | 
						|
						this.setState({ garden: newGarden });
 | 
						|
						this.props.onChanged && this.props.onChanged(newGarden);
 | 
						|
					}
 | 
						|
				}}
 | 
						|
			/>));
 | 
						|
 | 
						|
		return <div className="gardenDisplay">
 | 
						|
			<div className={classNames(classes)}>{children}</div>
 | 
						|
			<span className="infoRow">
 | 
						|
				{this.props.leftButton && <div className="button left" onClick={this.leftClicked}>{this.props.leftButton}</div>}
 | 
						|
				<div className={"passfail " + (pass ? 'pass' : 'fail')}>{pass ? '✓' : '🚫'}</div>
 | 
						|
				{this.props.rightButton && <div className="button right" onClick={this.rightClicked}>{this.props.rightButton}</div>}
 | 
						|
			</span>
 | 
						|
		</div>;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
interface GardenEditorProps extends React.Props<GardenEditor> {
 | 
						|
	onSaveClicked?(garden: Garden): void;
 | 
						|
	test?: Koan.ProducedStatement<any>;
 | 
						|
	garden?: Garden;
 | 
						|
}
 | 
						|
interface GardenEditorState {
 | 
						|
	garden?: Garden;
 | 
						|
	pass?: boolean;
 | 
						|
}
 | 
						|
class GardenEditor extends React.Component<GardenEditorProps, {}> {
 | 
						|
	state = { garden: this.props.garden };
 | 
						|
 | 
						|
	save = () => {
 | 
						|
		this.props.onSaveClicked && this.props.onSaveClicked(this.state.garden);
 | 
						|
	};
 | 
						|
 | 
						|
	render() {
 | 
						|
		return <div className="editor">
 | 
						|
			<GardenDisplay garden={this.state.garden} test={this.props.test} editable onChanged={g => this.setState({ garden: g }) } />
 | 
						|
			<StonePalette />
 | 
						|
			<div className="button save" onClick={this.save}>{'💾'}</div>
 | 
						|
		</div>;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
class StonePalette extends React.Component<{}, {}> {
 | 
						|
	render() {
 | 
						|
		let items: JSX.Element[] = [];
 | 
						|
		Gardens.RockColors.forEach(color => {
 | 
						|
			Gardens.RockShapes.forEach(shape => {
 | 
						|
				let name = getGardenName(color, shape);
 | 
						|
				let extraProps = { id: name, key: name };
 | 
						|
				let index = items.length;
 | 
						|
				items.push(<GardenCell
 | 
						|
					color={color}
 | 
						|
					shape={shape}
 | 
						|
					index={index}
 | 
						|
					movable
 | 
						|
					{...extraProps} />)
 | 
						|
			});
 | 
						|
		});
 | 
						|
		return <div className="palette">{items}</div>;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function getGardenName(color: Gardens.RockColor, shape: Gardens.RockShape) {
 | 
						|
	return 'draggable.' + Gardens.RockShape[shape] + '.' + Gardens.RockColor[color];
 | 
						|
}
 | 
						|
 | 
						|
 |