@gbl-uzh/platform 0.4.13 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/index.d.ts +7 -1473
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +11 -2012
  5. package/dist/index.js.map +1 -0
  6. package/dist/lib/SSELink.d.ts +9 -0
  7. package/dist/lib/SSELink.d.ts.map +1 -0
  8. package/dist/lib/SSELink.js +24 -0
  9. package/dist/lib/SSELink.js.map +1 -0
  10. package/dist/lib/apollo.d.ts +3 -4
  11. package/dist/lib/apollo.d.ts.map +1 -0
  12. package/dist/lib/apollo.js +50 -108
  13. package/dist/lib/apollo.js.map +1 -0
  14. package/dist/lib/constants.d.ts +8 -0
  15. package/dist/lib/constants.d.ts.map +1 -0
  16. package/dist/lib/logger.d.ts +4 -0
  17. package/dist/lib/logger.d.ts.map +1 -0
  18. package/dist/lib/logger.js +11 -0
  19. package/dist/lib/logger.js.map +1 -0
  20. package/dist/lib/pubsub.d.ts +5 -0
  21. package/dist/lib/pubsub.d.ts.map +1 -0
  22. package/dist/lib/pubsub.js +6 -0
  23. package/dist/lib/pubsub.js.map +1 -0
  24. package/dist/lib/util.d.ts +12 -13
  25. package/dist/lib/util.d.ts.map +1 -0
  26. package/dist/lib/util.js +63 -105
  27. package/dist/lib/util.js.map +1 -0
  28. package/dist/nexus.d.ts +11 -42
  29. package/dist/nexus.d.ts.map +1 -0
  30. package/dist/nexus.js +22 -2605
  31. package/dist/nexus.js.map +1 -0
  32. package/dist/ops/FGameData.graphql +1 -0
  33. package/dist/ops/FPeriodData.graphql +1 -0
  34. package/dist/ops/FPlayerData.graphql +0 -4
  35. package/dist/ops/FSegmentData.graphql +1 -0
  36. package/dist/ops/FStoryElementData.graphql +7 -0
  37. package/dist/ops/MActivateNextSegment.graphql +0 -3
  38. package/dist/ops/MAddGamePeriod.graphql +2 -2
  39. package/dist/ops/MUpdatePlayerData.graphql +1 -3
  40. package/dist/ops/QGame.graphql +2 -3
  41. package/dist/ops/QPastResults.graphql +0 -3
  42. package/dist/ops/QResult.graphql +4 -5
  43. package/dist/ops/QSpecificResults.graphql +11 -0
  44. package/dist/ops/QStoryElements.graphql +7 -0
  45. package/dist/schema.prisma +2 -3
  46. package/dist/services/AccountService.d.ts +65 -0
  47. package/dist/services/AccountService.d.ts.map +1 -0
  48. package/dist/services/AccountService.js +70 -0
  49. package/dist/services/AccountService.js.map +1 -0
  50. package/dist/services/EventService.d.ts +13 -0
  51. package/dist/services/EventService.d.ts.map +1 -0
  52. package/dist/services/EventService.js +180 -0
  53. package/dist/services/EventService.js.map +1 -0
  54. package/dist/services/GameService.d.ts +670 -0
  55. package/dist/services/GameService.d.ts.map +1 -0
  56. package/dist/services/GameService.js +1191 -0
  57. package/dist/services/GameService.js.map +1 -0
  58. package/dist/services/PlayService.d.ts +630 -0
  59. package/dist/services/PlayService.d.ts.map +1 -0
  60. package/dist/services/PlayService.js +534 -0
  61. package/dist/services/PlayService.js.map +1 -0
  62. package/dist/tsconfig.tsbuildinfo +1 -0
  63. package/dist/types/Achievement.d.ts +4 -0
  64. package/dist/types/Achievement.d.ts.map +1 -0
  65. package/dist/types/Achievement.js +35 -0
  66. package/dist/types/Achievement.js.map +1 -0
  67. package/dist/types/Game.d.ts +5 -0
  68. package/dist/types/Game.d.ts.map +1 -0
  69. package/dist/types/Game.js +85 -0
  70. package/dist/types/Game.js.map +1 -0
  71. package/dist/types/LearningElement.d.ts +5 -0
  72. package/dist/types/LearningElement.d.ts.map +1 -0
  73. package/dist/types/LearningElement.js +55 -0
  74. package/dist/types/LearningElement.js.map +1 -0
  75. package/dist/types/Mutation.d.ts +10 -0
  76. package/dist/types/Mutation.d.ts.map +1 -0
  77. package/dist/types/Mutation.js +194 -0
  78. package/dist/types/Mutation.js.map +1 -0
  79. package/dist/types/Player.d.ts +9 -0
  80. package/dist/types/Player.d.ts.map +1 -0
  81. package/dist/types/Player.js +139 -0
  82. package/dist/types/Player.js.map +1 -0
  83. package/dist/types/Query.d.ts +3 -0
  84. package/dist/types/Query.d.ts.map +1 -0
  85. package/dist/types/Query.js +91 -0
  86. package/dist/types/Query.js.map +1 -0
  87. package/dist/types/StoryElement.d.ts +3 -0
  88. package/dist/types/StoryElement.d.ts.map +1 -0
  89. package/dist/types/StoryElement.js +27 -0
  90. package/dist/types/StoryElement.js.map +1 -0
  91. package/dist/types/Subscription.d.ts +3 -0
  92. package/dist/types/Subscription.d.ts.map +1 -0
  93. package/dist/types/Subscription.js +35 -0
  94. package/dist/types/Subscription.js.map +1 -0
  95. package/dist/types.d.ts +139 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +26 -0
  98. package/dist/types.js.map +1 -0
  99. package/package.json +42 -38
@@ -0,0 +1,1191 @@
1
+ import * as DB from '@prisma/client';
2
+ import { nanoid } from 'nanoid';
3
+ import { repeat, none } from 'ramda';
4
+ import logger from '../lib/logger.js';
5
+ import { receiveEvents } from './EventService.js';
6
+
7
+ async function createGame({ name, playerCount }, ctx, { roleAssigner }) {
8
+ return ctx.prisma.game.create({
9
+ data: {
10
+ name,
11
+ owner: {
12
+ connect: {
13
+ id: ctx.user.sub,
14
+ },
15
+ },
16
+ players: {
17
+ create: repeat(0, playerCount).map((_, ix) => {
18
+ return {
19
+ facts: {},
20
+ token: nanoid(),
21
+ role: roleAssigner ? roleAssigner(ix) : undefined,
22
+ number: playerCount - ix,
23
+ name: `Team ${playerCount - ix}`,
24
+ level: {
25
+ connect: {
26
+ index: 0,
27
+ },
28
+ },
29
+ };
30
+ }),
31
+ },
32
+ },
33
+ include: {
34
+ players: true,
35
+ periods: true,
36
+ },
37
+ });
38
+ }
39
+ async function addGamePeriod({ gameId, facts, segmentCount }, ctx, { schema, services }) {
40
+ const validatedFacts = schema.validateSync(facts);
41
+ const game = await ctx.prisma.game.findUnique({
42
+ where: {
43
+ id: gameId,
44
+ },
45
+ include: {
46
+ periods: {
47
+ orderBy: {
48
+ index: 'desc',
49
+ },
50
+ take: 1,
51
+ include: {
52
+ segments: {
53
+ orderBy: {
54
+ index: 'desc',
55
+ },
56
+ take: 1,
57
+ },
58
+ },
59
+ },
60
+ },
61
+ });
62
+ if (!game)
63
+ return null;
64
+ const index = (game.periods[0]?.index ?? -1) + 1;
65
+ // TODO(JJ): Why do we provide validatedFacts twice?
66
+ // - remove periodFacts from payload for initialize?
67
+ const { resultFacts: initializedFacts } = services.Period.initialize(validatedFacts, {
68
+ // TODO(JJ): replace with undefined
69
+ // At RS: If we replace validatedFacts with periodFacts the
70
+ // Derivative Game will be broken, as it computes the trend from
71
+ // periodFacts instead of the first arguement
72
+ periodFacts: validatedFacts,
73
+ previousPeriodFacts: game.periods[0]?.facts,
74
+ previousSegmentFacts: game.periods[0]?.segments[0]?.facts,
75
+ periodIx: index,
76
+ });
77
+ console.log(game.periods[0]?.facts, game.periods[0]?.segments[0]?.facts, initializedFacts);
78
+ // create or update the facts and settings of a game period
79
+ return ctx.prisma.period.upsert({
80
+ where: {
81
+ gameId_index: {
82
+ gameId,
83
+ index,
84
+ },
85
+ },
86
+ create: {
87
+ index,
88
+ facts: initializedFacts,
89
+ game: {
90
+ connect: {
91
+ id: gameId,
92
+ },
93
+ },
94
+ segmentCount,
95
+ previousPeriod: {
96
+ connect: index > 0
97
+ ? {
98
+ gameId_index: {
99
+ gameId,
100
+ index: index - 1,
101
+ },
102
+ }
103
+ : [],
104
+ },
105
+ },
106
+ update: {
107
+ facts: initializedFacts,
108
+ },
109
+ include: {
110
+ segments: {
111
+ include: {
112
+ learningElements: true,
113
+ storyElements: true,
114
+ },
115
+ },
116
+ },
117
+ });
118
+ }
119
+ async function addPeriodSegment({ gameId, periodIx, facts, learningElements, storyElements, }, ctx, { schema, services }) {
120
+ const validatedFacts = schema.validateSync(facts);
121
+ const period = await ctx.prisma.period.findUnique({
122
+ where: {
123
+ gameId_index: {
124
+ gameId,
125
+ index: periodIx,
126
+ },
127
+ },
128
+ include: {
129
+ segments: {
130
+ orderBy: {
131
+ index: 'desc',
132
+ },
133
+ take: 1,
134
+ },
135
+ },
136
+ });
137
+ if (!period)
138
+ return null;
139
+ const index = (period.segments[0]?.index ?? -1) + 1;
140
+ const { resultFacts: initializedFacts } = services.Segment.initialize(validatedFacts, {
141
+ periodFacts: period.facts,
142
+ previousSegmentFacts: period.segments[0]?.facts,
143
+ segmentIx: index,
144
+ segmentCount: period.segmentCount,
145
+ periodIx,
146
+ });
147
+ // create or update the facts and settings of a period segment
148
+ return ctx.prisma.periodSegment.upsert({
149
+ where: {
150
+ gameId_periodIx_index: {
151
+ gameId,
152
+ periodIx,
153
+ index,
154
+ },
155
+ },
156
+ create: {
157
+ index,
158
+ facts: initializedFacts,
159
+ learningElements: {
160
+ connect: learningElements
161
+ ? learningElements.map((item) => ({ id: item }))
162
+ : [],
163
+ },
164
+ storyElements: {
165
+ connect: storyElements
166
+ ? storyElements.map((item) => ({ id: item }))
167
+ : [],
168
+ },
169
+ game: {
170
+ connect: {
171
+ id: gameId,
172
+ },
173
+ },
174
+ periodIx: periodIx,
175
+ period: {
176
+ connect: {
177
+ gameId_index: {
178
+ gameId,
179
+ index: periodIx,
180
+ },
181
+ },
182
+ },
183
+ previousSegment: {
184
+ connect: index > 0
185
+ ? {
186
+ gameId_periodIx_index: {
187
+ gameId,
188
+ periodIx,
189
+ index: index - 1,
190
+ },
191
+ }
192
+ : [],
193
+ },
194
+ },
195
+ update: {
196
+ facts: initializedFacts,
197
+ learningElements: {
198
+ connect: learningElements
199
+ ? learningElements.map((item) => ({ id: item }))
200
+ : [],
201
+ },
202
+ storyElements: {
203
+ connect: storyElements
204
+ ? storyElements.map((item) => ({ id: item }))
205
+ : [],
206
+ },
207
+ },
208
+ include: {
209
+ learningElements: true,
210
+ storyElements: true,
211
+ },
212
+ });
213
+ }
214
+ async function activateNextPeriod({ gameId }, ctx, { services }) {
215
+ logger.info('activating next period');
216
+ // get the current game and as well as the results of the initially active period
217
+ // these will be used by the model to compute the starting situation of the next period
218
+ const game = await ctx.prisma.game.findUnique({
219
+ where: {
220
+ id: gameId,
221
+ },
222
+ include: {
223
+ players: true,
224
+ periods: true,
225
+ results: {
226
+ where: {
227
+ type: 'SEGMENT_END',
228
+ },
229
+ orderBy: [{ period: { index: 'asc' } }, { segment: { index: 'asc' } }],
230
+ },
231
+ activePeriod: {
232
+ include: {
233
+ results: {
234
+ include: {
235
+ player: true,
236
+ },
237
+ },
238
+ nextPeriod: true,
239
+ previousPeriod: {
240
+ include: {
241
+ results: {
242
+ include: {
243
+ player: true,
244
+ },
245
+ },
246
+ },
247
+ },
248
+ activeSegment: {
249
+ include: {
250
+ results: {
251
+ include: {
252
+ player: true,
253
+ },
254
+ },
255
+ },
256
+ },
257
+ decisions: {
258
+ include: {
259
+ player: true,
260
+ },
261
+ },
262
+ },
263
+ },
264
+ },
265
+ });
266
+ if (!game)
267
+ return null;
268
+ logger.info(`game found, status ${game.status}`);
269
+ const currentPeriodIx = game.activePeriodIx;
270
+ const currentSegmentIx = game.activePeriod?.activeSegmentIx;
271
+ const nextPeriodIx = currentPeriodIx + 1;
272
+ // NotificationService.publishGlobalNotification({
273
+ // type: GlobalNotificationType.PERIOD_ACTIVATED,
274
+ // })
275
+ switch (game.status) {
276
+ // SCHEDULED -> PREPARATION
277
+ // if the game is scheduled, initialize period results and move to PREPARATION
278
+ case DB.GameStatus.SCHEDULED: {
279
+ const { results, extras } = computePeriodStartResults({
280
+ results: undefined,
281
+ players: game.players,
282
+ activePeriodIx: currentPeriodIx,
283
+ gameId: game.id,
284
+ periodFacts: game.periods?.[0]?.facts,
285
+ }, ctx, { services });
286
+ // update the status and active period of the current game
287
+ // and prepare PERIOD_START results
288
+ const result = await ctx.prisma.$transaction([
289
+ ctx.prisma.game.update({
290
+ where: {
291
+ id: gameId,
292
+ },
293
+ include: {
294
+ periods: {
295
+ include: {
296
+ segments: true,
297
+ },
298
+ },
299
+ },
300
+ data: {
301
+ status: DB.GameStatus.PREPARATION,
302
+ activePeriodIx: nextPeriodIx,
303
+ activePeriod: {
304
+ connect: {
305
+ gameId_index: {
306
+ gameId,
307
+ index: nextPeriodIx,
308
+ },
309
+ },
310
+ },
311
+ },
312
+ }),
313
+ ctx.prisma.period.update({
314
+ where: {
315
+ gameId_index: {
316
+ gameId,
317
+ index: nextPeriodIx,
318
+ },
319
+ },
320
+ data: {
321
+ results: {
322
+ create: results,
323
+ },
324
+ },
325
+ }),
326
+ ...extras,
327
+ ]);
328
+ return result;
329
+ }
330
+ // RUNNING -> CONSOLIDATION
331
+ // if the final segment is running, go on with consolidation of the period
332
+ // compute the results of the last segment and update the game status
333
+ case DB.GameStatus.RUNNING: {
334
+ if (!game.activePeriod?.activeSegment || !currentSegmentIx)
335
+ return null;
336
+ const { results, extras } = computeSegmentEndResults(game, ctx, {
337
+ services,
338
+ });
339
+ // update period facts when starting consolidation
340
+ const { resultFacts: consolidatedFacts } = services.Period.consolidate(game.activePeriod.facts, {
341
+ previousSegmentFacts: game.activePeriod.activeSegment.facts,
342
+ periodIx: currentPeriodIx,
343
+ });
344
+ const result = await ctx.prisma.$transaction([
345
+ ctx.prisma.game.update({
346
+ data: {
347
+ status: DB.GameStatus.CONSOLIDATION,
348
+ },
349
+ include: {
350
+ periods: {
351
+ include: {
352
+ segments: true,
353
+ },
354
+ },
355
+ },
356
+ where: {
357
+ id: gameId,
358
+ },
359
+ }),
360
+ ctx.prisma.period.update({
361
+ where: {
362
+ gameId_index: {
363
+ gameId,
364
+ index: currentPeriodIx,
365
+ },
366
+ },
367
+ data: {
368
+ facts: consolidatedFacts,
369
+ },
370
+ }),
371
+ ctx.prisma.periodSegment.update({
372
+ where: {
373
+ gameId_periodIx_index: {
374
+ gameId,
375
+ periodIx: currentPeriodIx,
376
+ index: currentSegmentIx,
377
+ },
378
+ },
379
+ data: {
380
+ results: {
381
+ // compute SEGMENT_END results using model
382
+ update: results,
383
+ },
384
+ },
385
+ include: {
386
+ results: {
387
+ include: {
388
+ player: true,
389
+ },
390
+ },
391
+ },
392
+ }),
393
+ ...extras,
394
+ ]);
395
+ return result;
396
+ }
397
+ // CONSOLIDATION -> RESULTS
398
+ // compute period end results and move to the results phase
399
+ case DB.GameStatus.CONSOLIDATION: {
400
+ if (!game.activePeriod?.activeSegment)
401
+ return null;
402
+ const { results, extras, promises } = await computePeriodEndResults({
403
+ segmentEndResults: game.results,
404
+ players: game.players,
405
+ activeSegmentResults: game.activePeriod.activeSegment.results,
406
+ segmentFacts: game.activePeriod.activeSegment.facts,
407
+ periodFacts: game.activePeriod.facts,
408
+ periodDecisions: game.activePeriod.decisions,
409
+ activePeriodIx: currentPeriodIx,
410
+ activeSegmentIx: currentSegmentIx,
411
+ gameId: game.id,
412
+ }, ctx, { services });
413
+ await Promise.all(promises);
414
+ // TODO(JJ): The error happens here for the last consolidation
415
+ // - there are no more periods
416
+ // - we prob. need to change the nextPeriodIx if it the last period
417
+ // suggestion
418
+ // - maybe setting game.activePeriod to undefined is enough
419
+ // - In the DB.GameStatus.RESULTS case set the new status
420
+ // to completed when we are done
421
+ // - we prob. need to update the game to completed state
422
+ // - maybe we would like to add a button that finishes the game?
423
+ // => it's better not imo, but open for discussion
424
+ // -> Discuss with RS
425
+ // const lastPeriodIx = game.periods.length - 1
426
+ let periodIx = nextPeriodIx;
427
+ // let data: any = {
428
+ // status: DB.GameStatus.RESULTS,
429
+ // }
430
+ // if (nextPeriodIx <= lastPeriodIx) {
431
+ // // periodIx = lastPeriodIx
432
+ // data.activePeriodIx = periodIx
433
+ // data.activePeriod = {
434
+ // connect: {
435
+ // gameId_index: {
436
+ // gameId,
437
+ // index: periodIx,
438
+ // },
439
+ // },
440
+ // }
441
+ // }
442
+ // TODO(JJ): Check with RS
443
+ // - when updating the game with the nextPeriodIx it crashes
444
+ const result = await ctx.prisma.$transaction([
445
+ // update the status and active period of the current game
446
+ ctx.prisma.game.update({
447
+ where: {
448
+ id: gameId,
449
+ },
450
+ include: {
451
+ periods: {
452
+ include: {
453
+ segments: true,
454
+ },
455
+ },
456
+ },
457
+ data: {
458
+ status: DB.GameStatus.RESULTS,
459
+ activePeriodIx: periodIx,
460
+ activePeriod: {
461
+ connect: {
462
+ gameId_index: {
463
+ gameId,
464
+ index: periodIx,
465
+ },
466
+ },
467
+ },
468
+ },
469
+ }),
470
+ // create PERIOD_END results based on the previous SEGMENT_END results
471
+ ctx.prisma.period.update({
472
+ where: {
473
+ gameId_index: {
474
+ gameId,
475
+ index: currentPeriodIx,
476
+ },
477
+ },
478
+ data: {
479
+ results: {
480
+ create: results,
481
+ },
482
+ },
483
+ include: {
484
+ results: true,
485
+ },
486
+ }),
487
+ ...extras,
488
+ ]);
489
+ return result;
490
+ }
491
+ // RESULTS -> PREPARATION
492
+ // if the game is in the results phase (between periods)
493
+ // initialize the next period and move to PREPARATION
494
+ case DB.GameStatus.RESULTS: {
495
+ // if there is no next period, return
496
+ if (!game.activePeriod) {
497
+ logger.warn('no next period available');
498
+ return null;
499
+ }
500
+ // if (game.activePeriodIx >= game.periods.length) {
501
+ // const result = await ctx.prisma.$transaction([
502
+ // ctx.prisma.game.update({
503
+ // where: {
504
+ // id: gameId,
505
+ // },
506
+ // include: {
507
+ // periods: {
508
+ // include: {
509
+ // segments: true,
510
+ // },
511
+ // },
512
+ // },
513
+ // data: {
514
+ // // TODO(JJ): Double-check there is not else to update?
515
+ // status: DB.GameStatus.COMPLETED,
516
+ // },
517
+ // }),
518
+ // ])
519
+ // return result
520
+ // }
521
+ const { results, extras } = computePeriodStartResults({
522
+ results: game.activePeriod.previousPeriod[0]?.results,
523
+ players: game.players,
524
+ activePeriodIx: currentPeriodIx,
525
+ gameId: game.id,
526
+ periodFacts: game.activePeriod.facts,
527
+ }, ctx, { services });
528
+ const result = await ctx.prisma.$transaction([
529
+ // update the status and active period of the current game
530
+ ctx.prisma.game.update({
531
+ where: {
532
+ id: gameId,
533
+ },
534
+ include: {
535
+ periods: {
536
+ include: {
537
+ segments: true,
538
+ },
539
+ },
540
+ },
541
+ data: {
542
+ status: DB.GameStatus.PREPARATION,
543
+ },
544
+ }),
545
+ // create PERIOD_START results based on the previous PERIOD_END results
546
+ ctx.prisma.period.update({
547
+ where: {
548
+ gameId_index: {
549
+ gameId,
550
+ index: currentPeriodIx,
551
+ },
552
+ },
553
+ data: {
554
+ results: {
555
+ create: results,
556
+ },
557
+ },
558
+ }),
559
+ ...extras,
560
+ ]);
561
+ return result;
562
+ }
563
+ default:
564
+ // PREPARATION, PAUSED, COMPLETED, etc.
565
+ return null;
566
+ }
567
+ }
568
+ async function activateNextSegment({ gameId }, ctx, { services }) {
569
+ const game = await ctx.prisma.game.findUnique({
570
+ where: {
571
+ id: gameId,
572
+ },
573
+ include: {
574
+ players: true,
575
+ periods: true,
576
+ segments: true,
577
+ activePeriod: {
578
+ include: {
579
+ results: {
580
+ include: {
581
+ player: true,
582
+ },
583
+ },
584
+ activeSegment: {
585
+ include: {
586
+ nextSegment: true,
587
+ results: {
588
+ include: {
589
+ player: true,
590
+ },
591
+ },
592
+ },
593
+ },
594
+ },
595
+ },
596
+ },
597
+ });
598
+ if (!game?.activePeriod)
599
+ return null;
600
+ const currentPeriodIx = game.activePeriodIx;
601
+ const currentSegmentIx = game.activePeriod.activeSegmentIx;
602
+ const nextSegmentIx = currentSegmentIx + 1;
603
+ // NotificationService.publishGlobalNotification({
604
+ // type: GlobalNotificationType.SEGMENT_ACTIVATED,
605
+ // })
606
+ switch (game.status) {
607
+ // PREPARATION -> RUNNING
608
+ // PAUSED -> RUNNING
609
+ case DB.GameStatus.PREPARATION:
610
+ case DB.GameStatus.PAUSED: {
611
+ const { results, extras } = computeSegmentStartResults(game, ctx, {
612
+ services,
613
+ });
614
+ const result = await ctx.prisma.$transaction([
615
+ ctx.prisma.game.update({
616
+ where: {
617
+ id: gameId,
618
+ },
619
+ include: {
620
+ periods: {
621
+ include: {
622
+ segments: true,
623
+ },
624
+ },
625
+ players: true,
626
+ },
627
+ data: {
628
+ status: DB.GameStatus.RUNNING,
629
+ },
630
+ }),
631
+ // update the active segment of the current period
632
+ ctx.prisma.period.update({
633
+ where: {
634
+ gameId_index: {
635
+ gameId,
636
+ index: currentPeriodIx,
637
+ },
638
+ },
639
+ data: {
640
+ activeSegmentIx: nextSegmentIx,
641
+ activeSegment: {
642
+ connect: {
643
+ gameId_periodIx_index: {
644
+ gameId,
645
+ periodIx: currentPeriodIx,
646
+ index: nextSegmentIx,
647
+ },
648
+ },
649
+ },
650
+ },
651
+ }),
652
+ // SEGMENT INITIALIZATION
653
+ ctx.prisma.periodSegment.update({
654
+ where: {
655
+ gameId_periodIx_index: {
656
+ gameId,
657
+ periodIx: currentPeriodIx,
658
+ index: nextSegmentIx,
659
+ },
660
+ },
661
+ data: {
662
+ results: {
663
+ create: results,
664
+ },
665
+ },
666
+ }),
667
+ ...extras,
668
+ ]);
669
+ return result;
670
+ }
671
+ // RUNNING -> PAUSED
672
+ // compute the segment results of the current segment and set to paused
673
+ case DB.GameStatus.RUNNING: {
674
+ // return if there is no next segment available
675
+ if (!game.activePeriod?.activeSegment?.nextSegment) {
676
+ return null;
677
+ }
678
+ const { results, extras } = computeSegmentEndResults(game, ctx, {
679
+ services,
680
+ });
681
+ const result = await ctx.prisma.$transaction([
682
+ ctx.prisma.game.update({
683
+ where: { id: gameId },
684
+ include: {
685
+ periods: {
686
+ include: {
687
+ segments: true,
688
+ },
689
+ },
690
+ players: true,
691
+ },
692
+ data: {
693
+ status: DB.GameStatus.PAUSED,
694
+ },
695
+ }),
696
+ ctx.prisma.periodSegment.update({
697
+ where: {
698
+ gameId_periodIx_index: {
699
+ gameId,
700
+ periodIx: currentPeriodIx,
701
+ index: currentSegmentIx,
702
+ },
703
+ },
704
+ data: {
705
+ results: {
706
+ update: results,
707
+ },
708
+ },
709
+ include: {
710
+ results: true,
711
+ },
712
+ }),
713
+ // reset player readiness
714
+ ctx.prisma.player.updateMany({
715
+ where: {
716
+ game: {
717
+ id: gameId,
718
+ },
719
+ },
720
+ data: {
721
+ isReady: false,
722
+ },
723
+ }),
724
+ ...extras,
725
+ ]);
726
+ return result;
727
+ }
728
+ default:
729
+ return null;
730
+ }
731
+ }
732
+ async function updatePlayerData({ name, facts }, ctx, { schema }) {
733
+ // if none of the arguments have been provided, no update is performed
734
+ if (none(Boolean, [name, facts])) {
735
+ return null;
736
+ }
737
+ let data = {};
738
+ if (name) {
739
+ data['name'] = name;
740
+ }
741
+ if (facts) {
742
+ data['facts'] = schema.validateSync(facts, {
743
+ stripUnknown: true,
744
+ });
745
+ }
746
+ const player = await ctx.prisma.player.update({
747
+ where: {
748
+ id: ctx.user.sub,
749
+ },
750
+ data,
751
+ include: {
752
+ level: true,
753
+ achievements: {
754
+ include: {
755
+ achievement: true,
756
+ },
757
+ },
758
+ },
759
+ });
760
+ return player;
761
+ }
762
+ async function getGames(args, ctx) {
763
+ const result = await ctx.prisma.game.findMany({
764
+ orderBy: {
765
+ id: 'desc',
766
+ },
767
+ include: {
768
+ _count: {
769
+ select: { players: true },
770
+ },
771
+ activePeriod: true,
772
+ },
773
+ });
774
+ return result.map((game) => ({
775
+ ...game,
776
+ activeSegmentIx: game.activePeriod?.activeSegmentIx,
777
+ }));
778
+ }
779
+ async function getGame(args, ctx) {
780
+ const gameId = args.id ?? ctx.user.gameId;
781
+ if (!gameId) {
782
+ return null;
783
+ }
784
+ return ctx.prisma.game.findUnique({
785
+ where: {
786
+ id: args.id,
787
+ },
788
+ include: {
789
+ players: {
790
+ include: {
791
+ level: true,
792
+ achievements: true,
793
+ },
794
+ orderBy: {
795
+ number: 'asc',
796
+ },
797
+ },
798
+ periods: {
799
+ orderBy: {
800
+ index: 'asc',
801
+ },
802
+ include: {
803
+ segments: {
804
+ orderBy: {
805
+ index: 'asc',
806
+ },
807
+ include: {
808
+ learningElements: true,
809
+ storyElements: true,
810
+ },
811
+ },
812
+ },
813
+ },
814
+ activePeriod: {
815
+ include: {
816
+ segments: {
817
+ orderBy: {
818
+ index: 'asc',
819
+ },
820
+ },
821
+ activeSegment: true,
822
+ },
823
+ },
824
+ },
825
+ });
826
+ }
827
+ async function getGameFromContext(ctx) {
828
+ return ctx.prisma.game.findUnique({
829
+ where: {
830
+ id: ctx.user.gameId,
831
+ },
832
+ include: {
833
+ activePeriod: true,
834
+ },
835
+ });
836
+ }
837
+ async function getLearningElements(args, ctx) {
838
+ return ctx.prisma.learningElement.findMany({
839
+ include: {
840
+ options: true,
841
+ },
842
+ });
843
+ }
844
+ async function getStoryElements(args, ctx) {
845
+ return ctx.prisma.storyElement.findMany();
846
+ }
847
+ function mapAction({ ctx, gameId, activePeriodIx, playerId }) {
848
+ return (action) => ctx.prisma.playerAction.create({
849
+ data: {
850
+ type: action.type,
851
+ facts: action.facts,
852
+ game: {
853
+ connect: { id: gameId },
854
+ },
855
+ player: {
856
+ connect: { id: playerId },
857
+ },
858
+ periodIx: activePeriodIx,
859
+ period: {
860
+ connect: {
861
+ gameId_index: {
862
+ gameId,
863
+ index: activePeriodIx,
864
+ },
865
+ },
866
+ },
867
+ segmentIx: typeof action.segment === 'number' ? action.segment : undefined,
868
+ segment: typeof action.segment === 'number'
869
+ ? {
870
+ connect: {
871
+ gameId_periodIx_index: {
872
+ gameId,
873
+ periodIx: activePeriodIx,
874
+ index: action.segment,
875
+ },
876
+ },
877
+ }
878
+ : undefined,
879
+ },
880
+ });
881
+ }
882
+ function computePeriodStartResults({ results, players, activePeriodIx, gameId, periodFacts }, ctx, { services }) {
883
+ const currentPeriodIx = activePeriodIx;
884
+ const nextPeriodIx = currentPeriodIx + 1;
885
+ let extras = [];
886
+ // if the game is running, transform previous results to next
887
+ if (currentPeriodIx >= 0) {
888
+ const result = results
889
+ // ensure that we only work on PERIOD_END results of the preceding period
890
+ .filter((result) => result.type === DB.PlayerResultType.PERIOD_END)
891
+ .map((result, ix, allResults) => {
892
+ const { resultFacts: facts, actions } = services.PeriodResult.start(result.facts, {
893
+ playerRole: result.player?.role ?? result.player.connect?.role,
894
+ periodFacts,
895
+ });
896
+ if (actions && actions.length > 0) {
897
+ const mapper = mapAction({
898
+ ctx,
899
+ gameId,
900
+ activePeriodIx: currentPeriodIx,
901
+ playerId: result.player.id,
902
+ });
903
+ extras = [...extras, ...actions.map(mapper)];
904
+ }
905
+ return {
906
+ type: DB.PlayerResultType.PERIOD_START,
907
+ periodIx: currentPeriodIx,
908
+ facts,
909
+ player: {
910
+ connect: {
911
+ id: result.player.id ?? result.player.connect.id,
912
+ },
913
+ },
914
+ game: {
915
+ connect: {
916
+ id: gameId,
917
+ },
918
+ },
919
+ };
920
+ });
921
+ return {
922
+ results: result,
923
+ extras,
924
+ };
925
+ }
926
+ // if the game has not started yet, generate initial PERIOD_START results
927
+ const result = players.map((player, ix, allPlayers) => {
928
+ const { resultFacts: facts, actions } = services.PeriodResult.initialize({}, { playerRole: player.role, periodFacts });
929
+ if (actions && actions.length > 0) {
930
+ const mapper = mapAction({
931
+ ctx,
932
+ gameId,
933
+ activePeriodIx: nextPeriodIx,
934
+ playerId: player.id,
935
+ });
936
+ extras = [...extras, ...actions.map(mapper)];
937
+ }
938
+ return {
939
+ type: DB.PlayerResultType.PERIOD_START,
940
+ periodIx: nextPeriodIx,
941
+ facts,
942
+ player: {
943
+ connect: {
944
+ id: player.id,
945
+ },
946
+ },
947
+ game: {
948
+ connect: {
949
+ id: gameId,
950
+ },
951
+ },
952
+ };
953
+ });
954
+ return {
955
+ results: result,
956
+ extras,
957
+ };
958
+ }
959
+ async function computePeriodEndResults({ segmentEndResults, players, activeSegmentResults, periodFacts, periodDecisions, segmentFacts, activePeriodIx, activeSegmentIx, gameId, }, ctx, { services }) {
960
+ let extras = [];
961
+ let promises = [];
962
+ const perPlayer = {};
963
+ players.forEach((player) => {
964
+ perPlayer[player.id] = {
965
+ segmentEndResults: segmentEndResults.filter((res) => res.playerId === player.id),
966
+ consolidationDecisions: periodDecisions.find((decision) => decision.playerId === player.id),
967
+ };
968
+ });
969
+ const results = activeSegmentResults
970
+ .filter((result) => result.type === DB.PlayerResultType.SEGMENT_END)
971
+ .map((result, ix, allResults) => {
972
+ const segmentEndResults = perPlayer[result.playerId].segmentEndResults;
973
+ const consolidationDecisions = perPlayer[result.playerId].consolidationDecisions;
974
+ const { resultFacts: facts, actions, events, } = services.PeriodResult.end(result.facts, {
975
+ segmentEndResults,
976
+ periodFacts,
977
+ segmentFacts,
978
+ playerRole: result.player.role,
979
+ playerLevel: result.player.levelIx + 1,
980
+ playerExperience: result.player.experience,
981
+ consolidationDecisions,
982
+ periodIx: activePeriodIx,
983
+ segmentIx: activeSegmentIx,
984
+ });
985
+ logger.debug(actions);
986
+ if (actions && actions.length > 0) {
987
+ const mapper = mapAction({
988
+ ctx,
989
+ gameId,
990
+ activePeriodIx,
991
+ playerId: result.player.id,
992
+ });
993
+ extras = [...extras, ...actions.map(mapper)];
994
+ }
995
+ promises = [
996
+ ...promises,
997
+ receiveEvents({
998
+ events,
999
+ ctx: {
1000
+ args: {
1001
+ playerId: result.player.id,
1002
+ periodIx: activePeriodIx,
1003
+ gameId,
1004
+ },
1005
+ user: ctx.user,
1006
+ achievements: result.player.achievementKeys,
1007
+ experience: result.player.experience,
1008
+ currentLevelIx: result.player.levelIx,
1009
+ },
1010
+ prisma: ctx.prisma,
1011
+ }),
1012
+ ];
1013
+ return {
1014
+ type: DB.PlayerResultType.PERIOD_END,
1015
+ periodIx: activePeriodIx,
1016
+ facts,
1017
+ player: {
1018
+ connect: {
1019
+ id: result.playerId,
1020
+ },
1021
+ },
1022
+ game: {
1023
+ connect: {
1024
+ id: gameId,
1025
+ },
1026
+ },
1027
+ };
1028
+ });
1029
+ return {
1030
+ extras,
1031
+ results,
1032
+ promises,
1033
+ };
1034
+ }
1035
+ function computeSegmentStartResults(game, ctx, { services }) {
1036
+ const currentSegmentIx = game.activePeriod.activeSegmentIx;
1037
+ const nextSegmentIx = currentSegmentIx + 1;
1038
+ let extras = [];
1039
+ // if there was a previous segment, compute the change in results
1040
+ if (currentSegmentIx >= 0) {
1041
+ const results = game.activePeriod.activeSegment.results
1042
+ .filter((result) => result.type === DB.PlayerResultType.SEGMENT_END)
1043
+ .reduce((acc, result, ix, allResults) => {
1044
+ const { resultFacts: facts, actions } = services.SegmentResult.start(result.facts, {
1045
+ playerRole: result.player.role,
1046
+ periodFacts: game.activePeriod.facts,
1047
+ segmentFacts: game.activePeriod.activeSegment.facts,
1048
+ nextSegmentFacts: game.activePeriod.activeSegment.nextSegment?.facts,
1049
+ segmentIx: nextSegmentIx,
1050
+ });
1051
+ if (actions && actions.length > 0) {
1052
+ const mapper = mapAction({
1053
+ ctx,
1054
+ gameId: game.id,
1055
+ activePeriodIx: game.activePeriodIx,
1056
+ playerId: result.player.id,
1057
+ });
1058
+ extras = [...extras, ...actions.map(mapper)];
1059
+ }
1060
+ const common = {
1061
+ facts,
1062
+ periodIx: game.activePeriodIx,
1063
+ segmentIx: nextSegmentIx,
1064
+ player: {
1065
+ connect: {
1066
+ id: result.playerId,
1067
+ },
1068
+ },
1069
+ period: {
1070
+ connect: {
1071
+ id: game.activePeriodId,
1072
+ },
1073
+ },
1074
+ game: {
1075
+ connect: {
1076
+ id: game.id,
1077
+ },
1078
+ },
1079
+ };
1080
+ return [
1081
+ ...acc,
1082
+ {
1083
+ ...common,
1084
+ type: DB.PlayerResultType.SEGMENT_START,
1085
+ },
1086
+ {
1087
+ ...common,
1088
+ type: DB.PlayerResultType.SEGMENT_END,
1089
+ },
1090
+ ];
1091
+ }, []);
1092
+ return {
1093
+ results,
1094
+ extras,
1095
+ };
1096
+ }
1097
+ // if it is the first segment, transform PERIOD_START to SEGMENT_START
1098
+ const results = game.activePeriod.results
1099
+ .filter((result) => result.type === DB.PlayerResultType.PERIOD_START)
1100
+ .reduce((acc, result, ix, allResults) => {
1101
+ let { resultFacts: facts } = services.SegmentResult.initialize(result.facts, {
1102
+ playerRole: result.player.role,
1103
+ periodFacts: game.activePeriod.facts,
1104
+ segmentFacts: game.activePeriod.activeSegment?.facts,
1105
+ nextSegmentFacts: game.activePeriod.activeSegment?.nextSegment?.facts,
1106
+ segmentIx: nextSegmentIx,
1107
+ });
1108
+ const common = {
1109
+ facts,
1110
+ periodIx: game.activePeriodIx,
1111
+ segmentIx: nextSegmentIx,
1112
+ player: {
1113
+ connect: {
1114
+ id: result.playerId,
1115
+ },
1116
+ },
1117
+ period: {
1118
+ connect: {
1119
+ id: game.activePeriodId,
1120
+ },
1121
+ },
1122
+ game: {
1123
+ connect: {
1124
+ id: game.id,
1125
+ },
1126
+ },
1127
+ };
1128
+ return [
1129
+ ...acc,
1130
+ {
1131
+ ...common,
1132
+ type: DB.PlayerResultType.SEGMENT_START,
1133
+ },
1134
+ {
1135
+ ...common,
1136
+ type: DB.PlayerResultType.SEGMENT_END,
1137
+ },
1138
+ ];
1139
+ }, []);
1140
+ return {
1141
+ results,
1142
+ extras,
1143
+ };
1144
+ }
1145
+ function computeSegmentEndResults(game, ctx, { services }) {
1146
+ let extras = [];
1147
+ const results = game.activePeriod.activeSegment.results
1148
+ .filter((result) => result.type === DB.PlayerResultType.SEGMENT_END)
1149
+ .map((result, ix, allResults) => {
1150
+ const { resultFacts: facts, actions } = services.SegmentResult.end(result.facts, {
1151
+ playerRole: result.player.role,
1152
+ periodFacts: game.activePeriod.facts,
1153
+ segmentFacts: game.activePeriod.activeSegment.facts,
1154
+ segmentIx: game.activePeriod.activeSegmentIx,
1155
+ });
1156
+ if (actions && actions.length > 0) {
1157
+ const mapper = mapAction({
1158
+ ctx,
1159
+ gameId: game.id,
1160
+ activePeriodIx: game.activePeriodIx,
1161
+ playerId: result.player.id,
1162
+ });
1163
+ extras = [...extras, ...actions.map(mapper)];
1164
+ }
1165
+ return {
1166
+ where: {
1167
+ periodIx_segmentIx_playerId_type: {
1168
+ periodIx: game.activePeriodIx,
1169
+ segmentIx: game.activePeriod.activeSegmentIx,
1170
+ playerId: result.playerId,
1171
+ type: DB.PlayerResultType.SEGMENT_END,
1172
+ },
1173
+ },
1174
+ data: {
1175
+ facts,
1176
+ game: {
1177
+ connect: {
1178
+ id: game.id,
1179
+ },
1180
+ },
1181
+ },
1182
+ };
1183
+ });
1184
+ return {
1185
+ results,
1186
+ extras,
1187
+ };
1188
+ }
1189
+
1190
+ export { activateNextPeriod, activateNextSegment, addGamePeriod, addPeriodSegment, computePeriodEndResults, computePeriodStartResults, computeSegmentEndResults, computeSegmentStartResults, createGame, getGame, getGameFromContext, getGames, getLearningElements, getStoryElements, updatePlayerData };
1191
+ //# sourceMappingURL=GameService.js.map