move-by-move 1.0.6 → 2.0.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.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # move-by-move
2
+
3
+ A simple, dependency-free game engine for turn-based games.
4
+
5
+ `move-by-move` gives you a tiny state container in the spirit of Redux, but built around **rules** instead of reducers. Every move runs through a pipeline of rules, each of which sees both the action and the current state and can transform either. This makes it natural to express turn-based game logic — validation, the move itself, win checks, turn switching — as a sequence of small, composable steps.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install move-by-move
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```ts
16
+ import createGame from 'move-by-move'
17
+
18
+ type State = { count: number }
19
+
20
+ const game = createGame<State>(
21
+ [
22
+ // each rule receives [action, state] and returns [action, state]
23
+ ([action, state]) => {
24
+ if (action.type === 'INCREMENT') {
25
+ return [action, { count: state.count + action.by }]
26
+ }
27
+ return [action, state]
28
+ },
29
+ ],
30
+ { count: 0 }, // initial state
31
+ )
32
+
33
+ const unsubscribe = game.subscribe(() => console.log('state:', game.getState()))
34
+
35
+ game.move({ type: 'INCREMENT', by: 1 }) // logs: state: { count: 1 }
36
+ game.move({ type: 'INCREMENT', by: 5 }) // logs: state: { count: 6 }
37
+
38
+ unsubscribe() // stop listening
39
+ ```
40
+
41
+ ## Core concept: rules
42
+
43
+ A **rule** is a function over a `[action, state]` tuple:
44
+
45
+ ```ts
46
+ type RuleIO<State> = [action: GameAction, state: State]
47
+ type Rule<State> = (input: RuleIO<State>) => RuleIO<State>
48
+ ```
49
+
50
+ When you call `game.move(action)`, the engine pipes `[action, state]` through every rule **in order** and uses the resulting state as the new state:
51
+
52
+ ```
53
+ [action, state] → rule1 → rule2 → rule3 → [action, newState]
54
+ └── becomes the new state
55
+ ```
56
+
57
+ Because each rule receives both the action and the (possibly already-transformed) state, rules can:
58
+
59
+ - **transform state** based on the action,
60
+ - **ignore** actions they don't care about (just return the input unchanged),
61
+ - **rewrite the action** for downstream rules,
62
+ - **run unconditionally**, e.g. a rule that switches the active player or checks for a winner after every move.
63
+
64
+ Order matters — a validation rule should run before the rule that applies the move, and a "check for win" rule should run after it.
65
+
66
+ ### Example: composing rules
67
+
68
+ ```ts
69
+ import createGame from 'move-by-move'
70
+
71
+ type State = {
72
+ board: (string | null)[]
73
+ turn: 'X' | 'O'
74
+ winner: string | null
75
+ }
76
+
77
+ const placeMark = ([action, state]: RuleIO<State>): RuleIO<State> => {
78
+ if (action.type !== 'PLACE' || state.winner) return [action, state]
79
+ if (state.board[action.index] !== null) return [action, state] // square taken
80
+ const board = [...state.board]
81
+ board[action.index] = state.turn
82
+ return [action, { ...state, board }]
83
+ }
84
+
85
+ const switchTurn = ([action, state]: RuleIO<State>): RuleIO<State> => {
86
+ if (action.type !== 'PLACE' || state.winner) return [action, state]
87
+ return [action, { ...state, turn: state.turn === 'X' ? 'O' : 'X' }]
88
+ }
89
+
90
+ const game = createGame<State>(
91
+ [placeMark, switchTurn],
92
+ { board: Array(9).fill(null), turn: 'X', winner: null },
93
+ )
94
+ ```
95
+
96
+ ## API
97
+
98
+ ### `createGame<State>(rules, initialState): Game<State>`
99
+
100
+ Creates a new game instance.
101
+
102
+ - `rules` — an array of rule functions, applied in order on every move.
103
+ - `initialState` — the starting state.
104
+
105
+ ### `Game<State>`
106
+
107
+ | Method | Description |
108
+ | --- | --- |
109
+ | `getState(): State` | Returns the current state. |
110
+ | `move(action: GameAction): void` | Runs `action` through the rule pipeline, updates state, and notifies subscribers. |
111
+ | `subscribe(listener: Function): () => void` | Registers a listener called (with no arguments) after every move. Use `getState()` inside it to read the new state. Returns an unsubscribe function — call it to remove the listener. |
112
+
113
+ ### `GameAction<Payload>`
114
+
115
+ An action is any payload object with a required `type` string:
116
+
117
+ ```ts
118
+ type GameAction<Payload = any> = Payload & { type: string }
119
+ ```
120
+
121
+ ## License
122
+
123
+ MIT © Andreas Riedmüller
package/lib/index.d.ts CHANGED
@@ -2,8 +2,7 @@ export declare type GameAction<Payload = any> = Payload & {
2
2
  type: string;
3
3
  };
4
4
  export interface Game<Type> {
5
- subscribe(listener: Function): void;
6
- unsubscribe(listener: Function): void;
5
+ subscribe(listener: Function): () => void;
7
6
  getState(): Type;
8
7
  move(action: GameAction): void;
9
8
  }
package/lib/index.js CHANGED
@@ -4,12 +4,13 @@ const helpers_1 = require("./helpers");
4
4
  function createGame(rules, initialGameState) {
5
5
  let listeners = [];
6
6
  let state = initialGameState;
7
- const subscribe = (listener) => {
8
- listeners = [...listeners, listener];
9
- };
10
7
  const unsubscribe = (listener) => {
11
8
  listeners = listeners.filter(item => item !== listener);
12
9
  };
10
+ const subscribe = (listener) => {
11
+ listeners = [...listeners, listener];
12
+ return () => unsubscribe(listener);
13
+ };
13
14
  const getState = () => {
14
15
  return state;
15
16
  };
@@ -22,7 +23,6 @@ function createGame(rules, initialGameState) {
22
23
  };
23
24
  return {
24
25
  subscribe,
25
- unsubscribe,
26
26
  getState,
27
27
  move,
28
28
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "move-by-move",
3
- "version": "1.0.6",
3
+ "version": "2.0.0",
4
4
  "description": "Simple game engine for turn based games",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",