-
Notifications
You must be signed in to change notification settings - Fork 2
/
itempile.js
174 lines (147 loc) · 4.9 KB
/
itempile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
'use strict';
const deepEqual = require('deep-equal');
const cloneObject = require('clone');
class ItemPile {
constructor(item, count, tags) {
this.item = (typeof(item) === 'string' ? ItemPile.itemFromString(item) : item);
this.count = count !== undefined ? count : 1;
this.tags = tags !== undefined ? tags : {};
}
clone() {
return new ItemPile(this.item, this.count, cloneObject(this.tags, false));
}
// maximum size items should pile
static get maxPileSize() {
return 64;
}
// convert item<->string; change these to use non-string items
static itemFromString(s) {
if (s instanceof ItemPile) return s;
return (!s ? '' : s);
}
static itemToString(item) {
return ''+item;
}
hasTags() {
return Object.keys(this.tags).length !== 0; // not "{}"
}
matchesType(itemPile) {
return this.item === itemPile.item;
}
matchesTypeAndCount(itemPile) {
return this.item === itemPile.item && this.count === itemPile.count;
}
matchesTypeAndTags(itemPile) {
return this.item === itemPile.item && deepEqual(this.tags, itemPile.tags, {strict:true});
}
matchesAll(itemPile) {
return this.matchesTypeAndCount(itemPile) && deepEqual(this.tags, itemPile.tags, {strict:true});
}
// can this pile be merged with another?
canPileWith(itemPile) {
if (itemPile.item !== this.item) return false;
if (itemPile.count === 0 || this.count === 0) return true; // (special case: can always merge with 0-size pile of same item, regardless of tags - for placeholder slots)
if (itemPile.hasTags() || this.hasTags()) return false; // any tag data makes unpileable
return true;
}
// combine two piles if possible, altering both this and argument pile
// returns count of items that didn't fit
mergePile(itemPile) {
if (!this.canPileWith(itemPile)) return false;
itemPile.count = this.increase(itemPile.count);
return itemPile.count;
}
// increase count by argument, returning number of items that didn't fit
increase(n) {
const a = this.tryAdding(n);
const newCount = a[0];
const excessCount = a[1];
this.count = newCount;
return excessCount;
}
// decrease count by argument, returning number of items removed
decrease(n) {
const a = this.trySubtracting(n);
const removedCount = a[0];
const remainingCount= a[1];
this.count = remainingCount;
return removedCount;
}
// try combining count of items up to max pile size, returns [newCount, excessCount]
tryAdding(n) {
// special case: infinite incoming count sets pile to infinite, even though >maxPileSize
// TODO: option to disable infinite piles? might want to add only up to 64 etc. (ref GH-2)
if (n === Infinity) return [Infinity, 0];
const sum = this.count + n;
if (sum > ItemPile.maxPileSize && this.count !== Infinity) { // (special case: infinite destination piles never overflow)
return [ItemPile.maxPileSize, sum - ItemPile.maxPileSize]; // overflowing pile
} else {
return [sum, 0]; // added everything they wanted
}
}
// try removing a finite count of items, returns [removedCount, remainingCount]
trySubtracting(n) {
const difference = this.count - n;
if (difference < 0) {
return [this.count, n - this.count]; // didn't have enough
} else {
return [n, this.count - n]; // had enough, some remain
}
}
// remove count of argument items, returning new pile of those items which were split off
splitPile(n) {
if (n === 0) return false;
if (n < 0) {
// negative count = all but n
n = this.count + n;
} else if (n < 1) {
// fraction = fraction
n = Math.ceil(this.count * n);
}
if (n > this.count) return false;
if (n !== Infinity) this.count -= n; // (subtract, but avoid Infinity - Infinity = NaN)
return new ItemPile(this.item, n, cloneObject(this.tags, false));
}
toString() {
if (this.hasTags()) {
return `${this.count}:${this.item} ${JSON.stringify(this.tags)}`;
} else {
return `${this.count}:${this.item}`;
}
}
static fromString(s) {
const a = s.match(/^([^:]+):([^ ]+) ?(.*)/); // assumptions: positive integral count, item name no spaces
if (!a) return undefined;
const countStr = a[1];
const itemStr = a[2];
const tagsStr = a[3];
let count;
if (countStr === 'Infinity') {
count = Infinity;
} else {
count = parseInt(countStr, 10);
}
const item = ItemPile.itemFromString(itemStr);
let tags;
if (tagsStr && tagsStr.length) {
tags = JSON.parse(tagsStr);
} else {
tags = {};
}
return new ItemPile(item, count, tags);
}
static fromArray(a) {
const item = a[0];
const count = a[1];
const tags = a[2];
return new ItemPile(item, count, tags);
}
static fromArrayIfArray(a) {
if (Array.isArray(a)) {
return ItemPile.fromArray(a);
} else {
return a;
}
}
}
module.exports = ItemPile;