@pkmn/engine 0.1.0-dev.8b83af6b → 0.1.0-dev.90d6c14c

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.
@@ -219,8 +219,12 @@ pub const Immune = enum(u8) {
219
219
  OHKO,
220
220
  };
221
221
 
222
+ /// Null object pattern implementation of `Log` backed by a `std.io.null_writer`.
223
+ /// Ignores anything sent to it, though protocol logging should additionally be turned off
224
+ /// entirely with `options.log`.
222
225
  pub const NULL = Log(@TypeOf(std.io.null_writer)){ .writer = std.io.null_writer };
223
226
 
227
+ /// Logs protocol information to its `Writer` during a battle update when `options.log` is enabled.
224
228
  pub fn Log(comptime Writer: type) type {
225
229
  return struct {
226
230
  const Self = @This();
@@ -231,6 +235,7 @@ pub fn Log(comptime Writer: type) type {
231
235
 
232
236
  pub fn move(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
233
237
  if (!enabled) return;
238
+
234
239
  assert(m != .None);
235
240
  try self.writer.writeAll(&.{
236
241
  @intFromEnum(ArgType.Move),
@@ -254,6 +259,7 @@ pub fn Log(comptime Writer: type) type {
254
259
 
255
260
  pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
256
261
  if (!enabled) return;
262
+
257
263
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
258
264
  try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
259
265
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -263,6 +269,7 @@ pub fn Log(comptime Writer: type) type {
263
269
 
264
270
  pub fn cant(self: Self, ident: ID, reason: Cant) Error!void {
265
271
  if (!enabled) return;
272
+
266
273
  assert(reason != .Disable);
267
274
  try self.writer.writeAll(&.{
268
275
  @intFromEnum(ArgType.Cant),
@@ -273,6 +280,7 @@ pub fn Log(comptime Writer: type) type {
273
280
 
274
281
  pub fn disabled(self: Self, ident: ID, m: anytype) Error!void {
275
282
  if (!enabled) return;
283
+
276
284
  try self.writer.writeAll(&.{
277
285
  @intFromEnum(ArgType.Cant),
278
286
  @as(u8, @bitCast(ident)),
@@ -283,12 +291,14 @@ pub fn Log(comptime Writer: type) type {
283
291
 
284
292
  pub fn faint(self: Self, ident: ID, done: bool) Error!void {
285
293
  if (!enabled) return;
294
+
286
295
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(ident)) });
287
296
  if (done) try self.writer.writeByte(@intFromEnum(ArgType.None));
288
297
  }
289
298
 
290
299
  pub fn turn(self: Self, num: u16) Error!void {
291
300
  if (!enabled) return;
301
+
292
302
  try self.writer.writeByte(@intFromEnum(ArgType.Turn));
293
303
  try self.writer.writeIntNative(u16, num);
294
304
  try self.writer.writeByte(@intFromEnum(ArgType.None));
@@ -296,6 +306,7 @@ pub fn Log(comptime Writer: type) type {
296
306
 
297
307
  pub fn win(self: Self, player: Player) Error!void {
298
308
  if (!enabled) return;
309
+
299
310
  try self.writer.writeAll(&.{
300
311
  @intFromEnum(ArgType.Win),
301
312
  @intFromEnum(player),
@@ -305,11 +316,13 @@ pub fn Log(comptime Writer: type) type {
305
316
 
306
317
  pub fn tie(self: Self) Error!void {
307
318
  if (!enabled) return;
319
+
308
320
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Tie), @intFromEnum(ArgType.None) });
309
321
  }
310
322
 
311
323
  pub fn damage(self: Self, ident: ID, pokemon: anytype, reason: Damage) Error!void {
312
324
  if (!enabled) return;
325
+
313
326
  assert(@intFromEnum(reason) <= @intFromEnum(Damage.LeechSeed));
314
327
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
315
328
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -325,6 +338,7 @@ pub fn Log(comptime Writer: type) type {
325
338
  source: ID,
326
339
  ) Error!void {
327
340
  if (!enabled) return;
341
+
328
342
  assert(@intFromEnum(reason) == @intFromEnum(Damage.RecoilOf));
329
343
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
330
344
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -338,6 +352,7 @@ pub fn Log(comptime Writer: type) type {
338
352
 
339
353
  pub fn heal(self: Self, ident: ID, pokemon: anytype, reason: Heal) Error!void {
340
354
  if (!enabled) return;
355
+
341
356
  assert(reason != .Drain);
342
357
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(ident)) });
343
358
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -347,6 +362,7 @@ pub fn Log(comptime Writer: type) type {
347
362
 
348
363
  pub fn drain(self: Self, source: ID, pokemon: anytype, target: ID) Error!void {
349
364
  if (!enabled) return;
365
+
350
366
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(source)) });
351
367
  try self.writer.writeIntNative(u16, pokemon.hp);
352
368
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
@@ -359,6 +375,7 @@ pub fn Log(comptime Writer: type) type {
359
375
 
360
376
  pub fn status(self: Self, ident: ID, value: u8, reason: Status) Error!void {
361
377
  if (!enabled) return;
378
+
362
379
  assert(reason != .From);
363
380
  try self.writer.writeAll(&.{
364
381
  @intFromEnum(ArgType.Status),
@@ -370,6 +387,7 @@ pub fn Log(comptime Writer: type) type {
370
387
 
371
388
  pub fn statusFrom(self: Self, ident: ID, value: u8, m: anytype) Error!void {
372
389
  if (!enabled) return;
390
+
373
391
  try self.writer.writeAll(&.{
374
392
  @intFromEnum(ArgType.Status),
375
393
  @as(u8, @bitCast(ident)),
@@ -381,6 +399,7 @@ pub fn Log(comptime Writer: type) type {
381
399
 
382
400
  pub fn curestatus(self: Self, ident: ID, value: u8, reason: CureStatus) Error!void {
383
401
  if (!enabled) return;
402
+
384
403
  try self.writer.writeAll(&.{
385
404
  @intFromEnum(ArgType.CureStatus),
386
405
  @as(u8, @bitCast(ident)),
@@ -391,6 +410,7 @@ pub fn Log(comptime Writer: type) type {
391
410
 
392
411
  pub fn boost(self: Self, ident: ID, reason: Boost, num: i8) Error!void {
393
412
  if (!enabled) return;
413
+
394
414
  assert(num != 0 and (reason != .Rage or num > 0));
395
415
  try self.writer.writeAll(&.{
396
416
  @intFromEnum(ArgType.Boost),
@@ -402,11 +422,13 @@ pub fn Log(comptime Writer: type) type {
402
422
 
403
423
  pub fn clearallboost(self: Self) Error!void {
404
424
  if (!enabled) return;
425
+
405
426
  try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
406
427
  }
407
428
 
408
429
  pub fn fail(self: Self, ident: ID, reason: Fail) Error!void {
409
430
  if (!enabled) return;
431
+
410
432
  try self.writer.writeAll(&.{
411
433
  @intFromEnum(ArgType.Fail),
412
434
  @as(u8, @bitCast(ident)),
@@ -416,11 +438,13 @@ pub fn Log(comptime Writer: type) type {
416
438
 
417
439
  pub fn miss(self: Self, source: ID) Error!void {
418
440
  if (!enabled) return;
441
+
419
442
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(source)) });
420
443
  }
421
444
 
422
445
  pub fn hitcount(self: Self, ident: ID, num: u8) Error!void {
423
446
  if (!enabled) return;
447
+
424
448
  try self.writer.writeAll(&.{
425
449
  @intFromEnum(ArgType.HitCount),
426
450
  @as(u8, @bitCast(ident)),
@@ -430,6 +454,7 @@ pub fn Log(comptime Writer: type) type {
430
454
 
431
455
  pub fn prepare(self: Self, source: ID, m: anytype) Error!void {
432
456
  if (!enabled) return;
457
+
433
458
  try self.writer.writeAll(&.{
434
459
  @intFromEnum(ArgType.Prepare),
435
460
  @as(u8, @bitCast(source)),
@@ -439,6 +464,7 @@ pub fn Log(comptime Writer: type) type {
439
464
 
440
465
  pub fn mustrecharge(self: Self, ident: ID) Error!void {
441
466
  if (!enabled) return;
467
+
442
468
  try self.writer.writeAll(&.{
443
469
  @intFromEnum(ArgType.MustRecharge),
444
470
  @as(u8, @bitCast(ident)),
@@ -447,6 +473,7 @@ pub fn Log(comptime Writer: type) type {
447
473
 
448
474
  pub fn activate(self: Self, ident: ID, reason: Activate) Error!void {
449
475
  if (!enabled) return;
476
+
450
477
  try self.writer.writeAll(&.{
451
478
  @intFromEnum(ArgType.Activate),
452
479
  @as(u8, @bitCast(ident)),
@@ -456,11 +483,13 @@ pub fn Log(comptime Writer: type) type {
456
483
 
457
484
  pub fn fieldactivate(self: Self) Error!void {
458
485
  if (!enabled) return;
486
+
459
487
  try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
460
488
  }
461
489
 
462
490
  pub fn start(self: Self, ident: ID, reason: Start) Error!void {
463
491
  if (!enabled) return;
492
+
464
493
  assert(@intFromEnum(reason) < @intFromEnum(Start.TypeChange));
465
494
  try self.writer.writeAll(&.{
466
495
  @intFromEnum(ArgType.Start),
@@ -471,6 +500,7 @@ pub fn Log(comptime Writer: type) type {
471
500
 
472
501
  pub fn typechange(self: Self, source: ID, types: anytype, target: ID) Error!void {
473
502
  if (!enabled) return;
503
+
474
504
  try self.writer.writeAll(&.{
475
505
  @intFromEnum(ArgType.Start),
476
506
  @as(u8, @bitCast(source)),
@@ -482,6 +512,7 @@ pub fn Log(comptime Writer: type) type {
482
512
 
483
513
  pub fn startEffect(self: Self, ident: ID, reason: Start, m: anytype) Error!void {
484
514
  if (!enabled) return;
515
+
485
516
  assert(@intFromEnum(reason) > @intFromEnum(Start.TypeChange));
486
517
  try self.writer.writeAll(&.{
487
518
  @intFromEnum(ArgType.Start),
@@ -493,6 +524,7 @@ pub fn Log(comptime Writer: type) type {
493
524
 
494
525
  pub fn end(self: Self, ident: ID, reason: End) Error!void {
495
526
  if (!enabled) return;
527
+
496
528
  try self.writer.writeAll(&.{
497
529
  @intFromEnum(ArgType.End),
498
530
  @as(u8, @bitCast(ident)),
@@ -502,16 +534,19 @@ pub fn Log(comptime Writer: type) type {
502
534
 
503
535
  pub fn ohko(self: Self) Error!void {
504
536
  if (!enabled) return;
537
+
505
538
  try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
506
539
  }
507
540
 
508
541
  pub fn crit(self: Self, ident: ID) Error!void {
509
542
  if (!enabled) return;
543
+
510
544
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(ident)) });
511
545
  }
512
546
 
513
547
  pub fn supereffective(self: Self, ident: ID) Error!void {
514
548
  if (!enabled) return;
549
+
515
550
  try self.writer.writeAll(&.{
516
551
  @intFromEnum(ArgType.SuperEffective),
517
552
  @as(u8, @bitCast(ident)),
@@ -520,6 +555,7 @@ pub fn Log(comptime Writer: type) type {
520
555
 
521
556
  pub fn resisted(self: Self, ident: ID) Error!void {
522
557
  if (!enabled) return;
558
+
523
559
  try self.writer.writeAll(&.{
524
560
  @intFromEnum(ArgType.Resisted),
525
561
  @as(u8, @bitCast(ident)),
@@ -528,6 +564,7 @@ pub fn Log(comptime Writer: type) type {
528
564
 
529
565
  pub fn immune(self: Self, ident: ID, reason: Immune) Error!void {
530
566
  if (!enabled) return;
567
+
531
568
  try self.writer.writeAll(&.{
532
569
  @intFromEnum(ArgType.Immune),
533
570
  @as(u8, @bitCast(ident)),
@@ -537,6 +574,7 @@ pub fn Log(comptime Writer: type) type {
537
574
 
538
575
  pub fn transform(self: Self, source: ID, target: ID) Error!void {
539
576
  if (!enabled) return;
577
+
540
578
  try self.writer.writeAll(&.{
541
579
  @intFromEnum(ArgType.Transform),
542
580
  @as(u8, @bitCast(source)),
@@ -546,18 +584,24 @@ pub fn Log(comptime Writer: type) type {
546
584
 
547
585
  pub fn laststill(self: Self) Error!void {
548
586
  if (!enabled) return;
587
+
549
588
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastStill)});
550
589
  }
551
590
 
552
591
  pub fn lastmiss(self: Self) Error!void {
553
592
  if (!enabled) return;
593
+
554
594
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastMiss)});
555
595
  }
556
596
  };
557
597
  }
558
598
 
599
+ /// `Log` type backed by the optimized `ByteStream.Writer`.
559
600
  pub const FixedLog = Log(ByteStream.Writer);
560
601
 
602
+ /// Stripped down version of `std.io.FixedBufferStream` optimized for efficiently writing the
603
+ /// individual protocol bytes. Note that the `ByteStream.Writer` is **not** a `std.io.Writer` and
604
+ /// should not be used for general purpose writing.
561
605
  pub const ByteStream = struct {
562
606
  buffer: []u8,
563
607
  pos: usize = 0,
@@ -855,6 +899,7 @@ pub fn expectLog(
855
899
  offset: usize,
856
900
  ) !void {
857
901
  if (!enabled) return;
902
+
858
903
  const color = color: {
859
904
  if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
860
905
  break :color true;
@@ -0,0 +1,353 @@
1
+ const builtin = @import("builtin");
2
+ const std = @import("std");
3
+
4
+ const assert = std.debug.assert;
5
+ const expect = std.testing.expect;
6
+ const expectEqual = std.testing.expectEqual;
7
+ const expectError = std.testing.expectError;
8
+
9
+ /// Specialization of a rational number used by the engine to compute probabilties.
10
+ /// For performance reasons the rational is only reduced lazily and thus `reduce` must be
11
+ /// invoked explicitly before reading.
12
+ pub fn Rational(comptime T: type) type {
13
+ return extern struct {
14
+ const Self = @This();
15
+
16
+ /// Numerator. Must always be >= 1. Not guaranteed to be reduced in all cases.
17
+ p: T = 1,
18
+ /// Denominator. Must always be >= 1. Not guaranteed to be reduced in all cases.
19
+ q: T = 1,
20
+
21
+ // With floats we can't rely on an overflow bit to let us know when to reduce, so we instead
22
+ // start reducing when we get sufficiently close to the limit of the mantissa (in our domain
23
+ // we expect updates to involve numbers < 2**10, so we should be safe not reducing before we
24
+ // are 2**10 away from "overflowing" the mantissa)
25
+ const REDUCE = if (@typeInfo(T) == .Float)
26
+ std.math.pow(T, 2, std.math.floatMantissaBits(T) - 10)
27
+ else
28
+ 0;
29
+
30
+ /// Possible error returned by operations on the Rational.
31
+ pub const Error = switch (@typeInfo(T)) {
32
+ .Int => error{Overflow},
33
+ .Float => error{},
34
+ else => unreachable,
35
+ };
36
+
37
+ /// Resets the rational back to 1.
38
+ pub fn reset(r: *Self) void {
39
+ r.p = 1;
40
+ r.q = 1;
41
+ }
42
+
43
+ /// Update the rational by multiplying its numerator by p and its denominator by q.
44
+ /// Both p and q must be >= 1, and if computable at comptime must have no common factors.
45
+ pub fn update(r: *Self, p: anytype, q: anytype) Error!void {
46
+ // FIXME
47
+ // std.debug.print("({d}/{d}) * ", .{p, q});
48
+ assert(p >= 1);
49
+ assert(q >= 1);
50
+
51
+ // If our parameters are not fully reduced they may prematurely
52
+ // cause overflow/loss of precision after the multiplication below
53
+ assert(switch (@typeInfo(@TypeOf(p, q))) {
54
+ .ComptimeInt, .ComptimeFloat => comptime gcd(p, q),
55
+ else => 1,
56
+ } == 1);
57
+
58
+ switch (@typeInfo(T)) {
59
+ .Int => {
60
+ // Greedily attempt to multiply and if it fails, reduce and try again
61
+ r.multiplication(p, q) catch |err| switch (err) {
62
+ error.Overflow => {
63
+ r.reduce();
64
+ try r.multiplication(p, q);
65
+ },
66
+ else => unreachable,
67
+ };
68
+ },
69
+ .Float => {
70
+ // Reduce in situations where we're likely to start losing precision
71
+ if (r.q > REDUCE or r.p > REDUCE) r.reduce();
72
+
73
+ r.p *= switch (@typeInfo(@TypeOf(p))) {
74
+ .Float, .ComptimeFloat => p,
75
+ else => @floatFromInt(p),
76
+ };
77
+ r.q *= switch (@typeInfo(@TypeOf(q))) {
78
+ .Float, .ComptimeFloat => q,
79
+ else => @floatFromInt(q),
80
+ };
81
+
82
+ // We should always be dealing with whole numbers
83
+ assert(std.math.modf(r.p).fpart == 0);
84
+ assert(std.math.modf(r.q).fpart == 0);
85
+ },
86
+ else => unreachable,
87
+ }
88
+ }
89
+
90
+ /// Add two rationals using the identity (a/b) + (c/d) = (ad+bc)/(bd).
91
+ pub fn add(r: *Self, s: *Self) Error!void {
92
+ switch (@typeInfo(T)) {
93
+ .Int => {
94
+ if (r.q == s.q) {
95
+ r.p = std.math.add(T, r.p, s.p) catch |err| switch (err) {
96
+ error.Overflow => val: {
97
+ r.reduce();
98
+ s.reduce();
99
+ break :val try std.math.add(T, r.p, s.p);
100
+ },
101
+ else => unreachable,
102
+ };
103
+ } else {
104
+ r.addition(s.p, s.q) catch |err| switch (err) {
105
+ error.Overflow => {
106
+ r.reduce();
107
+ s.reduce();
108
+ try r.addition(s.p, s.q);
109
+ },
110
+ else => unreachable,
111
+ };
112
+ }
113
+ },
114
+ .Float => {
115
+ if (r.q == s.q) {
116
+ if (r.p > REDUCE) r.reduce();
117
+ if (s.p > REDUCE) s.reduce();
118
+
119
+ r.p += s.p;
120
+ } else {
121
+ // Always reduce to minimize loss of precision from the multiplications
122
+ r.reduce();
123
+ s.reduce();
124
+
125
+ r.p = (r.p * s.q) + (r.q * s.p);
126
+ r.q *= s.q;
127
+ }
128
+
129
+ assert(std.math.modf(r.p).fpart == 0);
130
+ assert(std.math.modf(r.q).fpart == 0);
131
+ },
132
+ else => unreachable,
133
+ }
134
+ }
135
+
136
+ /// Multiplies two rationals.
137
+ pub fn mul(r: *Self, s: *Self) Error!void {
138
+ switch (@typeInfo(T)) {
139
+ .Int => {
140
+ r.multiplication(s.p, s.q) catch |err| switch (err) {
141
+ error.Overflow => {
142
+ r.reduce();
143
+ s.reduce();
144
+ try r.multiplication(s.p, s.q);
145
+ },
146
+ else => unreachable,
147
+ };
148
+ },
149
+ .Float => {
150
+ if (r.q > REDUCE or r.p > REDUCE) r.reduce();
151
+ if (s.q > REDUCE or s.p > REDUCE) s.reduce();
152
+
153
+ r.p *= s.p;
154
+ r.q *= s.q;
155
+
156
+ assert(std.math.modf(r.p).fpart == 0);
157
+ assert(std.math.modf(r.q).fpart == 0);
158
+ },
159
+ else => unreachable,
160
+ }
161
+ }
162
+
163
+ /// Normalize the rational by reducing by the greatest common divisor.
164
+ pub fn reduce(r: *Self) void {
165
+ const d = gcd(r.p, r.q);
166
+ if (d == 1) return;
167
+
168
+ assert(@mod(r.p, d) == 0);
169
+ assert(@mod(r.q, d) == 0);
170
+
171
+ r.p /= d;
172
+ r.q /= d;
173
+
174
+ assert(r.p >= 1);
175
+ assert(r.q >= 1);
176
+ }
177
+
178
+ pub fn format(
179
+ self: Self,
180
+ comptime fmt: []const u8,
181
+ opts: std.fmt.FormatOptions,
182
+ writer: anytype,
183
+ ) !void {
184
+ _ = .{ fmt, opts };
185
+ try writer.print("{d}/{d}", .{ self.p, self.q });
186
+ }
187
+
188
+ inline fn multiplication(r: *Self, p: anytype, q: anytype) !void {
189
+ r.p = try std.math.mul(T, r.p, p);
190
+ r.q = try std.math.mul(T, r.q, q);
191
+ }
192
+
193
+ inline fn addition(r: *Self, p: anytype, q: anytype) !void {
194
+ // (a/b) + (c/d) = (ad+bc)/(bd)
195
+ var d = try std.math.mul(T, r.q, q);
196
+ var n1 = try std.math.mul(T, r.p, q);
197
+ var n2 = try std.math.mul(T, r.q, p);
198
+
199
+ r.p = try std.math.add(T, n1, n2);
200
+ r.q = d;
201
+ }
202
+ };
203
+ }
204
+
205
+ // https://en.wikipedia.org/wiki/Euclidean_algorithm
206
+ inline fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
207
+ assert(p >= 1);
208
+ assert(q >= 1);
209
+
210
+ var a = p;
211
+ var b = q;
212
+ var c: @TypeOf(p, q) = undefined;
213
+
214
+ while (b != 0) {
215
+ c = b;
216
+ b = @mod(a, b);
217
+ a = c;
218
+ }
219
+
220
+ assert(a > 0);
221
+ return a;
222
+ }
223
+
224
+ fn doTurn(r: anytype) !void {
225
+ try r.update(1, 163); // Metronome
226
+ try r.update(33, 256); // Critical Hit (67 Speed)
227
+ try r.update(1, 39); // Damage roll
228
+ try r.update(89, 128); // Thunder accuracy (178/256)
229
+ try r.update(77, 256); // Thunder secondary proc
230
+ }
231
+
232
+ test Rational {
233
+ inline for (.{ u64, u128, f64 }) |t| {
234
+ var r: Rational(t) = .{};
235
+
236
+ var c: t = 128;
237
+ try r.update(c, 256);
238
+ try doTurn(&r);
239
+
240
+ r.reduce();
241
+ try expectEqual(Rational(t){ .p = 75383, .q = 35550920704 }, r);
242
+
243
+ try r.update(1, 4);
244
+ if (t == u64) {
245
+ try expectError(error.Overflow, doTurn(&r));
246
+ } else {
247
+ try doTurn(&r);
248
+ r.reduce();
249
+ try expectEqual(Rational(t){ .p = 5682596689, .q = 2527735925804191711232 }, r);
250
+ }
251
+
252
+ r.reset();
253
+
254
+ var s = Rational(t){ .p = 10, .q = 13 };
255
+ try r.mul(&s);
256
+ s = Rational(t){ .p = 3, .q = 4 };
257
+ try r.mul(&s);
258
+ try expectEqual(Rational(t){ .p = 30, .q = 52 }, r);
259
+
260
+ s = Rational(t){ .p = 1, .q = 3 };
261
+ try r.add(&s);
262
+ r.reduce();
263
+ try expectEqual(Rational(t){ .p = 71, .q = 78 }, r);
264
+ }
265
+ }
266
+
267
+ /// Adapter of std.math.big.Rational to the engines Rational interface.
268
+ pub const BigRational = struct {
269
+ /// The underlying rational number.
270
+ val: std.math.big.Rational,
271
+
272
+ /// Possible error returned by operations on the rational.
273
+ pub const Error = error{OutOfMemory};
274
+
275
+ /// Create a new BigRational wrapper. A small amount of memory will be allocated on
276
+ /// initialization. Not default initialized to 1 - you must explicitly `reset` first.
277
+ pub fn init(alloc: std.mem.Allocator) !BigRational {
278
+ return BigRational{ .val = try std.math.big.Rational.init(alloc) };
279
+ }
280
+
281
+ /// Frees all memory associated with a rational.
282
+ pub fn deinit(r: *BigRational) void {
283
+ r.val.deinit();
284
+ }
285
+
286
+ /// Resets the rational back to 1.
287
+ pub fn reset(r: *BigRational) !void {
288
+ try r.val.setInt(1);
289
+ }
290
+
291
+ /// Update the rational by multiplying its numerator by p and its denominator by q.
292
+ /// Both p and q must be >= 1.
293
+ pub fn update(r: *BigRational, p: anytype, q: anytype) !void {
294
+ assert(p >= 1);
295
+ assert(q >= 1);
296
+
297
+ var s = try std.math.big.Rational.init(r.val.p.allocator);
298
+ defer s.deinit();
299
+ try s.setRatio(p, q);
300
+ try r.val.mul(r.val, s);
301
+ }
302
+
303
+ /// Adds two rationals.
304
+ pub fn add(r: *BigRational, s: *const BigRational) !void {
305
+ try r.val.add(r.val, s.val);
306
+ }
307
+
308
+ /// Multiplies two rationals.
309
+ pub fn mul(r: *BigRational, s: *const BigRational) !void {
310
+ try r.val.mul(r.val, s.val);
311
+ }
312
+ };
313
+
314
+ test BigRational {
315
+ var r = try BigRational.init(std.testing.allocator);
316
+ defer r.deinit();
317
+ var s = try std.math.big.Rational.init(std.testing.allocator);
318
+ defer s.deinit();
319
+
320
+ try r.reset();
321
+
322
+ var c: u8 = 128;
323
+ try r.update(c, 256);
324
+ try doTurn(&r);
325
+
326
+ try s.setRatio(75383, 35550920704);
327
+ try expect((try s.order(r.val)) == .eq);
328
+
329
+ try r.update(1, 4);
330
+ try doTurn(&r);
331
+
332
+ try s.setRatio(5682596689, 2527735925804191711232);
333
+ try expect((try s.order(r.val)) == .eq);
334
+
335
+ try r.reset();
336
+
337
+ var t = try BigRational.init(std.testing.allocator);
338
+ defer t.deinit();
339
+
340
+ try t.val.setRatio(10, 13);
341
+ try r.mul(&t);
342
+ try t.val.setRatio(3, 4);
343
+ try r.mul(&t);
344
+
345
+ try s.setRatio(15, 26);
346
+ try expect((try s.order(r.val)) == .eq);
347
+
348
+ try t.val.setRatio(1, 3);
349
+ try r.add(&t);
350
+
351
+ try s.setRatio(71, 78);
352
+ try expect((try s.order(r.val)) == .eq);
353
+ }
@@ -72,11 +72,11 @@ test PSRNG {
72
72
  // https://pkmn.cc/pokered/engine/battle/core.asm#L6644-L6693
73
73
  // https://pkmn.cc/pokecrystal/engine/battle/core.asm#L6922-L6938
74
74
  pub const Gen12 = extern struct {
75
- seed: [10]u8,
75
+ seed: [9]u8,
76
76
  index: u8 = 0,
77
77
 
78
78
  comptime {
79
- assert(@sizeOf(Gen12) == 11);
79
+ assert(@sizeOf(Gen12) == 10);
80
80
  }
81
81
 
82
82
  pub fn percent(p: u8) u8 {
@@ -86,14 +86,14 @@ pub const Gen12 = extern struct {
86
86
  pub fn next(self: *Gen12) u8 {
87
87
  const val = 5 *% self.seed[self.index] +% 1;
88
88
  self.seed[self.index] = val;
89
- self.index = (self.index + 1) % 10;
89
+ self.index = (self.index + 1) % 9;
90
90
  return val;
91
91
  }
92
92
  };
93
93
 
94
94
  test Gen12 {
95
- const expected = [_]u8{ 6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 31, 56, 81 };
96
- var rng = Gen12{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } };
95
+ const expected = [_]u8{ 6, 11, 16, 21, 26, 31, 36, 41, 46, 31, 56, 81 };
96
+ var rng = Gen12{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
97
97
  for (expected) |e| {
98
98
  try expectEqual(e, rng.next());
99
99
  }
@@ -0,0 +1,12 @@
1
+ const std = @import("std");
2
+
3
+ const expectEqual = std.testing.expectEqual;
4
+
5
+ pub fn PointerType(comptime P: type, comptime C: type) type {
6
+ return if (@typeInfo(P).Pointer.is_const) *const C else *C;
7
+ }
8
+
9
+ test PointerType {
10
+ try expectEqual(*bool, PointerType(*u8, bool));
11
+ try expectEqual(*const f64, PointerType(*const i32, f64));
12
+ }