@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.
- package/README.md +39 -36
- package/build/pkg/addon.js +3 -2
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +24 -2
- package/build/pkg/gen1.js +13 -16
- package/build/pkg/gen1.js.map +1 -1
- package/build.zig +29 -13
- package/package.json +23 -14
- package/src/bin/install-pkmn-engine +9 -4
- package/src/lib/binding/node.zig +39 -131
- package/src/lib/binding/wasm.zig +2 -0
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/debug.zig +20 -11
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +60 -0
- package/src/lib/common/options.zig +9 -0
- package/src/lib/common/protocol.zig +45 -0
- package/src/lib/common/rational.zig +353 -0
- package/src/lib/common/rng.zig +5 -5
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/README.md +133 -135
- package/src/lib/gen1/calc.zig +427 -0
- package/src/lib/gen1/chance.zig +1044 -0
- package/src/lib/gen1/data.zig +35 -19
- package/src/lib/gen1/helpers.zig +428 -10
- package/src/lib/gen1/mechanics.zig +710 -414
- package/src/lib/gen2/README.md +12 -13
- package/src/lib/gen2/data.zig +22 -12
- package/src/lib/gen2/helpers.zig +3 -5
- package/src/lib/gen2/mechanics.zig +5 -4
- package/src/lib/pkmn.zig +61 -2
|
@@ -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
|
+
}
|
package/src/lib/common/rng.zig
CHANGED
|
@@ -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: [
|
|
75
|
+
seed: [9]u8,
|
|
76
76
|
index: u8 = 0,
|
|
77
77
|
|
|
78
78
|
comptime {
|
|
79
|
-
assert(@sizeOf(Gen12) ==
|
|
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) %
|
|
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,
|
|
96
|
-
var rng = Gen12{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
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
|
+
}
|