Skip to content

Instantly share code, notes, and snippets.

@stuf
Last active January 25, 2017 10:21
Show Gist options
  • Save stuf/5999385f41d86b520274d17a00844e4d to your computer and use it in GitHub Desktop.
Save stuf/5999385f41d86b520274d17a00844e4d to your computer and use it in GitHub Desktop.
On the topic of retrieving a described state of resources in the KCS API

Handling resource state from the KCS API

Due to the KCS API being a clusterfuck of all kinds of madness, we can't use a single, simple lens that will normalize the data straight up. Good thing is: this state is always specified as an array of either numbers, or objects.

Luckily, the partial.lenses library offers the L.choose function, that can be used to create optics based on the underlying view.

The result we want is something like this:

const resource = { id: 1, type: 'fuel', value: 420 };

If the state is a simple list of numbers, we can rewrite this list (in all cases but one, more or less) to use any index i of the list to represent a resource r that's i + 1 (see table below for resource types).

However, if the state is a list of objects, we will map the can just rewrite the objects to conform to the desired object, by rewriting the object and map the resource type to the id specified in the object.

To decide on which transformation to use, we are just going to assume the list consists always of items of a single type, and check the type of the list's head.

To be on the safe side and assume normal operation, in case the head of the list is of a type we haven't specified a transformation function to, we'll simply return R.identity as the default fallthrough.

Resource types

ID Type
1 Fuel
2 Ammo
3 Steel
4 Bauxite
5 Instant repair (buckets)
6 Construction materials
7 Instant construction material (flamethrowers)
8 Modernization material (screws)
// @flow
import { cond, inc, is, prop, always, compose, head, identity, T } from 'ramda';
import * as L from 'partial.lenses';
import { materialTypes as mats } from './_templates';
/**
* Transformation function for a list of numbers describing a resource state
*/
export const numberNormalizer = (n: number, i: number) => ({
id: inc(i),
type: prop(inc(i), mats),
value: n
});
/**
* Transformation function for a list of objects describing a resource state
*/
export const objectNormalizer = ({ api_id, api_value }: { api_id: number, api_value: number }) => ({
id: api_id,
type: prop(api_id, mats),
value: api_value
});
/**
* Simple conditional checker for deciding on which normalizing function to use
*/
export const decideNormalizer = cond([
[is(Number), always(numberNormalizer)],
[is(Object), always(objectNormalizer)],
[T, always(identity)]
]);
const normalizerLens = n => [L.elems, L.normalize(n)];
/**
* Compose a function that will take the `head` of a list of elements of any type,
* that will return a normalizing function appropriate for that function.
*
* If the the type is something that we don't know how to handle, it will return
* its identity instead.
*/
export const chooseNormalizer = compose(
normalizerLens,
decideNormalizer,
head
);
// @flow
import * as R from 'ramda';
import { chooseNormalizer } from './_normalizers';
/**
* Describe a dictionary of the different material/resource types that exist.
*/
export const materialTypes = R.fromPairs([
R.pair(0, '__zero'),
R.pair(1, 'fuel'),
R.pair(2, 'ammo'),
R.pair(3, 'steel'),
R.pair(4, 'bauxite'),
R.pair(5, 'buckets'),
R.pair(6, 'constructionMaterials'),
R.pair(7, 'flamethrowers'),
R.pair(8, 'modernization')
]);
/**
* Conditional material lens template. "Conditional" meaning that when focused on an element
* that is a list of values that describe some state of player resources or resources used.
*
* Because the API is such a wonderful tangle of horrors, the resource state can either be:
* - a flat list of numbers, with every resource value at index `n` meaning a resource of ID `n + 1`
* - a list of objects representing the resource type and value
*
* `chooseNormalizer` will take the head of this list and depending on the type of value `head` is,
* will return a lens of `L.normalize` with the appropriate transformation function for the elements
* for the list.
*/
export const materials = L.choose(chooseNormalizer);
/**
* Composed lens for specifying a root for this operation.
*/
export const materialsIn = (root: *) => [root, materials];
import * as L from 'partial.lenses';
import { chooseNormalizer } from './_normalizers';
import { materialsIn } from './_templates';
const data = {
api_material: [
{ api_member_id: 123456, api_id: 1, api_value: 54838 },
{ api_member_id: 123456, api_id: 2, api_value: 80565 },
{ api_member_id: 123456, api_id: 3, api_value: 300000 },
{ api_member_id: 123456, api_id: 4, api_value: 30753 },
{ api_member_id: 123456, api_id: 5, api_value: 1686 },
{ api_member_id: 123456, api_id: 6, api_value: 774 },
{ api_member_id: 123456, api_id: 7, api_value: 2759 },
{ api_member_id: 123456, api_id: 8, api_value: 104 }
]
};
const resultFromBasic = L.collect([
'api_material',
L.choose(chooseNormalizer)
], data);
// Or, we can use the paramteric lens template defined in `_templates.js`
const resultFromBasic2 = L.collect(materialsIn('api_material'), data);
[ { id: 1, type: 'fuel', value: 54838 },
{ id: 2, type: 'ammo', value: 80565 },
{ id: 3, type: 'steel', value: 300000 },
{ id: 4, type: 'bauxite', value: 30753 },
{ id: 5, type: 'buckets', value: 1686 },
{ id: 6, type: 'constructionMaterials', value: 774 },
{ id: 7, type: 'flamethrowers', value: 2759 },
{ id: 8, type: 'modernization', value: 104 } ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment