create-eigen-game 0.14.2 → 0.15.0

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.
@@ -0,0 +1,115 @@
1
+ import 'package:eigen_flutter/eigen_flutter.dart';
2
+
3
+ import '../generated/payloads.dart';
4
+
5
+ /// The offline half of version 1: the same four hooks the Worker owns,
6
+ /// transcribed so the game can be played on the device with no network.
7
+ ///
8
+ /// Entirely optional. Delete this file and the `local` override in
9
+ /// `rules.dart` and the game is unchanged, minus on-device play. What it buys
10
+ /// is a match a player can start on a train: the device commits every move
11
+ /// through these hooks, and the server replays the whole log through the
12
+ /// TypeScript unit when the device is next online, which is why the two must
13
+ /// agree move for move. The shared fixtures under `test/game/fixtures/` are
14
+ /// what keeps them agreeing.
15
+ ///
16
+ /// Read it beside `server/src/module/v1.ts`; the hook and parameter names are
17
+ /// deliberately identical.
18
+ class ExampleGameLocalRulesV1 extends ExampleGameV1LocalRulesBase {
19
+ const ExampleGameLocalRulesV1();
20
+
21
+ @override
22
+ Envelope<ExampleGameV1State> initialState({
23
+ required ExampleGameV1Config config,
24
+ required Rng rng,
25
+ required int playerCount,
26
+ }) => Envelope(
27
+ state: ExampleGameV1State(count: 0),
28
+ pendingPlayers: const [0],
29
+ );
30
+
31
+ @override
32
+ Envelope<ExampleGameV1State> applyAction({
33
+ required ExampleGameV1State state,
34
+ required List<int> pending,
35
+ required ExampleGameV1Action data,
36
+ required int playerIndex,
37
+ required Rng rng,
38
+ required ExampleGameV1Config config,
39
+ }) {
40
+ final count = state.count + data.amount;
41
+ final finished = count >= config.target;
42
+ return Envelope(
43
+ state: ExampleGameV1State(count: count),
44
+ pendingPlayers: finished ? const [] : [1 - playerIndex],
45
+ outcome: finished ? _outcome(playerIndex) : null,
46
+ );
47
+ }
48
+
49
+ @override
50
+ Envelope<ExampleGameV1State> applyLifecycle({
51
+ required ExampleGameV1State state,
52
+ required List<int> pending,
53
+ required LifecycleType type,
54
+ required LifecycleAction data,
55
+ required Rng rng,
56
+ required ExampleGameV1Config config,
57
+ }) {
58
+ final loser = type == LifecycleType.timeout
59
+ ? (pending.isEmpty ? 0 : pending.first)
60
+ : data.playerIndex!;
61
+ return Envelope(
62
+ state: state,
63
+ pendingPlayers: const [],
64
+ outcome: _outcome(1 - loser),
65
+ );
66
+ }
67
+
68
+ @override
69
+ ObservationSlice<ExampleGameV1Observation> computeObservation({
70
+ required ExampleGameV1State state,
71
+ required List<int> pending,
72
+ required int? playerIndex,
73
+ required int participantCount,
74
+ required TransitionCause<ExampleGameV1Action> cause,
75
+ required bool isReplay,
76
+ required ExampleGameV1Config config,
77
+ }) => ObservationSlice(
78
+ data: ExampleGameV1Observation(count: state.count),
79
+ pendingPlayers: pending,
80
+ );
81
+
82
+ /// The brains this build can run, keyed by the bot registry's `username`.
83
+ ///
84
+ /// An operator registers a row with this username and type `local`, and it
85
+ /// becomes an opponent in the solo picker's untimed mode. A username that
86
+ /// also has a TypeScript `botActions` entry is one bot playable both on the
87
+ /// device and on the server.
88
+ @override
89
+ Map<
90
+ String,
91
+ LocalBotAction<
92
+ ExampleGameV1Action,
93
+ ExampleGameV1Observation,
94
+ ExampleGameV1Config
95
+ >
96
+ >
97
+ get botActions => {
98
+ 'example-bot': (args) => ExampleGameV1Action(amount: 1),
99
+ };
100
+ }
101
+
102
+ List<Outcome> _outcome(int winner) => [
103
+ Outcome(
104
+ playerIndex: winner,
105
+ result: OutcomeResultEnum.win,
106
+ placement: 1,
107
+ teamIndex: winner,
108
+ ),
109
+ Outcome(
110
+ playerIndex: 1 - winner,
111
+ result: OutcomeResultEnum.loss,
112
+ placement: 2,
113
+ teamIndex: 1 - winner,
114
+ ),
115
+ ];
@@ -2,6 +2,7 @@ import 'package:eigen_flutter/eigen_flutter.dart';
2
2
  import 'package:flutter/material.dart';
3
3
 
4
4
  import '../generated/payloads.dart';
5
+ import 'local_rules.dart';
5
6
 
6
7
  class ExampleGameRulesV1 extends ExampleGameV1RulesBase {
7
8
  const ExampleGameRulesV1();
@@ -40,6 +41,11 @@ class ExampleGameRulesV1 extends ExampleGameV1RulesBase {
40
41
 
41
42
  @override
42
43
  bool botSeatable(BotSeatableArgs args) => true;
44
+
45
+ /// Offline play, opt-in. Remove this override and `local_rules.dart` if the
46
+ /// game should only ever be played against the server.
47
+ @override
48
+ ExampleGameLocalRulesV1 get local => const ExampleGameLocalRulesV1();
43
49
  }
44
50
 
45
51
  class _CounterBoard extends StatelessWidget {
@@ -0,0 +1,11 @@
1
+ # Web assets
2
+
3
+ `sqlite3.wasm` and `drift_worker.js` are drift's web runtime, taken verbatim
4
+ from the [drift 2.35.0
5
+ release](https://github.com/simolus3/drift/releases/tag/drift-2.35.0). They are
6
+ served from the app's own origin because that is where `LocalDatabase` looks for
7
+ them, and they are what gives a browser the storage offline play needs.
8
+
9
+ Replace both together whenever the `drift` constraint in `eigen_flutter`'s
10
+ pubspec moves, and take them from the matching release: the worker and the
11
+ module are one pair.