-
Notifications
You must be signed in to change notification settings - Fork 4
/
filter.js
317 lines (291 loc) · 9.89 KB
/
filter.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
const fs = require('fs');
const net = require('net');
const SOCK_ADDR = process.env.R38_SOCK || './r38.sock';
const STOP_SEQ = new Uint8Array([13, 10, 13, 10]); //'\r\n\r\n'
const server = net.createServer((c) => {
var allData;
console.error('client connected');
c.on('end', () => {
console.error('client disconnected');
});
c.on('data', (data) => {
if (!allData) {
allData = data;
} else {
allData = Buffer.concat([allData, data], allData.length + data.length);
}
var matches = true;
for (var i = data.length - 4; i < data.length; i++) {
var x = i - data.length + 4;
if (data.readUInt8(i) !== STOP_SEQ[i - data.length + 4]) {
matches = false;
break;
}
}
if (matches) {
doParse(c, allData.toString());
}
});
});
server.on('error', (err) => {
console.error('got error when starting server');
if (err.code === 'EADDRINUSE') {
console.error('address in use');
var clientSocket = new net.Socket();
clientSocket.on('error', (err) => {
if (err.code === 'ECONNREFUSED') {
console.error('removing old socket');
fs.unlinkSync(SOCK_ADDR);
server.listen(SOCK_ADDR);
} else if (err.code === 'EACCES') {
console.error('cannot remove old socket, EACCES. probably ran this as the wrong user?');
process.exit();
} else {
throw err;
}
});
clientSocket.connect({path: SOCK_ADDR}, () => {
console.error('server already running, exiting...');
process.exit();
});
} else {
throw err;
}
});
server.listen(SOCK_ADDR, () => {
console.error('server bound');
});
function stopServer() {
console.error('closing...');
if (server) {
server.unref();
server.close();
}
}
process.on('exit', stopServer);
process.on('SIGINT', stopServer);
process.on('SIGTERM', stopServer);
function doParse(client, objstr) {
var obj = JSON.parse(objstr);
var state = JSON.parse(objstr).draft.seats;
var myPosition = obj.draft.seats.findIndex((elem) => elem.playerId === obj.user);
var newEvents = [];
var librarian;
obj.draft.playerId = obj.user;
// create a map of which pack every card lives in.
// this isn't strictly necessary as we can limit our card searches to the pack
// that the player has available, but it's good to have to verify all events
// are valid.
var cardToPackAndIndex = {};
for (var i = 0; i < state.length; i++) {
state[i].round = 1;
var packs = state[i].packs;
for (var j = 0; j < packs.length; j++) {
var pack = packs[j];
pack.startSeat = i;
for (var k = 0; k < pack.length; k++) {
cardToPackAndIndex[pack[k].id] = { pack: pack, index: k };
if (pack[k].scryfall.name === 'Cogwork Librarian') {
if (librarian) {
throw Error('Cannot have multiple Cogwork Librarians in a draft without rewriting this logic.');
}
librarian = pack[k];
}
}
packs[j] = [packs[j]];
}
}
// a map that indicates if a pack has been seen by the player or not
var packSeen = [
[false, false, false],
[false, false, false],
[false, false, false],
[false, false, false],
[false, false, false],
[false, false, false],
[false, false, false],
[false, false, false],
];
// the player is always allowed to see their pack 1
if (myPosition >= 0) {
packSeen[myPosition][0] = true;
}
// a map from a pack's starting seat + round to what cards have been picked
// since the watched player last saw that pack
var shadowCards = {};
// a map from a pack's starting seat + round to the timestamp of the event
// that last added a card to this list. this is important because we want
// shadow pick events to have stable draftModified values.
var shadowModified = {};
for (var i = 0; i < obj.draft.events.length; i++) {
var event = obj.draft.events[i];
var pi = cardToPackAndIndex[event.cards[0]];
var shadowKey = pi.pack.startSeat + '|' + event.round;
if (event.position === myPosition) {
// the player we're watching made a pick. if previous cards
// have been picked from this pack, record those picks first
if (shadowCards[shadowKey]) {
newEvents.push({
announcements: [],
cards: shadowCards[shadowKey],
draftModified: shadowModified[shadowKey] + 0.5,
librarian: false,
position: -1,
round: event.round,
type: 'ShadowPick',
});
delete shadowCards[shadowKey];
delete shadowModified[shadowKey];
}
newEvents.push(event);
} else {
// another player made a pick. just note that a card was picked and when
// so the pack can be properly passed around by the UI.
newEvents.push({
announcements: [],
draftModified: event.draftModified,
librarian: event.librarian,
playerModified: event.playerModified,
position: event.position,
round: event.round,
type: 'SecretPick'
});
// add the card that was picked to the list of picked cards from that pack.
shadowCards[shadowKey] = event.cards.concat(shadowCards[shadowKey] || []);
shadowCards[shadowKey].sort(); // sort by card id so pick order can't be deduced.
shadowModified[shadowKey] = event.draftModified;
}
// now do the event. the purpose of the rest of this for loop body is to mark packs as seen by the player.
if (event.librarian) {
if (!librarian) {
throw Error('tried to place librarian but could not find it');
}
// replace the first card picked with cogwork librarian and remove the second card picked
var pi2 = cardToPackAndIndex[event.cards[1]];
pi.pack[pi.index] = librarian
pi2.pack[pi2.index] = null;
} else {
// remove the picked card
pi.pack[pi.index] = null;
}
// figure out where the pack is going
var nextPos = event.position;
if (event.round % 2 === 1) {
nextPos++;
if (nextPos === 8) {
nextPos = 0;
}
} else {
nextPos--;
if (nextPos === -1) {
nextPos = 7;
}
}
// sanity check the round
var stateRound = state[event.position].round;
if (stateRound !== event.round) {
throw Error('problem with rounds');
}
// sanity check the pack being picked from agrees with our current state
if (state[event.position].packs[event.round - 1][0] !== pi.pack) {
throw Error('problem with pack location');
}
// do the actual passing
var passedPack = state[event.position].packs[event.round - 1].shift();
state[nextPos].packs[event.round - 1].push(passedPack);
var startSeat = pi.pack.startSeat;
if (event.position === myPosition) {
// if the player we're watching just picked a card, they have seen the pack
packSeen[startSeat][event.round - 1] = true;
} else if (!packSeen[startSeat][event.round - 1]) {
// if another player has picked a card from this pack, and the player
// we're watching has never seen this pack, mark the card as forever hidden
var oldPack = obj.draft.seats[startSeat].packs[event.round - 1];
var oldCard = oldPack[pi.index];
oldPack[pi.index] = {
id: oldCard.id,
hidden: true,
scryfall: {
name: 'Forever Unknown Card',
}
};
}
// if the whole pack is empty, increment the round for that player
if (passedPack.every((x) => !x)) {
state[event.position].round++;
// because the whole pack is empty, we need to add the cards that have
// been picked from that pack to a shadow pick event only if the focused
// player is not the one that took the last card.
if (myPosition >= 0 && event.position !== myPosition && shadowCards[shadowKey]) {
newEvents.push({
announcements: [],
cards: shadowCards[shadowKey],
draftModified: shadowModified[shadowKey] + 0.5,
librarian: false,
position: -1,
round: event.round,
type: 'ShadowPick',
});
delete shadowCards[shadowKey];
delete shadowModified[shadowKey];
}
}
}
// now that we've translated all the events and hidden all the forever unknown cards,
// we need to see if there is a pack the watched player is able to pick from.
// if there is, mark that pack as seen and add that pack's most recent shadow pick
// to the event list
if (myPosition >= 0) {
var myRound = state[myPosition].round;
var availablePack = state[myPosition].packs[myRound - 1][0];
if (availablePack) {
var ss = availablePack.startSeat;
packSeen[ss][myRound - 1] = true;
var shadowKey = ss + '|' + myRound;
if (shadowCards[shadowKey]) {
newEvents.push({
announcements: [],
cards: shadowCards[shadowKey],
draftModified: shadowModified[shadowKey] + 0.5,
librarian: false,
position: -1,
round: event.round,
type: 'ShadowPick',
});
delete shadowCards[shadowKey];
delete shadowModified[shadowKey];
}
}
}
// mark all cards not yet seen as unknown cards
for (var i = 0; i < packSeen.length; i++) {
for (var j = 0; j < packSeen[i].length; j++) {
if (!packSeen[i][j]) {
obj.draft.seats[i].packs[j] = obj.draft.seats[i].packs[j].map((card) => {
if (card.hidden) {
return card;
}
return {
id: card.id,
hidden: true,
scryfall: {
name: 'Currently Unknown Card',
}
};
});
}
}
}
newEvents.sort((a, b) => {
if (a.draftModified < b.draftModified) {
return -1;
} else if (a.draftModified > b.draftModified) {
return 1;
}
throw Error('duplicate draftModified values');
});
obj.draft.events = newEvents;
// console.log(JSON.stringify(obj.draft));
client.end(JSON.stringify(obj.draft));
client.unref();
}