@pkmn/engine 0.1.0-dev.dd5b9081 → 0.1.0-dev.deda8593

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 (53) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data/ids.json +12 -12
  6. package/build/pkg/data/layout.json +2 -2
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +4 -4
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.js +2 -2
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/index.d.ts +2 -1
  13. package/build/pkg/index.js.map +1 -1
  14. package/build/pkg/protocol.js +61 -42
  15. package/build/pkg/protocol.js.map +1 -1
  16. package/build/test/display.js +2 -2
  17. package/build/test/display.js.map +1 -1
  18. package/build/tools/debug.js +5 -4
  19. package/build/tools/debug.js.map +1 -1
  20. package/build.zig +102 -104
  21. package/package.json +43 -23
  22. package/src/bin/install-pkmn-engine +26 -18
  23. package/src/lib/common/data.zig +9 -9
  24. package/src/lib/common/debug.zig +2 -2
  25. package/src/lib/common/optional.zig +9 -2
  26. package/src/lib/common/options.zig +2 -0
  27. package/src/lib/common/protocol.zig +739 -318
  28. package/src/lib/common/rational.zig +12 -9
  29. package/src/lib/common/rng.zig +25 -16
  30. package/src/lib/gen1/calc.zig +219 -49
  31. package/src/lib/gen1/chance.zig +138 -115
  32. package/src/lib/gen1/data/moves.zig +190 -176
  33. package/src/lib/gen1/data/species.zig +3 -3
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +44 -44
  36. package/src/lib/gen1/helpers.zig +148 -82
  37. package/src/lib/gen1/mechanics.zig +392 -406
  38. package/src/lib/gen2/calc.zig +143 -0
  39. package/src/lib/gen2/chance.zig +1315 -0
  40. package/src/lib/gen2/data/items.zig +12 -18
  41. package/src/lib/gen2/data/moves.zig +377 -344
  42. package/src/lib/gen2/data/species.zig +1 -1
  43. package/src/lib/gen2/data/types.zig +134 -31
  44. package/src/lib/gen2/data.zig +52 -44
  45. package/src/lib/gen2/generated.zig +806 -0
  46. package/src/lib/gen2/helpers.zig +37 -7
  47. package/src/lib/gen2/mechanics.zig +3086 -211
  48. package/src/lib/{binding/node.zig → node.zig} +10 -13
  49. package/src/lib/pkmn.zig +27 -12
  50. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  51. package/src/test/display/pkmn.html.tmpl +5 -5
  52. package/src/lib/gen1/README.md +0 -894
  53. package/src/lib/gen2/README.md +0 -383
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
 
3
4
  const common = @import("../common/data.zig");
4
5
  const optional = @import("../common/optional.zig");
@@ -35,6 +36,8 @@ const Status = data.Status;
35
36
  const Action = chance.Action;
36
37
  const Actions = chance.Actions;
37
38
 
39
+ const endian = builtin.cpu.arch.endian();
40
+
38
41
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
39
42
  /// random generator helpers.
40
43
  pub const Options = struct {
@@ -43,6 +46,8 @@ pub const Options = struct {
43
46
  /// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
44
47
  /// unimplementable in the engine.
45
48
  block: bool = showdown,
49
+ // FIXME: remove once durations work with -Dchance
50
+ durations: bool = false,
46
51
  };
47
52
 
48
53
  /// Helpers to simplify initialization of a Generation I battle.
@@ -86,18 +91,38 @@ pub const Battle = struct {
86
91
  };
87
92
 
88
93
  fn prng(rand: *PSRNG) data.PRNG {
89
- // GLITCH: initial bytes in seed can only range from 0-252, not 0-255
90
- const max: u8 = 253;
91
94
  return .{
92
95
  .src = .{
93
96
  .seed = if (showdown)
94
97
  rand.newSeed()
95
- else
96
- .{
97
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
98
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
99
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
100
- },
98
+ else seed: {
99
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
100
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
101
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
102
+
103
+ // Bytes in seed can only range from 0-252, not 0-255
104
+ const max: u8 = 253;
105
+ // Instructions take multiples of 4 cycles to execute
106
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
107
+ var add: u8 = rand.range(u8, 0, 256);
108
+ var carry: u8 = 0;
109
+
110
+ var i: u8 = 0;
111
+ while (i < seed.len) {
112
+ const div: u8 = @intCast(timer >> 8);
113
+ add +%= div +% carry;
114
+ if (add < max) {
115
+ seed[i] = add;
116
+ i += 1;
117
+ timer +%= 472;
118
+ carry = 1;
119
+ } else {
120
+ timer +%= 452;
121
+ carry = 0;
122
+ }
123
+ }
124
+ break :seed seed;
125
+ },
101
126
  },
102
127
  };
103
128
  }
@@ -210,6 +235,7 @@ pub const Pokemon = struct {
210
235
  var m: Move = .None;
211
236
  sample: while (true) {
212
237
  m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
238
+ if (opt.durations and durations(m)) continue :sample;
213
239
  if (opt.block and blocked(m)) continue :sample;
214
240
  for (0..i) |j| if (ms[j].id == m) continue :sample;
215
241
  break;
@@ -247,6 +273,23 @@ fn blocked(m: Move) bool {
247
273
  };
248
274
  }
249
275
 
276
+ fn durations(m: Move) bool {
277
+ // Any moves that have effects that result in duration tracking with -Dchance
278
+ return switch (Move.get(m).effect) {
279
+ .Confusion,
280
+ .Bide,
281
+ .Sleep,
282
+ .Rage,
283
+ .Binding,
284
+ .Thrashing,
285
+ .ConfusionChance,
286
+ .Disable,
287
+ .Metronome,
288
+ => true,
289
+ else => false,
290
+ };
291
+ }
292
+
250
293
  /// Convenience helper to create a move-type choice for the provided move slot.
251
294
  pub fn move(slot: u4) Choice {
252
295
  return .{ .type = .Move, .data = slot };
@@ -266,8 +309,8 @@ pub const Rolls = struct {
266
309
  const PLAYERS = [_]Optional(Player){ .P1, .P2 };
267
310
 
268
311
  /// Returns a slice with the correct range of values for speed ties given the `action` state.
269
- pub inline fn speedTie(action: Action) []const Optional(Player) {
270
- return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
312
+ pub fn speedTie(action: Action) []const Optional(Player) {
313
+ return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
271
314
  }
272
315
 
273
316
  const BOOL_NONE = [_]Optional(bool){.None};
@@ -275,27 +318,21 @@ pub const Rolls = struct {
275
318
 
276
319
  /// Returns a slice with the correct range of values for hits given the `action` state
277
320
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
278
- pub inline fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
321
+ pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
279
322
  if (parent == .true) return &BOOL_NONE;
280
- return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
323
+ return if (action.hit == .None) &BOOL_NONE else &BOOLS;
281
324
  }
282
325
 
283
326
  /// Returns a slice with the correct range of values for critical hits given the `action` state
284
327
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
285
- pub inline fn criticalHit(
286
- action: Action,
287
- parent: Optional(bool),
288
- ) []const Optional(bool) {
328
+ pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
289
329
  if (parent == .false) return &BOOL_NONE;
290
- return if (@field(action, "critical_hit") == .None) &BOOL_NONE else &BOOLS;
330
+ return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
291
331
  }
292
332
 
293
333
  /// Returns a slice with the correct range of values for secondary chances hits given the
294
334
  /// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
295
- pub inline fn secondaryChance(
296
- action: Action,
297
- parent: Optional(bool),
298
- ) []const Optional(bool) {
335
+ pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
299
336
  if (parent == .false) return &BOOL_NONE;
300
337
  return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
301
338
  }
@@ -305,8 +342,8 @@ pub const Rolls = struct {
305
342
 
306
343
  /// Returns the range bounding damage rolls given the `action` state and the state of
307
344
  /// the `parent` (whether the player's Pokémon's move hit).
308
- pub inline fn damage(action: Action, parent: Optional(bool)) Range {
309
- return if (parent == .false or @field(action, "damage") == 0)
345
+ pub fn damage(action: Action, parent: Optional(bool)) Range {
346
+ return if (parent == .false or action.damage == 0)
310
347
  .{ .min = 0, .max = 1 }
311
348
  else
312
349
  .{ .min = 217, .max = 256 };
@@ -314,7 +351,7 @@ pub const Rolls = struct {
314
351
 
315
352
  /// Returns the max damage roll which will produce the same damage as `roll`
316
353
  /// given the base damage in `summaries`.
317
- pub inline fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
354
+ pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
318
355
  if (roll == 0) return roll;
319
356
 
320
357
  const dmg = summaries.get(player.foe()).damage;
@@ -326,15 +363,15 @@ pub const Rolls = struct {
326
363
 
327
364
  /// Returns a slice with the correct range of values for confused given the `action` state
328
365
  /// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
329
- pub inline fn confused(action: Action, parent: u4) []const Optional(bool) {
330
- return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
366
+ pub fn confused(action: Action, parent: u4) []const Optional(bool) {
367
+ return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
331
368
  }
332
369
 
333
370
  /// Returns a slice with the correct range of values for paralysis given the `action` state
334
371
  /// and the state of the `parent` (whether the player's Pokémon was confused).
335
- pub inline fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
372
+ pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
336
373
  if (parent == .true) return &BOOL_NONE;
337
- return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
374
+ return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
338
375
  }
339
376
 
340
377
  const DURATION_NONE = [_]u1{0};
@@ -342,9 +379,9 @@ pub const Rolls = struct {
342
379
 
343
380
  /// Returns a slice with a range of values for duration given the `action` state
344
381
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
345
- pub inline fn duration(action: Action, parent: Optional(bool)) []const u1 {
382
+ pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
346
383
  if (parent == .false) return &DURATION_NONE;
347
- return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
384
+ return if (action.duration == 0) &DURATION_NONE else &DURATION;
348
385
  }
349
386
 
350
387
  const U3_NONE = [_]u3{0};
@@ -352,8 +389,8 @@ pub const Rolls = struct {
352
389
 
353
390
  /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
391
  pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const a = @field(action.durations, "sleep");
356
- const o = @field(origin.durations, "sleep");
392
+ const a = action.durations.sleep;
393
+ const o = origin.durations.sleep;
357
394
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
358
395
  assert(a == 0 or a == o + 1);
359
396
  return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -365,8 +402,8 @@ pub const Rolls = struct {
365
402
  /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
366
403
  pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
367
404
  if (parent > 0) return &DISABLE_NONE;
368
- const a = @field(action.durations, "disable");
369
- const o = @field(origin.durations, "disable");
405
+ const a = action.durations.disable;
406
+ const o = origin.durations.disable;
370
407
  if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
371
408
  assert(a == 0 or a == o + 1);
372
409
  return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
@@ -376,8 +413,8 @@ pub const Rolls = struct {
376
413
  /// and the state of the `parent` (the player's remaining sleep turns).
377
414
  pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
378
415
  if (parent > 0) return &U3_NONE;
379
- const a = @field(action.durations, "confusion");
380
- const o = @field(origin.durations, "confusion");
416
+ const a = action.durations.confusion;
417
+ const o = origin.durations.confusion;
381
418
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
419
  assert(a == 0 or a == o + 1);
383
420
  return if (o >= 5) &U3_NONE else if (o < 2)
@@ -390,8 +427,8 @@ pub const Rolls = struct {
390
427
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
391
428
  pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
392
429
  if (parent == .true) return &U3_NONE;
393
- const a = @field(action.durations, "attacking");
394
- const o = @field(origin.durations, "attacking");
430
+ const a = action.durations.attacking;
431
+ const o = origin.durations.attacking;
395
432
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
433
  assert(a == 0 or a == o + 1);
397
434
  return if (o >= 3) &U3_NONE else if (o < 2)
@@ -404,8 +441,8 @@ pub const Rolls = struct {
404
441
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
405
442
  pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
406
443
  if (parent == .true) return &U3_NONE;
407
- const a = @field(action.durations, "binding");
408
- const o = @field(origin.durations, "binding");
444
+ const a = action.durations.binding;
445
+ const o = origin.durations.binding;
409
446
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
447
  assert(a == 0 or a == o + 1);
411
448
  return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -418,9 +455,9 @@ pub const Rolls = struct {
418
455
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
419
456
  ///
420
457
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
421
- pub inline fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
458
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
422
459
  if (parent == .false) return &SLOT_NONE;
423
- return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
460
+ return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
424
461
  }
425
462
 
426
463
  const MULTI_NONE = [_]u4{0};
@@ -428,9 +465,9 @@ pub const Rolls = struct {
428
465
 
429
466
  /// Returns a slice with the correct range of values for multi hit given the `action` state
430
467
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
431
- pub inline fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
468
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
432
469
  if (parent == .false) return &MULTI_NONE;
433
- return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
470
+ return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
434
471
  }
435
472
 
436
473
  const PSYWAVE_NONE = [_]u8{0};
@@ -442,49 +479,34 @@ pub const Rolls = struct {
442
479
 
443
480
  /// Returns a slice with the correct range of values for psywave given the `action` state,
444
481
  /// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
445
- pub inline fn psywave(
482
+ pub fn psywave(
446
483
  action: Action,
447
484
  side: *data.Side,
448
485
  parent: Optional(bool),
449
486
  ) []const u8 {
450
487
  if (parent == .false) return &PSYWAVE_NONE;
451
- return if (@field(action, "psywave") == 0)
488
+ return if (action.psywave == 0)
452
489
  &PSYWAVE_NONE
453
490
  else
454
491
  PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
455
492
  }
456
493
 
457
494
  const MOVE_NONE = [_]Move{.None};
458
- const MOVES = init: {
459
- var moves: [Move.size - 2]Move = undefined;
460
- var i: usize = 0;
461
- for (@typeInfo(Move).Enum.fields) |f| {
462
- if (!(std.mem.eql(u8, f.name, "None") or
463
- std.mem.eql(u8, f.name, "Metronome") or
464
- std.mem.eql(u8, f.name, "Struggle") or
465
- std.mem.eql(u8, f.name, "SKIP_TURN")))
466
- {
467
- moves[i] = @field(Move, f.name);
468
- i += 1;
469
- }
470
- }
471
- break :init moves;
472
- };
473
495
 
474
496
  /// Returns a slice with the correct range of values for metronome given the `action` state.
475
- pub inline fn metronome(action: Action) []const Move {
476
- return if (@field(action, "metronome") == .None) &MOVE_NONE else &MOVES;
497
+ pub fn metronome(action: Action) []const Move {
498
+ return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
477
499
  }
478
500
  };
479
501
 
480
502
  test "Rolls.speedTie" {
481
- const actions = Actions{ .p1 = .{ .speed_tie = .P2 } };
503
+ const actions: Actions = .{ .p1 = .{ .speed_tie = .P2 } };
482
504
  try expectEqualSlices(Optional(Player), &.{ .P1, .P2 }, Rolls.speedTie(actions.p1));
483
505
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
484
506
  }
485
507
 
486
508
  test "Rolls.damage" {
487
- const actions = Actions{ .p2 = .{ .damage = 221 } };
509
+ const actions: Actions = .{ .p2 = .{ .damage = 221 } };
488
510
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
489
511
  try expectEqual(Rolls.Range{ .min = 217, .max = 256 }, Rolls.damage(actions.p2, .None));
490
512
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p2, .false));
@@ -493,16 +515,16 @@ test "Rolls.damage" {
493
515
  test "Rolls.coalesce" {
494
516
  var summaries =
495
517
  calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
496
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
497
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
498
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
518
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
519
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
520
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
499
521
  summaries.p1.damage.final = 74;
500
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
501
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
522
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
523
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
502
524
  }
503
525
 
504
526
  test "Rolls.hit" {
505
- const actions = Actions{ .p2 = .{ .hit = .true } };
527
+ const actions: Actions = .{ .p2 = .{ .hit = .true } };
506
528
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p1, .None));
507
529
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.hit(actions.p2, .None));
508
530
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p2, .true));
@@ -510,7 +532,7 @@ test "Rolls.hit" {
510
532
  }
511
533
 
512
534
  test "Rolls.secondaryChance" {
513
- const actions = Actions{ .p1 = .{ .secondary_chance = .true } };
535
+ const actions: Actions = .{ .p1 = .{ .secondary_chance = .true } };
514
536
  try expectEqualSlices(
515
537
  Optional(bool),
516
538
  &.{ .false, .true },
@@ -521,7 +543,7 @@ test "Rolls.secondaryChance" {
521
543
  }
522
544
 
523
545
  test "Rolls.criticalHit" {
524
- const actions = Actions{ .p1 = .{ .critical_hit = .true } };
546
+ const actions: Actions = .{ .p1 = .{ .critical_hit = .true } };
525
547
  try expectEqualSlices(
526
548
  Optional(bool),
527
549
  &.{ .false, .true },
@@ -532,14 +554,14 @@ test "Rolls.criticalHit" {
532
554
  }
533
555
 
534
556
  test "Rolls.confused" {
535
- const actions = Actions{ .p2 = .{ .confused = .true } };
557
+ const actions: Actions = .{ .p2 = .{ .confused = .true } };
536
558
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, 1));
537
559
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2, 1));
538
560
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, 0));
539
561
  }
540
562
 
541
563
  test "Rolls.paralyzed" {
542
- const actions = Actions{ .p2 = .{ .paralyzed = .true } };
564
+ const actions: Actions = .{ .p2 = .{ .paralyzed = .true } };
543
565
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p1, .None));
544
566
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .None));
545
567
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .false));
@@ -547,7 +569,7 @@ test "Rolls.paralyzed" {
547
569
  }
548
570
 
549
571
  test "Rolls.duration" {
550
- const actions = Actions{ .p2 = .{ .duration = 3 } };
572
+ const actions: Actions = .{ .p2 = .{ .duration = 3 } };
551
573
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p1, .None));
552
574
  try expectEqualSlices(u1, &.{1}, Rolls.duration(actions.p2, .None));
553
575
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
@@ -757,27 +779,27 @@ test "Rolls.binding" {
757
779
  }
758
780
 
759
781
  test "Rolls.moveSlot" {
760
- const actions = Actions{ .p2 = .{ .move_slot = 3 } };
782
+ const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
761
783
  try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
762
784
  try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
763
785
  try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p2, .false));
764
786
  }
765
787
 
766
788
  test "Rolls.multiHit" {
767
- const actions = Actions{ .p2 = .{ .multi_hit = 3 } };
789
+ const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
768
790
  try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p1, .None));
769
791
  try expectEqualSlices(u4, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
770
792
  try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p2, .false));
771
793
  }
772
794
 
773
795
  test "Rolls.metronome" {
774
- const actions = Actions{ .p2 = .{ .metronome = .Surf } };
796
+ const actions: Actions = .{ .p2 = .{ .metronome = .Surf } };
775
797
  try expectEqualSlices(Move, &.{.None}, Rolls.metronome(actions.p1));
776
798
  try expectEqual(@as(Move, @enumFromInt(24)), Rolls.metronome(actions.p2)[23]);
777
799
  }
778
800
 
779
801
  test "Rolls.psywave" {
780
- const actions = Actions{ .p2 = .{ .psywave = 79 } };
802
+ const actions: Actions = .{ .p2 = .{ .psywave = 79 } };
781
803
  var side = Side.init(&[_]Pokemon{.{
782
804
  .species = .Bulbasaur,
783
805
  .level = 100,
@@ -786,9 +808,53 @@ test "Rolls.psywave" {
786
808
 
787
809
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
788
810
  var rolls = Rolls.psywave(actions.p2, &side, .None);
789
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
811
+ try expectEqual(150, rolls[rolls.len - 1]);
790
812
  side.stored().level = 81;
791
813
  rolls = Rolls.psywave(actions.p2, &side, .None);
792
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
814
+ try expectEqual(121, rolls[rolls.len - 1]);
793
815
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
794
816
  }
817
+
818
+ /// Empirically determined maximum number of bytes possibly required to store the diff between two
819
+ /// Generation I battles.
820
+ pub const MAX_DIFFS: usize = 117;
821
+
822
+ /// The optimal size in bytes required to hold the diff between two Generation I battles.
823
+ /// At least as large as MAX_DIFFS.
824
+ pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
825
+ MAX_DIFFS
826
+ else
827
+ std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
828
+
829
+ /// TODO
830
+ pub fn diff(
831
+ a: *const data.Battle(data.PRNG),
832
+ b: *const data.Battle(data.PRNG),
833
+ w: protocol.ByteStream.Writer,
834
+ ) !usize {
835
+ const pos = w.stream.pos;
836
+ const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
837
+ assert(size % 2 == 0);
838
+ assert(size / 2 < 255);
839
+
840
+ var i: usize = 0;
841
+ while (i < size) : (i += 2) {
842
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
843
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
844
+ try w.writeByte(@intCast(i / 2));
845
+ try w.writeInt(u16, val, endian);
846
+ }
847
+ }
848
+
849
+ return w.stream.pos - pos;
850
+ }
851
+
852
+ /// TODO
853
+ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
854
+ var i: usize = 0;
855
+ while (i < buf.len) : (i += 3) {
856
+ const off = @as(u16, @intCast(buf[i])) * 2;
857
+ const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
858
+ std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
859
+ }
860
+ }